withContext calls are not reflected in coroutine debug dumps
Reproducer:
@Test
fun testProbes() = runBlocking {
val b1 = CompletableDeferred<Unit>()
val b2 = CompletableDeferred<Unit>()
println("runBlocking context: ${currentCoroutineContext()}")
launch { // in the context of runBlocking
withContext(Dispatchers.IO + CoroutineName("A")) {
println("launched job context: ${currentCoroutineContext()}")
b1.complete(Unit)
b2.await()
}
}
b1.await()
println("debug probes")
println(DebugProbes.dumpCoroutinesInfo())
}
Outputs:
runBlocking context: [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@b665f0a, BlockingEventLoop@3c0fe39d]
launched job context: [CoroutineId(2), CoroutineName(A), "A#2":DispatchedCoroutine{Active}@2cd23013, Dispatchers.IO]
debug probes
[CoroutineInfo(state=RUNNING,context=[CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@b665f0a, BlockingEventLoop@3c0fe39d]), CoroutineInfo(state=SUSPENDED,context=[CoroutineId(2), "coroutine#2":StandaloneCoroutine{Active}@7ed255c1, BlockingEventLoop@3c0fe39d])]
Note that the debug info for the second coroutine does not show the effect of withContext switch.
This is likely because kotlinx.coroutines.debug.internal.DebugCoroutineInfoImpl has immutable context and it is not updated from the frame in updateState.
The issue occasionally shows up in problem investigations, wrong context in the debug probes causes confusion.
That's a long-standing debate (https://github.com/Kotlin/kotlinx.coroutines/issues/3414#issuecomment-1245246031) about whether withContext is a new coroutine or not (and, for the change, coroutineScope) and is done on purpose in the current implementation. You can see that the actual contextual data is attached to the launched job context, including the name.
We can change that behaviour, but design help might be needed -- because scoped coroutines are filtered on purpose -- in the dumps, in the text output etc., and in other places they are, on purpose, a single coroutine with their outer scope (so it's actually walkable with coroutine debugger/stackframe walker etc.).
Maybe a special note in the text dump would do?
Hmm, it may be fun to see all context switches in the stacktraces. Sketched this approach: https://gist.github.com/vsalavatov/4d609f8f163840525f3346985158400e
Looks like this:
Coroutine StandaloneCoroutine{Active}@1500b2f3, state: SUSPENDED
at DebugProbesContextKt$main$1$1$1$1.invokeSuspend(DebugProbesContext.kt:24)
at [CoroutineName(A inner), DispatchedCoroutine{Active}@93a07fa, Dispatchers.Default.limitedParallelism(1)].(DebugProbesContext.kt:21)
at DebugProbesContextKt$main$1$1$1.invokeSuspend(DebugProbesContext.kt:21)
at [CoroutineName(A), DispatchedCoroutine{Active}@78d14e01, Dispatchers.IO].(DebugProbesContext.kt:19)
at DebugProbesContextKt$main$1$1.invokeSuspend(DebugProbesContext.kt:19)kotlin.Unit