Skip to content

Commit 37c2434

Browse files
jswrennhawkw
andcommitted
core: add Dispatch::downgrade() and WeakDispatch (#2293)
Allows `Subscriber`s and `Layer`s to stash their own `Dispatch` without causing a memory leak. ## Motivation Resolves a shortcoming of #2269: that it's impossible for `Subscriber`s or `Layer`s to stash a copy of their own `Dispatch` without creating a reference cycle that would prevent them from being dropped. ## Solution Introduces `WeakDispatch` (analogous to `std::sync::Weak`) that holds a weak pointer to a `Subscriber`. `WeakDispatch` can be created via `Dispatch::downgrade`, and can be transformed into a `Dispatch` via `WeakDispatch::upgrade`. Co-authored-by: Eliza Weisman <[email protected]>
1 parent b740179 commit 37c2434

File tree

4 files changed

+133
-14
lines changed

4 files changed

+133
-14
lines changed

tracing-core/src/dispatcher.rs

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,24 +134,58 @@ use crate::stdlib::{
134134
fmt,
135135
sync::{
136136
atomic::{AtomicBool, AtomicUsize, Ordering},
137-
Arc,
137+
Arc, Weak,
138138
},
139139
};
140140

141141
#[cfg(feature = "std")]
142142
use crate::stdlib::{
143143
cell::{Cell, RefCell, RefMut},
144144
error,
145-
sync::Weak,
146145
};
147146

147+
#[cfg(feature = "alloc")]
148+
use alloc::sync::{Arc, Weak};
149+
150+
#[cfg(feature = "alloc")]
151+
use core::ops::Deref;
152+
148153
/// `Dispatch` trace data to a [`Subscriber`].
149-
///
150154
#[derive(Clone)]
151155
pub struct Dispatch {
152156
subscriber: Arc<dyn Subscriber + Send + Sync>,
153157
}
154158

159+
/// `WeakDispatch` is a version of [`Dispatch`] that holds a non-owning reference
160+
/// to a [`Subscriber`].
161+
///
162+
/// The Subscriber` may be accessed by calling [`WeakDispatch::upgrade`],
163+
/// which returns an `Option<Dispatch>`. If all [`Dispatch`] clones that point
164+
/// at the `Subscriber` have been dropped, [`WeakDispatch::upgrade`] will return
165+
/// `None`. Otherwise, it will return `Some(Dispatch)`.
166+
///
167+
/// A `WeakDispatch` may be created from a [`Dispatch`] by calling the
168+
/// [`Dispatch::downgrade`] method. The primary use for creating a
169+
/// [`WeakDispatch`] is to allow a Subscriber` to hold a cyclical reference to
170+
/// itself without creating a memory leak. See [here] for details.
171+
///
172+
/// This type is analogous to the [`std::sync::Weak`] type, but for a
173+
/// [`Dispatch`] rather than an [`Arc`].
174+
///
175+
/// [`Arc`]: std::sync::Arc
176+
/// [here]: Subscriber#avoiding-memory-leaks
177+
#[derive(Clone)]
178+
pub struct WeakDispatch {
179+
subscriber: Weak<dyn Subscriber + Send + Sync>,
180+
}
181+
182+
#[cfg(feature = "alloc")]
183+
#[derive(Clone)]
184+
enum Kind<T> {
185+
Global(&'static (dyn Collect + Send + Sync)),
186+
Scoped(T),
187+
}
188+
155189
#[cfg(feature = "std")]
156190
thread_local! {
157191
static CURRENT_STATE: State = State {
@@ -430,12 +464,23 @@ impl Dispatch {
430464
Registrar(Arc::downgrade(&self.subscriber))
431465
}
432466

433-
#[inline(always)]
434-
#[cfg(feature = "alloc")]
435-
pub(crate) fn subscriber(&self) -> &(dyn Subscriber + Send + Sync) {
436-
match self.subscriber {
437-
Kind::Scoped(ref s) => Arc::deref(s),
438-
Kind::Global(s) => s,
467+
/// Creates a [`WeakDispatch`] from this `Dispatch`.
468+
///
469+
/// A [`WeakDispatch`] is similar to a [`Dispatch`], but it does not prevent
470+
/// the underlying [`Subscriber`] from being dropped. Instead, it only permits
471+
/// access while other references to the `Subscriber` exist. This is equivalent
472+
/// to the standard library's [`Arc::downgrade`] method, but for `Dispatch`
473+
/// rather than `Arc`.
474+
///
475+
/// The primary use for creating a [`WeakDispatch`] is to allow a `Subscriber`
476+
/// to hold a cyclical reference to itself without creating a memory leak.
477+
/// See [here] for details.
478+
///
479+
/// [`Arc::downgrade`]: std::sync::Arc::downgrade
480+
/// [here]: Subscriber#avoiding-memory-leaks
481+
pub fn downgrade(&self) -> WeakDispatch {
482+
WeakDispatch {
483+
subscriber: Arc::downgrade(&self.subscriber),
439484
}
440485
}
441486

@@ -682,6 +727,45 @@ where
682727
}
683728
}
684729

730+
// === impl WeakDispatch ===
731+
732+
impl WeakDispatch {
733+
/// Attempts to upgrade this `WeakDispatch` to a [`Dispatch`].
734+
///
735+
/// Returns `None` if the referenced `Dispatch` has already been dropped.
736+
///
737+
/// ## Examples
738+
///
739+
/// ```
740+
/// # use tracing_core::subscriber::NoSubscriber;
741+
/// # use tracing_core::dispatcher::Dispatch;
742+
/// let strong = Dispatch::new(NoSubscriber::default());
743+
/// let weak = strong.downgrade();
744+
///
745+
/// // The strong here keeps it alive, so we can still access the object.
746+
/// assert!(weak.upgrade().is_some());
747+
///
748+
/// drop(strong); // But not any more.
749+
/// assert!(weak.upgrade().is_none());
750+
/// ```
751+
pub fn upgrade(&self) -> Option<Dispatch> {
752+
self.subscriber
753+
.upgrade()
754+
.map(|subscriber| Dispatch { subscriber })
755+
}
756+
}
757+
758+
impl fmt::Debug for WeakDispatch {
759+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
760+
let mut tuple = f.debug_tuple("WeakDispatch");
761+
match self.subscriber.upgrade() {
762+
Some(subscriber) => tuple.field(&format_args!("Some({:p})", subscriber)),
763+
None => tuple.field(&format_args!("None")),
764+
};
765+
tuple.finish()
766+
}
767+
}
768+
685769
#[cfg(feature = "std")]
686770
impl Registrar {
687771
pub(crate) fn upgrade(&self) -> Option<Dispatch> {

tracing-core/src/subscriber.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ use crate::stdlib::{
8282
/// [`event_enabled`]: Subscriber::event_enabled
8383
pub trait Subscriber: 'static {
8484
/// Invoked when this subscriber becomes a [`Dispatch`].
85+
///
86+
/// ## Avoiding Memory Leaks
87+
///
88+
/// `Subscriber`s should not store their own [`Dispatch`]. Because the
89+
/// `Dispatch` owns the `Subscriber`, storing the `Dispatch` within the
90+
/// `Subscriber` will create a reference count cycle, preventing the `Dispatch`
91+
/// from ever being dropped.
92+
///
93+
/// Instead, when it is necessary to store a cyclical reference to the
94+
/// `Dispatch` within a `Subscriber`, use [`Dispatch::downgrade`] to convert a
95+
/// `Dispatch` into a [`WeakDispatch`]. This type is analogous to
96+
/// [`std::sync::Weak`], and does not create a reference count cycle. A
97+
/// [`WeakDispatch`] can be stored within a `Subscriber` without causing a
98+
/// memory leak, and can be [upgraded] into a `Dispatch` temporarily when
99+
/// the `Dispatch` must be accessed by the `Subscriber`.
100+
///
101+
/// [`WeakDispatch`]: crate::dispatcher::WeakDispatch
102+
/// [upgraded]: crate::dispatcher::WeakDispatch::upgrade
85103
fn on_register_dispatch(&self, subscriber: &Dispatch) {
86104
let _ = subscriber;
87105
}

tracing-subscriber/src/layer/mod.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -712,11 +712,28 @@ where
712712
Self: 'static,
713713
{
714714
/// Performs late initialization when installing this layer as a
715-
/// [subscriber].
715+
/// [`Subscriber`].
716716
///
717-
/// [subscriber]: tracing_core::Subscriber
718-
fn on_register_dispatch(&self, subscriber: &Dispatch) {
719-
let _ = subscriber;
717+
/// ## Avoiding Memory Leaks
718+
///
719+
/// `Layer`s should not store the [`Dispatch`] pointing to the [`Subscriber`]
720+
/// that they are a part of. Because the `Dispatch` owns the `Subscriber`,
721+
/// storing the `Dispatch` within the `Subscriber` will create a reference
722+
/// count cycle, preventing the `Dispatch` from ever being dropped.
723+
///
724+
/// Instead, when it is necessary to store a cyclical reference to the
725+
/// `Dispatch` within a `Layer`, use [`Dispatch::downgrade`] to convert a
726+
/// `Dispatch` into a [`WeakDispatch`]. This type is analogous to
727+
/// [`std::sync::Weak`], and does not create a reference count cycle. A
728+
/// [`WeakDispatch`] can be stored within a subscriber without causing a
729+
/// memory leak, and can be [upgraded] into a `Dispatch` temporarily when
730+
/// the `Dispatch` must be accessed by the subscriber.
731+
///
732+
/// [`WeakDispatch`]: tracing_core::dispatcher::WeakDispatch
733+
/// [upgraded]: tracing_core::dispatcher::WeakDispatch::upgrade
734+
/// [`Subscriber`]: tracing_core::Subscriber
735+
fn on_register_dispatch(&self, collector: &Dispatch) {
736+
let _ = collector;
720737
}
721738

722739
/// Performs late initialization when attaching a `Layer` to a

tracing/src/dispatcher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ pub use tracing_core::dispatcher::with_default;
133133
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
134134
pub use tracing_core::dispatcher::DefaultGuard;
135135
pub use tracing_core::dispatcher::{
136-
get_default, set_global_default, Dispatch, SetGlobalDefaultError,
136+
get_default, set_global_default, Dispatch, SetGlobalDefaultError, WeakDispatch,
137137
};
138138

139139
/// Private API for internal use by tracing's macros.

0 commit comments

Comments
 (0)