Skip to content

Latest commit

 

History

History
108 lines (79 loc) · 6.23 KB

debugging.md

File metadata and controls

108 lines (79 loc) · 6.23 KB

Table of contents

Debugging coroutines

Debugging asynchronous programs is challenging, because multiple concurrent coroutines are typically working at the same time. To help with that, kotlinx.coroutines comes with additional features for debugging: debug mode, stacktrace recovery and debug agent.

Debug mode

The first debugging feature of kotlinx.coroutines is debug mode. It can be enabled either by setting system property DEBUG_PROPERTY_NAME or by running Java with enabled assertions (-ea flag). The latter is helpful to have debug mode enabled by default in unit tests.

Debug mode attaches a unique name to every launched coroutine. Coroutine name can be seen in a regular Java debugger, in a string representation of the coroutine or in the thread name executing named coroutine. Overhead of this feature is negligible and it can be safely turned on by default to simplify logging and diagnostic.

Stacktrace recovery

Stacktrace recovery is another useful feature of debug mode. It is enabled by default in the debug mode, but can be separately disabled by setting kotlinx.coroutines.stacktrace.recovery system property to false.

Stacktrace recovery tries to stitch asynchronous exception stacktrace with a stacktrace of the receiver by copying it, providing not only information where an exception was thrown, but also where it was asynchronously rethrown or caught.

It is easy to demonstrate with actual stacktraces of the same program that awaits asynchronous operation in main function (runnable code is here):

Without recovery With recovery
before after

The only downside of this approach is losing referential transparency of the exception.

Note that suppressed exceptions are not copied and are left intact in the cause in order to prevent cycles in the exceptions chain, obscure[CIRCULAR REFERENCE] messages and even crashes in some frameworks

Stacktrace recovery machinery

This section explains the inner mechanism of stacktrace recovery and can be skipped.

When an exception is rethrown between coroutines (e.g. through withContext or Deferred.await boundary), stacktrace recovery machinery tries to create a copy of the original exception (with the original exception as the cause), then rewrite stacktrace of the copy with coroutine-related stack frames (using Throwable.setStackTrace) and then throws the resulting exception instead of the original one.

Exception copy logic is straightforward:

  1. If the exception class implements CopyableThrowable, CopyableThrowable.createCopy is used. null can be returned from createCopy to opt-out specific exception from being recovered.
  2. If the exception class has class-specific fields not inherited from Throwable, the exception is not copied.
  3. Otherwise, one of the public exception's constructor is invoked reflectively with an optional initCause call.
  4. If the reflective copy has a changed message (exception constructor passed a modified message parameter to the superclass), the exception is not copied in order to preserve a human-readable message. CopyableThrowable does not have such a limitation and allows the copy to have a message different from that of the original.

Debug agent

kotlinx-coroutines-debug module provides one of the most powerful debug capabilities in kotlinx.coroutines.

This is a separate module with a JVM agent that keeps track of all alive coroutines, introspects and dumps them similar to thread dump command, additionally enhancing stacktraces with information where coroutine was created.

The full tutorial of how to use debug agent can be found in the corresponding readme.

Android optimization

In optimized (release) builds with R8 version 1.6.0 or later both Debugging mode and Stacktrace recovery are permanently turned off. For more details see "Optimization" section for Android.