kotlinx.coroutines
kotlinx.coroutines copied to clipboard
Ability to obtain the current coroutine context in arbitrary code
In IJ we have huge amount of old Java code. Sometimes we want to track certain behaviour or turn on assertions depending on whether the code is running under a coroutine or not, or we'd like to access some value which is put into the coroutine context by the platform. With coroutines started by the platform we can achieve this by putting a simple ThreadContextElement on the platform side:
val ourCoroutineContext = ThreadLocal<CoroutineContext>()
object ThreadCoroutineContextElement :
ThreadContextElement<Unit>,
CoroutineContext.Key<ThreadCoroutineContextElement> {
override val key: CoroutineContext.Key<*>
get() = ThreadCoroutineContextElement
override fun updateThreadContext(context: CoroutineContext) {
ourCoroutineContext.set(context)
}
override fun restoreThreadContext(context: CoroutineContext, oldState: Unit) {
ourCoroutineContext.remove()
}
}
Cons of this approach:
- we cannot do this for all other coroutines which are launched in
GlobalScopeandCoroutineScope(EmptyCoroutineContext)outside of the platform, orrunBlocking. - existence of
ThreadContextElementin the context may trigger the slow path inkotlinx.coroutines.internal.ThreadContextKt#updateThreadContext, it won't be necessary if this behaviour could be natively supported by the library.
Please provide an API for obtaining the current coroutine context inside arbitrary code. I think this behaviour might be turned on or off by a system property. Alternatively, a ServiceLoader-based API for contributing to the default coroutine context would be also helpful.
a ServiceLoader-based API for contributing to the default coroutine context
I've just discovered #2932
https://github.com/JetBrains/intellij-deps-kotlinx.coroutines/commit/0103dd5694cb67f5790a41c0e02cdbc38829735b
I can't imagine how it could be implemented other than by always keeping a thread-local value with the current context that would need to be updated on every dispatch, and it seems like that's the approach you took in https://github.com/JetBrains/intellij-deps-kotlinx.coroutines/commit/0103dd5694cb67f5790a41c0e02cdbc38829735b.
I think the ServiceLoader-based API is the better way: with a global flag, when some parts of the program don't need this behavior, you can't do anything. If, on the other hand, you have a coroutine context element, you can remove it from the context where you don't need it. Other than this added flexibility, I don't see meaningful differences between the two approaches if we sufficiently limit the coroutine context elements suitable for the ServiceLoader API.
you can remove it from the context where you don't need it
How? Override with an empty element with the same key? I'd like to have a proper way of actually removing an element from the context, but I understand that it might be dangerous in general.
Override with an empty element with the same key?
Yep. Also, not in this case, but often, something like CoroutineContext(oldContext.minusKey(unnecessaryKey)) can help.
It would be great to have withoutContext(Key) {}