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)