All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pubnub.internal.retry.RetryableCallback.kt Maven / Gradle / Ivy

package com.pubnub.internal.retry

import com.pubnub.api.retry.RetryConfiguration
import com.pubnub.api.retry.RetryableEndpointGroup
import com.pubnub.internal.extension.scheduleWithDelay
import org.slf4j.LoggerFactory
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.concurrent.RejectedExecutionException
import java.util.concurrent.ScheduledExecutorService
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

internal abstract class RetryableCallback(
    retryConfiguration: RetryConfiguration,
    endpointGroupName: RetryableEndpointGroup,
    private val call: Call,
    private val isEndpointRetryable: Boolean,
    private val executorService: ScheduledExecutorService,
) : Callback, RetryableBase(retryConfiguration, endpointGroupName) {
    private val log = LoggerFactory.getLogger(this.javaClass.simpleName)
    private var retryCount = 0
    private var exponentialMultiplier = 0.0

    override fun onResponse(
        call: Call,
        response: Response,
    ) {
        if (shouldRetryOnResponse(response)) {
            retryOnResponseWithError(response)
        } else {
            onFinalResponse(call, response)
        }
    }

    override fun onFailure(
        call: Call,
        t: Throwable,
    ) {
        if (shouldRetryOnFailure(t)) {
            retryOnFailure()
        } else {
            onFinalFailure(call, t)
        }
    }

    private fun shouldRetryOnResponse(response: Response): Boolean {
        return !response.isSuccessful &&
            retryCount < maxRetryNumberFromConfiguration &&
            isErrorCodeRetryable(response.raw().code) &&
            isRetryConfSetForThisRestCall &&
            isEndpointRetryable
    }

    private fun shouldRetryOnFailure(t: Throwable): Boolean {
        val exception = Exception(t)
        return retryCount < maxRetryNumberFromConfiguration &&
            isExceptionRetryable(exception) &&
            isRetryConfSetForThisRestCall &&
            isEndpointRetryable
    }

    private fun isExceptionRetryable(e: Exception): Boolean {
        return e.cause?.let { cause ->
            retryableExceptions.any { it.isInstance(cause) }
        } ?: false
    }

    private fun retryOnFailure() {
        val effectiveDelay = getDelayFromRetryConfiguration()
        retry(effectiveDelay)
    }

    private fun retryOnResponseWithError(response: Response) {
        val effectiveDelay: Duration = getDelayForRetryOnResponse(response)
        retry(effectiveDelay)
    }

    private fun retry(delay: Duration) {
        retryCount++
        val randomDelayInMillis: Int = random.nextInt(MAX_RANDOM_DELAY_IN_MILLIS)
        val effectiveDelay: Duration = delay + randomDelayInMillis.milliseconds
        log.trace("Added random delay so effective retry delay is ${effectiveDelay.inWholeMilliseconds} millis")
        // don't want to block the main thread in case of Android so using executorService
        try {
            executorService.scheduleWithDelay(effectiveDelay) {
                call.clone().enqueue(this)
            }
        } catch (_: RejectedExecutionException) {
            log.trace("Unable to schedule retry, PubNub was likely already destroyed.")
        }
    }

    private fun getDelayForRetryOnResponse(response: Response): Duration {
        return getDelayBasedOnResponse(response)
    }

    abstract fun onFinalResponse(
        call: Call,
        response: Response,
    )

    abstract fun onFinalFailure(
        call: Call,
        t: Throwable,
    )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy