use std::cell::Cell;
use std::fmt;
use std::ptr;
struct Frame {
kind: &'static str,
name: *const str,
parent: *const Frame,
}
thread_local! {
static CURRENT: Cell<*const Frame> = const { Cell::new(ptr::null()) };
}
struct Restore(*const Frame);
impl Drop for Restore {
fn drop(&mut self) {
let _ = CURRENT.try_with(|current| current.set(self.0));
}
}
pub fn run_labeled<R>(kind: &'static str, name: &str, f: impl FnOnce() -> R) -> R {
let parent = CURRENT.try_with(Cell::get).unwrap_or(ptr::null());
let frame = Frame {
kind,
name: ptr::from_ref(name),
parent,
};
if CURRENT
.try_with(|current| current.set(ptr::from_ref(&frame)))
.is_err()
{
return f();
}
let _restore = Restore(parent);
f()
}
pub fn current_label<R>(f: impl for<'a> FnOnce(Option<TaskLabel<'a>>) -> R) -> R {
let frame = CURRENT.try_with(Cell::get).unwrap_or(ptr::null());
let label = unsafe { frame.as_ref() }.map(|frame| TaskLabel { frame });
f(label)
}
#[derive(Clone, Copy)]
pub struct TaskLabel<'a> {
frame: &'a Frame,
}
impl<'a> TaskLabel<'a> {
#[must_use]
pub fn kind(&self) -> &'static str {
self.frame.kind
}
#[must_use]
pub fn name(&self) -> &'a str {
unsafe { &*self.frame.name }
}
#[must_use]
pub fn parent(&self) -> Option<TaskLabel<'a>> {
unsafe { self.frame.parent.as_ref() }.map(|frame| TaskLabel { frame })
}
pub fn chain(self) -> impl Iterator<Item = TaskLabel<'a>> {
std::iter::successors(Some(self), TaskLabel::parent)
}
}
impl fmt::Debug for TaskLabel<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TaskLabel")
.field("kind", &self.kind())
.field("name", &self.name())
.finish()
}
}
impl fmt::Display for TaskLabel<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, label) in self.chain().enumerate() {
if i > 0 {
f.write_str(" <- ")?;
}
write!(f, "{} `{}`", label.kind(), label.name())?;
}
Ok(())
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use std::panic::{self, AssertUnwindSafe};
fn names() -> Vec<String> {
current_label(|label| {
label
.map(|l| l.chain().map(|l| l.name().to_owned()).collect())
.unwrap_or_default()
})
}
#[test]
fn no_label_outside() {
assert!(names().is_empty());
}
#[test]
fn nested_labels_form_a_chain_and_unwind() {
let outer = String::from("outer");
let value = run_labeled("graph", &outer, || {
run_labeled("spawn", "inner", || {
assert_eq!(names(), ["inner", "outer"]);
current_label(|l| assert_eq!(l.map(|l| l.kind()), Some("spawn")));
});
assert_eq!(names(), ["outer"]);
7
});
assert_eq!(value, 7);
assert!(names().is_empty());
}
#[test]
fn panic_restores_outer_label() {
run_labeled("graph", "outer", || {
let caught = panic::catch_unwind(AssertUnwindSafe(|| {
run_labeled("spawn", "inner", || panic!("boom"));
}));
assert!(caught.is_err());
assert_eq!(names(), ["outer"]);
});
assert!(names().is_empty());
}
#[test]
fn labels_are_per_thread() {
run_labeled("graph", "main-thread", || {
let other = std::thread::spawn(|| {
assert!(names().is_empty());
run_labeled("spawn", "worker", names)
})
.join()
.unwrap();
assert_eq!(other, ["worker"]);
assert_eq!(names(), ["main-thread"]);
});
}
#[test]
fn display_shows_chain() {
run_labeled("graph", "a", || {
run_labeled("async", "b", || {
let text = current_label(|l| l.map(|l| l.to_string()));
assert_eq!(text.as_deref(), Some("async `b` <- graph `a`"));
});
});
}
}