@@ -7,7 +7,7 @@ use std::{
77 collections:: HashMap ,
88 sync:: {
99 Arc , Mutex , Weak ,
10- atomic:: { AtomicBool , AtomicUsize , Ordering } ,
10+ atomic:: { AtomicBool , AtomicU64 , AtomicUsize , Ordering } ,
1111 mpsc:: { self , RecvTimeoutError } ,
1212 } ,
1313 thread,
@@ -146,9 +146,13 @@ struct ProcessInner {
146146 host_limits : ProcessLimits ,
147147 root : ConfinedFsRoot ,
148148 table : Mutex < HashMap < String , OwnedProcess > > ,
149+ closing : AtomicBool ,
150+ generation : AtomicU64 ,
149151 after_running_poll_hook : Mutex < Option < ProcessOpHook > > ,
150152 write_blocked_hook : Mutex < Option < ProcessOpHook > > ,
151153 before_write_cycle_hook : Mutex < Option < ProcessOpHook > > ,
154+ before_os_spawn_hook : Mutex < Option < ProcessOpHook > > ,
155+ after_os_spawn_hook : Mutex < Option < ProcessOpHook > > ,
152156 stdin_workers : AtomicUsize ,
153157}
154158
@@ -208,9 +212,13 @@ impl ProcessCapability {
208212 host_limits,
209213 root,
210214 table : Mutex :: new ( HashMap :: new ( ) ) ,
215+ closing : AtomicBool :: new ( false ) ,
216+ generation : AtomicU64 :: new ( 1 ) ,
211217 after_running_poll_hook : Mutex :: new ( None ) ,
212218 write_blocked_hook : Mutex :: new ( None ) ,
213219 before_write_cycle_hook : Mutex :: new ( None ) ,
220+ before_os_spawn_hook : Mutex :: new ( None ) ,
221+ after_os_spawn_hook : Mutex :: new ( None ) ,
214222 stdin_workers : AtomicUsize :: new ( 0 ) ,
215223 } ) ,
216224 } )
@@ -239,6 +247,9 @@ impl ProcessCapability {
239247 stdin : Option < & [ u8 ] > ,
240248 ) -> Result < ProcessSpawn , CapabilityError > {
241249 let claims = self . authorize ( token, CapabilityRisk :: Execute ) ?;
250+ if self . is_closing ( ) {
251+ return Err ( closing_error ( ) ) ;
252+ }
242253 if argv. is_empty ( ) {
243254 return Err ( CapabilityError :: new (
244255 "invalid_request" ,
@@ -281,16 +292,32 @@ impl ProcessCapability {
281292 {
282293 request = request. with_stdin ( stdin. to_vec ( ) ) ;
283294 }
295+ if self . is_closing ( ) {
296+ return Err ( closing_error ( ) ) ;
297+ }
298+ fire_hook ( & self . inner . before_os_spawn_hook ) ;
299+ if self . is_closing ( ) {
300+ return Err ( closing_error ( ) ) ;
301+ }
302+ let fence = self . inner . generation . load ( Ordering :: SeqCst ) ;
284303 let process = BoundedProcess :: spawn ( request) . map_err ( map_process_error) ?;
304+ fire_hook ( & self . inner . after_os_spawn_hook ) ;
285305 let handle = process. lifecycle_handle ( ) ;
286306 let write_handle = handle. clone ( ) ;
287307 let pid = handle. pid ( ) ;
288308 let id = uuid:: Uuid :: new_v4 ( ) . simple ( ) . to_string ( ) ;
289- self . inner
290- . table
291- . lock ( )
292- . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) )
293- . insert (
309+ {
310+ let mut table = self
311+ . inner
312+ . table
313+ . lock ( )
314+ . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) ) ;
315+ if self . is_closing ( ) || self . inner . generation . load ( Ordering :: SeqCst ) != fence {
316+ drop ( table) ;
317+ let _ = handle. shutdown ( ) ;
318+ return Err ( closing_error ( ) ) ;
319+ }
320+ table. insert (
294321 id. clone ( ) ,
295322 OwnedProcess {
296323 owner_key : claims. owner . key ( ) ,
@@ -299,6 +326,7 @@ impl ProcessCapability {
299326 cancel : cancel. clone ( ) ,
300327 } ,
301328 ) ;
329+ }
302330 let reaper = Arc :: new ( ProcessReaper {
303331 inner : Arc :: downgrade ( & self . inner ) ,
304332 id : id. clone ( ) ,
@@ -466,6 +494,24 @@ impl ProcessCapability {
466494 . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) ) = Some ( hook) ;
467495 }
468496
497+ /// Test barrier: fires immediately before the OS spawn syscall.
498+ pub fn set_before_os_spawn_hook ( & self , hook : Arc < dyn Fn ( ) + Send + Sync > ) {
499+ * self
500+ . inner
501+ . before_os_spawn_hook
502+ . lock ( )
503+ . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) ) = Some ( hook) ;
504+ }
505+
506+ /// Test barrier: fires after OS spawn and before table insert.
507+ pub fn set_after_os_spawn_hook ( & self , hook : Arc < dyn Fn ( ) + Send + Sync > ) {
508+ * self
509+ . inner
510+ . after_os_spawn_hook
511+ . lock ( )
512+ . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) ) = Some ( hook) ;
513+ }
514+
469515 /// Returns a bounded log window.
470516 pub fn log (
471517 & self ,
@@ -550,9 +596,13 @@ impl ProcessCapability {
550596
551597 /// Terminates every owned child and drops table entries.
552598 ///
553- /// Run cleanup must drain committed background residue; `cancel_all`
554- /// kills the process tree but leaves handles observable in the table.
599+ /// Closing is irreversible: later spawns refuse insert and terminate any
600+ /// OS process created after the fence. Run cleanup must drain committed
601+ /// background residue; `cancel_all` kills the process tree but leaves
602+ /// handles observable in the table.
555603 pub fn shutdown_all ( & self ) {
604+ self . inner . closing . store ( true , Ordering :: SeqCst ) ;
605+ self . inner . generation . fetch_add ( 1 , Ordering :: SeqCst ) ;
556606 let owned: Vec < OwnedProcess > = {
557607 let mut table = self
558608 . inner
@@ -566,6 +616,11 @@ impl ProcessCapability {
566616 }
567617 }
568618
619+ /// True after [`Self::shutdown_all`] has started. The fence is irreversible.
620+ pub fn is_closing ( & self ) -> bool {
621+ self . inner . closing . load ( Ordering :: SeqCst )
622+ }
623+
569624 fn authorize ( & self , token : & str , risk : CapabilityRisk ) -> Result < TokenClaims , CapabilityError > {
570625 match self
571626 . inner
@@ -578,6 +633,9 @@ impl ProcessCapability {
578633 }
579634
580635 fn lookup ( & self , token : & str , handle : & str ) -> Result < OwnedProcess , CapabilityError > {
636+ if self . is_closing ( ) {
637+ return Err ( closing_error ( ) ) ;
638+ }
581639 let claims = self . authorize ( token, CapabilityRisk :: Execute ) ?;
582640 let table = self
583641 . inner
@@ -712,6 +770,10 @@ fn fire_hook(slot: &Mutex<Option<ProcessOpHook>>) {
712770 }
713771}
714772
773+ fn closing_error ( ) -> CapabilityError {
774+ CapabilityError :: new ( "capability_unavailable" , "process capability is closing" )
775+ }
776+
715777fn interrupt_write_worker (
716778 handle : & BoundedProcessHandle ,
717779 worker : thread:: JoinHandle < ( ) > ,
0 commit comments