1414
1515use std:: cell:: RefCell ;
1616use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
17+ use std:: sync:: Arc ;
1718
1819static NEXT_TRACE_ID : AtomicUsize = AtomicUsize :: new ( 0 ) ;
1920
@@ -26,19 +27,22 @@ pub struct TraceInfo {
2627 pub message : String ,
2728}
2829
30+ struct TraceNode {
31+ info : TraceInfo ,
32+ parent : Option < Arc < TraceNode > > ,
33+ }
34+
2935thread_local ! {
30- static TRACE_STACK : RefCell <Vec < TraceInfo >> = const { RefCell :: new( Vec :: new ( ) ) } ;
36+ static TRACE_STACK : RefCell <Option < Arc < TraceNode >>> = const { RefCell :: new( None ) } ;
3137}
3238
3339/// RAII guard to manage the push and pop of trace information.
3440///
35- /// This struct is `!Send` and `!Sync` to prevent it from being held across
36- /// `.await` points in async tests, which would cause incorrect trace tracking
37- /// if the task moves between threads.
41+ /// This struct is `Send` to allow it to be used in async tests, but it should
42+ /// not be held across `.await` points directly unless the future is instrumented.
3843#[ doc( hidden) ]
3944pub struct ScopedTraceGuard {
4045 id : usize ,
41- _phantom : std:: marker:: PhantomData < * mut ( ) > ,
4246}
4347
4448impl ScopedTraceGuard {
@@ -47,32 +51,119 @@ impl ScopedTraceGuard {
4751 pub fn new ( message : String ) -> Self {
4852 let caller = std:: panic:: Location :: caller ( ) ;
4953 let id = NEXT_TRACE_ID . fetch_add ( 1 , Ordering :: Relaxed ) ;
54+ let info = TraceInfo { id, file : caller. file ( ) , line : caller. line ( ) , message } ;
55+
5056 TRACE_STACK . with ( |stack| {
51- // Use try_borrow_mut to avoid double panic if called during unwinding.
5257 if let Ok ( mut s) = stack. try_borrow_mut ( ) {
53- s. push ( TraceInfo { id, file : caller. file ( ) , line : caller. line ( ) , message } ) ;
58+ let prev = s. clone ( ) ;
59+ * s = Some ( Arc :: new ( TraceNode { info, parent : prev } ) ) ;
5460 }
5561 } ) ;
56- Self { id, _phantom : std :: marker :: PhantomData }
62+ Self { id }
5763 }
5864}
5965
6066impl Drop for ScopedTraceGuard {
6167 fn drop ( & mut self ) {
6268 TRACE_STACK . with ( |stack| {
63- // Use try_borrow_mut to avoid double panic if called during unwinding.
6469 if let Ok ( mut s) = stack. try_borrow_mut ( ) {
65- if let Some ( pos) = s. iter ( ) . rposition ( |t| t. id == self . id ) {
66- s. remove ( pos) ;
67- }
70+ * s = remove_from_list ( s. clone ( ) , self . id ) ;
6871 }
6972 } ) ;
7073 }
7174}
7275
76+ /// Removes a node with the given `id` from the trace stack list.
77+ ///
78+ /// Because the trace stack is structured as a persistent, reverse-linked list
79+ /// pointing to parent nodes, removing a node from the middle involves rebuilding
80+ /// the chain from the removed node up to the current head.
81+ fn remove_from_list ( head : Option < Arc < TraceNode > > , id : usize ) -> Option < Arc < TraceNode > > {
82+ let head = head?;
83+ if head. info . id == id {
84+ return head. parent . clone ( ) ;
85+ }
86+
87+ let mut current = head. parent . clone ( ) ;
88+ let mut nodes_to_recreate = vec ! [ head. info. clone( ) ] ;
89+
90+ while let Some ( node) = current {
91+ if node. info . id == id {
92+ let mut new_head = node. parent . clone ( ) ;
93+ for info in nodes_to_recreate. into_iter ( ) . rev ( ) {
94+ new_head = Some ( Arc :: new ( TraceNode { info, parent : new_head } ) ) ;
95+ }
96+ return new_head;
97+ }
98+ nodes_to_recreate. push ( node. info . clone ( ) ) ;
99+ current = node. parent . clone ( ) ;
100+ }
101+ Some ( head)
102+ }
103+
73104/// Retrieves a clone of the current thread's trace stack.
74105pub fn get_scoped_traces ( ) -> Vec < TraceInfo > {
75- TRACE_STACK . with ( |stack| stack. try_borrow ( ) . map ( |s| s. clone ( ) ) . unwrap_or_default ( ) )
106+ let mut traces = Vec :: new ( ) ;
107+ let mut current = TRACE_STACK . with ( |stack| stack. try_borrow ( ) . ok ( ) . and_then ( |s| s. clone ( ) ) ) ;
108+ while let Some ( node) = current {
109+ traces. push ( node. info . clone ( ) ) ;
110+ current = node. parent . clone ( ) ;
111+ }
112+ traces. reverse ( ) ;
113+ traces
114+ }
115+
116+ /// A future that instruments another future with a set of traces.
117+ pub struct InstrumentedFuture < F > {
118+ inner : F ,
119+ traces : Option < Arc < TraceNode > > ,
120+ }
121+
122+ impl < F > InstrumentedFuture < F > {
123+ pub fn new ( inner : F ) -> Self {
124+ Self { inner, traces : None }
125+ }
126+ }
127+
128+ impl < F : std:: future:: Future > std:: future:: Future for InstrumentedFuture < F > {
129+ type Output = F :: Output ;
130+
131+ fn poll (
132+ self : std:: pin:: Pin < & mut Self > ,
133+ cx : & mut std:: task:: Context < ' _ > ,
134+ ) -> std:: task:: Poll < Self :: Output > {
135+ // SAFETY: `InstrumentedFuture` provides structural pinning. If `self` is
136+ // pinned, it is safe to pin the `inner` future.
137+ let this = unsafe { self . get_unchecked_mut ( ) } ;
138+
139+ struct TraceSwapGuard < ' a > ( & ' a mut Option < Arc < TraceNode > > ) ;
140+ impl < ' a > TraceSwapGuard < ' a > {
141+ fn new ( traces : & ' a mut Option < Arc < TraceNode > > ) -> Self {
142+ TRACE_STACK . with ( |stack| {
143+ if let Ok ( mut s) = stack. try_borrow_mut ( ) {
144+ std:: mem:: swap ( & mut * s, traces) ;
145+ }
146+ } ) ;
147+ Self ( traces)
148+ }
149+ }
150+ impl < ' a > Drop for TraceSwapGuard < ' a > {
151+ fn drop ( & mut self ) {
152+ TRACE_STACK . with ( |stack| {
153+ if let Ok ( mut s) = stack. try_borrow_mut ( ) {
154+ std:: mem:: swap ( & mut * s, self . 0 ) ;
155+ }
156+ } ) ;
157+ }
158+ }
159+
160+ // Swap traces into thread-local, ensuring they are swapped back on return/panic.
161+ let _guard = TraceSwapGuard :: new ( & mut this. traces ) ;
162+
163+ // Poll inner future
164+ // SAFETY: As explained above, `this.inner` is properly pinned.
165+ unsafe { std:: pin:: Pin :: new_unchecked ( & mut this. inner ) } . poll ( cx)
166+ }
76167}
77168
78169// Test-only state and helpers, hidden from production API.
0 commit comments