@@ -608,8 +608,11 @@ impl PartitionStream for PyArrowStreamPartition {
608608 let factory = Python :: attach ( |py| self . stream_factory . clone_ref ( py) ) ;
609609
610610 // Create a lazy stream using try_stream! macro.
611- // This is cleaner than manual state management with unfold.
612- // Each iteration acquires the GIL and reads one batch.
611+ // The GIL is acquired only for the duration of each Python call
612+ // and released between batches. After each yielded batch we call
613+ // yield_now() to explicitly suspend this task, giving the Tokio
614+ // executor a chance to poll other partition streams (which can then
615+ // acquire the GIL and make progress in parallel).
613616 let batch_stream = try_stream ! {
614617 // Call factory to get the PyArrow RecordBatchReader
615618 let reader: Py <PyAny > = Python :: attach( |py| {
@@ -618,7 +621,10 @@ impl PartitionStream for PyArrowStreamPartition {
618621 } )
619622 } ) ?;
620623
621- // Read batches until StopIteration
624+ // Read batches until StopIteration.
625+ // The GIL is released between iterations; yield_now() ensures
626+ // other async tasks (i.e., other partitions) are scheduled
627+ // before this stream is polled again.
622628 loop {
623629 let batch_result = Python :: attach( |py| {
624630 let bound_reader = reader. bind( py) ;
@@ -644,7 +650,12 @@ impl PartitionStream for PyArrowStreamPartition {
644650 } ) ;
645651
646652 match batch_result {
647- Ok ( Some ( batch) ) => yield batch,
653+ Ok ( Some ( batch) ) => {
654+ yield batch;
655+ // Yield to the executor so that other partition
656+ // streams can acquire the GIL and make progress.
657+ tokio:: task:: yield_now( ) . await ;
658+ }
648659 Ok ( None ) => break ,
649660 Err ( e) => Err ( e) ?,
650661 }
0 commit comments