From 98af8bd1456329860ae20f41ff5485c089d03b65 Mon Sep 17 00:00:00 2001 From: Christina Quast Date: Thu, 24 Sep 2026 23:25:39 +0200 Subject: [PATCH] orchestrator: Clear the pending update job on a deferred or aborted report ReportUpdateDeferred and ReportUpdateAborted left pending_update set, so the slot stayed occupied and every later submit_update returned UpdateBusy. Salvaged from #441, which demo-wip picked up at an older state that predates the fix. Assisted-by: Claude Opus 5 --- services/orchestrator/driver/src/driver.rs | 2 ++ services/orchestrator/driver/src/tests.rs | 40 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/services/orchestrator/driver/src/driver.rs b/services/orchestrator/driver/src/driver.rs index 6f2d80c5..731ea84b 100644 --- a/services/orchestrator/driver/src/driver.rs +++ b/services/orchestrator/driver/src/driver.rs @@ -327,10 +327,12 @@ impl Platform for PlatformDriver { Ok(None) } Effect::ReportUpdateDeferred => { + self.pending_update = None; self.report(Report::UpdateDeferred); Ok(None) } Effect::ReportUpdateAborted => { + self.pending_update = None; self.report(Report::UpdateAborted); Ok(None) } diff --git a/services/orchestrator/driver/src/tests.rs b/services/orchestrator/driver/src/tests.rs index 81993dc1..d36b618f 100644 --- a/services/orchestrator/driver/src/tests.rs +++ b/services/orchestrator/driver/src/tests.rs @@ -1176,6 +1176,46 @@ fn refused_request_update_injects_no_event() { assert_eq!(orch.state(), State::Ready); } +// ReportUpdateDeferred clears pending_update so the next request is not +// permanently blocked. +#[test] +fn deferred_report_clears_pending_update() { + let mut driver = driver([MemImage::holding(valid_image())]); + driver.submit_update(C0).unwrap(); + assert_eq!(driver.pending_update(), Some(C0)); + + driver.execute(Effect::ReportUpdateDeferred).unwrap(); + + assert_eq!( + driver.pending_update(), + None, + "deferred report must clear the pending job" + ); + + // A subsequent submit succeeds: the slot is free. + driver.submit_update(C0).unwrap(); + assert_eq!(driver.pending_update(), Some(C0)); +} + +// ReportUpdateAborted clears pending_update so an update superseded by +// recovery does not block future requests. +#[test] +fn aborted_update_clears_pending_update() { + let mut driver = driver([MemImage::holding(valid_image())]); + driver.submit_update(C0).unwrap(); + + driver.execute(Effect::ReportUpdateAborted).unwrap(); + + assert_eq!( + driver.pending_update(), + None, + "aborted report must clear the pending job" + ); + + driver.submit_update(C0).unwrap(); + assert_eq!(driver.pending_update(), Some(C0)); +} + // The Updatable seam is wired but not yet driven: no executor exists until // the update pump lands. This pins the mock against the trait's ordering // rule so the wiring cannot rot in the meantime.