From 6ea9b979c84eb01b5b9efc418abcd9fc17bf928a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 19:02:44 +0000 Subject: [PATCH] AI masks: the worker stops counting a job as pending before it replies The pending counter was decremented when the job's guard dropped at the end of the loop iteration, after the reply was sent, so a waiting caller (CLI / MCP mask.add) could see the segmenter still busy right after its answer arrived. Under a loaded test run that made tests_segment::waiting_requests_with_a_damaged_model_are_errors fail on assert!(!busy()). The guard is now dropped just before each reply. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8 --- crates/engine/src/segment/worker.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/segment/worker.rs b/crates/engine/src/segment/worker.rs index 9ded0fbe..b4a1eedf 100644 --- a/crates/engine/src/segment/worker.rs +++ b/crates/engine/src/segment/worker.rs @@ -210,8 +210,11 @@ fn run(rx: &mpsc::Receiver, shared: &Shared, idle: Duration) { } let Some(mut job) = queue.pop_front() else { continue }; let is_detail = matches!(job.kind, Kind::Detail { .. }); - let _done = Done(if is_detail { &shared.detail } else { &shared.pending }); + // counts the job as pending until just before its reply goes out: a caller woken by the + // reply must not still see the worker busy with it + let done = Done(if is_detail { &shared.detail } else { &shared.pending }); if queue.iter().any(|later| supersedes(later, &job)) { + drop(done); let _ = job.reply.send(Outcome { tag: job.tag, result: Ok(None), superseded: true }); continue; } @@ -229,6 +232,7 @@ fn run(rx: &mpsc::Receiver, shared: &Shared, idle: Duration) { state.cache = None; } shared.loaded.store(state.model.is_some(), Ordering::SeqCst); + drop(done); let _ = job.reply.send(Outcome { tag: job.tag, result, superseded: false }); } }