Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to recover the coroutine's true call trace?

This is a painfully familiar problem to anyone working with async APIs: when your call encounters a failure, the async library's private thread detects it, creates an exception object, and passes it to your callback. The only info worth a dime in that exception is its message and possibly its type. The stack trace is worthless.

Cross-breed that with Google Play's way of reporting the app crashes: the message is stripped and all you get is the stack trace. Now you've got nothing to go with. You just know your app has a bug that you didn't detect in your own testing.

Kotlin's coroutines at least give us some hope that this could be made better. The coroutine stack trace is conceptually there, just difficult to extract. However, currently the stack traces we get are the same, useless ones I described above.

I'm familiar with the kotlinx-coroutines-debug module and I see there are some provisions on the implementation side to recreate the coroutine stack trace, but how can I make use of these facilities in a production app installed on my user's smartphone?

like image 806
Marko Topolnik Avatar asked Jan 24 '19 14:01

Marko Topolnik


People also ask

How do I get CoroutineContext?

You can find it on LeanPub. If you take a look at the coroutine builders' definitions, you will see that their first parameter is of type CoroutineContext . public fun CoroutineScope. launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.

What is default dispatcher?

The default dispatcher is used when no other dispatcher is explicitly specified in the scope. It is represented by Dispatchers. Default and uses a shared background pool of threads. newSingleThreadContext creates a thread for the coroutine to run.

What is runBlocking?

Usually, runBlocking it is used in unit tests in Android or in some other cases of synchronous code. Keep in mind that runBlocking is not recommended for production code. runBlocking builder does almost the same thing as launch builder: it creates a coroutine and calls its start function.


2 Answers

First off all the feature is broken at the moment. If the bug is fixed I would try System.setProperty(DEBUG_PROPERTY_NAME,DEBUG_PROPERTY_VALUE_ON).

like image 87
niels Avatar answered Sep 28 '22 18:09

niels


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. I called it Stacktrace-decoroutinator.

The reason why the stacktrace is lost is that when the coroutine wakes up, only the last method of its call stack is called.

My library replaces the coroutine awakening implementation. It generates classes at runtime with names that match the entire coroutine call stack. These classes don't do anything except call each other in the coroutine call stack sequence.

Thus, if the coroutine throws an exception, they mimic the real call stack of the coroutine during the creation of the exception stacktrace.

like image 41
Denis Avatar answered Sep 26 '22 18:09

Denis