15
15
*/
16
16
package io .awspring .cloud .sqs .integration ;
17
17
18
- import static java .util .Collections .singletonMap ;
19
18
import static org .assertj .core .api .Assertions .assertThat ;
20
19
21
20
import io .awspring .cloud .sqs .config .SqsBootstrapConfiguration ;
36
35
import java .util .List ;
37
36
import java .util .Queue ;
38
37
import java .util .Random ;
39
- import java .util .concurrent .CompletableFuture ;
40
38
import java .util .concurrent .ConcurrentLinkedQueue ;
41
39
import java .util .concurrent .CountDownLatch ;
42
40
import java .util .concurrent .Semaphore ;
47
45
import java .util .function .IntUnaryOperator ;
48
46
import java .util .stream .Collectors ;
49
47
import java .util .stream .IntStream ;
50
- import org .junit .jupiter .api .BeforeAll ;
51
48
import org .junit .jupiter .api .Test ;
52
49
import org .junit .jupiter .params .ParameterizedTest ;
53
50
import org .junit .jupiter .params .provider .CsvSource ;
60
57
import org .springframework .context .annotation .Import ;
61
58
import org .springframework .messaging .Message ;
62
59
import org .springframework .messaging .support .MessageBuilder ;
63
- import software .amazon .awssdk .services .sqs .SqsAsyncClient ;
64
- import software .amazon .awssdk .services .sqs .model .QueueAttributeName ;
65
60
66
61
/**
67
62
* Integration tests for SQS containers back pressure management.
@@ -73,65 +68,6 @@ class SqsBackPressureIntegrationTests extends BaseSqsIntegrationTest {
73
68
74
69
private static final Logger logger = LoggerFactory .getLogger (SqsBackPressureIntegrationTests .class );
75
70
76
- static final String RECEIVES_MESSAGE_QUEUE_NAME = "receives_message_test_queue" ;
77
-
78
- static final String RECEIVES_MESSAGE_BATCH_QUEUE_NAME = "receives_message_batch_test_queue" ;
79
-
80
- static final String RECEIVES_MESSAGE_ASYNC_QUEUE_NAME = "receives_message_async_test_queue" ;
81
-
82
- static final String DOES_NOT_ACK_ON_ERROR_QUEUE_NAME = "does_not_ack_test_queue" ;
83
-
84
- static final String DOES_NOT_ACK_ON_ERROR_ASYNC_QUEUE_NAME = "does_not_ack_async_test_queue" ;
85
-
86
- static final String DOES_NOT_ACK_ON_ERROR_BATCH_QUEUE_NAME = "does_not_ack_batch_test_queue" ;
87
-
88
- static final String DOES_NOT_ACK_ON_ERROR_BATCH_ASYNC_QUEUE_NAME = "does_not_ack_batch_async_test_queue" ;
89
-
90
- static final String RESOLVES_PARAMETER_TYPES_QUEUE_NAME = "resolves_parameter_type_test_queue" ;
91
-
92
- static final String MANUALLY_START_CONTAINER = "manually_start_container_test_queue" ;
93
-
94
- static final String MANUALLY_CREATE_CONTAINER_QUEUE_NAME = "manually_create_container_test_queue" ;
95
-
96
- static final String MANUALLY_CREATE_INACTIVE_CONTAINER_QUEUE_NAME = "manually_create_inactive_container_test_queue" ;
97
-
98
- static final String MANUALLY_CREATE_FACTORY_QUEUE_NAME = "manually_create_factory_test_queue" ;
99
-
100
- static final String CONSUMES_ONE_MESSAGE_AT_A_TIME_QUEUE_NAME = "consumes_one_message_test_queue" ;
101
-
102
- static final String MAX_CONCURRENT_MESSAGES_QUEUE_NAME = "max_concurrent_messages_test_queue" ;
103
-
104
- static final String LOW_RESOURCE_FACTORY = "lowResourceFactory" ;
105
-
106
- static final String MANUAL_ACK_FACTORY = "manualAcknowledgementFactory" ;
107
-
108
- static final String MANUAL_ACK_BATCH_FACTORY = "manualAcknowledgementBatchFactory" ;
109
-
110
- static final String ACK_AFTER_SECOND_ERROR_FACTORY = "ackAfterSecondErrorFactory" ;
111
-
112
- @ BeforeAll
113
- static void beforeTests () {
114
- SqsAsyncClient client = createAsyncClient ();
115
- CompletableFuture .allOf (createQueue (client , RECEIVES_MESSAGE_QUEUE_NAME ),
116
- createQueue (client , DOES_NOT_ACK_ON_ERROR_QUEUE_NAME ,
117
- singletonMap (QueueAttributeName .VISIBILITY_TIMEOUT , "1" )),
118
- createQueue (client , DOES_NOT_ACK_ON_ERROR_ASYNC_QUEUE_NAME ,
119
- singletonMap (QueueAttributeName .VISIBILITY_TIMEOUT , "1" )),
120
- createQueue (client , DOES_NOT_ACK_ON_ERROR_BATCH_QUEUE_NAME ,
121
- singletonMap (QueueAttributeName .VISIBILITY_TIMEOUT , "1" )),
122
- createQueue (client , DOES_NOT_ACK_ON_ERROR_BATCH_ASYNC_QUEUE_NAME ,
123
- singletonMap (QueueAttributeName .VISIBILITY_TIMEOUT , "1" )),
124
- createQueue (client , RECEIVES_MESSAGE_ASYNC_QUEUE_NAME ),
125
- createQueue (client , RECEIVES_MESSAGE_BATCH_QUEUE_NAME ),
126
- createQueue (client , RESOLVES_PARAMETER_TYPES_QUEUE_NAME ,
127
- singletonMap (QueueAttributeName .VISIBILITY_TIMEOUT , "20" )),
128
- createQueue (client , MANUALLY_CREATE_CONTAINER_QUEUE_NAME ),
129
- createQueue (client , MANUALLY_CREATE_INACTIVE_CONTAINER_QUEUE_NAME ),
130
- createQueue (client , MANUALLY_CREATE_FACTORY_QUEUE_NAME ),
131
- createQueue (client , CONSUMES_ONE_MESSAGE_AT_A_TIME_QUEUE_NAME ),
132
- createQueue (client , MAX_CONCURRENT_MESSAGES_QUEUE_NAME )).join ();
133
- }
134
-
135
71
@ Autowired
136
72
SqsTemplate sqsTemplate ;
137
73
@@ -202,11 +138,12 @@ void staticBackPressureLimitShouldCapQueueProcessingCapacity(int staticLimit, in
202
138
.queueNames (
203
139
queueName )
204
140
.configure (options -> options .pollTimeout (Duration .ofSeconds (1 ))
205
- .backPressureHandlerSupplier (() -> new CompositeBackPressureHandler (List .of (limiter ,
206
- SemaphoreBackPressureHandler .builder ().batchSize (5 ).totalPermits (5 )
207
- .acquireTimeout (Duration .ofSeconds (1L ))
208
- .throughputConfiguration (BackPressureMode .AUTO ).build ()),
209
- 5 )))
141
+ .backPressureHandlerSupplier (() -> new CompositeBackPressureHandler (
142
+ List .of (limiter ,
143
+ SemaphoreBackPressureHandler .builder ().batchSize (5 ).totalPermits (5 )
144
+ .acquireTimeout (Duration .ofSeconds (1L ))
145
+ .throughputConfiguration (BackPressureMode .AUTO ).build ()),
146
+ 5 , Duration .ofMillis (50L ))))
210
147
.messageListener (msg -> {
211
148
int concurrentRqs = concurrentRequest .incrementAndGet ();
212
149
maxConcurrentRequest .updateAndGet (max -> Math .max (max , concurrentRqs ));
@@ -241,11 +178,12 @@ void zeroBackPressureLimitShouldStopQueueProcessing() throws Exception {
241
178
.queueNames (
242
179
queueName )
243
180
.configure (options -> options .pollTimeout (Duration .ofSeconds (1 ))
244
- .backPressureHandlerSupplier (() -> new CompositeBackPressureHandler (List .of (limiter ,
245
- SemaphoreBackPressureHandler .builder ().batchSize (5 ).totalPermits (5 )
246
- .acquireTimeout (Duration .ofSeconds (1L ))
247
- .throughputConfiguration (BackPressureMode .AUTO ).build ()),
248
- 5 )))
181
+ .backPressureHandlerSupplier (() -> new CompositeBackPressureHandler (
182
+ List .of (limiter ,
183
+ SemaphoreBackPressureHandler .builder ().batchSize (5 ).totalPermits (5 )
184
+ .acquireTimeout (Duration .ofSeconds (1L ))
185
+ .throughputConfiguration (BackPressureMode .AUTO ).build ()),
186
+ 5 , Duration .ofMillis (50L ))))
249
187
.messageListener (msg -> {
250
188
int concurrentRqs = concurrentRequest .incrementAndGet ();
251
189
maxConcurrentRequest .updateAndGet (max -> Math .max (max , concurrentRqs ));
@@ -278,23 +216,33 @@ void changeInBackPressureLimitShouldAdaptQueueProcessingCapacity() throws Except
278
216
var latch = new CountDownLatch (nbMessages );
279
217
var controlSemaphore = new Semaphore (0 );
280
218
var advanceSemaphore = new Semaphore (0 );
219
+ var processingFailed = new AtomicBoolean (false );
220
+ var isDraining = new AtomicBoolean (false );
281
221
var container = SqsMessageListenerContainer
282
222
.builder ().sqsAsyncClient (
283
223
BaseSqsIntegrationTest .createAsyncClient ())
284
224
.queueNames (
285
225
queueName )
286
226
.configure (options -> options .pollTimeout (Duration .ofSeconds (1 ))
287
- .backPressureHandlerSupplier (() -> new CompositeBackPressureHandler (List .of (limiter ,
288
- SemaphoreBackPressureHandler .builder ().batchSize (5 ).totalPermits (5 )
289
- .acquireTimeout (Duration .ofSeconds (1L ))
290
- .throughputConfiguration (BackPressureMode .AUTO ).build ()),
291
- 5 )))
227
+ .backPressureHandlerSupplier (() -> new CompositeBackPressureHandler (
228
+ List .of (limiter ,
229
+ SemaphoreBackPressureHandler .builder ().batchSize (5 ).totalPermits (5 )
230
+ .acquireTimeout (Duration .ofSeconds (1L ))
231
+ .throughputConfiguration (BackPressureMode .AUTO ).build ()),
232
+ 5 , Duration .ofMillis (50L ))))
292
233
.messageListener (msg -> {
293
234
try {
294
- controlSemaphore .acquire ();
235
+ if (!controlSemaphore .tryAcquire (5 , TimeUnit .SECONDS ) && !isDraining .get ()) {
236
+ processingFailed .set (true );
237
+ throw new IllegalStateException ("Failed to wait for control semaphore" );
238
+ }
295
239
}
296
240
catch (InterruptedException e ) {
297
- throw new RuntimeException (e );
241
+ if (!isDraining .get ()) {
242
+ processingFailed .set (true );
243
+ Thread .currentThread ().interrupt ();
244
+ throw new RuntimeException (e );
245
+ }
298
246
}
299
247
int concurrentRqs = concurrentRequest .incrementAndGet ();
300
248
maxConcurrentRequest .updateAndGet (max -> Math .max (max , concurrentRqs ));
@@ -310,14 +258,16 @@ class Controller {
310
258
private final Semaphore controlSemaphore ;
311
259
private final NonBlockingExternalConcurrencyLimiterBackPressureHandler limiter ;
312
260
private final AtomicInteger maxConcurrentRequest ;
261
+ private final AtomicBoolean processingFailed ;
313
262
314
263
Controller (Semaphore advanceSemaphore , Semaphore controlSemaphore ,
315
264
NonBlockingExternalConcurrencyLimiterBackPressureHandler limiter ,
316
- AtomicInteger maxConcurrentRequest ) {
265
+ AtomicInteger maxConcurrentRequest , AtomicBoolean processingFailed ) {
317
266
this .advanceSemaphore = advanceSemaphore ;
318
267
this .controlSemaphore = controlSemaphore ;
319
268
this .limiter = limiter ;
320
269
this .maxConcurrentRequest = maxConcurrentRequest ;
270
+ this .processingFailed = processingFailed ;
321
271
}
322
272
323
273
public void updateLimit (int newLimit ) {
@@ -341,9 +291,11 @@ void waitForAdvance(int permits) throws InterruptedException {
341
291
.withFailMessage (() -> "Waiting for %d permits timed out. Only %d permits available"
342
292
.formatted (permits , advanceSemaphore .availablePermits ()))
343
293
.isTrue ();
294
+ assertThat (processingFailed .get ()).isFalse ();
344
295
}
345
296
}
346
- var controller = new Controller (advanceSemaphore , controlSemaphore , limiter , maxConcurrentRequest );
297
+ var controller = new Controller (advanceSemaphore , controlSemaphore , limiter , maxConcurrentRequest ,
298
+ processingFailed );
347
299
try {
348
300
container .start ();
349
301
@@ -386,8 +338,10 @@ void waitForAdvance(int permits) throws InterruptedException {
386
338
controller .waitForAdvance (50 );
387
339
assertThat (latch .await (10 , TimeUnit .SECONDS )).isTrue ();
388
340
assertThat (controller .maxConcurrentRequest .get ()).isEqualTo (5 );
341
+ assertThat (processingFailed .get ()).isFalse ();
389
342
}
390
343
finally {
344
+ isDraining .set (true );
391
345
container .stop ();
392
346
}
393
347
}
@@ -500,13 +454,12 @@ void unsynchronizedChangesInBackPressureLimitShouldAdaptQueueProcessingCapacity(
500
454
options -> options .pollTimeout (Duration .ofSeconds (1 ))
501
455
.standbyLimitPollingInterval (
502
456
Duration .ofMillis (1 ))
503
- .backPressureHandlerSupplier (() -> new StatisticsBphDecorator (
504
- new CompositeBackPressureHandler (List . of ( limiter ,
505
- SemaphoreBackPressureHandler .builder ().batchSize ( 10 ). totalPermits (10 )
506
- .acquireTimeout (Duration .ofSeconds (1L ))
457
+ .backPressureHandlerSupplier (
458
+ () -> new StatisticsBphDecorator ( new CompositeBackPressureHandler (
459
+ List . of ( limiter , SemaphoreBackPressureHandler .builder ().batchSize (10 )
460
+ .totalPermits ( 10 ). acquireTimeout (Duration .ofSeconds (1L ))
507
461
.throughputConfiguration (BackPressureMode .AUTO ).build ()),
508
- 10 ),
509
- eventsCsvWriter )))
462
+ 10 , Duration .ofMillis (50L )), eventsCsvWriter )))
510
463
.messageListener (msg -> {
511
464
int currentConcurrentRq = concurrentRequest .incrementAndGet ();
512
465
maxConcurrentRequest .updateAndGet (max -> Math .max (max , currentConcurrentRq ));
0 commit comments