I’m working on a Quarkus Kotlin project where we are using coroutines almost exclusively, but one challenge I’ve run into is working out how to correctly implement AOP interceptors when the methods we are intercepting are suspend functions, and the activity we want to do “around” the call is also suspending, but our access to the invocation context is at the Java reflection level. While Quarkus has some Kotlin/coroutine support in filters, there is currently no direct support for interceptors.
I have an approach that appears to work but I’m not confident that this is the correct approach and worry it’s going to break in some subtle way. So I’d welcome some critique of this code and suggestions on how to do this better. In particular the direct use of COROUTINE_SUSPENDED worries me here.
@InterceptorBinding
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
annotation class MyInterceptor
@MyInterceptor
@Interceptor
@Priority(1)
class CoroutineInterceptor {
@AroundInvoke
fun intercept(context: InvocationContext): Any? {
val continuation = context.parameters.find { it is Continuation<*> } as Continuation<*>?
return if (continuation != null) {
// launch a job to do our "around" work and then complete the invocation on the callee
// we use the context from the continuation so *should* share the same job/scheduling?
CoroutineScope(continuation.context).launch {
somethingAsync()
context.proceed()
}
// return this special value to the caller to say we are suspending
kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
} else {
// no-op if not a suspend function
context.proceed()
}
}
suspend fun somethingAsync(): Unit {
TODO()
}
}