Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d2600ab3 authored by Hasib Prince's avatar Hasib Prince
Browse files

Merge branch '1679-retry_exp_backoff' into 'main'

added retry with exponential backoff

See merge request !395
parents 5739e2be e933c9c8
Loading
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
@@ -21,12 +21,18 @@ package foundation.e.apps.data
import foundation.e.apps.data.playstore.utils.GPlayHttpClient
import foundation.e.apps.data.playstore.utils.GplayHttpRequestException
import foundation.e.apps.data.login.exceptions.GPlayException
import kotlinx.coroutines.delay
import timber.log.Timber
import java.net.SocketTimeoutException

private const val TIMEOUT = "Timeout"
private const val UNKNOWN = "Unknown"
private const val STATUS = "Status:"
private const val ERROR_GPLAY_API = "Gplay api has faced error!"
private const val REGEX_429_OR_401 = "429|401"
private const val MAX_RETRY_DELAY_IN_SECONDS = 300
private const val ONE_SECOND_IN_MILLIS = 1000L
private const val INITIAL_DELAY_RETRY_IN_SECONDS = 10

suspend fun <T> handleNetworkResult(call: suspend () -> T): ResultSupreme<T> {
    return try {
@@ -71,3 +77,40 @@ private fun extractErrorMessage(e: Exception): String {
    }
    return (e.localizedMessage?.ifBlank { ERROR_GPLAY_API } ?: ERROR_GPLAY_API) + " $STATUS $status"
}

suspend fun <T> retryWithBackoff(retryDelayInSeconds: Int = -1, operation: suspend () -> T): T? {
    var result: T? = null
    try {
        if (retryDelayInSeconds > 0) {
            delay(ONE_SECOND_IN_MILLIS * retryDelayInSeconds)
        }

        result = operation()

        if (shouldRetry(result, retryDelayInSeconds)) {
            Timber.w("Retrying...: $retryDelayInSeconds")
            result = retryWithBackoff(calculateRetryDelay(retryDelayInSeconds), operation)
        }

    } catch (e: Exception) {
        Timber.e(e)
        if (retryDelayInSeconds < MAX_RETRY_DELAY_IN_SECONDS) {
            return retryWithBackoff(calculateRetryDelay(retryDelayInSeconds), operation)
        }
    }

    return result
}

private fun calculateRetryDelay(retryDelayInSecond: Int) =
    if (retryDelayInSecond < 0) INITIAL_DELAY_RETRY_IN_SECONDS else retryDelayInSecond * 2


private fun <T> shouldRetry(result: T, retryDelayInSecond: Int) =
    result is ResultSupreme<*> && !result.isSuccess() && retryDelayInSecond < MAX_RETRY_DELAY_IN_SECONDS
            && isExceptionAllowedToRetry(result.exception)

private fun isExceptionAllowedToRetry(exception: Exception?): Boolean {
    // Here, (value != true) is used, because value can be null also and we want to allow retry for null message
    return exception?.message?.contains(Regex(REGEX_429_OR_401)) != true
}
+15 −12
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import foundation.e.apps.data.login.api.PlayStoreLoginManagerFactory
import foundation.e.apps.data.login.api.PlayStoreLoginManager
import foundation.e.apps.data.login.api.GoogleLoginManager
import foundation.e.apps.data.login.api.PlayStoreLoginWrapper
import foundation.e.apps.data.retryWithBackoff
import timber.log.Timber
import java.util.Locale
import javax.inject.Inject
@@ -77,25 +78,27 @@ class PlayStoreAuthenticator @Inject constructor(
    override suspend fun login(): AuthObject.GPlayAuth {
        val savedAuth = getSavedAuthData()

        val authData = (
            savedAuth ?: run {
        val authData = savedAuth ?: run {
            // if no saved data, then generate new auth data.
                generateAuthData().let {
            val result = retryWithBackoff {
                generateAuthData()
            }

            result?.let {
                if (it.isSuccess()) it.data!!
                else return AuthObject.GPlayAuth(it, user)
            }
        }
            )

        val formattedAuthData = formatAuthData(authData)
        formattedAuthData.locale = locale
        val formattedAuthData = authData?.let { formatAuthData(it) }
        formattedAuthData?.locale = locale
        val result: ResultSupreme<AuthData?> = ResultSupreme.create(
            status = ResultStatus.OK,
            data = formattedAuthData
        )
        result.otherPayload = formattedAuthData.email
        result.otherPayload = formattedAuthData?.email

        if (savedAuth == null) {
        if (savedAuth == null && formattedAuthData != null) {
            saveAuthData(formattedAuthData)
        }