1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use anyhow::Context;
use fslock::{
    IntoOsString,
    LockFile,
    ToOsStr,
};
use std::sync::Arc;

/// An async `LockFile`.
///
/// Implemented by blocking on the tokio threadpool
#[derive(Debug, Clone)]
pub struct AsyncLockFile {
    file: Arc<tokio::sync::Mutex<LockFile>>,
}

impl AsyncLockFile {
    /// Open a file for locking
    pub async fn open<P>(path: P) -> anyhow::Result<Self>
    where
        P: IntoOsString,
    {
        let path = path.into_os_string()?;
        tokio::task::spawn_blocking(move || Self::blocking_open(&path))
            .await
            .context("failed to join task")?
    }

    /// Open a file for locking in a blocking manner
    pub fn blocking_open<P>(path: &P) -> anyhow::Result<Self>
    where
        P: ToOsStr + ?Sized,
    {
        let file = LockFile::open(path)?;
        Ok(Self {
            file: Arc::new(tokio::sync::Mutex::new(file)),
        })
    }

    /// Lock the file
    pub async fn lock(&self) -> anyhow::Result<()> {
        let mut file = self.file.clone().lock_owned().await;
        Ok(tokio::task::spawn_blocking(move || file.lock())
            .await
            .context("failed to join task")??)
    }

    /// Lock the file, writing the PID to it
    pub async fn lock_with_pid(&self) -> anyhow::Result<()> {
        let mut file = self.file.clone().lock_owned().await;
        Ok(tokio::task::spawn_blocking(move || file.lock_with_pid())
            .await
            .context("failed to join task")??)
    }

    /// Try to lock the file, returning `true` if successful.
    pub async fn try_lock(&self) -> anyhow::Result<bool> {
        let mut file = self.file.clone().lock_owned().await;
        Ok(tokio::task::spawn_blocking(move || file.try_lock())
            .await
            .context("failed to join task")??)
    }

    /// Try to lock a file with a pid, returning `true` if successful.
    pub async fn try_lock_with_pid(&self) -> anyhow::Result<bool> {
        let mut file = self.file.clone().lock_owned().await;
        Ok(
            tokio::task::spawn_blocking(move || file.try_lock_with_pid())
                .await
                .context("failed to join task")??,
        )
    }

    /// Try to lock a file with a pid, returning `true` if successful in a blocking manner.
    pub fn try_lock_with_pid_blocking(&self) -> anyhow::Result<bool> {
        Ok(self.file.blocking_lock().try_lock_with_pid()?)
    }

    /// Returns `true` if this owns the lock
    pub async fn owns_lock(&self) -> anyhow::Result<bool> {
        let file = self.file.clone().lock_owned().await;
        tokio::task::spawn_blocking(move || file.owns_lock())
            .await
            .context("failed to join task")
    }

    /// Unlock the file
    pub async fn unlock(&self) -> anyhow::Result<()> {
        let mut file = self.file.clone().lock_owned().await;
        Ok(tokio::task::spawn_blocking(move || file.unlock())
            .await
            .context("failed to join task")??)
    }

    /// Unlock the file in a blocking manner
    pub fn blocking_unlock(&self) -> anyhow::Result<()> {
        Ok(self.file.blocking_lock().unlock()?)
    }
}