|
| 1 | +//! This module contains code for stack-like storage for `HubSwitchGuard`s keyed |
| 2 | +//! by tracing span ID. |
| 3 | +
|
| 4 | +use std::collections::hash_map::Entry; |
| 5 | +use std::collections::HashMap; |
| 6 | + |
| 7 | +use sentry_core::HubSwitchGuard; |
| 8 | +use tracing_core::span::Id as SpanId; |
| 9 | + |
| 10 | +/// Holds per-span stacks of `HubSwitchGuard`s to handle span re-entrancy. |
| 11 | +/// |
| 12 | +/// Each time a span is entered, we should push a new guard onto the stack. |
| 13 | +/// When the span exits, we should pop the guard from the stack. |
| 14 | +pub(super) struct SpanGuardStack { |
| 15 | + /// The map of span IDs to their respective guard stacks. |
| 16 | + guards: HashMap<SpanId, Vec<HubSwitchGuard>>, |
| 17 | +} |
| 18 | + |
| 19 | +impl SpanGuardStack { |
| 20 | + /// Creates an empty guard stack map. |
| 21 | + pub(super) fn new() -> Self { |
| 22 | + Self { |
| 23 | + guards: HashMap::new(), |
| 24 | + } |
| 25 | + } |
| 26 | + |
| 27 | + /// Pushes a guard for the given span ID, creating the stack if needed. |
| 28 | + pub(super) fn push(&mut self, id: SpanId, guard: HubSwitchGuard) { |
| 29 | + self.guards.entry(id).or_default().push(guard); |
| 30 | + } |
| 31 | + |
| 32 | + /// Pops the most recent guard for the span ID, removing the stack when empty. |
| 33 | + pub(super) fn pop(&mut self, id: SpanId) -> Option<HubSwitchGuard> { |
| 34 | + match self.guards.entry(id) { |
| 35 | + Entry::Occupied(mut entry) => { |
| 36 | + let stack = entry.get_mut(); |
| 37 | + let guard = stack.pop(); |
| 38 | + if stack.is_empty() { |
| 39 | + entry.remove(); |
| 40 | + } |
| 41 | + guard |
| 42 | + } |
| 43 | + Entry::Vacant(_) => None, |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + /// Removes all guards for the span ID without returning them. |
| 48 | + /// |
| 49 | + /// This function guarantees that the guards are dropped in LIFO order. |
| 50 | + /// That way, the hub which was active when the span was first entered |
| 51 | + /// will be the one active after this function returns. |
| 52 | + /// |
| 53 | + /// Typically, remove should only get called once the span is fully |
| 54 | + /// exited, so this removal order guarantee is mostly just defensive. |
| 55 | + pub(super) fn remove(&mut self, id: &SpanId) { |
| 56 | + self.guards |
| 57 | + .remove(id) |
| 58 | + .into_iter() |
| 59 | + .flatten() |
| 60 | + .rev() // <- we drop in reverse order |
| 61 | + .for_each(drop); |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +#[cfg(test)] |
| 66 | +mod tests { |
| 67 | + use super::SpanGuardStack; |
| 68 | + use sentry_core::{Hub, HubSwitchGuard}; |
| 69 | + use std::sync::Arc; |
| 70 | + use tracing_core::span::Id as SpanId; |
| 71 | + |
| 72 | + #[test] |
| 73 | + fn pop_is_lifo() { |
| 74 | + let initial = Hub::current(); |
| 75 | + let hub_a = Arc::new(Hub::new_from_top(initial.clone())); |
| 76 | + let hub_b = Arc::new(Hub::new_from_top(hub_a.clone())); |
| 77 | + |
| 78 | + let mut stack = SpanGuardStack::new(); |
| 79 | + let id = SpanId::from_u64(1); |
| 80 | + |
| 81 | + stack.push(id.clone(), HubSwitchGuard::new(hub_a.clone())); |
| 82 | + assert!(Arc::ptr_eq(&Hub::current(), &hub_a)); |
| 83 | + |
| 84 | + stack.push(id.clone(), HubSwitchGuard::new(hub_b.clone())); |
| 85 | + assert!(Arc::ptr_eq(&Hub::current(), &hub_b)); |
| 86 | + |
| 87 | + drop(stack.pop(id.clone()).expect("guard for hub_b")); |
| 88 | + assert!(Arc::ptr_eq(&Hub::current(), &hub_a)); |
| 89 | + |
| 90 | + drop(stack.pop(id.clone()).expect("guard for hub_a")); |
| 91 | + assert!(Arc::ptr_eq(&Hub::current(), &initial)); |
| 92 | + |
| 93 | + assert!(stack.pop(id).is_none()); |
| 94 | + } |
| 95 | + |
| 96 | + #[test] |
| 97 | + fn remove_drops_all_guards_in_lifo_order() { |
| 98 | + let initial = Hub::current(); |
| 99 | + let hub_a = Arc::new(Hub::new_from_top(initial.clone())); |
| 100 | + let hub_b = Arc::new(Hub::new_from_top(hub_a.clone())); |
| 101 | + |
| 102 | + assert!(!Arc::ptr_eq(&hub_b, &initial)); |
| 103 | + assert!(!Arc::ptr_eq(&hub_a, &initial)); |
| 104 | + assert!(!Arc::ptr_eq(&hub_a, &hub_b)); |
| 105 | + |
| 106 | + let mut stack = SpanGuardStack::new(); |
| 107 | + let id = SpanId::from_u64(2); |
| 108 | + |
| 109 | + stack.push(id.clone(), HubSwitchGuard::new(hub_a.clone())); |
| 110 | + assert!(Arc::ptr_eq(&Hub::current(), &hub_a)); |
| 111 | + |
| 112 | + stack.push(id.clone(), HubSwitchGuard::new(hub_b.clone())); |
| 113 | + assert!(Arc::ptr_eq(&Hub::current(), &hub_b)); |
| 114 | + |
| 115 | + stack.remove(&id); |
| 116 | + assert!(Arc::ptr_eq(&Hub::current(), &initial)); |
| 117 | + } |
| 118 | +} |
0 commit comments