Merge pull request #4995 from vector-im/feature/fre/fix_retry_delay_on_429

Do not auto-retry on 429 in case of too long retry delay
This commit is contained in:
Benoit Marty 2022-01-20 14:31:29 +01:00 committed by GitHub
commit d82743eeab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 38 deletions

1
changelog.d/4995.removal Normal file
View File

@ -0,0 +1 @@
429 are not automatically retried anymore in case of too long retry delay

View File

@ -32,13 +32,18 @@ fun Throwable.is401() =
fun Throwable.isTokenError() = fun Throwable.isTokenError() =
this is Failure.ServerError && this is Failure.ServerError &&
(error.code == MatrixError.M_UNKNOWN_TOKEN || (error.code == MatrixError.M_UNKNOWN_TOKEN ||
error.code == MatrixError.M_MISSING_TOKEN || error.code == MatrixError.M_MISSING_TOKEN ||
error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT) error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT)
fun Throwable.isLimitExceededError() =
this is Failure.ServerError &&
httpCode == 429 &&
error.code == MatrixError.M_LIMIT_EXCEEDED
fun Throwable.shouldBeRetried(): Boolean { fun Throwable.shouldBeRetried(): Boolean {
return this is Failure.NetworkConnection || return this is Failure.NetworkConnection ||
this is IOException || this is IOException ||
(this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED) this.isLimitExceededError()
} }
/** /**

View File

@ -19,8 +19,9 @@ package org.matrix.android.sdk.internal.network
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.failure.getRetryDelay import org.matrix.android.sdk.api.failure.getRetryDelay
import org.matrix.android.sdk.api.failure.isLimitExceededError
import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.internal.network.ssl.CertUtil import org.matrix.android.sdk.internal.network.ssl.CertUtil
import retrofit2.HttpException import retrofit2.HttpException
@ -33,7 +34,8 @@ import java.io.IOException
* *
* @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError] * @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError]
* @param canRetry if set to true, the request will be executed again in case of error, after a delay * @param canRetry if set to true, the request will be executed again in case of error, after a delay
* @param maxDelayBeforeRetry the max delay to wait before a retry * @param maxDelayBeforeRetry the max delay to wait before a retry. Note that in the case of a 429, if the provided delay exceeds this value, the error will
* be propagated as it does not make sense to retry it with a shorter delay.
* @param maxRetriesCount the max number of retries * @param maxRetriesCount the max number of retries
* @param requestBlock a suspend lambda to perform the network request * @param requestBlock a suspend lambda to perform the network request
*/ */
@ -74,23 +76,26 @@ internal suspend inline fun <DATA> executeRequest(globalErrorReceiver: GlobalErr
currentRetryCount++ currentRetryCount++
if (exception is Failure.ServerError && if (exception.isLimitExceededError() && currentRetryCount < maxRetriesCount) {
exception.httpCode == 429 &&
exception.error.code == MatrixError.M_LIMIT_EXCEEDED &&
currentRetryCount < maxRetriesCount) {
// 429, we can retry // 429, we can retry
delay(exception.getRetryDelay(1_000)) val retryDelay = exception.getRetryDelay(1_000)
if (retryDelay <= maxDelayBeforeRetry) {
delay(retryDelay)
} else {
// delay is too high to be retried, propagate the exception
throw exception
}
} else if (canRetry && currentRetryCount < maxRetriesCount && exception.shouldBeRetried()) { } else if (canRetry && currentRetryCount < maxRetriesCount && exception.shouldBeRetried()) {
delay(currentDelay) delay(currentDelay)
currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry) currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry)
// Try again (loop) // Try again (loop)
} else { } else {
throw when (exception) { throw when (exception) {
is IOException -> Failure.NetworkConnection(exception) is IOException -> Failure.NetworkConnection(exception)
is Failure.ServerError, is Failure.ServerError,
is Failure.OtherServerError, is Failure.OtherServerError,
is CancellationException -> exception is CancellationException -> exception
else -> Failure.Unknown(exception) else -> Failure.Unknown(exception)
} }
} }
} }

View File

@ -23,8 +23,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.getRetryDelay import org.matrix.android.sdk.api.failure.getRetryDelay
import org.matrix.android.sdk.api.failure.isLimitExceededError
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
@ -145,17 +145,17 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
task.execute() task.execute()
} catch (exception: Throwable) { } catch (exception: Throwable) {
when { when {
exception is IOException || exception is Failure.NetworkConnection -> { exception is IOException || exception is Failure.NetworkConnection -> {
canReachServer.set(false) canReachServer.set(false)
task.markAsFailedOrRetry(exception, 0) task.markAsFailedOrRetry(exception, 0)
} }
(exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> { (exception.isLimitExceededError()) -> {
task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000)) task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000))
} }
exception is CancellationException -> { exception is CancellationException -> {
Timber.v("## $task has been cancelled, try next task") Timber.v("## $task has been cancelled, try next task")
} }
else -> { else -> {
Timber.v("## un-retryable error for $task, try next task") Timber.v("## un-retryable error for $task, try next task")
// this task is in error, check next one? // this task is in error, check next one?
task.onTaskFailed() task.onTaskFailed()

View File

@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.auth.data.sessionId
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isLimitExceededError
import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.failure.isTokenError
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.CryptoService
@ -171,7 +171,7 @@ internal class EventSenderProcessorThread @Inject constructor(
break@retryLoop break@retryLoop
} catch (exception: Throwable) { } catch (exception: Throwable) {
when { when {
exception is IOException || exception is Failure.NetworkConnection -> { exception is IOException || exception is Failure.NetworkConnection -> {
canReachServer = false canReachServer = false
if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed() if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed()
while (!canReachServer) { while (!canReachServer) {
@ -180,7 +180,7 @@ internal class EventSenderProcessorThread @Inject constructor(
waitForNetwork() waitForNetwork()
} }
} }
(exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> { (exception.isLimitExceededError()) -> {
if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed() if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed()
Timber.v("## SendThread retryLoop retryable error for $task reason: ${exception.localizedMessage}") Timber.v("## SendThread retryLoop retryable error for $task reason: ${exception.localizedMessage}")
// wait a bit // wait a bit
@ -188,17 +188,17 @@ internal class EventSenderProcessorThread @Inject constructor(
sleep(3_000) sleep(3_000)
continue@retryLoop continue@retryLoop
} }
exception.isTokenError() -> { exception.isTokenError() -> {
Timber.v("## SendThread retryLoop retryable TOKEN error, interrupt") Timber.v("## SendThread retryLoop retryable TOKEN error, interrupt")
// we can exit the loop // we can exit the loop
task.onTaskFailed() task.onTaskFailed()
throw InterruptedException() throw InterruptedException()
} }
exception is CancellationException -> { exception is CancellationException -> {
Timber.v("## SendThread task has been cancelled") Timber.v("## SendThread task has been cancelled")
break@retryLoop break@retryLoop
} }
else -> { else -> {
Timber.v("## SendThread retryLoop Un-Retryable error, try next task") Timber.v("## SendThread retryLoop Un-Retryable error, try next task")
// this task is in error, check next one? // this task is in error, check next one?
task.onTaskFailed() task.onTaskFailed()

View File

@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.MatrixIdFailure import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.failure.isLimitExceededError
import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
@ -58,53 +59,53 @@ class DefaultErrorFormatter @Inject constructor(
} }
is Failure.ServerError -> { is Failure.ServerError -> {
when { when {
throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> { throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
// Special case for terms and conditions // Special case for terms and conditions
stringProvider.getString(R.string.error_terms_not_accepted) stringProvider.getString(R.string.error_terms_not_accepted)
} }
throwable.isInvalidPassword() -> { throwable.isInvalidPassword() -> {
stringProvider.getString(R.string.auth_invalid_login_param) stringProvider.getString(R.string.auth_invalid_login_param)
} }
throwable.error.code == MatrixError.M_USER_IN_USE -> { throwable.error.code == MatrixError.M_USER_IN_USE -> {
stringProvider.getString(R.string.login_signup_error_user_in_use) stringProvider.getString(R.string.login_signup_error_user_in_use)
} }
throwable.error.code == MatrixError.M_BAD_JSON -> { throwable.error.code == MatrixError.M_BAD_JSON -> {
stringProvider.getString(R.string.login_error_bad_json) stringProvider.getString(R.string.login_error_bad_json)
} }
throwable.error.code == MatrixError.M_NOT_JSON -> { throwable.error.code == MatrixError.M_NOT_JSON -> {
stringProvider.getString(R.string.login_error_not_json) stringProvider.getString(R.string.login_error_not_json)
} }
throwable.error.code == MatrixError.M_THREEPID_DENIED -> { throwable.error.code == MatrixError.M_THREEPID_DENIED -> {
stringProvider.getString(R.string.login_error_threepid_denied) stringProvider.getString(R.string.login_error_threepid_denied)
} }
throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> { throwable.isLimitExceededError() -> {
limitExceededError(throwable.error) limitExceededError(throwable.error)
} }
throwable.error.code == MatrixError.M_TOO_LARGE -> { throwable.error.code == MatrixError.M_TOO_LARGE -> {
stringProvider.getString(R.string.error_file_too_big_simple) stringProvider.getString(R.string.error_file_too_big_simple)
} }
throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> { throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
stringProvider.getString(R.string.login_reset_password_error_not_found) stringProvider.getString(R.string.login_reset_password_error_not_found)
} }
throwable.error.code == MatrixError.M_USER_DEACTIVATED -> { throwable.error.code == MatrixError.M_USER_DEACTIVATED -> {
stringProvider.getString(R.string.auth_invalid_login_deactivated_account) stringProvider.getString(R.string.auth_invalid_login_deactivated_account)
} }
throwable.error.code == MatrixError.M_THREEPID_IN_USE && throwable.error.code == MatrixError.M_THREEPID_IN_USE &&
throwable.error.message == "Email is already in use" -> { throwable.error.message == "Email is already in use" -> {
stringProvider.getString(R.string.account_email_already_used_error) stringProvider.getString(R.string.account_email_already_used_error)
} }
throwable.error.code == MatrixError.M_THREEPID_IN_USE && throwable.error.code == MatrixError.M_THREEPID_IN_USE &&
throwable.error.message == "MSISDN is already in use" -> { throwable.error.message == "MSISDN is already in use" -> {
stringProvider.getString(R.string.account_phone_number_already_used_error) stringProvider.getString(R.string.account_phone_number_already_used_error)
} }
throwable.error.code == MatrixError.M_THREEPID_AUTH_FAILED -> { throwable.error.code == MatrixError.M_THREEPID_AUTH_FAILED -> {
stringProvider.getString(R.string.error_threepid_auth_failed) stringProvider.getString(R.string.error_threepid_auth_failed)
} }
throwable.error.code == MatrixError.M_UNKNOWN && throwable.error.code == MatrixError.M_UNKNOWN &&
throwable.error.message == "Not allowed to join this room" -> { throwable.error.message == "Not allowed to join this room" -> {
stringProvider.getString(R.string.room_error_access_unauthorized) stringProvider.getString(R.string.room_error_access_unauthorized)
} }
else -> { else -> {
throwable.error.message.takeIf { it.isNotEmpty() } throwable.error.message.takeIf { it.isNotEmpty() }
?: throwable.error.code.takeIf { it.isNotEmpty() } ?: throwable.error.code.takeIf { it.isNotEmpty() }
} }