diff --git a/tracing-core/src/callsite.rs b/tracing-core/src/callsite.rs index 5e48473e15..02a8d9eb36 100644 --- a/tracing-core/src/callsite.rs +++ b/tracing-core/src/callsite.rs @@ -3,7 +3,11 @@ use crate::stdlib::{ fmt, hash::{Hash, Hasher}, - sync::Mutex, + ptr, + sync::{ + atomic::{AtomicPtr, Ordering}, + Mutex, MutexGuard, + }, vec::Vec, }; use crate::{ @@ -13,61 +17,18 @@ use crate::{ }; lazy_static! { - static ref REGISTRY: Mutex = Mutex::new(Registry { - callsites: Vec::new(), - dispatchers: Vec::new(), - }); -} - -struct Registry { - callsites: Vec<&'static dyn Callsite>, - dispatchers: Vec, + static ref REGISTRY: Registry = Registry { + callsites: LinkedList::new(), + dispatchers: Mutex::new(Vec::new()), + }; } -impl Registry { - fn rebuild_callsite_interest(&self, callsite: &'static dyn Callsite) { - let meta = callsite.metadata(); - - // Iterate over the subscribers in the registry, and — if they are - // active — register the callsite with them. - let mut interests = self - .dispatchers - .iter() - .filter_map(|registrar| registrar.try_register(meta)); - - // Use the first subscriber's `Interest` as the base value. - let interest = if let Some(interest) = interests.next() { - // Combine all remaining `Interest`s. - interests.fold(interest, Interest::and) - } else { - // If nobody was interested in this thing, just return `never`. - Interest::never() - }; - - callsite.set_interest(interest) - } - - fn rebuild_interest(&mut self) { - let mut max_level = LevelFilter::OFF; - self.dispatchers.retain(|registrar| { - if let Some(dispatch) = registrar.upgrade() { - // If the subscriber did not provide a max level hint, assume - // that it may enable every level. - let level_hint = dispatch.max_level_hint().unwrap_or(LevelFilter::TRACE); - if level_hint > max_level { - max_level = level_hint; - } - true - } else { - false - } - }); +type Dispatchers = Vec; +type Callsites = LinkedList; - self.callsites.iter().for_each(|&callsite| { - self.rebuild_callsite_interest(callsite); - }); - LevelFilter::set_max(max_level); - } +struct Registry { + callsites: Callsites, + dispatchers: Mutex, } /// Trait implemented by callsites. @@ -105,6 +66,18 @@ pub struct Identifier( pub &'static dyn Callsite, ); +/// A registration with the callsite registry. +/// +/// Every [`Callsite`] implementation must provide a `&'static Registration` +/// when calling [`register`] to add itself to the global callsite registry. +/// +/// [`Callsite`]: crate::callsite::Callsite +/// [`register`]: crate::callsite::register +pub struct Registration { + callsite: T, + next: AtomicPtr>, +} + /// Clear and reregister interest on every [`Callsite`] /// /// This function is intended for runtime reconfiguration of filters on traces @@ -125,24 +98,76 @@ pub struct Identifier( /// [`Interest::sometimes()`]: ../subscriber/struct.Interest.html#method.sometimes /// [`Subscriber`]: ../subscriber/trait.Subscriber.html pub fn rebuild_interest_cache() { - let mut registry = REGISTRY.lock().unwrap(); - registry.rebuild_interest(); + let mut dispatchers = REGISTRY.dispatchers.lock().unwrap(); + let callsites = ®ISTRY.callsites; + rebuild_interest(callsites, &mut dispatchers); } /// Register a new `Callsite` with the global registry. /// /// This should be called once per callsite after the callsite has been /// constructed. -pub fn register(callsite: &'static dyn Callsite) { - let mut registry = REGISTRY.lock().unwrap(); - registry.rebuild_callsite_interest(callsite); - registry.callsites.push(callsite); +pub fn register(registration: &'static Registration) { + let mut dispatchers = REGISTRY.dispatchers.lock().unwrap(); + rebuild_callsite_interest(&mut dispatchers, registration.callsite); + REGISTRY.callsites.push(registration); } pub(crate) fn register_dispatch(dispatch: &Dispatch) { - let mut registry = REGISTRY.lock().unwrap(); - registry.dispatchers.push(dispatch.registrar()); - registry.rebuild_interest(); + let mut dispatchers = REGISTRY.dispatchers.lock().unwrap(); + let callsites = ®ISTRY.callsites; + + dispatchers.push(dispatch.registrar()); + + rebuild_interest(callsites, &mut dispatchers); +} + +fn rebuild_callsite_interest( + dispatchers: &mut MutexGuard<'_, Vec>, + callsite: &'static dyn Callsite, +) { + let meta = callsite.metadata(); + + // Iterate over the subscribers in the registry, and — if they are + // active — register the callsite with them. + let mut interests = dispatchers + .iter() + .filter_map(|registrar| registrar.try_register(meta)); + + // Use the first subscriber's `Interest` as the base value. + let interest = if let Some(interest) = interests.next() { + // Combine all remaining `Interest`s. + interests.fold(interest, Interest::and) + } else { + // If nobody was interested in this thing, just return `never`. + Interest::never() + }; + + callsite.set_interest(interest) +} + +fn rebuild_interest( + callsites: &Callsites, + dispatchers: &mut MutexGuard<'_, Vec>, +) { + let mut max_level = LevelFilter::OFF; + dispatchers.retain(|registrar| { + if let Some(dispatch) = registrar.upgrade() { + // If the subscriber did not provide a max level hint, assume + // that it may enable every level. + let level_hint = dispatch.max_level_hint().unwrap_or(LevelFilter::TRACE); + if level_hint > max_level { + max_level = level_hint; + } + true + } else { + false + } + }); + + callsites.for_each(|reg| rebuild_callsite_interest(dispatchers, reg.callsite)); + + LevelFilter::set_max(max_level); } // ===== impl Identifier ===== @@ -169,3 +194,155 @@ impl Hash for Identifier { (self.0 as *const dyn Callsite).hash(state) } } + +// ===== impl Registration ===== + +impl Registration { + /// Construct a new `Registration` from some `&'static dyn Callsite` + pub const fn new(callsite: T) -> Self { + Self { + callsite, + next: AtomicPtr::new(ptr::null_mut()), + } + } +} + +impl fmt::Debug for Registration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Registration") + .field("callsite", &format_args!("{:p}", self.callsite)) + .field( + "next", + &format_args!("{:p}", self.next.load(Ordering::Acquire)), + ) + .finish() + } +} + +// ===== impl LinkedList ===== + +/// An intrusive atomic push-only linked list. +struct LinkedList { + head: AtomicPtr, +} + +impl LinkedList { + fn new() -> Self { + LinkedList { + head: AtomicPtr::new(ptr::null_mut()), + } + } + + fn for_each(&self, mut f: impl FnMut(&'static Registration)) { + let mut head = self.head.load(Ordering::Acquire); + + while let Some(reg) = unsafe { head.as_ref() } { + f(reg); + + head = reg.next.load(Ordering::Acquire); + } + } + + fn push(&self, registration: &'static Registration) { + let mut head = self.head.load(Ordering::Acquire); + + loop { + registration.next.store(head, Ordering::Release); + + assert_ne!( + registration as *const _, head, + "Attempted to register a `Callsite` that already exists! \ + This will cause an infinite loop when attempting to read from the \ + callsite cache. This is likely a bug! You should only need to call \ + `tracing-core::callsite::register` once per `Callsite`." + ); + + match self.head.compare_exchange( + head, + registration as *const _ as *mut _, + Ordering::AcqRel, + Ordering::Acquire, + ) { + Ok(_) => { + break; + } + Err(current) => head = current, + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Eq, PartialEq)] + struct Cs1; + static CS1: Cs1 = Cs1; + static REG1: Registration = Registration::new(&CS1); + + impl Callsite for Cs1 { + fn set_interest(&self, _interest: Interest) {} + fn metadata(&self) -> &Metadata<'_> { + unimplemented!("not needed for this test") + } + } + + struct Cs2; + static CS2: Cs2 = Cs2; + static REG2: Registration = Registration::new(&CS2); + + impl Callsite for Cs2 { + fn set_interest(&self, _interest: Interest) {} + fn metadata(&self) -> &Metadata<'_> { + unimplemented!("not needed for this test") + } + } + + #[test] + fn linked_list_push() { + let linked_list = LinkedList::new(); + + linked_list.push(®1); + linked_list.push(®2); + + let mut i = 0; + + linked_list.for_each(|reg| { + if i == 0 { + assert!( + ptr::eq(reg, ®2), + "Registration pointers need to match REG2" + ); + } else { + assert!( + ptr::eq(reg, ®1), + "Registration pointers need to match REG1" + ); + } + + i += 1; + }); + } + + #[test] + #[should_panic] + fn linked_list_repeated() { + let linked_list = LinkedList::new(); + + linked_list.push(®1); + // Pass in same reg and we should panic... + linked_list.push(®1); + + linked_list.for_each(|_| {}); + } + + #[test] + fn linked_list_empty() { + let linked_list = LinkedList::new(); + + linked_list.for_each(|_| { + panic!("List should be empty"); + }); + } +} diff --git a/tracing-core/src/dispatcher.rs b/tracing-core/src/dispatcher.rs index 658463584e..68d6fdb3e0 100644 --- a/tracing-core/src/dispatcher.rs +++ b/tracing-core/src/dispatcher.rs @@ -783,6 +783,7 @@ impl Drop for DefaultGuard { #[cfg(test)] mod test { + use super::*; #[cfg(feature = "std")] use crate::stdlib::sync::atomic::{AtomicUsize, Ordering}; diff --git a/tracing/src/lib.rs b/tracing/src/lib.rs index 6ae17e085a..0cf2521335 100644 --- a/tracing/src/lib.rs +++ b/tracing/src/lib.rs @@ -921,8 +921,11 @@ pub mod subscriber; #[doc(hidden)] pub mod __macro_support { - pub use crate::callsite::Callsite; - use crate::stdlib::sync::atomic::{AtomicUsize, Ordering}; + pub use crate::callsite::{Callsite, Registration}; + use crate::stdlib::{ + fmt, + sync::atomic::{AtomicUsize, Ordering}, + }; use crate::{subscriber::Interest, Metadata}; use tracing_core::Once; @@ -934,14 +937,17 @@ pub mod __macro_support { /// by the `tracing` macros, but it is not part of the stable versioned API. /// Breaking changes to this module may occur in small-numbered versions /// without warning. - #[derive(Debug)] - pub struct MacroCallsite { + pub struct MacroCallsite + where + T: 'static, + { interest: AtomicUsize, meta: &'static Metadata<'static>, - registration: Once, + register: Once, + registration: &'static Registration, } - impl MacroCallsite { + impl MacroCallsite { /// Returns a new `MacroCallsite` with the specified `Metadata`. /// /// /!\ WARNING: This is *not* a stable API! /!\ @@ -950,14 +956,20 @@ pub mod __macro_support { /// by the `tracing` macros, but it is not part of the stable versioned API. /// Breaking changes to this module may occur in small-numbered versions /// without warning. - pub const fn new(meta: &'static Metadata<'static>) -> Self { + pub const fn new( + meta: &'static Metadata<'static>, + registration: &'static Registration, + ) -> Self { Self { interest: AtomicUsize::new(0xDEADFACED), meta, - registration: Once::new(), + register: Once::new(), + registration, } } + } + impl MacroCallsite<&'static dyn Callsite> { /// Registers this callsite with the global callsite registry. /// /// If the callsite is already registered, this does nothing. @@ -972,8 +984,8 @@ pub mod __macro_support { // This only happens once (or if the cached interest value was corrupted). #[cold] pub fn register(&'static self) -> Interest { - self.registration - .call_once(|| crate::callsite::register(self)); + self.register + .call_once(|| crate::callsite::register(self.registration)); match self.interest.load(Ordering::Relaxed) { 0 => Interest::never(), 2 => Interest::always(), @@ -1054,6 +1066,17 @@ pub mod __macro_support { &self.meta } } + + impl fmt::Debug for MacroCallsite { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MacroCallsite") + .field("interest", &self.interest) + .field("meta", &self.meta) + .field("register", &self.register) + .field("registration", &self.registration) + .finish() + } + } } mod sealed { diff --git a/tracing/src/macros.rs b/tracing/src/macros.rs index a3e646d4d8..54eea57a45 100644 --- a/tracing/src/macros.rs +++ b/tracing/src/macros.rs @@ -54,7 +54,7 @@ macro_rules! span { }; (target: $target:expr, $lvl:expr, $name:expr, $($fields:tt)*) => { { - use $crate::__macro_support::Callsite as _; + use $crate::__macro_support::{Callsite as _, Registration}; static CALLSITE: $crate::__macro_support::MacroCallsite = $crate::callsite2! { name: $name, kind: $crate::metadata::Kind::SPAN, @@ -62,6 +62,7 @@ macro_rules! span { level: $lvl, fields: $($fields)* }; + let mut interest = $crate::subscriber::Interest::never(); if $crate::level_enabled!($lvl) && { interest = CALLSITE.interest(); !interest.is_never() }{ CALLSITE.dispatch_span(interest, |current| { @@ -1846,7 +1847,7 @@ macro_rules! callsite { level: $lvl:expr, fields: $($fields:tt)* ) => {{ - use $crate::__macro_support::MacroCallsite; + use $crate::__macro_support::{MacroCallsite, Registration}; static META: $crate::Metadata<'static> = { $crate::metadata! { name: $name, @@ -1857,7 +1858,8 @@ macro_rules! callsite { kind: $kind, } }; - static CALLSITE: MacroCallsite = MacroCallsite::new(&META); + static REG: Registration = Registration::new(&CALLSITE); + static CALLSITE: MacroCallsite = MacroCallsite::new(&META, ®); CALLSITE.register(); &CALLSITE }}; @@ -1897,7 +1899,7 @@ macro_rules! callsite2 { level: $lvl:expr, fields: $($fields:tt)* ) => {{ - use $crate::__macro_support::MacroCallsite; + use $crate::__macro_support::{MacroCallsite, Registration}; static META: $crate::Metadata<'static> = { $crate::metadata! { name: $name, @@ -1908,7 +1910,9 @@ macro_rules! callsite2 { kind: $kind, } }; - MacroCallsite::new(&META) + static REG: Registration = Registration::new(&CALLSITE); + + MacroCallsite::new(&META, ®) }}; }