From 0dd26c96a937ea698fd683ef479281edf9858e83 Mon Sep 17 00:00:00 2001 From: Harjot Gill Date: Mon, 17 Aug 2026 16:49:11 -0700 Subject: [PATCH] fix: route checkpoint controls through host --- crates/celld/js/harness.js | 12 ++++++++- crates/celld/runtime.rs | 53 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/crates/celld/js/harness.js b/crates/celld/js/harness.js index a05d6e2c9..0c591e3b5 100644 --- a/crates/celld/js/harness.js +++ b/crates/celld/js/harness.js @@ -3026,9 +3026,19 @@ class DurableObjectNamespace { const headersJson = req._headersJson !== undefined ? req._headersJson : JSON.stringify(Array.from(req.headers)); + // A response control header (checkpoint publication today, and other + // host-owned transitions in the future) must cross the host dispatcher: + // that is where celld consumes the instruction and replaces it with the + // verified result headers. The owned fast path deliberately bypasses + // that boundary, so callers opt this rare operation out without slowing + // ordinary resident-cell traffic. Header names have already been + // normalized by Request/Headers; a quoted value cannot spoof this JSON + // token because JSON escapes its quotes. + const hostDispatch = headersJson.includes( + '"x-celld-host-dispatch"'); // Fast path: this isolate owns the target cell — run the DO // in-isolate, avoiding the __do_call host round trip. - if (__cell.owned[scope]) { + if (__cell.owned[scope] && !hostDispatch) { return await invoke(() => __dispatchTo( scope, req.url, req.method, body_, headersJson, diff --git a/crates/celld/runtime.rs b/crates/celld/runtime.rs index fcce16b74..a5674c8e6 100644 --- a/crates/celld/runtime.rs +++ b/crates/celld/runtime.rs @@ -209,6 +209,25 @@ impl CellInitializationReservation { cell: cell.to_string(), }) } + + fn acquire_if_inactive( + cells: &Arc>, + cell: &str, + ) -> anyhow::Result> { + let mut registry = cells.lock().expect("cell registry poisoned"); + if registry.starting.contains_key(cell) + || registry.published.contains_key(cell) + || registry.initializing.contains(cell) + { + return Ok(None); + } + registry.reserve_initialization(cell)?; + drop(registry); + Ok(Some(Self { + cells: cells.clone(), + cell: cell.to_string(), + })) + } } impl Drop for CellInitializationReservation { @@ -469,11 +488,22 @@ impl RuntimeManager { checkpoint_id: &str, target_cell: &str, ) -> anyhow::Result { - let _target_reservation = CellInitializationReservation::acquire(&self.cells, target_cell)?; + // A retry after activation must verify the immutable seed and succeed; + // reserving first used to reject that exact retry merely because the + // target runtime now existed. A never-seen target remains reserved across + // publication so activation cannot race the final ready manifest. + let target_reservation = + CellInitializationReservation::acquire_if_inactive(&self.cells, target_cell)?; + let target_active = target_reservation.is_none(); self.replication .as_ref() .ok_or_else(|| anyhow!("forking requires durable replication"))? - .publish_fork_seed_from_checkpoint(source_cell, checkpoint_id, target_cell, false) + .publish_fork_seed_from_checkpoint( + source_cell, + checkpoint_id, + target_cell, + target_active, + ) .await } @@ -2099,4 +2129,23 @@ mod tests { CellInitializationReservation::acquire(&cells, "Class:target") .expect("reservation is released after publication"); } + + #[test] + fn retry_reservation_distinguishes_an_existing_target() { + let cells = Arc::new(Mutex::new(CellRegistry::default())); + let first = CellInitializationReservation::acquire_if_inactive(&cells, "Class:target") + .expect("reserve unused target") + .expect("unused target is reserved"); + assert!( + CellInitializationReservation::acquire_if_inactive(&cells, "Class:target") + .expect("recognize existing target") + .is_none() + ); + drop(first); + assert!( + CellInitializationReservation::acquire_if_inactive(&cells, "Class:target") + .expect("reserve released target") + .is_some() + ); + } }