Skip to content

KAFKA-20985: Keep static member id when LeaveGroup is suppressed - #23266

Open
suzhiking wants to merge 1 commit into
apache:trunkfrom
suzhiking:KAFKA-20985-static-member-fenced-rejoin
Open

KAFKA-20985: Keep static member id when LeaveGroup is suppressed#23266
suzhiking wants to merge 1 commit into
apache:trunkfrom
suzhiking:KAFKA-20985-static-member-fenced-rejoin

Conversation

@suzhiking

@suzhiking suzhiking commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

A static consumer that calls unsubscribe() suppresses the LeaveGroup RPC in AbstractCoordinator.maybeLeaveGroup(), but resetGenerationOnLeaveGroup() wiped the local member id anyway. The subsequent rejoin therefore carries UNKNOWN_MEMBER_ID while 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 the group.instance.id: it evicts the current member and fences its pending join/sync attempts with FENCED_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 explicit LEAVE_GROUP close 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, ConsumerCoordinatorTest and clients checkstyle pass locally.

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
suzhiking force-pushed the KAFKA-20985-static-member-fenced-rejoin branch from 10a2ead to c1a8d0c Compare August 25, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clients consumer small Small PRs triage PRs from the community

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant