4343import io .awspring .cloud .sqs .listener .acknowledgement .BatchAcknowledgement ;
4444import io .awspring .cloud .sqs .listener .acknowledgement .SqsAcknowledgementExecutor ;
4545import io .awspring .cloud .sqs .listener .acknowledgement .handler .AcknowledgementMode ;
46+ import io .awspring .cloud .sqs .listener .errorhandler .AsyncDefaultErrorHandler ;
4647import io .awspring .cloud .sqs .listener .errorhandler .AsyncErrorHandler ;
4748import io .awspring .cloud .sqs .listener .interceptor .AsyncMessageInterceptor ;
4849import io .awspring .cloud .sqs .listener .sink .MessageSink ;
5253import java .lang .reflect .Method ;
5354import java .time .Duration ;
5455import java .util .ArrayList ;
56+ import java .util .Map ;
5557import java .util .Collection ;
5658import java .util .Collections ;
5759import java .util .List ;
5860import java .util .UUID ;
5961import java .util .concurrent .BrokenBarrierException ;
62+ import java .util .concurrent .ConcurrentHashMap ;
6063import java .util .concurrent .CompletableFuture ;
6164import java .util .concurrent .CountDownLatch ;
6265import java .util .concurrent .CyclicBarrier ;
9396 * @author Mikhail Strokov
9497 * @author Michael Sosa
9598 * @author gustavomonarin
99+ * @author Bruno Garcia
100+ * @author Rafael Pavarini
96101 */
97102@ SpringBootTest
98103@ TestPropertySource (properties = { "property.one=1" , "property.five.seconds=5s" ,
@@ -130,6 +135,10 @@ class SqsIntegrationTests extends BaseSqsIntegrationTest {
130135
131136 static final String MAX_CONCURRENT_MESSAGES_QUEUE_NAME = "max_concurrent_messages_test_queue" ;
132137
138+ static final String SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_QUEUE_NAME = "success_visibility_timeout_to_zero_test_queue" ;
139+
140+ static final String SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_BATCH_QUEUE_NAME = "success_visibility_batch_timeout_to_zero_test_queue" ;
141+
133142 static final String LOW_RESOURCE_FACTORY = "lowResourceFactory" ;
134143
135144 static final String MANUAL_ACK_FACTORY = "manualAcknowledgementFactory" ;
@@ -138,6 +147,8 @@ class SqsIntegrationTests extends BaseSqsIntegrationTest {
138147
139148 static final String ACK_AFTER_SECOND_ERROR_FACTORY = "ackAfterSecondErrorFactory" ;
140149
150+ static final String SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_FACTORY = "receivesMessageErrorFactory" ;
151+
141152 @ BeforeAll
142153 static void beforeTests () {
143154 SqsAsyncClient client = createAsyncClient ();
@@ -158,7 +169,12 @@ static void beforeTests() {
158169 createQueue (client , MANUALLY_CREATE_INACTIVE_CONTAINER_QUEUE_NAME ),
159170 createQueue (client , MANUALLY_CREATE_FACTORY_QUEUE_NAME ),
160171 createQueue (client , CONSUMES_ONE_MESSAGE_AT_A_TIME_QUEUE_NAME ),
161- createQueue (client , MAX_CONCURRENT_MESSAGES_QUEUE_NAME )).join ();
172+ createQueue (client , MAX_CONCURRENT_MESSAGES_QUEUE_NAME ),
173+ createQueue (client , SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_QUEUE_NAME ,
174+ singletonMap (QueueAttributeName .VISIBILITY_TIMEOUT , "500" )),
175+ createQueue (client , SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_BATCH_QUEUE_NAME ,
176+ singletonMap (QueueAttributeName .VISIBILITY_TIMEOUT , "500" )))
177+ .join ();
162178 }
163179
164180 @ Autowired
@@ -184,6 +200,27 @@ void receivesMessage() throws Exception {
184200 assertThat (latchContainer .acknowledgementCallbackSuccessLatch .await (10 , TimeUnit .SECONDS )).isTrue ();
185201 }
186202
203+ @ Test
204+ void receivesMessageVisibilityTimeout () throws Exception {
205+ String messageBody = UUID .randomUUID ().toString ();
206+ sqsTemplate .send (SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_QUEUE_NAME , messageBody );
207+ logger .debug ("Sent message to queue {} with messageBody {}" , SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_QUEUE_NAME ,
208+ messageBody );
209+
210+ assertThat (latchContainer .receivesRetryMessageQuicklyLatch .await (10 , TimeUnit .SECONDS )).isTrue ();
211+ }
212+
213+ @ Test
214+ void receivesMessageVisibilityTimeoutBatch () throws Exception {
215+ List <Message <String >> messages = create10Messages ("receivesMessageVisibilityTimeoutBatch" );
216+
217+ sqsTemplate .sendManyAsync (SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_BATCH_QUEUE_NAME , messages );
218+ logger .debug ("Sent message to queue {} with messageBody {}" ,
219+ SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_BATCH_QUEUE_NAME , messages );
220+
221+ assertThat (latchContainer .receivesRetryBatchMessageQuicklyLatch .await (10 , TimeUnit .SECONDS )).isTrue ();
222+ }
223+
187224 @ Test
188225 void receivesMessageBatch () throws Exception {
189226 List <Message <String >> messages = create10Messages ("receivesMessageBatch" );
@@ -425,6 +462,68 @@ CompletableFuture<Void> listen(String message, @Header(SqsHeaders.SQS_QUEUE_NAME
425462 }
426463 }
427464
465+ static class ErrorHandlerVisibilityTest {
466+
467+ @ Autowired
468+ LatchContainer latchContainer ;
469+
470+ private static final Map <String , Long > previousReceivedMessageTimestamps = new ConcurrentHashMap <>();
471+
472+ private static final int MAX_EXPECTED_ELAPSED_TIME_BETWEEN_MSG_RECEIVES_IN_MS = 5000 ;
473+
474+ @ SqsListener (queueNames = SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_QUEUE_NAME , messageVisibilitySeconds = "500" , factory = SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_FACTORY , id = "visibilityErrHandler" )
475+ CompletableFuture <Void > listen (Message <String > message ,
476+ @ Header (SqsHeaders .SQS_QUEUE_NAME_HEADER ) String queueName ) {
477+ logger .info ("Received message {} from queue {}" , message , queueName );
478+ String msgId = MessageHeaderUtils .getHeader (message , "id" , UUID .class ).toString ();
479+ Long prevReceivedMessageTimestamp = previousReceivedMessageTimestamps .get (msgId );
480+ if (prevReceivedMessageTimestamp == null ) {
481+ previousReceivedMessageTimestamps .put (msgId , System .currentTimeMillis ());
482+ return CompletableFuture
483+ .failedFuture (new RuntimeException ("Expected exception from visibility-err-handler" ));
484+ }
485+
486+ long elapsedTimeBetweenMessageReceivesInMs = System .currentTimeMillis () - prevReceivedMessageTimestamp ;
487+ if (elapsedTimeBetweenMessageReceivesInMs < MAX_EXPECTED_ELAPSED_TIME_BETWEEN_MSG_RECEIVES_IN_MS ) {
488+ latchContainer .receivesRetryMessageQuicklyLatch .countDown ();
489+ }
490+
491+ return CompletableFuture .completedFuture (null );
492+ }
493+ }
494+
495+ static class ErrorHandlerVisibilityBatchTest {
496+
497+ @ Autowired
498+ LatchContainer latchContainer ;
499+
500+ private static final Map <String , Long > previousReceivedMessageTimestamps = new ConcurrentHashMap <>();
501+
502+ private static final int MAX_EXPECTED_ELAPSED_TIME_BETWEEN_BATCH_MSG_RECEIVES_IN_MS = 5000 ;
503+
504+ @ SqsListener (queueNames = SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_BATCH_QUEUE_NAME , messageVisibilitySeconds = "500" , factory = SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_FACTORY , id = "visibilityBatchErrHandler" )
505+ CompletableFuture <Void > listen (List <Message <String >> messages ) {
506+ logger .info ("Received messages {} from queue {}" , MessageHeaderUtils .getId (messages ),
507+ messages .get (0 ).getHeaders ().get (SqsHeaders .SQS_QUEUE_NAME_HEADER ));
508+
509+ for (Message <String > message : messages ) {
510+ String msgId = MessageHeaderUtils .getHeader (message , "id" , UUID .class ).toString ();
511+ if (!previousReceivedMessageTimestamps .containsKey (msgId )) {
512+ previousReceivedMessageTimestamps .put (msgId , System .currentTimeMillis ());
513+ return CompletableFuture .failedFuture (new RuntimeException ("Expected exception from visibility-err-handler" ));
514+ }
515+ else {
516+ long timediff = System .currentTimeMillis () - previousReceivedMessageTimestamps .get (msgId );
517+ if (MAX_EXPECTED_ELAPSED_TIME_BETWEEN_BATCH_MSG_RECEIVES_IN_MS > timediff ) {
518+ latchContainer .receivesRetryBatchMessageQuicklyLatch .countDown ();
519+ }
520+ }
521+ }
522+
523+ return CompletableFuture .completedFuture (null );
524+ }
525+ }
526+
428527 static class DoesNotAckOnErrorBatchListener {
429528
430529 @ Autowired
@@ -500,6 +599,8 @@ void listen(String message) throws BrokenBarrierException, InterruptedException
500599 static class LatchContainer {
501600
502601 final CountDownLatch receivesMessageLatch = new CountDownLatch (1 );
602+ final CountDownLatch receivesRetryMessageQuicklyLatch = new CountDownLatch (1 );
603+ final CountDownLatch receivesRetryBatchMessageQuicklyLatch = new CountDownLatch (10 );
503604 final CountDownLatch receivesMessageBatchLatch = new CountDownLatch (20 );
504605 final CountDownLatch receivesMessageAsyncLatch = new CountDownLatch (1 );
505606 final CountDownLatch doesNotAckLatch = new CountDownLatch (2 );
@@ -576,6 +677,21 @@ public SqsMessageListenerContainerFactory<Object> ackAfterSecondErrorFactory() {
576677 .build ();
577678 }
578679
680+ @ Bean (name = SUCCESS_VISIBILITY_TIMEOUT_TO_ZERO_FACTORY )
681+ public SqsMessageListenerContainerFactory <Object > errorHandlerVisibility () {
682+ return SqsMessageListenerContainerFactory
683+ .builder ()
684+ .configure (options -> options
685+ .maxConcurrentMessages (10 )
686+ .pollTimeout (Duration .ofSeconds (10 ))
687+ .maxMessagesPerPoll (10 )
688+ .queueAttributeNames (Collections .singletonList (QueueAttributeName .QUEUE_ARN ))
689+ .maxDelayBetweenPolls (Duration .ofSeconds (10 )))
690+ .errorHandler (new AsyncDefaultErrorHandler <>())
691+ .sqsAsyncClientSupplier (BaseSqsIntegrationTest ::createAsyncClient )
692+ .build ();
693+ }
694+
579695 private List <ContainerComponentFactory <Object , SqsContainerOptions >> getExceptionThrowingAckExecutor () {
580696 return Collections .singletonList (new StandardSqsComponentFactory <Object >() {
581697 @ Override
@@ -738,6 +854,16 @@ DoesNotAckOnErrorAsyncListener doesNotAckOnErrorAsyncListener() {
738854 return new DoesNotAckOnErrorAsyncListener ();
739855 }
740856
857+ @ Bean
858+ ErrorHandlerVisibilityTest errorHandlerVisibilityTest () {
859+ return new ErrorHandlerVisibilityTest ();
860+ }
861+
862+ @ Bean
863+ ErrorHandlerVisibilityBatchTest errorHandlerVisibilityBatchTest () {
864+ return new ErrorHandlerVisibilityBatchTest ();
865+ }
866+
741867 @ Bean
742868 DoesNotAckOnErrorBatchListener doesNotAckOnErrorBatchListener () {
743869 return new DoesNotAckOnErrorBatchListener ();
0 commit comments