diff --git a/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt b/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt index 2f042909db6..7a36e5b6f49 100644 --- a/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt +++ b/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt @@ -6,8 +6,10 @@ package io.ktor.client.plugins import io.ktor.client.* import io.ktor.client.call.* +import io.ktor.client.network.sockets.* import io.ktor.client.request.* import io.ktor.client.statement.* +import io.ktor.client.utils.* import io.ktor.events.* import io.ktor.http.* import io.ktor.util.* @@ -167,14 +169,16 @@ public class HttpRequestRetry internal constructor(configuration: Configuration) /** * Enables retrying a request if an exception is thrown during the [HttpSend] phase * and specifies the number of retries. - * By default, [HttpRequestTimeoutException] is not retried. Set [retryOnTimeout] to `true` to retry on timeout. + * By default, [HttpRequestTimeoutException], [ConnectTimeoutException] and [SocketTimeoutException] + * are not retried. + * Set [retryOnTimeout] to `true` to retry on timeout. * Note, that in this case, [HttpTimeout] plugin should be installed after [HttpRequestRetry]. */ public fun retryOnException(maxRetries: Int = -1, retryOnTimeout: Boolean = false) { retryOnExceptionIf(maxRetries) { _, cause -> when { + cause.isTimeoutException() -> retryOnTimeout cause is CancellationException -> false - cause is HttpRequestTimeoutException && retryOnTimeout -> true else -> true } } @@ -392,3 +396,10 @@ private val RetryDelayPerRequestAttributeKey = AttributeKey Long>( "RetryDelayPerRequestAttributeKey" ) + +private fun Throwable.isTimeoutException(): Boolean { + val exception = unwrapCancellationException() + return exception is HttpRequestTimeoutException || + exception is ConnectTimeoutException || + exception is SocketTimeoutException +} diff --git a/ktor-client/ktor-client-core/js/src/io/ktor/client/utils/ExceptionUtilsJs.kt b/ktor-client/ktor-client-core/js/src/io/ktor/client/utils/ExceptionUtilsJs.kt index 237ad648850..2f01ecee8ba 100644 --- a/ktor-client/ktor-client-core/js/src/io/ktor/client/utils/ExceptionUtilsJs.kt +++ b/ktor-client/ktor-client-core/js/src/io/ktor/client/utils/ExceptionUtilsJs.kt @@ -9,4 +9,15 @@ import io.ktor.utils.io.* /** * If the exception contains cause that differs from [CancellationException] returns it otherwise returns itself. */ -public actual fun Throwable.unwrapCancellationException(): Throwable = this +public actual fun Throwable.unwrapCancellationException(): Throwable { + var exception: Throwable? = this + while (exception is CancellationException) { + // If there is a cycle, we return the initial exception. + if (exception == exception.cause) { + return this + } + exception = exception.cause + } + + return exception ?: this +} diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpRequestRetryTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpRequestRetryTest.kt index 2ef322bd577..4c3d7b13e61 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpRequestRetryTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpRequestRetryTest.kt @@ -408,9 +408,8 @@ class HttpRequestRetryTest { } test { client -> - assertFailsWith { - client.get {} - } + val response = client.get {} + assertEquals(HttpStatusCode.OK, response.status) } } } diff --git a/ktor-server/ktor-server-core/jvmAndNix/src/io/ktor/server/routing/RoutingResolveContext.kt b/ktor-server/ktor-server-core/jvmAndNix/src/io/ktor/server/routing/RoutingResolveContext.kt index 25eee305b1c..3f6f12feb9b 100644 --- a/ktor-server/ktor-server-core/jvmAndNix/src/io/ktor/server/routing/RoutingResolveContext.kt +++ b/ktor-server/ktor-server-core/jvmAndNix/src/io/ktor/server/routing/RoutingResolveContext.kt @@ -231,8 +231,8 @@ public class RoutingResolveContext( trait: ArrayList ) { val current = failedEvaluation ?: return - if ((current.quality < new.quality || failedEvaluationDepth < trait.size) - && trait.all { + if ((current.quality < new.quality || failedEvaluationDepth < trait.size) && + trait.all { it.quality == RouteSelectorEvaluation.qualityTransparent || it.quality == RouteSelectorEvaluation.qualityConstant }