1
\$\begingroup\$

I have a scenario in which I call many APIs throughout my application lifecycle.

In case the server sends me a 503 response code, it will also send a lockdown object which has a timestamp associated in one of the fields. The reason of this being that I have to block further calls to the server till the timestamp expires.

My solution consists of a retrofit client coupled with an okhttp Interceptor

Here are the steps of execution:

Interceptor (Sentinel) intercepts the request Interceptor checks a global exception value which is held by an object class (Singelton - LockDownManager ) returns a true or false based on the current timestamp. The LockDownManager is also responsible for memoizing the exception, incase the server is still locked down it helps in rethrowing the same exception.

below is my implementation, I am only including the relevant parts from Sentinel:

   class Sentinel : Interceptor {

        override fun intercept(chain: Interceptor.Chain): Response {
            // Check with global counter if request can be executed
            val canClientMakeRequest = LockDownManager.isServerAvailable()
            if (!canClientMakeRequest) {
                throw LockDownManager.ex as HttpStatusDetailedException
            } else {
                // Mark exception null
                LockDownManager.ex = null
            }
       //....... Continue execution 
       // Response received
       // Check if response contains 503 object 
       // Done by checkIfHasToMemoize(...)
    }

Here is how exception is memoized and parsed:

fun checkIfHasToMemoize(resp: Response) {
    // Parse response and derive errorObj - excluded 
    val errorObj = httpResult.httpError
    if (errorObj != null) {
        val detailedException =
            HttpStatusDetailedException(httpStatusCode, "Unexpected HTTP(S) result:" +
                " $result", errorObj)
        // Set LockDown -> isServerAvailable
        if (detailedException.httpCode == 503 && null != LockDownManager.ex)
            LockDownManager.ex = detailedException

        throw detailedException

    }

Finally here is my singleton Manager:

object LockDownManager {

    @Volatile
    var ex: HttpStatusDetailedException? = null

     fun isServerAvailable(): Boolean {
        if (ex == null) {
             return true
        }
        val serverAvailableTimestamp = ex?.blockUntil ?: 0L
        if (serverAvailableTimestamp == 0L) {
            return true
        }
        val c = Calendar.getInstance()
        c.timeZone = TimeZone.getTimeZone("UTC")
        val currentTime = c.timeInMillis
        return currentTime > serverAvailableTimestamp
    }
}

This is a multithreaded environment and more than the semantics of my variable names I am concerned about the veracity of thread safety.

\$\endgroup\$
  • \$\begingroup\$ Do you get a performance issue if you don't memoize the exceptions? Exceptions that are created at one place and thrown in another are generally less useful as they reuse the same stacktrace. \$\endgroup\$ – Simon Forsberg Apr 25 at 8:50
  • \$\begingroup\$ Memoization is by design, I am rethrowing the memoized exception because there is no chance for the client to connect back to the server and get a new exception - think of it in a way like caging the client and rethrowing the same exception on each retry within the specified time. \$\endgroup\$ – User3 Apr 25 at 14:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.