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 2d123f94 fix(arrow/array): silence copylocks warning in
numeric_generic.go (#823)
2d123f94 is described below
commit 2d123f9469eecae89ffc002d77b4514da4ff95d1
Author: Matt Topol <[email protected]>
AuthorDate: Sun May 24 18:26:39 2026 -0400
fix(arrow/array): silence copylocks warning in numeric_generic.go (#823)
### Rationale for this change
`go vet ./arrow/array/` reports:
```
arrow/array/numeric_generic.go:39:9: return copies lock value:
numericArray[T] contains array contains sync/atomic.Int64 contains
sync/atomic.noCopy
```
The `newNumericData` helper instantiated a `numericArray[T]` zero value,
mutated it, and returned it **by value**. Returning this value copies
the embedded `array` struct, which contains an `atomic.Int64` refcount
with a `noCopy` marker. The 15 `New<T>Data` callers then copy the value
a second time when embedding it into their wrapper struct literal (e.g.
`return &Int64{numericArray: newNumericData[int64](data)}`).
In practice the refcount value is only copied during single-threaded
construction before the resulting `*Type` pointer is shared, so no race
exists today (this is acknowledged in the original commit that
introduced `atomic.Int64` — #326). However, the warning is permanent
noise in `go vet` output, masks any future *real* copylocks regressions
in this code path, and signals a fragile pattern that one careless
refactor would turn into a real bug.
### What changes are included in this PR?
- Replace `newNumericData[T](data) numericArray[T]` with
`initNumericArray[T](a *numericArray[T], data)`. The helper now
initializes an already-allocated `numericArray[T]` in place — no value
return, no copy.
- Update all 15 `New<T>Data` constructors (`NewInt64Data`,
`NewUint64Data`, `NewInt32Data`, `NewUint32Data`, `NewInt16Data`,
`NewUint16Data`, `NewInt8Data`, `NewUint8Data`, `NewFloat32Data`,
`NewFloat64Data`, `NewDate32Data`, `NewDate64Data`, `NewTime32Data`,
`NewTime64Data`, `NewDurationData`) to allocate the concrete wrapper on
the heap first, then pass a pointer to the embedded `numericArray` field
via Go field promotion.
This mirrors the existing idiom already used by `NewStringData`,
`NewBinaryData`, and `newDecimalData` in this same package.
### Are these changes tested?
- `go vet ./arrow/array/` is clean for `numeric_generic.go` (an
unrelated `extension.go:100` warning involving `reflect.ValueOf` remains
and is out of scope for this issue).
- `go test ./arrow/...` passes (15 packages).
- `go test ./parquet/pqarrow/...` passes (excluding pre-existing tests
that require `PARQUET_TEST_DATA`).
### Are there any user-facing changes?
No. `numericArray[T]` is unexported and the public `New<T>Data`
signatures and runtime behavior are unchanged. The number of heap
allocations per constructor is the same as before — fields are now
constructed directly inside the heap-allocated wrapper rather than
copied into it.
Closes #821
---
arrow/array/numeric_generic.go | 67 ++++++++++++++++++++++++++++++------------
1 file changed, 49 insertions(+), 18 deletions(-)
diff --git a/arrow/array/numeric_generic.go b/arrow/array/numeric_generic.go
index 874e86d6..1b671fc7 100644
--- a/arrow/array/numeric_generic.go
+++ b/arrow/array/numeric_generic.go
@@ -32,11 +32,12 @@ type numericArray[T arrow.IntType | arrow.UintType |
arrow.FloatType] struct {
values []T
}
-func newNumericData[T arrow.IntType | arrow.UintType | arrow.FloatType](data
arrow.ArrayData) numericArray[T] {
- a := numericArray[T]{}
+// initNumericArray initializes an already-allocated numericArray[T] in place.
+// It takes a pointer (rather than returning a value) so that the embedded
+// atomic refcount is never copied, which would otherwise trigger copylocks.
+func initNumericArray[T arrow.IntType | arrow.UintType | arrow.FloatType](a
*numericArray[T], data arrow.ArrayData) {
a.refCount.Add(1)
a.setData(data.(*Data))
- return a
}
func (a *numericArray[T]) Reset(data *Data) {
@@ -237,7 +238,9 @@ type Duration struct {
}
func NewDurationData(data arrow.ArrayData) *Duration {
- return &Duration{numericArray: newNumericData[arrow.Duration](data)}
+ a := &Duration{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Duration) DurationValues() []arrow.Duration { return a.Values() }
@@ -270,7 +273,9 @@ type Int64 struct {
}
func NewInt64Data(data arrow.ArrayData) *Int64 {
- return &Int64{numericArray: newNumericData[int64](data)}
+ a := &Int64{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Int64) Int64Values() []int64 { return a.Values() }
@@ -280,7 +285,9 @@ type Uint64 struct {
}
func NewUint64Data(data arrow.ArrayData) *Uint64 {
- return &Uint64{numericArray: newNumericData[uint64](data)}
+ a := &Uint64{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Uint64) Uint64Values() []uint64 { return a.Values() }
@@ -290,7 +297,9 @@ type Float32 struct {
}
func NewFloat32Data(data arrow.ArrayData) *Float32 {
- return &Float32{floatArray[float32]{newNumericData[float32](data)}}
+ a := &Float32{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Float32) Float32Values() []float32 { return a.Values() }
@@ -300,7 +309,9 @@ type Float64 struct {
}
func NewFloat64Data(data arrow.ArrayData) *Float64 {
- return &Float64{floatArray:
floatArray[float64]{newNumericData[float64](data)}}
+ a := &Float64{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Float64) Float64Values() []float64 { return a.Values() }
@@ -310,7 +321,9 @@ type Int32 struct {
}
func NewInt32Data(data arrow.ArrayData) *Int32 {
- return &Int32{newNumericData[int32](data)}
+ a := &Int32{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Int32) Int32Values() []int32 { return a.Values() }
@@ -320,7 +333,9 @@ type Uint32 struct {
}
func NewUint32Data(data arrow.ArrayData) *Uint32 {
- return &Uint32{numericArray: newNumericData[uint32](data)}
+ a := &Uint32{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Uint32) Uint32Values() []uint32 { return a.Values() }
@@ -330,7 +345,9 @@ type Int16 struct {
}
func NewInt16Data(data arrow.ArrayData) *Int16 {
- return &Int16{newNumericData[int16](data)}
+ a := &Int16{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Int16) Int16Values() []int16 { return a.Values() }
@@ -340,7 +357,9 @@ type Uint16 struct {
}
func NewUint16Data(data arrow.ArrayData) *Uint16 {
- return &Uint16{numericArray: newNumericData[uint16](data)}
+ a := &Uint16{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Uint16) Uint16Values() []uint16 { return a.Values() }
@@ -350,7 +369,9 @@ type Int8 struct {
}
func NewInt8Data(data arrow.ArrayData) *Int8 {
- return &Int8{oneByteArrs[int8]{newNumericData[int8](data)}}
+ a := &Int8{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Int8) Int8Values() []int8 { return a.Values() }
@@ -360,7 +381,9 @@ type Uint8 struct {
}
func NewUint8Data(data arrow.ArrayData) *Uint8 {
- return &Uint8{oneByteArrs[uint8]{newNumericData[uint8](data)}}
+ a := &Uint8{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Uint8) Uint8Values() []uint8 { return a.Values() }
@@ -370,7 +393,9 @@ type Time32 struct {
}
func NewTime32Data(data arrow.ArrayData) *Time32 {
- return
&Time32{timeArray[arrow.Time32]{newNumericData[arrow.Time32](data)}}
+ a := &Time32{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Time32) Time32Values() []arrow.Time32 { return a.Values() }
@@ -380,7 +405,9 @@ type Time64 struct {
}
func NewTime64Data(data arrow.ArrayData) *Time64 {
- return
&Time64{timeArray[arrow.Time64]{newNumericData[arrow.Time64](data)}}
+ a := &Time64{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Time64) Time64Values() []arrow.Time64 { return a.Values() }
@@ -390,7 +417,9 @@ type Date32 struct {
}
func NewDate32Data(data arrow.ArrayData) *Date32 {
- return
&Date32{dateArray[arrow.Date32]{newNumericData[arrow.Date32](data)}}
+ a := &Date32{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Date32) Date32Values() []arrow.Date32 { return a.Values() }
@@ -400,7 +429,9 @@ type Date64 struct {
}
func NewDate64Data(data arrow.ArrayData) *Date64 {
- return
&Date64{dateArray[arrow.Date64]{newNumericData[arrow.Date64](data)}}
+ a := &Date64{}
+ initNumericArray(&a.numericArray, data)
+ return a
}
func (a *Date64) Date64Values() []arrow.Date64 { return a.Values() }