distributed_lock_file/
handle.rs

1//! File lock handle implementation.
2
3use std::path::PathBuf;
4use std::sync::Arc;
5use tokio::sync::watch;
6use tracing::instrument;
7
8use distributed_lock_core::error::{LockError, LockResult};
9use distributed_lock_core::traits::LockHandle;
10
11use fd_lock::{RwLock, RwLockWriteGuard};
12
13/// Handle for a held file lock.
14///
15/// Dropping this handle releases the lock and deletes the lock file.
16pub struct FileLockHandle {
17    /// The locked file guard (released on drop).
18    /// We use a custom wrapper to manage the lifetime.
19    /// Wrapped in ManuallyDrop so we can move it out in release().
20    inner: std::mem::ManuallyDrop<LockGuard>,
21    /// Path to the lock file (for cleanup).
22    path: PathBuf,
23    /// Watch channel for lock lost detection (not supported for file locks).
24    #[allow(dead_code)]
25    lost_sender: Arc<watch::Sender<bool>>,
26    lost_receiver: watch::Receiver<bool>,
27}
28
29/// Internal wrapper that holds both the RwLock and its guard.
30/// This allows us to keep the guard alive while still being able to move the handle.
31struct LockGuard {
32    _lock_file: Arc<RwLock<std::fs::File>>,
33    _guard: std::mem::ManuallyDrop<RwLockWriteGuard<'static, std::fs::File>>,
34}
35
36unsafe impl Send for LockGuard {}
37unsafe impl Sync for LockGuard {}
38
39impl LockGuard {
40    fn new(lock_file: RwLock<std::fs::File>) -> LockResult<Self> {
41        // Wrap in Arc first
42        let lock_file_arc = Arc::new(lock_file);
43
44        // Now acquire the lock guard by getting a mutable reference to the Arc's inner RwLock
45        // This is safe because:
46        // 1. We just created the Arc, so we're the only owner
47        // 2. We'll keep the Arc alive for the lifetime of the guard
48        let guard = unsafe {
49            // Get a raw pointer to the RwLock inside the Arc
50            let rwlock_ptr = Arc::as_ptr(&lock_file_arc) as *mut RwLock<std::fs::File>;
51            // Try to acquire the lock
52            (*rwlock_ptr).try_write()
53        }
54        .map_err(|_| {
55            LockError::Backend(Box::new(std::io::Error::new(
56                std::io::ErrorKind::WouldBlock,
57                "lock already held",
58            )))
59        })?;
60
61        // Extend the guard's lifetime to 'static using unsafe
62        // This is safe because:
63        // 1. The guard borrows from lock_file_arc
64        // 2. We keep lock_file_arc alive for the entire lifetime of LockGuard
65        // 3. LockGuard is dropped before lock_file_arc, so the guard is dropped first
66        let guard_box = Box::new(guard);
67        let guard_ptr = Box::into_raw(guard_box);
68        let guard = unsafe { *Box::from_raw(guard_ptr) };
69        let guard = std::mem::ManuallyDrop::new(guard);
70
71        Ok(Self {
72            _lock_file: lock_file_arc,
73            _guard: guard,
74        })
75    }
76}
77
78impl Drop for LockGuard {
79    fn drop(&mut self) {
80        // Manually drop the guard to release the lock
81        unsafe {
82            std::mem::ManuallyDrop::drop(&mut self._guard);
83        }
84    }
85}
86
87impl FileLockHandle {
88    /// Creates a new file lock handle by acquiring the lock.
89    pub(crate) fn try_new(lock_file: RwLock<std::fs::File>, path: PathBuf) -> LockResult<Self> {
90        let inner = LockGuard::new(lock_file)?;
91        let (sender, receiver) = watch::channel(false);
92        Ok(Self {
93            inner: std::mem::ManuallyDrop::new(inner),
94            path,
95            lost_sender: Arc::new(sender),
96            lost_receiver: receiver,
97        })
98    }
99}
100
101impl LockHandle for FileLockHandle {
102    fn lost_token(&self) -> &watch::Receiver<bool> {
103        &self.lost_receiver
104    }
105
106    #[instrument(skip(self), fields(lock.path = %self.path.display(), backend = "file"))]
107    async fn release(self) -> LockResult<()> {
108        // Wrap self in ManuallyDrop to prevent its Drop implementation from running
109        let mut this = std::mem::ManuallyDrop::new(self);
110
111        // Release the lock by manually dropping the inner guard
112        unsafe {
113            std::mem::ManuallyDrop::drop(&mut this.inner);
114        }
115
116        // Try to delete the lock file (best effort)
117        let _ = std::fs::remove_file(&this.path);
118
119        Ok(())
120    }
121}
122
123impl Drop for FileLockHandle {
124    fn drop(&mut self) {
125        // Release the lock by manually dropping the inner guard
126        unsafe {
127            std::mem::ManuallyDrop::drop(&mut self.inner);
128        }
129
130        // Try to delete the lock file (best effort)
131        let _ = std::fs::remove_file(&self.path);
132    }
133}