Skip to content

Commit 28a0444

Browse files
committed
fix: serialize cancellation before queued runs
1 parent fead992 commit 28a0444

1 file changed

Lines changed: 67 additions & 1 deletion

File tree

src/agent/mod.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1989,7 +1989,10 @@ impl ActorLogic<BusMessage> for AgentLogic {
19891989
h.cancel_children_for_parent(&chat_id);
19901990
}
19911991
}
1992-
if let Some((_, token)) = self.cancellation_tokens.remove(&chat_id) {
1992+
// Keep ownership registered until the reasoning task emits its
1993+
// terminal lifecycle event and finalizes. New inbound arriving
1994+
// during cancellation must queue behind that acknowledgement.
1995+
if let Some(token) = self.cancellation_tokens.get(&chat_id) {
19931996
token.cancel();
19941997
let _ = self.logger_tx.send(BusMessage::Log(
19951998
LogEvent::info(
@@ -5328,6 +5331,69 @@ mod tests {
53285331
);
53295332
}
53305333

5334+
#[tokio::test]
5335+
async fn inbound_after_cancel_waits_for_old_terminal_before_new_start() {
5336+
let calls = Arc::new(AtomicUsize::new(0));
5337+
let (mut agent, mut outbound_rx) =
5338+
build_agent_with_provider(Box::new(LongSleepProvider { calls }));
5339+
let chat_id = "cancel-serialization-chat";
5340+
agent
5341+
.process(BusMessage::Inbound(test_inbound(chat_id, "first")))
5342+
.await
5343+
.expect("start first run");
5344+
5345+
let first_run_id = loop {
5346+
match outbound_rx.recv().await {
5347+
Some(BusMessage::RunLifecycle(RunLifecycleEvent::Started {
5348+
run_id,
5349+
chat_id: event_chat,
5350+
})) if event_chat == chat_id => break run_id,
5351+
Some(_) => continue,
5352+
None => panic!("outbound channel closed before first start"),
5353+
}
5354+
};
5355+
5356+
agent
5357+
.process(BusMessage::Cancel(chat_id.to_string()))
5358+
.await
5359+
.expect("cancel accepted");
5360+
agent
5361+
.process(BusMessage::Inbound(test_inbound(chat_id, "second")))
5362+
.await
5363+
.expect("queue second run while cancellation unwinds");
5364+
5365+
let first_after_cancel = loop {
5366+
match outbound_rx.recv().await {
5367+
Some(BusMessage::RunLifecycle(event)) => break event,
5368+
Some(_) => continue,
5369+
None => panic!("outbound channel closed during cancellation"),
5370+
}
5371+
};
5372+
assert!(matches!(
5373+
first_after_cancel,
5374+
RunLifecycleEvent::Terminated {
5375+
run_id,
5376+
chat_id: event_chat,
5377+
outcome: RunOutcome::Cancelled,
5378+
} if run_id == first_run_id && event_chat == chat_id
5379+
));
5380+
5381+
let second_start = loop {
5382+
match outbound_rx.recv().await {
5383+
Some(BusMessage::RunLifecycle(event @ RunLifecycleEvent::Started { .. })) => {
5384+
break event;
5385+
}
5386+
Some(_) => continue,
5387+
None => panic!("outbound channel closed before second start"),
5388+
}
5389+
};
5390+
assert!(matches!(
5391+
second_start,
5392+
RunLifecycleEvent::Started { run_id, chat_id: event_chat }
5393+
if run_id != first_run_id && event_chat == chat_id
5394+
));
5395+
}
5396+
53315397
#[tokio::test]
53325398
async fn clarification_inbound_routes_via_hub_before_reasoning_spawn() {
53335399
let hub = Arc::new(ClarificationHub::new());

0 commit comments

Comments
 (0)