zeroshade commented on code in PR #1938:
URL: https://github.com/apache/iceberg-go/pull/1938#discussion_r3883475169


##########
table/arrow_scanner.go:
##########
@@ -1769,59 +1890,81 @@ func createIterator(ctx context.Context, numWorkers 
uint, records <-chan enumera
        }
 }
 
-func (as *arrowScan) recordBatchesFromTasksAndDeletes(ctx context.Context, 
tasks []FileScanTask, deletesPerFile perFilePosDeletes, dvBitmaps 
perFileDVBitmaps, eqDeleteSets map[int][]*equalityDeleteSet, invariants 
*arrowScanInvariants) iter.Seq2[arrow.RecordBatch, error] {
-       extSet := substrait.NewExtensionSet()
-
-       ctx, cancel := context.WithCancelCause(exprs.WithExtensionIDSet(ctx, 
extSet))
-       taskChan := make(chan tblutils.Enumerated[FileScanTask], len(tasks))
-
-       // numWorkers := 1
-       numWorkers := min(as.concurrency, len(tasks))
-       records := make(chan enumeratedRecord, numWorkers)
+func (as *arrowScan) recordBatchesFromTasksAndDeletes(ctx context.Context, 
tasks []FileScanTask, positionDeleteLoader *lazyPositionDeleteLoader, dvBitmaps 
perFileDVBitmaps, eqDeleteSets map[int][]*equalityDeleteSet, invariants 
*arrowScanInvariants) iter.Seq2[arrow.RecordBatch, error] {
+       return func(yield func(arrow.RecordBatch, error) bool) {
+               extSet := substrait.NewExtensionSet()
+               scanCtx, cancel := 
context.WithCancelCause(exprs.WithExtensionIDSet(ctx, extSet))
+               numWorkers := min(as.concurrency, len(tasks))
+               taskChan := make(chan tblutils.Enumerated[FileScanTask], 
len(tasks))
+               records := make(chan enumeratedRecord, numWorkers)
+
+               var wg sync.WaitGroup
+               wg.Add(numWorkers)
+               for range numWorkers {
+                       go func() {
+                               defer wg.Done()
+                               for {
+                                       select {
+                                       case <-scanCtx.Done():
+                                               return
+                                       case task, ok := <-taskChan:
+                                               if !ok {
+                                                       return
+                                               }
+                                               if scanCtx.Err() != nil {
+                                                       return
+                                               }
+
+                                               filePath := 
task.Value.File.FilePath()
+                                               var positionalDeletes 
positionDeletes
+                                               if positionDeleteLoader != nil {
+                                                       var err error
+                                                       positionalDeletes, err 
= positionDeleteLoader.load(scanCtx, task.Value)
+                                                       if err != nil {
+                                                               records <- 
enumeratedRecord{Task: task, Err: err}
+                                                               cancel(err)
+
+                                                               return
+                                                       }
+                                               }
+
+                                               if err := 
as.recordsFromTask(scanCtx, task, records,
+                                                       positionalDeletes,
+                                                       dvBitmaps[filePath],
+                                                       
eqDeleteSets[task.Index],
+                                                       invariants); err != nil 
{
+                                                       cancel(err)
+
+                                                       return
+                                               }
+                                       }
+                               }
+                       }()
+               }
 
-       var wg sync.WaitGroup
-       wg.Add(numWorkers)
-       for range numWorkers {
                go func() {
-                       defer wg.Done()
-                       for {
+                       for i, t := range tasks {
                                select {
-                               case <-ctx.Done():
+                               case <-scanCtx.Done():

Review Comment:
   **[P1] Cancellation can deadlock iteration indefinitely.** This return 
bypasses `close(taskChan)`, `wg.Wait()`, and `close(records)`. 
`createIteratorWithCleanup` then cancels and drains the sequenced channel, but 
`MakeSequencedChan` cannot close because its `records` source remains open. I 
reproduced this with one valid task and an already-canceled context: iteration 
failed to return within 200 ms on the first attempt. Please make producer 
teardown unconditional (for example, defer closing `taskChan`, waiting for 
workers, and closing `records`) and add pre-canceled and early-termination 
regression tests.



##########
table/arrow_scanner.go:
##########
@@ -1769,59 +1890,81 @@ func createIterator(ctx context.Context, numWorkers 
uint, records <-chan enumera
        }
 }
 
-func (as *arrowScan) recordBatchesFromTasksAndDeletes(ctx context.Context, 
tasks []FileScanTask, deletesPerFile perFilePosDeletes, dvBitmaps 
perFileDVBitmaps, eqDeleteSets map[int][]*equalityDeleteSet, invariants 
*arrowScanInvariants) iter.Seq2[arrow.RecordBatch, error] {
-       extSet := substrait.NewExtensionSet()
-
-       ctx, cancel := context.WithCancelCause(exprs.WithExtensionIDSet(ctx, 
extSet))
-       taskChan := make(chan tblutils.Enumerated[FileScanTask], len(tasks))
-
-       // numWorkers := 1
-       numWorkers := min(as.concurrency, len(tasks))
-       records := make(chan enumeratedRecord, numWorkers)
+func (as *arrowScan) recordBatchesFromTasksAndDeletes(ctx context.Context, 
tasks []FileScanTask, positionDeleteLoader *lazyPositionDeleteLoader, dvBitmaps 
perFileDVBitmaps, eqDeleteSets map[int][]*equalityDeleteSet, invariants 
*arrowScanInvariants) iter.Seq2[arrow.RecordBatch, error] {
+       return func(yield func(arrow.RecordBatch, error) bool) {
+               extSet := substrait.NewExtensionSet()
+               scanCtx, cancel := 
context.WithCancelCause(exprs.WithExtensionIDSet(ctx, extSet))
+               numWorkers := min(as.concurrency, len(tasks))
+               taskChan := make(chan tblutils.Enumerated[FileScanTask], 
len(tasks))
+               records := make(chan enumeratedRecord, numWorkers)
+
+               var wg sync.WaitGroup
+               wg.Add(numWorkers)
+               for range numWorkers {
+                       go func() {
+                               defer wg.Done()
+                               for {
+                                       select {
+                                       case <-scanCtx.Done():
+                                               return
+                                       case task, ok := <-taskChan:
+                                               if !ok {
+                                                       return
+                                               }
+                                               if scanCtx.Err() != nil {
+                                                       return
+                                               }
+
+                                               filePath := 
task.Value.File.FilePath()
+                                               var positionalDeletes 
positionDeletes
+                                               if positionDeleteLoader != nil {
+                                                       var err error
+                                                       positionalDeletes, err 
= positionDeleteLoader.load(scanCtx, task.Value)
+                                                       if err != nil {
+                                                               records <- 
enumeratedRecord{Task: task, Err: err}

Review Comment:
   **[P2] Deferred positional-delete errors can leak out-of-order Arrow 
batches.** A later task can emit a batch while an earlier task is loading its 
delete file. If the earlier load then fails here, the sequencer emits the error 
but abandons batches still held in its priority queue when `records` closes; 
iterator cleanup never sees those records to release them. A checked-allocator 
probe with task 1 queued ahead of task 0’s error reproduced a 128-byte Arrow 
leak. Please give the sequencer an error/close discard path that releases 
queued record batches, with a deterministic two-worker regression test.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to