|
16 | 16 | import org.apache.kafka.clients.consumer.KafkaConsumer; |
17 | 17 | import org.apache.kafka.clients.consumer.OffsetAndMetadata; |
18 | 18 | import org.apache.kafka.common.TopicPartition; |
| 19 | +import org.apache.kafka.common.errors.RebalanceInProgressException; |
19 | 20 | import org.apache.kafka.common.errors.RecordDeserializationException; |
20 | 21 | import org.junit.jupiter.api.Assertions; |
21 | 22 | import org.junit.jupiter.api.BeforeEach; |
|
60 | 61 | import static org.junit.jupiter.api.Assertions.assertTrue; |
61 | 62 | import static org.mockito.ArgumentMatchers.any; |
62 | 63 | import static org.mockito.ArgumentMatchers.anyInt; |
| 64 | +import static org.mockito.ArgumentMatchers.anyMap; |
| 65 | +import static org.mockito.Mockito.doThrow; |
63 | 66 | import static org.mockito.Mockito.doAnswer; |
64 | 67 | import static org.mockito.Mockito.mock; |
65 | 68 | import static org.mockito.Mockito.when; |
@@ -589,6 +592,88 @@ public void testAwsGlueErrorWithAcknowledgements() throws Exception { |
589 | 592 | }); |
590 | 593 | } |
591 | 594 |
|
| 595 | + @Test |
| 596 | + public void testCommitOffsets_RebalanceInProgressException_DoesNotClearOffsets() throws Exception { |
| 597 | + String topic = topicConfig.getName(); |
| 598 | + TopicPartition topicPartition = new TopicPartition(topic, testPartition); |
| 599 | + |
| 600 | + when(topicConfig.getSerdeFormat()).thenReturn(MessageFormat.PLAINTEXT); |
| 601 | + when(topicConfig.getAutoCommit()).thenReturn(false); |
| 602 | + when(topicConfig.getCommitInterval()).thenReturn(Duration.ofMillis(0)); |
| 603 | + |
| 604 | + consumer = createObjectUnderTest("plaintext", false); |
| 605 | + consumer.onPartitionsAssigned(List.of(topicPartition)); |
| 606 | + |
| 607 | + consumerRecords = createPlainTextRecords(topic, 100L); |
| 608 | + when(kafkaConsumer.poll(any(Duration.class))).thenReturn(consumerRecords); |
| 609 | + |
| 610 | + doThrow(new RebalanceInProgressException("Rebalance in progress")) |
| 611 | + .when(kafkaConsumer).commitSync(anyMap()); |
| 612 | + |
| 613 | + consumer.consumeRecords(); |
| 614 | + |
| 615 | + Map<TopicPartition, OffsetAndMetadata> offsetsBeforeCommit = new HashMap<>(consumer.getOffsetsToCommit()); |
| 616 | + Assertions.assertFalse(offsetsBeforeCommit.isEmpty(), "Offsets should be populated after consuming records"); |
| 617 | + Assertions.assertEquals(102L, offsetsBeforeCommit.get(topicPartition).offset()); |
| 618 | + |
| 619 | + Thread testThread = new Thread(() -> { |
| 620 | + try { |
| 621 | + java.lang.reflect.Method method = consumer.getClass().getDeclaredMethod("commitOffsets", boolean.class); |
| 622 | + method.setAccessible(true); |
| 623 | + method.invoke(consumer, true); |
| 624 | + } catch (Exception e) { |
| 625 | + throw new RuntimeException(e); |
| 626 | + } |
| 627 | + }); |
| 628 | + testThread.start(); |
| 629 | + testThread.join(5000); |
| 630 | + |
| 631 | + Map<TopicPartition, OffsetAndMetadata> offsetsAfterFailedCommit = consumer.getOffsetsToCommit(); |
| 632 | + Assertions.assertFalse(offsetsAfterFailedCommit.isEmpty(), |
| 633 | + "Offsets should NOT be cleared after RebalanceInProgressException"); |
| 634 | + Assertions.assertEquals(offsetsBeforeCommit.get(topicPartition).offset(), |
| 635 | + offsetsAfterFailedCommit.get(topicPartition).offset(), |
| 636 | + "Offset value should remain unchanged for retry after rebalance completes"); |
| 637 | + } |
| 638 | + |
| 639 | + @Test |
| 640 | + public void testCommitOffsets_OtherException_ClearsOffsets() throws Exception { |
| 641 | + String topic = topicConfig.getName(); |
| 642 | + TopicPartition topicPartition = new TopicPartition(topic, testPartition); |
| 643 | + |
| 644 | + when(topicConfig.getAutoCommit()).thenReturn(false); |
| 645 | + when(topicConfig.getCommitInterval()).thenReturn(Duration.ofMillis(0)); |
| 646 | + |
| 647 | + consumer = createObjectUnderTest("plaintext", false); |
| 648 | + consumer.onPartitionsAssigned(List.of(topicPartition)); |
| 649 | + |
| 650 | + consumerRecords = createPlainTextRecords(topic, 100L); |
| 651 | + when(kafkaConsumer.poll(any(Duration.class))).thenReturn(consumerRecords); |
| 652 | + |
| 653 | + doThrow(new RuntimeException("Generic commit failure")) |
| 654 | + .when(kafkaConsumer).commitSync(anyMap()); |
| 655 | + |
| 656 | + consumer.consumeRecords(); |
| 657 | + |
| 658 | + Assertions.assertFalse(consumer.getOffsetsToCommit().isEmpty(), |
| 659 | + "Offsets should be populated after consuming records"); |
| 660 | + |
| 661 | + Thread testThread = new Thread(() -> { |
| 662 | + try { |
| 663 | + java.lang.reflect.Method method = consumer.getClass().getDeclaredMethod("commitOffsets", boolean.class); |
| 664 | + method.setAccessible(true); |
| 665 | + method.invoke(consumer, true); |
| 666 | + } catch (Exception e) { |
| 667 | + } |
| 668 | + }); |
| 669 | + testThread.start(); |
| 670 | + testThread.join(5000); |
| 671 | + |
| 672 | + Map<TopicPartition, OffsetAndMetadata> offsetsAfterFailedCommit = consumer.getOffsetsToCommit(); |
| 673 | + Assertions.assertTrue(offsetsAfterFailedCommit.isEmpty(), |
| 674 | + "Offsets should be cleared after non-rebalance exception"); |
| 675 | + } |
| 676 | + |
592 | 677 | private ConsumerRecords createPlainTextRecords(String topic, final long startOffset) { |
593 | 678 | Map<TopicPartition, List<ConsumerRecord>> records = new HashMap<>(); |
594 | 679 | ConsumerRecord<String, String> record1 = new ConsumerRecord<>(topic, testPartition, startOffset, testKey1, testValue1); |
|
0 commit comments