spring-retry icon indicating copy to clipboard operation
spring-retry copied to clipboard

Spring Retry not working with Kotlin Async Coroutine

Open nayanseth opened this issue 2 years ago • 2 comments

Note: the below function test is being called via a launch coroutine.

The following code works:

@Retryable(value=[RetryException::class])
suspend fun test() {
  throw RetryException("blah")
}

However, the moment I add an async call, retry stops working:

@Retryable(value=[RetryException::class])
suspend fun test() {
  val deferred = supervisorScope {
    async { library.method() }
  }
  deferred.await()
  throw RetryException("blah")
}

What could be wrong?

nayanseth avatar Jun 16 '22 02:06 nayanseth

The @Retryable creates a proxy call around the method with try...catch. I don't think that Kotlin async behavior does fit into the try...catch model.

It might be great to see a generated Java code back from this Kotlin to determine when exactly that @Retryable sits in the call stack. It is very likely that there are some anonymous classes are generated and their method calls are done in the separate thread which is already out of retry proxy.

artembilan avatar Jun 16 '22 15:06 artembilan

You can find more information on Coroutines exception handling here. Maybe worth to have a reproducer to analyze the behavior and evaluate if that makes sense to support it.

If not supported, maybe worth to throw an early error at startup if possible and document it is not supported, leveraging KotlinDetector.isSuspendingFunction() to avoid silent failures.

Not sure if that matters here, but from a bytecode / Java reflection perspective, suspend fun test() in Kotlin will be translated to something like Object test(Continuation continuation) in Java, so to get the real signature you need to identify suspending functions with KotlinDetector.isSuspendingFunction() and then for example use Kotlin reflection to get the real return value / parameter via ReflectJvmMapping.getKotlinFunction (see usages in Spring Framework). Also if you want to re-invoke the suspending function, you probably need to leverage Kotlin reflection API as well.

sdeleuze avatar Jun 17 '22 09:06 sdeleuze