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,
)
}