Skip to content

Commit b9b2ad3

Browse files
committed
Fixed an issue where async waiters failed to match wrapped exception
1 parent 3905559 commit b9b2ad3

File tree

6 files changed

+62
-8
lines changed

6 files changed

+62
-8
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Fixed an issue where an async waiter failed to match exception when the exception was wrapped with CompletionException. See [#2460](https://github.com/aws/aws-sdk-java-v2/issues/2460)"
6+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/waiters/AsyncWaiterExecutor.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.List;
1919
import java.util.Optional;
2020
import java.util.concurrent.CompletableFuture;
21+
import java.util.concurrent.CompletionException;
2122
import java.util.concurrent.ScheduledExecutorService;
2223
import java.util.concurrent.TimeUnit;
2324
import java.util.function.Supplier;
@@ -71,7 +72,17 @@ private void runAsyncPollingFunction(Supplier<CompletableFuture<T>> asyncPolling
7172
long startTime) {
7273
asyncPollingFunction.get().whenComplete((response, exception) -> {
7374
try {
74-
Either<T, Throwable> responseOrException = exception == null ? Either.left(response) : Either.right(exception);
75+
Either<T, Throwable> responseOrException;
76+
77+
if (exception == null) {
78+
responseOrException = Either.left(response);
79+
} else {
80+
if (exception instanceof CompletionException) {
81+
responseOrException = Either.right(exception.getCause());
82+
} else {
83+
responseOrException = Either.right(exception);
84+
}
85+
}
7586

7687
Optional<WaiterAcceptor<? super T>> optionalWaiterAcceptor =
7788
executorHelper.firstWaiterAcceptorIfMatched(responseOrException);

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/waiters/WaiterExecutorHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public Either<Long, SdkClientException> nextDelayOrUnretryableException(int atte
9090

9191
public SdkClientException noneMatchException(Either<T, Throwable> responseOrException) {
9292
return responseOrException.map(
93-
r -> SdkClientException.create("No acceptor was matched for the response"),
93+
r -> SdkClientException.create("No acceptor was matched for the response: " + r),
9494
t -> SdkClientException.create("An exception was thrown and did not match any "
9595
+ "waiter acceptors", t));
9696
}

core/sdk-core/src/test/java/software/amazon/awssdk/core/waiters/AsyncWaiterTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2121

2222
import java.util.concurrent.CompletableFuture;
23+
import java.util.concurrent.CompletionException;
2324
import java.util.function.BiFunction;
2425
import java.util.function.Supplier;
2526
import org.junit.Test;
@@ -48,6 +49,17 @@ public BiFunction<Integer, TestWaiterConfiguration, WaiterResponse<String>> succ
4849
.runAsync(new ThrowExceptionResource(count)).join();
4950
}
5051

52+
@Test
53+
public void matchException_exceptionIsWrapped_shouldReturnUnwrappedException() {
54+
TestWaiterConfiguration waiterConfig = new TestWaiterConfiguration()
55+
.overrideConfiguration(p -> p.maxAttempts(3).backoffStrategy(BackoffStrategy.none()))
56+
.addAcceptor(WaiterAcceptor.successOnExceptionAcceptor(s -> s.getMessage().contains(SUCCESS_STATE_MESSAGE)));
57+
58+
WaiterResponse<String> response = successOnWrappedExceptionWaiterOperation().apply(1, waiterConfig);
59+
assertThat(response.matched().exception().get()).isExactlyInstanceOf(RuntimeException.class);
60+
assertThat(response.attemptsExecuted()).isEqualTo(1);
61+
}
62+
5163
@Test
5264
public void missingScheduledExecutor_shouldThrowException() {
5365
assertThatThrownBy(() -> AsyncWaiter.builder(String.class)
@@ -91,6 +103,15 @@ public void requestOverrideConfig_shouldTakePrecedence() {
91103
.join()).hasMessageContaining("exceeded the max retry attempts: 1");
92104
}
93105

106+
private BiFunction<Integer, TestWaiterConfiguration, WaiterResponse<String>> successOnWrappedExceptionWaiterOperation() {
107+
return (count, waiterConfiguration) -> AsyncWaiter.builder(String.class)
108+
.overrideConfiguration(waiterConfiguration.getPollingStrategy())
109+
.acceptors(waiterConfiguration.getWaiterAcceptors())
110+
.scheduledExecutorService(executorService)
111+
.build()
112+
.runAsync(new ThrowCompletionExceptionResource(count)).join();
113+
}
114+
94115
private static final class ReturnResponseResource implements Supplier<CompletableFuture<String>> {
95116
private final int successAttemptIndex;
96117
private int count;
@@ -125,6 +146,23 @@ public CompletableFuture<String> get() {
125146

126147
return CompletableFutureUtils.failedFuture(new RuntimeException(SUCCESS_STATE_MESSAGE));
127148
}
149+
}
128150

151+
private static final class ThrowCompletionExceptionResource implements Supplier<CompletableFuture<String>> {
152+
private final int successAttemptIndex;
153+
private int count;
154+
155+
public ThrowCompletionExceptionResource(int successAttemptIndex) {
156+
this.successAttemptIndex = successAttemptIndex;
157+
}
158+
159+
@Override
160+
public CompletableFuture<String> get() {
161+
if (++count < successAttemptIndex) {
162+
return CompletableFutureUtils.failedFuture(new CompletionException(new RuntimeException(NON_SUCCESS_STATE_MESSAGE)));
163+
}
164+
165+
return CompletableFutureUtils.failedFuture(new CompletionException(new RuntimeException(SUCCESS_STATE_MESSAGE)));
166+
}
129167
}
130168
}

services/dynamodb/src/it/java/software/amazon/awssdk/services/dynamodb/WaitersIntegrationTest.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@
1616
package software.amazon.awssdk.services.dynamodb;
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
19-
import static org.junit.Assert.assertTrue;
2019

2120
import java.util.concurrent.CompletableFuture;
22-
import java.util.concurrent.ExecutionException;
2321
import org.assertj.core.api.Condition;
2422
import org.junit.AfterClass;
2523
import org.junit.BeforeClass;
@@ -30,7 +28,6 @@
3028
import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
3129
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
3230
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
33-
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
3431
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
3532
import software.amazon.awssdk.services.dynamodb.model.KeyType;
3633
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
@@ -70,10 +67,10 @@ public static void setUp() {
7067

7168
@AfterClass
7269
public static void cleanUp() {
73-
dynamo.deleteTable(DeleteTableRequest.builder().tableName(TABLE_NAME).build());
70+
dynamoAsync.deleteTable(DeleteTableRequest.builder().tableName(TABLE_NAME).build());
7471

7572
WaiterResponse<DescribeTableResponse> waiterResponse =
76-
dynamo.waiter().waitUntilTableNotExists(b -> b.tableName(TABLE_NAME));
73+
dynamoAsync.waiter().waitUntilTableNotExists(b -> b.tableName(TABLE_NAME)).join();
7774

7875
assertThat(waiterResponse.matched().response()).isEmpty();
7976
assertThat(waiterResponse.matched().exception()).hasValueSatisfying(

services/s3/src/it/java/software/amazon/awssdk/services/s3/GetObjectAsyncIntegrationTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ public static void setupFixture() throws IOException {
6363
s3Async.putObject(PutObjectRequest.builder()
6464
.bucket(BUCKET)
6565
.key(KEY)
66-
.build(), file.toPath()).join();
66+
.build(), file.toPath());
67+
68+
s3Async.waiter().waitUntilObjectExists(b -> b.bucket(BUCKET).key(KEY)).join();
6769
}
6870

6971
@AfterClass

0 commit comments

Comments
 (0)