Skip to content
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ project(':streams') {
compile "$slf4jlog4j"
compile 'org.rocksdb:rocksdbjni:3.10.1'

testCompile '$junit'
testCompile "$junit"
testCompile project(path: ':clients', configuration: 'archives')
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* administratively adjusted).
* <p>
* There are many uses for this functionality. One common use is saving offsets in a custom store. By saving offsets in
* the {@link #onPartitionsRevoked(Consumer, Collection)} call we can ensure that any time partition assignment changes
* the {@link #onPartitionsRevoked(Collection)}, call we can ensure that any time partition assignment changes
* the offset gets saved.
* <p>
* Another use is flushing out any kind of cache of intermediate results the consumer may be keeping. For example,
Expand All @@ -42,21 +42,27 @@
* <p>
* This callback will execute in the user thread as part of the {@link Consumer#poll(long) poll(long)} call whenever partition assignment changes.
* <p>
* It is guaranteed that all consumer processes will invoke {@link #onPartitionsRevoked(Consumer, Collection) onPartitionsRevoked} prior to
* any process invoking {@link #onPartitionsAssigned(Consumer, Collection) onPartitionsAssigned}. So if offsets or other state is saved in the
* {@link #onPartitionsRevoked(Consumer, Collection) onPartitionsRevoked} call it is guaranteed to be saved by the time the process taking over that
* partition has their {@link #onPartitionsAssigned(Consumer, Collection) onPartitionsAssigned} callback called to load the state.
* It is guaranteed that all consumer processes will invoke {@link #onPartitionsRevoked(Collection) onPartitionsRevoked} prior to
* any process invoking {@link #onPartitionsAssigned(Collection) onPartitionsAssigned}. So if offsets or other state is saved in the
* {@link #onPartitionsRevoked(Collection) onPartitionsRevoked} call it is guaranteed to be saved by the time the process taking over that
* partition has their {@link #onPartitionsAssigned(Collection) onPartitionsAssigned} callback called to load the state.
* <p>
* Here is pseudo-code for a callback implementation for saving offsets:
* <pre>
* {@code
* public class SaveOffsetsOnRebalance implements ConsumerRebalanceListener {
* public void onPartitionsAssigned(Consumer<?, ?> consumer, Collection<TopicPartition> partitions) {
* private Consumer<?,?> consumer;
*
* public SaveOffsetsOnRebalance(Consumer<?,?> consumer) {
* this.consumer = consumer;
* }
*
* public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
* // read the offsets from an external store using some custom code not described here
* for(TopicPartition partition: partitions)
* consumer.seek(partition, readOffsetFromExternalStore(partition));
* }
* public void onPartitionsRevoked(Consumer<?, ?> consumer, Collection<TopicPartition> partitions) {
* public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
* // save the offsets in an external store using some custom code not described here
* for(TopicPartition partition: partitions)
* saveOffsetInExternalStore(consumer.position(partition));
Expand All @@ -69,18 +75,17 @@ public interface ConsumerRebalanceListener {

/**
* A callback method the user can implement to provide handling of customized offsets on completion of a successful
* partition re-assignement. This method will be called after an offset re-assignement completes and before the
* partition re-assignment. This method will be called after an offset re-assignment completes and before the
* consumer starts fetching data.
* <p>
* It is guaranteed that all the processes in a consumer group will execute their
* {@link #onPartitionsRevoked(Consumer, Collection)} callback before any instance executes its
* {@link #onPartitionsAssigned(Consumer, Collection)} callback.
* {@link #onPartitionsRevoked(Collection)} callback before any instance executes its
* {@link #onPartitionsAssigned(Collection)} callback.
*
* @param consumer Reference to the consumer for convenience
* @param partitions The list of partitions that are now assigned to the consumer (may include partitions previously
* assigned to the consumer)
*/
public void onPartitionsAssigned(Consumer<?, ?> consumer, Collection<TopicPartition> partitions);
public void onPartitionsAssigned(Collection<TopicPartition> partitions);

/**
* A callback method the user can implement to provide handling of offset commits to a customized store on the start
Expand All @@ -90,8 +95,7 @@ public interface ConsumerRebalanceListener {
* <p>
* For examples on usage of this API, see Usage Examples section of {@link KafkaConsumer KafkaConsumer}
*
* @param consumer Reference to the consumer for convenience
* @param partitions The list of partitions that were assigned to the consumer on the last rebalance
*/
public void onPartitionsRevoked(Consumer<?, ?> consumer, Collection<TopicPartition> partitions);
public void onPartitionsRevoked(Collection<TopicPartition> partitions);
}
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,13 @@
* </ol>
*
* This type of usage is simplest when the partition assignment is also done manually (this would be likely in the
* search index use case described above). If the partition assignment is done automatically special care will also be
* needed to handle the case where partition assignments change. This can be handled using a special callback specified
* using <code>rebalance.callback.class</code>, which specifies an implementation of the interface
* {@link ConsumerRebalanceListener}. When partitions are taken from a consumer the consumer will want to commit its
* offset for those partitions by implementing
* {@link ConsumerRebalanceListener#onPartitionsRevoked(Consumer, Collection)}. When partitions are assigned to a
* search index use case described above). If the partition assignment is done automatically special care is
* needed to handle the case where partition assignments change. This can be done by providing a
* {@link ConsumerRebalanceListener} instance in the call to {@link #subscribe(List, ConsumerRebalanceListener)}.
* When partitions are taken from a consumer the consumer will want to commit its offset for those partitions by
* implementing {@link ConsumerRebalanceListener#onPartitionsRevoked(Collection)}. When partitions are assigned to a
* consumer, the consumer will want to look up the offset for those new partitions an correctly initialize the consumer
* to that position by implementing {@link ConsumerRebalanceListener#onPartitionsAssigned(Consumer, Collection)}.
* to that position by implementing {@link ConsumerRebalanceListener#onPartitionsAssigned(Collection)}.
* <p>
* Another common use for {@link ConsumerRebalanceListener} is to flush any caches the application maintains for
* partitions that are moved elsewhere.
Expand Down Expand Up @@ -395,7 +394,7 @@
*
*/
@InterfaceStability.Unstable
public class KafkaConsumer<K, V> implements Consumer<K, V>, Metadata.Listener {
public class KafkaConsumer<K, V> implements Consumer<K, V> {

private static final Logger log = LoggerFactory.getLogger(KafkaConsumer.class);
private static final long NO_CURRENT_THREAD = -1L;
Expand All @@ -417,6 +416,7 @@ public class KafkaConsumer<K, V> implements Consumer<K, V>, Metadata.Listener {
private final boolean autoCommit;
private final long autoCommitIntervalMs;
private boolean closed = false;
private Metadata.Listener metadataListener;

// currentThread holds the threadId of the current thread accessing KafkaConsumer
// and is used to prevent multi-threaded access
Expand Down Expand Up @@ -652,7 +652,7 @@ public void subscribe(List<String> topics, ConsumerRebalanceListener listener) {
acquire();
try {
log.debug("Subscribed to topic(s): {}", Utils.join(topics, ", "));
this.subscriptions.subscribe(topics, SubscriptionState.wrapListener(this, listener));
this.subscriptions.subscribe(topics, listener);
metadata.setTopics(topics);
} finally {
release();
Expand Down Expand Up @@ -700,9 +700,22 @@ public void subscribe(Pattern pattern, ConsumerRebalanceListener listener) {
acquire();
try {
log.debug("Subscribed to pattern: {}", pattern);
this.subscriptions.subscribe(pattern, SubscriptionState.wrapListener(this, listener));
metadataListener = new Metadata.Listener() {
@Override
public void onMetadataUpdate(Cluster cluster) {
final List<String> topicsToSubscribe = new ArrayList<>();

for (String topic : cluster.topics())
if (subscriptions.getSubscribedPattern().matcher(topic).matches())
topicsToSubscribe.add(topic);

subscriptions.changeSubscription(topicsToSubscribe);
metadata.setTopics(topicsToSubscribe);
}
};
this.subscriptions.subscribe(pattern, listener);
this.metadata.needMetadataForAllTopics(true);
this.metadata.addListener(this);
this.metadata.addListener(metadataListener);
} finally {
release();
}
Expand All @@ -716,7 +729,7 @@ public void unsubscribe() {
try {
this.subscriptions.unsubscribe();
this.metadata.needMetadataForAllTopics(false);
this.metadata.removeListener(this);
this.metadata.removeListener(metadataListener);
} finally {
release();
}
Expand Down Expand Up @@ -1214,17 +1227,4 @@ private void release() {
if (refcount.decrementAndGet() == 0)
currentThread.set(NO_CURRENT_THREAD);
}

@Override
public void onMetadataUpdate(Cluster cluster) {
final List<String> topicsToSubscribe = new ArrayList<>();

for (String topic : cluster.topics())
if (this.subscriptions.getSubscribedPattern().matcher(topic).matches())
topicsToSubscribe.add(topic);

subscriptions.changeSubscription(topicsToSubscribe);
metadata.setTopics(topicsToSubscribe);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public synchronized void subscribe(List<String> topics) {
@Override
public void subscribe(Pattern pattern, final ConsumerRebalanceListener listener) {
ensureNotClosed();
this.subscriptions.subscribe(pattern, SubscriptionState.wrapListener(this, listener));
this.subscriptions.subscribe(pattern, listener);
List<String> topicsToSubscribe = new ArrayList<>();
for (String topic: partitions.keySet()) {
if (pattern.matcher(topic).matches() &&
Expand All @@ -84,7 +84,7 @@ public void subscribe(Pattern pattern, final ConsumerRebalanceListener listener)
@Override
public synchronized void subscribe(List<String> topics, final ConsumerRebalanceListener listener) {
ensureNotClosed();
this.subscriptions.subscribe(topics, SubscriptionState.wrapListener(this, listener));
this.subscriptions.subscribe(topics, listener);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.apache.kafka.clients.consumer.internals;

import org.apache.kafka.clients.ClientResponse;
import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
import org.apache.kafka.clients.consumer.OffsetCommitCallback;
import org.apache.kafka.common.KafkaException;
import org.apache.kafka.common.MetricName;
Expand Down Expand Up @@ -155,15 +156,15 @@ public void ensurePartitionAssignment() {
if (!subscriptions.partitionAssignmentNeeded())
return;

SubscriptionState.RebalanceListener listener = subscriptions.listener();
ConsumerRebalanceListener listener = subscriptions.listener();

// execute the user's listener before rebalance
log.debug("Revoking previously assigned partitions {}", this.subscriptions.assignedPartitions());
try {
Set<TopicPartition> revoked = new HashSet<>(subscriptions.assignedPartitions());
listener.onPartitionsRevoked(revoked);
} catch (Exception e) {
log.error("User provided listener " + listener.underlying().getClass().getName()
log.error("User provided listener " + listener.getClass().getName()
+ " failed on partition revocation: ", e);
}

Expand All @@ -175,7 +176,7 @@ public void ensurePartitionAssignment() {
Set<TopicPartition> assigned = new HashSet<>(subscriptions.assignedPartitions());
listener.onPartitionsAssigned(assigned);
} catch (Exception e) {
log.error("User provided listener " + listener.underlying().getClass().getName()
log.error("User provided listener " + listener.getClass().getName()
+ " failed on partition assignment: ", e);
}
}
Expand Down Expand Up @@ -641,6 +642,10 @@ public void handle(HeartbeatResponse heartbeatResponse, RequestFuture<Void> futu
log.info("Attempt to heart beat failed since coordinator is either not started or not valid, marking it as dead.");
coordinatorDead();
future.raise(Errors.forCode(error));
} else if (error == Errors.REBALANCE_IN_PROGRESS.code()) {
log.info("Attempt to heart beat failed since the group is rebalancing, try to re-join group.");
subscriptions.needReassignment();
future.raise(Errors.REBALANCE_IN_PROGRESS);
} else if (error == Errors.ILLEGAL_GENERATION.code()) {
log.info("Attempt to heart beat failed since generation id is not legal, try to re-join group.");
subscriptions.needReassignment();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,17 @@

package org.apache.kafka.clients.consumer.internals;

import java.util.Collection;

import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
import org.apache.kafka.common.TopicPartition;

import java.util.Collection;

public class NoOpConsumerRebalanceListener implements ConsumerRebalanceListener {

@Override
public void onPartitionsAssigned(Consumer<?, ?> consumer, Collection<TopicPartition> partitions) {}
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {}

@Override
public void onPartitionsRevoked(Consumer<?, ?> consumer, Collection<TopicPartition> partitions) {}
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@
*/
package org.apache.kafka.clients.consumer.internals;

import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
import org.apache.kafka.clients.consumer.OffsetResetStrategy;
import org.apache.kafka.common.TopicPartition;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
Expand Down Expand Up @@ -70,7 +68,8 @@ public class SubscriptionState {
private final OffsetResetStrategy defaultResetStrategy;

/* Listener to be invoked when assignment changes */
private RebalanceListener listener;
private ConsumerRebalanceListener listener;

private static final String SUBSCRIPTION_EXCEPTION_MESSAGE =
"Subscription to topics, partitions and pattern are mutually exclusive";

Expand All @@ -84,7 +83,7 @@ public SubscriptionState(OffsetResetStrategy defaultResetStrategy) {
this.subscribedPattern = null;
}

public void subscribe(List<String> topics, RebalanceListener listener) {
public void subscribe(List<String> topics, ConsumerRebalanceListener listener) {
if (listener == null)
throw new IllegalArgumentException("RebalanceListener cannot be null");

Expand Down Expand Up @@ -130,7 +129,7 @@ public void assign(List<TopicPartition> partitions) {
this.assignment.keySet().retainAll(this.userAssignment);
}

public void subscribe(Pattern pattern, RebalanceListener listener) {
public void subscribe(Pattern pattern, ConsumerRebalanceListener listener) {
if (listener == null)
throw new IllegalArgumentException("RebalanceListener cannot be null");

Expand Down Expand Up @@ -305,7 +304,7 @@ private void addAssignedPartition(TopicPartition tp) {
this.assignment.put(tp, new TopicPartitionState());
}

public RebalanceListener listener() {
public ConsumerRebalanceListener listener() {
return listener;
}

Expand Down Expand Up @@ -375,46 +374,4 @@ private boolean isFetchable() {

}

public static RebalanceListener wrapListener(final Consumer<?, ?> consumer,
final ConsumerRebalanceListener listener) {
if (listener == null)
throw new IllegalArgumentException("ConsumerRebalanceLister must not be null");

return new RebalanceListener() {
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
listener.onPartitionsAssigned(consumer, partitions);
}

@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
listener.onPartitionsRevoked(consumer, partitions);
}

@Override
public ConsumerRebalanceListener underlying() {
return listener;
}
};
}

/**
* Wrapper around {@link ConsumerRebalanceListener} to get around the need to provide a reference
* to the consumer in this class.
*/
public static class RebalanceListener {
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {

}

public void onPartitionsRevoked(Collection<TopicPartition> partitions) {

}

public ConsumerRebalanceListener underlying() {
return null;
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ public enum Errors {
new ApiException("Some of the committing partitions are not assigned the committer")),
INVALID_COMMIT_OFFSET_SIZE(28,
new ApiException("The committing offset data size is not valid")),
AUTHORIZATION_FAILED(29, new ApiException("Request is not authorized."));
AUTHORIZATION_FAILED(29, new ApiException("Request is not authorized.")),
REBALANCE_IN_PROGRESS(30,
new ApiException("The group is rebalancing, so a rejoin is needed."));

private static final Logger log = LoggerFactory.getLogger(Errors.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.apache.kafka.common.protocol.types.Schema;
import org.apache.kafka.common.protocol.types.Struct;

import java.nio.ByteBuffer;;
import java.nio.ByteBuffer;
import java.util.*;

public class LeaderAndIsrRequest extends AbstractRequest {
Expand Down
Loading