-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-9823: Remember the sent generation for the coordinator request #8445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
5e404e2
c5cf2e6
0df8dc9
f40decd
1e1ffb0
29570b2
ea6dbfc
69de4bd
a431d3c
c37e9f7
d04ae3b
e32dc60
2793e1e
c624089
fc4c688
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -568,10 +568,14 @@ RequestFuture<ByteBuffer> sendJoinGroupRequest() { | |
|
|
||
| int joinGroupTimeoutMs = Math.max(rebalanceConfig.rebalanceTimeoutMs, rebalanceConfig.rebalanceTimeoutMs + 5000); | ||
| return client.send(coordinator, requestBuilder, joinGroupTimeoutMs) | ||
| .compose(new JoinGroupResponseHandler()); | ||
| .compose(new JoinGroupResponseHandler(generation)); | ||
| } | ||
|
|
||
| private class JoinGroupResponseHandler extends CoordinatorResponseHandler<JoinGroupResponse, ByteBuffer> { | ||
| private JoinGroupResponseHandler(final Generation generation) { | ||
| super(generation); | ||
| } | ||
|
|
||
| @Override | ||
| public void handle(JoinGroupResponse joinResponse, RequestFuture<ByteBuffer> future) { | ||
| Errors error = joinResponse.error(); | ||
|
|
@@ -606,9 +610,12 @@ public void handle(JoinGroupResponse joinResponse, RequestFuture<ByteBuffer> fut | |
| // backoff and retry | ||
| future.raise(error); | ||
| } else if (error == Errors.UNKNOWN_MEMBER_ID) { | ||
| // reset the member id and retry immediately | ||
| resetGenerationOnResponseError(ApiKeys.JOIN_GROUP, error); | ||
| log.debug("Attempt to join group failed due to unknown member id."); | ||
| log.debug("Attempt to join group failed due to unknown member id with {}.", sentGeneration); | ||
| // only need to reset the member id if generation has not been changed, | ||
| // then retry immediately | ||
| if (generationUnchanged()) | ||
| resetGenerationOnResponseError(ApiKeys.JOIN_GROUP, error); | ||
|
|
||
| future.raise(error); | ||
| } else if (error == Errors.COORDINATOR_NOT_AVAILABLE | ||
| || error == Errors.NOT_COORDINATOR) { | ||
|
|
@@ -617,7 +624,10 @@ public void handle(JoinGroupResponse joinResponse, RequestFuture<ByteBuffer> fut | |
| log.debug("Attempt to join group failed due to obsolete coordinator information: {}", error.message()); | ||
| future.raise(error); | ||
| } else if (error == Errors.FENCED_INSTANCE_ID) { | ||
| log.error("Received fatal exception: group.instance.id gets fenced"); | ||
| // for join-group request, even if the generation has changed we would not expect the instance id | ||
| // gets fenced, and hence we always treat this as a fatal error | ||
| log.error("Attempt to join group failed due to group instance id {} gets fenced with {}", | ||
| rebalanceConfig.groupInstanceId, sentGeneration); | ||
| future.raise(error); | ||
| } else if (error == Errors.INCONSISTENT_GROUP_PROTOCOL | ||
| || error == Errors.INVALID_SESSION_TIMEOUT | ||
|
|
@@ -708,10 +718,14 @@ private RequestFuture<ByteBuffer> sendSyncGroupRequest(SyncGroupRequest.Builder | |
| if (coordinatorUnknown()) | ||
| return RequestFuture.coordinatorNotAvailable(); | ||
| return client.send(coordinator, requestBuilder) | ||
| .compose(new SyncGroupResponseHandler()); | ||
| .compose(new SyncGroupResponseHandler(generation)); | ||
| } | ||
|
|
||
| private class SyncGroupResponseHandler extends CoordinatorResponseHandler<SyncGroupResponse, ByteBuffer> { | ||
| private SyncGroupResponseHandler(final Generation generation) { | ||
| super(generation); | ||
| } | ||
|
|
||
| @Override | ||
| public void handle(SyncGroupResponse syncResponse, | ||
| RequestFuture<ByteBuffer> future) { | ||
|
|
@@ -737,16 +751,21 @@ public void handle(SyncGroupResponse syncResponse, | |
| log.debug("SyncGroup failed because the group began another rebalance"); | ||
| future.raise(error); | ||
| } else if (error == Errors.FENCED_INSTANCE_ID) { | ||
| log.error("Received fatal exception: group.instance.id gets fenced"); | ||
| // for sync-group request, even if the generation has changed we would not expect the instance id | ||
| // gets fenced, and hence we always treat this as a fatal error | ||
| log.error("SyncGroup failed with {} due to group.instance.id {} gets fenced", | ||
| sentGeneration, rebalanceConfig.groupInstanceId); | ||
| future.raise(error); | ||
| } else if (error == Errors.UNKNOWN_MEMBER_ID | ||
| || error == Errors.ILLEGAL_GENERATION) { | ||
| log.debug("SyncGroup failed: {}", error.message()); | ||
| resetGenerationOnResponseError(ApiKeys.SYNC_GROUP, error); | ||
| log.info("SyncGroup failed with {}: {}, would request re-join", sentGeneration, error.message()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: More importantly the log message is outside of the if branch below. But if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note on the caller
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure how this comment was changed, but this still doesn't read well for me. |
||
| if (generationUnchanged()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey @guozhangwang. What I've observed is that if the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rationale is that if the generation has changed, it is either reset by the heartbeat thread in which case the generation is reset and rejoin is already requested, or it is changed by another join-group request; but since inside AbstractCoordinator we will only have one in-flight request at a given time the second scenario should not happen. So the only possibility is the heartbeat resetting.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. Thanks |
||
| resetGenerationOnResponseError(ApiKeys.SYNC_GROUP, error); | ||
|
|
||
| future.raise(error); | ||
| } else if (error == Errors.COORDINATOR_NOT_AVAILABLE | ||
| || error == Errors.NOT_COORDINATOR) { | ||
| log.debug("SyncGroup failed: {}", error.message()); | ||
| log.debug("SyncGroup failed: {}, marking coordinator unknown", error.message()); | ||
| markCoordinatorUnknown(); | ||
| future.raise(error); | ||
| } else { | ||
|
|
@@ -890,8 +909,8 @@ protected synchronized String memberId() { | |
| } | ||
|
|
||
| private synchronized void resetGeneration() { | ||
| this.rejoinNeeded = true; | ||
| this.generation = Generation.NO_GENERATION; | ||
| rejoinNeeded = true; | ||
| generation = Generation.NO_GENERATION; | ||
| } | ||
|
|
||
| synchronized void resetGenerationOnResponseError(ApiKeys api, Errors error) { | ||
|
|
@@ -989,7 +1008,7 @@ public synchronized RequestFuture<Void> maybeLeaveGroup(String leaveReason) { | |
| Collections.singletonList(new MemberIdentity().setMemberId(generation.memberId)) | ||
| ); | ||
|
|
||
| future = client.send(coordinator, request).compose(new LeaveGroupResponseHandler()); | ||
| future = client.send(coordinator, request).compose(new LeaveGroupResponseHandler(generation)); | ||
| client.pollNoWakeup(); | ||
| } | ||
|
|
||
|
|
@@ -1003,6 +1022,10 @@ protected boolean isDynamicMember() { | |
| } | ||
|
|
||
| private class LeaveGroupResponseHandler extends CoordinatorResponseHandler<LeaveGroupResponse, Void> { | ||
| private LeaveGroupResponseHandler(final Generation generation) { | ||
| super(generation); | ||
| } | ||
|
|
||
| @Override | ||
| public void handle(LeaveGroupResponse leaveResponse, RequestFuture<Void> future) { | ||
| final List<MemberResponse> members = leaveResponse.memberResponses(); | ||
|
|
@@ -1013,10 +1036,10 @@ public void handle(LeaveGroupResponse leaveResponse, RequestFuture<Void> future) | |
|
|
||
| final Errors error = leaveResponse.error(); | ||
| if (error == Errors.NONE) { | ||
| log.debug("LeaveGroup request returned successfully"); | ||
| log.debug("LeaveGroup response with {} returned successfully: {}", sentGeneration, response); | ||
| future.complete(null); | ||
| } else { | ||
| log.error("LeaveGroup request failed with error: {}", error.message()); | ||
| log.error("LeaveGroup response with {} failed with error: {}", sentGeneration, error.message()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: seems the original was a little more accurate? The response did not fail; it just contained an error indicating the request had failed. |
||
| future.raise(error); | ||
| } | ||
| } | ||
|
|
@@ -1037,10 +1060,8 @@ synchronized RequestFuture<Void> sendHeartbeatRequest() { | |
| } | ||
|
|
||
| private class HeartbeatResponseHandler extends CoordinatorResponseHandler<HeartbeatResponse, Void> { | ||
| private final Generation sentGeneration; | ||
|
|
||
| private HeartbeatResponseHandler(final Generation generation) { | ||
| this.sentGeneration = generation; | ||
| super(generation); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -1061,16 +1082,36 @@ public void handle(HeartbeatResponse heartbeatResponse, RequestFuture<Void> futu | |
| requestRejoin(); | ||
| future.raise(error); | ||
| } else if (error == Errors.ILLEGAL_GENERATION) { | ||
| log.info("Attempt to heartbeat failed since generation {} is not current", sentGeneration.generationId); | ||
| resetGenerationOnResponseError(ApiKeys.HEARTBEAT, error); | ||
| future.raise(error); | ||
| if (generationUnchanged()) { | ||
| log.info("Attempt to heartbeat failed since current {} is not valid, resetting generation", sentGeneration); | ||
| resetGenerationOnResponseError(ApiKeys.HEARTBEAT, error); | ||
| future.raise(error); | ||
| } else { | ||
| // if the generation has changed, then ignore this error | ||
| log.info("Attempt to heartbeat failed since old {} is not valid, ignoring the error", sentGeneration); | ||
| future.complete(null); | ||
| } | ||
| } else if (error == Errors.FENCED_INSTANCE_ID) { | ||
| log.error("Received fatal exception: group.instance.id gets fenced"); | ||
| future.raise(error); | ||
| if (generationUnchanged()) { | ||
| log.info("Attempt to heartbeat failed since current {} gets fenced with group instance id {}", | ||
| sentGeneration, rebalanceConfig.groupInstanceId); | ||
| future.raise(error); | ||
| } else { | ||
| // if the generation has changed, then ignore this error | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why we ignore fenced errors here but not in the JoinGroup/SyncGroup handlers.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As explained in the PR description, I intentionally make the logic differently: the rationale is that for join / sync we should only have one request in-flight at a given time, and at that time if the generation has changed it should be from the heartbeat error handling that resets it; for this case this client should have not re-joined and replaced its member.id, so if the error returns it still indicates another member with the same instance.id has replaced the member.id. For heartbeat though, we've observed it is possible that, 1) heartbeat sent, 2) the same member rejoins group, gets a new member.id, 3) the previous heartbeat is handled and the error set. In this case we should not treat it as fatal. |
||
| log.info("Attempt to heartbeat failed since old {} gets fenced with group instance id {}, " + | ||
| "ignoring the error", sentGeneration, rebalanceConfig.groupInstanceId); | ||
| future.complete(null); | ||
| } | ||
| } else if (error == Errors.UNKNOWN_MEMBER_ID) { | ||
| log.info("Attempt to heartbeat failed since member id {} is not valid.", sentGeneration.memberId); | ||
| resetGenerationOnResponseError(ApiKeys.HEARTBEAT, error); | ||
| future.raise(error); | ||
| if (generationUnchanged()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In other response handlers, we combine UNKNOWN_MEMBER_ID and ILLEGAL_GENERATION cases. Do you think we could also consolidate here as well?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I agree we should try to consolidate. Looking at this handler alone, we have basically the same code for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SG, let me try it out. |
||
| log.info("Attempt to heartbeat failed since current {} member id is unknown, resetting generation", sentGeneration); | ||
| resetGenerationOnResponseError(ApiKeys.HEARTBEAT, error); | ||
| future.raise(error); | ||
| } else { | ||
| // if the generation has changed, then ignore this error | ||
| log.info("Attempt to heartbeat failed since old {} member id is unknown, ignoring the error", sentGeneration); | ||
| future.complete(null); | ||
| } | ||
| } else if (error == Errors.GROUP_AUTHORIZATION_FAILED) { | ||
| future.raise(GroupAuthorizationException.forGroupId(rebalanceConfig.groupId)); | ||
| } else { | ||
|
|
@@ -1080,7 +1121,12 @@ public void handle(HeartbeatResponse heartbeatResponse, RequestFuture<Void> futu | |
| } | ||
|
|
||
| protected abstract class CoordinatorResponseHandler<R, T> extends RequestFutureAdapter<ClientResponse, T> { | ||
| protected ClientResponse response; | ||
| CoordinatorResponseHandler(final Generation generation) { | ||
| this.sentGeneration = generation; | ||
| } | ||
|
|
||
| final Generation sentGeneration; | ||
| ClientResponse response; | ||
|
|
||
| public abstract void handle(R response, RequestFuture<T> future); | ||
|
|
||
|
|
@@ -1106,6 +1152,11 @@ public void onSuccess(ClientResponse clientResponse, RequestFuture<T> future) { | |
| } | ||
| } | ||
|
|
||
| boolean generationUnchanged() { | ||
| synchronized (AbstractCoordinator.this) { | ||
| return generation.equals(sentGeneration); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| protected Meter createMeter(Metrics metrics, String groupName, String baseName, String descriptiveName) { | ||
|
|
@@ -1421,7 +1472,7 @@ private static class UnjoinedGroupException extends RetriableException { | |
| } | ||
|
|
||
| // For testing only below | ||
| public Heartbeat heartbeat() { | ||
| final Heartbeat heartbeat() { | ||
| return heartbeat; | ||
| } | ||
|
|
||
|
|
@@ -1449,4 +1500,12 @@ final boolean hasUnknownGeneration() { | |
| final boolean hasValidMemberId() { | ||
| return generation != Generation.NO_GENERATION && generation.hasMemberId(); | ||
| } | ||
|
|
||
| final void setNewGeneration(final Generation generation) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we make these methods synchronized?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These functions are only used in non-integration unit tests so I think it is not necessary. LMK if you have a strong motivation?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just because they are exposed. If they ultimately got used in unit tests involving concurrent threads, we would need them to be synchronized. Seems there's no downside to being on the safe side?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup I think so, let me synchronize this then :) |
||
| this.generation = generation; | ||
| } | ||
|
|
||
| final void setNewState(final MemberState state) { | ||
| this.state = state; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -272,6 +272,18 @@ private void maybeUpdateJoinedSubscription(Set<TopicPartition> assignedPartition | |
| } | ||
| } | ||
|
|
||
| private Exception invokeOnAssignment(final ConsumerPartitionAssignor assignor, final Assignment assignment) { | ||
| log.info("Notifying assignor about the new {}", assignment); | ||
|
|
||
| try { | ||
| assignor.onAssignment(assignment, groupMetadata); | ||
| } catch (Exception e) { | ||
| return e; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private Exception invokePartitionsAssigned(final Set<TopicPartition> assignedPartitions) { | ||
| log.info("Adding newly assigned partitions: {}", Utils.join(assignedPartitions, ", ")); | ||
|
|
||
|
|
@@ -351,7 +363,7 @@ protected void onJoinComplete(int generation, | |
|
|
||
| // should at least encode the short version | ||
| if (assignmentBuffer.remaining() < 2) | ||
| throw new IllegalStateException("There is insufficient bytes available to read assignment from the sync-group response (" + | ||
| throw new IllegalStateException("There are insufficient bytes available to read assignment from the sync-group response (" + | ||
| "actual byte size " + assignmentBuffer.remaining() + ") , this is not expected; " + | ||
| "it is possible that the leader's assign function is buggy and did not return any assignment for this member, " + | ||
| "or because static member is configured and the protocol is buggy hence did not get the assignment for this member"); | ||
|
|
@@ -406,11 +418,7 @@ protected void onJoinComplete(int generation, | |
| maybeUpdateJoinedSubscription(assignedPartitions); | ||
|
|
||
| // Catch any exception here to make sure we could complete the user callback. | ||
| try { | ||
| assignor.onAssignment(assignment, groupMetadata); | ||
| } catch (Exception e) { | ||
| firstException.compareAndSet(null, e); | ||
| } | ||
| firstException.compareAndSet(null, invokeOnAssignment(assignor, assignment)); | ||
|
|
||
| // Reschedule the auto commit starting from now | ||
| if (autoCommitEnabled) | ||
|
|
@@ -1064,10 +1072,12 @@ public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception | |
| * which returns a request future that can be polled in the case of a synchronous commit or ignored in the | ||
| * asynchronous case. | ||
| * | ||
| * NOTE: This is visible only for testing | ||
| * | ||
| * @param offsets The list of offsets per partition that should be committed. | ||
| * @return A request future whose value indicates whether the commit was successful or not | ||
| */ | ||
| private RequestFuture<Void> sendOffsetCommitRequest(final Map<TopicPartition, OffsetAndMetadata> offsets) { | ||
| RequestFuture<Void> sendOffsetCommitRequest(final Map<TopicPartition, OffsetAndMetadata> offsets) { | ||
| if (offsets.isEmpty()) | ||
| return RequestFuture.voidSuccess(); | ||
|
|
||
|
|
@@ -1135,14 +1145,14 @@ private RequestFuture<Void> sendOffsetCommitRequest(final Map<TopicPartition, Of | |
| log.trace("Sending OffsetCommit request with {} to coordinator {}", offsets, coordinator); | ||
|
|
||
| return client.send(coordinator, builder) | ||
| .compose(new OffsetCommitResponseHandler(offsets)); | ||
| .compose(new OffsetCommitResponseHandler(offsets, generation)); | ||
|
hachikuji marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private class OffsetCommitResponseHandler extends CoordinatorResponseHandler<OffsetCommitResponse, Void> { | ||
|
|
||
| private final Map<TopicPartition, OffsetAndMetadata> offsets; | ||
|
|
||
| private OffsetCommitResponseHandler(Map<TopicPartition, OffsetAndMetadata> offsets) { | ||
| private OffsetCommitResponseHandler(Map<TopicPartition, OffsetAndMetadata> offsets, Generation generation) { | ||
| super(generation); | ||
| this.offsets = offsets; | ||
| } | ||
|
|
||
|
|
@@ -1190,8 +1200,16 @@ public void handle(OffsetCommitResponse commitResponse, RequestFuture<Void> futu | |
| future.raise(error); | ||
| return; | ||
| } else if (error == Errors.FENCED_INSTANCE_ID) { | ||
| log.error("Received fatal exception: group.instance.id gets fenced"); | ||
| future.raise(error); | ||
| log.info("OffsetCommit failed with {} due to group instance id {} fenced", sentGeneration, rebalanceConfig.groupInstanceId); | ||
|
|
||
| // if the generation has changed, do not raise the fatal error but rebalance-in-progress | ||
| if (generationUnchanged()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why could we still survive from a fenced instance id in commit request?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No we could not "survive", the point of throwing RebalanceInProgress is not to survive, but to give a different error case to the caller that it do not necessarily have to exit as zombie but could retry. |
||
| future.raise(error); | ||
| } else { | ||
| future.raise(new RebalanceInProgressException("Offset commit cannot be completed since the " + | ||
| "consumer member's old generation is fenced by its group instance id, it is possible that " + | ||
| "this consumer has already participated another rebalance and got a new generation")); | ||
| } | ||
| return; | ||
| } else if (error == Errors.REBALANCE_IN_PROGRESS) { | ||
| /* Consumer should not try to commit offset in between join-group and sync-group, | ||
|
|
@@ -1209,9 +1227,18 @@ public void handle(OffsetCommitResponse commitResponse, RequestFuture<Void> futu | |
| return; | ||
| } else if (error == Errors.UNKNOWN_MEMBER_ID | ||
| || error == Errors.ILLEGAL_GENERATION) { | ||
| // need to reset generation and re-join group | ||
| resetGenerationOnResponseError(ApiKeys.OFFSET_COMMIT, error); | ||
| future.raise(new CommitFailedException()); | ||
| log.info("OffsetCommit failed with {}: {}", sentGeneration, error.message()); | ||
|
|
||
| // only need to reset generation and re-join group if generation has not changed; | ||
| // otherwise only raise rebalance-in-progress error | ||
| if (generationUnchanged()) { | ||
| resetGenerationOnResponseError(ApiKeys.OFFSET_COMMIT, error); | ||
| future.raise(new CommitFailedException()); | ||
| } else { | ||
| future.raise(new RebalanceInProgressException("Offset commit cannot be completed since the " + | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems a bit misleading, because the consumer is not actually participating in an ongoing rebalance (yet)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @guozhangwang can you elaborate on why we don't just throw
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the generation has changed since the commit request is sent, then it is likely that it has participated in a new rebalance (and hence get a new generation), or it has reset its generation due to the heartbeat failure. So this commit failure is recoverable.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Chatted about this offline, will just leave the concrete proposal here:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reasoning being that we use
Note that if we dropped out of the group and already completed the rejoin, the state will be |
||
| "consumer member's generation is already stale, meaning it has already participated another rebalance and " + | ||
| "got a new generation. You can try completing the rebalance by calling poll() and then retry commit again")); | ||
| } | ||
| return; | ||
| } else { | ||
| future.raise(new KafkaException("Unexpected error in commit: " + error.message())); | ||
|
|
@@ -1253,6 +1280,10 @@ private RequestFuture<Map<TopicPartition, OffsetAndMetadata>> sendOffsetFetchReq | |
| } | ||
|
|
||
| private class OffsetFetchResponseHandler extends CoordinatorResponseHandler<OffsetFetchResponse, Map<TopicPartition, OffsetAndMetadata>> { | ||
| private OffsetFetchResponseHandler() { | ||
| super(Generation.NO_GENERATION); | ||
| } | ||
|
|
||
| @Override | ||
| public void handle(OffsetFetchResponse response, RequestFuture<Map<TopicPartition, OffsetAndMetadata>> future) { | ||
| if (response.hasError()) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can fix the grammar a little
Similarly for a couple below