1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use Future;
use Pin;
use ;
use crateContextSnapshot;
use cratestorage;
/// A `Future` wrapper that carries a [`ContextSnapshot`] and installs it into
/// thread-local storage on every `poll()`. This is the **runtime-agnostic**
/// mechanism for propagating context across async boundaries — it works with
/// any executor (Tokio, async-std, smol, etc.) because it relies only on
/// thread-local storage, not runtime-specific task-locals.
///
/// # How it works
///
/// On each `poll()`:
/// 1. The captured snapshot is pushed onto the thread-local context stack as a
/// new scope, using [`force_thread_local`](crate::force_thread_local) to
/// bypass any Tokio task-local dispatch.
/// 2. The inner future is polled. Because `force_thread_local` sets a
/// thread-local depth counter (`FORCE_THREAD_LOCAL_DEPTH > 0`), **all** code
/// executed during this poll — including `get_context`, `set_context`, and
/// any regular async functions reached via `.await` — is routed to thread-local
/// storage automatically. No special wrappers are needed in inner code.
/// 3. Any mutations made during polling are saved back to the snapshot so that
/// state persists across `.await` suspension points.
/// 4. The pushed scope is popped, restoring the thread-local to its prior state.
///
/// Because `poll()` always runs on the OS thread currently executing the task,
/// and we set up / tear down thread-local around each poll, context effectively
/// follows the task across thread migrations.
///
/// # Why inner async functions just work
///
/// A common concern is whether regular async functions (returning plain `Future`,
/// not `ContextFuture`) will see the context when `.await`ed inside a
/// `ContextFuture`. The answer is **yes** — here's why:
///
/// When `ContextFuture::poll()` is called by the executor, it calls
/// `force_thread_local(|| { ... })`, which increments the thread-local
/// `FORCE_THREAD_LOCAL_DEPTH` counter. This counter stays > 0 for the entire
/// duration of the poll. The context dispatch function (`with_current_cell`)
/// checks this counter first — if > 0, it skips task-local lookup and goes
/// straight to thread-local storage. Since the snapshot has been installed in
/// thread-local, all `get_context`/`set_context` calls during the poll will
/// find the correct values.
///
/// When the inner future `.await`s a sub-future that returns `Pending`:
/// 1. The sub-future's `poll` returns `Pending`.
/// 2. The async block (inner future) also returns `Pending`.
/// 3. `ContextFuture::poll` saves mutations back to the snapshot and pops the
/// scope. The depth counter goes back to 0.
/// 4. On the next poll (possibly on a different thread), `ContextFuture::poll`
/// repeats the whole setup — re-installs snapshot, increments depth, polls
/// the inner future, which resumes where it left off.
///
/// This means context is correctly propagated regardless of how many `.await`
/// points exist, how many regular futures are chained, or how many times the
/// task migrates between threads.
///
/// # Comparison with Tokio `with_context`
///
/// | | `with_context` (Tokio) | `ContextFuture` (any runtime) |
/// |---|---|---|
/// | **Runtime** | Tokio only | Any executor |
/// | **Mechanism** | `tokio::task_local!` | Thread-local + poll-wrapper |
/// | **Feature flag** | *(always available)* | `context-future` |
/// | **Inner code needs wrappers?** | No | No |
/// | **Overhead per poll** | None (task-local is persistent) | O(N) snapshot install/teardown |
///
/// # Example
///
/// ```rust,ignore
/// use dcontext::{register, set_context, get_context, with_context_future};
///
/// register::<TraceId>("trace_id");
/// set_context("trace_id", TraceId("abc".into()));
///
/// // Wrap the top-level future — all inner .await chains see context
/// let fut = with_context_future(async {
/// // Direct access — no force_thread_local needed
/// let t: TraceId = get_context("trace_id");
///
/// // Regular async functions also see context automatically
/// let result = some_regular_async_fn().await;
/// });
/// ```
// SAFETY: ContextFuture is Send if the inner future is Send.
// The snapshot is always Send (it uses Arc<HashMap<...>> of Send values).
unsafe
/// Push a scope with snapshot values onto the thread-local stack.
/// Returns a ScopeGuard that pops on drop.
/// Capture the current **thread-local** context and wrap a future so it carries
/// that context through any async executor. This is the runtime-agnostic
/// alternative to [`with_context`](crate::with_context) (which requires Tokio).
///
/// Uses `force_thread_local` internally to snapshot from thread-local storage,
/// since `ContextFuture` operates entirely on thread-local state. If you have
/// context in a Tokio task-local and want to bridge to `ContextFuture`, call
/// [`snapshot()`](crate::snapshot) yourself and use [`ContextFuture::new`].
///
/// # Example
///
/// ```rust,ignore
/// use dcontext::{register, set_context, get_context, with_context_future};
///
/// register::<MyTraceId>("trace_id");
/// set_context("trace_id", MyTraceId("abc".into()));
///
/// // Works with any executor
/// let fut = with_context_future(async {
/// let tid: MyTraceId = get_context("trace_id");
/// assert_eq!(tid.0, "abc");
/// });
/// ```