Skip to content

Fix MakeIngesterClient leaking grpc.ClientConn and goroutines on Run() failure - #7839

Open
DeviousCardi wants to merge 2 commits into
cortexproject:masterfrom
DeviousCardi:fix/7759-ingester-client-leak
Open

DeviousCardi wants to merge 2 commits into
cortexproject:masterfrom
DeviousCardi:fix/7759-ingester-client-leak

Conversation

@DeviousCardi

@DeviousCardi DeviousCardi commented Sep 15, 2026 •

Copy link
Copy Markdown

Summary

Fixes #7759.

When useStreamConnection is true, MakeIngesterClient creates a grpc.ClientConn, then calls Run() to start INGESTER_CLIENT_STREAM_WORKER_COUNT (100) push-stream workers. If Run() returns an error, the function returned it without closing conn or cancelling the stream context — leaking the connection and any goroutines from workers that had already started successfully. In production this showed up as orphaned ClientConns reconnecting forever to unreachable ingester addresses after rollouts (observed via addrConn.resetTransportAndUnlock goroutine counts and steady SYN traffic to dead pod IPs).

Two related issues in the same Run() were fixed alongside the primary leak:

  • Orphaned workers on partial failure: as soon as one worker's PushStream() fails, streamCancel() is now called immediately so sibling workers that already succeeded stop rather than continuing to run against a client that's about to be discarded.
  • Data race: workerErr was written from all 100 worker goroutines with no synchronization. Replaced with a buffered chan error (sized to the worker count) so the first error is collected safely.

Changes

  • MakeIngesterClient: close conn and cancel streamCtx before returning an error from Run().
  • Run(): collect worker errors via a channel instead of an unsynchronized shared variable; cancel streamCtx as soon as any worker fails, so siblings stop promptly.
  • Added a CHANGELOG.md entry.

Test plan

  • go build ./pkg/ingester/client/...
  • go test ./pkg/ingester/client/... -race — including two new tests: a unit test with a mock scheduler exercising partial-failure cancellation, and an end-to-end test that reproduces the issue's own scenario (unreachable address) and asserts via runtime.Stack scanning that no resetTransportAndUnlock/newClientStreamWithParams goroutines survive after MakeIngesterClient returns an error.
  • Ran the new tests 20x under -race with no flakiness.

🤖 Generated with Claude Code

Written by Codex.

@DeviousCardi
DeviousCardi requested a review from a team as a code owner September 15, 2026 08:46
Comment thread CHANGELOG.md Outdated
@DeviousCardi
DeviousCardi force-pushed the fix/7759-ingester-client-leak branch from 2cc8757 to 70be36e Compare September 15, 2026 17:02

@friedrichg friedrichg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@DeviousCardi
DeviousCardi force-pushed the fix/7759-ingester-client-leak branch from ab46387 to d7679fc Compare October 8, 2026 03:13
When useStreamConnection is true, MakeIngesterClient() started stream-push
workers via Run() but discarded the already-dialed grpc.ClientConn and the
streamCtx/streamCancel pair on error, leaking the connection (and its
reconnect loop) plus any job-processing goroutines started by workers that
had already succeeded. Close the connection and cancel the stream context
before returning the error.

Run() also had two secondary defects: workerErr was written by every worker
goroutine with no synchronization (a data race), and a failing worker never
signalled its siblings to stop, letting up to 99 successful workers and their
streams outlive the discarded client. Collect worker errors through a
buffered channel instead, and cancel streamCtx as soon as any worker fails so
siblings stop opening new streams and already-started job-processing
goroutines exit via ctx.Done().

Fixes cortexproject#7759

Signed-off-by: DeviousCardi <aaravsjadav@gmail.com>
Signed-off-by: DeviousCardi <aaravsjadav@gmail.com>
@DeviousCardi
DeviousCardi force-pushed the fix/7759-ingester-client-leak branch from d7679fc to cfe69ea Compare October 12, 2026 03:08

@sandy2008 sandy2008 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The leak fix looks right: both new tests fail on the merge-base and pass here, also under -race. Two small things inline. FYI, #7800 targets the same issue (#7759).

Comment on lines +235 to +236
streamCancel()
errCh <- err

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small ordering issue: since streamCancel() runs before errCh <- err, a sibling woken by the cancellation can enqueue its context canceled first, and Run then returns that instead of the real failure, contrary to the comment below. In a probe where one worker fails while the other 99 wait on ctx, that happened in about a quarter of the runs. Sending first is safe because errCh has room for every worker:

Suggested change
streamCancel()
errCh <- err
errCh <- err
streamCancel()

Comment thread CHANGELOG.md
* [BUGFIX] Tenant Federation: Fix regex tenant federation resolving to an empty user list right after startup. #7811
* [BUGFIX] Ingester: Don't count a forced head compaction skipped because blocks shipping is in progress as a failure. Previously such skips incremented `cortex_ingester_tsdb_compactions_failed_total`, producing spurious alerts. #7842
* [BUGFIX] Compactor: Fix the final cleanup of a tenant marked for deletion being reported as failed on object stores that return an error when deleting a missing object (GCS, Azure, Swift, OCI). #7861
* [BUGFIX] Ingester Client: Fix `MakeIngesterClient` leaking the `grpc.ClientConn` and stream-push worker goroutines when starting stream workers fails (`-distributor.use-stream-push=true`). Also fix a data race on the collected worker error, and stop already-started workers instead of leaving them running when a sibling worker fails. #7839

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since 1.22.0 was marked in progress (#7836), this line ends up in the ## 1.22.0 in progress section (I checked with a trial merge onto current master). Unless it's meant for a 1.22 backport, could you move it under ## master / unreleased?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MakeIngesterClient leaks grpc.ClientConn and goroutines on failure

3 participants