Skip to content

Commit 4e93400

Browse files
Fix: remove failed queue attributes from cache (#1491) (#1510)
* fix: remove failed queue attributes from cache * add SqsTemplateTests test cases * update shouldCacheSuccessfulQueueAttributesWithAttributeNames test case comment * update shouldRemoveFailedQueueAttributesFromCache test case comment --------- (cherry picked from commit fe9f347) Signed-off-by: LeeHyungGeol <[email protected]> Co-authored-by: 이형걸 <[email protected]>
1 parent acbbf90 commit 4e93400

File tree

3 files changed

+156
-4
lines changed

3 files changed

+156
-4
lines changed

spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/SqsTemplate.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2023 the original author or authors.
2+
* Copyright 2013-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -73,6 +73,7 @@
7373
*
7474
* @author Tomaz Fernandes
7575
* @author Zhong Xi Lu
76+
* @author Hyunggeol Lee
7677
*
7778
* @since 3.0
7879
*/
@@ -470,8 +471,18 @@ private boolean isSkipAttribute(MessageSystemAttributeName name) {
470471
}
471472

472473
private CompletableFuture<QueueAttributes> getQueueAttributes(String endpointName) {
473-
return this.queueAttributesCache.computeIfAbsent(endpointName,
474-
newName -> doGetQueueAttributes(endpointName, newName));
474+
CompletableFuture<QueueAttributes> future = this.queueAttributesCache.computeIfAbsent(endpointName,
475+
newName -> doGetQueueAttributes(endpointName, newName));
476+
477+
// Remove failed futures from cache
478+
future.whenComplete((result, throwable) -> {
479+
if (throwable != null) {
480+
this.queueAttributesCache.remove(endpointName);
481+
logger.debug("Removed failed queue attributes from cache for: {}", endpointName);
482+
}
483+
});
484+
485+
return future;
475486
}
476487

477488
private CompletableFuture<QueueAttributes> doGetQueueAttributes(String endpointName, String newName) {

spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateObservationTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.awspring.cloud.sqs.operations;
1717

18+
import static org.assertj.core.api.Assertions.assertThat;
1819
import static org.mockito.ArgumentMatchers.any;
1920
import static org.mockito.BDDMockito.given;
2021
import static org.mockito.Mockito.mock;
@@ -40,6 +41,7 @@
4041
* Tests for {@link SqsTemplate} observation support.
4142
*
4243
* @author Tomaz Fernandes
44+
* @author Hyunggeol Lee
4345
*/
4446
class SqsTemplateObservationTest {
4547

@@ -217,4 +219,42 @@ protected KeyValues getCustomLowCardinalityKeyValues(SqsTemplateObservation.Cont
217219
.hasLowCardinalityKeyValue("messaging.destination.kind", "queue")
218220
.hasHighCardinalityKeyValue("messaging.message.id", messageId.toString());
219221
}
222+
223+
@Test
224+
void shouldCreateSeparateObservationsForRetryAfterCacheFailure() {
225+
// Given - First attempt will fail
226+
String payload = "test-payload";
227+
RuntimeException firstException = new RuntimeException("First attempt - queue not found");
228+
229+
given(mockSqsAsyncClient.getQueueUrl(any(GetQueueUrlRequest.class)))
230+
.willReturn(CompletableFuture.failedFuture(firstException));
231+
232+
// When - First attempt (failure)
233+
try {
234+
sqsTemplate.send(queueName, payload);
235+
}
236+
catch (Exception e) {
237+
// Expected
238+
}
239+
240+
// Then - Verify first observation has error
241+
TestObservationRegistryAssert.then(observationRegistry).hasNumberOfObservationsEqualTo(1)
242+
.hasSingleObservationThat().hasError();
243+
244+
// Given - Second attempt will succeed (cache was cleared)
245+
UUID messageId = UUID.randomUUID();
246+
given(mockSqsAsyncClient.getQueueUrl(any(GetQueueUrlRequest.class))).willReturn(
247+
CompletableFuture.completedFuture(GetQueueUrlResponse.builder().queueUrl(queueName).build()));
248+
249+
given(mockSqsAsyncClient.sendMessage(any(SendMessageRequest.class))).willReturn(CompletableFuture
250+
.completedFuture(SendMessageResponse.builder().messageId(messageId.toString()).build()));
251+
252+
// When - Second attempt (success)
253+
SendResult<String> result = sqsTemplate.send(queueName, payload);
254+
255+
// Then - Two separate observations should be created
256+
TestObservationRegistryAssert.then(observationRegistry).hasNumberOfObservationsEqualTo(2);
257+
258+
assertThat(result).isNotNull();
259+
}
220260
}

spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateTests.java

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2023 the original author or authors.
2+
* Copyright 2013-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@
5050

5151
/**
5252
* @author Tomaz Fernandes
53+
* @author Hyunggeol Lee
5354
*/
5455
@SuppressWarnings("unchecked")
5556
class SqsTemplateTests {
@@ -1218,4 +1219,104 @@ void shouldPropagateTracingAsMessageSystemAttribute() {
12181219
value -> assertThat(value.stringValue()).isEqualTo("abc"));
12191220
}
12201221

1222+
@Test
1223+
void shouldRemoveFailedQueueAttributesFromCache() {
1224+
// Given - First attempt will fail
1225+
String queue = "test-queue";
1226+
String payload = "test-payload";
1227+
1228+
CompletableFuture<GetQueueUrlResponse> failedFuture = new CompletableFuture<>();
1229+
failedFuture.completeExceptionally(new RuntimeException("Queue attributes resolution failed"));
1230+
1231+
given(mockClient.getQueueUrl(any(GetQueueUrlRequest.class))).willReturn(failedFuture);
1232+
1233+
SqsOperations template = SqsTemplate.newTemplate(mockClient);
1234+
1235+
// When - First attempt should fail
1236+
assertThatThrownBy(() -> template.send(queue, payload)).isInstanceOf(MessagingOperationFailedException.class);
1237+
1238+
// Then - Setup successful response for retry
1239+
GetQueueUrlResponse urlResponse = GetQueueUrlResponse.builder().queueUrl(queue).build();
1240+
given(mockClient.getQueueUrl(any(GetQueueUrlRequest.class)))
1241+
.willReturn(CompletableFuture.completedFuture(urlResponse));
1242+
1243+
mockQueueAttributes(mockClient, Map.of());
1244+
1245+
SendMessageResponse sendResponse = SendMessageResponse.builder().messageId(UUID.randomUUID().toString())
1246+
.build();
1247+
given(mockClient.sendMessage(any(SendMessageRequest.class)))
1248+
.willReturn(CompletableFuture.completedFuture(sendResponse));
1249+
1250+
// When - Retry should work (not use cached failure)
1251+
SendResult<String> result = template.send(queue, payload);
1252+
1253+
// Then - Verify getQueueUrl was called twice (failure was not cached)
1254+
then(mockClient).should(times(2)).getQueueUrl(any(GetQueueUrlRequest.class));
1255+
assertThat(result.endpoint()).isEqualTo(queue);
1256+
assertThat(result.message().getPayload()).isEqualTo(payload);
1257+
}
1258+
1259+
@Test
1260+
void shouldCacheSuccessfulQueueAttributes() {
1261+
// Given - Setup successful responses
1262+
String queue = "test-queue";
1263+
String payload1 = "test-payload-1";
1264+
String payload2 = "test-payload-2";
1265+
1266+
GetQueueUrlResponse urlResponse = GetQueueUrlResponse.builder().queueUrl(queue).build();
1267+
given(mockClient.getQueueUrl(any(GetQueueUrlRequest.class)))
1268+
.willReturn(CompletableFuture.completedFuture(urlResponse));
1269+
1270+
SendMessageResponse sendResponse = SendMessageResponse.builder().messageId(UUID.randomUUID().toString())
1271+
.build();
1272+
given(mockClient.sendMessage(any(SendMessageRequest.class)))
1273+
.willReturn(CompletableFuture.completedFuture(sendResponse));
1274+
1275+
SqsOperations template = SqsTemplate.newTemplate(mockClient);
1276+
1277+
// When - Send twice to same queue
1278+
SendResult<String> result1 = template.send(queue, payload1);
1279+
SendResult<String> result2 = template.send(queue, payload2);
1280+
1281+
// Then - Queue URL should be cached (only called once)
1282+
then(mockClient).should(times(1)).getQueueUrl(any(GetQueueUrlRequest.class));
1283+
then(mockClient).should(times(2)).sendMessage(any(SendMessageRequest.class));
1284+
}
1285+
1286+
@Test
1287+
void shouldCacheSuccessfulQueueAttributesWithAttributeNames() {
1288+
// Given - Template with queueAttributeNames configured
1289+
String queue = "test-queue";
1290+
String payload1 = "test-payload-1";
1291+
String payload2 = "test-payload-2";
1292+
1293+
GetQueueUrlResponse urlResponse = GetQueueUrlResponse.builder().queueUrl(queue).build();
1294+
given(mockClient.getQueueUrl(any(GetQueueUrlRequest.class)))
1295+
.willReturn(CompletableFuture.completedFuture(urlResponse));
1296+
1297+
GetQueueAttributesResponse attributesResponse = GetQueueAttributesResponse.builder()
1298+
.attributes(Map.of(QueueAttributeName.QUEUE_ARN, "test-arn")).build();
1299+
given(mockClient.getQueueAttributes(any(Consumer.class)))
1300+
.willReturn(CompletableFuture.completedFuture(attributesResponse));
1301+
1302+
SendMessageResponse sendResponse = SendMessageResponse.builder().messageId(UUID.randomUUID().toString())
1303+
.build();
1304+
given(mockClient.sendMessage(any(SendMessageRequest.class)))
1305+
.willReturn(CompletableFuture.completedFuture(sendResponse));
1306+
1307+
// Create template with queueAttributeNames configured
1308+
SqsOperations template = SqsTemplate.builder().sqsAsyncClient(mockClient)
1309+
.configure(
1310+
options -> options.queueAttributeNames(Collections.singletonList(QueueAttributeName.QUEUE_ARN)))
1311+
.buildSyncTemplate();
1312+
1313+
// When - Send twice to same queue
1314+
template.send(queue, payload1);
1315+
template.send(queue, payload2);
1316+
1317+
// Then - Queue attributes should be cached (only called once each)
1318+
then(mockClient).should(times(1)).getQueueUrl(any(GetQueueUrlRequest.class));
1319+
then(mockClient).should(times(1)).getQueueAttributes(any(Consumer.class));
1320+
then(mockClient).should(times(2)).sendMessage(any(SendMessageRequest.class));
1321+
}
12211322
}

0 commit comments

Comments
 (0)