lianetm commented on code in PR #22747:
URL: https://github.com/apache/kafka/pull/22747#discussion_r3532096372


##########
clients/src/main/java/org/apache/kafka/clients/consumer/internals/CommitRequestManager.java:
##########
@@ -1223,9 +1225,14 @@ private void onFailure(final long currentTimeMs,
                     " anymore.", responseError);
                 future.completeExceptionally(exception);
             } else if (responseError == Errors.STALE_MEMBER_EPOCH) {
-                log.error("OffsetFetch failed with {} and the consumer is not 
part " +
-                    "of the group anymore (it probably left the group, got 
fenced" +
-                    " or failed). The request cannot be retried and will 
fail.", responseError);
+                if (memberInfo.memberEpoch.isPresent()) {
+                    log.debug("OffsetFetch failed with {}. The member has a 
newer epoch, so the " +
+                        "request will be retried with it.", responseError);

Review Comment:
   will be retried...if there's time left (worth clarifying, we cannot 
guarantee it will be retried at this point. The retry logic is elsewhere and 
based on expiration)



##########
clients/src/main/java/org/apache/kafka/clients/consumer/internals/CommitRequestManager.java:
##########
@@ -554,8 +554,10 @@ private void fetchOffsetsWithRetries(final 
OffsetFetchRequestState fetchRequest,
         currentResult.whenComplete((res, error) -> {
             boolean inflightRemoved = 
pendingRequests.inflightOffsetFetches.remove(fetchRequest);
             if (!inflightRemoved) {
-                log.warn("A duplicated, inflight, request was identified, but 
unable to find it in the " +
-                    "outbound buffer: {}", fetchRequest);
+                // Requests deduplicated onto another pending request are 
never added to the
+                // buffers, so there is nothing to remove for them here.
+                log.debug("Completed request not found in the in-flight buffer 
(it was " +
+                    "deduplicated and chained onto an existing request): {}", 
fetchRequest);

Review Comment:
   dedup/chained it's certainly one of the reasons for this, but I'm not sure 
it's the only one. E.g, requests that remain unsent (not added to inflight) if 
we don't have a coordinator?. So maybe we can consider letting the log with 
what we're sure of (what it had before, dup found but not inflight), and we 
mention some of the potential reasons in a comment? wdyt? 



##########
clients/src/test/java/org/apache/kafka/clients/consumer/internals/CommitRequestManagerTest.java:
##########
@@ -1238,6 +1238,66 @@ public void 
testSyncOffsetFetchFailsWithStaleEpochAndRetriesWithNewEpoch() {
         assertEquals(memberId, reqData.groups().get(0).memberId());
     }
 
+    // Same as testSyncOffsetFetchFailsWithStaleEpochAndRetriesWithNewEpoch, 
but with a
+    // duplicated fetch for the same partitions chained onto the in-flight 
request when the
+    // STALE_MEMBER_EPOCH error is received. The retry of the chained request 
must not be
+    // deduplicated against the already-completed in-flight request: chaining 
onto a completed
+    // future fails it again immediately and re-triggers the retry in a tight 
synchronous loop
+    // that never sends a request with the new epoch and never completes the 
callers' futures
+    // (KAFKA-20765).
+    @Test
+    public void 
testDuplicatedOffsetFetchFailsWithStaleEpochAndRetriesWithNewEpoch() {
+        CommitRequestManager commitRequestManager = create(false, 100);
+        Set<TopicPartition> partitions = Collections.singleton(new 
TopicPartition("t1", 0));
+        
when(coordinatorRequestManager.coordinator()).thenReturn(Optional.of(mockedNode));
+
+        // Two callers fetch offsets for the same partitions; the second 
request is deduplicated
+        // and chained onto the first.
+        long deadlineMs = time.milliseconds() + defaultApiTimeoutMs;
+        CompletableFuture<CommitRequestManager.OffsetFetchResult> firstResult =
+            commitRequestManager.fetchOffsets(partitions, deadlineMs);
+        CompletableFuture<CommitRequestManager.OffsetFetchResult> secondResult 
=
+            commitRequestManager.fetchOffsets(partitions, deadlineMs);
+
+        // A single deduplicated request goes on the wire.
+        NetworkClientDelegate.PollResult res = 
commitRequestManager.poll(time.milliseconds());
+        assertEquals(1, res.unsentRequests.size());
+
+        // Mock member has a new valid epoch, so STALE_MEMBER_EPOCH is 
retriable.
+        int newEpoch = 8;
+        String memberId = "member1";
+        commitRequestManager.onMemberEpochUpdated(Optional.of(newEpoch), 
memberId);
+
+        // Receive error when member already has a newer member epoch. Request 
should be retried.
+        res.unsentRequests.get(0).handler().onComplete(
+            buildOffsetFetchClientResponse(res.unsentRequests.get(0), 
partitions, Errors.STALE_MEMBER_EPOCH));
+
+        // The failed request should be removed from the in-flight buffer, a 
retry should be
+        // enqueued, and the callers' futures should still be waiting for the 
retry's outcome.
+        assertEquals(0, 
commitRequestManager.pendingRequests.inflightOffsetFetches.size());
+        assertEquals(1, 
commitRequestManager.pendingRequests.unsentOffsetFetches.size());
+        assertFalse(firstResult.isDone());
+        assertFalse(secondResult.isDone());
+
+        // The retried request should be sent after the backoff, with the 
latest member ID and epoch.
+        time.sleep(retryBackoffMs);

Review Comment:
   uhm I think there is no backoff here (by the time it fails the new epoch is 
known already, so we just send it on the next poll). I expect the test should 
pass without the sleep and that we can remove the comment too. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to