Skip to content

Commit 6209cbc

Browse files
karthiknadigCopilot
andcommitted
test: integrate bounded descendant lease verification (Refs #532)
Merge the reviewed parent CI correction and deterministic contention-retry regression test without rewriting published history. Preserve all protocol cases and original shutdown deadlines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents f668ed5 + 0fee376 commit 6209cbc

1 file changed

Lines changed: 65 additions & 3 deletions

File tree

‎crates/pet/tests/jsonrpc_server_test.rs‎

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,65 @@ fn stdin_eof_exits_while_output_is_not_drained() {
960960
reader.join().unwrap();
961961
}
962962

963+
fn wait_for_descendant_lease(
964+
mut try_lock: impl FnMut() -> Result<(), fs::TryLockError>,
965+
timeout: Duration,
966+
) -> std::io::Result<()> {
967+
let started = Instant::now();
968+
loop {
969+
match try_lock() {
970+
Ok(()) => return Ok(()),
971+
Err(fs::TryLockError::Error(error)) => return Err(error),
972+
Err(fs::TryLockError::WouldBlock) => {}
973+
}
974+
let remaining = timeout.saturating_sub(started.elapsed());
975+
if remaining.is_zero() {
976+
return Err(std::io::Error::new(
977+
std::io::ErrorKind::TimedOut,
978+
"descendant still holds its lease at the shutdown deadline",
979+
));
980+
}
981+
thread::sleep(Duration::from_millis(10).min(remaining));
982+
}
983+
}
984+
985+
#[test]
986+
fn descendant_lease_wait_is_bounded_and_requires_release() {
987+
let directory = tempfile::tempdir().unwrap();
988+
let path = directory.path().join("lease");
989+
let holder = fs::File::create(&path).unwrap();
990+
holder.try_lock().unwrap();
991+
let lease = fs::OpenOptions::new()
992+
.read(true)
993+
.write(true)
994+
.open(path)
995+
.unwrap();
996+
let started = Instant::now();
997+
assert_eq!(
998+
wait_for_descendant_lease(|| lease.try_lock(), Duration::from_millis(20))
999+
.unwrap_err()
1000+
.kind(),
1001+
std::io::ErrorKind::TimedOut
1002+
);
1003+
assert!(started.elapsed() < Duration::from_secs(1));
1004+
let mut holder = Some(holder);
1005+
let mut attempts = 0;
1006+
wait_for_descendant_lease(
1007+
|| {
1008+
attempts += 1;
1009+
let result = lease.try_lock();
1010+
if attempts == 1 {
1011+
assert!(matches!(result, Err(fs::TryLockError::WouldBlock)));
1012+
drop(holder.take());
1013+
}
1014+
result
1015+
},
1016+
Duration::from_secs(1),
1017+
)
1018+
.expect("lease polling must observe release after initial contention");
1019+
assert_eq!(attempts, 2);
1020+
}
1021+
9631022
#[cfg(feature = "ci")]
9641023
#[test]
9651024
fn stdin_eof_cancels_an_active_interpreter_and_its_descendant() {
@@ -1025,10 +1084,13 @@ fn stdin_eof_cancels_an_active_interpreter_and_its_descendant() {
10251084
"active-probe shutdown failed: {status}; stderr: {}",
10261085
client.stderr_output()
10271086
);
1087+
// Observe OS lease release within the same budget as server shutdown.
1088+
wait_for_descendant_lease(
1089+
|| lease.try_lock(),
1090+
Duration::from_secs(4).saturating_sub(started.elapsed()),
1091+
)
1092+
.expect("shutdown must release the actual descendant's lease");
10281093
assert!(started.elapsed() < Duration::from_secs(4));
1029-
lease
1030-
.try_lock()
1031-
.expect("shutdown must release the actual descendant's lease");
10321094
assert!(
10331095
request.join().unwrap().is_err(),
10341096
"an active request must be cancelled, not reported as successful"

0 commit comments

Comments
 (0)