taer
08/07/2024, 2:21 PMupdateThreadContext and restoreThreadContext don't seem to get invoked if there is no coroutineDispatcher in the context.
Ran into this in a small grpc client app. We mistakenly used suspend fun main instead of runBlocking, so the code was running with no dispatcher.
We noticed that our ThreadContextElement was present in the coroutineContext when resumed, but was not being restored to the ThreadLocal. Once we added a coroutineDispatcher to the context, everything worked.
Running with a Dispatcher is obviously something useful. But it was a surprise that the coroutineContext was restored without the ThreadLocals.
Is this a bug?
Reproduced here: https://github.com/taer/coroutine-dispatch-issueDmitry Khalanskiy [JB]
08/07/2024, 2:30 PMThreadContextElement, like any context element except for ContinuationInterceptor, is simply an object lying in a CoroutineContext, and someone has to look at it and know to interpret it somehow. suspend fun main is implemented on the level of the language, so it clearly has no knowledge of context elements introduced by the kotlinx.coroutines library. I don't think it's possible to change this behavior, though it's probably worth explicitly highlighting in the documentation.taer
08/07/2024, 2:33 PMDmitry Khalanskiy [JB]
08/07/2024, 2:36 PMcoroutineContext is a language-level feature, as is delivering it to the resumed code, and ThreadContextElement is a library-level construct. The language doesn't know about our library.taer
08/07/2024, 2:38 PMtaer
08/07/2024, 2:38 PMtaer
08/07/2024, 2:41 PMwithContinuationContext inside CoroutineContext, which sounds semi-generic. But I'm too deep now in code that would take a bit to figure out. 🙂taer
08/07/2024, 7:15 PMupdateThreadContextSam
08/08/2024, 7:09 AMContinuation, which is part of the language/stdlib. The injection of library-level features happens when the language calls continuation.intercepted(). That calls through to
coroutineContext[ContinuationInterceptor]?.interceptContinuation(this)
So by placing a ContinuationInterceptor into the coroutine context, the coroutines library can modify (decorate) the continuation (coroutine) that's created by the language. In practice, all the continuation interceptors inherit from CoroutineDispatcher. I think it mostly makes sense that the dispatcher deals with thread context, since the dispatcher is the thing providing the thread. I could see an argument for breaking up the ContinuationInterceptor class hierarchy to separate out the different responsibility more, though.Sam
08/08/2024, 7:11 AMwithContext function messes with this pattern to save time, but that's an implementation detail)taer
08/08/2024, 3:26 PM