Skip to content

Commit

Permalink
map ETIMEDOUT or AWS_IO_SOCKET_TIMEOUT exception to java.net.SocketTi…
Browse files Browse the repository at this point in the history
…meoutException
  • Loading branch information
thomasjinlo committed Mar 7, 2025
1 parent 65b048b commit d43620a
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static software.amazon.awssdk.utils.NumericUtils.saturatedCast;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.time.Duration;
import javax.net.ssl.SSLHandshakeException;
import software.amazon.awssdk.annotations.SdkInternalApi;
Expand All @@ -35,6 +36,7 @@
@SdkInternalApi
public final class CrtUtils {
public static final int CRT_TLS_NEGOTIATION_ERROR_CODE = 1029;
public static final int CRT_SOCKET_TIMEOUT = 1048;

private CrtUtils() {
}
Expand All @@ -54,9 +56,12 @@ public static Throwable wrapConnectionFailureException(Throwable throwable) {
Throwable toThrow = new IOException("An exception occurred when acquiring a connection", throwable);
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
int httpErrorCode = httpException.getErrorCode();

if (httpException.getErrorCode() == CRT_TLS_NEGOTIATION_ERROR_CODE) {
if (httpErrorCode == CRT_TLS_NEGOTIATION_ERROR_CODE) {
toThrow = new SSLHandshakeException(httpException.getMessage());
} else if (httpErrorCode == CRT_SOCKET_TIMEOUT) {
toThrow = new SocketTimeoutException(httpException.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
import static software.amazon.awssdk.http.crt.CrtHttpClientTestUtils.createRequest;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import javax.net.ssl.SSLHandshakeException;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -59,6 +63,13 @@ public static Stream<Throwable> retryableExceptions() {
"connection closed"));
}

public static Stream<Entry<Integer, Class<? extends Throwable>>> mappedExceptions() {
return Stream.of(
new SimpleEntry<>(0x0405, SSLHandshakeException.class), // For AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE (1029)
new SimpleEntry<>(0x0418, SocketTimeoutException.class) // For AWS_IO_SOCKET_TIMEOUT (1048)
);
}

@BeforeEach
public void setup() {
requestExecutor = new CrtRequestExecutor();
Expand Down Expand Up @@ -111,6 +122,23 @@ public void execute_AcquireConnectionFailure_shouldAlwaysWrapIOException() {
assertThatThrownBy(executeFuture::join).hasCauseInstanceOf(IOException.class).hasRootCause(exception);
}

@ParameterizedTest
@MethodSource("mappedExceptions")
public void execute_AcquireConnectionFailure_shouldAlwaysBeInstanceOfIOException(Entry<Integer, Class<? extends Throwable>> entry) {
int errorCode = entry.getKey();
Class<? extends Throwable> ioExceptionSubclass = entry.getValue();

CrtRequestContext context = crtRequestContext();
HttpException exception = new HttpException(errorCode);
CompletableFuture<HttpClientConnection> completableFuture = CompletableFutureUtils.failedFuture(exception);

Mockito.when(connectionManager.acquireConnection()).thenReturn(completableFuture);

CompletableFuture<SdkHttpFullResponse> executeFuture = requestExecutor.execute(context);
assertThatThrownBy(executeFuture::join).hasCauseInstanceOf(IOException.class).hasMessageContaining(exception.getMessage());
assertThatThrownBy(executeFuture::join).hasCauseInstanceOf(ioExceptionSubclass);
}

@Test
public void executeRequest_failedOfNonRetryableHttpException_shouldNotWrapIOException() {
HttpException exception = new HttpException(0x0801); // AWS_ERROR_HTTP_HEADER_NOT_FOUND
Expand Down

0 comments on commit d43620a

Please sign in to comment.