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 a27b596c fix(arrow/array): avoid copying initialized extension
refcounts (#1089)
a27b596c is described below
commit a27b596cc5067442294b9d38071b0576f9a285e9
Author: Minh Vu <[email protected]>
AuthorDate: Fri Aug 7 18:30:40 2026 +0200
fix(arrow/array): avoid copying initialized extension refcounts (#1089)
### Rationale for this change
The extension array constructor initializes the base reference count and
then copies the complete value through reflection. That can copy an
atomic after first use and triggers the copylocks vet warning.
### What changes are included in this PR?
Allocate the concrete extension array first and initialize its embedded
ExtensionArrayBase directly in the destination.
### Are these changes tested?
- `go test ./arrow/array`
- go vet ./arrow/array
### Are there any user-facing changes?
No API changes. This corrects the reported behavior while preserving the
existing ownership and compatibility contracts.
---
arrow/array/extension.go | 22 +++++++++++++---------
arrow/array/extension_test.go | 19 +++++++++++++++++++
2 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/arrow/array/extension.go b/arrow/array/extension.go
index e509b5e0..5a4d3c86 100644
--- a/arrow/array/extension.go
+++ b/arrow/array/extension.go
@@ -85,19 +85,23 @@ func NewExtensionArrayWithStorage(dt arrow.ExtensionType,
storage arrow.Array) a
// NewExtensionData expects a data with a datatype of arrow.ExtensionType and
// underlying data built for the storage array.
func NewExtensionData(data arrow.ArrayData) ExtensionArray {
- base := ExtensionArrayBase{}
- base.refCount.Add(1)
- base.setData(data.(*Data))
+ if data.DataType().ID() != arrow.EXTENSION {
+ panic("arrow/array: must use extension type to construct an
extension array")
+ }
+ dtype, ok := data.DataType().(arrow.ExtensionType)
+ if !ok {
+ panic("arrow/array: DataType for ExtensionArray must implement
arrow.ExtensionType")
+ }
// use the ExtensionType's ArrayType to construct the correctly typed
object
// to use as the ExtensionArray interface. reflect.New returns a
pointer to
// the newly created object.
- arr := reflect.New(base.ExtensionType().ArrayType())
- // set the embedded ExtensionArrayBase to the value we created above.
We know
- // that this field will exist because the interface requires embedding
ExtensionArrayBase
- // so we don't have to separately check, this will panic if called on
an ArrayType
- // that doesn't embed ExtensionArrayBase which is what we want.
- arr.Elem().FieldByName("ExtensionArrayBase").Set(reflect.ValueOf(base))
+ arr := reflect.New(dtype.ArrayType())
+ // Initialize the embedded base in place so its atomic reference count
is not
+ // copied after first use.
+ base :=
arr.Elem().FieldByName("ExtensionArrayBase").Addr().Interface().(*ExtensionArrayBase)
+ base.refCount.Add(1)
+ base.setData(data.(*Data))
return arr.Interface().(ExtensionArray)
}
diff --git a/arrow/array/extension_test.go b/arrow/array/extension_test.go
index b2eba8e0..d3875eda 100644
--- a/arrow/array/extension_test.go
+++ b/arrow/array/extension_test.go
@@ -23,6 +23,7 @@ import (
"github.com/apache/arrow-go/v18/arrow/array"
"github.com/apache/arrow-go/v18/arrow/memory"
"github.com/apache/arrow-go/v18/internal/types"
+ "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
@@ -81,6 +82,24 @@ func (e *ExtensionTypeTestSuite) TestParametricArrays() {
e.True(array.RecordEqual(rb, rb))
}
+func TestNewExtensionArrayWithStorageReleasesAllReferences(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+
+ builder := array.NewInt32Builder(mem)
+ builder.Append(42)
+ storage := builder.NewInt32Array()
+ builder.Release()
+
+ ext := array.NewExtensionArrayWithStorage(types.NewParametric1Type(6),
storage)
+ require.IsType(t, &types.Parametric1Array{}, ext)
+ require.Equal(t, 1, ext.Len())
+ require.Equal(t, "42", ext.ValueStr(0))
+
+ ext.Release()
+ storage.Release()
+ mem.AssertSize(t, 0)
+}
+
func TestExtensionTypes(t *testing.T) {
suite.Run(t, new(ExtensionTypeTestSuite))
}