Skip to content

Commit cc926b3

Browse files
authored
GH-3373: Enhancing handling of seeking failures due to consumer rebalancing
Fixes: #3373 * Exclude unassigned partitions during seeking * Set group initial rebalancing delay for test * Rename method
1 parent 4c1ac20 commit cc926b3

File tree

2 files changed

+32
-17
lines changed

2 files changed

+32
-17
lines changed

spring-kafka/src/main/java/org/springframework/kafka/listener/KafkaMessageListenerContainer.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3005,13 +3005,20 @@ private void timedAcks() {
30053005
}
30063006

30073007
private void processSeeks() {
3008-
processTimestampSeeks();
3008+
Collection<TopicPartition> assigned = getAssignedPartitions();
3009+
processTimestampSeeks(assigned);
30093010
TopicPartitionOffset offset = this.seeks.poll();
30103011
while (offset != null) {
30113012
traceSeek(offset);
30123013
try {
3013-
SeekPosition position = offset.getPosition();
30143014
TopicPartition topicPartition = offset.getTopicPartition();
3015+
if (assigned == null || !assigned.contains(topicPartition)) {
3016+
this.logger.warn("No current assignment for partition " + topicPartition +
3017+
" due to partition reassignment prior to seeking.");
3018+
offset = this.seeks.poll();
3019+
continue;
3020+
}
3021+
SeekPosition position = offset.getPosition();
30153022
Long whereTo = offset.getOffset();
30163023
Function<Long, Long> offsetComputeFunction = offset.getOffsetComputeFunction();
30173024
if (position == null) {
@@ -3056,11 +3063,17 @@ else if (SeekPosition.TIMESTAMP.equals(position)) {
30563063
}
30573064
}
30583065

3059-
private void processTimestampSeeks() {
3066+
private void processTimestampSeeks(@Nullable Collection<TopicPartition> assigned) {
30603067
Iterator<TopicPartitionOffset> seekIterator = this.seeks.iterator();
30613068
Map<TopicPartition, Long> timestampSeeks = null;
30623069
while (seekIterator.hasNext()) {
30633070
TopicPartitionOffset tpo = seekIterator.next();
3071+
if (assigned == null || !assigned.contains(tpo.getTopicPartition())) {
3072+
this.logger.warn("No current assignment for partition " + tpo.getTopicPartition() +
3073+
" due to partition reassignment prior to seeking.");
3074+
seekIterator.remove();
3075+
continue;
3076+
}
30643077
if (SeekPosition.TIMESTAMP.equals(tpo.getPosition())) {
30653078
if (timestampSeeks == null) {
30663079
timestampSeeks = new HashMap<>();

spring-kafka/src/test/java/org/springframework/kafka/listener/AbstractConsumerSeekAwareTests.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@
5858
*/
5959
@DirtiesContext
6060
@SpringJUnitConfig
61-
@EmbeddedKafka(topics = {AbstractConsumerSeekAwareTests.TOPIC}, partitions = 3)
61+
@EmbeddedKafka(topics = {AbstractConsumerSeekAwareTests.TOPIC},
62+
partitions = 9,
63+
brokerProperties = "group.initial.rebalance.delay.ms:4000")
6264
class AbstractConsumerSeekAwareTests {
6365

6466
static final String TOPIC = "Seek";
@@ -74,7 +76,7 @@ class AbstractConsumerSeekAwareTests {
7476

7577
@Test
7678
public void checkCallbacksAndTopicPartitions() {
77-
await().timeout(Duration.ofSeconds(5))
79+
await().timeout(Duration.ofSeconds(15))
7880
.untilAsserted(() -> {
7981
Map<ConsumerSeekCallback, List<TopicPartition>> callbacksAndTopics =
8082
multiGroupListener.getCallbacksAndTopics();
@@ -103,29 +105,29 @@ public void checkCallbacksAndTopicPartitions() {
103105
void seekForAllGroups() throws Exception {
104106
template.send(TOPIC, "test-data");
105107
template.send(TOPIC, "test-data");
106-
assertThat(MultiGroupListener.latch1.await(30, TimeUnit.SECONDS)).isTrue();
107-
assertThat(MultiGroupListener.latch2.await(30, TimeUnit.SECONDS)).isTrue();
108+
assertThat(MultiGroupListener.latch1.await(15, TimeUnit.SECONDS)).isTrue();
109+
assertThat(MultiGroupListener.latch2.await(15, TimeUnit.SECONDS)).isTrue();
108110

109111
MultiGroupListener.latch1 = new CountDownLatch(2);
110112
MultiGroupListener.latch2 = new CountDownLatch(2);
111113

112114
multiGroupListener.seekToBeginning();
113-
assertThat(MultiGroupListener.latch1.await(30, TimeUnit.SECONDS)).isTrue();
114-
assertThat(MultiGroupListener.latch2.await(30, TimeUnit.SECONDS)).isTrue();
115+
assertThat(MultiGroupListener.latch1.await(15, TimeUnit.SECONDS)).isTrue();
116+
assertThat(MultiGroupListener.latch2.await(15, TimeUnit.SECONDS)).isTrue();
115117
}
116118

117119
@Test
118120
void seekForSpecificGroup() throws Exception {
119121
template.send(TOPIC, "test-data");
120122
template.send(TOPIC, "test-data");
121-
assertThat(MultiGroupListener.latch1.await(30, TimeUnit.SECONDS)).isTrue();
122-
assertThat(MultiGroupListener.latch2.await(30, TimeUnit.SECONDS)).isTrue();
123+
assertThat(MultiGroupListener.latch1.await(15, TimeUnit.SECONDS)).isTrue();
124+
assertThat(MultiGroupListener.latch2.await(15, TimeUnit.SECONDS)).isTrue();
123125

124126
MultiGroupListener.latch1 = new CountDownLatch(2);
125127
MultiGroupListener.latch2 = new CountDownLatch(2);
126128

127-
multiGroupListener.seekToBeginningForGroup("group2");
128-
assertThat(MultiGroupListener.latch2.await(30, TimeUnit.SECONDS)).isTrue();
129+
multiGroupListener.seekToBeginningFor("group2");
130+
assertThat(MultiGroupListener.latch2.await(15, TimeUnit.SECONDS)).isTrue();
129131
assertThat(MultiGroupListener.latch1.await(1, TimeUnit.SECONDS)).isFalse();
130132
assertThat(MultiGroupListener.latch1.getCount()).isEqualTo(2);
131133
}
@@ -168,19 +170,19 @@ static class MultiGroupListener extends AbstractConsumerSeekAware {
168170

169171
static CountDownLatch latch2 = new CountDownLatch(2);
170172

171-
@KafkaListener(groupId = "group1", topics = TOPIC/*TODO until we figure out non-relevant partitions on assignment, concurrency = "2"*/)
173+
@KafkaListener(groupId = "group1", topics = TOPIC, concurrency = "2")
172174
void listenForGroup1(String in) {
173175
latch1.countDown();
174176
}
175177

176-
@KafkaListener(groupId = "group2", topics = TOPIC/*TODO until we figure out non-relevant partitions on assignment, concurrency = "2"*/)
178+
@KafkaListener(groupId = "group2", topics = TOPIC, concurrency = "7")
177179
void listenForGroup2(String in) {
178180
latch2.countDown();
179181
}
180182

181-
void seekToBeginningForGroup(String groupIdForSeek) {
183+
void seekToBeginningFor(String groupId) {
182184
getCallbacksAndTopics().forEach((cb, topics) -> {
183-
if (groupIdForSeek.equals(cb.getGroupId())) {
185+
if (groupId.equals(cb.getGroupId())) {
184186
topics.forEach(tp -> cb.seekToBeginning(tp.topic(), tp.partition()));
185187
}
186188
});

0 commit comments

Comments
 (0)