-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Stack trace recovery #74
Comments
This is indeed a known issue on our plate. Thank you for starting the thread, though. I'm curious what kind of stack-track you would personally prefer to see in this case? |
I want to see pointers to actual code lines with function calls. In my example above, the pointer to the line with call of |
One more thing. I think the problem is critical for Kotlin coroutines project: non-informative stack traces make debugging of coroutines too expensive as well as supporting of a program that uses coroutines. From my point of view the main goals of coroutines is to make asynchronous programming both easier and cheaper. But with expensive debugging this goal cannot be achieved. |
It is indeed important. Kotlin coroutines are already easier than alternative async solutions which are all notoriously known for their hard-to-decipher stack traces, but we can also make Kotlin coroutines stack traces better. It is not a straightforward thing to do on JVM and will likely come at a considerable performance penalty (to be paid when exception is being propagated though coroutine), so the current idea is to make it available under a debug system property. |
I think in the beginning the cost does not matter, especially that it will be optional. |
The serious thing is the main func immediately exit, when some func throw exception! Example Codes:
OutPuts:
|
This is a typical thing really ugly to do, but very nice to have. Manipulate stack trace looks hard, perhaps a cheaper way is to improve the current coroutine debugging facilities and provide this information only when Idea for stack trace:
|
I reply myself: we cannot replace the original exception with a new one for many reason: primary the Exception constructor is not public. Moreover using cause exception looks wrong. May be a good idea to annotate correlate exceptions (the |
@molixiaoge |
@fvasco There are two basic approach to fix stack-traces in the example presented by @wetnose: One approach is to simply modify the stack trace in exceptions that are thrown from inside of coroutines. This could be done by a byte-code modification debug agent that you would attach to your JVM for better debugging of coroutines and would generally incur additional cost only when exceptions are being thrown. We could do anything we want, e.g, for example, keep some nice dividers showing both the actual Java stack and the coroutine stack (that still lives on the heap) The other approach is to provide some "debug" mode for Kotlin compiler that would emit byte code in such a way, that Java stack of a coroutine is fully restored on coroutine resume (similar to the way Quasar does it). However, this would make resumption of any coroutine a more eager, and thus more costly, procedure. |
Regarding the second option: is it possible to produce the required bytecode but use a flag to enable it? @elizarov Thanks for clarification, both looks really better solutions. |
That would considerable increase byte code size (or so it seems now). I'd rathe produce minimal byte code, but have a byte-code instrumenting agent that adjusts for better stack-traces in debug mode... That is all just stipulation so far and does require research. |
@elizarov, the 2nd solution allows us to analyze stack traces from logs as well, so I prefer it. |
@wetnose The first, too. It does not really matter. The end result is that exceptions will have the correct stack traces (how they got them is an implementation detail) |
@elizarov, is this feature already planned to be included in an incoming lib version? |
I suspected it, it is the same case of debug symbols and probably configure a flag to turn in on/off is the simplest part of the work. Now Quasar use live instrumentation or AOT instrumentation, so it covers both scenarios. |
@wetnose We are planning to work on the coroutine status & stack-trace recovery during this summer. Stay tuned. |
Here is an update on this issue. Under the most recent version of Kotlin compiler (1.1.4-eap-33) the topic-starter's example code produces the following stack-trace:
As you can see, named suspending functions now execute directly in the the correspondingly named functions on JVM, so the top of the exception stack-trace directly refers to the name of the corresponding suspending function that threw exception in the source code. |
I'm closing this particular issue since 1.1.4 was just released |
I mean, this recovers a little bit of the data, but in terms of the instrimentation solution we were talking about this doesn't even come close... consider: //this is line 100 in my test suite
@Test fun `entry-point`() = runBlocking {
PlatformImpl.startup{}
val event = 1
val result = run(JavaFx){
doUIWork(event)
}
println("got $result!")
}
suspend fun doUIWork(event: Int): Int{
val value = event
return run(CommonPool){
doHeavyWork(value)
}
}
suspend fun doHeavyWork(value: Int): Int{
val innerVal = value
val result = run(BlockableThreadPool.asCoroutineDispatcher()){
doIOWork(innerVal)
}
return result
}
suspend fun doIOWork(value: Int): Int = TODO("value + 1") outputs
when it should output something resembling
notice I'm saying "at Thread.run at suspendCoroutineOrReturn" which is where I'm getting a little inventive. It could instead use the standard |
Unfortunately, that's way too expensive to capture a stack-trace on every Meanwhile, though instrumentation we are tackling a subset of this problem. In particular, stitching various context switched that happen inside the coroutine (the first parts of your desired stack). |
How about this one? fun main(args : Array<String>) {
runBlocking {
getData()
}
}
private suspend fun getData() {
suspendCoroutine<Unit> { continuation ->
thread {
continuation.resumeWithException(Exception("Test Exception"))
}
}
} Running this code will produce this exception:
This exception only works if This is especially frustrating when coroutines is wrapped around 3rd party library such as Google Play services's
This is helpful if developer can reproduce the crash in the IDE. But more often than not crashes arrive via crash reporting tools where developer can only see the exception. |
I concur with the previous post - to be of any help, the stacktrace needs to include the point where the Is there no information about the original stack that can be attached to the exception when |
What's the status? (The PR was closed without a merge) |
@SUPERCILEX It will be released in 1.1 |
Looks like the label needs to be updated then 🙂 |
Any progress on that. 1.3.50 gives stack trace as bad as it was. |
@yaitskov What is your use-case? The issue here was fixed a while ago. If you have something else, please report. Also, make sure you actually enable stack trace recovery: https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/debugging.md |
My stack trace looks same with -ea or kotlinx.coroutines.stacktrace.recovery.
I tried to pass garbage in argLine like -ae then java complains. jdk1.8.0_211
|
That's what I get on your examples. All the line numbers are correct:
I'm running the latest versions:
I've tried with older versions, but I still cannot reproduce your problem. Can you share a self-contained maven project, please? |
I applied the question.
Test result is that
"Calling Attach a link with a similar problem |
Yes. Tail calls to suspending functions are removed from stacktraces. We might consider recovering them in the future, but it is not in our near term plans. |
I faced the same problem. The Kotlin coroutine debug library didn't help me in any way. Therefore, after studying the implementation of coroutines, I wrote my own solution based on bytecode generation and MethodHandle API. It supports JVM 1.8 and Android API 26 or higher. |
Look at the example below
The execution of the code results to an exception with the incomplete stack trace.
The stack is starting from the bridge function and completes with the last continuation. All the intermediate calls are missing.
The problem makes it difficult to debug the app because it is not possible to determine the true continuation caller. The stack trace of an exception, thrown from a coroutine, becomes useless.
The text was updated successfully, but these errors were encountered: