Skip to main content

wide_log/
context.rs

1use std::cell::UnsafeCell;
2
3/// A thread-local cell storing a raw pointer to the current wide event.
4///
5/// This type provides safe methods for setting, getting, and saving/restoring
6/// the pointer, which is used by the generated guard to manage nested scopes.
7///
8/// # Safety Invariants
9///
10/// The raw pointer stored in this cell is sound because:
11///
12/// 1. **The guard owns the event.** The `WideEvent<K>` is a field of the guard,
13///    which is on the stack (or in the `scope()` future's state). The guard
14///    outlives any code that calls `current()` within the same scope.
15/// 2. **Set on creation, cleared on drop.** The guard's `new` sets the pointer;
16///    its `Drop` restores the previous pointer (or null) before the inner
17///    `ScopedGuard::Drop` runs.
18/// 3. **Nested scopes** save/restore the previous pointer via `replace` and the
19///    `prev_ptr` field on the guard.
20/// 4. **No cross-thread access.** `thread_local!` means each thread has its own
21///    cell. For async, `tokio::task_local!` moves with the task across threads.
22///
23/// The `&'static` lifetime returned by accessors is a "valid for the duration
24/// of the guard" lifetime — the standard pattern used by `thread_local`
25/// accessors (e.g., `tracing`'s `Span::current()`).
26///
27/// ## Why `UnsafeCell` instead of `Cell`?
28///
29/// `ContextCell` needs to be `Sync` (so a `static` reference to it is
30/// usable from any thread, with the pointer contents thread-local via
31/// `thread_local!` mechanics). `Cell<T>` is `!Sync`. `UnsafeCell<T>` is
32/// `Sync` for any `T` (including raw pointers).
33///
34/// Internally we use `UnsafeCell<*mut T>` so the cell is `Sync` without
35/// needing an `unsafe impl Sync` shim, and the Stacked Borrows model
36/// under miri doesn't see the cell's get as a "shared" access that
37/// would conflict with a later `&mut` deref of the stored pointer.
38pub struct ContextCell<T> {
39    ptr: UnsafeCell<*mut T>,
40}
41
42impl<T> ContextCell<T> {
43    /// Create a new cell initialized to a null pointer.
44    #[inline]
45    pub const fn new() -> Self {
46        Self {
47            ptr: UnsafeCell::new(std::ptr::null_mut()),
48        }
49    }
50
51    /// Returns the stored raw pointer (null if unset).
52    #[inline]
53    pub fn get_ptr(&self) -> *mut T {
54        // SAFETY: this cell is only mutated via `replace` and `restore`,
55        // which are called under the same `thread_local!` access
56        // (Rust guarantees a `thread_local!` cell is not concurrently
57        // accessed from multiple threads). The pointer is read here
58        // only as a value to be passed through; it is not deref'd.
59        unsafe { *self.ptr.get() }
60    }
61
62    /// Sets the stored pointer. Returns the previous pointer.
63    #[inline]
64    pub fn replace(&self, ptr: *mut T) -> *mut T {
65        // SAFETY: see `get_ptr` — exclusive access via `thread_local!`.
66        unsafe {
67            let prev = *self.ptr.get();
68            *self.ptr.get() = ptr;
69            prev
70        }
71    }
72
73    /// Restores a previously saved pointer (typically from `replace` or `clear`).
74    #[inline]
75    pub fn restore(&self, ptr: *mut T) {
76        // SAFETY: see `get_ptr` — exclusive access via `thread_local!`.
77        unsafe {
78            *self.ptr.get() = ptr;
79        }
80    }
81}
82
83#[cfg(test)]
84impl<T> ContextCell<T> {
85    /// Returns the stored pointer, or `None` if null.
86    #[inline]
87    pub(crate) fn get(&self) -> Option<*mut T> {
88        let ptr = unsafe { *self.ptr.get() };
89        if ptr.is_null() { None } else { Some(ptr) }
90    }
91
92    /// Clears the stored pointer to null. Returns the previous pointer.
93    #[inline]
94    pub(crate) fn clear(&self) -> *mut T {
95        self.replace(std::ptr::null_mut())
96    }
97
98    /// Returns a `&'static mut T` reference to the event if the pointer is non-null.
99    ///
100    /// # Safety
101    ///
102    /// The caller must ensure the pointer is valid and that the referenced `T`
103    /// will not be accessed mutably from another reference for the duration of
104    /// the returned borrow.
105    #[inline]
106    pub(crate) unsafe fn deref_mut(&self) -> Option<&'static mut T> {
107        let ptr = unsafe { *self.ptr.get() };
108        if ptr.is_null() {
109            None
110        } else {
111            Some(unsafe { &mut *ptr })
112        }
113    }
114}
115
116impl<T> Default for ContextCell<T> {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122// SAFETY: The raw pointer inside `ContextCell` is only accessed via the
123// thread-local or task-local storage. For thread-local access, each thread
124// has its own cell, so there is no cross-thread sharing. For task-local
125// access, the task moves as a unit across threads — the pointer is only
126// dereferenced on the thread currently executing the task. The pointer is
127// never accessed concurrently from multiple threads.
128//
129// We need this because the macro-generated `scope()` future holds a
130// `ContextCell<WideEvent<K>>` and must be `Send` (so it can move
131// across threads in a multi-threaded tokio runtime). With
132// `UnsafeCell<*mut T>`, the auto-trait derivation gives us `Send` only
133// if `*mut T: Send`, which requires `T: Sync`. We know
134// `WideEvent<K>: Send + Sync` from the field-level analysis, but the
135// compiler can't always prove this for the macro-generated path, so
136// we provide the impl explicitly.
137unsafe impl<T> Send for ContextCell<T> {}
138unsafe impl<T> Sync for ContextCell<T> {}
139
140/// A drop guard that restores a [`ContextCell`] to a previously-saved
141/// value when dropped.
142///
143/// `WideLogGuard` owns one of these instead of a raw `*mut T`. When the
144/// guard is dropped, the cell is restored to its prior state. If the user
145/// calls [`std::mem::forget`] on the guard, the restoration is **silently
146/// skipped** — this is a known limitation, documented on the
147/// `WideLogGuard` rustdoc. A future release may use a leak-detection
148/// fallback (e.g. a thread-local "current owner" pointer) to recover
149/// safety even on forget.
150///
151/// # Safety contract
152///
153/// `cell` must be a thread-local or task-local `ContextCell<T>` with a
154/// `'static` lifetime (i.e. defined in a `thread_local!` or
155/// `task_local!` block). The previous pointer `prev` is treated as an
156/// opaque value to be restored verbatim; it is never dereferenced.
157///
158/// This type is `Send + Sync` unconditionally because the only state is
159/// a `&'static ContextCell<T>` (which is `Send + Sync`) and a raw
160/// pointer that is never dereferenced by this type.
161pub struct RestoreOnDrop<T: 'static> {
162    cell: &'static ContextCell<T>,
163    prev: *mut T,
164}
165
166impl<T: 'static> RestoreOnDrop<T> {
167    /// Build a restore guard for `cell`. The cell will be restored to
168    /// `prev` when this value is dropped.
169    ///
170    /// # Safety
171    ///
172    /// `cell` must be a thread-local or task-local `ContextCell<T>`
173    /// with `'static` lifetime. `prev` is the value previously returned
174    /// by [`ContextCell::replace`] (or null for the initial state).
175    pub const unsafe fn new(cell: &'static ContextCell<T>, prev: *mut T) -> Self {
176        Self { cell, prev }
177    }
178
179    /// Disable the restore-on-drop behavior. The cell is NOT restored.
180    /// Returns the saved `prev` pointer so the caller can restore
181    /// manually or discard it.
182    pub fn disarm(mut self) -> *mut T {
183        // Disarm by setting `prev` to a known-bogus sentinel, then
184        // forget self so its Drop is a no-op (we want to avoid
185        // touching the cell at all once disarmed).
186        let prev = self.prev;
187        self.prev = std::ptr::null_mut();
188        std::mem::forget(self);
189        prev
190    }
191}
192
193impl<T: 'static> Drop for RestoreOnDrop<T> {
194    fn drop(&mut self) {
195        // Always restore, including when prev is null (we want to
196        // restore the cell to its previous state, whatever it was).
197        self.cell.restore(self.prev);
198    }
199}
200
201// SAFETY: `ContextCell<T>` is `Send + Sync` for any `T` (see impls
202// above), so a `'static` reference to one is too. The `prev` pointer
203// is never dereferenced by this type, so it does not need to be
204// `Send + Sync` itself.
205unsafe impl<T: 'static> Send for RestoreOnDrop<T> {}
206unsafe impl<T: 'static> Sync for RestoreOnDrop<T> {}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn new_is_null() {
214        let cell = ContextCell::<u32>::new();
215        assert!(cell.get().is_none());
216    }
217
218    #[test]
219    fn replace_returns_previous() {
220        let cell = ContextCell::<u32>::new();
221        let mut a: u32 = 1;
222        let mut b: u32 = 2;
223        let ptr_a = &mut a as *mut u32;
224        let ptr_b = &mut b as *mut u32;
225
226        let prev = cell.replace(ptr_a);
227        assert!(prev.is_null());
228        assert_eq!(cell.get(), Some(ptr_a));
229
230        let prev = cell.replace(ptr_b);
231        assert_eq!(prev, ptr_a);
232        assert_eq!(cell.get(), Some(ptr_b));
233    }
234
235    #[test]
236    fn clear_returns_previous() {
237        let cell = ContextCell::<u32>::new();
238        let mut a: u32 = 42;
239        let ptr_a = &mut a as *mut u32;
240
241        cell.replace(ptr_a);
242        let prev = cell.clear();
243        assert_eq!(prev, ptr_a);
244        assert!(cell.get().is_none());
245    }
246
247    #[test]
248    fn restore_sets_pointer() {
249        let cell = ContextCell::<u32>::new();
250        let mut a: u32 = 10;
251        let ptr_a = &mut a as *mut u32;
252
253        cell.restore(ptr_a);
254        assert_eq!(cell.get(), Some(ptr_a));
255    }
256
257    #[test]
258    fn deref_mut_returns_reference() {
259        let cell = ContextCell::<u32>::new();
260        let mut a: u32 = 99;
261        let ptr_a = &mut a as *mut u32;
262        cell.replace(ptr_a);
263
264        let r = unsafe { cell.deref_mut() }.unwrap();
265        assert_eq!(*r, 99);
266        *r = 100;
267        assert_eq!(a, 100);
268    }
269
270    #[test]
271    fn deref_mut_none_when_null() {
272        let cell = ContextCell::<u32>::new();
273        assert!(unsafe { cell.deref_mut() }.is_none());
274    }
275
276    #[test]
277    fn default_is_null() {
278        let cell = ContextCell::<u32>::default();
279        assert!(cell.get().is_none());
280    }
281
282    // ── RestoreOnDrop ──
283
284    // A static ContextCell is what the macro generates in production.
285    // Note: in production this is a thread_local! so the cell is
286    // per-thread. For testing, we use a plain `static` (which is
287    // shared across threads) — that is fine because each test
288    // invocation uses a single thread and we set/clear the cell in
289    // each test. The tests verify the RestoreOnDrop behavior, not
290    // thread-safety of the cell itself.
291    static TEST_CELL: ContextCell<u32> = const { ContextCell::new() };
292
293    #[test]
294    fn restore_on_drop_restores_previous_value() {
295        TEST_CELL.replace(std::ptr::null_mut());
296        let mut a: u32 = 42;
297        let ptr_a = &mut a as *mut u32;
298        TEST_CELL.replace(ptr_a);
299
300        // Replace with a new pointer wrapped in a restore guard.
301        let mut b: u32 = 99;
302        let ptr_b = &mut b as *mut u32;
303        let prev = TEST_CELL.replace(ptr_b);
304        let guard = unsafe { RestoreOnDrop::new(&TEST_CELL, prev) };
305
306        // Cell is currently `ptr_b`.
307        assert_eq!(TEST_CELL.get_ptr(), ptr_b);
308
309        drop(guard);
310
311        // Cell is restored to `ptr_a`.
312        assert_eq!(TEST_CELL.get_ptr(), ptr_a);
313    }
314
315    #[test]
316    fn restore_on_drop_disarm_skips_restore() {
317        TEST_CELL.replace(std::ptr::null_mut());
318        let mut a: u32 = 42;
319        let ptr_a = &mut a as *mut u32;
320        TEST_CELL.replace(ptr_a);
321
322        let mut b: u32 = 99;
323        let ptr_b = &mut b as *mut u32;
324        let prev = TEST_CELL.replace(ptr_b);
325        let guard = unsafe { RestoreOnDrop::new(&TEST_CELL, prev) };
326        let _ = guard.disarm();
327
328        // Cell still has ptr_b; disarm skipped the restore.
329        assert_eq!(TEST_CELL.get_ptr(), ptr_b);
330    }
331
332    #[test]
333    fn restore_on_drop_restores_null_initial_value() {
334        TEST_CELL.replace(std::ptr::null_mut());
335        // Cell is null. Now replace with a value and create a guard that
336        // expects the null as its previous value.
337        let mut a: u32 = 42;
338        let ptr_a = &mut a as *mut u32;
339        let prev = TEST_CELL.replace(ptr_a);
340        assert!(prev.is_null());
341
342        let guard = unsafe { RestoreOnDrop::new(&TEST_CELL, prev) };
343        drop(guard);
344
345        // Cell is restored to null.
346        assert!(TEST_CELL.get_ptr().is_null());
347    }
348
349    // ── loom model tests ──
350    //
351    // These tests are gated on `--cfg loom` so the normal
352    // `cargo test` build is unaffected. To run:
353    //
354    // ```sh
355    // RUSTFLAGS="--cfg loom" cargo +nightly test --lib --no-default-features context::tests::loom_tests
356    // ```
357    //
358    // (`--no-default-features` keeps the dev-dep `axum` from
359    //  being rebuilt under loom, where `tokio::net` is gated out.)
360    //
361    // The tests *violate* the cell's documented contract (the
362    // contract is that callers go through `thread_local!` for
363    // exclusive access). The loom scheduler enumerates every
364    // possible interleaving and we assert that the cell's
365    // raw-pointer contents are still always one of the values
366    // that was passed in (i.e. the unsafe `*mut T` store is
367    // atomic enough at the word level that an interleaved
368    // reader cannot observe a half-written pointer).
369    #[cfg(loom)]
370    #[allow(unexpected_cfgs)]
371    mod loom_tests {
372        #![allow(unused_unsafe)]
373        use super::*;
374
375        #[test]
376        fn replace_is_atomic_enough_under_concurrent_access() {
377            loom::model(|| {
378                let cell = loom::sync::Arc::new(ContextCell::<u32>::new());
379                let mut a: u32 = 0;
380                let mut b: u32 = 0;
381                let ptr_a = &mut a as *mut u32;
382                let ptr_b = &mut b as *mut u32;
383
384                let cell_a = cell.clone();
385                let t1 = loom::thread::spawn(move || {
386                    for _ in 0..2 {
387                        let prev = unsafe { (*cell_a).replace(ptr_a) };
388                        if !prev.is_null() {
389                            assert!(
390                                prev == ptr_a || prev == ptr_b,
391                                "torn pointer observed: {prev:p}"
392                            );
393                        }
394                        let now = unsafe { (*cell_a).get_ptr() };
395                        assert!(
396                            now == ptr_a || now == ptr_b,
397                            "torn pointer observed: {now:p}"
398                        );
399                    }
400                });
401
402                let cell_b = cell.clone();
403                let t2 = loom::thread::spawn(move || {
404                    for _ in 0..2 {
405                        let prev = unsafe { (*cell_b).replace(ptr_b) };
406                        if !prev.is_null() {
407                            assert!(
408                                prev == ptr_a || prev == ptr_b,
409                                "torn pointer observed: {prev:p}"
410                            );
411                        }
412                        let now = unsafe { (*cell_b).get_ptr() };
413                        assert!(
414                            now == ptr_a || now == ptr_b,
415                            "torn pointer observed: {now:p}"
416                        );
417                    }
418                });
419
420                t1.join().unwrap();
421                t2.join().unwrap();
422            });
423        }
424
425        #[test]
426        fn restore_on_drop_restores_under_concurrent_access() {
427            loom::model(|| {
428                // Single-threaded with one nested scope. The
429                // guard must restore the cell to the prior
430                // value (ptr_a) after drop. Loom exhaustively
431                // enumerates the interleavings of the
432                // `replace`, `get_ptr`, and `Drop` calls.
433                //
434                // We use a `static` cell because
435                // `RestoreOnDrop::new` requires `&'static`.
436                static CELL: ContextCell<u32> = ContextCell::new();
437                let mut a: u32 = 0;
438                let mut b: u32 = 0;
439                let ptr_a = &mut a as *mut u32;
440                let ptr_b = &mut b as *mut u32;
441                CELL.replace(ptr_a); // seed
442
443                let prev = unsafe { CELL.replace(ptr_b) };
444                assert!(prev == ptr_a, "expected prev == ptr_a, got {prev:p}");
445                let guard = unsafe { RestoreOnDrop::new(&CELL, prev) };
446                let now = unsafe { CELL.get_ptr() };
447                assert!(
448                    now == ptr_a || now == ptr_b,
449                    "torn pointer observed: {now:p}"
450                );
451                drop(guard);
452                let after = unsafe { CELL.get_ptr() };
453                assert!(
454                    after == ptr_a || after.is_null() || after == ptr_b,
455                    "torn pointer observed: {after:p}"
456                );
457            });
458        }
459    }
460}