fix(rsqueue): buffer PollAddress errCh to prevent goroutine leak on abandoned poll#293
Merged
Conversation
…bandoned poll DatabaseQueue.PollAddress returned an unbuffered error channel. When the caller stopped receiving (e.g. its request context was canceled and it returned), the polling goroutine's single error send parked forever — leaking one goroutine per abandoned poll. Under load these accumulate and degrade the server. Buffer the channel by 1: the goroutine sends at most one value before closing, so a single slot guarantees the send never blocks regardless of whether a receiver is still present. Adds TestPollErrAbandonedReceiver (leaktest) covering the abandoned-receiver case. It uses the correct `defer leaktest.Check(c)()` form; the existing poll tests call `defer leaktest.Check(c)` without invoking the returned checker, so their leak assertion is a no-op (noted, out of scope to fix here). Fixes the platform-lib side of rstudio/package-manager#19008. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DatabaseQueue.PollAddressreturned an unbuffered error channel. Its polling goroutine has exactly one send (errCh <- erron a non-lock store error). When the caller stops receiving — e.g. its request context is canceled and it returns from theselect { case <-ctx.Done(): ... case err = <-errCh: }wait inrscache/file.go— that send parks forever, leaking one goroutine per abandoned poll. Under bulk request pile-ups these accumulate and degrade the server.Downstream this is rstudio/package-manager#19008 (blocked
PollAddresssenders observed in production goroutine dumps).Fix
Buffer the channel by 1 (
make(chan error, 1)). The goroutine sends at most one value before closing, so a single slot guarantees the send never blocks regardless of whether a receiver is still present. This mirrors the "guarantee the sender can always complete" approach rather than depending on a live receiver.rscache/file.go(fileCache.get/Get);RecordingProducer.PollAddressis a test double and unaffected.Test
Adds
TestPollErrAbandonedReceiver— starts a poll, never receives from the returned channel, and asserts (vialeaktest) the poll goroutine has exited. RED before the fix (leaked goroutine parked at the send), GREEN after.Note: the new test uses the correct
defer leaktest.Check(c)()form. The existing poll tests calldefer leaktest.Check(c)without invoking the returned checker, so their leak assertion is currently a no-op — flagged here; fixing those is out of scope for this change.go test ./pkg/rsqueue/... ./pkg/rscache/...andgo vetpass.Downstream
Package Manager (#19008) vendors this module; once released (a patch tag, e.g.
v4.3.1), PPM will bumpgo.mod+go mod vendorand add a### FixedNEWS entry referencing #19008.