async_reify/lib.rs
1#![deny(unsafe_code)]
2
3//! Instrument `async` functions to extract their continuation structure
4//! as an inspectable, serializable step graph.
5//!
6//! `async fn` in Rust compiles down to a state machine that the executor
7//! polls. Most of the time you do not need to think about that, but when
8//! something is mysteriously slow, hangs, or stalls, you would really
9//! like to see the shape of what is happening: which await points were
10//! hit, in what order, how often each was polled, and whether each one
11//! returned `Ready` or `Pending`.
12//!
13//! This crate gives you a low-friction way to capture that. Wrap a future
14//! in [`TracedFuture`] (or sprinkle [`LabeledFuture`] across specific
15//! await points), drive it to completion, and you get a [`Trace`] of
16//! [`PollEvent`]s. Pass that to [`reify_execution`] for an
17//! [`AsyncStepGraph`] grouped by label, and [`to_dot`] to render it as
18//! Graphviz.
19//!
20//! With the `serde` feature, [`PollEvent`], [`Trace`], and
21//! [`AsyncStepGraph`] all serialize cleanly (timestamps are stored as
22//! [`Duration`](std::time::Duration) since trace start, so traces are
23//! deterministic and portable).
24//!
25//! With the `macros` feature, the [`macro@trace_async`] attribute proc
26//! macro (re-exported from
27//! [`async-reify-macros`](https://docs.rs/async-reify-macros)) rewrites
28//! every `.await` in a function body into a [`LabeledFuture`] so you do
29//! not have to label each one by hand. Labels look like
30//! `"<expr> @ file.rs:42"` for easy navigation back to the source.
31//!
32//! # Workflow
33//!
34//! 1. Wrap futures in [`TracedFuture`] (or use [`LabeledFuture`] /
35//! `#[trace_async]` for finer-grained labels).
36//! 2. Drive them to completion. They behave like ordinary futures; the
37//! only side effect is recording a [`PollEvent`] every time they are
38//! polled.
39//! 3. Convert the [`Trace`] to an [`AsyncStepGraph`] with
40//! [`reify_execution`].
41//! 4. Inspect, serialize, or render. [`to_dot`] outputs Graphviz DOT.
42//!
43//! See the [`trace_workflow` example][example] for an end-to-end run, and
44//! [`docs/phase4-async-reify.md`][phase4] for the design choices (why
45//! `Arc<Mutex<...>>` for shared logging, why label-based step grouping,
46//! why DOT rather than full deterministic replay).
47//!
48//! [example]: https://github.com/joshburgess/reify-reflect/blob/main/async-reify/examples/trace_workflow.rs
49//! [phase4]: https://github.com/joshburgess/reify-reflect/blob/main/docs/phase4-async-reify.md
50//!
51//! # Examples
52//!
53//! ```
54//! use async_reify::{TracedFuture, PollResult};
55//!
56//! # tokio_test::block_on(async {
57//! let (result, trace) = TracedFuture::run(async { 42 }).await;
58//! assert_eq!(result, 42);
59//! assert!(!trace.events.is_empty());
60//! assert!(matches!(trace.events.last().unwrap().result, PollResult::Ready));
61//! # });
62//! ```
63
64mod graph;
65mod labeled;
66mod traced;
67
68pub use graph::{reify_execution, to_dot, AsyncStepGraph, StepNode, StepOutcome};
69pub use labeled::LabeledFuture;
70pub use traced::{PollEvent, PollResult, Trace, TracedFuture};
71
72/// Attribute proc macro that rewrites every `.await` in an async function
73/// body into a [`LabeledFuture`] recording into a shared [`Trace`].
74///
75/// Re-exported from `async-reify-macros` when the `macros` feature is
76/// enabled.
77#[cfg(feature = "macros")]
78pub use async_reify_macros::trace_async;