Skip to content

Commit 14b0e1b

Browse files
committed
refactor: only return span instead of entire context for avoiding expensive cloning
1 parent 0172924 commit 14b0e1b

File tree

4 files changed

+51
-20
lines changed

4 files changed

+51
-20
lines changed

opentelemetry-sdk/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
- Added `Resource::get_ref(&self, key: &Key) -> Option<&Value>` to allow retrieving a reference to a resource value without cloning.
66
- **Breaking** Removed the following public hidden methods from the `SdkTracer` [#3227][3227]:
77
- `id_generator`, `should_sample`
8-
- **Fix**: Fixed panic when `SpanProcessor::on_end` calls `Context::current()` ([#3262][3262]).
9-
- Updated `SpanProcessor::on_end` documentation to clarify that `Context::current()` returns the parent context, not the span's context
8+
- **Fix**: Fixed panic when `SpanProcessor::on_end` calls `Context::current()` ([#2871][2871]).
9+
- Updated `SpanProcessor::on_end` documentation to clarify that `Context::current()` should not be relied upon in `on_end`
10+
- `Context::current()` returns whatever context happens to be active, which may be unrelated to the span being ended
1011
- Added best practice guidance: extract context information in `on_start` and store as span attributes
1112
- Documented that the panic fix in `opentelemetry` allows safe calls to `Context::current()` from `on_end`
1213

opentelemetry-sdk/src/trace/span_processor.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ pub trait SpanProcessor: Send + Sync + std::fmt::Debug {
8686
///
8787
/// # Accessing Context
8888
///
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).
89+
/// **Important**: Do not rely on [`Context::current()`] in `on_end`. When `on_end`
90+
/// is called during span cleanup, `Context::current()` returns whatever context
91+
/// happens to be active at that moment, which is typically unrelated to the span
92+
/// being ended. Contexts can be activated in any order and are not necessarily
93+
/// hierarchical.
9294
///
9395
/// **Best Practice**: Extract any needed context information in [`on_start`]
9496
/// and store it as span attributes. This ensures the information is available

opentelemetry/CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
- `with_trace_id`, `with_span_id`, `with_end_time`, `with_status`, `with_sampling_result`
99
- **Added** `#[must_use]` attribute to `opentelemetry::metrics::AsyncInstrumentBuilder` to add compile time warning when `.build()` is not called on observable instrument builders, preventing silent failures where callbacks are never registered and metrics are never reported.
1010
- **Fix**: Fixed panic when calling `Context::current()` from `Drop` implementations triggered by `ContextGuard` cleanup ([#2871][2871]).
11-
- Modified `ContextGuard::drop` to extract the context outside of `borrow_mut()` scope before dropping it
11+
- Modified `ContextGuard::drop` to extract only the span (not the entire context) outside of `borrow_mut()` scope before dropping it
1212
- This prevents "RefCell already mutably borrowed" panics when Span's `Drop` implementation calls `Context::current()` during context cleanup
13-
- **Note**: `Context::current()` called from within a Drop triggered by context cleanup will return the parent context, not the context being dropped (documented behavior)
13+
- **Important**: `Context::current()` called from within a Drop triggered by context cleanup returns whatever context happens to be current at that moment, which may be unrelated to the context being dropped
1414

1515
[3227]: https://github.com/open-telemetry/opentelemetry-rust/pull/3227
1616
[2871]: https://github.com/open-telemetry/opentelemetry-rust/issues/2871

opentelemetry/src/context.rs

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,12 @@ impl Context {
119119
///
120120
/// When called from within a [`Drop`] implementation that is triggered by
121121
/// 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.
122+
/// of context cleanup), this function returns **whatever context happens to be
123+
/// current after the guard is popped**, not the context being dropped.
124+
///
125+
/// **Important**: The returned context may be completely unrelated to the
126+
/// context being dropped, as contexts can be activated in any order and are
127+
/// not necessarily hierarchical. Do not rely on any relationship between them.
124128
///
125129
/// This behavior is by design and prevents panics that would otherwise occur
126130
/// from attempting to borrow the context while it's being mutably borrowed
@@ -478,11 +482,14 @@ impl Drop for ContextGuard {
478482
fn drop(&mut self) {
479483
let id = self.cx_pos;
480484
if id > ContextStack::BASE_POS && id < ContextStack::MAX_POS {
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 =
485+
// Extract only the span to drop outside of borrow_mut to avoid panic
486+
// when the span's drop implementation calls Context::current()
487+
#[cfg(feature = "trace")]
488+
let _to_drop =
484489
CURRENT_CONTEXT.with(|context_stack| context_stack.borrow_mut().pop_id(id));
485-
// Context is automatically dropped here, outside of borrow_mut scope
490+
#[cfg(not(feature = "trace"))]
491+
CURRENT_CONTEXT.with(|context_stack| context_stack.borrow_mut().pop_id(id));
492+
// Span (if any) is automatically dropped here, outside of borrow_mut scope
486493
}
487494
}
488495
}
@@ -533,6 +540,12 @@ struct ContextStack {
533540
_marker: PhantomData<*const ()>,
534541
}
535542

543+
// Type alias for what pop_id returns - only return the span when trace feature is enabled
544+
#[cfg(feature = "trace")]
545+
type PopIdReturn = Option<Arc<SynchronizedSpan>>;
546+
#[cfg(not(feature = "trace"))]
547+
type PopIdReturn = ();
548+
536549
impl ContextStack {
537550
const BASE_POS: u16 = 0;
538551
const MAX_POS: u16 = u16::MAX;
@@ -561,7 +574,7 @@ impl ContextStack {
561574
}
562575

563576
#[inline(always)]
564-
fn pop_id(&mut self, pos: u16) -> Option<Context> {
577+
fn pop_id(&mut self, pos: u16) -> PopIdReturn {
565578
if pos == ContextStack::BASE_POS || pos == ContextStack::MAX_POS {
566579
// The empty context is always at the bottom of the [`ContextStack`]
567580
// and cannot be popped, and the overflow position is invalid, so do
@@ -575,6 +588,7 @@ impl ContextStack {
575588
"Attempted to pop the overflow position which is not allowed"
576589
}
577590
);
591+
#[cfg(feature = "trace")]
578592
return None;
579593
}
580594
let len: u16 = self.stack.len() as u16;
@@ -588,11 +602,19 @@ impl ContextStack {
588602
// empty context is always at the bottom of the stack if the
589603
// [`ContextStack`] is not empty.
590604
if let Some(Some(next_cx)) = self.stack.pop() {
591-
// Return the old context to be dropped outside of borrow_mut
592-
Some(std::mem::replace(&mut self.current_cx, next_cx))
593-
} else {
594-
None
605+
// Extract and return only the span to avoid cloning the entire Context
606+
#[cfg(feature = "trace")]
607+
{
608+
let old_cx = std::mem::replace(&mut self.current_cx, next_cx);
609+
return old_cx.span;
610+
}
611+
#[cfg(not(feature = "trace"))]
612+
{
613+
self.current_cx = next_cx;
614+
}
595615
}
616+
#[cfg(feature = "trace")]
617+
return None;
596618
} else {
597619
// This is an out of order pop.
598620
if pos >= len {
@@ -603,10 +625,16 @@ impl ContextStack {
603625
stack_length = len,
604626
message = "Attempted to pop beyond the end of the context stack"
605627
);
628+
#[cfg(feature = "trace")]
606629
return None;
607630
}
608-
// Clear out the entry at the given id and return it
609-
self.stack[pos as usize].take()
631+
// Clear out the entry at the given id and extract its span
632+
#[cfg(feature = "trace")]
633+
return self.stack[pos as usize].take().and_then(|cx| cx.span);
634+
#[cfg(not(feature = "trace"))]
635+
{
636+
self.stack[pos as usize] = None;
637+
}
610638
}
611639
}
612640

0 commit comments

Comments
 (0)