hcrosse opened a new issue, #1451:
URL: https://github.com/apache/arrow-go/issues/1451

   ### Describe the bug, including details regarding any error messages, 
version, and platform.
   
   `ExecuteScalarExpression` returns a nested struct field without retaining 
it. Releasing the input frees the result's buffers while the caller still holds 
the result, and releasing the result with `-tags assert` panics with `too many 
releases`.
   
   I reproduced this on main at `22c9f26d` and on v18.8.0, on macOS arm64, with 
`FieldRefList("s", "a")` over a two-row record. Without assertions, the 
`CheckedAllocator` reports zero bytes after the input is released, down from 
192, even though the result is still held.
   
   **Reproduction**
   
   Run it in a module that requires `github.com/apache/arrow-go/v18 v18.8.0`, 
or in a subdirectory of a checkout at `22c9f26d`, with `go run .` and then `go 
run -tags assert .`:
   
   ```go
   package main
   
   import (
        "context"
        "fmt"
        "strings"
   
        "github.com/apache/arrow-go/v18/arrow"
        "github.com/apache/arrow-go/v18/arrow/array"
        "github.com/apache/arrow-go/v18/arrow/compute"
        "github.com/apache/arrow-go/v18/arrow/compute/exprs"
        "github.com/apache/arrow-go/v18/arrow/memory"
   )
   
   func main() {
        mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
   
        sc := arrow.NewSchema([]arrow.Field{{Name: "s", Type: arrow.StructOf(
                arrow.Field{Name: "a", Type: arrow.PrimitiveTypes.Int32, 
Nullable: true}), Nullable: true}}, nil)
        rec, _, err := array.RecordFromJSON(mem, sc, strings.NewReader(`[{"s": 
{"a": 1}}, {"s": {"a": 2}}]`))
        if err != nil {
                panic(err)
        }
   
        bldr := exprs.NewExprBuilder(exprs.NewDefaultExtensionSet())
        if err := bldr.SetInputSchema(sc); err != nil {
                panic(err)
        }
        e, err := bldr.FieldRefList("s", "a").BuildExpr()
        if err != nil {
                panic(err)
        }
   
        ctx := compute.WithAllocator(context.Background(), mem)
        result, err := exprs.ExecuteScalarExpression(ctx, sc, e, 
compute.NewDatumWithoutOwning(rec))
        if err != nil {
                panic(err)
        }
        fmt.Printf("result=%v bytes allocated=%d\n", result, mem.CurrentAlloc())
   
        rec.Release()
        // The result still references s.a, so its buffers should still be 
allocated.
        fmt.Printf("after rec.Release, result still held: bytes 
allocated=%d\n", mem.CurrentAlloc())
   
        result.Release()
        fmt.Printf("after result.Release: bytes allocated=%d\n", 
mem.CurrentAlloc())
   }
   ```
   
   Output without assertions:
   
   ```text
   result=Array:{int32} bytes allocated=192
   after rec.Release, result still held: bytes allocated=0
   after result.Release: bytes allocated=0
   ```
   
   With `-tags assert`, the program instead panics at `result.Release()` with 
`too many releases`.
   
   **Expected behavior**
   
   The result should keep its buffers alive until the caller releases it, 
independently of the input record. Releasing both should not panic.
   
   **Cause and scope**
   
   `GetReferencedValue` returns the struct child borrowed from its input, 
without retaining it:
   
   
https://github.com/apache/arrow-go/blob/22c9f26df0abbc2066f5d9ae45f31720d29a4300/arrow/compute/exprs/field_refs.go#L205-L216
   
   `execFieldRef` returns that datum without taking a reference. Its callers 
release the result, including the cast and scalar-function paths. List-element 
and map-value references on scalar inputs return new scalars instead of 
borrowed values, so a fix cannot retain every result:
   
   - 
https://github.com/apache/arrow-go/blob/22c9f26df0abbc2066f5d9ae45f31720d29a4300/arrow/compute/exprs/exec.go#L558-L562
 releases the cast argument.
   - 
https://github.com/apache/arrow-go/blob/22c9f26df0abbc2066f5d9ae45f31720d29a4300/arrow/compute/exprs/exec.go#L590-L594
 releases each scalar-function argument.
   
   Top-level field references take their own reference and are not affected. 
This is separate from the type-mismatch leak in the same function, reported in 
https://github.com/apache/arrow-go/issues/1450. The reproduction exercises a 
successful nested field reference directly through `ExecuteScalarExpression`, 
not a cast or scalar function.
   
   A filter on a nested column takes the same path. On v18.8.0, evaluating 
`equal(s.a, 1)` and then releasing the record panics with `too many releases` 
under `-tags assert`, because the scalar function releases the record's own 
child.
   
   I have a patch with tests, built on the fix for that leak, and can open a PR.
   
   ### Component(s)
   
   Other
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to