Skip to main content

git_workflow/pool/
lock.rs

1//! File-based locking for the worktree pool using flock (via fs2)
2
3use std::fs::{self, File};
4use std::path::{Path, PathBuf};
5use std::thread;
6use std::time::{Duration, Instant};
7
8use fs2::FileExt;
9
10use crate::error::{GwError, Result};
11
12/// Default lock timeout
13const LOCK_TIMEOUT: Duration = Duration::from_secs(10);
14
15/// Retry interval between lock attempts
16const LOCK_RETRY_INTERVAL: Duration = Duration::from_millis(50);
17
18/// A held file lock on the pool
19pub struct PoolLock {
20    _file: File,
21    _path: PathBuf,
22}
23
24impl PoolLock {
25    /// Acquire an exclusive lock on the pool directory with retry.
26    /// Creates the pool directory and lock file if they don't exist.
27    /// Retries for up to 10 seconds before returning PoolLockTimeout.
28    pub fn acquire(pool_dir: &Path) -> Result<Self> {
29        Self::acquire_with_timeout(pool_dir, LOCK_TIMEOUT)
30    }
31
32    /// Acquire an exclusive lock with a custom timeout.
33    pub fn acquire_with_timeout(pool_dir: &Path, timeout: Duration) -> Result<Self> {
34        fs::create_dir_all(pool_dir)?;
35        let lock_path = pool_dir.join("pool.lock");
36        let file = File::create(&lock_path)?;
37
38        let start = Instant::now();
39        loop {
40            match file.try_lock_exclusive() {
41                Ok(()) => {
42                    return Ok(Self {
43                        _file: file,
44                        _path: lock_path,
45                    });
46                }
47                Err(_) if start.elapsed() < timeout => {
48                    thread::sleep(LOCK_RETRY_INTERVAL);
49                }
50                Err(_) => {
51                    return Err(GwError::PoolLockTimeout);
52                }
53            }
54        }
55    }
56}
57
58// Lock is released when PoolLock is dropped (File close releases flock)