sandbox: a setup assertion walked a tree that still held the warm-up worker #208
4 changed files with 36 additions and 4 deletions
1
jouledesk/jouledesk-rs/Cargo.lock
generated
1
jouledesk/jouledesk-rs/Cargo.lock
generated
|
|
@ -1116,6 +1116,7 @@ dependencies = [
|
|||
"hex",
|
||||
"jcp-receipt",
|
||||
"jcp-schema",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
|
|
|||
1
joulehook/joulehook-rs/Cargo.lock
generated
1
joulehook/joulehook-rs/Cargo.lock
generated
|
|
@ -412,6 +412,7 @@ dependencies = [
|
|||
"hex",
|
||||
"jcp-receipt",
|
||||
"jcp-schema",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
|
|
|||
1
sandbox/sandbox-rs/Cargo.lock
generated
1
sandbox/sandbox-rs/Cargo.lock
generated
|
|
@ -1066,6 +1066,7 @@ dependencies = [
|
|||
"hex",
|
||||
"jcp-receipt",
|
||||
"jcp-schema",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -106,6 +106,32 @@ fn ps_tree(root: i32) -> Vec<i32> {
|
|||
tree
|
||||
}
|
||||
|
||||
/// `root`'s tree once it has exactly `want` members. Panics after 20 s,
|
||||
/// naming the tree it last saw.
|
||||
///
|
||||
/// A server's reply does not mean its worker is gone. The worker prints and
|
||||
/// the parent reads that line while the worker still has its work to do and
|
||||
/// its exit and reaping after that, so a tree walked straight after a reply
|
||||
/// can still hold it. Waiting for the shape a test assumes is the only way to
|
||||
/// assert that shape; asserting it on the first walk asserts a race, and that
|
||||
/// race is real — the count came back 62 instead of 61 in 2 runs of 5 on this
|
||||
/// host, more often the busier it is.
|
||||
fn settled_tree(root: i32, want: usize) -> Vec<i32> {
|
||||
let deadline = Instant::now() + Duration::from_secs(20);
|
||||
loop {
|
||||
let tree = ps_tree(root);
|
||||
if tree.len() == want {
|
||||
return tree;
|
||||
}
|
||||
assert!(
|
||||
Instant::now() < deadline,
|
||||
"the tree of {root} never settled to {want} members; last saw {}: {tree:?}",
|
||||
tree.len()
|
||||
);
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
}
|
||||
|
||||
/// Each of `pids`' rusage once every one of them is quiet by the oracle's
|
||||
/// rule: not running or queued (or exited) at two reads 20 ms apart, with
|
||||
/// the same energy at both. Panics after 20 s.
|
||||
|
|
@ -277,11 +303,14 @@ fn a_worker_reaped_while_its_tree_is_walked_is_never_missed() {
|
|||
],
|
||||
);
|
||||
let root = s.pid() as u32;
|
||||
// The first round trip runs once all 60 are forked; the tree is quiet
|
||||
// before the first span.
|
||||
// The first round trip runs once all 60 are forked. It does not leave the
|
||||
// tree quiet, though — the worker it forks prints its pid before doing its
|
||||
// work, so the reply arrives while that worker is still running — hence
|
||||
// the wait below rather than a bare walk.
|
||||
let _ = s.ask("warm up");
|
||||
let tree = ps_tree(s.pid());
|
||||
assert_eq!(tree.len(), 61, "setup: the server and its 60 idle children: {tree:?}");
|
||||
// Not `ps_tree` + `assert_eq!` here: the warm-up round trip returns while
|
||||
// its worker is still the server's child, so the first walk sees 62.
|
||||
let tree = settled_tree(s.pid(), 61);
|
||||
quiet(&tree);
|
||||
let meter = s.meter();
|
||||
let (mut exited, mut reaped, mut unsettled) = (0, 0, 0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue