Skip to content

fix(contextutil): prevent goroutine leak on context cancellation - #761

Open
G360-Niek wants to merge 1 commit into
projectdiscovery:mainfrom
guardian360:fix/contextutil-goroutine-leak
Open

fix(contextutil): prevent goroutine leak on context cancellation#761
G360-Niek wants to merge 1 commit into
projectdiscovery:mainfrom
guardian360:fix/contextutil-goroutine-leak

Conversation

@G360-Niek

Copy link
Copy Markdown

What

ExecFunc, ExecFuncWithTwoReturns, and ExecFuncWithThreeReturns run fn in a goroutine and return via ctx.Done() when the context is cancelled before fn finishes. The worker then sends its result on an unbuffered channel that no longer has a receiver and blocks forever — leaking one goroutine (and, through reader.ConnReadN, the underlying connection) per cancelled call.

This buffers each result channel (cap 1) so the worker can always send and exit even after the caller has returned.

Proof (added regression tests)

context/leak_test.go drives 50 context-cancelled calls per function whose fn outlives the context, then asserts the goroutine count returns to baseline.

  • Before (unbuffered): all three fail — ~50 goroutines still alive after 50 context-cancelled calls.
  • After (buffered): all three pass in ~0.3s; the full context/ suite and -race stay green.

Scope

This fixes the root leak in the ExecFunc* helpers, which also makes reader.ConnReadN fully cleanable — a caller that closes the connection on cancellation now leaks nothing. The one residual case (a ConnReadN whose caller never closes the connection leaves the worker parked in FD.Read) would need ConnReadN to close / set a read deadline on cancel; happy to follow up separately if you'd like that addressed here too.

Refs #759

ExecFunc, ExecFuncWithTwoReturns and ExecFuncWithThreeReturns run fn in a
goroutine and return via ctx.Done() when the context is cancelled before
fn finishes. The worker then sends its result on an unbuffered channel
that no longer has a receiver and blocks forever, leaking one goroutine
(and, through reader.ConnReadN, the underlying connection) per cancelled
call.

Buffer each result channel (cap 1) so the worker can always send and exit
even after the caller has already returned.

Add regression tests that drive many context-cancelled calls whose fn
outlives the context and assert the goroutine count returns to baseline;
they fail on the unbuffered channels and pass with the fix.

Refs projectdiscovery#759
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant