clickhouse-go
clickhouse-go copied to clipboard
wait until processImpl completes before closing stream chan
Fix race condition in query processing
Problem
There was a race condition between closing the stream channel and writing to it from a background goroutine in conn_query.go.
Race condition scenario:
-
conn_query.gostarts a goroutine that callsc.process(ctx, onProcess)(line 45) -
c.process()spawns another goroutine internally that callsc.processImpl()(line 106 inconn_process.go) -
c.processImpl()reads data from the server and callson.data(block)for each data block (line 170 inconn_process.go) - When the context is cancelled,
c.process()returns withctx.Err() - The outer goroutine in
conn_query.goimmediately closes thestreamchannel (line 54) -
However, the inner goroutine from step 2 may still be running and attempting to write to
streamviaon.data(block) - Writing to a closed channel causes a panic:
panic: send on closed channel
Stack trace example:
goroutine 1: close(stream)
goroutine 2: stream <- b // panic!
Solution
Modified conn_process.go to ensure that the process() function waits for the internal goroutine to complete before returning, even when the context is cancelled.
Changes:
- When
ctx.Done()is triggered, after callingc.cancel(), we now wait for the goroutine to finish by selecting onerrChordoneCh - This guarantees that
on.datawill not be called afterprocess()returns - The
streamchannel can now be safely closed inconn_query.gowithout race conditions
Impact
- Fixes panic when cancelling queries that are actively receiving data
- Ensures proper cleanup and resource management
- No breaking changes to public API
- Minimal performance impact (only adds synchronization on context cancellation path)
@nmerkulov thanks for the PR :) do you mind adding a test case to lock the behavior?