@@ -115,6 +115,18 @@ impl Context {
115115
116116 /// Returns an immutable snapshot of the current thread's context.
117117 ///
118+ /// # Behavior During Context Drop
119+ ///
120+ /// When called from within a [`Drop`] implementation that is triggered by
121+ /// a [`ContextGuard`] being dropped (e.g., when a [`Span`] is dropped as part
122+ /// of context cleanup), this function returns the **parent context**, not the
123+ /// context being dropped.
124+ ///
125+ /// This behavior is by design and prevents panics that would otherwise occur
126+ /// from attempting to borrow the context while it's being mutably borrowed
127+ /// for cleanup. See [issue #2871](https://github.com/open-telemetry/opentelemetry-rust/issues/2871)
128+ /// for details.
129+ ///
118130 /// # Examples
119131 ///
120132 /// ```
@@ -130,6 +142,8 @@ impl Context {
130142 /// let _guard = Context::new().with_value(ValueA("a")).attach();
131143 /// do_work()
132144 /// ```
145+ ///
146+ /// [`Span`]: crate::trace::Span
133147 pub fn current ( ) -> Self {
134148 Self :: map_current ( |cx| cx. clone ( ) )
135149 }
@@ -464,7 +478,12 @@ impl Drop for ContextGuard {
464478 fn drop ( & mut self ) {
465479 let id = self . cx_pos ;
466480 if id > ContextStack :: BASE_POS && id < ContextStack :: MAX_POS {
467- CURRENT_CONTEXT . with ( |context_stack| context_stack. borrow_mut ( ) . pop_id ( id) ) ;
481+ // Extract the context to drop outside of borrow_mut to avoid panic
482+ // when the context's drop implementation (e.g., Span::drop) calls Context::current()
483+ let _context_to_drop = CURRENT_CONTEXT . with ( |context_stack| {
484+ context_stack. borrow_mut ( ) . pop_id ( id)
485+ } ) ;
486+ // Context is automatically dropped here, outside of borrow_mut scope
468487 }
469488 }
470489}
@@ -543,7 +562,7 @@ impl ContextStack {
543562 }
544563
545564 #[ inline( always) ]
546- fn pop_id ( & mut self , pos : u16 ) {
565+ fn pop_id ( & mut self , pos : u16 ) -> Option < Context > {
547566 if pos == ContextStack :: BASE_POS || pos == ContextStack :: MAX_POS {
548567 // The empty context is always at the bottom of the [`ContextStack`]
549568 // and cannot be popped, and the overflow position is invalid, so do
@@ -557,7 +576,7 @@ impl ContextStack {
557576 "Attempted to pop the overflow position which is not allowed"
558577 }
559578 ) ;
560- return ;
579+ return None ;
561580 }
562581 let len: u16 = self . stack . len ( ) as u16 ;
563582 // Are we at the top of the [`ContextStack`]?
@@ -570,7 +589,10 @@ impl ContextStack {
570589 // empty context is always at the bottom of the stack if the
571590 // [`ContextStack`] is not empty.
572591 if let Some ( Some ( next_cx) ) = self . stack . pop ( ) {
573- self . current_cx = next_cx;
592+ // Return the old context to be dropped outside of borrow_mut
593+ Some ( std:: mem:: replace ( & mut self . current_cx , next_cx) )
594+ } else {
595+ None
574596 }
575597 } else {
576598 // This is an out of order pop.
@@ -582,10 +604,10 @@ impl ContextStack {
582604 stack_length = len,
583605 message = "Attempted to pop beyond the end of the context stack"
584606 ) ;
585- return ;
607+ return None ;
586608 }
587- // Clear out the entry at the given id.
588- _ = self . stack [ pos as usize ] . take ( ) ;
609+ // Clear out the entry at the given id and return it
610+ self . stack [ pos as usize ] . take ( )
589611 }
590612 }
591613
0 commit comments