use std::{
cell::RefCell,
fmt, mem, str,
sync::atomic::{self, AtomicUsize, Ordering},
};
use crate::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use owning_ref::OwningHandle;
use std::collections::HashSet;
pub(crate) use tracing_core::span::{Attributes, Current, Id, Record};
use tracing_core::{dispatcher, Metadata};
pub struct Span<'a> {
lock: OwningHandle<RwLockReadGuard<'a, Slab>, RwLockReadGuard<'a, Slot>>,
}
#[derive(Debug)]
pub struct Context<'a, N> {
store: &'a Store,
new_visitor: &'a N,
}
#[derive(Debug)]
pub(crate) struct Store {
inner: RwLock<Slab>,
next: AtomicUsize,
}
#[derive(Debug)]
pub(crate) struct Data {
parent: Option<Id>,
metadata: &'static Metadata<'static>,
ref_count: AtomicUsize,
is_empty: bool,
}
#[derive(Debug)]
struct Slab {
slab: Vec<RwLock<Slot>>,
}
#[derive(Debug)]
struct Slot {
fields: String,
span: State,
}
#[derive(Debug)]
enum State {
Full(Data),
Empty(usize),
}
struct ContextId {
id: Id,
duplicate: bool,
}
struct SpanStack {
stack: Vec<ContextId>,
ids: HashSet<Id>,
}
impl SpanStack {
fn new() -> Self {
SpanStack {
stack: vec![],
ids: HashSet::new(),
}
}
fn push(&mut self, id: Id) {
let duplicate = self.ids.contains(&id);
if !duplicate {
self.ids.insert(id.clone());
}
self.stack.push(ContextId { id, duplicate })
}
fn pop(&mut self, expected_id: &Id) -> Option<Id> {
if &self.stack.last()?.id == expected_id {
let ContextId { id, duplicate } = self.stack.pop()?;
if !duplicate {
self.ids.remove(&id);
}
Some(id)
} else {
None
}
}
#[inline]
fn current(&self) -> Option<&Id> {
self.stack
.iter()
.rev()
.find(|context_id| !context_id.duplicate)
.map(|context_id| &context_id.id)
}
}
thread_local! {
static CONTEXT: RefCell<SpanStack> = RefCell::new(SpanStack::new());
}
macro_rules! debug_panic {
($($args:tt)*) => {
#[cfg(debug_assertions)] {
if !std::thread::panicking() {
panic!($($args)*)
}
}
}
}
impl<'a> Span<'a> {
pub fn name(&self) -> &'static str {
match self.lock.span {
State::Full(ref data) => data.metadata.name(),
State::Empty(_) => unreachable!(),
}
}
pub fn metadata(&self) -> &'static Metadata<'static> {
match self.lock.span {
State::Full(ref data) => data.metadata,
State::Empty(_) => unreachable!(),
}
}
pub fn fields(&self) -> &str {
self.lock.fields.as_ref()
}
pub fn parent(&self) -> Option<&Id> {
match self.lock.span {
State::Full(ref data) => data.parent.as_ref(),
State::Empty(_) => unreachable!(),
}
}
#[inline(always)]
fn with_parent<'store, F, E>(
self,
my_id: &Id,
last_id: Option<&Id>,
f: &mut F,
store: &'store Store,
) -> Result<(), E>
where
F: FnMut(&Id, Span<'_>) -> Result<(), E>,
{
if let Some(parent_id) = self.parent() {
if Some(parent_id) != last_id {
if let Some(parent) = store.get(parent_id) {
parent.with_parent(parent_id, Some(my_id), f, store)?;
} else {
debug_panic!("missing span for {:?}; this is a bug", parent_id);
}
}
}
f(my_id, self)
}
}
impl<'a> fmt::Debug for Span<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Span")
.field("name", &self.name())
.field("parent", &self.parent())
.field("metadata", self.metadata())
.field("fields", &self.fields())
.finish()
}
}
impl<'a, N> Context<'a, N> {
pub fn visit_spans<F, E>(&self, mut f: F) -> Result<(), E>
where
F: FnMut(&Id, Span<'_>) -> Result<(), E>,
{
CONTEXT
.try_with(|current| {
if let Some(id) = current.borrow().current() {
if let Some(span) = self.store.get(id) {
return span.with_parent(id, None, &mut f, self.store);
} else {
debug_panic!("missing span for {:?}; this is a bug", id);
}
}
Ok(())
})
.unwrap_or(Ok(()))
}
pub fn with_current<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce((&Id, Span<'_>)) -> R,
{
CONTEXT
.try_with(|current| {
if let Some(id) = current.borrow().current() {
if let Some(span) = self.store.get(&id) {
return Some(f((&id, span)));
} else {
debug_panic!("missing span for {:?}, this is a bug", id);
}
}
None
})
.ok()?
}
pub(crate) fn new(store: &'a Store, new_visitor: &'a N) -> Self {
Self { store, new_visitor }
}
pub fn new_visitor<'writer>(
&self,
writer: &'writer mut dyn fmt::Write,
is_empty: bool,
) -> N::Visitor
where
N: super::NewVisitor<'writer>,
{
self.new_visitor.make(writer, is_empty)
}
}
#[inline]
fn idx_to_id(idx: usize) -> Id {
Id::from_u64(idx as u64 + 1)
}
#[inline]
fn id_to_idx(id: &Id) -> usize {
id.into_u64() as usize - 1
}
impl Store {
pub(crate) fn with_capacity(capacity: usize) -> Self {
Store {
inner: RwLock::new(Slab {
slab: Vec::with_capacity(capacity),
}),
next: AtomicUsize::new(0),
}
}
#[inline]
pub(crate) fn current(&self) -> Option<Id> {
CONTEXT
.try_with(|current| current.borrow().current().cloned())
.ok()?
}
pub(crate) fn push(&self, id: &Id) {
let _ = CONTEXT.try_with(|current| current.borrow_mut().push(self.clone_span(id)));
}
pub(crate) fn pop(&self, expected_id: &Id) {
let id = CONTEXT
.try_with(|current| current.borrow_mut().pop(expected_id))
.ok()
.and_then(|i| i);
if let Some(id) = id {
let _ = self.drop_span(id);
}
}
#[inline]
pub(crate) fn new_span<N>(&self, attrs: &Attributes<'_>, new_visitor: &N) -> Id
where
N: for<'a> super::NewVisitor<'a>,
{
let mut span = Some(Data::new(attrs, self));
loop {
let head = self.next.load(Ordering::Relaxed);
{
let this = try_lock!(self.inner.read(), else return Id::from_u64(0xDEADFACE));
if head < this.slab.len() {
if let Ok(mut slot) = this.slab[head].try_write() {
if let Some(next) = slot.next() {
if self.next.compare_and_swap(head, next, Ordering::Release) == head {
slot.fill(span.take().unwrap(), attrs, new_visitor);
return idx_to_id(head);
}
}
}
atomic::spin_loop_hint();
continue;
}
}
if let Ok(mut this) = self.inner.try_write() {
let len = this.slab.len();
let slot = Slot::new(span.take().unwrap(), attrs, new_visitor);
this.slab.push(RwLock::new(slot));
self.next.store(len + 1, Ordering::Release);
return idx_to_id(len);
}
atomic::spin_loop_hint();
}
}
#[inline]
pub(crate) fn get(&self, id: &Id) -> Option<Span<'_>> {
let read = try_lock!(self.inner.read(), else return None);
let lock = OwningHandle::try_new(read, |slab| {
unsafe { &*slab }.read_slot(id_to_idx(id)).ok_or(())
})
.ok()?;
Some(Span { lock })
}
#[inline]
pub(crate) fn record<N>(&self, id: &Id, fields: &Record<'_>, new_recorder: &N)
where
N: for<'a> super::NewVisitor<'a>,
{
let slab = try_lock!(self.inner.read(), else return);
let slot = slab.write_slot(id_to_idx(id));
if let Some(mut slot) = slot {
slot.record(fields, new_recorder);
}
}
pub(crate) fn drop_span(&self, id: Id) -> bool {
let this = try_lock!(self.inner.read(), else return false);
let idx = id_to_idx(&id);
if !this
.slab
.get(idx)
.and_then(|lock| {
let span = try_lock!(lock.read(), else return None);
Some(span.drop_ref())
})
.unwrap_or_else(|| {
debug_panic!("tried to drop {:?} but it no longer exists!", id);
false
})
{
return false;
}
atomic::fence(Ordering::Acquire);
this.remove(&self.next, idx);
true
}
pub(crate) fn clone_span(&self, id: &Id) -> Id {
let this = try_lock!(self.inner.read(), else return id.clone());
let idx = id_to_idx(id);
if let Some(span) = this.slab.get(idx).and_then(|span| span.read().ok()) {
span.clone_ref();
} else {
debug_panic!(
"tried to clone {:?}, but no span exists with that ID. this is a bug!",
id
);
}
id.clone()
}
}
impl Data {
pub(crate) fn new(attrs: &Attributes<'_>, store: &Store) -> Self {
let parent = if attrs.is_root() {
None
} else if attrs.is_contextual() {
store.current().as_ref().map(|id| store.clone_span(id))
} else {
attrs.parent().map(|id| store.clone_span(id))
};
Self {
metadata: attrs.metadata(),
parent,
ref_count: AtomicUsize::new(1),
is_empty: true,
}
}
}
impl Drop for Data {
fn drop(&mut self) {
if self.parent.is_some() {
dispatcher::get_default(|subscriber| {
if let Some(parent) = self.parent.take() {
let _ = subscriber.try_close(parent);
}
})
}
}
}
impl Slot {
fn new<N>(mut data: Data, attrs: &Attributes<'_>, new_visitor: &N) -> Self
where
N: for<'a> super::NewVisitor<'a>,
{
let mut fields = String::new();
{
let mut recorder = new_visitor.make(&mut fields, true);
attrs.record(&mut recorder);
}
if fields.is_empty() {
data.is_empty = false;
}
Self {
fields,
span: State::Full(data),
}
}
fn next(&self) -> Option<usize> {
match self.span {
State::Empty(next) => Some(next),
_ => None,
}
}
fn fill<N>(&mut self, mut data: Data, attrs: &Attributes<'_>, new_visitor: &N) -> usize
where
N: for<'a> super::NewVisitor<'a>,
{
let fields = &mut self.fields;
{
let mut recorder = new_visitor.make(fields, true);
attrs.record(&mut recorder);
}
if fields.is_empty() {
data.is_empty = false;
}
match mem::replace(&mut self.span, State::Full(data)) {
State::Empty(next) => next,
State::Full(_) => unreachable!("tried to fill a full slot"),
}
}
fn record<N>(&mut self, fields: &Record<'_>, new_visitor: &N)
where
N: for<'a> super::NewVisitor<'a>,
{
let state = &mut self.span;
let buf = &mut self.fields;
match state {
State::Empty(_) => return,
State::Full(ref mut data) => {
{
let mut recorder = new_visitor.make(buf, data.is_empty);
fields.record(&mut recorder);
}
if buf.is_empty() {
data.is_empty = false;
}
}
}
}
fn drop_ref(&self) -> bool {
match self.span {
State::Full(ref data) => {
let refs = data.ref_count.fetch_sub(1, Ordering::Release);
debug_assert!(
if std::thread::panicking() {
true
} else {
refs != std::usize::MAX
},
"reference count overflow!"
);
refs == 1
}
State::Empty(_) => false,
}
}
fn clone_ref(&self) {
match self.span {
State::Full(ref data) => {
let _refs = data.ref_count.fetch_add(1, Ordering::Release);
debug_assert!(_refs != 0, "tried to clone a span that already closed!");
}
State::Empty(_) => {
unreachable!("tried to clone a ref to a span that no longer exists, this is a bug")
}
}
}
}
impl Slab {
#[inline]
fn write_slot(&self, idx: usize) -> Option<RwLockWriteGuard<'_, Slot>> {
self.slab.get(idx).and_then(|slot| slot.write().ok())
}
#[inline]
fn read_slot(&self, idx: usize) -> Option<RwLockReadGuard<'_, Slot>> {
self.slab
.get(idx)
.and_then(|slot| slot.read().ok())
.and_then(|lock| match lock.span {
State::Empty(_) => None,
State::Full(_) => Some(lock),
})
}
fn remove(&self, next: &AtomicUsize, idx: usize) -> Option<Data> {
loop {
let head = next.load(Ordering::Relaxed);
let mut slot = try_lock!(self.slab[idx].write(), else return None);
let data = match mem::replace(&mut slot.span, State::Empty(head)) {
State::Full(data) => data,
state => {
slot.span = state;
return None;
}
};
if next.compare_and_swap(head, idx, Ordering::Release) == head {
slot.fields.clear();
return Some(data);
}
atomic::spin_loop_hint();
}
}
}