1use crate::infra::error::LockError;
4use fd_lock::{RwLock, RwLockWriteGuard};
5use std::fs::{self, File, OpenOptions};
6use std::path::{Path, PathBuf};
7use std::time::{Duration, Instant};
8
9pub struct SlotLock {
11 path: PathBuf,
12 guard: Option<RwLockWriteGuard<'static, File>>,
15 raw_lock: *mut RwLock<File>,
16}
17
18impl SlotLock {
19 pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
20 pub const POLL_INTERVAL: Duration = Duration::from_millis(50);
21
22 pub fn acquire(lock_path: &Path) -> Result<Self, LockError> {
24 Self::acquire_with_timeout(lock_path, Self::DEFAULT_TIMEOUT, Self::POLL_INTERVAL)
25 }
26
27 pub fn acquire_with_timeout(
29 lock_path: &Path,
30 timeout: Duration,
31 poll_interval: Duration,
32 ) -> Result<Self, LockError> {
33 if let Some(parent) = lock_path.parent() {
34 fs::create_dir_all(parent).map_err(|e| LockError::IoError {
35 path: lock_path.to_string_lossy().to_string(),
36 message: e.to_string(),
37 })?;
38 }
39
40 let file = OpenOptions::new()
41 .read(true)
42 .write(true)
43 .create(true)
44 .truncate(false)
45 .open(lock_path)
46 .map_err(|e| LockError::IoError {
47 path: lock_path.to_string_lossy().to_string(),
48 message: e.to_string(),
49 })?;
50
51 let raw_lock = Box::into_raw(Box::new(RwLock::new(file)));
52 let start = Instant::now();
53
54 loop {
55 let rwlock_ref: &'static mut RwLock<File> = unsafe { &mut *raw_lock };
57
58 match rwlock_ref.try_write() {
59 Ok(guard) => {
60 return Ok(SlotLock {
61 path: lock_path.to_path_buf(),
62 guard: Some(guard),
63 raw_lock,
64 });
65 }
66 Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
67 if start.elapsed() >= timeout {
68 unsafe {
69 let _ = Box::from_raw(raw_lock);
70 }
71 return Err(LockError::Timeout {
72 path: lock_path.to_string_lossy().to_string(),
73 timeout_secs: timeout.as_secs(),
74 });
75 }
76 std::thread::sleep(poll_interval);
77 }
78 Err(err) => {
79 unsafe {
80 let _ = Box::from_raw(raw_lock);
81 }
82 return Err(LockError::IoError {
83 path: lock_path.to_string_lossy().to_string(),
84 message: err.to_string(),
85 });
86 }
87 }
88 }
89 }
90
91 pub fn path(&self) -> &Path {
93 &self.path
94 }
95}
96
97impl Drop for SlotLock {
98 fn drop(&mut self) {
99 self.guard.take();
101 if !self.raw_lock.is_null() {
103 unsafe {
104 let _ = Box::from_raw(self.raw_lock);
105 }
106 self.raw_lock = std::ptr::null_mut();
107 }
108 }
109}
110
111unsafe impl Send for SlotLock {}