use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::future::Future;
use std::sync::{Arc, RwLock, Weak};
use opentelemetry::propagation::TextMapPropagator;
use opentelemetry::trace::{SpanContext, TraceContextExt, TraceFlags, TraceState};
use opentelemetry_sdk::propagation::TraceContextPropagator;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::api::runtime::callbacks::EventSubscriberFn;
use crate::api::scope::{ScopeHandle, ScopeType};
use crate::context::registries::ScopeLocalRegistries;
use crate::error::{FlowError, Result};
use crate::registry::{RegistryEntry, SortedRegistry};
pub struct ScopeStack {
stack: Vec<ScopeHandle>,
scope_registries: HashMap<Uuid, ScopeLocalRegistries>,
fresh_agents: HashSet<Uuid>,
propagated_parent_uuid: Option<Uuid>,
propagated_root_uuid: Option<Uuid>,
propagated_traceparent: Option<String>,
propagated_tracestate: Option<String>,
is_rootless_propagation: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PropagationContext {
pub version: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub root_uuid: Option<Uuid>,
pub parent_uuid: Uuid,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub traceparent: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tracestate: Option<String>,
}
fn is_usable_relay_identifier(uuid: Uuid) -> bool {
let bytes = uuid.as_bytes();
bytes[8..].iter().any(|byte| *byte != 0)
}
impl PropagationContext {
pub const VERSION: u16 = 1;
pub fn to_json(&self) -> Result<String> {
self.validate()?;
Ok(serde_json::to_string(&self.clone().normalized())
.expect("PropagationContext is always JSON serializable"))
}
pub fn to_traceparent(&self) -> Result<String> {
self.validate()?;
let context = self.clone().normalized();
if let Some(traceparent) = context.traceparent {
return Ok(traceparent);
}
let Some(root_uuid) = context.root_uuid else {
return Err(FlowError::InvalidArgument(
"rootless propagation context cannot be converted to traceparent".into(),
));
};
Ok(crate::observability::format_traceparent(
root_uuid,
self.parent_uuid,
))
}
pub fn from_json(value: &str) -> Result<Self> {
let context: Self = serde_json::from_str(value).map_err(|error| {
FlowError::InvalidArgument(format!("invalid propagation context JSON: {error}"))
})?;
context.validate()?;
Ok(context.normalized())
}
pub fn validate(&self) -> Result<()> {
if self.version != Self::VERSION {
return Err(FlowError::InvalidArgument(format!(
"unsupported propagation context version {}; expected {}",
self.version,
Self::VERSION
)));
}
for (name, uuid) in [("parent_uuid", self.parent_uuid)]
.into_iter()
.chain(self.root_uuid.map(|uuid| ("root_uuid", uuid)))
{
if !is_usable_relay_identifier(uuid) {
return Err(FlowError::InvalidArgument(format!(
"propagation context {name} is not a usable Relay identifier"
)));
}
}
Ok(())
}
pub fn normalized(mut self) -> Self {
let (traceparent, tracestate, _) =
normalize_w3c_headers(self.traceparent.as_deref(), self.tracestate.as_deref());
self.traceparent = traceparent;
self.tracestate = tracestate;
self
}
}
fn parse_w3c_tracestate(header: &str) -> Option<TraceState> {
let mut members = Vec::new();
let mut keys = HashSet::new();
for (index, member) in header.split(',').enumerate() {
if index >= 32 {
return None;
}
let member = member.trim_matches([' ', '\t']);
if member.is_empty() {
continue;
}
let (key, value) = member.split_once('=')?;
let name_chars = |name: &str| {
name.bytes().all(|b| {
b.is_ascii_lowercase()
|| b.is_ascii_digit()
|| matches!(b, b'_' | b'-' | b'*' | b'/')
})
};
let valid_key = if let Some((tenant, system)) = key.split_once('@') {
(1..=241).contains(&tenant.len())
&& tenant.as_bytes()[0].is_ascii_alphanumeric()
&& name_chars(tenant)
&& (1..=14).contains(&system.len())
&& system.as_bytes()[0].is_ascii_lowercase()
&& name_chars(system)
} else {
(1..=256).contains(&key.len())
&& key.as_bytes()[0].is_ascii_lowercase()
&& name_chars(key)
};
if !valid_key
|| !keys.insert(key)
|| !(1..=256).contains(&value.len())
|| !value
.bytes()
.all(|b| (0x20..=0x7e).contains(&b) && !matches!(b, b',' | b'='))
{
return None;
}
members.push((key, value));
}
TraceState::from_key_value(members).ok()
}
pub(crate) fn normalize_w3c_headers(
traceparent: Option<&str>,
tracestate: Option<&str>,
) -> (Option<String>, Option<String>, Option<SpanContext>) {
let Some(traceparent) = traceparent else {
if tracestate.is_some() {
log::warn!(target: "nemo_relay.runtime", event = "invalid_w3c_trace_context"; "Ignoring tracestate without traceparent");
}
return (None, None, None);
};
let mut carrier = HashMap::from([("traceparent".to_string(), traceparent.to_string())]);
if let Some(tracestate) = tracestate {
if let Some(state) = parse_w3c_tracestate(tracestate) {
carrier.insert("tracestate".to_string(), state.header());
} else {
log::warn!(target: "nemo_relay.runtime", event = "invalid_w3c_trace_context"; "Ignoring invalid W3C tracestate");
}
}
let context = TraceContextPropagator::new().extract(&carrier);
if !context.span().span_context().is_valid() {
log::warn!(target: "nemo_relay.runtime", event = "invalid_w3c_trace_context"; "Ignoring invalid W3C trace context");
return (None, None, None);
}
let mut canonical = HashMap::new();
TraceContextPropagator::new().inject_context(&context, &mut canonical);
let tracestate = canonical
.remove("tracestate")
.filter(|value| !value.is_empty());
(
canonical.remove("traceparent"),
tracestate,
Some(context.span().span_context().clone()),
)
}
pub(crate) fn w3c_span_context(
traceparent: Option<&str>,
tracestate: Option<&str>,
) -> Option<SpanContext> {
normalize_w3c_headers(traceparent, tracestate).2
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct W3cTraceContext {
traceparent: String,
tracestate: Option<String>,
}
impl W3cTraceContext {
pub(crate) fn new(traceparent: impl Into<String>, tracestate: Option<String>) -> Result<Self> {
let traceparent = traceparent.into();
let (traceparent, tracestate, _) =
normalize_w3c_headers(Some(&traceparent), tracestate.as_deref());
let Some(traceparent) = traceparent else {
return Err(FlowError::InvalidArgument(
"invalid W3C trace context".into(),
));
};
Ok(Self {
traceparent,
tracestate,
})
}
pub(crate) fn traceparent(&self) -> &str {
&self.traceparent
}
pub(crate) fn tracestate(&self) -> Option<&str> {
self.tracestate.as_deref()
}
pub(crate) fn span_context(&self) -> Option<SpanContext> {
w3c_span_context(Some(&self.traceparent), self.tracestate.as_deref())
}
}
impl ScopeStack {
fn snapshot(&self) -> Self {
Self {
stack: self.stack.clone(),
scope_registries: self.scope_registries.clone(),
fresh_agents: self.fresh_agents.clone(),
propagated_parent_uuid: self.propagated_parent_uuid,
propagated_root_uuid: self.propagated_root_uuid,
propagated_traceparent: self.propagated_traceparent.clone(),
propagated_tracestate: self.propagated_tracestate.clone(),
is_rootless_propagation: self.is_rootless_propagation,
}
}
pub(crate) fn root_only_snapshot(&self) -> Self {
let root = self
.stack
.first()
.expect("scope stack should never be empty")
.clone();
let mut scope_registries = HashMap::new();
if let Some(registries) = self.scope_registries.get(&root.uuid) {
scope_registries.insert(root.uuid, registries.clone());
}
Self {
stack: vec![root.clone()],
scope_registries,
fresh_agents: self
.fresh_agents
.contains(&root.uuid)
.then_some(root.uuid)
.into_iter()
.collect(),
propagated_parent_uuid: self.propagated_parent_uuid,
propagated_root_uuid: self.propagated_root_uuid,
propagated_traceparent: self.propagated_traceparent.clone(),
propagated_tracestate: self.propagated_tracestate.clone(),
is_rootless_propagation: self.is_rootless_propagation,
}
}
pub(crate) fn snapshot_through_scope(&self, scope_uuid: &Uuid) -> Option<Self> {
let end = self
.stack
.iter()
.position(|scope| scope.uuid == *scope_uuid)?;
let stack = self.stack[..=end].to_vec();
let visible = stack.iter().map(|scope| scope.uuid).collect::<HashSet<_>>();
Some(Self {
stack,
scope_registries: self
.scope_registries
.iter()
.filter(|(uuid, _)| visible.contains(uuid))
.map(|(uuid, registries)| (*uuid, registries.clone()))
.collect(),
fresh_agents: self.fresh_agents.intersection(&visible).copied().collect(),
propagated_parent_uuid: self.propagated_parent_uuid,
propagated_root_uuid: self.propagated_root_uuid,
propagated_traceparent: self.propagated_traceparent.clone(),
propagated_tracestate: self.propagated_tracestate.clone(),
is_rootless_propagation: self.is_rootless_propagation,
})
}
pub fn new() -> Self {
let root = ScopeHandle::builder()
.name("root")
.scope_type(ScopeType::Agent)
.build();
let root_uuid = root.uuid;
Self {
stack: vec![root],
scope_registries: HashMap::new(),
fresh_agents: HashSet::from([root_uuid]),
propagated_parent_uuid: None,
propagated_root_uuid: None,
propagated_traceparent: None,
propagated_tracestate: None,
is_rootless_propagation: false,
}
}
fn from_propagation(context: &PropagationContext) -> Result<Self> {
context.validate()?;
let context = context.clone().normalized();
let (root, parent) = match context.root_uuid {
Some(root_uuid) => {
let root = ScopeHandle::builder()
.uuid(root_uuid)
.name("propagated-root")
.scope_type(ScopeType::Agent)
.build();
let parent = (root_uuid != context.parent_uuid).then(|| {
ScopeHandle::builder()
.uuid(context.parent_uuid)
.parent_uuid(root_uuid)
.name("propagated-parent")
.scope_type(ScopeType::Unknown)
.build()
});
(root, parent)
}
None => (
ScopeHandle::builder()
.uuid(context.parent_uuid)
.name("propagated-root")
.scope_type(ScopeType::Agent)
.build(),
None,
),
};
let root_uuid = root.uuid;
let mut stack = vec![root];
if let Some(parent) = parent {
stack.push(parent);
}
Ok(Self {
stack,
scope_registries: HashMap::new(),
fresh_agents: HashSet::from([root_uuid]),
propagated_parent_uuid: Some(context.parent_uuid),
propagated_root_uuid: context.root_uuid,
propagated_traceparent: context.traceparent,
propagated_tracestate: context.tracestate,
is_rootless_propagation: context.root_uuid.is_none(),
})
}
pub fn push(&mut self, handle: ScopeHandle) {
if matches!(handle.scope_type, ScopeType::Agent) {
self.fresh_agents.insert(handle.uuid);
}
self.stack.push(handle);
}
pub fn top(&self) -> &ScopeHandle {
self.stack
.last()
.expect("scope stack should never be empty")
}
pub fn top_mut(&mut self) -> &mut ScopeHandle {
self.stack
.last_mut()
.expect("scope stack should never be empty")
}
pub fn root_uuid(&self) -> Uuid {
self.stack
.first()
.expect("scope stack should never be empty")
.uuid
}
pub(crate) fn event_propagation_root_uuid(&self) -> Option<Uuid> {
self.propagated_root_uuid
.or_else(|| {
self.stack
.iter()
.skip(1)
.find(|scope| {
scope.scope_type == ScopeType::Agent
&& is_usable_relay_identifier(scope.uuid)
})
.map(|scope| scope.uuid)
})
.or_else(|| (!self.is_rootless_propagation).then(|| self.root_uuid()))
}
pub(crate) fn event_propagation_parent_uuid(&self) -> Option<Uuid> {
self.propagated_parent_uuid
}
pub(crate) fn event_w3c_headers(
&self,
parent_uuid: Option<Uuid>,
) -> (Option<String>, Option<String>) {
let Some(parent_uuid) = parent_uuid else {
return (None, None);
};
let parent_is_propagated = self.propagated_parent_uuid == Some(parent_uuid);
if parent_is_propagated {
if self.propagated_traceparent.is_some() {
return (
self.propagated_traceparent.clone(),
self.propagated_tracestate.clone(),
);
}
if self.propagated_root_uuid.is_none() {
return (None, None);
}
}
self.w3c_headers_for_span(parent_uuid, parent_uuid)
}
pub fn is_propagated_parent(&self, uuid: Uuid) -> bool {
self.propagated_parent_uuid == Some(uuid)
}
pub fn scopes(&self) -> &[ScopeHandle] {
&self.stack
}
pub fn find(&self, uuid: &Uuid) -> Option<&ScopeHandle> {
self.stack.iter().find(|handle| handle.uuid == *uuid)
}
pub fn remove(&mut self, uuid: &Uuid) -> Result<ScopeHandle> {
let top = self
.stack
.last()
.expect("scope stack should never be empty");
if top.uuid == *uuid {
if self.stack.len() == 1 {
return Err(FlowError::InvalidArgument(
"root scope cannot be removed".into(),
));
}
self.scope_registries.remove(uuid);
self.fresh_agents.remove(uuid);
return Ok(self
.stack
.pop()
.expect("scope stack should contain a removable top scope"));
}
if self.stack.iter().any(|handle| handle.uuid == *uuid) {
return Err(FlowError::InvalidArgument(
"scope handle is not at the top of the stack".into(),
));
}
Err(FlowError::NotFound("scope handle not found".into()))
}
fn owning_agent_uuid(&self, parent_uuid: Option<Uuid>) -> Uuid {
let search_end = parent_uuid
.and_then(|parent_uuid| {
self.stack
.iter()
.position(|scope| scope.uuid == parent_uuid)
})
.map_or(self.stack.len(), |index| index + 1);
self.stack[..search_end]
.iter()
.rev()
.find(|scope| matches!(scope.scope_type, ScopeType::Agent))
.map(|scope| scope.uuid)
.expect("scope stack should always contain an owning agent")
}
pub(crate) fn take_agent_freshness(&mut self, parent_uuid: Option<Uuid>) -> bool {
let uuid = self.owning_agent_uuid(parent_uuid);
self.fresh_agents.remove(&uuid)
}
pub(crate) fn mark_agent_fresh(&mut self, parent_uuid: Option<Uuid>) {
let uuid = self.owning_agent_uuid(parent_uuid);
self.fresh_agents.insert(uuid);
}
pub(crate) fn local_registries_mut(
&mut self,
uuid: &Uuid,
) -> Option<&mut ScopeLocalRegistries> {
if !self.stack.iter().any(|handle| handle.uuid == *uuid) {
return None;
}
Some(self.scope_registries.entry(*uuid).or_default())
}
pub(crate) fn collect_scope_local_registries<'a, T: RegistryEntry>(
&'a self,
field: impl Fn(&'a ScopeLocalRegistries) -> &'a SortedRegistry<T>,
) -> Vec<&'a SortedRegistry<T>> {
self.stack
.iter()
.filter_map(|handle| self.scope_registries.get(&handle.uuid))
.map(field)
.collect()
}
pub(crate) fn snapshot_scope_local_registries<T: RegistryEntry + Clone>(
&self,
field: impl Fn(&ScopeLocalRegistries) -> &SortedRegistry<T>,
) -> Vec<SortedRegistry<T>> {
self.collect_scope_local_registries(field)
.into_iter()
.cloned()
.collect()
}
pub(crate) fn collect_scope_local_subscribers(&self) -> Vec<EventSubscriberFn> {
self.stack
.iter()
.filter_map(|handle| self.scope_registries.get(&handle.uuid))
.flat_map(|registries| registries.event_subscribers.values().cloned())
.collect()
}
}
impl std::fmt::Debug for ScopeStack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ScopeStack")
.field("stack", &self.stack)
.field("scope_registries_count", &self.scope_registries.len())
.field("fresh_agent_count", &self.fresh_agents.len())
.finish()
}
}
impl Default for ScopeStack {
fn default() -> Self {
Self::new()
}
}
pub type ScopeStackHandle = Arc<RwLock<ScopeStack>>;
#[derive(Clone)]
pub(crate) struct AnchoredActiveEvent {
event_uuid: Uuid,
scope_stack: Weak<RwLock<ScopeStack>>,
anchor_scope_uuid: Uuid,
}
#[derive(Clone)]
pub struct ThreadScopeStackBinding {
stack: ScopeStackHandle,
explicit: bool,
active_event: Option<AnchoredActiveEvent>,
active_event_trace_context: Option<W3cTraceContext>,
}
impl ThreadScopeStackBinding {
pub fn stack(&self) -> ScopeStackHandle {
self.stack.clone()
}
}
pub fn create_scope_stack() -> ScopeStackHandle {
Arc::new(RwLock::new(ScopeStack::new()))
}
pub(crate) fn root_scope_stack_snapshot(stack: &ScopeStackHandle) -> Result<ScopeStackHandle> {
let root_only = stack
.read()
.map_err(|error| FlowError::Internal(format!("scope stack lock poisoned: {error}")))?
.root_only_snapshot();
Ok(Arc::new(RwLock::new(root_only)))
}
#[doc(hidden)]
pub(crate) fn snapshot_scope_stack(handle: &ScopeStackHandle) -> Result<ScopeStackHandle> {
let stack = handle
.read()
.unwrap_or_else(|error| error.into_inner())
.snapshot();
Ok(Arc::new(RwLock::new(stack)))
}
pub fn create_scope_stack_from_propagation(
context: &PropagationContext,
) -> Result<ScopeStackHandle> {
Ok(Arc::new(RwLock::new(ScopeStack::from_propagation(
context,
)?)))
}
pub fn fork_scope_stack() -> Result<ScopeStackHandle> {
let context = capture_propagation_context()?;
create_scope_stack_from_propagation(&context)
}
fn captured_w3c_headers(
stack: &ScopeStack,
active_uuid: Option<Uuid>,
parent_uuid: Uuid,
) -> (Option<String>, Option<String>) {
if let Some(active_context) = active_event_trace_context() {
(
Some(active_context.traceparent.clone()),
active_context.tracestate.clone(),
)
} else if active_uuid.is_some() {
stack.w3c_headers_for_span(parent_uuid, stack.top().uuid)
} else {
stack.event_w3c_headers(Some(parent_uuid))
}
}
fn captured_imported_w3c_headers(
stack: &ScopeStack,
parent_uuid: Uuid,
) -> (Option<String>, Option<String>) {
if stack.propagated_parent_uuid == Some(parent_uuid) {
return (
stack.propagated_traceparent.clone(),
stack.propagated_tracestate.clone(),
);
}
w3c_span_context(
stack.propagated_traceparent.as_deref(),
stack.propagated_tracestate.as_deref(),
)
.map(|parent| w3c_headers_for_parent(parent, parent_uuid))
.unwrap_or_default()
}
pub fn capture_propagation_context() -> Result<PropagationContext> {
let active_uuid = active_event_uuid();
let parent_uuid = active_uuid.unwrap_or_else(|| task_scope_top().uuid);
let stack = current_scope_stack();
let stack_guard = stack
.read()
.map_err(|error| FlowError::Internal(error.to_string()))?;
let root_uuid = stack_guard.event_propagation_root_uuid();
let (traceparent, tracestate) = captured_w3c_headers(&stack_guard, active_uuid, parent_uuid);
let traceparent = traceparent
.or_else(|| active_uuid.map(|uuid| crate::observability::format_traceparent(uuid, uuid)));
let context = PropagationContext {
version: PropagationContext::VERSION,
root_uuid,
parent_uuid,
traceparent,
tracestate,
};
context.validate()?;
Ok(context)
}
pub fn capture_rootless_propagation_context() -> Result<PropagationContext> {
let parent_uuid = active_event_uuid().unwrap_or_else(|| task_scope_top().uuid);
let stack = current_scope_stack();
let stack_guard = stack
.read()
.map_err(|error| FlowError::Internal(error.to_string()))?;
let (traceparent, tracestate) = captured_imported_w3c_headers(&stack_guard, parent_uuid);
let context = PropagationContext {
version: PropagationContext::VERSION,
root_uuid: None,
parent_uuid,
traceparent,
tracestate,
};
context.validate()?;
Ok(context)
}
pub fn capture_propagation_context_with_root(
root_uuid: Option<Uuid>,
) -> Result<PropagationContext> {
let Some(root_uuid) = root_uuid else {
return capture_rootless_propagation_context();
};
let mut context = capture_rootless_propagation_context()?;
context.root_uuid = Some(root_uuid);
context.validate()?;
Ok(context)
}
pub(crate) fn capture_w3c_trace_context() -> Result<W3cTraceContext> {
let context = capture_propagation_context()?;
let Some(traceparent) = context.traceparent else {
return Err(FlowError::InvalidArgument(
"no emitted Relay scope is available for W3C trace context capture".into(),
));
};
W3cTraceContext::new(traceparent, context.tracestate).map_err(|_| {
FlowError::InvalidArgument(
"no emitted Relay scope is available for W3C trace context capture".into(),
)
})
}
pub fn capture_traceparent() -> Result<String> {
Ok(capture_w3c_trace_context()?.traceparent)
}
impl ScopeStack {
fn propagated_span_context(&self, uuid: Uuid) -> Option<SpanContext> {
if self.propagated_parent_uuid != Some(uuid) {
return None;
}
if let Some(context) = w3c_span_context(
self.propagated_traceparent.as_deref(),
self.propagated_tracestate.as_deref(),
) {
return Some(context);
}
let root_uuid = self.propagated_root_uuid?;
if !is_usable_relay_identifier(root_uuid) || !is_usable_relay_identifier(uuid) {
return None;
}
Some(SpanContext::new(
crate::observability::relay_trace_id(root_uuid),
crate::observability::relay_span_id(uuid),
TraceFlags::SAMPLED,
true,
TraceState::default(),
))
}
fn local_span_context(&self, uuid: Uuid) -> Option<SpanContext> {
self.local_span_context_inner(uuid, self.stack.len(), &mut HashSet::new())
}
fn local_span_context_inner(
&self,
uuid: Uuid,
before_index: usize,
visiting: &mut HashSet<Uuid>,
) -> Option<SpanContext> {
if !is_usable_relay_identifier(uuid) || !visiting.insert(uuid) {
return None;
}
if let Some(context) = self.propagated_span_context(uuid) {
visiting.remove(&uuid);
return Some(context);
}
let (index, scope) = self
.stack
.iter()
.enumerate()
.take(before_index)
.skip(1)
.rev()
.find(|(_, scope)| scope.uuid == uuid)?;
let stack_parent_context = |visiting: &mut HashSet<Uuid>| {
index
.checked_sub(1)
.and_then(|parent_index| self.stack.get(parent_index))
.and_then(|parent| self.parent_span_context_inner(parent.uuid, index, visiting))
};
let parent_context = match scope.parent_uuid {
Some(parent_uuid) => self
.parent_span_context_inner(parent_uuid, index, visiting)
.or_else(|| {
if is_usable_relay_identifier(parent_uuid) && self.find(&parent_uuid).is_none()
{
stack_parent_context(visiting)
} else {
None
}
}),
None => stack_parent_context(visiting),
};
let context = match parent_context {
Some(parent) => SpanContext::new(
parent.trace_id(),
crate::observability::relay_span_id(uuid),
parent.trace_flags(),
false,
parent.trace_state().clone(),
),
None => SpanContext::new(
crate::observability::relay_trace_id(uuid),
crate::observability::relay_span_id(uuid),
TraceFlags::SAMPLED,
false,
TraceState::default(),
),
};
visiting.remove(&uuid);
context.is_valid().then_some(context)
}
fn parent_span_context_inner(
&self,
uuid: Uuid,
before_index: usize,
visiting: &mut HashSet<Uuid>,
) -> Option<SpanContext> {
self.propagated_span_context(uuid)
.or_else(|| self.local_span_context_inner(uuid, before_index, visiting))
}
fn w3c_headers_for_span(
&self,
span_uuid: Uuid,
causal_parent_uuid: Uuid,
) -> (Option<String>, Option<String>) {
if span_uuid == causal_parent_uuid
&& self.propagated_parent_uuid == Some(causal_parent_uuid)
&& self.propagated_traceparent.is_some()
{
return (
self.propagated_traceparent.clone(),
self.propagated_tracestate.clone(),
);
}
self.local_span_context(causal_parent_uuid)
.or_else(|| {
if is_usable_relay_identifier(causal_parent_uuid)
&& self.find(&causal_parent_uuid).is_none()
{
self.local_span_context(self.top().uuid)
} else {
None
}
})
.map(|parent| w3c_headers_for_parent(parent, span_uuid))
.unwrap_or_default()
}
}
fn w3c_headers_for_parent(
parent: SpanContext,
parent_uuid: Uuid,
) -> (Option<String>, Option<String>) {
let child = SpanContext::new(
parent.trace_id(),
crate::observability::relay_span_id(parent_uuid),
parent.trace_flags(),
false,
parent.trace_state().clone(),
);
let context = opentelemetry::Context::new().with_remote_span_context(child);
let mut carrier = HashMap::new();
TraceContextPropagator::new().inject_context(&context, &mut carrier);
(
carrier.remove("traceparent"),
carrier
.remove("tracestate")
.filter(|value| !value.is_empty()),
)
}
pub(crate) fn trace_context_for_managed_span(
span_uuid: Uuid,
causal_parent_uuid: Option<Uuid>,
) -> Result<W3cTraceContext> {
let stack = current_scope_stack();
let stack_guard = stack
.read()
.map_err(|error| FlowError::Internal(error.to_string()))?;
let (traceparent, tracestate) = if causal_parent_uuid == active_event_uuid() {
active_event_trace_context()
.and_then(|context| context.span_context())
.map(|context| w3c_headers_for_parent(context, span_uuid))
.unwrap_or_else(|| stack_guard.w3c_headers_for_span(span_uuid, stack_guard.top().uuid))
} else {
causal_parent_uuid
.map(|parent_uuid| stack_guard.w3c_headers_for_span(span_uuid, parent_uuid))
.unwrap_or_default()
};
W3cTraceContext::new(
traceparent
.unwrap_or_else(|| crate::observability::format_traceparent(span_uuid, span_uuid)),
tracestate,
)
}
pub(crate) fn capture_trace_context() -> Result<(String, Option<String>)> {
let context = capture_w3c_trace_context()?;
Ok((context.traceparent, context.tracestate))
}
tokio::task_local! {
pub static TASK_SCOPE_STACK: ScopeStackHandle;
static ACTIVE_EVENT: AnchoredActiveEvent;
static ACTIVE_EVENT_TRACE_CONTEXT: Option<W3cTraceContext>;
}
pub async fn with_active_event_uuid<T>(uuid: Uuid, future: impl Future<Output = T>) -> T {
with_active_event_trace_context(uuid, None, future).await
}
pub(crate) async fn with_active_event_trace_context<T>(
uuid: Uuid,
trace_context: Option<W3cTraceContext>,
future: impl Future<Output = T>,
) -> T {
let (scope_stack, anchor_scope_uuid) = scope_stack_identity_and_anchor();
let active_event = AnchoredActiveEvent {
event_uuid: uuid,
scope_stack,
anchor_scope_uuid,
};
with_anchored_active_event(active_event, trace_context, future).await
}
pub(crate) fn with_thread_active_event_trace_context<T>(
uuid: Uuid,
trace_context: Option<W3cTraceContext>,
callback: impl FnOnce() -> T,
) -> T {
struct RestoreActiveEvent {
event: Option<AnchoredActiveEvent>,
trace_context: Option<W3cTraceContext>,
}
impl Drop for RestoreActiveEvent {
fn drop(&mut self) {
THREAD_ACTIVE_EVENT.with(|event| *event.borrow_mut() = self.event.take());
THREAD_ACTIVE_EVENT_TRACE_CONTEXT
.with(|context| *context.borrow_mut() = self.trace_context.take());
}
}
let (scope_stack, anchor_scope_uuid) = scope_stack_identity_and_anchor();
let event = AnchoredActiveEvent {
event_uuid: uuid,
scope_stack,
anchor_scope_uuid,
};
let _restore = RestoreActiveEvent {
event: THREAD_ACTIVE_EVENT.with(|current| current.replace(Some(event))),
trace_context: THREAD_ACTIVE_EVENT_TRACE_CONTEXT
.with(|current| current.replace(trace_context)),
};
callback()
}
pub(crate) async fn with_anchored_active_event<T>(
active_event: AnchoredActiveEvent,
trace_context: Option<W3cTraceContext>,
future: impl Future<Output = T>,
) -> T {
ACTIVE_EVENT
.scope(
active_event,
ACTIVE_EVENT_TRACE_CONTEXT.scope(trace_context, future),
)
.await
}
pub(crate) fn capture_anchored_active_event() -> Option<AnchoredActiveEvent> {
ACTIVE_EVENT
.try_with(Clone::clone)
.ok()
.or_else(thread_active_event)
}
pub(crate) fn rebind_active_event_to_stack(
active_event: AnchoredActiveEvent,
scope_stack: &ScopeStackHandle,
) -> AnchoredActiveEvent {
let guard = scope_stack
.read()
.unwrap_or_else(|error| error.into_inner());
AnchoredActiveEvent {
event_uuid: active_event.event_uuid,
scope_stack: Arc::downgrade(scope_stack),
anchor_scope_uuid: if guard.find(&active_event.anchor_scope_uuid).is_some() {
active_event.anchor_scope_uuid
} else {
guard.top().uuid
},
}
}
pub(crate) fn active_event_uuid() -> Option<Uuid> {
ACTIVE_EVENT
.try_with(|event| event.event_uuid)
.ok()
.or_else(thread_active_event_uuid)
}
pub(crate) fn active_event_trace_context() -> Option<W3cTraceContext> {
match ACTIVE_EVENT_TRACE_CONTEXT.try_with(Clone::clone) {
Ok(context) => context,
Err(_) => {
thread_active_event()?;
THREAD_ACTIVE_EVENT_TRACE_CONTEXT.with(|context| context.borrow().clone())
}
}
}
fn thread_active_event() -> Option<AnchoredActiveEvent> {
let mut event = THREAD_ACTIVE_EVENT.with(|active| active.borrow().clone())?;
let stack = current_scope_stack();
let event_stack = event.scope_stack.upgrade()?;
if !Arc::ptr_eq(&event_stack, &stack) {
return None;
}
let guard = stack.read().unwrap_or_else(|error| error.into_inner());
if event.anchor_scope_uuid != guard.top().uuid {
if guard.find(&event.anchor_scope_uuid).is_some() {
return None;
}
event.anchor_scope_uuid = guard.top().uuid;
THREAD_ACTIVE_EVENT.with(|active| *active.borrow_mut() = Some(event.clone()));
}
Some(event)
}
pub(crate) fn thread_active_event_uuid() -> Option<Uuid> {
thread_active_event().map(|event| event.event_uuid)
}
thread_local! {
static SCOPE_STACK_OVERRIDE: RefCell<Option<ScopeStackHandle>> = const { RefCell::new(None) };
static THREAD_SCOPE_STACK: RefCell<ScopeStackHandle> = RefCell::new(create_scope_stack());
static THREAD_SCOPE_STACK_EXPLICIT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
static THREAD_ACTIVE_EVENT: RefCell<Option<AnchoredActiveEvent>> = const { RefCell::new(None) };
static THREAD_ACTIVE_EVENT_TRACE_CONTEXT: RefCell<Option<W3cTraceContext>> = const { RefCell::new(None) };
}
pub fn current_scope_stack() -> ScopeStackHandle {
if let Some(stack) = SCOPE_STACK_OVERRIDE.with(|stack| stack.borrow().clone()) {
return stack;
}
TASK_SCOPE_STACK
.try_with(|stack| stack.clone())
.unwrap_or_else(|_| THREAD_SCOPE_STACK.with(|stack| stack.borrow().clone()))
}
pub(crate) fn current_context_scope_stack() -> Option<ScopeStackHandle> {
SCOPE_STACK_OVERRIDE
.with(|stack| stack.borrow().clone())
.or_else(|| TASK_SCOPE_STACK.try_with(Clone::clone).ok())
}
pub fn with_scope_stack<T>(handle: ScopeStackHandle, f: impl FnOnce() -> T) -> T {
struct OverrideGuard {
previous: Option<ScopeStackHandle>,
}
impl Drop for OverrideGuard {
fn drop(&mut self) {
let previous = self.previous.take();
SCOPE_STACK_OVERRIDE.with(|stack| *stack.borrow_mut() = previous);
}
}
let previous = SCOPE_STACK_OVERRIDE.with(|stack| stack.replace(Some(handle)));
let _guard = OverrideGuard { previous };
f()
}
pub fn set_thread_scope_stack(handle: ScopeStackHandle) {
clear_thread_active_event_for_stack_change(&handle);
THREAD_SCOPE_STACK.with(|stack| *stack.borrow_mut() = handle);
THREAD_SCOPE_STACK_EXPLICIT.with(|flag| flag.set(true));
}
pub fn capture_thread_scope_stack() -> ThreadScopeStackBinding {
let stack = THREAD_SCOPE_STACK.with(|stack| stack.borrow().clone());
let explicit = THREAD_SCOPE_STACK_EXPLICIT.with(|flag| flag.get());
ThreadScopeStackBinding {
stack,
explicit,
active_event: THREAD_ACTIVE_EVENT.with(|event| event.borrow().clone()),
active_event_trace_context: THREAD_ACTIVE_EVENT_TRACE_CONTEXT
.with(|context| context.borrow().clone()),
}
}
pub fn restore_thread_scope_stack(binding: ThreadScopeStackBinding) {
THREAD_SCOPE_STACK.with(|stack| *stack.borrow_mut() = binding.stack);
THREAD_SCOPE_STACK_EXPLICIT.with(|flag| flag.set(binding.explicit));
THREAD_ACTIVE_EVENT.with(|event| *event.borrow_mut() = binding.active_event);
THREAD_ACTIVE_EVENT_TRACE_CONTEXT
.with(|context| *context.borrow_mut() = binding.active_event_trace_context);
}
#[cfg(feature = "worker-grpc")]
pub(crate) fn install_thread_continuation_context(
scope_stack: &ScopeStackHandle,
active_event: Option<AnchoredActiveEvent>,
active_event_trace_context: Option<W3cTraceContext>,
) -> ThreadScopeStackBinding {
let previous = capture_thread_scope_stack();
sync_thread_scope_stack(scope_stack.clone());
THREAD_ACTIVE_EVENT.with(|event| {
*event.borrow_mut() =
active_event.map(|event| rebind_active_event_to_stack(event, scope_stack));
});
THREAD_ACTIVE_EVENT_TRACE_CONTEXT
.with(|context| *context.borrow_mut() = active_event_trace_context);
previous
}
pub fn sync_thread_scope_stack(handle: ScopeStackHandle) {
clear_thread_active_event_for_stack_change(&handle);
THREAD_SCOPE_STACK.with(|stack| *stack.borrow_mut() = handle);
}
fn clear_thread_active_event_for_stack_change(handle: &ScopeStackHandle) {
let stack_changed = THREAD_SCOPE_STACK.with(|current| !Arc::ptr_eq(¤t.borrow(), handle));
if stack_changed {
THREAD_ACTIVE_EVENT.with(|event| *event.borrow_mut() = None);
THREAD_ACTIVE_EVENT_TRACE_CONTEXT.with(|context| *context.borrow_mut() = None);
}
}
pub(crate) fn sync_thread_active_event_for_stack(scope_stack: &ScopeStackHandle) {
let active_event = capture_anchored_active_event()
.map(|active_event| rebind_active_event_to_stack(active_event, scope_stack));
let trace_context = active_event
.as_ref()
.and_then(|_| active_event_trace_context());
THREAD_ACTIVE_EVENT.with(|event| *event.borrow_mut() = active_event);
THREAD_ACTIVE_EVENT_TRACE_CONTEXT.with(|context| *context.borrow_mut() = trace_context);
}
fn scope_stack_identity_and_anchor() -> (Weak<RwLock<ScopeStack>>, Uuid) {
let stack = current_scope_stack();
let guard = stack.read().unwrap_or_else(|error| error.into_inner());
(Arc::downgrade(&stack), guard.top().uuid)
}
pub fn scope_stack_active() -> bool {
if SCOPE_STACK_OVERRIDE.with(|stack| stack.borrow().is_some()) {
return true;
}
TASK_SCOPE_STACK
.try_with(|_| true)
.unwrap_or_else(|_| THREAD_SCOPE_STACK_EXPLICIT.with(|flag| flag.get()))
}
pub fn propagate_scope_to_thread() -> Result<ScopeStackHandle> {
if !scope_stack_active() {
return Err(FlowError::Internal(
"no active scope stack in current context; call create_scope_stack() and set_thread_scope_stack() first"
.into(),
));
}
Ok(current_scope_stack())
}
pub fn task_scope_top() -> ScopeHandle {
let stack = current_scope_stack();
let guard = stack.read().expect("scope stack lock poisoned");
guard.top().clone()
}
pub fn task_scope_push(handle: ScopeHandle) {
let stack = current_scope_stack();
let mut guard = stack.write().expect("scope stack lock poisoned");
guard.push(handle);
}
pub fn task_scope_remove(uuid: &Uuid) -> Result<ScopeHandle> {
let stack = current_scope_stack();
let mut guard = stack.write().expect("scope stack lock poisoned");
guard.remove(uuid)
}