clawhdf5: a dropped FileEditor releases its lock at once
CI / test-arm64 (pull_request) Successful in 1m38s
CI / test (pull_request) Successful in 28m11s

FileEditor's flock belongs to the open file description. When another
thread forks to spawn a process, the child shares the locked descriptor
until it execs, so a reopen right after the drop could be refused with
Error::Locked (a one-off failure of edit_interop::editor_locks_the_file in
a parallel test run). Drop now unlocks before closing, which releases the
lock for every descriptor sharing it.

Reproducer edit_tests::drop_releases_the_lock_while_other_threads_spawn_processes
(4 threads running `true`, 2000 open/drop rounds): 1483 of 2000 reopens
refused before, 0 in 30 runs after (tank). An OFD lock would not help: it
is inherited across fork the same way and does not conflict with
libhdf5's flock. The agent store's lock file unlocks on drop too.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-28 21:05:17 -05:00
co-authored by Claude Opus 5.5
parent 5eae9ee60b
commit 3eca5d8334
5 changed files with 116 additions and 2 deletions
+12 -2
View File
@@ -20,7 +20,7 @@ const LOCK_RETRY_DELAY: std::time::Duration = std::time::Duration::from_millis(1
/// never leaves a stale lock behind; the empty lock file itself is harmless).
#[derive(Debug)]
pub(crate) struct StoreLock {
_file: File,
file: File,
}
impl StoreLock {
@@ -42,7 +42,7 @@ impl StoreLock {
let mut attempts_left = LOCK_RETRIES;
loop {
match file.try_lock() {
Ok(()) => return Ok(Self { _file: file }),
Ok(()) => return Ok(Self { file }),
Err(TryLockError::WouldBlock) if attempts_left > 0 => {
attempts_left -= 1;
std::thread::sleep(LOCK_RETRY_DELAY);
@@ -60,6 +60,16 @@ impl StoreLock {
}
}
impl Drop for StoreLock {
/// Unlocks before the file is closed: a process another thread forks
/// inherits the descriptor until it execs, and a `flock` lasts while any
/// descriptor of the open file does, so closing alone could keep the
/// store locked for a moment after the drop (see `FileEditor`'s `Drop`).
fn drop(&mut self) {
let _ = self.file.unlock();
}
}
#[cfg(test)]
mod tests {
use super::*;