Wait for the managed run to settle before the prune scenario deletes it

The stored view reports a Petri run ended as soon as its own run.finished
record is folded, which is before the server stores the terminal
lifecycle record and settles the managed run in its map. The delete
precheck reads that map, so a delete sent as soon as the API reports the
run ended can be refused as active instead of by the held lease. The
scenario now waits for the managed run to settle, through a test-support
accessor for its status, before it deletes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-09-20 16:03:20 -04:00
parent 9edbacfd5c
commit 9a93bbcfbd
No known key found for this signature in database
2 changed files with 31 additions and 0 deletions

View file

@ -1201,6 +1201,19 @@ impl AppState {
&self.petri_projector
}
/// The status the server holds for a managed run, so a test can wait
/// for the run to settle in the server's own map (what the delete
/// precheck reads) and not only in the stored view, which can report
/// the run ended first.
#[cfg(any(test, feature = "test-support"))]
#[must_use]
pub fn test_managed_run_status(&self, run_id: &RunId) -> Option<RunStatus> {
self.runs
.lock()
.ok()
.and_then(|runs| runs.get(run_id).map(|managed_run| managed_run.status))
}
/// The pool the Petri view tables live in, so a test can read them.
#[cfg(any(test, feature = "test-support"))]
pub fn test_petri_view_pool(&self) -> DbPool {

View file

@ -802,6 +802,9 @@ async fn deleting_a_run_prunes_its_host_workspace_through_petri() {
let store = state.test_petri_run_store();
let key = RunKey::new(run_id.clone());
wait_for_free_lease(store, &key).await;
// The view reports the run ended from Petri's own finish, before the
// server settles the managed run the delete precheck reads.
wait_for_managed_settle(&state, &run_id).await;
// A live handle on the run, as its worker holds one, refuses the
// delete: Petri will not prune under a lease someone holds.
@ -858,6 +861,21 @@ async fn deleting_a_run_prunes_its_host_workspace_through_petri() {
}
/// Wait until no owner holds the run's lease.
/// Wait until the server's own map holds the run as ended.
async fn wait_for_managed_settle(state: &AppState, run_id: &str) {
let run_id: RunId = run_id.parse().expect("a run id");
for _ in 0..500 {
if state
.test_managed_run_status(&run_id)
.is_none_or(|status| status.is_terminal())
{
return;
}
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
panic!("the managed run did not settle");
}
async fn wait_for_free_lease(store: &SqliteRunStore, key: &RunKey) {
for _ in 0..500 {
if store.owner(key).await.expect("reads the lease").is_none() {