In the following code, all calls to retry get the warning "Recursive call is not a tail call":
private tailrec suspend fun <T> retry(numberOfRetries: Int, block: suspend () -> T): Result<T> =
runCatching {
Timber.d("retry($numberOfRetries)")
block()
}.fold(
onSuccess = { Result.success(it) },
onFailure = { throwable ->
when (throwable) {
is TimeoutCancellationException -> {
Timber.e(throwable, "Request ran into timeout - retrying immediately...")
if (numberOfRetries > 0) {
retry(numberOfRetries - 1, block)
} else {
Result.failure(throwable)
}
}
is HttpException -> {
if (throwable.code() == HttpStatusCode.NOT_FOUND_404) {
Timber.e(throwable, "Request returned 404 - don't retry!")
Result.failure(throwable)
} else {
Timber.e(throwable, "Request returned some other error code - retrying in 3 seconds...")
if (numberOfRetries > 0) {
delay(DELAY_BEFORE_RETRY_MS)
retry(numberOfRetries - 1, block)
} else {
Result.failure(throwable)
}
}
}
else -> {
Timber.e(throwable, "Some other problem with request - retrying in 3 seconds...")
if (numberOfRetries > 0) {
delay(DELAY_BEFORE_RETRY_MS)
retry(numberOfRetries - 1, block)
} else {
Result.failure(throwable)
}
}
}
}
)
- Why?
- Is there a way to rewrite this function to make it tail recursive?
Looks like the tailrec detection doesn't go through inline higher-order functions. You're using
foldhere and passing callbacks, you're not callingretrydirectly fromretry's body.Here is a simpler example demonstrating the issue:
I think tail recursion is also not supported in
try/catch/finallyblocks, so getting rid of therunCatching/foldwill not help in this sense.If you really really want recursion here, you can probably get around it using Ivo's answer.
That being said, retrying is rather inherently a repetition, so I wouldn't go with a recursive approach in the first place. Using recursion makes it unclear which error will end up being reported for instance. It also adds a bunch of extra
ifs in your current code. How about this?I would even go as far as saying don't use
Resultin business code, and just throw, but that impacts your signature so I won't change it.Also, note that you're catching
Throwablewhich means you're catching WAY MORE than just request errors here - which makes your message misleading.