Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
37b5c63
first version
guozhangwang Sep 6, 2019
4a34116
add unit tests
guozhangwang Sep 7, 2019
d964cea
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Sep 10, 2019
f5b8f96
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Sep 11, 2019
b07bac2
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Sep 12, 2019
29f8942
fix unit tests
guozhangwang Sep 12, 2019
3000d6f
add RebalanceInProgressException
guozhangwang Sep 13, 2019
8464fa7
rebase from trunk
guozhangwang Sep 17, 2019
f804b3d
refactor unit tests
guozhangwang Sep 17, 2019
3c62029
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Sep 17, 2019
284ef77
poll with 1ms than 0ms for discover coordinator
guozhangwang Sep 18, 2019
9825063
refactor the newly added test with auto-tick 1ms
guozhangwang Sep 18, 2019
929048a
rebase from trunk
guozhangwang Oct 21, 2019
58adc09
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Oct 22, 2019
c79a93d
fix unit tests
guozhangwang Oct 22, 2019
75c4e88
rebase from trunk again
guozhangwang Oct 22, 2019
95ef3fb
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Oct 27, 2019
3b844ff
use RetriableCommitFailedException
guozhangwang Oct 27, 2019
781514d
unit tests
guozhangwang Oct 27, 2019
8b08a3e
handle retrialbe commit failed in streams
guozhangwang Oct 27, 2019
8fa5d74
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Oct 29, 2019
d75fcb9
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Oct 29, 2019
b176e4e
fix unit tests
guozhangwang Oct 29, 2019
7a826f9
remove imports
guozhangwang Oct 29, 2019
31e372e
fix a real bug exposed by flaky test
guozhangwang Oct 30, 2019
366739b
remove debugging info
guozhangwang Oct 30, 2019
faf787e
minor fix on CommitFailed class
guozhangwang Oct 30, 2019
c71af82
rebase from trunk
guozhangwang Nov 8, 2019
ffe6146
more java doc
guozhangwang Nov 8, 2019
518049c
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Dec 11, 2019
30d5e53
address comments
guozhangwang Dec 12, 2019
fa3456d
address comments
guozhangwang Dec 24, 2019
0220d96
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Jan 2, 2020
27d013a
address comment
guozhangwang Jan 2, 2020
94611cc
do not reset if generation has changed
guozhangwang Jan 2, 2020
b529847
remove restrictive check on commit
guozhangwang Jan 3, 2020
33fd785
check assignment
guozhangwang Jan 3, 2020
1df9808
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Jan 3, 2020
3057405
more comments
guozhangwang Jan 3, 2020
21f531d
remember the fatal exception and throw
guozhangwang Jan 4, 2020
effb29d
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Jan 6, 2020
6fbd976
comments
guozhangwang Jan 6, 2020
f4e7111
Merge branch 'trunk' of https://github.com/apache/kafka into K8421-re…
guozhangwang Jan 8, 2020
91b6606
move back to RebalanceInProgressException
guozhangwang Jan 8, 2020
0ad52ec
address comments
guozhangwang Jan 9, 2020
27f76b7
rebase from trunk
guozhangwang Jan 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public class CommitFailedException extends KafkaException {

private static final long serialVersionUID = 1L;

public CommitFailedException(final String message) {
super(message);
}

public CommitFailedException() {
super("Commit cannot be completed since the group has already " +
"rebalanced and assigned the partitions to another member. This means that the time " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1231,9 +1231,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(timer.remainingMs() > 0 ? time.timer(1L) : timer);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been trying to follow this. Looking into ConsumerCoordinator, I think I see what you're talking about, there are several opportunities to return early if the timer expires. But is 1ms enough to guarantee that we'll actually get through to where we fetch the data? Is it possible to just modify the conditionals farther down to make sure that we can always make progress even when the timer is set to 0ms (i.e., the request is fully asynchronous). To me, a timeout of 0ms doesn't mean that the operation should take no time at all, just that it shouldn't block. It seems "within bounds" to still perform any operation we need in order to guarantee we make progress.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the original problem is that with timeout(0), we cannot receive the join-group response and send the sync-group request in a single call as it is two round-trips; so I made a 1ms here trying to proceed faster instead requesting users to call poll() repeatedly. But now I realized that while fixing 6df058e the client.transmitSends() actually worked around it so now we can indeed call with 0m always.

I'll change it back to always call 0ms now.

} else {
while (!updateAssignmentMetadataIfNeeded(time.timer(Long.MAX_VALUE))) {
log.warn("Still waiting for metadata");
Expand Down Expand Up @@ -1307,12 +1309,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();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this dropped? Are you thinking it's just redundant?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah... that makes sense.

return fetcher.fetchedRecords();
}

Expand All @@ -1334,6 +1330,9 @@ private Map<TopicPartition, List<ConsumerRecord<K, V>>> pollForFetches(Timer tim
* @throws org.apache.kafka.clients.consumer.CommitFailedException if the commit failed and cannot be retried.
* This can only occur if you are using automatic group management with {@link #subscribe(Collection)},
* or if there is an active group with the same groupId which is using group management.
* @throws org.apache.kafka.clients.consumer.RetriableCommitFailedException if the commit failed but can be retriable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify: can be? This implies that we don't know if it's really retriable, just that it might be retriable. Is that the intent?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "can be retried"?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Can be retriable" does seem to imply that in some circumstances it can be retried, while in others it can't be retried at all. "Can be retried" implies it's up to the user whether they want to retry

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think retried is better, will change accordingly.

* This can occur if, e.g. consumer instance is in the middle of a rebalance. In such cases
* commit could be retried after the rebalance is completed with the {@link #poll(Duration)} call.
* @throws org.apache.kafka.common.errors.WakeupException if {@link #wakeup()} is called before or while this
* function is called
* @throws org.apache.kafka.common.errors.InterruptException if the calling thread is interrupted before or while
Expand Down Expand Up @@ -1369,6 +1368,9 @@ public void commitSync() {
* @throws org.apache.kafka.clients.consumer.CommitFailedException if the commit failed and cannot be retried.
* This can only occur if you are using automatic group management with {@link #subscribe(Collection)},
* or if there is an active group with the same groupId which is using group management.
* @throws org.apache.kafka.clients.consumer.RetriableCommitFailedException if the commit failed but can be retriable.
* This can occur if, e.g. consumer instance is in the middle of a rebalance. In such cases
* commit could be retried after the rebalance is completed with the {@link #poll(Duration)} call.
* @throws org.apache.kafka.common.errors.WakeupException if {@link #wakeup()} is called before or while this
* function is called
* @throws org.apache.kafka.common.errors.InterruptException if the calling thread is interrupted before or while
Expand Down Expand Up @@ -1402,7 +1404,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
Expand All @@ -1415,6 +1418,9 @@ public void commitSync(Duration timeout) {
* @throws org.apache.kafka.clients.consumer.CommitFailedException if the commit failed and cannot be retried.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we make this more specific, or maybe provide examples? eg this happens if you try to commit offsets for partitions you don't own, or you fell out of the group

* This can only occur if you are using automatic group management with {@link #subscribe(Collection)},
* or if there is an active group with the same groupId which is using group management.
* @throws org.apache.kafka.clients.consumer.RetriableCommitFailedException if the commit failed but can be retriable.
* This can occur if, e.g. consumer instance is in the middle of a rebalance. In such cases

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: If a user tries to commit during an eager rebalance, the CommitFailedException will still be thrown -- we should consider making this explicit, I worry users might read this and think it's ok to commit during an (eager) rebalance as long as they catch the RetriableCommitFailedException

* commit could be retried after the rebalance is completed with the {@link #poll(Duration)} call.
* @throws org.apache.kafka.common.errors.WakeupException if {@link #wakeup()} is called before or while this
* function is called
* @throws org.apache.kafka.common.errors.InterruptException if the calling thread is interrupted before or while
Expand All @@ -1435,43 +1441,48 @@ public void commitSync(final Map<TopicPartition, OffsetAndMetadata> offsets) {
}

/**
* Commit the specified offsets for the specified list of topics and partitions.
* <p>
* 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.
* <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.
* <p>
* Note that asynchronous offset commits sent previously with the {@link #commitAsync(OffsetCommitCallback)}
* (or similar) are guaranteed to have their callbacks invoked prior to completion of this method.
*
* @param offsets A map of offsets by partition with associated metadata
* @param timeout The maximum amount of time to await completion of the offset commit
* @throws org.apache.kafka.clients.consumer.CommitFailedException if the commit failed and cannot be retried.
* This can only occur if you are using automatic group management with {@link #subscribe(Collection)},
* or if there is an active group with the same groupId which is using group management.
* @throws org.apache.kafka.common.errors.WakeupException if {@link #wakeup()} is called before or while this
* function is called
* @throws org.apache.kafka.common.errors.InterruptException if the calling thread is interrupted before or while
* this function is called
* @throws org.apache.kafka.common.errors.AuthenticationException if authentication fails. See the exception for more details
* @throws org.apache.kafka.common.errors.AuthorizationException if not authorized to the topic or to the
* configured groupId. See the exception for more details
* @throws java.lang.IllegalArgumentException if the committed offset is negative
* @throws org.apache.kafka.common.KafkaException for any other unrecoverable errors (e.g. if offset metadata
* is too large or if the topic does not exist).
* @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.
*/
* Commit the specified offsets for the specified list of topics and partitions.
* <p>
* 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. 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This is a synchronous commits and will block until either the commit succeeds, an unrecoverable error is
* This is a synchronous commit 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.
* <p>
* Note that asynchronous offset commits sent previously with the {@link #commitAsync(OffsetCommitCallback)}
* (or similar) are guaranteed to have their callbacks invoked prior to completion of this method.
*
* @param offsets A map of offsets by partition with associated metadata
* @param timeout The maximum amount of time to await completion of the offset commit
* @throws org.apache.kafka.clients.consumer.CommitFailedException if the commit failed and cannot be retried.
* This can only occur if you are using automatic group management with {@link #subscribe(Collection)},
* or if there is an active group with the same groupId which is using group management.
* @throws org.apache.kafka.clients.consumer.RetriableCommitFailedException if the commit failed but can be retriable.
* This can occur if, e.g. consumer instance is in the middle of a rebalance. In such cases
* commit could be retried after the rebalance is completed with the {@link #poll(Duration)} call.
* @throws org.apache.kafka.common.errors.WakeupException if {@link #wakeup()} is called before or while this
* function is called
* @throws org.apache.kafka.common.errors.InterruptException if the calling thread is interrupted before or while
* this function is called
* @throws org.apache.kafka.common.errors.AuthenticationException if authentication fails. See the exception for more details
* @throws org.apache.kafka.common.errors.AuthorizationException if not authorized to the topic or to the
* configured groupId. See the exception for more details
* @throws java.lang.IllegalArgumentException if the committed offset is negative
* @throws org.apache.kafka.common.KafkaException for any other unrecoverable errors (e.g. if offset metadata
* is too large or if the topic does not exist).
* @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.
*/
@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 " +
Expand Down Expand Up @@ -1521,7 +1532,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.
Expand All @@ -1541,14 +1553,29 @@ public void commitAsync(final Map<TopicPartition, OffsetAndMetadata> offsets, Of
acquireAndEnsureOpen();
try {
maybeThrowInvalidGroupIdException();
maybeThrowIfCommitOffsetsNotOwned(offsets);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 CommitFailedException? Is this a behavioral change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this catch since we reverted the logic to verify only assigned partitions can be committed?

callback.onComplete(offsets, e);
} finally {
release();
}
}

private void maybeThrowIfCommitOffsetsNotOwned(final Map<TopicPartition, OffsetAndMetadata> offsets) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 updateLastSeenEpochIfNewer. I wonder if we could consolidate these two loops.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 offset of topic partition " + tp +
" since the partition was not dynamically assigned to this consumer");
}
}
}

/**
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.apache.kafka.common.TopicPartition;

import java.time.Duration;
import java.util.Collection;
import java.util.Map;

Expand All @@ -37,6 +38,9 @@ public interface OffsetCommitCallback {
* @throws org.apache.kafka.clients.consumer.CommitFailedException if the commit failed and cannot be retried.
* This can only occur if you are using automatic group management with {@link KafkaConsumer#subscribe(Collection)},
* or if there is an active group with the same groupId which is using group management.
* @throws org.apache.kafka.clients.consumer.RetriableCommitFailedException if the commit failed but can be retriable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: "can be retried."

* This can occur if, e.g. consumer instance is in the middle of a rebalance. In such cases
* commit could be retried after the rebalance is completed with the {@link #poll(Duration)} call.
* @throws org.apache.kafka.common.errors.WakeupException if {@link KafkaConsumer#wakeup()} is called before or while this
* function is called
* @throws org.apache.kafka.common.errors.InterruptException if the calling thread is interrupted before or while
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,16 @@
*/
package org.apache.kafka.clients.consumer;

import org.apache.kafka.common.errors.RetriableException;
import org.apache.kafka.common.errors.ApiException;

public class RetriableCommitFailedException extends RetriableException {
public class RetriableCommitFailedException extends ApiException {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is rather a fix: RetriableCommitFailedException should not be retriable (internally) as it indicate the caller whoever call commit-offsets that they need to retry. The only edge case is the auto commit mechanism, which calls commitOffsetAsync internally, but it should still be considered as a caller of that function so itself should be doing the retry logic.

Note this exception is already exposed as a public exception but there's no behavioral changes introduced by this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, the RetriableException marker interface refers to being internally retried, not to being retriable vs fatal for the external (user) caller?

@guozhangwang guozhangwang Oct 29, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Technically speaking all ApiExceptions are sort of "fatal", and it's up to users whether it is safe to retry -- for example, if you retry on getting an error from produce, you may then get out-of-order data into Kafka, but if that is fine from user's perspective, then they can still retry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a bit odd that CommitFailedException is a KafkaException, whereas RetriableCommitFailedException is an ApiException...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.. thinking about that again I would consider changing it to KafkaException since ApiException should be returned from the broker while this one should only be interpreted inside the internal of consumer.


private static final long serialVersionUID = 1L;

public static RetriableCommitFailedException withUnderlyingMessage(String additionalMessage) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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);
super("Offset commit failed with a retriable exception. This is usually because the consumer is no longer " +
"part of a group or is participating a rebalance right now. You should first call poll to complete " +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"part of a group or is participating a rebalance right now. You should first call poll to complete " +
"part of a group or is participating in a rebalance right now. You should first call poll to complete " +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the consumer is no longer part of a group is a bit ambiguous, it sounds like this will be thrown if a consumer falls out of the group or hasn't yet joined, but in those cases we just throw CommitFailedException. Can we rephrase this somehow? When might this be thrown for reasons besides a rebalance in progress?

"the rebalance and then commit the latest consumed offsets.", t);
}

public RetriableCommitFailedException(String message) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. Are we safe to remove these given that this is a public API. It's probably unlikely anyone is using them, but still..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I was not sure we allow public classes to be used in our API "contract" :P Anyhow, I don't feel strong about it and I can revert.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
public abstract class AbstractCoordinator implements Closeable {
public static final String HEARTBEAT_THREAD_PREFIX = "kafka-coordinator-heartbeat-thread";

private enum MemberState {
protected enum MemberState {
UNJOINED, // the client is not part of a group
REBALANCING, // the client has begun rebalancing
STABLE, // the client has joined and is sending heartbeats
Expand All @@ -130,11 +130,11 @@ private enum MemberState {
private MemberState state = MemberState.UNJOINED;
private HeartbeatThread heartbeatThread = null;
private RequestFuture<ByteBuffer> joinFuture = null;
private RequestFuture<Void> findCoordinatorFuture = null;
private Generation generation = Generation.NO_GENERATION;
private long lastRebalanceStartMs = -1L;
private long lastRebalanceEndMs = -1L;

private RequestFuture<Void> findCoordinatorFuture = null;

/**
* Initialize the coordination manager.
Expand Down
Loading