@@ -29,7 +29,7 @@ use libz_rs_sys as z;
2929use std:: collections:: BTreeMap ;
3030use std:: io:: { Read , Write } ;
3131use std:: sync:: atomic:: { AtomicBool , AtomicUsize , Ordering } ;
32- use std:: sync:: mpsc:: { self , RecvTimeoutError , TrySendError } ;
32+ use std:: sync:: mpsc:: { self , RecvTimeoutError , TryRecvError , TrySendError } ;
3333use std:: sync:: { Arc , Condvar , Mutex } ;
3434use std:: thread;
3535use std:: time:: { Duration , Instant } ;
@@ -2838,6 +2838,77 @@ struct NativeResult {
28382838 result : Result < crate :: parallel:: deflate:: Chunk , NativeError > ,
28392839}
28402840
2841+ /// Coordinator-owned native results that have arrived ahead of output order.
2842+ ///
2843+ /// Decode and marker-resolution work share one worker pool but publish through
2844+ /// separate bounded channels. A coordinator waiting for resolution must keep
2845+ /// draining this inbox so every worker cannot become blocked publishing native
2846+ /// work. The scheduler bounds the number of queued and in-flight decode tasks;
2847+ /// moving their completed results here therefore preserves the existing memory
2848+ /// bound without making either result channel unbounded.
2849+ struct NativeResultInbox {
2850+ receiver : mpsc:: Receiver < NativeResult > ,
2851+ pending : BTreeMap < usize , Result < crate :: parallel:: deflate:: Chunk , NativeError > > ,
2852+ }
2853+
2854+ impl NativeResultInbox {
2855+ fn new ( receiver : mpsc:: Receiver < NativeResult > ) -> Self {
2856+ Self {
2857+ receiver,
2858+ pending : BTreeMap :: new ( ) ,
2859+ }
2860+ }
2861+
2862+ fn retain_from ( & mut self , next_index : usize ) {
2863+ self . pending . retain ( |index, _| * index >= next_index) ;
2864+ }
2865+
2866+ #[ inline]
2867+ fn offer ( & mut self , result : NativeResult , next_index : usize ) {
2868+ if result. index >= next_index {
2869+ self . pending . insert ( result. index , result. result ) ;
2870+ }
2871+ }
2872+
2873+ /// Moves every currently available native result out of the bounded
2874+ /// worker channel, discarding results made stale by a member transition.
2875+ fn drain_available ( & mut self , next_index : usize ) {
2876+ while let Ok ( result) = self . receiver . try_recv ( ) {
2877+ self . offer ( result, next_index) ;
2878+ }
2879+ }
2880+
2881+ /// Receives one native result in task-index order.
2882+ #[ inline]
2883+ fn receive (
2884+ & mut self ,
2885+ next_index : usize ,
2886+ cancelled : & AtomicBool ,
2887+ ) -> Result < Result < crate :: parallel:: deflate:: Chunk , NativeError > , DecodeError > {
2888+ loop {
2889+ if let Some ( result) = self . pending . remove ( & next_index) {
2890+ return Ok ( result) ;
2891+ }
2892+ match self . receiver . recv_timeout ( Duration :: from_millis ( 10 ) ) {
2893+ Ok ( result) => self . offer ( result, next_index) ,
2894+ Err ( RecvTimeoutError :: Timeout ) => {
2895+ if cancelled. load ( Ordering :: Relaxed ) {
2896+ return Err ( DecodeError :: Cancelled ) ;
2897+ }
2898+ }
2899+ Err ( RecvTimeoutError :: Disconnected ) => {
2900+ return Err ( DecodeError :: WorkerPanicked ) ;
2901+ }
2902+ }
2903+ }
2904+ }
2905+
2906+ #[ cfg( test) ]
2907+ fn pending_indices ( & self ) -> Vec < usize > {
2908+ self . pending . keys ( ) . copied ( ) . collect ( )
2909+ }
2910+ }
2911+
28412912struct ResolveTask {
28422913 sequence : usize ,
28432914 predecessor : Window ,
@@ -3370,21 +3441,44 @@ fn emit_resolved_parts<O: Output>(
33703441 Ok ( ( ) )
33713442}
33723443
3444+ #[ allow( clippy:: too_many_arguments) ]
33733445fn wait_for_resolved (
3374- receiver : & mpsc:: Receiver < ResolveResult > ,
3375- pending : & mut BTreeMap < usize , Result < ResolvedParts , crate :: parallel:: MarkerError > > ,
3446+ resolve_receiver : & mpsc:: Receiver < ResolveResult > ,
3447+ resolve_pending : & mut BTreeMap < usize , Result < ResolvedParts , crate :: parallel:: MarkerError > > ,
3448+ native_results : & mut NativeResultInbox ,
3449+ next_native_index : usize ,
33763450 next_sequence : usize ,
33773451 cancelled : & AtomicBool ,
33783452 bit_offset : u64 ,
33793453) -> Result < ResolvedParts , DecodeError > {
33803454 let result = loop {
3381- if let Some ( result) = pending . remove ( & next_sequence) {
3455+ if let Some ( result) = resolve_pending . remove ( & next_sequence) {
33823456 break result;
33833457 }
3384- match receiver. recv_timeout ( Duration :: from_millis ( 10 ) ) {
3458+
3459+ // Preserve the original immediate-result fast path. Native draining
3460+ // is needed only when the required resolution is not already waiting.
3461+ match resolve_receiver. try_recv ( ) {
3462+ Ok ( result) if result. sequence < next_sequence => continue ,
3463+ Ok ( result) if result. sequence == next_sequence => break result. result ,
3464+ Ok ( result) => {
3465+ resolve_pending. insert ( result. sequence , result. result ) ;
3466+ continue ;
3467+ }
3468+ Err ( TryRecvError :: Disconnected ) => return Err ( DecodeError :: WorkerPanicked ) ,
3469+ Err ( TryRecvError :: Empty ) => { }
3470+ }
3471+
3472+ // A resolution worker cannot run if every shared worker is blocked on
3473+ // the bounded native-result channel. Drain that channel before each
3474+ // short resolution wait to break the circular dependency. The short
3475+ // interval also bounds the race in which native results arrive after
3476+ // the drain but before this receive begins.
3477+ native_results. drain_available ( next_native_index) ;
3478+ match resolve_receiver. recv_timeout ( Duration :: from_millis ( 1 ) ) {
33853479 Ok ( result) if result. sequence < next_sequence => { }
33863480 Ok ( result) => {
3387- pending . insert ( result. sequence , result. result ) ;
3481+ resolve_pending . insert ( result. sequence , result. result ) ;
33883482 }
33893483 Err ( RecvTimeoutError :: Timeout ) => {
33903484 if cancelled. load ( Ordering :: Relaxed ) {
@@ -3450,8 +3544,10 @@ fn enqueue_native_resolution(
34503544
34513545#[ allow( clippy:: too_many_arguments) ]
34523546fn drain_native_resolutions < O : Output > (
3453- receiver : & mpsc:: Receiver < ResolveResult > ,
3454- pending : & mut BTreeMap < usize , Result < ResolvedParts , crate :: parallel:: MarkerError > > ,
3547+ resolve_receiver : & mpsc:: Receiver < ResolveResult > ,
3548+ resolve_pending : & mut BTreeMap < usize , Result < ResolvedParts , crate :: parallel:: MarkerError > > ,
3549+ native_results : & mut NativeResultInbox ,
3550+ next_native_index : usize ,
34553551 next_sequence : & mut usize ,
34563552 outstanding : & mut usize ,
34573553 cancelled : & AtomicBool ,
@@ -3462,7 +3558,15 @@ fn drain_native_resolutions<O: Output>(
34623558 total_output : & mut u64 ,
34633559) -> Result < ( ) , DecodeError > {
34643560 while * outstanding != 0 {
3465- let parts = wait_for_resolved ( receiver, pending, * next_sequence, cancelled, bit_offset) ?;
3561+ let parts = wait_for_resolved (
3562+ resolve_receiver,
3563+ resolve_pending,
3564+ native_results,
3565+ next_native_index,
3566+ * next_sequence,
3567+ cancelled,
3568+ bit_offset,
3569+ ) ?;
34663570 emit_resolved_parts ( parts, config, output, accounting, total_output) ?;
34673571 * next_sequence += 1 ;
34683572 * outstanding -= 1 ;
@@ -3944,12 +4048,10 @@ where
39444048 Arc :: clone ( runtime) ,
39454049 ) ) ;
39464050 let worker_pool_count = adaptive_workers. worker_pool_limit ( ) . min ( worker_count) ;
3947- // Result channels need spare slots beyond the active ranks because the
3948- // same pool executes marker resolution. If every worker blocks while
3949- // publishing speculative decode, no rank remains to resolve an exact
3950- // member bridge. The configured window carries the usual two-slot slack;
3951- // the scheduling horizon below still follows the adaptive active limit, so
3952- // this capacity does not admit extra speculative tasks.
4051+ // Decode and marker-resolution tasks share this worker pool. Both result
4052+ // channels use the bounded scheduling horizon; resolution waits drain the
4053+ // native inbox so its backpressure cannot keep every worker from servicing
4054+ // the higher-priority resolution queue.
39534055 let pipeline_capacity = config
39544056 . in_flight_chunks
39554057 . max ( worker_pool_count)
@@ -3967,7 +4069,7 @@ where
39674069 let mut current_bit = first_deflate_bit;
39684070 let mut next_to_schedule = initial_task_window;
39694071 let mut next_to_emit = 0_usize ;
3970- let mut pending = BTreeMap :: new ( ) ;
4072+ let mut native_results = NativeResultInbox :: new ( receiver ) ;
39714073 let mut prepared_total_output = 0_u64 ;
39724074 let mut next_resolve_sequence = 0_usize ;
39734075 let mut next_resolve_to_emit = 0_usize ;
@@ -4033,6 +4135,8 @@ where
40334135 drain_native_resolutions (
40344136 & resolve_receiver,
40354137 & mut resolve_pending,
4138+ & mut native_results,
4139+ next_to_emit,
40364140 & mut next_resolve_to_emit,
40374141 & mut outstanding_resolves,
40384142 cancelled,
@@ -4135,7 +4239,7 @@ where
41354239 continue ' decode;
41364240 }
41374241
4138- pending . retain ( |index , _| * index >= target_index) ;
4242+ native_results . retain_from ( target_index) ;
41394243 next_to_emit = target_index;
41404244 if next_to_schedule < target_index {
41414245 next_to_schedule = target_index;
@@ -4193,6 +4297,8 @@ where
41934297 let parts = wait_for_resolved (
41944298 & resolve_receiver,
41954299 & mut resolve_pending,
4300+ & mut native_results,
4301+ next_to_emit,
41964302 next_resolve_to_emit,
41974303 cancelled,
41984304 current_bit,
@@ -4232,25 +4338,7 @@ where
42324338 if cancelled. load ( Ordering :: Relaxed ) {
42334339 return Err ( DecodeError :: Cancelled ) ;
42344340 }
4235- let result = loop {
4236- if let Some ( result) = pending. remove ( & next_to_emit) {
4237- break result;
4238- }
4239- match receiver. recv_timeout ( Duration :: from_millis ( 10 ) ) {
4240- Ok ( result) if result. index < next_to_emit => { }
4241- Ok ( result) => {
4242- pending. insert ( result. index , result. result ) ;
4243- }
4244- Err ( RecvTimeoutError :: Timeout ) => {
4245- if cancelled. load ( Ordering :: Relaxed ) {
4246- return Err ( DecodeError :: Cancelled ) ;
4247- }
4248- }
4249- Err ( RecvTimeoutError :: Disconnected ) => {
4250- return Err ( DecodeError :: WorkerPanicked ) ;
4251- }
4252- }
4253- } ;
4341+ let result = native_results. receive ( next_to_emit, cancelled) ?;
42544342
42554343 let mut gap_bridge = None ;
42564344 let chunk = match result {
@@ -4277,6 +4365,8 @@ where
42774365 drain_native_resolutions (
42784366 & resolve_receiver,
42794367 & mut resolve_pending,
4368+ & mut native_results,
4369+ next_to_emit,
42804370 & mut next_resolve_to_emit,
42814371 & mut outstanding_resolves,
42824372 cancelled,
@@ -4325,6 +4415,8 @@ where
43254415 let parts = wait_for_resolved (
43264416 & resolve_receiver,
43274417 & mut resolve_pending,
4418+ & mut native_results,
4419+ next_to_emit,
43284420 next_resolve_to_emit,
43294421 cancelled,
43304422 current_bit,
@@ -4353,6 +4445,8 @@ where
43534445 let parts = wait_for_resolved (
43544446 & resolve_receiver,
43554447 & mut resolve_pending,
4448+ & mut native_results,
4449+ next_to_emit,
43564450 next_resolve_to_emit,
43574451 cancelled,
43584452 current_bit,
@@ -4382,6 +4476,8 @@ where
43824476 drain_native_resolutions (
43834477 & resolve_receiver,
43844478 & mut resolve_pending,
4479+ & mut native_results,
4480+ next_to_emit,
43854481 & mut next_resolve_to_emit,
43864482 & mut outstanding_resolves,
43874483 cancelled,
@@ -4902,10 +4998,16 @@ where
49024998mod tests {
49034999 use super :: {
49045000 INDEPENDENT_MEMBER_TASK_MAX_CANDIDATES , InputCursor , MAX_EXACT_GAP_BITS , MemberAccounting ,
4905- MemberHeader , SourceCursor , Window , batch_independent_headers, bridge_exact_gap,
4906- find_gzip_magic, find_gzip_magic_scalar, independent_member_task_candidate_limit,
4907- inflate_tail, validate_footer,
5001+ MemberHeader , NativeError , NativeResult , NativeResultInbox , ResolveResult , SourceCursor ,
5002+ Window , batch_independent_headers, bridge_exact_gap, find_gzip_magic,
5003+ find_gzip_magic_scalar, independent_member_task_candidate_limit, inflate_tail,
5004+ send_native_result, send_resolve_result, validate_footer, wait_for_resolved,
49085005 } ;
5006+ use std:: collections:: BTreeMap ;
5007+ use std:: sync:: atomic:: AtomicBool ;
5008+ use std:: sync:: { Arc , mpsc} ;
5009+ use std:: thread;
5010+ use std:: time:: Duration ;
49095011
49105012 fn header ( start : u64 ) -> MemberHeader {
49115013 MemberHeader {
@@ -5119,4 +5221,67 @@ mod tests {
51195221 assert_eq ! ( validate_footer( & mut cursor, 6 , 0 , & accounting) . unwrap( ) , 0 ) ;
51205222 assert_eq ! ( cursor. position( ) , 8 ) ;
51215223 }
5224+
5225+ #[ test]
5226+ fn resolution_wait_drains_native_results_without_losing_order ( ) {
5227+ let ( finished_sender, finished_receiver) = mpsc:: channel ( ) ;
5228+
5229+ thread:: spawn ( move || {
5230+ let stopped = Arc :: new ( AtomicBool :: new ( false ) ) ;
5231+ let ( native_sender, native_receiver) = mpsc:: sync_channel ( 2 ) ;
5232+ let ( resolve_sender, resolve_receiver) = mpsc:: sync_channel ( 1 ) ;
5233+ for index in [ 0 , 1 ] {
5234+ native_sender
5235+ . send ( NativeResult {
5236+ index,
5237+ result : Err ( NativeError :: UnexpectedEof ) ,
5238+ } )
5239+ . unwrap ( ) ;
5240+ }
5241+
5242+ let worker_stopped = Arc :: clone ( & stopped) ;
5243+ let worker = thread:: spawn ( move || {
5244+ send_native_result (
5245+ & native_sender,
5246+ & worker_stopped,
5247+ NativeResult {
5248+ index : 2 ,
5249+ result : Err ( NativeError :: UnexpectedEof ) ,
5250+ } ,
5251+ ) ;
5252+ send_resolve_result (
5253+ & resolve_sender,
5254+ & worker_stopped,
5255+ ResolveResult {
5256+ sequence : 0 ,
5257+ result : Ok ( ( vec ! [ 1 ] , vec ! [ 2 ] , vec ! [ 3 ] ) ) ,
5258+ } ,
5259+ ) ;
5260+ } ) ;
5261+
5262+ let mut native_results = NativeResultInbox :: new ( native_receiver) ;
5263+ let mut resolve_pending = BTreeMap :: new ( ) ;
5264+ let resolved = wait_for_resolved (
5265+ & resolve_receiver,
5266+ & mut resolve_pending,
5267+ & mut native_results,
5268+ 1 ,
5269+ 0 ,
5270+ & stopped,
5271+ 0 ,
5272+ )
5273+ . unwrap ( ) ;
5274+ worker. join ( ) . unwrap ( ) ;
5275+ native_results. drain_available ( 1 ) ;
5276+ finished_sender
5277+ . send ( ( resolved, native_results. pending_indices ( ) ) )
5278+ . unwrap ( ) ;
5279+ } ) ;
5280+
5281+ let ( resolved, native_indices) = finished_receiver
5282+ . recv_timeout ( Duration :: from_secs ( 5 ) )
5283+ . expect ( "resolution wait deadlocked behind the full native-result channel" ) ;
5284+ assert_eq ! ( resolved, ( vec![ 1 ] , vec![ 2 ] , vec![ 3 ] ) ) ;
5285+ assert_eq ! ( native_indices, [ 1 , 2 ] ) ;
5286+ }
51225287}
0 commit comments