fix(kafka): Call poll() during consumer pause to maintain group membership - #7072
fix(kafka): Call poll() during consumer pause to maintain group membership#7072graytaylor0 wants to merge 1 commit into
Conversation
…rship When the circuit breaker or other external condition pauses the Kafka consumer via pauseConsumePredicate, the consume loop previously called Thread.sleep() and then continue, completely skipping the poll() call. Since the Kafka client tracks time between poll() calls via max.poll.interval.ms, exceeding this interval causes the consumer to voluntarily leave the group and trigger a rebalance. This fix adds a doPoll() call while the consumer is paused. Since consumer.pause() is already called before it, poll() returns zero records while still counting as a valid poll for the interval timer, preventing unnecessary rebalances during backpressure. Signed-off-by: Taylor Gray <tylgry@amazon.com>
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
| LOG.debug("Pause and skip consuming from Kafka topic due to an external condition: {}", pauseConsumePredicate); | ||
| paused = true; | ||
| consumer.pause(consumer.assignment()); | ||
| doPoll(); |
There was a problem hiding this comment.
doPoll() result is intentionally discarded. Add a one-line comment since a rebalance mid-poll could return records that are safely re-delivered later.
| // Need to wait longer than the 1s sleep in the pause branch for the second iteration | ||
| Thread consumerThread = new Thread(() -> consumer.run()); | ||
| consumerThread.start(); | ||
| Thread.sleep(2500); |
There was a problem hiding this comment.
Thread.sleep(2500) is slow and timing-dependent. Please use Awaitility.await().untilAsserted(() -> verify(...resume())).
| // Run in a thread and shut down after a short delay | ||
| Thread consumerThread = new Thread(() -> consumer.run()); | ||
| consumerThread.start(); | ||
| Thread.sleep(100); |
There was a problem hiding this comment.
Thread.sleep(100) before the verify. I prefer Awaitility over a fixed sleep.
| .thenReturn(true) | ||
| .thenReturn(false); | ||
| when(kafkaConsumer.poll(any(Duration.class))).thenReturn(ConsumerRecords.empty()); | ||
| when(kafkaConsumer.assignment()).thenReturn(java.util.Collections.singleton(new TopicPartition(topic, testPartition))); |
There was a problem hiding this comment.
inline FQN java.util.Collections.singleton(...) Please import Collections.
|
|
||
| // Verify poll() was called even though consuming was paused | ||
| verify(kafkaConsumer, org.mockito.Mockito.atLeastOnce()).pause(any()); | ||
| verify(kafkaConsumer, org.mockito.Mockito.atLeastOnce()).poll(any(Duration.class)); |
There was a problem hiding this comment.
inline FQN org.mockito.Mockito.atLeastOnce() static-import atLeastOnce.
| consumerThread.join(5000); | ||
|
|
||
| // Verify poll() was called even though consuming was paused | ||
| verify(kafkaConsumer, org.mockito.Mockito.atLeastOnce()).pause(any()); |
There was a problem hiding this comment.
inline FQN org.mockito.Mockito.atLeastOnce() static-import atLeastOnce.
| consumerThread.join(5000); | ||
|
|
||
| // Verify that consumer was paused and then resumed | ||
| verify(kafkaConsumer, org.mockito.Mockito.atLeastOnce()).pause(any()); |
There was a problem hiding this comment.
inline FQN org.mockito.Mockito.atLeastOnce() static-import atLeastOnce.
|
|
||
| // Verify that consumer was paused and then resumed | ||
| verify(kafkaConsumer, org.mockito.Mockito.atLeastOnce()).pause(any()); | ||
| verify(kafkaConsumer, org.mockito.Mockito.atLeastOnce()).resume(any()); |
There was a problem hiding this comment.
inline FQN org.mockito.Mockito.atLeastOnce() static-import atLeastOnce.
srikanthpadakanti
left a comment
There was a problem hiding this comment.
Hey @graytaylor0 Thank you for your contribution. Please address the comments.
When the circuit breaker or other external condition pauses the Kafka consumer via pauseConsumePredicate, the consume loop previously called Thread.sleep() and then continue, completely skipping the poll() call. Since the Kafka client tracks time between poll() calls via max.poll.interval.ms, exceeding this interval causes the consumer to voluntarily leave the group and trigger a rebalance.
This fix adds a doPoll() call while the consumer is paused. Since consumer.pause() is already called before it, poll() returns zero records while still counting as a valid poll for the interval timer, preventing unnecessary rebalances during backpressure.
Issues Resolved
Resolves #[Issue number to be closed when this PR is merged]
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.