Skip to content

Commit 28d4647

Browse files
authored
fix: route checkpoint controls through host (#5)
1 parent c7caa90 commit 28d4647

2 files changed

Lines changed: 62 additions & 3 deletions

File tree

‎crates/celld/js/harness.js‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3026,9 +3026,19 @@ class DurableObjectNamespace {
30263026
const headersJson = req._headersJson !== undefined
30273027
? req._headersJson
30283028
: JSON.stringify(Array.from(req.headers));
3029+
// A response control header (checkpoint publication today, and other
3030+
// host-owned transitions in the future) must cross the host dispatcher:
3031+
// that is where celld consumes the instruction and replaces it with the
3032+
// verified result headers. The owned fast path deliberately bypasses
3033+
// that boundary, so callers opt this rare operation out without slowing
3034+
// ordinary resident-cell traffic. Header names have already been
3035+
// normalized by Request/Headers; a quoted value cannot spoof this JSON
3036+
// token because JSON escapes its quotes.
3037+
const hostDispatch = headersJson.includes(
3038+
'"x-celld-host-dispatch"');
30293039
// Fast path: this isolate owns the target cell — run the DO
30303040
// in-isolate, avoiding the __do_call host round trip.
3031-
if (__cell.owned[scope]) {
3041+
if (__cell.owned[scope] && !hostDispatch) {
30323042
return await invoke(() => __dispatchTo(
30333043
scope, req.url, req.method, body_,
30343044
headersJson,

‎crates/celld/runtime.rs‎

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,25 @@ impl CellInitializationReservation {
209209
cell: cell.to_string(),
210210
})
211211
}
212+
213+
fn acquire_if_inactive(
214+
cells: &Arc<Mutex<CellRegistry>>,
215+
cell: &str,
216+
) -> anyhow::Result<Option<Self>> {
217+
let mut registry = cells.lock().expect("cell registry poisoned");
218+
if registry.starting.contains_key(cell)
219+
|| registry.published.contains_key(cell)
220+
|| registry.initializing.contains(cell)
221+
{
222+
return Ok(None);
223+
}
224+
registry.reserve_initialization(cell)?;
225+
drop(registry);
226+
Ok(Some(Self {
227+
cells: cells.clone(),
228+
cell: cell.to_string(),
229+
}))
230+
}
212231
}
213232

214233
impl Drop for CellInitializationReservation {
@@ -469,11 +488,22 @@ impl RuntimeManager {
469488
checkpoint_id: &str,
470489
target_cell: &str,
471490
) -> anyhow::Result<crate::ltx_repl::ForkSeedManifest> {
472-
let _target_reservation = CellInitializationReservation::acquire(&self.cells, target_cell)?;
491+
// A retry after activation must verify the immutable seed and succeed;
492+
// reserving first used to reject that exact retry merely because the
493+
// target runtime now existed. A never-seen target remains reserved across
494+
// publication so activation cannot race the final ready manifest.
495+
let target_reservation =
496+
CellInitializationReservation::acquire_if_inactive(&self.cells, target_cell)?;
497+
let target_active = target_reservation.is_none();
473498
self.replication
474499
.as_ref()
475500
.ok_or_else(|| anyhow!("forking requires durable replication"))?
476-
.publish_fork_seed_from_checkpoint(source_cell, checkpoint_id, target_cell, false)
501+
.publish_fork_seed_from_checkpoint(
502+
source_cell,
503+
checkpoint_id,
504+
target_cell,
505+
target_active,
506+
)
477507
.await
478508
}
479509

@@ -2099,4 +2129,23 @@ mod tests {
20992129
CellInitializationReservation::acquire(&cells, "Class:target")
21002130
.expect("reservation is released after publication");
21012131
}
2132+
2133+
#[test]
2134+
fn retry_reservation_distinguishes_an_existing_target() {
2135+
let cells = Arc::new(Mutex::new(CellRegistry::default()));
2136+
let first = CellInitializationReservation::acquire_if_inactive(&cells, "Class:target")
2137+
.expect("reserve unused target")
2138+
.expect("unused target is reserved");
2139+
assert!(
2140+
CellInitializationReservation::acquire_if_inactive(&cells, "Class:target")
2141+
.expect("recognize existing target")
2142+
.is_none()
2143+
);
2144+
drop(first);
2145+
assert!(
2146+
CellInitializationReservation::acquire_if_inactive(&cells, "Class:target")
2147+
.expect("reserve released target")
2148+
.is_some()
2149+
);
2150+
}
21022151
}

0 commit comments

Comments
 (0)