-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-8421: Still return data during rebalance #7312
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 12 commits
37b5c63
4a34116
d964cea
f5b8f96
b07bac2
29f8942
3000d6f
8464fa7
f804b3d
3c62029
284ef77
9825063
929048a
58adc09
c79a93d
75c4e88
95ef3fb
3b844ff
781514d
8b08a3e
8fa5d74
d75fcb9
b176e4e
7a826f9
31e372e
366739b
faf787e
c71af82
ffe6146
518049c
30d5e53
fa3456d
0220d96
27d013a
94611cc
b529847
33fd785
1df9808
3057405
21f531d
effb29d
6fbd976
f4e7111
91b6606
0ad52ec
27f76b7
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 |
|---|---|---|
|
|
@@ -1210,9 +1210,11 @@ private ConsumerRecords<K, V> poll(final Timer timer, final boolean includeMetad | |
| client.maybeTriggerWakeup(); | ||
|
|
||
| if (includeMetadataInTimeout) { | ||
| if (!updateAssignmentMetadataIfNeeded(timer)) { | ||
| return ConsumerRecords.empty(); | ||
| } | ||
| // try to update assignment metadata BUT do not need to block on the timer, | ||
| // since even if we are 1) in the middle of a rebalance or 2) have partitions | ||
| // with unknown starting positions we may still want to return some data | ||
| // as long as there are some partitions fetchable | ||
| updateAssignmentMetadataIfNeeded(time.timer(1L)); | ||
| } else { | ||
| while (!updateAssignmentMetadataIfNeeded(time.timer(Long.MAX_VALUE))) { | ||
| log.warn("Still waiting for metadata"); | ||
|
|
@@ -1285,12 +1287,6 @@ private Map<TopicPartition, List<ConsumerRecord<K, V>>> pollForFetches(Timer tim | |
| }); | ||
| timer.update(pollTimer.currentTimeMs()); | ||
|
|
||
| // after the long poll, we should check whether the group needs to rebalance | ||
| // prior to returning data so that the group can stabilize faster | ||
| if (coordinator != null && coordinator.rejoinNeededOrPending()) { | ||
| return Collections.emptyMap(); | ||
| } | ||
|
|
||
|
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. Why is this dropped? Are you thinking it's just redundant?
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. Isn't this the "main point" of this PR -- we don't want to return nothing just because a rebalance is needed or pending
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. Oh, yeah... that makes sense. |
||
| return fetcher.fetchedRecords(); | ||
| } | ||
|
|
||
|
|
@@ -1324,6 +1320,8 @@ private Map<TopicPartition, List<ConsumerRecord<K, V>>> pollForFetches(Timer tim | |
| * @throws org.apache.kafka.common.errors.TimeoutException if the timeout specified by {@code default.api.timeout.ms} expires | ||
| * before successful completion of the offset commit | ||
| * @throws org.apache.kafka.common.errors.FencedInstanceIdException if this consumer instance gets fenced by broker. | ||
| * @throws org.apache.kafka.common.errors.RebalanceInProgressException if this consumer instance is in the middle of a rebalance; | ||
| * Commit could be retried after the rebalance is completed with the {@link #poll(Duration)} call. | ||
| */ | ||
| @Override | ||
| public void commitSync() { | ||
|
|
@@ -1359,6 +1357,8 @@ public void commitSync() { | |
| * @throws org.apache.kafka.common.errors.TimeoutException if the timeout expires before successful completion | ||
| * of the offset commit | ||
| * @throws org.apache.kafka.common.errors.FencedInstanceIdException if this consumer instance gets fenced by broker. | ||
| * @throws org.apache.kafka.common.errors.RebalanceInProgressException if this consumer instance is in the middle of a rebalance; | ||
| * Commit could be retried after the rebalance is completed with the {@link #poll(Duration)} call. | ||
| */ | ||
| @Override | ||
| public void commitSync(Duration timeout) { | ||
|
|
@@ -1380,7 +1380,8 @@ public void commitSync(Duration timeout) { | |
| * This commits offsets to Kafka. The offsets committed using this API will be used on the first fetch after every | ||
| * rebalance and also on startup. As such, if you need to store offsets in anything other than Kafka, this API | ||
| * should not be used. The committed offset should be the next message your application will consume, | ||
| * i.e. lastProcessedMessageOffset + 1. | ||
| * i.e. lastProcessedMessageOffset + 1. If automatic group management with {@link #subscribe(Collection)} is used, | ||
| * then the committed offsets must belong to the currently auto-assigned partitions. | ||
| * <p> | ||
| * This is a synchronous commits and will block until either the commit succeeds or an unrecoverable error is | ||
| * encountered (in which case it is thrown to the caller), or the timeout specified by {@code default.api.timeout.ms} expires | ||
|
|
@@ -1406,6 +1407,8 @@ public void commitSync(Duration timeout) { | |
| * @throws org.apache.kafka.common.errors.TimeoutException if the timeout expires before successful completion | ||
| * of the offset commit | ||
| * @throws org.apache.kafka.common.errors.FencedInstanceIdException if this consumer instance gets fenced by broker. | ||
| * @throws org.apache.kafka.common.errors.RebalanceInProgressException if this consumer instance is in the middle of a rebalance; | ||
| * Commit could be retried after the rebalance is completed with the {@link #poll(Duration)} call. | ||
| */ | ||
| @Override | ||
| public void commitSync(final Map<TopicPartition, OffsetAndMetadata> offsets) { | ||
|
|
@@ -1418,7 +1421,8 @@ public void commitSync(final Map<TopicPartition, OffsetAndMetadata> offsets) { | |
| * This commits offsets to Kafka. The offsets committed using this API will be used on the first fetch after every | ||
| * rebalance and also on startup. As such, if you need to store offsets in anything other than Kafka, this API | ||
| * should not be used. The committed offset should be the next message your application will consume, | ||
| * i.e. lastProcessedMessageOffset + 1. | ||
| * i.e. lastProcessedMessageOffset + 1. If automatic group management with {@link #subscribe(Collection)} is used, | ||
| * then the committed offsets must belong to the currently auto-assigned partitions. | ||
| * <p> | ||
| * This is a synchronous commits and will block until either the commit succeeds, an unrecoverable error is | ||
| * encountered (in which case it is thrown to the caller), or the timeout expires. | ||
|
|
@@ -1444,12 +1448,15 @@ public void commitSync(final Map<TopicPartition, OffsetAndMetadata> offsets) { | |
| * @throws org.apache.kafka.common.errors.TimeoutException if the timeout expires before successful completion | ||
| * of the offset commit | ||
| * @throws org.apache.kafka.common.errors.FencedInstanceIdException if this consumer instance gets fenced by broker. | ||
| * @throws org.apache.kafka.common.errors.RebalanceInProgressException if this consumer instance is in the middle of a rebalance; | ||
| * Commit could be retried after the rebalance is completed with the {@link #poll(Duration)} call. | ||
| */ | ||
| @Override | ||
| public void commitSync(final Map<TopicPartition, OffsetAndMetadata> offsets, final Duration timeout) { | ||
| acquireAndEnsureOpen(); | ||
| try { | ||
| maybeThrowInvalidGroupIdException(); | ||
| maybeThrowIfCommitOffsetsNotOwned(offsets); | ||
| offsets.forEach(this::updateLastSeenEpochIfNewer); | ||
| if (!coordinator.commitOffsetsSync(new HashMap<>(offsets), time.timer(timeout))) { | ||
| throw new TimeoutException("Timeout of " + timeout.toMillis() + "ms expired before successfully " + | ||
|
|
@@ -1499,7 +1506,8 @@ public void commitAsync(OffsetCommitCallback callback) { | |
| * This commits offsets to Kafka. The offsets committed using this API will be used on the first fetch after every | ||
| * rebalance and also on startup. As such, if you need to store offsets in anything other than Kafka, this API | ||
| * should not be used. The committed offset should be the next message your application will consume, | ||
| * i.e. lastProcessedMessageOffset + 1. | ||
| * i.e. lastProcessedMessageOffset + 1. If automatic group management with {@link #subscribe(Collection)} is used, | ||
| * then the committed offsets must belong to the currently auto-assigned partitions. | ||
| * <p> | ||
| * This is an asynchronous call and will not block. Any errors encountered are either passed to the callback | ||
| * (if provided) or discarded. | ||
|
|
@@ -1519,14 +1527,29 @@ public void commitAsync(final Map<TopicPartition, OffsetAndMetadata> offsets, Of | |
| acquireAndEnsureOpen(); | ||
| try { | ||
| maybeThrowInvalidGroupIdException(); | ||
| maybeThrowIfCommitOffsetsNotOwned(offsets); | ||
|
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. Did you consider the compatibility of this change? We probably should have been more restrictive from the beginning, but is it a risk to change now? An alternative might be to just log a warning.
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. Originally I thought about this as a bug fix since we only check generation id at the broker-side, but now as I examine the javadoc again, we never state that for these two functions users should only commit on auto-assigned partitions -- in fact, if that's the case then they likely should just use the other two overloads unless they do not want the current position to be committed -- like in Streams, if some records get deserialization errors. So after a second thought I'm happy to take it out of this PR and still allow users to do so (perhaps I'd just make it clear that, if you use these two functions we would NOT check it so you can actually commit whatever you want and calling these two functions with your own caution). WYDT @hachikuji ?
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. It seems safer that way, but it does feel a little unsatisfying to be left in this state forever since we probably should have always had the validation. Perhaps we could file a JIRA and add the validation in 3.0 or something?
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. Now I actually feel if it is by-design to allow user this flexibility with auto-assignment but with their own caution? If we always want to restrict to assigned partitions then most likely they would not need these two overloads. |
||
| log.debug("Committing offsets: {}", offsets); | ||
| offsets.forEach(this::updateLastSeenEpochIfNewer); | ||
| coordinator.commitOffsetsAsync(new HashMap<>(offsets), callback); | ||
| } catch (CommitFailedException e) { | ||
| log.error("Failed to commit offsets asynchronously because they do not belong to dynamically assigned partitions"); | ||
|
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. Just wondering, does this mean we did not used to check if all the offsets actually belonged to auto-assigned partitions? Or did we just straight throw 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. I think this is not a behavior change: before this, the broker would check the commit offset request (but only on the generation id, as the coordinator did not keep trace who-owns-what), and if failed it will return the error code which would be translated to CommitFailed on the callback still. With this we will set the same error on the callback, but much earlier and skip the offset commit request.
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. Do we still need this |
||
| callback.onComplete(offsets, e); | ||
| } finally { | ||
| release(); | ||
| } | ||
| } | ||
|
|
||
| private void maybeThrowIfCommitOffsetsNotOwned(final Map<TopicPartition, OffsetAndMetadata> offsets) { | ||
|
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: The callers of this iterate the committed offsets twice. Once in order to check partition ownership and then a second time to invoke
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. Yes, if we are not gonna remove this (see other comment above) I can do this. |
||
| if (subscriptions.partitionsAutoAssigned()) { | ||
| final Set<TopicPartition> partitions = assignment(); | ||
| for (final TopicPartition tp : offsets.keySet()) { | ||
| if (!partitions.contains(tp)) | ||
| throw new CommitFailedException("cannot commit offsets out of owned partitions with " + | ||
| "dynamic partition assigned from subscription"); | ||
|
guozhangwang marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Overrides the fetch offsets that the consumer will use on the next {@link #poll(Duration) poll(timeout)}. If this API | ||
| * is invoked for the same partition more than once, the latest offset will be used on the next poll(). Note that | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,12 +22,6 @@ public class RetriableCommitFailedException extends RetriableException { | |
|
|
||
| private static final long serialVersionUID = 1L; | ||
|
|
||
| public static RetriableCommitFailedException withUnderlyingMessage(String additionalMessage) { | ||
|
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. This function is not used anywhere so remove. |
||
| return new RetriableCommitFailedException("Offset commit failed with a retriable exception. " + | ||
| "You should retry committing the latest consumed offsets. " + | ||
| "The underlying error was: " + additionalMessage); | ||
| } | ||
|
|
||
| public RetriableCommitFailedException(Throwable t) { | ||
| super("Offset commit failed with a retriable exception. You should retry committing " + | ||
| "the latest consumed offsets.", t); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -36,6 +36,7 @@ | |||||
| import org.apache.kafka.common.errors.FencedInstanceIdException; | ||||||
| import org.apache.kafka.common.errors.GroupAuthorizationException; | ||||||
| import org.apache.kafka.common.errors.InterruptException; | ||||||
| import org.apache.kafka.common.errors.RebalanceInProgressException; | ||||||
| import org.apache.kafka.common.errors.RetriableException; | ||||||
| import org.apache.kafka.common.errors.TimeoutException; | ||||||
| import org.apache.kafka.common.errors.TopicAuthorizationException; | ||||||
|
|
@@ -1045,10 +1046,14 @@ private RequestFuture<Void> sendOffsetCommitRequest(final Map<TopicPartition, Of | |||||
| if (subscriptions.partitionsAutoAssigned()) { | ||||||
| generation = generation(); | ||||||
| // if the generation is null, we are not part of an active group (and we expect to be). | ||||||
| // the only thing we can do is fail the commit and let the user rejoin the group in poll() | ||||||
| // the only thing we can do is fail the commit and let the user rejoin the group in poll(); | ||||||
| // we use RebalanceInProgressException to indicate this may not be a fatal error, and leave | ||||||
| // it to users whether they want to handle differently than CommitFailed | ||||||
| if (generation == null) { | ||||||
| log.info("Failing OffsetCommit request since the consumer is not part of an active group"); | ||||||
| return RequestFuture.failure(new CommitFailedException()); | ||||||
| return RequestFuture.failure(new RebalanceInProgressException("Offset commit cannot be completed since 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. This is the main proposal for returning a different exception.
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. Is it always the case that 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. This is a good question, I would replace this case with the RetriableCommitFailed exception (see my other comment below). |
||||||
| "consumer group is not part of an active group yet. You can try completing the rebalance " + | ||||||
|
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.
Suggested change
|
||||||
| "by calling poll() and then retry the operation")); | ||||||
| } | ||||||
| } else | ||||||
| generation = Generation.NO_GENERATION; | ||||||
|
|
@@ -1127,15 +1132,19 @@ public void handle(OffsetCommitResponse commitResponse, RequestFuture<Void> futu | |||||
| /* Consumer never tries to commit offset in between join-group and sync-group, | ||||||
| * and hence on broker-side it is not expected to see a commit offset request | ||||||
| * during CompletingRebalance phase; if it ever happens then broker would return | ||||||
| * this error. In this case we should just treat as a fatal CommitFailed exception. | ||||||
| * However, we do not need to reset generations and just request re-join, such that | ||||||
| * if the caller decides to proceed and poll, it would still try to proceed and re-join normally. | ||||||
| * this error. In this case we would just throw a RebalanceInProgressException | ||||||
| * and request re-join, but we do not need to reset generations. | ||||||
| * If the caller decides to proceed and poll, it would still try to proceed and re-join normally. | ||||||
| */ | ||||||
| requestRejoin(); | ||||||
| future.raise(new RebalanceInProgressException()); | ||||||
|
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. This is the main proposal for returning a different exception. |
||||||
| return; | ||||||
| } else if (error == Errors.UNKNOWN_MEMBER_ID) { | ||||||
| // need to reset generation and re-join group | ||||||
| resetGenerationOnResponseError(ApiKeys.OFFSET_COMMIT, error); | ||||||
| future.raise(new CommitFailedException()); | ||||||
| return; | ||||||
| } else if (error == Errors.UNKNOWN_MEMBER_ID | ||||||
|
guozhangwang marked this conversation as resolved.
|
||||||
| || error == Errors.ILLEGAL_GENERATION) { | ||||||
| } else if (error == Errors.ILLEGAL_GENERATION) { | ||||||
| // need to reset generation and re-join group | ||||||
| resetGenerationOnResponseError(ApiKeys.OFFSET_COMMIT, error); | ||||||
| future.raise(new CommitFailedException()); | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -222,21 +222,45 @@ public synchronized boolean assignFromUser(Set<TopicPartition> partitions) { | |
| if (this.assignment.partitionSet().equals(partitions)) | ||
| return false; | ||
|
|
||
| Map<TopicPartition, TopicPartitionState> assignedPartitionStates = partitionToStateMap(partitions); | ||
|
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: should declare final as well?
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. Ack. |
||
|
|
||
| assignmentId++; | ||
|
|
||
| // update the subscribed topics | ||
| Set<String> manualSubscribedTopics = new HashSet<>(); | ||
| Map<TopicPartition, TopicPartitionState> partitionToState = new HashMap<>(); | ||
| for (TopicPartition partition : partitions) { | ||
| TopicPartitionState state = assignment.stateValue(partition); | ||
| if (state == null) | ||
| state = new TopicPartitionState(); | ||
| partitionToState.put(partition, state); | ||
| manualSubscribedTopics.add(partition.topic()); | ||
| } | ||
| this.assignment.set(partitionToState); | ||
|
|
||
| this.assignment.set(assignedPartitionStates); | ||
| return changeSubscription(manualSubscribedTopics); | ||
| } | ||
|
|
||
| /** | ||
| * Change the assignment to the specified partitions returned from the coordinator, note this is | ||
| * different from {@link #assignFromUser(Set)} which directly set the assignment from user inputs. | ||
| */ | ||
| public synchronized void assignFromSubscribed(Collection<TopicPartition> assignments) { | ||
| if (!this.partitionsAutoAssigned()) | ||
| throw new IllegalArgumentException("Attempt to dynamically assign partitions while manual assignment in use"); | ||
|
|
||
|
|
||
|
guozhangwang marked this conversation as resolved.
Outdated
|
||
| final Map<TopicPartition, TopicPartitionState> assignedPartitionStates = partitionToStateMap(assignments); | ||
| assignmentId++; | ||
| this.assignment.set(assignedPartitionStates); | ||
| } | ||
|
|
||
| private Map<TopicPartition, TopicPartitionState> partitionToStateMap(Collection<TopicPartition> assignments) { | ||
| Map<TopicPartition, TopicPartitionState> map = new HashMap<>(assignments.size()); | ||
|
guozhangwang marked this conversation as resolved.
Outdated
|
||
| for (final TopicPartition tp : assignments) { | ||
| if (assignment.contains(tp)) | ||
| map.put(tp, assignment.stateValue(tp)); | ||
| else | ||
| map.put(tp, new TopicPartitionState()); | ||
| } | ||
| return map; | ||
| } | ||
|
|
||
| /** | ||
| * @return true if assignments matches subscription, otherwise false | ||
| */ | ||
|
|
@@ -262,20 +286,6 @@ public synchronized boolean checkAssignmentMatchedSubscription(Collection<TopicP | |
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Change the assignment to the specified partitions returned from the coordinator, note this is | ||
| * different from {@link #assignFromUser(Set)} which directly set the assignment from user inputs. | ||
| */ | ||
| public synchronized void assignFromSubscribed(Collection<TopicPartition> assignments) { | ||
| if (!this.partitionsAutoAssigned()) | ||
| throw new IllegalArgumentException("Attempt to dynamically assign partitions while manual assignment in use"); | ||
|
|
||
|
|
||
| Map<TopicPartition, TopicPartitionState> assignedPartitionStates = partitionToStateMap(assignments); | ||
| assignmentId++; | ||
| this.assignment.set(assignedPartitionStates); | ||
| } | ||
|
|
||
| private void registerRebalanceListener(ConsumerRebalanceListener listener) { | ||
| if (listener == null) | ||
| throw new IllegalArgumentException("RebalanceListener cannot be null"); | ||
|
|
@@ -410,7 +420,7 @@ synchronized List<TopicPartition> fetchablePartitions(Predicate<TopicPartition> | |
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| synchronized boolean partitionsAutoAssigned() { | ||
| public synchronized boolean partitionsAutoAssigned() { | ||
|
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. Called by KafkaConsumer
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: wonder if it's worth changing this name. For example:
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. ack. |
||
| return this.subscriptionType == SubscriptionType.AUTO_TOPICS || this.subscriptionType == SubscriptionType.AUTO_PATTERN; | ||
| } | ||
|
|
||
|
|
@@ -669,13 +679,6 @@ public synchronized ConsumerRebalanceListener rebalanceListener() { | |
| return rebalanceListener; | ||
| } | ||
|
|
||
| private static Map<TopicPartition, TopicPartitionState> partitionToStateMap(Collection<TopicPartition> assignments) { | ||
| Map<TopicPartition, TopicPartitionState> map = new HashMap<>(assignments.size()); | ||
| for (TopicPartition tp : assignments) | ||
| map.put(tp, new TopicPartitionState()); | ||
| return map; | ||
| } | ||
|
|
||
| private static class TopicPartitionState { | ||
|
|
||
| private FetchState fetchState; | ||
|
|
||
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.
This is the tricky part: I have to use a non-zero timer so that blocking rpc like find-coordinator is guaranteed to poll for once, otherwise the
future.hasExpiredwill trigger immediately and we would doomed to be not finishing that call.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.
I am wondering about two things here:
kafka/clients/src/main/java/org/apache/kafka/clients/consumer/internals/ConsumerNetworkClient.java
Lines 210 to 215 in d112ffd
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.
If the timer is set to zero, it means that we would not send the request at all -- note that
client.sendcall just queued up the request in the queue, and onlyclient.pollwould write it to the socket, and that's why I cannot have the timer to be 0.Setting it to be 1 should be robust since the
timeris initialized at the beginning of the call and until the firstclient.poll()it would not be checked, and hence not returned early.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.
I've further polished it to only use
timer(1)if the remaining is still > 0, in this case we still make sure thatconsumer.poll(0)can return instantaneously.