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 d1a74fbc fix(arrow/compute): do not return a result from a cancelled 
call (#1410)
d1a74fbc is described below

commit d1a74fbc9869a373408cfbe8783f01a19428a208
Author: Matt Topol <[email protected]>
AuthorDate: Fri Oct 23 18:34:20 2026 +0100

    fix(arrow/compute): do not return a result from a cancelled call (#1410)
    
    ### Rationale for this change
    
    `TestCallFunctionPreservesCallerCancellationForVectorFunction` fails
    intermittently under `-race` (e.g. ARM64 macOS 14 Go 1.26 - CGO on
    #1406):
    
    ```
    --- FAIL: TestCallFunctionPreservesCallerCancellationForVectorFunction
        exec_test.go:385: Expected nil, but got: &compute.ArrayDatum{...}
    ```
    
    It is a real race, not a test problem: a cancelled `CallFunction` can
    return a result with a nil error.
    
    - The kernel gets the **caller's** context: `kctx :=
    &exec.KernelCtx{Ctx: ctx, ...}` in `execInternal`.
    - `execInternal` then derives a **child** context
    (`context.WithCancel(ctx)`), and every cancellation check after that
    (`WrapResults`, and the `ctx.Err()` check after it) uses the child.
    - Cancelling a context closes its `Done` channel *before* cancelling its
    children. In that gap the kernel can wake on the caller's cancellation,
    return, and the executor can send its output and close the channel;
    `WrapResults` then collects the output while the child context still
    reports no error, and `execInternal` returns it.
    
    On main, running that test 20,000 times under `-race` fails 15 times.
    
    ### What changes are included in this PR?
    
    `execInternal` keeps a reference to the caller's context and, after
    `WrapResults`, treats the call as cancelled if either the caller's or
    the child context is done. The returned error is the caller's
    cancellation cause when the caller cancelled, otherwise the child's
    (unchanged). The kernel, executors and `WrapResults` are unchanged.
    
    ### Are these changes tested?
    
    By the existing
    `TestCallFunctionPreservesCallerCancellationForVectorFunction` (and its
    scalar counterpart), which is the regression test for this race:
    
    - main: 15 failures in 20,000 runs under `-race`;
    - this PR: 0 failures in 20,000 runs under `-race`.
    
    `go test ./arrow/compute/...` and `go test -race ./arrow/compute` pass;
    `go vet` (with `-asmdecl=false -framepointer=false` for the existing
    assembly warnings) and `gofmt` are clean.
    
    ### Are there any user-facing changes?
    
    Yes: a cancelled compute call no longer occasionally returns a result
    with a nil error. No API changes.
---
 arrow/compute/exec.go | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/arrow/compute/exec.go b/arrow/compute/exec.go
index 5a68f11d..528a0409 100644
--- a/arrow/compute/exec.go
+++ b/arrow/compute/exec.go
@@ -166,6 +166,11 @@ func execInternal(ctx context.Context, fn Function, opts 
FunctionOptions, passed
 
        ectx := GetExecCtx(ctx)
 
+       // The kernel watches the caller's context (kctx above), and cancelling 
a
+       // parent closes its Done channel before its children are cancelled. 
Check
+       // callerCtx as well as ctx below, so a result produced in that gap is 
not
+       // returned from a cancelled call.
+       callerCtx := ctx
        ctx, cancel := context.WithCancel(ctx)
        defer cancel()
 
@@ -178,14 +183,18 @@ func execInternal(ctx context.Context, fn Function, opts 
FunctionOptions, passed
        }()
 
        result = executor.WrapResults(ctx, ch, haveChunkedArray(input.Values))
-       if ctx.Err() != nil {
+       if callerCtx.Err() != nil || ctx.Err() != nil {
                for value := range ch {
                        if value != nil {
                                value.Release()
                        }
                }
                if err == nil {
-                       err = context.Cause(ctx)
+                       if callerCtx.Err() != nil {
+                               err = context.Cause(callerCtx)
+                       } else {
+                               err = context.Cause(ctx)
+                       }
                }
                if result != nil {
                        result.Release()

Reply via email to