|
38 | 38 | import org.apache.pulsar.client.api.Schema;
|
39 | 39 | import org.junit.jupiter.api.Test;
|
40 | 40 |
|
| 41 | +import org.springframework.core.log.LogAccessor; |
41 | 42 | import org.springframework.pulsar.core.DefaultPulsarConsumerFactory;
|
42 | 43 | import org.springframework.pulsar.core.DefaultPulsarProducerFactory;
|
43 | 44 | import org.springframework.pulsar.core.PulsarOperations;
|
|
51 | 52 | */
|
52 | 53 | public class DefaultPulsarConsumerErrorHandlerTests implements PulsarTestContainerSupport {
|
53 | 54 |
|
| 55 | + private final LogAccessor logger = new LogAccessor(this.getClass()); |
| 56 | + |
54 | 57 | @Test
|
55 | 58 | @SuppressWarnings("unchecked")
|
56 | 59 | void happyPathErrorHandlingForRecordMessageListener() throws Exception {
|
@@ -564,4 +567,78 @@ else if (message.getValue() == 7) {
|
564 | 567 | pulsarClient.close();
|
565 | 568 | }
|
566 | 569 |
|
| 570 | + @Test |
| 571 | + @SuppressWarnings("unchecked") |
| 572 | + void whenBatchRecordListenerOneMessageBatchFailsThenSentToDltProperly() throws Exception { |
| 573 | + var topicName = "default-error-handler-tests-9"; |
| 574 | + var pulsarClient = PulsarClient.builder().serviceUrl(PulsarTestContainerSupport.getPulsarBrokerUrl()).build(); |
| 575 | + var pulsarConsumerFactory = new DefaultPulsarConsumerFactory<Integer>(pulsarClient, |
| 576 | + List.of((consumerBuilder) -> { |
| 577 | + consumerBuilder.topic(topicName); |
| 578 | + consumerBuilder.subscriptionName("%s-sub".formatted(topicName)); |
| 579 | + })); |
| 580 | + // Prepare container for batch consume |
| 581 | + var pulsarContainerProperties = new PulsarContainerProperties(); |
| 582 | + pulsarContainerProperties.setSchema(Schema.INT32); |
| 583 | + pulsarContainerProperties.setAckMode(AckMode.MANUAL); |
| 584 | + pulsarContainerProperties.setBatchListener(true); |
| 585 | + pulsarContainerProperties.setMaxNumMessages(1); |
| 586 | + pulsarContainerProperties.setBatchTimeoutMillis(60_000); |
| 587 | + PulsarBatchAcknowledgingMessageListener<?> pulsarBatchMessageListener = mock(); |
| 588 | + doAnswer(invocation -> { |
| 589 | + List<Message<Integer>> message = invocation.getArgument(1); |
| 590 | + Message<Integer> integerMessage = message.get(0); |
| 591 | + Integer value = integerMessage.getValue(); |
| 592 | + if (value == 0) { |
| 593 | + throw new PulsarBatchListenerFailedException("failed", integerMessage); |
| 594 | + } |
| 595 | + Acknowledgement acknowledgment = invocation.getArgument(2); |
| 596 | + List<MessageId> messageIds = new ArrayList<>(); |
| 597 | + for (Message<Integer> integerMessage1 : message) { |
| 598 | + messageIds.add(integerMessage1.getMessageId()); |
| 599 | + } |
| 600 | + acknowledgment.acknowledge(messageIds); |
| 601 | + return new Object(); |
| 602 | + }).when(pulsarBatchMessageListener).received(any(Consumer.class), any(List.class), any(Acknowledgement.class)); |
| 603 | + pulsarContainerProperties.setMessageListener(pulsarBatchMessageListener); |
| 604 | + var container = new DefaultPulsarMessageListenerContainer<>(pulsarConsumerFactory, pulsarContainerProperties); |
| 605 | + |
| 606 | + // Set error handler to recover after 2 retries |
| 607 | + PulsarTemplate<Integer> mockPulsarTemplate = mock(RETURNS_DEEP_STUBS); |
| 608 | + PulsarOperations.SendMessageBuilder<Integer> sendMessageBuilderMock = mock(); |
| 609 | + when(mockPulsarTemplate.newMessage(any(Integer.class)) |
| 610 | + .withTopic(any(String.class)) |
| 611 | + .withMessageCustomizer(any(TypedMessageBuilderCustomizer.class))).thenReturn(sendMessageBuilderMock); |
| 612 | + container.setPulsarConsumerErrorHandler(new DefaultPulsarConsumerErrorHandler<>( |
| 613 | + new PulsarDeadLetterPublishingRecoverer<>(mockPulsarTemplate), new FixedBackOff(100, 2))); |
| 614 | + try { |
| 615 | + container.start(); |
| 616 | + // Send single message in batch |
| 617 | + var pulsarProducerFactory = new DefaultPulsarProducerFactory<Integer>(pulsarClient, topicName); |
| 618 | + var pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory); |
| 619 | + pulsarTemplate.sendAsync(0); |
| 620 | + // Initial call should fail |
| 621 | + // Next 2 calls should fail (retries 2) |
| 622 | + // No more calls after that - msg should go to DLT |
| 623 | + await().atMost(Duration.ofSeconds(30)) |
| 624 | + .untilAsserted(() -> verify(pulsarBatchMessageListener, times(3)).received(any(Consumer.class), |
| 625 | + any(List.class), any(Acknowledgement.class))); |
| 626 | + await().atMost(Duration.ofSeconds(30)) |
| 627 | + .untilAsserted(() -> verify(sendMessageBuilderMock, times(1)).sendAsync()); |
| 628 | + } |
| 629 | + finally { |
| 630 | + safeStopContainer(container); |
| 631 | + } |
| 632 | + pulsarClient.close(); |
| 633 | + } |
| 634 | + |
| 635 | + private void safeStopContainer(PulsarMessageListenerContainer container) { |
| 636 | + try { |
| 637 | + container.stop(); |
| 638 | + } |
| 639 | + catch (Exception ex) { |
| 640 | + logger.warn(ex, "Failed to stop container %s: %s".formatted(container, ex.getMessage())); |
| 641 | + } |
| 642 | + } |
| 643 | + |
567 | 644 | }
|
0 commit comments