test(e2e): pin the tree and the registry agreeing under a race

Twelve `tab new` at once against one workspace, then check that every
tab landed exactly once and that the tree's pane count still matches the
registry's.

Both invariants this leans on are invisible to a single-threaded test.
The store takes `notify_order` before its state lock and holds it across
delivery, so subscribers see mutations in the order they happened; and
each `tab new` spawns its pane before the tree is asked to hold it, so a
refusal in between leaves a pane running that nothing references. That
second one is quiet — it shows up only as these two counts disagreeing,
which is what `pane close --orphans` exists to mop up.

Verified by hand first: twelve racing creates, and a mixed race of
splits, tab creates, renames and closes, both left the counts equal with
no orphans; the same held with a GUI mirroring the changes live, whose
log recorded no resync or divergence. This is that check, kept.

Also measured while here, and sound, so it is not re-run: 180 pane
create/close cycles move the daemon from 10 fds and 5 threads to 13 and
7, and then stay there across two further rounds — one-time overhead,
not a leak.
This commit is contained in:
l0ng-ai
2026-08-16 07:55:15 +08:00
parent 6238b8eb13
commit df22d9be21
+53
View File
@@ -51,6 +51,10 @@ fn main() {
run_keep_files_the_pane_so_ls_shows_it,
),
("send_then_capture_round_trip", send_then_capture_round_trip),
(
"concurrent_tab_creates_leave_the_tree_and_registry_agreeing",
concurrent_tab_creates_leave_the_tree_and_registry_agreeing,
),
(
"send_enter_submits_in_a_paste_aware_raw_mode_tui",
send_enter_submits_in_a_paste_aware_raw_mode_tui,
@@ -500,6 +504,55 @@ fn run_streams_output_and_passes_the_exit_code(daemon: &Daemon) {
);
}
/// Twelve clients adding a tab to one workspace at once, and every pane
/// accounted for afterwards.
///
/// The store takes `notify_order` before its state lock and holds it across
/// delivery so subscribers see mutations in the order they happened; each
/// `tab new` also spawns its pane before the tree is asked to hold it. Neither
/// is visible from a single-threaded test, and the failure they guard against
/// is quiet: a pane the registry runs that no tree references shows up only as
/// these two counts disagreeing, which is exactly what `pane close --orphans`
/// exists to mop up.
fn concurrent_tab_creates_leave_the_tree_and_registry_agreeing(daemon: &Daemon) {
const RACERS: usize = 12;
let ws = daemon.run_json(&["ws", "new", "racews"]);
let ws_id = ws["id"].as_str().expect("ws new prints the id").to_string();
std::thread::scope(|scope| {
for _ in 0..RACERS {
let ws_id = ws_id.clone();
scope.spawn(move || {
let out = daemon.run(&["tab", "new", &ws_id]);
assert!(
out.status.success(),
"a racing `tab new` failed: {}",
String::from_utf8_lossy(&out.stderr)
);
});
}
});
let tabs = daemon.run_json(&["tab", "ls", &ws_id]);
let tabs = tabs["tabs"].as_array().expect("tab ls lists tabs").len();
assert_eq!(tabs, RACERS, "every racing tab has to land, and land once");
let listed = daemon.run_json(&["ls"]);
let tree: u64 = listed["workspaces"]
.as_array()
.expect("ls lists workspaces")
.iter()
.map(|w| w["panes"].as_u64().unwrap_or_default())
.sum();
let running = daemon.run_json(&["pane", "ls", "--all"]);
let running = running["panes"].as_array().expect("pane ls --all").len() as u64;
assert_eq!(
tree, running,
"the tree and the registry disagree, so a pane is running that nothing holds"
);
}
fn run_keep_files_the_pane_so_ls_shows_it(daemon: &Daemon) {
let ws = daemon.run_json(&["ws", "new", "runws"]);
let ws_id = ws["id"].as_str().expect("ws new prints the id").to_string();