use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::future::Future;
use std::sync::{Arc, RwLock};
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>,
}
#[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,
}
impl PropagationContext {
pub const VERSION: u16 = 1;
pub fn to_json(&self) -> Result<String> {
self.validate()?;
Ok(serde_json::to_string(self).expect("PropagationContext is always JSON serializable"))
}
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)
}
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)))
{
let bytes = uuid.as_bytes();
if bytes.iter().all(|byte| *byte == 0) || bytes[8..].iter().all(|byte| *byte == 0) {
return Err(FlowError::InvalidArgument(format!(
"propagation context {name} is not a usable Relay identifier"
)));
}
}
Ok(())
}
}
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,
}
}
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,
}
}
fn from_propagation(context: &PropagationContext) -> Result<Self> {
context.validate()?;
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: context.root_uuid.map(|_| context.parent_uuid),
})
}
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 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 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 struct ThreadScopeStackBinding {
stack: ScopeStackHandle,
explicit: bool,
}
impl ThreadScopeStackBinding {
pub fn stack(&self) -> ScopeStackHandle {
self.stack.clone()
}
}
pub fn create_scope_stack() -> ScopeStackHandle {
Arc::new(RwLock::new(ScopeStack::new()))
}
#[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)
}
pub fn capture_propagation_context() -> Result<PropagationContext> {
capture_propagation_context_with_root(None)
}
pub fn capture_propagation_context_with_root(
root_uuid: Option<Uuid>,
) -> Result<PropagationContext> {
let context = PropagationContext {
version: PropagationContext::VERSION,
root_uuid,
parent_uuid: ACTIVE_EVENT_UUID
.try_with(|uuid| *uuid)
.unwrap_or_else(|_| task_scope_top().uuid),
};
context.validate()?;
Ok(context)
}
tokio::task_local! {
pub static TASK_SCOPE_STACK: ScopeStackHandle;
static ACTIVE_EVENT_UUID: Uuid;
}
pub async fn with_active_event_uuid<T>(uuid: Uuid, future: impl Future<Output = T>) -> T {
ACTIVE_EVENT_UUID.scope(uuid, future).await
}
pub(crate) fn active_event_uuid() -> Option<Uuid> {
ACTIVE_EVENT_UUID.try_with(|uuid| *uuid).ok()
}
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) };
}
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) {
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 }
}
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));
}
pub fn sync_thread_scope_stack(handle: ScopeStackHandle) {
THREAD_SCOPE_STACK.with(|stack| *stack.borrow_mut() = handle);
}
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)
}