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

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

package com.pubnub.internal.retry

import com.pubnub.api.PubNubError
import com.pubnub.api.PubNubException
import com.pubnub.api.retry.RetryConfiguration
import com.pubnub.api.retry.RetryableEndpointGroup
import com.pubnub.internal.PubNubRetryableException
import okhttp3.ResponseBody.Companion.toResponseBody
import org.slf4j.LoggerFactory
import retrofit2.Call
import retrofit2.Response

internal class RetryableRestCaller(
    retryConfiguration: RetryConfiguration,
    endpointGroupName: RetryableEndpointGroup,
    private val isEndpointRetryable: Boolean,
) : RetryableBase(retryConfiguration, endpointGroupName) {
    private val log = LoggerFactory.getLogger(this.javaClass.simpleName)
    internal lateinit var call: Call

    internal fun execute(callToBeExecuted: Call): Response {
        call = callToBeExecuted
        var numberOfAttempts = 0
        while (true) {
            val (response, exception) = executeRestCall()
            if (!shouldRetry(response) || numberOfAttempts++ >= maxRetryNumberFromConfiguration) {
                // http issue can be represented by error code inside unsuccessful response or by exception
                // if it is represented by error code then we want to pass response for further processing
                if (response.isSuccessful || exception == null) {
                    return response
                } else {
                    throw exception
                }
            }
            val randomDelayInMillis = random.nextInt(MAX_RANDOM_DELAY_IN_MILLIS)
            val effectiveDelayInMillis = getDelayBasedOnResponse(response).inWholeMilliseconds + randomDelayInMillis
            log.trace("Added random delay so effective retry delay is $effectiveDelayInMillis")
            Thread.sleep(effectiveDelayInMillis) // we want to sleep here on current thread since this is synchronous call

            call = call.clone()
        }
    }

    private fun executeRestCall(): Pair, Exception?> {
        var pubNubException: Exception? = null
        try {
            return try {
                val response = call.execute()
                Pair(response, pubNubException)
            } catch (e: Exception) {
                pubNubException =
                    PubNubException(
                        pubnubError = PubNubError.PARSING_ERROR,
                        errorMessage = e.toString(),
                        affectedCall = call,
                    )

                if (isExceptionRetryable(e)) {
                    throw PubNubRetryableException(
                        pubnubError = PubNubError.CONNECT_EXCEPTION,
                        errorMessage = e.toString(),
                        statusCode = SERVICE_UNAVAILABLE, // all retryable exceptions internally are mapped to 503 error
                    )
                } else {
                    throw pubNubException
                }
            }
        } catch (e: PubNubRetryableException) {
            return Pair(
                Response.error(e.statusCode, e.errorMessage?.toResponseBody() ?: "".toResponseBody()),
                pubNubException,
            )
        }
    }

    private fun isExceptionRetryable(e: Exception): Boolean {
        return retryableExceptions.any { it.isInstance(e) }
    }

    private fun shouldRetry(response: Response) =
        !response.isSuccessful &&
            isErrorCodeRetryable(response.raw().code) &&
            isRetryConfSetForThisRestCall &&
            isEndpointRetryable
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy