KAFKA-20985: Keep static member id when LeaveGroup is suppressed - #23266
Open
suzhiking wants to merge 1 commit into
Open
KAFKA-20985: Keep static member id when LeaveGroup is suppressed#23266suzhiking wants to merge 1 commit into
suzhiking wants to merge 1 commit into
Conversation
A static consumer that calls unsubscribe() suppresses the LeaveGroup RPC, but resetGenerationOnLeaveGroup() wiped the local member id anyway, so the subsequent rejoin carried UNKNOWN_MEMBER_ID while the coordinator still had the member registered under its old id. The coordinator has to treat such a join as a new process claiming the group.instance.id, so it evicts the current member and fences any of its pending join/sync attempts with FENCED_INSTANCE_ID. A consumer can hit this against itself: if its first blind rejoin is abandoned client-side (for example during a coordinator stall) but still delivered, whichever of the two joins is processed last fences the other, and the pending attempt of the same consumer receives FencedInstanceIdException, which Kafka Streams treats as fatal (SHUTDOWN_APPLICATION). Keep the member id whenever no LeaveGroup was actually sent, using the existing keep-member-id branch of resetStateAndGeneration; state and generation are still reset and a rejoin is requested as before. The next rejoin then identifies as the existing member and is handled as a recognized rejoin instead of a static replacement, so no ordering of connections and request queues can make a lone consumer fence itself. Dynamic members and explicit LEAVE_GROUP close operations keep the previous behavior. The resetGenerationOnLeaveGroup() wrapper is removed: its only production caller was this path, and its name and hardcoded id-wipe encode exactly the assumption this change corrects. resetStateAndRejoin becomes package-private for the one test that used the wrapper as a generic reset, matching the neighboring resetStateOnResponseError.
suzhiking
force-pushed
the
KAFKA-20985-static-member-fenced-rejoin
branch
from
August 25, 2026 18:34
10a2ead to
c1a8d0c
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.
A static consumer that calls
unsubscribe()suppresses the LeaveGroup RPC inAbstractCoordinator.maybeLeaveGroup(), butresetGenerationOnLeaveGroup()wiped the local member id anyway. The subsequent rejoin therefore carriesUNKNOWN_MEMBER_IDwhile the coordinator still has the member registered under its old id, and the coordinator has to treat such a join as a new process claiming thegroup.instance.id: it evicts the current member and fences its pending join/sync attempts withFENCED_INSTANCE_ID.A lone consumer can hit this against itself. If its first blind rejoin is abandoned client-side (for example during a coordinator stall) but was already delivered, the broker still processes it later; whichever of the two joins is processed last fences the other, and the pending attempt of the same consumer receives
FencedInstanceIdException, which Kafka Streams treats as fatal (SHUTDOWN_APPLICATION). See KAFKA-20985 for the full failure sequence observed on a long-running Streams application.The fix keeps the member id whenever no LeaveGroup was actually sent, reusing the existing keep-member-id branch of
resetStateAndGeneration(). State and generation are still reset and a rejoin is requested exactly as before; only the id retention changes, and only for the suppressed case. The next rejoin then identifies as the existing member and is handled as a recognized rejoin instead of a static replacement, so no ordering of connections and request queues can make a consumer fence itself. Dynamic members and explicitLEAVE_GROUPclose operations keep the previous behavior.Testing: new regression test
testStaticMemberKeepsMemberIdWhenLeaveGroupIsSuppressed(static member, DEFAULT membership operation: no LeaveGroup sent, member id retained, generation reset, rejoin requested);AbstractCoordinatorTest,ConsumerCoordinatorTestand clients checkstyle pass locally.