filelock 0.7.0

Cross-platform file locking library for Rust
Documentation
mod common;

use common::TestDir;
use filelock::FileLock;
use std::io::{BufRead, BufReader, Write};
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

const CHILD_MODE: &str = "FILELOCK_PROCESS_TEST_CHILD";
const LOCK_PATH: &str = "FILELOCK_PROCESS_TEST_PATH";

#[test]
fn test_exclusive_lock_across_processes() {
    if std::env::var_os(CHILD_MODE).is_some() {
        run_holder_process();
        return;
    }

    let test_dir = TestDir::new("process");
    let path = test_dir.path("test.lock");
    let mut holder = Command::new(std::env::current_exe().unwrap())
        .args([
            "--exact",
            "test_exclusive_lock_across_processes",
            "--nocapture",
        ])
        .env(CHILD_MODE, "1")
        .env(LOCK_PATH, &path)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    let stdout = holder.stdout.take().unwrap();
    let (locked_tx, locked_rx) = mpsc::channel();
    let output_reader = thread::spawn(move || {
        for line in BufReader::new(stdout).lines() {
            if line.unwrap() == "LOCKED" {
                let _ = locked_tx.send(());
            }
        }
    });

    if locked_rx.recv_timeout(Duration::from_secs(5)).is_err() {
        let _ = holder.kill();
        let _ = holder.wait();
        output_reader.join().unwrap();
        panic!("holder process did not acquire the file lock");
    }

    let mut contender = FileLock::new(&path).unwrap();
    let unavailable_while_held = contender.try_lock().unwrap().is_none();

    writeln!(holder.stdin.take().unwrap(), "RELEASE").unwrap();
    let status = holder.wait().unwrap();
    output_reader.join().unwrap();

    let available_after_release = contender.try_lock().unwrap().is_some();
    assert!(status.success(), "holder process failed: {status}");
    assert!(
        unavailable_while_held,
        "another process acquired an already-held file lock"
    );
    assert!(
        available_after_release,
        "file lock remained unavailable after the holder exited"
    );
}

fn run_holder_process() {
    let path = std::env::var_os(LOCK_PATH).unwrap();
    let mut lock = FileLock::new(path).unwrap();
    let _guard = lock.lock().unwrap();

    println!("LOCKED");
    std::io::stdout().flush().unwrap();

    let mut command = String::new();
    std::io::stdin().read_line(&mut command).unwrap();
    assert_eq!(command.trim(), "RELEASE");
}