fix(nodejs): answer queued rebalance events when the consumer stops - #90348
Draft
jose-sequeira wants to merge 2 commits into
Draft
fix(nodejs): answer queued rebalance events when the consumer stops#90348jose-sequeira wants to merge 2 commits into
jose-sequeira wants to merge 2 commits into
Conversation
Contributor
🤖 CI report✅ Trunk lane — non-backend laneThis PR is assigned to the non-backend lane. It does not run backend Python tests and may merge in parallel with PRs in other lanes. |
1 task
stopConsuming flips running and the loop exits with any rebalance event still in rebalanceQueue unanswered. librdkafka blocks disconnect() until the application responds to a pending cooperative rebalance, so a stop that lands mid-rebalance hangs the process. Answer whatever is left in the queue after the shutdown drain: unassign revokes, accept assigns (no loop polls any more, and disconnect's final revoke releases them).
Each test already owns its topic and consumer group. What kept them sequential was three tests mutating defaultConfig for the drain budget and the background task cap, which KafkaConsumerV2 read at construction. Add both as optional per-consumer config so the tests pass them directly, run the nine tests with it.concurrent, and track every consumer so afterAll closes what a timed-out test leaves behind. The fenced-batch test asserted the retained partition's committed offset stayed at the pre-gate mark. The fence is per partition, so the laggard's store for a partition the consumer still holds is valid and lands on the next auto-commit tick; the old assertion passed only by reading before that tick. Assert the documented contract instead.
jose-sequeira
force-pushed
the
jose-sequeira/consumer-v2-shutdown-rebalance
branch
from
August 28, 2026 08:25
18c9e30 to
05b2bdb
Compare
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.
Problem
A
KafkaConsumerV2that stops while a rebalance is in progress can hang forever indisconnect(). In production that is a pod that never finishes shutting down after SIGTERM. In CI it surfaced as the rebalance integration suite wedging past its test timeout, with the worker process surviving--forceExit.rebalanceCallbackonly queues ASSIGN/REVOKE events; the run loop answers them withincrementalAssign/incrementalUnassign.stopConsuming()flipsrunning, and the loop exits with whatever is still inrebalanceQueueunanswered.disconnect()until the application responds to the pending cooperative rebalance, so it never returns.rebalanceCallbackonly covers events that arrive after the flip, not ones queued just before it.Found while making the rebalance suite concurrent, which turns that window from rare into 1 run in 5.
Changes
disconnect()'s final revoke releases them).KafkaConsumerV2Configgains optionalmaxBackgroundTasksandrebalanceTimeoutMs, defaulting to thedefaultConfigvalues as before, so a consumer's budgets can be set per instance instead of by mutating the global.defaultConfignow pass budgets through the new fields (mechanical, but prettier re-wraps everyit.concurrent(call, hence the large diff).afterAll, so a test that dies before itsfinallycannot keep the worker alive.Note
Stacked on #90032 so that PR stays test-only. This layer is the one that touches
consumer-v2.ts.How did you test this code?
stopConsuming()returned, andrdKafkaConsumer.disconnect()never called back, with the consumer holding one partition and its second revoke hook not yet run.consumer-v2.test.tsandconsumer-v2.integration.test.tspass locally (35 tests).hogli review(the Greptile CLI does not install in this environment).Automatic notifications
Docs update
None.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Claude Code (Claude Fable 5) under @jose-sequeira's direction, continuing from #90032. Skills invoked:
/writing-tests,/stacking-prs,/writing-pr-descriptions. Sequence: the suite was made concurrent, one test wedged intermittently, instrumentation showed the hang was in teardown rather than in any wait, and the shutdown path was fixed at the source rather than by making the test wait for the rebalance to settle. The per-consumer config fields were added at the reviewer's request instead of keeping the global mutation.