Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom interceptors in coroutines

I've been getting familiar with the ContinuationInterceptor section of coroutines I wrote the following section of code to verify my idea

class MyContext: CoroutineContext.Element{
    override val key : CoroutineContext.Key<*>
    get() = object : CoroutineContext.Key<MyContext>{}
}

val myInterceptor = object : ContinuationInterceptor {
    //Key is set to a non-interceptor type
    override val key : CoroutineContext.Key<MyContext>
    get() = object : CoroutineContext.Key<MyContext>{}

    override fun <T> interceptContinuation(continuation: 
        Continuation<T>): Continuation<T> {
        Log.i(TAG,"interceptor:"+continuation.context[CoroutineName].toString())
        return Continuation(EmptyCoroutineContext) {
            thread(name = "myThread") {
                continuation.resumeWith(it)
            }
        }
    }
}

lifecycleScope.launch(myInterceptor + CoroutineName("MY1")) {
    Log.i(TAG,"MY1 start:"+Thread.currentThread().name)
    fun1()

    launch(CoroutineName("MY2")) {
        Log.i(TAG,"MY2 run:"+Thread.currentThread().name)
    }
    withContext(Dispatchers.Main+CoroutineName("MY3")) {
        Log.i(TAG,"MY3 run:"+Thread.currentThread().name)
    }
    Log.i(TAG,"MY1 end:"+Thread.currentThread().name)
}

enter image description here

I don't have the myInterceptor Key specified as ContinuationInterceptor.Key.

So coroutines should not recognize it as an Interceptor.

MY1 and MY2 prove this idea, but MY3 is intercepted by myInterceptor and runs in a new thread.

I wonder:

  1. Why is myInterceptor recognized as an interceptor

  2. Why does not Dispatcher.Main work

like image 588
CoderZeng Avatar asked Sep 11 '25 21:09

CoderZeng


1 Answers

The behavior of the shown code is weird. I couldn't find the exact reason why.

I fixed some obvious problems with your code, such as returning a new object from key every time, and not implementing equals. I return a singleton, so the default equals works:

class MyContext: CoroutineContext.Element {
    companion object Key : CoroutineContext.Key<MyContext>
    override val key: CoroutineContext.Key<MyContext> = Key
}

val myInterceptor = object : ContinuationInterceptor {
    override val key: CoroutineContext.Key<MyContext> = MyContext.Key

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        println("interceptContinuation: ${continuation.context[CoroutineName]?.name}")
        return Continuation(EmptyCoroutineContext) {
            thread(name = "myThread") {
                continuation.resumeWith(it)
            }
        }
    }

    override fun toString() = "My Interceptor"
}

These fixes don't change the behavior, though, and we can see it's broken even before messing with the dispatching:

val goodCtx = CoroutineName("A") + CoroutineName("B")
val badCtx = myInterceptor + myInterceptor
println(goodCtx)
println(badCtx)

This prints

CoroutineName(B)
[My Interceptor, My Interceptor]

The first line shows you what the behavior is supposed to be, and the second one shows what it is with your implementation. You broke the equality relation. I didn't get to realizing why.

Even this finding doesn't directly explain how come your interceptor ends up being used, but it does give you some insight into what kind of things start happening when you use the framework against the intention of the authors.

like image 88
Marko Topolnik Avatar answered Sep 14 '25 10:09

Marko Topolnik