From 9eaa641822ca5d746ef6c567eba0ebb28472310e Mon Sep 17 00:00:00 2001 From: Libby Date: Fri, 13 Nov 2026 04:12:39 +0000 Subject: [PATCH 1/2] cultcache-rs: the unlock-fault test retries a try-lock held by a concurrent fork-exec (rs-unlock-fault-test-flake) Co-Authored-By: Claude Sonnet 5.5 --- packages/cultcache-rs/src/lib.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/cultcache-rs/src/lib.rs b/packages/cultcache-rs/src/lib.rs index 90c09435..c4045a63 100644 --- a/packages/cultcache-rs/src/lib.rs +++ b/packages/cultcache-rs/src/lib.rs @@ -8176,11 +8176,20 @@ mod tests { with_write_fault(WriteStep::Unlock, || store.push(&pushed))?; assert!(with_write_fault(WriteStep::Unlock, || store.pull_all())?.contains(&pushed)); - // The lock was released: another writer takes it at once. - assert!(matches!( - store.try_compare_exchange_snapshot(&store.pull_all()?, &replacement)?, - TryCompareExchangeSnapshotOutcome::Exchanged - )); + // The lock was released: another writer takes it. The faulted unlock above left the release to the close of the + // descriptor (see `release_lock`), which frees the lock only once no other descriptor shares its open file + // description, and the test that spawns a child (a fork-exec duplicates every descriptor of the process until the + // exec) can hold such a duplicate on another test thread for a moment. So a contended try-lock is retried for up to + // two seconds; a lock that was never released stays contended and still fails. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); + let outcome = loop { + let outcome = store.try_compare_exchange_snapshot(&store.pull_all()?, &replacement)?; + if !matches!(outcome, TryCompareExchangeSnapshotOutcome::LockContended) || std::time::Instant::now() >= deadline { + break outcome; + } + std::thread::sleep(std::time::Duration::from_millis(5)); + }; + assert!(matches!(outcome, TryCompareExchangeSnapshotOutcome::Exchanged), "{outcome:?}"); // The keyed store releases its lock the same way. let mut keyed = RedbMessagePackBackingStore::new(temp.path().join("keyed.redb"))?; -- 2.47.3 From b22100e58d1715fec073c7062b6568606723776f Mon Sep 17 00:00:00 2001 From: Libby Date: Fri, 13 Nov 2026 04:34:50 +0000 Subject: [PATCH 2/2] cultcache-rs: the unlock-fault test comment names the shared lock of the faulted pull_all as the only one a fork can hold Co-Authored-By: Claude Sonnet 5.5 --- packages/cultcache-rs/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/cultcache-rs/src/lib.rs b/packages/cultcache-rs/src/lib.rs index c4045a63..edfc0c91 100644 --- a/packages/cultcache-rs/src/lib.rs +++ b/packages/cultcache-rs/src/lib.rs @@ -8176,11 +8176,12 @@ mod tests { with_write_fault(WriteStep::Unlock, || store.push(&pushed))?; assert!(with_write_fault(WriteStep::Unlock, || store.pull_all())?.contains(&pushed)); - // The lock was released: another writer takes it. The faulted unlock above left the release to the close of the - // descriptor (see `release_lock`), which frees the lock only once no other descriptor shares its open file - // description, and the test that spawns a child (a fork-exec duplicates every descriptor of the process until the - // exec) can hold such a duplicate on another test thread for a moment. So a contended try-lock is retried for up to - // two seconds; a lock that was never released stays contended and still fails. + // The lock was released: another writer takes it. The shared lock of the faulted `pull_all` just above was left to the + // close of its descriptor (see `release_lock`), which frees it only once no other descriptor shares its open file + // description, and a test that spawns a child (a fork-exec duplicates every descriptor of the process until the exec) + // can hold such a duplicate on another test thread for a moment. A faulted exclusive lock cannot contend the try-lock: + // the blocking `pull_all` in its argument waits it out. So a contended try-lock is retried for up to two seconds; a + // lock that was never released stays contended and still fails. let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); let outcome = loop { let outcome = store.try_compare_exchange_snapshot(&store.pull_all()?, &replacement)?; -- 2.47.3