Skip to content

Commit 16deb7c

Browse files
authored
Merge pull request #82 from mladedav/dm/recursive
feat: detect recursive calls that can deadlock `HierarchicalLayer`
2 parents 80c943b + 20277e7 commit 16deb7c

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

src/lib.rs

+39-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ use std::{
1010
io::{self, IsTerminal},
1111
iter::Fuse,
1212
mem,
13-
sync::Mutex,
13+
sync::{
14+
atomic::{AtomicBool, Ordering},
15+
Mutex,
16+
},
17+
thread::LocalKey,
1418
time::Instant,
1519
};
1620
use tracing_core::{
@@ -463,6 +467,28 @@ where
463467
unit = self.styled(Style::new().dimmed(), unit),
464468
)
465469
}
470+
471+
fn is_recursive() -> Option<RecursiveGuard> {
472+
thread_local! {
473+
pub static IS_EMPTY: AtomicBool = const { AtomicBool::new(true) };
474+
}
475+
476+
IS_EMPTY.with(|is_empty| {
477+
is_empty
478+
.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed)
479+
.ok()
480+
.map(|_| RecursiveGuard(&IS_EMPTY))
481+
})
482+
}
483+
}
484+
485+
struct RecursiveGuard(&'static LocalKey<AtomicBool>);
486+
487+
impl Drop for RecursiveGuard {
488+
fn drop(&mut self) {
489+
self.0
490+
.with(|is_empty| is_empty.store(true, Ordering::Relaxed));
491+
}
466492
}
467493

468494
impl<S, W, FT> Layer<S> for HierarchicalLayer<W, FT>
@@ -472,6 +498,10 @@ where
472498
FT: FormatTime + 'static,
473499
{
474500
fn on_new_span(&self, attrs: &Attributes, id: &Id, ctx: Context<S>) {
501+
let Some(_guard) = Self::is_recursive() else {
502+
return;
503+
};
504+
475505
let span = ctx.span(id).expect("in new_span but span does not exist");
476506

477507
if span.extensions().get::<Data>().is_none() {
@@ -507,6 +537,10 @@ where
507537
}
508538

509539
fn on_event(&self, event: &Event<'_>, ctx: Context<S>) {
540+
let Some(_guard) = Self::is_recursive() else {
541+
return;
542+
};
543+
510544
let span = ctx.current_span();
511545
let span_id = span.id();
512546
let span = span_id.and_then(|id| ctx.span(id));
@@ -588,6 +622,10 @@ where
588622
}
589623

590624
fn on_close(&self, id: Id, ctx: Context<S>) {
625+
let Some(_guard) = Self::is_recursive() else {
626+
return;
627+
};
628+
591629
let bufs = &mut *self.bufs.lock().unwrap();
592630

593631
let span = ctx.span(&id).expect("invalid span in on_close");

tests/recursive_event.rs

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use std::{io, str, sync::Mutex};
2+
3+
use tracing::subscriber::set_global_default;
4+
use tracing_subscriber::{layer::SubscriberExt, registry};
5+
6+
use tracing_tree::HierarchicalLayer;
7+
8+
struct RecursiveWriter(Mutex<Vec<u8>>);
9+
10+
impl io::Write for &RecursiveWriter {
11+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
12+
self.0.lock().unwrap().extend(buf);
13+
14+
tracing::error!("Nobody expects the Spanish Inquisition");
15+
16+
Ok(buf.len())
17+
}
18+
19+
fn flush(&mut self) -> io::Result<()> {
20+
tracing::error!("Nobody expects the Spanish Inquisition");
21+
Ok(())
22+
}
23+
}
24+
25+
/// This test checks that if `tracing` events happen during processing of
26+
/// `on_event`, the library does not deadlock.
27+
#[test]
28+
fn recursive_event() {
29+
static WRITER: RecursiveWriter = RecursiveWriter(Mutex::new(Vec::new()));
30+
31+
let subscriber = registry().with(HierarchicalLayer::new(2).with_writer(|| &WRITER));
32+
// This has to be its own integration test because we can't just set a
33+
// global default like this otherwise and not expect everything else to
34+
// break.
35+
set_global_default(subscriber).unwrap();
36+
37+
tracing::error!("We can never expect the unexpected.");
38+
39+
let output = WRITER.0.lock().unwrap();
40+
let output = str::from_utf8(&output).unwrap();
41+
42+
// If this test finished we're happy. Let's just also check that we did
43+
// in fact log _something_ and that the logs from within the writer did
44+
// not actually go through.
45+
assert!(output.contains("We can never expect the unexpected."));
46+
assert!(!output.contains("Nobody expects the Spanish Inquisition"));
47+
}

0 commit comments

Comments
 (0)