Skip to content

Commit 87cb339

Browse files
committed
return parent context in on_end instead of causing panic
1 parent df412fe commit 87cb339

File tree

3 files changed

+80
-10
lines changed

3 files changed

+80
-10
lines changed

opentelemetry-sdk/src/trace/mod.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,24 @@ mod tests {
137137
}
138138
}
139139

140-
fn on_end(&self, _span: SpanData) {
141-
// TODO: Accessing Context::current() will panic today and hence commented out.
140+
fn on_end(&self, span: SpanData) {
141+
// Fixed: Context::current() no longer panics from Drop
142142
// See https://github.com/open-telemetry/opentelemetry-rust/issues/2871
143-
// let _c = Context::current();
143+
let current_cx = Context::current();
144+
145+
// IMPORTANT NOTE: The context returned here is the PARENT context,
146+
// NOT the span's context (which has already been popped from the stack).
147+
// The span's baggage was extracted in on_start and stored as attributes.
148+
149+
// Verify: on_start stored the baggage as an attribute
150+
assert!(
151+
span.attributes.iter().any(|kv| kv.key.as_str() == "bag-key"),
152+
"Baggage should have been stored as span attribute in on_start"
153+
);
154+
155+
// The current context's baggage is the parent's baggage (if any)
156+
println!("on_end: parent context baggage = {:?}",
157+
current_cx.baggage().iter().map(|(k, _)| k).collect::<Vec<_>>());
144158
}
145159

146160
fn force_flush(&self) -> crate::error::OTelSdkResult {

opentelemetry-sdk/src/trace/span_processor.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,43 @@ pub trait SpanProcessor: Send + Sync + std::fmt::Debug {
7979
/// synchronously on the thread that started the span, therefore it should
8080
/// not block or throw exceptions.
8181
fn on_start(&self, span: &mut Span, cx: &Context);
82+
8283
/// `on_end` is called after a `Span` is ended (i.e., the end timestamp is
8384
/// already set). This method is called synchronously within the `Span::end`
8485
/// API, therefore it should not block or throw an exception.
86+
///
87+
/// # Accessing Context
88+
///
89+
/// If you need to access context information (such as baggage) in `on_end`,
90+
/// note that calling [`Context::current()`] will return the **parent context**,
91+
/// not the span's context (which has already been removed from the context stack).
92+
///
93+
/// **Best Practice**: Extract any needed context information in [`on_start`]
94+
/// and store it as span attributes. This ensures the information is available
95+
/// in the [`SpanData`] passed to `on_end`.
96+
///
97+
/// # Example
98+
///
99+
/// ```rust,ignore
100+
/// impl SpanProcessor for MyProcessor {
101+
/// fn on_start(&self, span: &mut Span, cx: &Context) {
102+
/// // Extract baggage and store as span attribute
103+
/// if let Some(value) = cx.baggage().get("my-key") {
104+
/// span.set_attribute(KeyValue::new("my-key", value.to_string()));
105+
/// }
106+
/// }
107+
///
108+
/// fn on_end(&self, span: SpanData) {
109+
/// // Access the attribute stored in on_start
110+
/// let my_value = span.attributes.iter()
111+
/// .find(|kv| kv.key.as_str() == "my-key");
112+
/// }
113+
/// }
114+
/// ```
115+
///
116+
/// [`on_start`]: SpanProcessor::on_start
117+
/// [`Context::current()`]: opentelemetry::Context::current
118+
///
85119
/// TODO - This method should take reference to `SpanData`
86120
fn on_end(&self, span: SpanData);
87121
/// Force the spans lying in the cache to be exported.

opentelemetry/src/context.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)