# Async backtraces in Tokio To get async functions to show in the backtrace, they must be explicitly annotated: ```rust #[tokio::instrument] async fn my_fn() { // code } ``` The `instrument` macro expands to something like: ```rust async fn my_fn() { async fn inner() { } tokio::foo::instrument(location_info!(), my_fn()).await } ``` The `instrument` function takes a future and decorates it with logic that builds up a "backtrace" of ".await" points. The backtrace is an intrusive linked list with the location info stored in the `Instrument<T>` future, which ends up getting stored in the generator itself. This avoids having to allocate anything at runtime. The intrusive linked list implementation already exists in Tokio: [linked_list.rs](https://github.com/tokio-rs/tokio/blob/master/tokio/src/util/linked_list.rs). An example of head/tail pointers is [here](https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/io/scheduled_io.rs#L31) and an example nodes is [here](https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/io/scheduled_io.rs#L80). Look at these types to learn more about how to use it. For async backtraces, the head/tail pointers will be stored in the Task structure, probably the [trailer](https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/task/core.rs#L36), though it would be good for an initial implementation to avoid updating the task structure (see below). The nodes will be embedded in the `Instrument` struct return by the `instrument()` utility method. Note, the pseudocode omits unsafe code and structs like `UnsafeCell`. ```rust struct Instrument<T> { future: T, location: Location, } struct Location { // Data necessary for rendering the backtrace ... // Intrusive linked-list pointers pointers: linked_list::Pointers<Self>, // Should never be `!Unpin`. _p: PhantomPinned, } ``` ## Initial implementation The initial implementation should be in a standalone crate. This will allow faster experimentation and iteration at the cost of some performance. As the standalone implementation matures, we can merge it into Tokio proper Let's call the crate `async-backtrace` for now. The user will need to decorate all spawned tasks so the crate can establish the backtrce "root". I would model Tokio's [task builder](https://docs.rs/tokio/latest/tokio/task/struct.Builder.html) for this. ```rust= async_backtrace::Task::builder() // take &'static str, this probably should be the "task type name" .name("foo") .spawn(async { ... }) ``` And for instrumentation: ```rust #[async_backtrace::instrument] async fn foo() { } ``` For the initial implementation, I would probably just create a global static structure that holds all the in-flight tasks. Something like: ```rust= struct Task { name: &'static str, waiters: Mutex<Waiters>, } // Something like... static TASKS: Mutex<HashSet<Arc<Task>>>; ``` When a task is spawned, create an `Arc<Task>` at the root of the Tokio task. Use [TaskLocal](https://docs.rs/tokio/latest/tokio/macro.task_local.html) to set the current `Arc<Task>`. Then use that thread-local when pushing/popping a new frame onto the async backtrace. ### Dumping backtraces Initially, you probably want to do something like the JVM here. Register a signal handler and when the signal is received, lock the global `TASKS` structure, walk all tasks, and render the backtrace to standard err (or out? whatever the JVM does).