Skip to main content

agent_first_pay/store/
lock.rs

1use fs2::FileExt;
2use std::fs::File;
3use std::path::Path;
4use std::time::{Duration, Instant};
5
6/// Default lock timeout: 5 seconds.
7const DEFAULT_TIMEOUT_MS: u64 = 5000;
8/// Retry interval when waiting for the lock.
9const RETRY_INTERVAL_MS: u64 = 50;
10
11/// RAII guard for a data-directory exclusive lock.
12/// The lock is released when this value is dropped.
13#[derive(Debug)]
14pub struct DataLock {
15    _file: File,
16}
17
18/// Try to acquire an exclusive lock on `{data_dir}/afpay.lock` with a timeout.
19/// Retries with short intervals until the timeout expires.
20pub fn acquire(data_dir: &str, timeout_ms: Option<u64>) -> Result<DataLock, String> {
21    let timeout = Duration::from_millis(timeout_ms.unwrap_or(DEFAULT_TIMEOUT_MS));
22
23    let dir = Path::new(data_dir);
24    std::fs::create_dir_all(dir)
25        .map_err(|e| format!("cannot create data directory {data_dir}: {e}"))?;
26
27    let lock_path = dir.join("afpay.lock");
28    let file = File::create(&lock_path)
29        .map_err(|e| format!("cannot create lock file {}: {e}", lock_path.display()))?;
30
31    let start = Instant::now();
32    loop {
33        match file.try_lock_exclusive() {
34            Ok(()) => return Ok(DataLock { _file: file }),
35            Err(_) => {
36                if start.elapsed() >= timeout {
37                    return Err(format!(
38                        "timeout acquiring lock on {data_dir} after {}ms; another operation may be in progress",
39                        timeout.as_millis()
40                    ));
41                }
42                std::thread::sleep(Duration::from_millis(RETRY_INTERVAL_MS));
43            }
44        }
45    }
46}