Skip to content

Race condition between StreamRow() and Close() #4790

@e-gineer

Description

@e-gineer

Description

While the wrapper's Close() method is now idempotent (bug #4712 fix verified), there's a critical race condition when StreamRow() is called concurrently with Close(). The race detector shows data race when Close() closes the RowChan while concurrent StreamRow() call tries to send.

When Close() closes the RowChan, any concurrent StreamRow() call will panic with "send on closed channel".

Severity

HIGH - Panic on concurrent Close() and StreamRow()

Reproduction

  1. Create a Result and start streaming rows from a goroutine
  2. After reading partial results, call Close() while the streaming goroutine is still active
  3. Observe panic: "send on closed channel"
  4. Run with -race to see data race

Expected Behavior

StreamRow() should be safe to call concurrently with Close(). Options:

  1. Add synchronization to check if channel is closed before sending
  2. Document that callers must ensure no concurrent StreamRow() calls during Close()
  3. Use a done channel pattern to signal writers to stop before closing

Test Reference

Test: TestResult_CloseAfterPartialRead in pkg/query/queryresult/result_test.go:149 (currently skipped)

Suggested Fix

Add a closed flag in the wrapper:

type Result struct {
    *queryresult.Result[TimingResultStream]
    closeOnce sync.Once
    closed    atomic.Bool
}

func (r *Result) StreamRow(row []interface{}) {
    if !r.closed.Load() {
        r.Result.StreamRow(row)
    }
}

Related Code

  • pkg/query/queryresult/result.go:22-26
  • pipe-fittings/v2/queryresult/result.go:56 - StreamRow implementation

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstaleNo recent activity has been detected on this issue/PR and it will be closed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions