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/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new a99b4506 fix(arrow/csv): release Reader record builders (#1059)
a99b4506 is described below

commit a99b4506c2113363799cfd198631ef2559c9355f
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 5 19:44:12 2026 +0200

    fix(arrow/csv): release Reader record builders (#1059)
    
    ### Rationale for this change
    
    Every CSV reader owns a `RecordBuilder`, but final `Reader.Release` did
    not release it. Header processing with an explicit schema also replaced
    the initial builder without releasing it, leaking two builders on that
    path.
    
    ### What changes are included in this PR?
    
    Release the old builder before header-driven replacement, and release
    the active builder when the reader reference count reaches zero.
    
    ### Are these changes tested?
    
    Yes. The regression test uses a supported custom extension builder that
    reserves allocator-backed storage when constructed and counts releases.
    It covers explicit-schema readers with and without headers, proving that
    one builder is released on the direct path and both builders are
    released on the header-replacement path.
    
    `go test ./arrow/csv -run TestReaderReleaseFreesRecordBuilders`
    
    ### Are there any user-facing changes?
    
    No API changes. Releasing a CSV reader now frees every builder it owns.
---
 arrow/csv/reader.go      |  5 +++++
 arrow/csv/reader_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+)

diff --git a/arrow/csv/reader.go b/arrow/csv/reader.go
index 9f3c7724..0d4ee0f3 100644
--- a/arrow/csv/reader.go
+++ b/arrow/csv/reader.go
@@ -166,6 +166,7 @@ func (r *Reader) readHeader() error {
 
                meta := r.schema.Metadata()
                r.schema = arrow.NewSchema(fields, &meta)
+               r.bld.Release()
                r.bld = array.NewRecordBuilder(r.mem, r.schema)
                return nil
        }
@@ -943,6 +944,10 @@ func (r *Reader) Release() {
                if r.cur != nil {
                        r.cur.Release()
                }
+               if r.bld != nil {
+                       r.bld.Release()
+                       r.bld = nil
+               }
        }
 }
 
diff --git a/arrow/csv/reader_test.go b/arrow/csv/reader_test.go
index 60af76fa..acc516aa 100644
--- a/arrow/csv/reader_test.go
+++ b/arrow/csv/reader_test.go
@@ -496,6 +496,54 @@ rec[2]["date64"]: [(null)]
        }
 }
 
+type releaseCountingBuilder struct {
+       array.Builder
+       releases *int
+}
+
+func (b *releaseCountingBuilder) Release() {
+       *b.releases++
+       b.Builder.Release()
+}
+
+type preallocatingUUIDType struct {
+       *extensions.UUIDType
+       releases *int
+}
+
+func (t *preallocatingUUIDType) NewBuilder(mem memory.Allocator) array.Builder 
{
+       b := extensions.NewUUIDBuilder(mem)
+       b.Reserve(1)
+       return &releaseCountingBuilder{Builder: b, releases: t.releases}
+}
+
+func TestReaderReleaseFreesRecordBuilders(t *testing.T) {
+       for _, withHeader := range []bool{false, true} {
+               t.Run(fmt.Sprintf("header=%t", withHeader), func(t *testing.T) {
+                       mem := 
memory.NewCheckedAllocator(memory.DefaultAllocator)
+                       var releases int
+                       extType := &preallocatingUUIDType{
+                               UUIDType: extensions.NewUUIDType(),
+                               releases: &releases,
+                       }
+                       schema := arrow.NewSchema([]arrow.Field{{Name: "uuid", 
Type: extType}}, nil)
+                       r := 
csv.NewReader(strings.NewReader("uuid\n00000000-0000-0000-0000-000000000001\n"),
 schema,
+                               csv.WithAllocator(mem), 
csv.WithHeader(withHeader))
+                       if withHeader {
+                               require.True(t, r.Next())
+                       }
+                       r.Release()
+
+                       expectedReleases := 1
+                       if withHeader {
+                               expectedReleases = 2
+                       }
+                       require.Equal(t, expectedReleases, releases)
+                       mem.AssertSize(t, 0)
+               })
+       }
+}
+
 func TestCSVReaderWithChunk(t *testing.T) {
        mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
        defer mem.AssertSize(t, 0)

Reply via email to