pub const RERUN_SESSION_TRACESTATE_KEY: &str = "rerun_session_id";
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RerunTracingSessionId(String);
impl RerunTracingSessionId {
fn fresh() -> Self {
let n: u32 = rand::random();
Self(format!("rs_{n:08x}"))
}
pub fn parse(s: &str) -> Option<Self> {
let rest = s.strip_prefix("rs_")?;
if rest.len() == 8
&& rest
.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
{
Some(Self(s.to_owned()))
} else {
None
}
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for RerunTracingSessionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<RerunTracingSessionId> for String {
fn from(id: RerunTracingSessionId) -> Self {
id.0
}
}
static ACTIVE_TRACING_SESSION_COUNT: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
pub fn inc_active_tracing_session_count() {
ACTIVE_TRACING_SESSION_COUNT.fetch_add(1, std::sync::atomic::Ordering::Release);
}
pub fn dec_active_tracing_session_count() {
ACTIVE_TRACING_SESSION_COUNT.fetch_sub(1, std::sync::atomic::Ordering::Release);
}
struct ActiveSessionGuard;
impl ActiveSessionGuard {
fn new() -> Self {
inc_active_tracing_session_count();
Self
}
}
impl Drop for ActiveSessionGuard {
fn drop(&mut self) {
dec_active_tracing_session_count();
}
}
#[cfg(feature = "session_id_reader")]
pub type SessionIdReader = fn() -> Option<RerunTracingSessionId>;
#[cfg(feature = "session_id_reader")]
static SESSION_ID_READER: std::sync::OnceLock<SessionIdReader> = std::sync::OnceLock::new();
#[cfg(feature = "session_id_reader")]
pub(crate) fn set_session_id_reader(reader: SessionIdReader) {
SESSION_ID_READER.set(reader).ok();
}
fn read_via_reader() -> Option<RerunTracingSessionId> {
cfg_select! {
feature = "session_id_reader" => {
let reader = SESSION_ID_READER.get()?;
reader()
}
_ => {
None
}
}
}
tokio::task_local! {
static CURRENT_TRACING_SESSION_ID: Option<RerunTracingSessionId>;
}
#[must_use]
pub fn with_current_tracing_session<F>(
f: F,
) -> tokio::task::futures::TaskLocalFuture<Option<RerunTracingSessionId>, F>
where
F: std::future::Future,
{
let sid = read_current_tracing_session_id_at_boundary();
CURRENT_TRACING_SESSION_ID.scope(sid, f)
}
fn read_current_tracing_session_id_at_boundary() -> Option<RerunTracingSessionId> {
if ACTIVE_TRACING_SESSION_COUNT.load(std::sync::atomic::Ordering::Acquire) == 0 {
return None;
}
read_via_reader()
}
pub fn current_rerun_session_id() -> Option<RerunTracingSessionId> {
if ACTIVE_TRACING_SESSION_COUNT.load(std::sync::atomic::Ordering::Acquire) == 0 {
return None;
}
if let Ok(opt) = CURRENT_TRACING_SESSION_ID.try_with(|sid| sid.clone()) {
return opt;
}
read_via_reader()
}
#[cfg(test)]
pub(crate) async fn scope_session_id_for_test<F: std::future::Future>(
sid: Option<RerunTracingSessionId>,
f: F,
) -> F::Output {
let _guard = ActiveSessionGuard::new();
CURRENT_TRACING_SESSION_ID.scope(sid, f).await
}
pub async fn with_tracing_session<F: std::future::Future>(f: F) -> F::Output {
if !crate::is_telemetry_active() {
tracing::warn!(
"with_tracing_session is a no-op: the rerun telemetry stack is not active. \
Call `Telemetry::init` first to enable session correlation."
);
return f.await;
}
let sid = RerunTracingSessionId::fresh();
tracing::info!("rerun tracing session started: {sid}");
let _guard = ActiveSessionGuard::new();
let t0 = std::time::Instant::now();
let out = CURRENT_TRACING_SESSION_ID.scope(Some(sid.clone()), f).await;
tracing::info!(
rerun_session_id = %sid,
elapsed_s = format!("{:.3}", t0.elapsed().as_secs_f64()),
"rerun tracing session finished",
);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_malformed_ids() {
assert!(RerunTracingSessionId::parse("").is_none());
assert!(RerunTracingSessionId::parse("rs_").is_none());
assert!(RerunTracingSessionId::parse("rs_cafebab").is_none()); assert!(RerunTracingSessionId::parse("rs_cafebabe1").is_none()); assert!(RerunTracingSessionId::parse("rs_CAFEBABE").is_none()); assert!(RerunTracingSessionId::parse("rs_cafebabz").is_none()); assert!(RerunTracingSessionId::parse("xx_cafebabe").is_none()); assert!(RerunTracingSessionId::parse("cafebabe").is_none()); }
#[test]
fn accepts_well_formed_id() {
assert_eq!(
RerunTracingSessionId::parse("rs_cafebabe")
.unwrap()
.as_str(),
"rs_cafebabe",
);
assert!(RerunTracingSessionId::parse("rs_00000000").is_some());
assert!(RerunTracingSessionId::parse("rs_ffffffff").is_some());
assert!(RerunTracingSessionId::parse("rs_0123abcd").is_some());
}
#[test]
fn fresh_generates_valid_id() {
for _ in 0..16 {
let sid = RerunTracingSessionId::fresh();
assert!(
RerunTracingSessionId::parse(&sid.to_string()).is_some(),
"fresh() produced unparsable id: {sid}"
);
}
}
#[test]
fn nested_sessions_shadow_and_restore() {
use parking_lot::Mutex;
use std::sync::Arc;
crate::telemetry::set_telemetry_active_for_test(true);
let rt = tokio::runtime::Builder::new_current_thread() .enable_all()
.build()
.unwrap();
let captures: Arc<Mutex<[Option<RerunTracingSessionId>; 3]>> =
Arc::new(Mutex::new([None, None, None]));
let captures_outer = Arc::clone(&captures);
rt.block_on(super::with_tracing_session(async move {
captures_outer.lock()[0] = current_rerun_session_id();
let captures_inner = Arc::clone(&captures_outer);
super::with_tracing_session(async move {
captures_inner.lock()[1] = current_rerun_session_id();
})
.await;
captures_outer.lock()[2] = current_rerun_session_id();
}));
let captures = captures.lock();
let outer = captures[0].clone().expect("outer scope should be active");
let inner = captures[1].clone().expect("inner scope should be active");
let after_inner = captures[2]
.clone()
.expect("outer should be restored after inner exit");
drop(captures);
assert_ne!(outer, inner, "nested scope should generate a distinct id");
assert_eq!(
outer, after_inner,
"outer id should be restored after inner exits"
);
assert!(
current_rerun_session_id().is_none(),
"session should be cleared after outermost exits"
);
}
#[test]
fn gate_inc_dec_round_trips() {
use std::sync::atomic::Ordering;
assert_eq!(ACTIVE_TRACING_SESSION_COUNT.load(Ordering::Acquire), 0);
inc_active_tracing_session_count();
assert_eq!(ACTIVE_TRACING_SESSION_COUNT.load(Ordering::Acquire), 1);
dec_active_tracing_session_count();
assert_eq!(ACTIVE_TRACING_SESSION_COUNT.load(Ordering::Acquire), 0);
}
#[test]
fn counter_balanced_on_panic_in_body() {
use std::panic::AssertUnwindSafe;
use std::sync::atomic::Ordering;
crate::telemetry::set_telemetry_active_for_test(true);
let baseline = ACTIVE_TRACING_SESSION_COUNT.load(Ordering::Acquire);
let rt = tokio::runtime::Builder::new_current_thread() .enable_all()
.build()
.unwrap();
#[expect(clippy::disallowed_methods, reason = "tests compile with panic=unwind")]
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
rt.block_on(super::with_tracing_session(async {
panic!("boom");
}));
}));
assert!(result.is_err(), "panic should have propagated");
assert_eq!(
ACTIVE_TRACING_SESSION_COUNT.load(Ordering::Acquire),
baseline,
"active-session counter must return to baseline after panic",
);
}
}