AndrewJSchofield commented on code in PR #22270:
URL: https://github.com/apache/kafka/pull/22270#discussion_r3380345333


##########
clients/src/main/java/org/apache/kafka/clients/consumer/ConsumerRebalanceListener.java:
##########
@@ -89,33 +107,44 @@
  * Here is pseudo-code for a callback implementation for saving offsets:
  * <pre>
  * {@code
- *   public class SaveOffsetsOnRebalance implements ConsumerRebalanceListener {
- *       private Consumer<?,?> consumer;
- *
- *       public SaveOffsetsOnRebalance(Consumer<?,?> consumer) {
- *           this.consumer = consumer;
- *       }
- *
- *       public void onPartitionsRevoked(Collection<TopicPartition> 
partitions) {
+ *   consumer.setConsumerRebalanceListener(new ConsumerRebalanceListener() {
+ *       @Override
+ *       public void onPartitionsRevoked(Collection<TopicPartition> 
partitions, RebalanceConsumer rebalanceConsumer) {
  *           // save the offsets in an external store using some custom code 
not described here
  *           for(TopicPartition partition: partitions)
- *              saveOffsetInExternalStore(consumer.position(partition));
+ *              
saveOffsetInExternalStore(rebalanceConsumer.position(partition));
  *       }
  *
- *       public void onPartitionsLost(Collection<TopicPartition> partitions) {
+ *       @Override
+ *       public void onPartitionsLost(Collection<TopicPartition> partitions, 
RebalanceConsumer rebalanceConsumer) {
  *           // do not need to save the offsets since these partitions are 
probably owned by other consumers already
  *       }
  *
- *       public void onPartitionsAssigned(Collection<TopicPartition> 
partitions) {
+ *       @Override
+ *       public void onPartitionsAssigned(Collection<TopicPartition> 
partitions, RebalanceConsumer rebalanceConsumer) {
  *           // read the offsets from an external store using some custom code 
not described here
  *           for(TopicPartition partition: partitions)
- *              consumer.seek(partition, 
readOffsetFromExternalStore(partition));
+ *              rebalanceConsumer.seek(partition, 
readOffsetFromExternalStore(partition));
  *       }
- *   }
+ *   });
+ *   consumer.subscribe(List.of("topic-1", "topic-2"));
  * }
  * </pre>
+ *
+ * TODO(adikou): re-enable as a real {@code @deprecated} javadoc tag once 
internal call sites have
+ *               migrated off ConsumerRebalanceListener; left for now to avoid 
-Werror.
+ * &#64;deprecated Since 4.4, replaced by {@link RebalanceListener}. New code 
should implement
+ *             {@link RebalanceListener} directly and register it via
+ *             {@code Consumer.setRebalanceListener(RebalanceListener)}. This 
interface remains for
+ *             source compatibility with pre-existing one-argument callback 
implementations and will
+ *             continue to be supported for the foreseeable future to give 
users time to migrate.
+ *
+ * @see RebalanceListener
+ * @see RebalanceConsumer
  */
-public interface ConsumerRebalanceListener {
+// TODO(adikou): re-enable @Deprecated once internal call sites have migrated; 
left commented for now to avoid -Werror.

Review Comment:
   We do not tend to leave TODO in the code. I suggest creating a JIRA instead 
for the follow-on work.



##########
clients/src/main/java/org/apache/kafka/clients/consumer/ConsumerRebalanceListener.java:
##########
@@ -89,33 +107,44 @@
  * Here is pseudo-code for a callback implementation for saving offsets:
  * <pre>
  * {@code
- *   public class SaveOffsetsOnRebalance implements ConsumerRebalanceListener {
- *       private Consumer<?,?> consumer;
- *
- *       public SaveOffsetsOnRebalance(Consumer<?,?> consumer) {
- *           this.consumer = consumer;
- *       }
- *
- *       public void onPartitionsRevoked(Collection<TopicPartition> 
partitions) {
+ *   consumer.setConsumerRebalanceListener(new ConsumerRebalanceListener() {
+ *       @Override
+ *       public void onPartitionsRevoked(Collection<TopicPartition> 
partitions, RebalanceConsumer rebalanceConsumer) {
  *           // save the offsets in an external store using some custom code 
not described here
  *           for(TopicPartition partition: partitions)
- *              saveOffsetInExternalStore(consumer.position(partition));
+ *              
saveOffsetInExternalStore(rebalanceConsumer.position(partition));
  *       }
  *
- *       public void onPartitionsLost(Collection<TopicPartition> partitions) {
+ *       @Override
+ *       public void onPartitionsLost(Collection<TopicPartition> partitions, 
RebalanceConsumer rebalanceConsumer) {
  *           // do not need to save the offsets since these partitions are 
probably owned by other consumers already
  *       }
  *
- *       public void onPartitionsAssigned(Collection<TopicPartition> 
partitions) {
+ *       @Override
+ *       public void onPartitionsAssigned(Collection<TopicPartition> 
partitions, RebalanceConsumer rebalanceConsumer) {
  *           // read the offsets from an external store using some custom code 
not described here
  *           for(TopicPartition partition: partitions)
- *              consumer.seek(partition, 
readOffsetFromExternalStore(partition));
+ *              rebalanceConsumer.seek(partition, 
readOffsetFromExternalStore(partition));
  *       }
- *   }
+ *   });
+ *   consumer.subscribe(List.of("topic-1", "topic-2"));
  * }
  * </pre>
+ *
+ * TODO(adikou): re-enable as a real {@code @deprecated} javadoc tag once 
internal call sites have

Review Comment:
   Again, I wouldn't leave a TODO. We understand that `[1/N]` PRs are the first 
of several pieces.



##########
clients/src/main/java/org/apache/kafka/clients/consumer/ConsumerRebalanceListener.java:
##########
@@ -89,33 +107,44 @@
  * Here is pseudo-code for a callback implementation for saving offsets:
  * <pre>
  * {@code
- *   public class SaveOffsetsOnRebalance implements ConsumerRebalanceListener {
- *       private Consumer<?,?> consumer;
- *
- *       public SaveOffsetsOnRebalance(Consumer<?,?> consumer) {
- *           this.consumer = consumer;
- *       }
- *
- *       public void onPartitionsRevoked(Collection<TopicPartition> 
partitions) {
+ *   consumer.setConsumerRebalanceListener(new ConsumerRebalanceListener() {

Review Comment:
   This method is called `setRebalanceListener` in the KIP since the latest 
revision.



##########
clients/src/main/java/org/apache/kafka/clients/consumer/RebalanceListener.java:
##########
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kafka.clients.consumer;
+
+import org.apache.kafka.common.TopicPartition;
+
+import java.time.Duration;
+import java.util.Collection;
+
+/**
+ * A callback interface that the user can implement to trigger custom actions 
when the set of partitions assigned to the
+ * consumer changes.
+ * <p>
+ * This is applicable when the consumer is having Kafka auto-manage group 
membership. If the consumer directly assigns partitions,
+ * those partitions will never be reassigned and this callback is not 
applicable.
+ * <p>
+ * When Kafka is managing the group membership, a partition re-assignment will 
be triggered any time the members of the group change or the subscription
+ * of the members changes. This can occur when processes die, new process 
instances are added or old instances come back to life after failure.
+ * Partition re-assignments can also be triggered by changes affecting the 
subscribed topics (e.g. when the number of partitions is
+ * administratively adjusted).
+ *
+ * <h3>Consumer-Aware Callbacks</h3>
+ *
+ * Each callback method receives a {@link RebalanceConsumer}, a restricted 
view of the {@link Consumer} that exposes
+ * only operations safe to invoke during a rebalance (e.g. offset commits, 
seeks, position queries). The
+ * {@code RebalanceConsumer} is valid only for the duration of the callback; 
storing a reference and using it later
+ * will throw {@link IllegalStateException}.
+ * <p>
+ * Prefer using the provided {@code RebalanceConsumer} instead of capturing 
the full {@link Consumer} reference
+ * externally. This avoids accidental use of operations like {@code poll()}, 
{@code close()}, or {@code subscribe()}
+ * that could corrupt consumer state mid-rebalance.
+ *
+ * <h3>Common Use Cases</h3>
+ *
+ * There are many uses for this functionality. One common use is saving 
offsets in a custom store. By saving offsets in
+ * the {@link #onPartitionsRevoked(Collection, RebalanceConsumer)} 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,
+ * consider a case where the consumer is subscribed to a topic containing user 
page views, and the goal is to count the
+ * number of page views per user for each five minute window. Let's say the 
topic is partitioned by the user id so that
+ * all events for a particular user go to a single consumer instance. The 
consumer can keep in memory a running
+ * tally of actions per user and only flush these out to a remote data store 
when its cache gets too big. However if a
+ * partition is reassigned it may want to automatically trigger a flush of 
this cache, before the new owner takes over
+ * consumption.
+ * <p>
+ * This callback will only execute in the user thread as part of the {@link 
KafkaConsumer#poll(java.time.Duration) poll(Duration)} call
+ * whenever partition assignment changes.
+ * <p>
+ * Under normal conditions, if a partition is reassigned from one consumer to 
another, then the old consumer will
+ * always invoke {@link #onPartitionsRevoked(Collection, RebalanceConsumer) 
onPartitionsRevoked} for that partition
+ * prior to the new consumer invoking {@link #onPartitionsAssigned(Collection, 
RebalanceConsumer) onPartitionsAssigned}
+ * for the same partition. So if offsets or other state is saved durably in the
+ * {@link #onPartitionsRevoked(Collection, RebalanceConsumer) 
onPartitionsRevoked} callback by one consumer member,
+ * it will always be accessible by the time the other consumer member taking 
over that partition triggers its
+ * {@link #onPartitionsAssigned(Collection, RebalanceConsumer) 
onPartitionsAssigned} callback to load the state.
+ * <p>
+ * You can think of revocation as a graceful way to give up ownership of a 
partition. In some cases, the consumer may not have an opportunity to do so.
+ * For example, if the session times out, then the partitions may be 
reassigned before we have a chance to revoke them gracefully.
+ * For this case, we have a third callback {@link 
#onPartitionsLost(Collection, RebalanceConsumer)}. The difference between this 
function and
+ * {@link #onPartitionsRevoked(Collection, RebalanceConsumer)} is that upon 
invocation of {@link #onPartitionsLost(Collection, RebalanceConsumer)}, the 
partitions
+ * may already be owned by some other members in the group and therefore users 
would not be able to commit its consumed offsets for example.
+ * Users could implement these two functions differently (by default,
+ * {@link #onPartitionsLost(Collection, RebalanceConsumer)} will be calling 
{@link #onPartitionsRevoked(Collection, RebalanceConsumer)} directly); for 
example, in the
+ * {@link #onPartitionsLost(Collection, RebalanceConsumer)} we should not need 
to store the offsets since we know these partitions are no longer owned by the 
consumer
+ * at that time.
+ * <p>
+ * During a rebalance event, the {@link #onPartitionsAssigned(Collection, 
RebalanceConsumer) onPartitionsAssigned} function will always be triggered 
exactly once when
+ * the rebalance completes. That is, even if there are no newly assigned 
partitions for a consumer member, its {@link #onPartitionsAssigned(Collection, 
RebalanceConsumer) onPartitionsAssigned}
+ * will still be triggered with an empty collection of partitions. As a result 
this function can be used also to notify when a rebalance event has happened.
+ * With eager rebalancing, {@link #onPartitionsRevoked(Collection, 
RebalanceConsumer)} will always be called at the start of a rebalance. On the 
other hand, {@link #onPartitionsLost(Collection, RebalanceConsumer)}
+ * will only be called when there were non-empty partitions that were lost.
+ * With cooperative rebalancing, {@link #onPartitionsRevoked(Collection, 
RebalanceConsumer)} and {@link #onPartitionsLost(Collection, RebalanceConsumer)}
+ * will only be triggered when there are non-empty partitions revoked or lost 
from this consumer member during a rebalance event.
+ * <p>
+ * It is possible
+ * for a {@link org.apache.kafka.common.errors.WakeupException} or {@link 
org.apache.kafka.common.errors.InterruptException}
+ * to be raised from one of these nested invocations. In this case, the 
exception will be propagated to the current
+ * invocation of {@link KafkaConsumer#poll(java.time.Duration)} in which this 
callback is being executed. This means it is not
+ * necessary to catch these exceptions and re-attempt to wakeup or interrupt 
the consumer thread.
+ * Also if the callback function implementation itself throws an exception, 
this exception will be propagated to the current
+ * invocation of {@link KafkaConsumer#poll(java.time.Duration)} as well.
+ * <p>
+ * Note that callbacks only serve as notification of an assignment change.
+ * They cannot be used to express acceptance of the change.
+ * Hence throwing an exception from a callback does not affect the assignment 
in any way,
+ * as it will be propagated all the way up to the {@link 
KafkaConsumer#poll(java.time.Duration)} call.
+ * If user captures the exception in the caller, the callback is still assumed 
successful and no further retries will be attempted.
+ *
+ * <h3>Relation to {@link ConsumerRebalanceListener}</h3>
+ *
+ * {@code RebalanceListener} is the preferred entry point for new code. The 
legacy {@link ConsumerRebalanceListener}
+ * interface extends this one and adds one-argument variants of each callback 
for source compatibility with
+ * pre-existing implementations. New code should implement {@code 
RebalanceListener} directly and register it via
+ * {@code Consumer.setRebalanceListener(RebalanceListener)}.
+ * <p>
+ *
+ * Here is pseudo-code for a callback implementation for saving offsets:
+ * <pre>
+ * {@code
+ *   consumer.setRebalanceListener(new RebalanceListener() {
+ *       @Override
+ *       public void onPartitionsRevoked(Collection<TopicPartition> 
partitions, RebalanceConsumer rebalanceConsumer) {
+ *           // save the offsets in an external store using some custom code 
not described here
+ *           for(TopicPartition partition: partitions)
+ *              
saveOffsetInExternalStore(rebalanceConsumer.position(partition));
+ *       }
+ *
+ *       @Override
+ *       public void onPartitionsLost(Collection<TopicPartition> partitions, 
RebalanceConsumer rebalanceConsumer) {
+ *           // do not need to save the offsets since these partitions are 
probably owned by other consumers already
+ *       }
+ *
+ *       @Override
+ *       public void onPartitionsAssigned(Collection<TopicPartition> 
partitions, RebalanceConsumer rebalanceConsumer) {
+ *           // read the offsets from an external store using some custom code 
not described here
+ *           for(TopicPartition partition: partitions)
+ *              rebalanceConsumer.seek(partition, 
readOffsetFromExternalStore(partition));
+ *       }
+ *   });
+ *   consumer.subscribe(List.of("topic-1", "topic-2"));
+ * }
+ * </pre>
+ *
+ * @see RebalanceConsumer
+ * @see ConsumerRebalanceListener
+ */
+public interface RebalanceListener {
+
+    /**
+     * A callback method the user can implement to provide handling of offset 
commits to a customized store.
+     * This method will be called during a rebalance operation when the 
consumer has to give up some partitions.
+     * The consumer may need to give up some partitions (thus this callback 
executed) under the following scenarios:
+     * <ul>
+     *     <li>If the consumer assignment changes</li>
+     *     <li>If the consumer is being closed ({@link 
KafkaConsumer#close(CloseOptions option)})</li>
+     *     <li>If the consumer is unsubscribing ({@link 
KafkaConsumer#unsubscribe()})</li>
+     * </ul>
+     * It is recommended that offsets should be committed in this callback to 
either Kafka or a
+     * custom offset store to prevent duplicate data.
+     * <p>
+     * This callback is always called before re-assigning the partitions.
+     * If the consumer is using the {@link GroupProtocol#CLASSIC} rebalance 
protocol:
+     * <ul>
+     *     <li>
+     *         In eager rebalancing, onPartitionsRevoked will be called with 
the full set of assigned partitions as a parameter (all partitions are revoked).
+     *         It will be called even if there are no partitions to revoke.
+     *     </li>
+     *     <li>
+     *         In cooperative rebalancing, onPartitionsRevoked will be called 
with the set of partitions to revoke,
+     *         iff the set is non-empty.

Review Comment:
   I'd prefer to see `if and only if` in the javadoc, rather than `iff` which 
is not really a proper word. One other instance too.



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

To unsubscribe, e-mail: [email protected]

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

Reply via email to