Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide an API to recover stacktrace of the exception when it does not cross coroutine boundary #2607

Open
qwwdfsad opened this issue Mar 24, 2021 · 1 comment

Comments

@qwwdfsad
Copy link
Member

kotlinx.coroutines provides a stacktrace recovery machinery in order to provide complete stacktraces that preserve information about all suspended functions in the stracktrace.

But the exception recovery only works through coroutine boundaries.

For example, the following code:

suspend fun foo() {
    yield() // Suspend
    bar()
    yield()
}

suspend fun bar() {
    yield() // Suspend
    throw IllegalStateException()
}

runBlocking { // Could've been 'launch' or any other coroutine builder
    foo()
}

will be missing foo function from the stacktrace because IllegalStateException does not cross any suspendable coroutine boundaries and stacktrace recovery machinery don't have a chance to kick in.

Instead, we could've provided a new top-level suspend function that recovers the stacktrace. Library authors and application developers could've been using that along with stacktrace-sensitive exceptions and/or "expected" failures.

There is a lot of shape for such API, e.g. throw IllegalStateException().recoverStacktrace() or

recovering {
    require(myArg >= 1, "Expected myArg to be >= 1")
}

but the biggest question of this issue is whether it is useful for other developers.
Opinions regarding usefulness of such API are welcomed

@dovchinnikov
Copy link
Contributor

In IJ we often log an error trace and proceed without throwing, this would be a huge help to have the ability to recover full trace.

We'd like to obtain “trace when caught” at the point of “trace when thrown” in the following example:

package com.foo.bar

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.seconds

fun main() {
  runBlocking {
    try {
      withContext(Dispatchers.Default) {
        delay(1.seconds)
        doWork()
      }
    }
    catch (t: Throwable) {
      println("trace when caught:")
      t.printStackTrace(System.out)
    }
  }
}

private fun doWork(): Int {
  val ise = IllegalStateException("Internal invariant failed")
  println("trace when thrown:")
  ise.printStackTrace(System.out)
  throw ise
}
trace when thrown:
java.lang.IllegalStateException: Internal invariant failed
	at com.foo.bar.RecoveryExampleKt.doWork(recoveryExample.kt:26)
	at com.foo.bar.RecoveryExampleKt.access$doWork(recoveryExample.kt:1)
	at com.foo.bar.RecoveryExampleKt$main$1$1.invokeSuspend(recoveryExample.kt:15)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
trace when caught:
java.lang.IllegalStateException: Internal invariant failed
	at com.foo.bar.RecoveryExampleKt.doWork(recoveryExample.kt:26)
	at com.foo.bar.RecoveryExampleKt.access$doWork(recoveryExample.kt:1)
	at com.foo.bar.RecoveryExampleKt$main$1$1.invokeSuspend(recoveryExample.kt:15)
	at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:46)
	at com.foo.bar.RecoveryExampleKt$main$1.invokeSuspend(recoveryExample.kt:13)
Caused by: java.lang.IllegalStateException: Internal invariant failed
	at com.foo.bar.RecoveryExampleKt.doWork(recoveryExample.kt:26)
	at com.foo.bar.RecoveryExampleKt.access$doWork(recoveryExample.kt:1)
	at com.foo.bar.RecoveryExampleKt$main$1$1.invokeSuspend(recoveryExample.kt:15)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants