fabro/lib/crates/fabro-proc/src/flock.rs
Bryan Helmkamp 2348a1e483
test(harness): use advisory locks for peer presence
Replace PID-based liveness probing in `live_marker_count` with flock
advisory-lock presence detection. Each test process opens
`<session_root>/clients/<pid>` once, holds LOCK_SH for the lifetime
of any live TestContext in the process, and releases it explicitly
when `cleanup_session_root` fires at refcount zero. Reapers probe with
LOCK_EX | LOCK_NB: success means the previous owner is gone (normal
exit, panic, SIGKILL, or zombie — the kernel releases advisory locks
at process exit in every case) and the stale marker is removed.

Compared to the PID check this was replacing:
  - Handles PID recycling correctly (the new holder does not inherit
    the previous owner's advisory lock).
  - Handles zombies correctly without shelling out to `ps`.
  - Costs one open + one flock per peer, ~50 us on macOS.

The marker handle is stored in a process-scoped
`Mutex<Option<(PathBuf, File)>>` so it can be released and
reacquired across the drop-to-zero / rise-from-zero cycles that
`session_refs` already implements. Storing the path alongside the
handle enables a debug assertion that the process never drifts
between session roots.

`ClientMarker` and its serde plumbing are removed; the marker file is
now empty, its existence and lock state carrying the signal.

Full workspace wall-clock after A+B+C: 13.3–13.6 s, down from 20–25 s
on HEAD before the fix and comparable to the 14 s Friday baseline
despite the intervening +85 tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:33:53 -04:00

94 lines
2.8 KiB
Rust

use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;
/// Try to acquire an exclusive (write) lock on `file` without blocking.
///
/// Returns `Ok(true)` if the lock was acquired, `Ok(false)` if another
/// process/fd already holds the lock, and `Err` for unexpected errors.
pub fn try_flock_exclusive(file: &File) -> io::Result<bool> {
// SAFETY: flock() on a valid fd is safe; LOCK_NB makes it non-blocking.
let ret = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
if ret == 0 {
Ok(true)
} else {
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::EWOULDBLOCK) => Ok(false),
_ => Err(err),
}
}
}
/// Try to acquire a shared (read) lock on `file` without blocking.
///
/// Returns `Ok(true)` if the lock was acquired, `Ok(false)` if another
/// process/fd already holds an incompatible lock, and `Err` for unexpected
/// errors.
pub fn try_flock_shared(file: &File) -> io::Result<bool> {
// SAFETY: flock() on a valid fd is safe; LOCK_NB makes it non-blocking.
let ret = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_SH | libc::LOCK_NB) };
if ret == 0 {
Ok(true)
} else {
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::EWOULDBLOCK) => Ok(false),
_ => Err(err),
}
}
}
/// Release any lock held on `file`.
pub fn flock_unlock(file: &File) -> io::Result<()> {
// SAFETY: flock() with LOCK_UN on a valid fd is safe.
let ret = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
if ret == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(test)]
#[expect(
clippy::disallowed_methods,
reason = "tests exercise sync flock semantics on real temp files; uses std::fs::File directly"
)]
mod tests {
use std::fs::File;
use super::*;
#[test]
fn acquire_exclusive_lock() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("lock");
let file = File::create(&path).unwrap();
let acquired = try_flock_exclusive(&file).unwrap();
assert!(acquired);
}
#[test]
fn unlock_then_reacquire() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("lock");
let file = File::create(&path).unwrap();
assert!(try_flock_exclusive(&file).unwrap());
flock_unlock(&file).unwrap();
assert!(try_flock_exclusive(&file).unwrap());
}
#[test]
fn second_fd_blocked() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("lock");
let file1 = File::create(&path).unwrap();
let file2 = File::open(&path).unwrap();
assert!(try_flock_exclusive(&file1).unwrap());
assert!(!try_flock_exclusive(&file2).unwrap());
}
}