Skip to content

Commit 8afb2a4

Browse files
committed
Remove globally-shared INACTIVE_SPAN RwLock
This RwLock was a heavy contention point in applications that generate a lot of spans, as every span operation must lock and unlock the RwLock. Each lock/unlock is an atomic RMW and thus dirties the cache line for all other cores. We can avoid the global singleton `INACTIVE_SPAN` entirely with some changes to our internal `SharedSpanHandle` API.
1 parent f71d4d1 commit 8afb2a4

File tree

2 files changed

+29
-30
lines changed

2 files changed

+29
-30
lines changed

foundations/src/telemetry/tracing/internal.rs

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ use std::sync::Arc;
1313

1414
pub(crate) type Tracer = cf_rustracing::Tracer<BoxSampler<SpanContextState>, SpanContextState>;
1515

16-
static INACTIVE_SPAN: RwLock<Span> = RwLock::new(Span::inactive());
17-
1816
/// Shared span with mutability and additional reference tracking for
1917
/// ad-hoc inspection.
2018
#[derive(Clone, Debug)]
@@ -29,19 +27,13 @@ impl SharedSpanHandle {
2927
TracingHarness::get().active_roots.track(span)
3028
}
3129

32-
pub(crate) fn read(&self) -> parking_lot::RwLockReadGuard<'_, Span> {
33-
match self {
34-
SharedSpanHandle::Tracked(handle) => handle.read(),
35-
SharedSpanHandle::Untracked(rw_lock) => rw_lock.read(),
36-
SharedSpanHandle::Inactive => INACTIVE_SPAN.read(),
37-
}
38-
}
30+
pub(crate) fn with_read<R>(&self, f: impl FnOnce(&Span) -> R) -> R {
31+
static INACTIVE: Span = Span::inactive();
3932

40-
pub(crate) fn write(&self) -> parking_lot::RwLockWriteGuard<'_, Span> {
4133
match self {
42-
SharedSpanHandle::Tracked(handle) => handle.write(),
43-
SharedSpanHandle::Untracked(rw_lock) => rw_lock.write(),
44-
SharedSpanHandle::Inactive => INACTIVE_SPAN.write(),
34+
SharedSpanHandle::Tracked(handle) => f(&handle.read()),
35+
SharedSpanHandle::Untracked(rw_lock) => f(&rw_lock.read()),
36+
SharedSpanHandle::Inactive => f(&INACTIVE),
4537
}
4638
}
4739
}
@@ -51,6 +43,8 @@ impl From<SharedSpanHandle> for Arc<RwLock<Span>> {
5143
match value {
5244
SharedSpanHandle::Tracked(handle) => Arc::clone(&handle),
5345
SharedSpanHandle::Untracked(rw_lock) => rw_lock,
46+
// This is only used in `rustracing_span()`, which should rarely
47+
// need to be called. Allocating a fresh Arc every time is thus fine.
5448
SharedSpanHandle::Inactive => Arc::new(RwLock::new(Span::inactive())),
5549
}
5650
}
@@ -78,16 +72,23 @@ impl From<Span> for SharedSpan {
7872
}
7973

8074
pub fn write_current_span(write_fn: impl FnOnce(&mut Span)) {
81-
if let Some(span) = current_span() {
82-
if span.is_sampled {
83-
write_fn(&mut span.inner.write());
84-
}
85-
}
75+
let span = match current_span() {
76+
Some(span) if span.is_sampled => span,
77+
_ => return,
78+
};
79+
80+
let mut span_guard = match &span.inner {
81+
SharedSpanHandle::Tracked(handle) => handle.write(),
82+
SharedSpanHandle::Untracked(rw_lock) => rw_lock.write(),
83+
SharedSpanHandle::Inactive => unreachable!("inactive spans can't be sampled"),
84+
};
85+
86+
write_fn(&mut span_guard);
8687
}
8788

8889
pub(crate) fn create_span(name: impl Into<Cow<'static, str>>) -> SharedSpan {
8990
match current_span() {
90-
Some(parent) => parent.inner.read().child(name, |o| o.start()),
91+
Some(parent) => parent.inner.with_read(|s| s.child(name, |o| o.start())),
9192
None => start_trace(name, Default::default()),
9293
}
9394
.into()
@@ -150,8 +151,11 @@ fn link_new_trace_with_current(
150151
root_span_name: &str,
151152
new_trace_root_span: &mut Span,
152153
) {
153-
let current_span_lock = current_span.inner.read();
154-
let mut new_trace_ref_span = create_fork_ref_span(root_span_name, &current_span_lock);
154+
let (mut new_trace_ref_span, current_trace_id) = current_span.inner.with_read(|s| {
155+
let trace_id = span_trace_id(s);
156+
let ref_span = create_fork_ref_span(root_span_name, s);
157+
(ref_span, trace_id)
158+
});
155159

156160
if let Some(trace_id) = span_trace_id(&*new_trace_root_span) {
157161
new_trace_ref_span.set_tag(|| {
@@ -164,7 +168,7 @@ fn link_new_trace_with_current(
164168
new_trace_ref_span.set_tag(|| Tag::new("trace_id", trace_id));
165169
}
166170

167-
if let Some(trace_id) = span_trace_id(&current_span_lock) {
171+
if let Some(trace_id) = current_trace_id {
168172
new_trace_root_span.set_tag(|| Tag::new("trace_id", trace_id));
169173
}
170174

@@ -194,10 +198,7 @@ pub(crate) fn fork_trace(fork_name: impl Into<Cow<'static, str>>) -> SharedSpan
194198
.into()
195199
}
196200

197-
fn create_fork_ref_span(
198-
fork_name: &str,
199-
current_span_lock: &parking_lot::RwLockReadGuard<Span>,
200-
) -> Span {
201+
fn create_fork_ref_span(fork_name: &str, current_span_lock: &Span) -> Span {
201202
let fork_ref_span_name = format!("[{fork_name} ref]");
202203

203204
current_span_lock.child(fork_ref_span_name, |o| o.start())

foundations/src/telemetry/tracing/mod.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ pub struct StartTraceOptions {
236236
///
237237
/// Returns `None` if the span is not sampled and don't have associated trace.
238238
pub fn trace_id() -> Option<String> {
239-
span_trace_id(&current_span()?.inner.read())
239+
current_span()?.inner.with_read(span_trace_id)
240240
}
241241

242242
/// Returns tracing state for the current span that can be serialized and passed to other services
@@ -288,9 +288,7 @@ pub fn trace_id() -> Option<String> {
288288
pub fn state_for_trace_stitching() -> Option<SerializableTraceState> {
289289
current_span()?
290290
.inner
291-
.read()
292-
.context()
293-
.map(|c| c.state().clone())
291+
.with_read(|s| Some(s.context()?.state().clone()))
294292
}
295293

296294
/// Returns the value to be used as a W3C traceparent header.

0 commit comments

Comments
 (0)