Skip to content

Unnecessary retries for certain errors #7

Description

@DebanKsahu

Describe the bug
The retry policy implemented in KTorApiClient in such a way that the eligibility only checked through response code but if for some reason response code didn't came because of some error then retry happen irrespective of the exception.

            try {
                val resolvedUrl = VariableResolver.resolve(request.url, variableLayers)
                if (resolvedUrl.startsWith("ws://") || resolvedUrl.startsWith("wss://")) {
                    val wsResponse = executeWebSocketRequest(request, resolvedUrl, startTime)
                    emit(NetworkEvent.Success(wsResponse))
                    return@flow
                }

                val preparedRequest = buildRequest(request, variableLayers)
                interceptors.forEach { interceptor -> interceptor.onRequest(preparedRequest) }

                val response = httpClient.request(preparedRequest)
                val headersReceivedTime = currentTimeMillis()
                val serverMs = headersReceivedTime - startTime
                val duration = currentTimeMillis() - startTime
                interceptors.forEach { interceptor -> interceptor.onResponse(response, duration) }

                val mappedResponse = response.toResponseDefinition(
                    request.id, duration,
                    serverMs = serverMs,
                    requestStartTime = startTime,
                )
                val shouldRetry = mappedResponse.statusCode in retryPolicy.retryOnStatusCodes

                if (!shouldRetry || attempt == retryPolicy.maxAttempts) {
                    emit(NetworkEvent.Success(mappedResponse))
                    return@flow
                }

                val delayMs = retryPolicy.delayForAttempt(attempt)
                emit(
                    NetworkEvent.RetryScheduled(
                        attempt,
                        delayMs,
                        "status=${mappedResponse.statusCode}"
                    )
                )
                delay(delayMs)
            } catch (throwable: Throwable) {
                lastThrowable = throwable
                logger.error("Request failed at attempt $attempt", throwable)
                interceptors.forEach { interceptor -> interceptor.onFailure(throwable, attempt) }

                if (attempt == retryPolicy.maxAttempts) {
                    break
                }

                val delayMs = retryPolicy.delayForAttempt(attempt)
                emit(
                    NetworkEvent.RetryScheduled(
                        attempt,
                        delayMs,
                        throwable.message ?: "unknown error"
                    )
                )
                delay(delayMs)
            }
        }

To get a response code successfully, the code above val response = httpClient.request(preparedRequest) like
buildRequest(), VariableResolver.resolve() etc. should execute successfully but if error occur there (e.g.: Invalid URL etc.) then the catch{...} block will execute where retry happen until attempt reached to maxAttempts but we know the error is on fundamental level because of which no matter how many time we try it will not reach to success (e.g: If the URL is invlaid no matter how many retry happen, request can't reach to server) so the retry is totally unnecessary.

Expected behavior
For certain exceptions like these mentioned above retry should be skipped.

Environment (please complete the following information):

  • OS: Windows11
  • Java: 21
  • App version / commit SHA: 1.16.0
  • Module/area: core-network
  • Run mode: None

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions