Skip to content

Commit b11c064

Browse files
hawkwkaffarell
authored andcommitted
core: fix scoped dispatchers clobbering the global default (tokio-rs#2065)
## Motivation PR tokio-rs#2001 introduced --- or rather, _uncovered_ --- a bug which occurs when a global default subscriber is set *after* a scoped default has been set. When the scoped default guard is dropped, it resets the thread-local default cell to whatever subscriber was the default when the scoped default was set. This allows nesting scoped default contexts. However, when there was *no* default subscriber when the `DefaultGuard` was created, it sets the "previous" subscriber as `NoSubscriber`. This means dropping a `DefaultGuard` that was created before any other subscriber was set as default will reset that thread's default to `NoSubscriber`. Because tokio-rs#2001 changed the dispatcher module to stop using `NoSubscriber` as a placeholder for "use the global default if one exists", this means that the global default is permanently clobbered on the thread that set the scoped default prior to setting the global one. ## Solution This PR changes the behavior when creating a `DefaultGuard` when no default has been set. Instead of populating the "previous" dispatcher with `NoSubscriber`, it instead leaves the `DefaultGuard` with a `None`. When the `DefaultGuard` is dropped, if the subscriber is `None`, it will just clear the thread-local cell, rather than setting it to `NoSubscriber`. This way, the next time the cell is accessed, we will check if a global default exists to populate the thread-local, and everything works correctly. As a side benefit, this also makes the code a bit simpler! I've also added a test reproducing the bug. This PR is against `v0.1.x` rather than `master`, because the issue does not exist on `master` due to other implementation differences in v0.2. We may want to forward-port the test to guard against future regressions, though. Fixes tokio-rs#2050
1 parent b257c2c commit b11c064

File tree

2 files changed

+45
-17
lines changed

2 files changed

+45
-17
lines changed

tracing-core/src/dispatcher.rs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -684,15 +684,10 @@ impl State {
684684
let prior = CURRENT_STATE
685685
.try_with(|state| {
686686
state.can_enter.set(true);
687-
state
688-
.default
689-
.replace(Some(new_dispatch))
690-
// if the scoped default was not set on this thread, set the
691-
// `prior` default to the global default to populate the
692-
// scoped default when unsetting *this* default
693-
.unwrap_or_else(|| get_global().cloned().unwrap_or_else(Dispatch::none))
687+
state.default.replace(Some(new_dispatch))
694688
})
695-
.ok();
689+
.ok()
690+
.flatten();
696691
EXISTS.store(true, Ordering::Release);
697692
DefaultGuard(prior)
698693
}
@@ -734,15 +729,13 @@ impl<'a> Drop for Entered<'a> {
734729
impl Drop for DefaultGuard {
735730
#[inline]
736731
fn drop(&mut self) {
737-
if let Some(dispatch) = self.0.take() {
738-
// Replace the dispatcher and then drop the old one outside
739-
// of the thread-local context. Dropping the dispatch may
740-
// lead to the drop of a subscriber which, in the process,
741-
// could then also attempt to access the same thread local
742-
// state -- causing a clash.
743-
let prev = CURRENT_STATE.try_with(|state| state.default.replace(Some(dispatch)));
744-
drop(prev)
745-
}
732+
// Replace the dispatcher and then drop the old one outside
733+
// of the thread-local context. Dropping the dispatch may
734+
// lead to the drop of a subscriber which, in the process,
735+
// could then also attempt to access the same thread local
736+
// state -- causing a clash.
737+
let prev = CURRENT_STATE.try_with(|state| state.default.replace(self.0.take()));
738+
drop(prev)
746739
}
747740
}
748741

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#![cfg(feature = "std")]
2+
use tracing_mock::*;
3+
4+
#[test]
5+
fn scoped_clobbers_global() {
6+
// Reproduces https://github.com/tokio-rs/tracing/issues/2050
7+
8+
let (scoped, scoped_handle) = subscriber::mock()
9+
.event(event::msg("before global"))
10+
.event(event::msg("before drop"))
11+
.done()
12+
.run_with_handle();
13+
14+
let (global, global_handle) = subscriber::mock()
15+
.event(event::msg("after drop"))
16+
.done()
17+
.run_with_handle();
18+
19+
// Set a scoped default subscriber, returning a guard.
20+
let guard = tracing::subscriber::set_default(scoped);
21+
tracing::info!("before global");
22+
23+
// Now, set the global default.
24+
tracing::subscriber::set_global_default(global)
25+
.expect("global default should not already be set");
26+
// This event should still be collected by the scoped default.
27+
tracing::info!("before drop");
28+
29+
// Drop the guard. Now, the global default subscriber should be used.
30+
drop(guard);
31+
tracing::info!("after drop");
32+
33+
scoped_handle.assert_finished();
34+
global_handle.assert_finished();
35+
}

0 commit comments

Comments
 (0)