You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by ze...@apache.org on 2026/06/24 19:38:04 UTC

(iceberg-go) branch main updated: fix(table): release empty scan batches (#1290)

This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new d60220fb fix(table): release empty scan batches (#1290)
d60220fb is described below

commit d60220fbfa3120d5d16897051c80d46fe816fe34
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Wed Jun 24 21:37:59 2026 +0200

    fix(table): release empty scan batches (#1290)
    
    Summary
    
    Release zero-row record batches before `createIterator` skips them. The
    iterator owns batches after pulling them from the sequenced channel, so
    an empty batch still needs the same cleanup as any other batch that is
    not yielded.
    
    The test uses a checked allocator and a zero-row slice backed by a real
    Arrow buffer, so it catches the leak directly instead of relying on a
    full scan setup.
    
    Testing
    
    - `go test ./table -run '^TestCreateIteratorReleasesEmptyRecordBatches$'
    -count=1`
    - `go test ./table -count=1`
    - `go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
    run --timeout=10m ./table/...`
    - `git diff --check`
---
 table/arrow_scanner.go      |  2 ++
 table/arrow_scanner_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/table/arrow_scanner.go b/table/arrow_scanner.go
index 3643de66..26b18d19 100644
--- a/table/arrow_scanner.go
+++ b/table/arrow_scanner.go
@@ -1277,6 +1277,8 @@ func createIterator(ctx context.Context, numWorkers uint, records <-chan enumera
 
 				if rec.NumRows() == 0 {
 					// skip empty records
+					rec.Release()
+
 					continue
 				}
 
diff --git a/table/arrow_scanner_test.go b/table/arrow_scanner_test.go
index b527d896..6cbe7170 100644
--- a/table/arrow_scanner_test.go
+++ b/table/arrow_scanner_test.go
@@ -18,6 +18,7 @@
 package table
 
 import (
+	"context"
 	"errors"
 	iofs "io/fs"
 	"strconv"
@@ -34,6 +35,7 @@ import (
 	"github.com/apache/arrow-go/v18/parquet/pqarrow"
 	"github.com/apache/iceberg-go"
 	iceio "github.com/apache/iceberg-go/io"
+	"github.com/apache/iceberg-go/table/internal"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -325,3 +327,46 @@ func TestReleasePerFilePosDeletes(t *testing.T) {
 		require.NotPanics(t, func() { releasePerFilePosDeletes(m) })
 	})
 }
+
+func TestCreateIteratorReleasesEmptyRecordBatches(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+	defer mem.AssertSize(t, 0)
+
+	bldr := array.NewInt64Builder(mem)
+	bldr.AppendValues([]int64{1, 2}, nil)
+	arr := bldr.NewArray()
+	bldr.Release()
+
+	emptyArr := array.NewSlice(arr, 0, 0)
+	arr.Release()
+
+	schema := arrow.NewSchema([]arrow.Field{
+		{Name: "id", Type: arrow.PrimitiveTypes.Int64},
+	}, nil)
+	batch := array.NewRecordBatch(schema, []arrow.Array{emptyArr}, 0)
+	emptyArr.Release()
+
+	records := make(chan enumeratedRecord, 1)
+	records <- enumeratedRecord{
+		Record: internal.Enumerated[arrow.RecordBatch]{
+			Value: batch,
+			Index: 0,
+			Last:  true,
+		},
+		Task: internal.Enumerated[FileScanTask]{
+			Index: 0,
+			Last:  true,
+		},
+	}
+	close(records)
+
+	ctx, cancel := context.WithCancelCause(context.Background())
+	itr := createIterator(ctx, 1, records, nil, cancel, 0)
+	for rec, err := range itr {
+		require.NoError(t, err)
+		if rec != nil {
+			rec.Release()
+		}
+		require.Fail(t, "empty record batch should not be yielded")
+	}
+}