question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

If change the dispatcher of the coroutine, the stack trace will be cropped

See original GitHub issue

There is a trivial case with nested calls to suspend functions. In the last of the calls, an exception is thrown, which is caught only in the start function, which launched this entire chain of suspend function calls, and the exception is sent to the log. It is argued that the stack trace in the log should be complete, since Stack trace recovery was released in 1.1 https://github.com/Kotlin/kotlinx.coroutines/issues/74 And it really seems to work, but only if you do not change the dispatcher in intermediate calls. But if you change it, then the stack stack turns out to be cut off.

Dispatcher change example:

val logger: Logger = LoggerFactory.getLogger("Main")
fun main() {
    runBlocking {
        try {
            callThrowableSuspendFun()
        } catch (ex: Exception) {
            logger.error("Error while execute", ex)
        }
    }
}
suspend fun changeContextAndThrow(): String {
    logger.info("Call function changeContextAndThrow")
    return withContext(Dispatchers.IO) {
        throw IllegalArgumentException("Throw from changeContextAndThrow")
    }
}
suspend fun callThrowableSuspendFun(): String {
    logger.info("Call function callThrowableSuspendFun")
    return changeContextAndThrow()
}

Log:

83 [main] INFO Main - Call function callThrowableSuspendFun
83 [main] INFO Main - Call function changeContextAndThrow
98 [main] ERROR Main - Error while execute
java.lang.IllegalArgumentException: Throw from changeContextAndThrow
    at MainKt$changeContextAndThrow$2.invokeSuspend(Main.kt:30)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)

An example without changing the dispatcher:

val logger: Logger = LoggerFactory.getLogger("Main")
fun main() {
    runBlocking {
        try {
            callThrowableSuspendFun()
        } catch (ex: Exception) {
            logger.error("Error while execute", ex)
        }
    }
}
suspend fun changeContextAndThrow(): String {
    logger.info("Call function changeContextAndThrow")
    return coroutineScope {
        throw IllegalArgumentException("Throw from changeContextAndThrow")
    }
}
suspend fun callThrowableSuspendFun(): String {
    logger.info("Call function callThrowableSuspendFun")
    return changeContextAndThrow()
}

Log:

132 [main] INFO Main - Call function callThrowableSuspendFun
132 [main] INFO Main - Call function changeContextAndThrow
156 [main] ERROR Main - Error while execute
java.lang.IllegalArgumentException: Throw from changeContextAndThrow
    at MainKt$changeContextAndThrow$2.invokeSuspend(Main.kt:30)
    at MainKt$changeContextAndThrow$2.invoke(Main.kt)
    at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
    at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:180)
    at MainKt.changeContextAndThrow(Main.kt:29)
    at MainKt.callThrowableSuspendFun(Main.kt:36)
    at MainKt$main$1.invokeSuspend(Main.kt:20)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:270)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:79)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:54)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:36)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
    at MainKt.main(Main.kt:18)
    at MainKt.main(Main.kt)

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
qwwdfsadcommented, Nov 15, 2019

No, for the same reasons as it cannot be done with regular threads and context switches.

Quoting already mentioned documentation:

Overhead of this feature is negligible and it can be safely turned on by default to simplify logging and diagnostic.

0reactions
gnefedevcommented, Mar 17, 2021

I wrote a workaround like this:

suspend fun <T> withIoDispatcher(block: suspend CoroutineScope.() -> T): T =
    try {
        withContext(IO, block)
    } catch (e: Throwable) {
        e.stackTrace += RuntimeException().stackTrace
        throw e
    }
Read more comments on GitHub >

github_iconTop Results From Across the Web

Kotlin Coroutines Dispatchers.IO is not creating expected ...
In each loop iteration, I'm creating a coroutine with Dispatchers.IO, But when I see the output, it is not created 1_000_000 coroutines, and...
Read more >
Android Kotlin Coroutine Best Practices | by Ken Yee - Medium
The main dispatcher (if you don't specify anything to run on) is the UI one; you should only change UI elements in this...
Read more >
Coroutine Exception Handling & Observability with Firebase
The default behavior to handle exceptions is simply to print the stack-trace but followed by the application crashing. So if you want to...
Read more >
Coroutine context and dispatchers - Kotlin
The coroutine dispatcher can confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined.
Read more >
Forget RxJava: Kotlin Coroutines are all you need. Part 1/2
Now a stack-trace comes from your crash reporting tool: ... What if Kotlin Coroutines can make our life better?
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found