Skip to content

Commit ee29e34

Browse files
committed
KAFKA-20253: Trigger rejoin on heartbeat thread AuthenticationException
The AuthenticationException catch in HeartbeatThread#run only calls setFailureCause(e) and exits, so the member stays STABLE, rejoinNeeded stays false, and heartbeatThread is cleared to null. The main thread's timeToNextHeartbeat() then returns 0 from the stale heartbeatTimer, causing NetworkClient.poll(0) to spin via selectNow() indefinitely. Extend the existing "error -> requestRejoin()" pattern already used in HeartbeatResponseHandler to the auth catch, so the next poll() runs ensureActiveGroup() -> startHeartbeatThreadIfNeeded() -> heartbeat.resetTimeouts(), restoring consumer state. Verified with a new unit test and end-to-end via mstruk/kafka-consumer-reproducer (101% -> ~0.3% CPU, fresh generation).
1 parent 678c0e0 commit ee29e34

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

clients/src/main/java/org/apache/kafka/clients/consumer/internals/AbstractCoordinator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,6 +1571,7 @@ public void onFailure(RuntimeException e) {
15711571
} catch (AuthenticationException e) {
15721572
log.error("An authentication error occurred in the heartbeat thread", e);
15731573
setFailureCause(e);
1574+
requestRejoin("authentication error in heartbeat thread");
15741575
} catch (GroupAuthorizationException e) {
15751576
log.error("A group authorization error occurred in the heartbeat thread", e);
15761577
setFailureCause(e);

clients/src/test/java/org/apache/kafka/clients/consumer/internals/AbstractCoordinatorTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,43 @@ public void testUncaughtExceptionInHeartbeatThread() throws Exception {
12881288
}
12891289
}
12901290

1291+
@Test
1292+
public void testAuthenticationErrorInHeartbeatThreadTriggersRejoin() throws Exception {
1293+
setupCoordinator();
1294+
1295+
mockClient.prepareResponse(groupCoordinatorResponse(node, Errors.NONE));
1296+
mockClient.prepareResponse(joinGroupFollowerResponse(1, memberId, leaderId, Errors.NONE));
1297+
mockClient.prepareResponse(syncGroupResponse(Errors.NONE));
1298+
1299+
final AuthenticationException authError = new AuthenticationException("test auth failure");
1300+
1301+
mockClient.prepareResponse(body -> {
1302+
if (body instanceof HeartbeatRequest)
1303+
throw authError;
1304+
return false;
1305+
}, heartbeatResponse(Errors.UNKNOWN_SERVER_ERROR));
1306+
1307+
coordinator.ensureActiveGroup();
1308+
assertFalse(coordinator.rejoinNeededOrPending(),
1309+
"Sanity: group should be stable before the auth error");
1310+
1311+
mockTime.sleep(HEARTBEAT_INTERVAL_MS);
1312+
1313+
TestUtils.waitForCondition(() -> {
1314+
try {
1315+
coordinator.pollHeartbeat(mockTime.milliseconds());
1316+
return false;
1317+
} catch (AuthenticationException e) {
1318+
assertSame(authError, e);
1319+
return true;
1320+
}
1321+
}, 3000, "HeartbeatThread did not propagate the authentication error in time");
1322+
1323+
assertTrue(coordinator.rejoinNeededOrPending(),
1324+
"Expected the heartbeat thread to request a rejoin after an AuthenticationException " +
1325+
"so the next poll() restarts the heartbeat machinery via ensureActiveGroup()");
1326+
}
1327+
12911328
@Test
12921329
public void testPollHeartbeatAwakesHeartbeatThread() throws Exception {
12931330
final int longRetryBackoffMs = 10000;

0 commit comments

Comments
 (0)