Skip to content

Commit 7d35b68

Browse files
authored
Fixed a race condition in AWS CRT-based S3 client that could cause e… (#5441)
* Fixed a race condition in AWS CRT-based S3 client that could cause error to be thrown. * Fix checkstyle error
1 parent ce6691b commit 7d35b68

File tree

3 files changed

+48
-7
lines changed

3 files changed

+48
-7
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS CRT-based S3 Client",
4+
"contributor": "",
5+
"description": "Fixed a race condition in AWS CRT-based S3 client that could cause `s3metaRequest is not initialized yet` error to be thrown."
6+
}

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapter.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@
1818
import static software.amazon.awssdk.utils.FunctionalUtils.runAndLogError;
1919

2020
import java.nio.ByteBuffer;
21+
import java.time.Duration;
2122
import java.util.concurrent.CompletableFuture;
23+
import java.util.concurrent.ExecutionException;
24+
import java.util.concurrent.TimeUnit;
25+
import java.util.concurrent.TimeoutException;
2226
import software.amazon.awssdk.annotations.SdkInternalApi;
27+
import software.amazon.awssdk.annotations.SdkTestInternalApi;
2328
import software.amazon.awssdk.core.async.listener.PublisherListener;
2429
import software.amazon.awssdk.core.exception.SdkClientException;
2530
import software.amazon.awssdk.crt.CRT;
@@ -38,6 +43,7 @@
3843
@SdkInternalApi
3944
public final class S3CrtResponseHandlerAdapter implements S3MetaRequestResponseHandler {
4045
private static final Logger log = Logger.loggerFor(S3CrtResponseHandlerAdapter.class);
46+
private static final Duration META_REQUEST_TIMEOUT = Duration.ofSeconds(10);
4147
private final CompletableFuture<Void> resultFuture;
4248
private final SdkAsyncHttpResponseHandler responseHandler;
4349

@@ -47,13 +53,23 @@ public final class S3CrtResponseHandlerAdapter implements S3MetaRequestResponseH
4753
private final CompletableFuture<S3MetaRequestWrapper> metaRequestFuture;
4854

4955
private final PublisherListener<S3MetaRequestProgress> progressListener;
56+
private final Duration s3MetaRequestTimeout;
5057

5158
private volatile boolean responseHandlingInitiated;
5259

5360
public S3CrtResponseHandlerAdapter(CompletableFuture<Void> executeFuture,
5461
SdkAsyncHttpResponseHandler responseHandler,
5562
PublisherListener<S3MetaRequestProgress> progressListener,
5663
CompletableFuture<S3MetaRequestWrapper> metaRequestFuture) {
64+
this(executeFuture, responseHandler, progressListener, metaRequestFuture, META_REQUEST_TIMEOUT);
65+
}
66+
67+
@SdkTestInternalApi
68+
public S3CrtResponseHandlerAdapter(CompletableFuture<Void> executeFuture,
69+
SdkAsyncHttpResponseHandler responseHandler,
70+
PublisherListener<S3MetaRequestProgress> progressListener,
71+
CompletableFuture<S3MetaRequestWrapper> metaRequestFuture,
72+
Duration s3MetaRequestTimeout) {
5773
this.resultFuture = executeFuture;
5874
this.metaRequestFuture = metaRequestFuture;
5975

@@ -71,14 +87,20 @@ public S3CrtResponseHandlerAdapter(CompletableFuture<Void> executeFuture,
7187

7288
this.responseHandler = responseHandler;
7389
this.progressListener = progressListener == null ? new NoOpPublisherListener() : progressListener;
90+
this.s3MetaRequestTimeout = s3MetaRequestTimeout;
7491
}
7592

7693
private S3MetaRequestWrapper s3MetaRequest() {
77-
if (!metaRequestFuture.isDone()) {
78-
return null;
94+
try {
95+
return metaRequestFuture.get(s3MetaRequestTimeout.toMillis(), TimeUnit.MILLISECONDS);
96+
} catch (ExecutionException | TimeoutException e) {
97+
failResponseHandlerAndFuture(
98+
new RuntimeException("Timeout waiting for metaRequest to be ready", e));
99+
} catch (InterruptedException e) {
100+
Thread.currentThread().interrupt();
101+
failResponseHandlerAndFuture(new RuntimeException(e));
79102
}
80-
81-
return metaRequestFuture.join();
103+
return null;
82104
}
83105

84106
@Override
@@ -110,9 +132,6 @@ public int onResponseBody(ByteBuffer bodyBytesIn, long objectRangeStart, long ob
110132

111133
S3MetaRequestWrapper metaRequest = s3MetaRequest();
112134
if (metaRequest == null) {
113-
// should not happen
114-
failResponseHandlerAndFuture(SdkClientException.create("Unexpected exception occurred: s3metaRequest is not "
115-
+ "initialized yet"));
116135
return;
117136
}
118137
metaRequest.incrementReadWindow(bytesReceived);

services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapterTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import java.nio.ByteBuffer;
2828
import java.nio.charset.StandardCharsets;
29+
import java.time.Duration;
2930
import java.util.concurrent.CompletableFuture;
3031
import java.util.concurrent.TimeUnit;
3132
import org.junit.Before;
@@ -91,6 +92,21 @@ public void successfulResponse_shouldCompleteFutureSuccessfully() throws Excepti
9192
verify(sdkResponseHandler).onHeaders(any(SdkHttpResponse.class));
9293
}
9394

95+
@Test
96+
public void s3MetaRequestNotFinish_shouldFailFuture() throws Exception {
97+
S3CrtResponseHandlerAdapter responseHandlerAdapter = new S3CrtResponseHandlerAdapter(future,
98+
sdkResponseHandler,
99+
null,
100+
new CompletableFuture<>(),
101+
Duration.ofMillis(10));
102+
int statusCode = 200;
103+
responseHandlerAdapter.onResponseHeaders(statusCode, new HttpHeader[0]);
104+
responseHandlerAdapter.onResponseBody(ByteBuffer.wrap("helloworld1".getBytes()), 1, 2);
105+
106+
assertThatThrownBy(() -> future.get(5, TimeUnit.SECONDS))
107+
.hasMessageContaining("Timeout waiting for metaRequest");
108+
}
109+
94110
@Test
95111
public void nullByteBuffer_shouldCompleteFutureExceptionally() {
96112
HttpHeader[] httpHeaders = new HttpHeader[2];

0 commit comments

Comments
 (0)