Skip to content

Commit

Permalink
fix: make connection resets retryable in OkHttp engine
Browse files Browse the repository at this point in the history
  • Loading branch information
aajtodd committed Jul 19, 2023
1 parent 751f451 commit 011273c
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 1 deletion.
8 changes: 8 additions & 0 deletions .changes/d50afb27-0e5c-46b3-9c9c-865a3c75dd21.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "d50afb27-0e5c-46b3-9c9c-865a3c75dd21",
"type": "bugfix",
"description": "Retry connection reset errors in OkHttp engine",
"issues": [
"awslabs/aws-sdk-kotlin#905"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import okhttp3.*
import okhttp3.Authenticator
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.internal.http.HttpMethod
import java.io.EOFException
import java.io.IOException
import java.net.*
import javax.net.ssl.SSLHandshakeException
Expand Down Expand Up @@ -191,16 +192,22 @@ internal inline fun<T> mapOkHttpExceptions(block: () -> T): T =
throw HttpException(ex, ex.errCode(), ex.isRetryable())
}

private fun Exception.isRetryable(): Boolean = isCauseOrSuppressed<ConnectException>()
private fun Exception.isRetryable(): Boolean = isCauseOrSuppressed<ConnectException>() || isConnectionClosedException()
private fun Exception.errCode(): HttpErrorCode = when {
isConnectTimeoutException() -> HttpErrorCode.CONNECT_TIMEOUT
isConnectionClosedException() -> HttpErrorCode.CONNECTION_CLOSED
isCauseOrSuppressed<SocketTimeoutException>() -> HttpErrorCode.SOCKET_TIMEOUT
isCauseOrSuppressed<SSLHandshakeException>() -> HttpErrorCode.TLS_NEGOTIATION_ERROR
else -> HttpErrorCode.SDK_UNKNOWN
}

private fun Exception.isConnectTimeoutException(): Boolean =
findCauseOrSuppressed<SocketTimeoutException>()?.message?.contains("connect", ignoreCase = true) == true

private fun Exception.isConnectionClosedException(): Boolean =
message?.contains("unexpected end of stream") == true &&
(cause as? Exception)?.findCauseOrSuppressed<EOFException>()?.message?.contains("\\n not found: limit=0") == true

private inline fun <reified T> Exception.isCauseOrSuppressed(): Boolean = findCauseOrSuppressed<T>() != null
private inline fun <reified T> Exception.findCauseOrSuppressed(): T? {
if (this is T) return this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package aws.smithy.kotlin.runtime.http.engine.okhttp

import aws.smithy.kotlin.runtime.http.HttpErrorCode
import aws.smithy.kotlin.runtime.http.HttpException
import java.io.EOFException
import java.io.IOException
import java.net.ConnectException
import java.net.SocketTimeoutException
Expand All @@ -33,6 +34,8 @@ class OkHttpExceptionTest {
ExceptionTest(SocketTimeoutException("read timeout"), expectedError = HttpErrorCode.SOCKET_TIMEOUT),
ExceptionTest(IOException(SSLHandshakeException("negotiate error")), expectedError = HttpErrorCode.TLS_NEGOTIATION_ERROR),
ExceptionTest(ConnectException("test connect error"), expectedError = HttpErrorCode.SDK_UNKNOWN, true),
// see https://github.com/awslabs/aws-sdk-kotlin/issues/905
ExceptionTest(IOException("unexpected end of stream on https://test.aws.com", EOFException("\\n not found: limit=0 content=...")), expectedError = HttpErrorCode.CONNECTION_CLOSED, expectedRetryable = true),
)

for (test in tests) {
Expand Down

0 comments on commit 011273c

Please sign in to comment.