Skip to content

Commit 0fcc9fd

Browse files
authored
core: add intrusive linked list for callsite registry (#988)
This change adds an intrusive `LinkedList` for the callsite registry. The linked list is lock-free and can be written to from multiple threads. This list also does not require any allocations due to its intrusive nature. This though does require a breaking change to the `Callsite` trait to allow callsites to store the pointer to the next item in the intrusive list. Properoties of the intrusive atomically linked-list: - Only supports `LinkedList::push` and `LinkedList::for_each`. - The items in the list can only be added and not removed. Closes #860
1 parent 9c6dd2d commit 0fcc9fd

File tree

4 files changed

+282
-77
lines changed

4 files changed

+282
-77
lines changed

tracing-core/src/callsite.rs

Lines changed: 239 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
use crate::stdlib::{
44
fmt,
55
hash::{Hash, Hasher},
6-
sync::Mutex,
6+
ptr,
7+
sync::{
8+
atomic::{AtomicPtr, Ordering},
9+
Mutex, MutexGuard,
10+
},
711
vec::Vec,
812
};
913
use crate::{
@@ -13,61 +17,18 @@ use crate::{
1317
};
1418

1519
lazy_static! {
16-
static ref REGISTRY: Mutex<Registry> = Mutex::new(Registry {
17-
callsites: Vec::new(),
18-
dispatchers: Vec::new(),
19-
});
20-
}
21-
22-
struct Registry {
23-
callsites: Vec<&'static dyn Callsite>,
24-
dispatchers: Vec<dispatcher::Registrar>,
20+
static ref REGISTRY: Registry = Registry {
21+
callsites: LinkedList::new(),
22+
dispatchers: Mutex::new(Vec::new()),
23+
};
2524
}
2625

27-
impl Registry {
28-
fn rebuild_callsite_interest(&self, callsite: &'static dyn Callsite) {
29-
let meta = callsite.metadata();
30-
31-
// Iterate over the subscribers in the registry, and — if they are
32-
// active — register the callsite with them.
33-
let mut interests = self
34-
.dispatchers
35-
.iter()
36-
.filter_map(|registrar| registrar.try_register(meta));
37-
38-
// Use the first subscriber's `Interest` as the base value.
39-
let interest = if let Some(interest) = interests.next() {
40-
// Combine all remaining `Interest`s.
41-
interests.fold(interest, Interest::and)
42-
} else {
43-
// If nobody was interested in this thing, just return `never`.
44-
Interest::never()
45-
};
46-
47-
callsite.set_interest(interest)
48-
}
49-
50-
fn rebuild_interest(&mut self) {
51-
let mut max_level = LevelFilter::OFF;
52-
self.dispatchers.retain(|registrar| {
53-
if let Some(dispatch) = registrar.upgrade() {
54-
// If the subscriber did not provide a max level hint, assume
55-
// that it may enable every level.
56-
let level_hint = dispatch.max_level_hint().unwrap_or(LevelFilter::TRACE);
57-
if level_hint > max_level {
58-
max_level = level_hint;
59-
}
60-
true
61-
} else {
62-
false
63-
}
64-
});
26+
type Dispatchers = Vec<dispatcher::Registrar>;
27+
type Callsites = LinkedList;
6528

66-
self.callsites.iter().for_each(|&callsite| {
67-
self.rebuild_callsite_interest(callsite);
68-
});
69-
LevelFilter::set_max(max_level);
70-
}
29+
struct Registry {
30+
callsites: Callsites,
31+
dispatchers: Mutex<Dispatchers>,
7132
}
7233

7334
/// Trait implemented by callsites.
@@ -105,6 +66,18 @@ pub struct Identifier(
10566
pub &'static dyn Callsite,
10667
);
10768

69+
/// A registration with the callsite registry.
70+
///
71+
/// Every [`Callsite`] implementation must provide a `&'static Registration`
72+
/// when calling [`register`] to add itself to the global callsite registry.
73+
///
74+
/// [`Callsite`]: crate::callsite::Callsite
75+
/// [`register`]: crate::callsite::register
76+
pub struct Registration<T = &'static dyn Callsite> {
77+
callsite: T,
78+
next: AtomicPtr<Registration<T>>,
79+
}
80+
10881
/// Clear and reregister interest on every [`Callsite`]
10982
///
11083
/// This function is intended for runtime reconfiguration of filters on traces
@@ -125,24 +98,76 @@ pub struct Identifier(
12598
/// [`Interest::sometimes()`]: ../subscriber/struct.Interest.html#method.sometimes
12699
/// [`Subscriber`]: ../subscriber/trait.Subscriber.html
127100
pub fn rebuild_interest_cache() {
128-
let mut registry = REGISTRY.lock().unwrap();
129-
registry.rebuild_interest();
101+
let mut dispatchers = REGISTRY.dispatchers.lock().unwrap();
102+
let callsites = &REGISTRY.callsites;
103+
rebuild_interest(callsites, &mut dispatchers);
130104
}
131105

132106
/// Register a new `Callsite` with the global registry.
133107
///
134108
/// This should be called once per callsite after the callsite has been
135109
/// constructed.
136-
pub fn register(callsite: &'static dyn Callsite) {
137-
let mut registry = REGISTRY.lock().unwrap();
138-
registry.rebuild_callsite_interest(callsite);
139-
registry.callsites.push(callsite);
110+
pub fn register(registration: &'static Registration) {
111+
let mut dispatchers = REGISTRY.dispatchers.lock().unwrap();
112+
rebuild_callsite_interest(&mut dispatchers, registration.callsite);
113+
REGISTRY.callsites.push(registration);
140114
}
141115

142116
pub(crate) fn register_dispatch(dispatch: &Dispatch) {
143-
let mut registry = REGISTRY.lock().unwrap();
144-
registry.dispatchers.push(dispatch.registrar());
145-
registry.rebuild_interest();
117+
let mut dispatchers = REGISTRY.dispatchers.lock().unwrap();
118+
let callsites = &REGISTRY.callsites;
119+
120+
dispatchers.push(dispatch.registrar());
121+
122+
rebuild_interest(callsites, &mut dispatchers);
123+
}
124+
125+
fn rebuild_callsite_interest(
126+
dispatchers: &mut MutexGuard<'_, Vec<dispatcher::Registrar>>,
127+
callsite: &'static dyn Callsite,
128+
) {
129+
let meta = callsite.metadata();
130+
131+
// Iterate over the subscribers in the registry, and — if they are
132+
// active — register the callsite with them.
133+
let mut interests = dispatchers
134+
.iter()
135+
.filter_map(|registrar| registrar.try_register(meta));
136+
137+
// Use the first subscriber's `Interest` as the base value.
138+
let interest = if let Some(interest) = interests.next() {
139+
// Combine all remaining `Interest`s.
140+
interests.fold(interest, Interest::and)
141+
} else {
142+
// If nobody was interested in this thing, just return `never`.
143+
Interest::never()
144+
};
145+
146+
callsite.set_interest(interest)
147+
}
148+
149+
fn rebuild_interest(
150+
callsites: &Callsites,
151+
dispatchers: &mut MutexGuard<'_, Vec<dispatcher::Registrar>>,
152+
) {
153+
let mut max_level = LevelFilter::OFF;
154+
dispatchers.retain(|registrar| {
155+
if let Some(dispatch) = registrar.upgrade() {
156+
// If the subscriber did not provide a max level hint, assume
157+
// that it may enable every level.
158+
let level_hint = dispatch.max_level_hint().unwrap_or(LevelFilter::TRACE);
159+
if level_hint > max_level {
160+
max_level = level_hint;
161+
}
162+
true
163+
} else {
164+
false
165+
}
166+
});
167+
168+
callsites.for_each(|reg| rebuild_callsite_interest(dispatchers, reg.callsite));
169+
170+
LevelFilter::set_max(max_level);
146171
}
147172

148173
// ===== impl Identifier =====
@@ -169,3 +194,155 @@ impl Hash for Identifier {
169194
(self.0 as *const dyn Callsite).hash(state)
170195
}
171196
}
197+
198+
// ===== impl Registration =====
199+
200+
impl<T> Registration<T> {
201+
/// Construct a new `Registration` from some `&'static dyn Callsite`
202+
pub const fn new(callsite: T) -> Self {
203+
Self {
204+
callsite,
205+
next: AtomicPtr::new(ptr::null_mut()),
206+
}
207+
}
208+
}
209+
210+
impl fmt::Debug for Registration {
211+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212+
f.debug_struct("Registration")
213+
.field("callsite", &format_args!("{:p}", self.callsite))
214+
.field(
215+
"next",
216+
&format_args!("{:p}", self.next.load(Ordering::Acquire)),
217+
)
218+
.finish()
219+
}
220+
}
221+
222+
// ===== impl LinkedList =====
223+
224+
/// An intrusive atomic push-only linked list.
225+
struct LinkedList {
226+
head: AtomicPtr<Registration>,
227+
}
228+
229+
impl LinkedList {
230+
fn new() -> Self {
231+
LinkedList {
232+
head: AtomicPtr::new(ptr::null_mut()),
233+
}
234+
}
235+
236+
fn for_each(&self, mut f: impl FnMut(&'static Registration)) {
237+
let mut head = self.head.load(Ordering::Acquire);
238+
239+
while let Some(reg) = unsafe { head.as_ref() } {
240+
f(reg);
241+
242+
head = reg.next.load(Ordering::Acquire);
243+
}
244+
}
245+
246+
fn push(&self, registration: &'static Registration) {
247+
let mut head = self.head.load(Ordering::Acquire);
248+
249+
loop {
250+
registration.next.store(head, Ordering::Release);
251+
252+
assert_ne!(
253+
registration as *const _, head,
254+
"Attempted to register a `Callsite` that already exists! \
255+
This will cause an infinite loop when attempting to read from the \
256+
callsite cache. This is likely a bug! You should only need to call \
257+
`tracing-core::callsite::register` once per `Callsite`."
258+
);
259+
260+
match self.head.compare_exchange(
261+
head,
262+
registration as *const _ as *mut _,
263+
Ordering::AcqRel,
264+
Ordering::Acquire,
265+
) {
266+
Ok(_) => {
267+
break;
268+
}
269+
Err(current) => head = current,
270+
}
271+
}
272+
}
273+
}
274+
275+
#[cfg(test)]
276+
mod tests {
277+
use super::*;
278+
279+
#[derive(Eq, PartialEq)]
280+
struct Cs1;
281+
static CS1: Cs1 = Cs1;
282+
static REG1: Registration = Registration::new(&CS1);
283+
284+
impl Callsite for Cs1 {
285+
fn set_interest(&self, _interest: Interest) {}
286+
fn metadata(&self) -> &Metadata<'_> {
287+
unimplemented!("not needed for this test")
288+
}
289+
}
290+
291+
struct Cs2;
292+
static CS2: Cs2 = Cs2;
293+
static REG2: Registration = Registration::new(&CS2);
294+
295+
impl Callsite for Cs2 {
296+
fn set_interest(&self, _interest: Interest) {}
297+
fn metadata(&self) -> &Metadata<'_> {
298+
unimplemented!("not needed for this test")
299+
}
300+
}
301+
302+
#[test]
303+
fn linked_list_push() {
304+
let linked_list = LinkedList::new();
305+
306+
linked_list.push(&REG1);
307+
linked_list.push(&REG2);
308+
309+
let mut i = 0;
310+
311+
linked_list.for_each(|reg| {
312+
if i == 0 {
313+
assert!(
314+
ptr::eq(reg, &REG2),
315+
"Registration pointers need to match REG2"
316+
);
317+
} else {
318+
assert!(
319+
ptr::eq(reg, &REG1),
320+
"Registration pointers need to match REG1"
321+
);
322+
}
323+
324+
i += 1;
325+
});
326+
}
327+
328+
#[test]
329+
#[should_panic]
330+
fn linked_list_repeated() {
331+
let linked_list = LinkedList::new();
332+
333+
linked_list.push(&REG1);
334+
// Pass in same reg and we should panic...
335+
linked_list.push(&REG1);
336+
337+
linked_list.for_each(|_| {});
338+
}
339+
340+
#[test]
341+
fn linked_list_empty() {
342+
let linked_list = LinkedList::new();
343+
344+
linked_list.for_each(|_| {
345+
panic!("List should be empty");
346+
});
347+
}
348+
}

tracing-core/src/dispatcher.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ impl Drop for DefaultGuard {
783783

784784
#[cfg(test)]
785785
mod test {
786+
786787
use super::*;
787788
#[cfg(feature = "std")]
788789
use crate::stdlib::sync::atomic::{AtomicUsize, Ordering};

0 commit comments

Comments
 (0)