@@ -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
214233impl 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