@@ -538,41 +538,55 @@ async fn deadline_terminates_child_process_without_residue() {
538538async fn deadline_is_cumulative_from_admission ( ) {
539539 let provider = ScriptedProvider :: new ( ) ;
540540 provider. push_hang ( ) ;
541- let timeout = Duration :: from_millis ( 400 ) ;
542- let state = loop_service ( short_config ( timeout) , & provider) ;
541+ let state = loop_service ( short_config ( Duration :: from_millis ( 400 ) ) , & provider) ;
543542 let service = state. service ( ) ;
544543 let admitted = service
545544 . admit ( admit_request ( ) )
546545 . await
547546 . expect ( "admission should succeed" ) ;
548- tokio:: time:: sleep ( Duration :: from_millis ( 250 ) ) . await ;
549- let remaining = service
547+ let cancellation = service
550548 . handle ( & admitted. run_id )
551549 . expect ( "live handle" )
552550 . cancellation ( )
553- . remaining_deadline ( )
554- . expect ( "deadline" ) ;
555- assert ! (
556- remaining < timeout,
557- "remaining deadline {remaining:?} must be less than the original {timeout:?}"
558- ) ;
559- assert ! ( remaining > Duration :: from_millis( 20 ) ) ;
560- let started = Instant :: now ( ) ;
561- service
562- . clone ( )
563- . run_worker ( admitted. run_id . clone ( ) , "ignored" . to_string ( ) )
564- . await ;
565- let elapsed = started. elapsed ( ) ;
551+ . clone ( ) ;
552+ let admitted_deadline = cancellation. deadline_instant ( ) . expect ( "admission deadline" ) ;
553+ let preparation_observed = Arc :: new ( AtomicBool :: new ( false ) ) ;
554+ service. inject_agent_compile_lookup_observer ( Arc :: new ( {
555+ let cancellation = cancellation. clone ( ) ;
556+ let preparation_observed = Arc :: clone ( & preparation_observed) ;
557+ move || {
558+ preparation_observed. store ( true , Ordering :: SeqCst ) ;
559+ assert_eq ! ( cancellation. deadline_instant( ) , Some ( admitted_deadline) ) ;
560+ // Consume the admitted budget during preparation. A worker must
561+ // not gain a new budget when it finally starts the RSS invocation.
562+ thread:: sleep ( admitted_deadline. saturating_duration_since ( Instant :: now ( ) ) ) ;
563+ assert_eq ! ( cancellation. remaining_deadline( ) , Some ( Duration :: ZERO ) ) ;
564+ }
565+ } ) ) ;
566+ // Preserve the queue delay as well as preparation time in the same budget.
567+ tokio:: time:: sleep ( Duration :: from_millis ( 250 ) ) . await ;
568+ tokio:: time:: timeout (
569+ Duration :: from_secs ( 5 ) ,
570+ service
571+ . clone ( )
572+ . run_worker ( admitted. run_id . clone ( ) , "ignored" . to_string ( ) ) ,
573+ )
574+ . await
575+ . expect ( "expired worker and cleanup must finish within the outer bound" ) ;
566576
577+ assert ! ( preparation_observed. load( Ordering :: SeqCst ) ) ;
578+ assert_eq ! ( cancellation. deadline_instant( ) , Some ( admitted_deadline) ) ;
567579 assert_eq ! (
568580 terminal_events( & service, & admitted. run_id) ,
569581 vec![ "run.cancelled" . to_string( ) ]
570582 ) ;
571583 assert_eq ! ( cancel_reason( & service, & admitted. run_id) , "deadline" ) ;
572- assert ! (
573- elapsed < timeout,
574- "worker should observe remaining deadline, not a fresh {timeout:?}: {elapsed:?}"
584+ assert_eq ! (
585+ provider. call_count( ) ,
586+ 0 ,
587+ "preparation exhausted the admission deadline before provider work"
575588 ) ;
589+ assert ! ( service. capability_host_closed( & admitted. run_id) ) ;
576590}
577591
578592#[ tokio:: test( flavor = "multi_thread" ) ]
0 commit comments