19
19
import static java .util .stream .Collectors .toList ;
20
20
import static org .assertj .core .api .Assertions .assertThat ;
21
21
import static org .awaitility .Awaitility .await ;
22
+ import static org .mockito .ArgumentMatchers .any ;
23
+ import static org .mockito .Mockito .doAnswer ;
24
+ import static org .mockito .Mockito .spy ;
22
25
23
26
import com .fasterxml .jackson .databind .ObjectMapper ;
24
27
import io .awspring .cloud .sqs .CompletableFutures ;
81
84
import org .springframework .util .Assert ;
82
85
import org .springframework .util .StopWatch ;
83
86
import software .amazon .awssdk .services .sqs .SqsAsyncClient ;
87
+ import software .amazon .awssdk .services .sqs .model .ChangeMessageVisibilityBatchRequest ;
88
+ import software .amazon .awssdk .services .sqs .model .ChangeMessageVisibilityBatchRequestEntry ;
84
89
import software .amazon .awssdk .services .sqs .model .QueueAttributeName ;
85
90
86
91
/**
@@ -116,7 +121,10 @@ class SqsFifoIntegrationTests extends BaseSqsIntegrationTest {
116
121
117
122
static final String OBSERVES_MESSAGE_FIFO_QUEUE_NAME = "observes_fifo_message_test_queue.fifo" ;
118
123
124
+ static final String FIFO_VISIBILITY_TIMEOUT_EXTENSION_QUEUE_NAME = "fifo_visibility_timeout_extension_test_queue.fifo" ;
125
+
119
126
private static final String ERROR_ON_ACK_FACTORY = "errorOnAckFactory" ;
127
+ private static final String VISIBILITY_TIMEOUT_EXTENSION_FACTORY = "visibilityTimeoutExtensionFactory" ;
120
128
121
129
@ Autowired
122
130
LatchContainer latchContainer ;
@@ -165,6 +173,7 @@ static void beforeTests() {
165
173
createFifoQueue (client , FIFO_MANUALLY_CREATE_FACTORY_QUEUE_NAME ),
166
174
createFifoQueue (client , FIFO_MANUALLY_CREATE_BATCH_CONTAINER_QUEUE_NAME ),
167
175
createFifoQueue (client , OBSERVES_MESSAGE_FIFO_QUEUE_NAME ),
176
+ createFifoQueue (client , FIFO_VISIBILITY_TIMEOUT_EXTENSION_QUEUE_NAME , getVisibilityAttribute ("5" )),
168
177
createFifoQueue (client , FIFO_MANUALLY_CREATE_BATCH_FACTORY_QUEUE_NAME )).join ();
169
178
}
170
179
@@ -460,6 +469,26 @@ public void onMessage(Collection<Message<String>> messages) {
460
469
461
470
}
462
471
472
+ @ Test
473
+ void visibilityTimeoutExtensionWorksForFifoBatch () throws Exception {
474
+ final int messageCount = this .settings .messagesPerMessageGroup ;
475
+ // There will be messageCount - 1 requests to change visibility, before each message except the first one
476
+ latchContainer .visibilityTimeoutExtensionLatch = new CountDownLatch (messageCount - 1 );
477
+
478
+ List <String > values = IntStream .range (0 , messageCount ).mapToObj (String ::valueOf ).toList ();
479
+ String messageGroupId = UUID .randomUUID ().toString ();
480
+ sqsTemplate .sendMany (FIFO_VISIBILITY_TIMEOUT_EXTENSION_QUEUE_NAME ,
481
+ createMessagesFromValues (messageGroupId , values ));
482
+
483
+ assertThat (latchContainer .visibilityTimeoutExtensionLatch .await (settings .latchTimeoutSeconds , TimeUnit .SECONDS ))
484
+ .isTrue ();
485
+ List <Integer > expectedRequestEntryCounts = IntStream .range (1 , messageCount ).map (i -> messageCount - i ).boxed ()
486
+ .toList ();
487
+ assertThat (messagesContainer .visibilityTimeoutExtensionBatchRequests ).as (
488
+ "Number of entries in each ChangeMessageVisibilityBatchRequest should decrease by 1 on each message" )
489
+ .extracting (List ::size ).containsExactlyElementsOf (expectedRequestEntryCounts );
490
+ }
491
+
463
492
@ Test
464
493
void manuallyCreatesContainer () throws Exception {
465
494
List <String > values = IntStream .range (0 , this .settings .messagesPerTest ).mapToObj (String ::valueOf )
@@ -657,6 +686,13 @@ void listen(List<Message<String>> messages) {
657
686
658
687
}
659
688
689
+ static class VisibilityTimeoutExtensionListener {
690
+ @ SqsListener (queueNames = FIFO_VISIBILITY_TIMEOUT_EXTENSION_QUEUE_NAME , messageVisibilitySeconds = "5" , factory = VISIBILITY_TIMEOUT_EXTENSION_FACTORY )
691
+ void listen (String message ) {
692
+ logger .debug ("Processing message: {}" , message );
693
+ }
694
+ }
695
+
660
696
static class LatchContainer {
661
697
662
698
Settings settings ;
@@ -678,6 +714,7 @@ static class LatchContainer {
678
714
CountDownLatch stopsProcessingOnAckErrorHasThrown ;
679
715
CountDownLatch receivesBatchManyGroupsLatch ;
680
716
CountDownLatch receivesFifoBatchGroupingStrategyMultipleGroupsInSameBatchLatch ;
717
+ CountDownLatch visibilityTimeoutExtensionLatch ;
681
718
682
719
LatchContainer (Settings settings ) {
683
720
this .settings = settings ;
@@ -698,6 +735,7 @@ static class LatchContainer {
698
735
this .receivesFifoBatchGroupingStrategyMultipleGroupsInSameBatchLatch = new CountDownLatch (1 );
699
736
this .stopsProcessingOnAckErrorHasThrown = new CountDownLatch (1 );
700
737
this .observesFifoMessageLatch = new CountDownLatch (1 );
738
+ this .visibilityTimeoutExtensionLatch = new CountDownLatch (1 );
701
739
}
702
740
703
741
}
@@ -711,6 +749,7 @@ static class MessagesContainer {
711
749
List <String > manuallyCreatedBatchFactoryMessages = Collections .synchronizedList (new ArrayList <>());
712
750
List <String > stopsProcessingOnAckErrorBeforeThrown = Collections .synchronizedList (new ArrayList <>());
713
751
List <String > stopsProcessingOnAckErrorAfterThrown = Collections .synchronizedList (new ArrayList <>());
752
+ List <List <String >> visibilityTimeoutExtensionBatchRequests = Collections .synchronizedList (new ArrayList <>());
714
753
715
754
}
716
755
@@ -820,6 +859,28 @@ private void handleResult(Message<String> message) {
820
859
return factory ;
821
860
}
822
861
862
+ @ Bean (VISIBILITY_TIMEOUT_EXTENSION_FACTORY )
863
+ SqsMessageListenerContainerFactory <String > visibilityTrackingSqsListenerContainerFactory () {
864
+ SqsAsyncClient spyAsyncClient = spy (createAsyncClient ());
865
+
866
+ doAnswer (invocation -> {
867
+ ChangeMessageVisibilityBatchRequest request = invocation .getArgument (0 );
868
+ messagesContainer .visibilityTimeoutExtensionBatchRequests .add (request .entries ().stream ().map (ChangeMessageVisibilityBatchRequestEntry ::receiptHandle ).toList ());
869
+ latchContainer .visibilityTimeoutExtensionLatch .countDown ();
870
+
871
+ return invocation .callRealMethod ();
872
+ }).when (spyAsyncClient ).changeMessageVisibilityBatch (any (ChangeMessageVisibilityBatchRequest .class ));
873
+
874
+ SqsMessageListenerContainerFactory <String > factory = new SqsMessageListenerContainerFactory <>();
875
+ factory .configure (options -> options
876
+ .maxConcurrentMessages (10 )
877
+ .acknowledgementThreshold (10 )
878
+ .acknowledgementOrdering (AcknowledgementOrdering .ORDERED_BY_GROUP )
879
+ .messageVisibility (Duration .ofSeconds (5 )));
880
+ factory .setSqsAsyncClientSupplier (() -> spyAsyncClient );
881
+ return factory ;
882
+ }
883
+
823
884
@ Bean
824
885
public MessageListenerContainer <String > manuallyCreatedContainer () {
825
886
SqsMessageListenerContainer <String > container = new SqsMessageListenerContainer <>(createAsyncClient ());
@@ -931,6 +992,11 @@ ReceivesBatchesFromManyGroupsListener receiveBatchesFromManyGroupsListener() {
931
992
return new ReceivesBatchesFromManyGroupsListener ();
932
993
}
933
994
995
+ @ Bean
996
+ VisibilityTimeoutExtensionListener visibilityTimeoutExtensionListener () {
997
+ return new VisibilityTimeoutExtensionListener ();
998
+ }
999
+
934
1000
@ Bean
935
1001
ObservesFifoMessageListener observesFifoMessageListener () {
936
1002
return new ObservesFifoMessageListener ();
0 commit comments