You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
How can we support debugging async Rust? As it is basically impossible to use a step debugger.
First off, the background. Async Rust has a runtime. The runtime manages a number of async tasks, called Future. Each Future is a state machine (the complex part is it is hierarchical), and it can only progress by having someone polling it. From an instruction point of view, each async function is a block of code ("closure") that can be reentered an unknown number of times. There is a context associated with this future, and our goal is to track the lifecycle of each Future, from create to drop, and every time it makes progress. Every await point is a yield point, meaning the closure returns, and the state machine may or may not have a state transition.
Static analysis already tells us whether a function is async. (it is actually quite tricky to derive this from looking at the assembly).
I think we can model the event stream in FireDBG just like regular functions, but with an additional async context pointer. At any given point in time, the pointer should uniquely identifies a Future. But we also need to hook into the Future lifecycle and record the "async context create" and "async context destroy" events. The Pin semantic ensures that once a Future is being polled, it stays in place in memory. Then we should have enough information to reconstruct an async timeline. Hierarchical async functions shares the same async context, so they can be uniquely identified by (async context, function address).
Constructing a call tree requires parent-child relation. We need to identify the "true" parent of an async function on runtime.
We have to be aware of b, c both being children of a, instead of a -> b -> c if we naively look at the sequence of events. At least in the above case, we can reconstruct the async call stack by looking at the real stack trace:
#0 func_b/c
#1 poll*
#2 tokio
#3 tokio
#4 func_a
#5 poll*
...
#9 main
It should be doable to capture the parameters of an async function on first call, but I couldn't think of a way to capture the return value (yet).
How can we support debugging async Rust? As it is basically impossible to use a step debugger.
First off, the background. Async Rust has a runtime. The runtime manages a number of async tasks, called
Future
. Each Future is a state machine (the complex part is it is hierarchical), and it can only progress by having someone polling it. From an instruction point of view, each async function is a block of code ("closure") that can be reentered an unknown number of times. There is a context associated with this future, and our goal is to track the lifecycle of each Future, from create to drop, and every time it makes progress. Every await point is a yield point, meaning the closure returns, and the state machine may or may not have a state transition.Static analysis already tells us whether a function is async. (it is actually quite tricky to derive this from looking at the assembly).
I think we can model the event stream in FireDBG just like regular functions, but with an additional async context pointer. At any given point in time, the pointer should uniquely identifies a Future. But we also need to hook into the Future lifecycle and record the "async context create" and "async context destroy" events. The
Pin
semantic ensures that once a Future is being polled, it stays in place in memory. Then we should have enough information to reconstruct an async timeline. Hierarchical async functions shares the same async context, so they can be uniquely identified by (async context, function address).Constructing a call tree requires parent-child relation. We need to identify the "true" parent of an async function on runtime.
We have to be aware of
b
,c
both being children ofa
, instead ofa -> b -> c
if we naively look at the sequence of events. At least in the above case, we can reconstruct the async call stack by looking at the real stack trace:It should be doable to capture the parameters of an async function on first call, but I couldn't think of a way to capture the return value (yet).
Reference: https://fitzgeraldnick.com/2019/08/27/async-stacks-in-rust.html
The text was updated successfully, but these errors were encountered: