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() }

Reply via email to