kotlinx.coroutines
kotlinx.coroutines copied to clipboard
Support injection of default context elements
CoroutineScope.newCoroutineContext currently sets up coroutine contexts via a hard-wired mechanism: https://github.com/Kotlin/kotlinx.coroutines/blob/287a931d3b8ce534757c6c2399eb72e6574bcf69/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt#L32-L37
Here is a use case where a customizable set of default context elements would help:
The Kotest assertions library has a Soft Assertions feature which collects multiple failures and finally reports them in a single exception. It uses a thread-local error-collecting context to achieve this. ThreadLocal<T>.asContextElement makes this work with thread-switching coroutines.
However, the user must either use the Kotest framework (which sets up the coroutine context) or remember to insert a provided errorCollectorContextElement wherever a top-level coroutine context is created:
@Test
fun `assertSoftly demonstration`() = runBlocking(Dispatchers.Unconfined + errorCollectorContextElement) {
// Two intentional failures will be collected reported as one.
assertSoftly {
1 + 1 shouldBe 2 + 1
delay(10) // the coroutine will switch threads here
2 + 3 shouldBe 5 + 1
}
}
Ideally, the Kotest assertions library would take care of such initialization, allowing the user to just use runBlocking(Dispatchers.Unconfined) { ... } without having to care about the errorCollectorContextElement.
An idea would be to provide a DefaultContextFactory, which could be set up on the JVM via ServiceLoader, analogous to the existing MainDispatcherFactory.
Related:
- https://github.com/kotest/kotest/pull/2500
- #1696
Notable: both use cases reported for this involve CopyableThreadContextElement. I think we can easily provide a limited version of default context element injection just for CopyableThreadContextElement. This way, nothing strange should happen.
Question: is there a reason why https://github.com/kotest/kotest/blob/42d571cb22bae6ef60db701615b63c27130223b8/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/assertions/ErrorCollector.kt#L40-L62 is not just threadLocalErrorCollector.asContextElement()?
ErrorCollectorContextElement is a CopyableThreadContextElement which does a deep copy of the underlying CoroutineLocalErrorCollector. If we'd use a plain asContextElement() instead, we'd miss ErrorCollector isolation. The test "concurrent withClue invocations should be isolated from each other" then fails.
ServiceLoader API will also help us with tracing and other observability. We also wanted to override the default dispatcher but gave up. Since we have a separate fork, we might return to it, so it'd be great to have the flexibility in the library instead. Also we'd like to be able to bind an arbitrary coroutine to our global root job if it does not have a parent.
I ran into a similar use-case while looking into whether a multiplatform logging MDC like construct would be possible. A version limited to CopyableThreadContextElement would be fine as long as it's available on all platforms (aka https://github.com/Kotlin/kotlinx.coroutines/issues/3326).
Hi! I ran into the need of custom factory for tracing purposes. I would really like to have something available here since it doesn't sound very complicated.