2026-02-06 11:25:37 +00:00
|
|
|
use std::any::Any;
|
2026-02-20 17:34:43 +00:00
|
|
|
use std::any::TypeId;
|
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
|
use std::sync::Arc;
|
2026-02-06 11:25:37 +00:00
|
|
|
|
2026-02-07 10:36:45 +00:00
|
|
|
use crate::Error;
|
2026-01-25 13:38:34 +00:00
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
// ─── ExitValue ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Opaque typed value attached to a completed actor's exit.
|
|
|
|
|
///
|
|
|
|
|
/// Created via [`Ctx::stop_with`]. Delivered to monitors/watchers in
|
|
|
|
|
/// [`Down::exit_value`] and [`ActorExited::exit_value`].
|
|
|
|
|
///
|
|
|
|
|
/// Clone is an `Arc` bump (zero allocation).
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct ExitValue(Arc<dyn Any + Send + Sync>);
|
|
|
|
|
|
|
|
|
|
impl ExitValue {
|
|
|
|
|
/// Wrap a typed value as an opaque exit value.
|
|
|
|
|
pub fn new<T: Any + Send + Sync>(value: T) -> Self {
|
|
|
|
|
Self(Arc::new(value))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Attempt to downcast to a concrete type by reference.
|
|
|
|
|
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
|
|
|
|
|
self.0.downcast_ref::<T>()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq for ExitValue {
|
|
|
|
|
fn eq(&self, _other: &Self) -> bool {
|
|
|
|
|
false // opaque blob — always not equal
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Eq for ExitValue {}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Debug for ExitValue {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
f.write_str("ExitValue(..)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// System-level information visible to actors.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct SystemInfo {
|
|
|
|
|
/// Index of the worker thread this actor is running on.
|
|
|
|
|
pub worker_id: usize,
|
|
|
|
|
/// Total number of worker threads in the runtime.
|
|
|
|
|
pub num_workers: usize,
|
|
|
|
|
/// Total number of live actors across all workers.
|
|
|
|
|
pub total_actors: usize,
|
|
|
|
|
/// Milliseconds since the runtime was created.
|
|
|
|
|
pub uptime_ms: u64,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-13 07:42:44 +00:00
|
|
|
/// Why an actor exited.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
|
|
|
pub enum ExitReason {
|
|
|
|
|
/// Actor was explicitly stopped or removed from the pool.
|
|
|
|
|
Stopped,
|
|
|
|
|
/// Actor panicked during message handling.
|
|
|
|
|
Panicked,
|
|
|
|
|
/// The node hosting the actor left the cluster (SWIM Dead).
|
|
|
|
|
NodeDown,
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Actor stopped with a typed exit value (via [`Ctx::stop_with`]).
|
|
|
|
|
Completed,
|
2026-02-13 07:42:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Delivered to watchers when a watched actor exits.
|
|
|
|
|
///
|
|
|
|
|
/// Implements `Message` (Clone + Send + Sync + 'static) so it can be
|
|
|
|
|
/// delivered through normal mailbox channels.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
|
|
|
pub struct ActorExited {
|
|
|
|
|
/// The address of the actor that died.
|
|
|
|
|
pub addr: ActorAddress,
|
|
|
|
|
/// Why it exited.
|
|
|
|
|
pub reason: ExitReason,
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Typed exit value if the actor called [`Ctx::stop_with`].
|
|
|
|
|
#[cfg_attr(feature = "serde", serde(skip))]
|
|
|
|
|
pub exit_value: Option<ExitValue>,
|
2026-02-13 07:42:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
impl PartialEq for ActorExited {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.addr == other.addr && self.reason == other.reason
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Eq for ActorExited {}
|
|
|
|
|
|
2026-01-25 13:38:34 +00:00
|
|
|
/// The primary trait defining data that can be passed to and from actor processes
|
|
|
|
|
pub trait Message: 'static + Sized + Clone + Send + Sync {}
|
|
|
|
|
impl<T: 'static + Sized + Clone + Send + Sync> Message for T {}
|
|
|
|
|
|
|
|
|
|
pub trait ActorInterface: 'static + Send {
|
|
|
|
|
type Incoming: Message;
|
|
|
|
|
type Response: Message;
|
2026-02-06 11:25:37 +00:00
|
|
|
fn handle(&mut self, ctx: &Ctx, msg: Self::Incoming);
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
|
|
|
|
|
/// Called once after the actor is added to a worker, before the first message.
|
|
|
|
|
/// Receives `&Ctx` so the actor can send messages or spawn children during init.
|
|
|
|
|
///
|
|
|
|
|
/// If `on_start` panics, the actor is immediately poisoned (no restart attempted).
|
|
|
|
|
fn on_start(&mut self, _ctx: &Ctx) {}
|
|
|
|
|
|
|
|
|
|
/// Called when the actor is being gracefully stopped (via `ctx.stop_self()` or
|
|
|
|
|
/// `Runtime::stop_actor()`), before removal from the worker pool.
|
|
|
|
|
///
|
|
|
|
|
/// NOT called when an actor is poisoned by panic — panicked actors may have
|
|
|
|
|
/// corrupt state and calling methods on them is unsafe.
|
|
|
|
|
fn on_stop(&mut self, _ctx: &Ctx) {}
|
|
|
|
|
|
|
|
|
|
/// Called when a monitored actor dies (via [`Ctx::monitor`]).
|
|
|
|
|
///
|
|
|
|
|
/// Override this to react to death notifications without making [`Down`]
|
|
|
|
|
/// your `Incoming` type. Default: no-op (the `Down` message is silently consumed).
|
|
|
|
|
///
|
|
|
|
|
/// If your `Incoming` type IS `Down`, this method is never called — the
|
|
|
|
|
/// normal `handle()` receives the message instead.
|
|
|
|
|
fn handle_down(&mut self, _ctx: &Ctx, _down: Down) {}
|
2026-02-13 07:42:44 +00:00
|
|
|
|
|
|
|
|
/// Called when a watched actor exits. Override to react to death notifications.
|
|
|
|
|
///
|
|
|
|
|
/// Default: no-op (notification is silently consumed).
|
|
|
|
|
fn on_actor_exit(&mut self, _ctx: &Ctx, _exited: ActorExited) {}
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A unique address for this actor. 32 bytes is overkill for a small application,
|
|
|
|
|
/// but most systems are powerful, and this allows us to create a global map of
|
|
|
|
|
/// actor processes in the future, without worrying about collision.
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
2026-02-08 16:18:39 +00:00
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
2026-01-25 13:38:34 +00:00
|
|
|
pub struct ActorAddress(pub [u8; 32]);
|
2026-02-08 16:18:39 +00:00
|
|
|
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
/// Custom Hash: only hash the first 8 bytes since all 32 are random.
|
|
|
|
|
/// SipHash on 8 bytes is ~3x faster than on 32 bytes, with identical
|
|
|
|
|
/// collision properties (2^64 possible values from cryptographic randomness).
|
|
|
|
|
impl std::hash::Hash for ActorAddress {
|
|
|
|
|
#[inline]
|
|
|
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
|
|
|
// SAFETY: ActorAddress is always 32 bytes, so [..8] is valid.
|
|
|
|
|
state.write_u64(u64::from_ne_bytes(
|
|
|
|
|
self.0[..8].try_into().unwrap(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 16:18:39 +00:00
|
|
|
impl std::fmt::Display for ActorAddress {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
for b in &self.0[..8] {
|
|
|
|
|
write!(f, "{:02x}", b)?;
|
|
|
|
|
}
|
|
|
|
|
write!(f, "\u{2026}")
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-25 13:38:34 +00:00
|
|
|
impl ActorAddress {
|
|
|
|
|
pub fn new_random() -> Self {
|
|
|
|
|
let mut bytes = [0u8; 32];
|
2026-02-06 14:45:19 +00:00
|
|
|
crate::get_random(&mut bytes);
|
2026-01-25 13:38:34 +00:00
|
|
|
Self(bytes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
// ─── Environment ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// A typed key-value map that flows from parent to child at spawn time.
|
|
|
|
|
///
|
|
|
|
|
/// Analogous to Unix `environ` — provides inherited configuration without
|
|
|
|
|
/// threading values through every constructor. Clone is an Arc bump (zero allocation).
|
|
|
|
|
///
|
|
|
|
|
/// Values are stored as `Arc<dyn Any>` so that [`EnvironmentBuilder::from_env`]
|
|
|
|
|
/// can clone individual entries cheaply (Arc bump) for copy-on-write overrides.
|
|
|
|
|
#[derive(Clone, Default)]
|
|
|
|
|
pub struct Environment {
|
|
|
|
|
inner: Arc<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Environment {
|
|
|
|
|
/// Create an empty environment.
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
inner: Arc::new(HashMap::new()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read a typed value from the environment.
|
|
|
|
|
pub fn get<T: Any + Send + Sync>(&self) -> Option<&T> {
|
|
|
|
|
self.inner
|
|
|
|
|
.get(&TypeId::of::<T>())
|
|
|
|
|
.and_then(|v| v.downcast_ref::<T>())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if the environment contains a value of type `T`.
|
|
|
|
|
pub fn contains<T: Any + Send + Sync>(&self) -> bool {
|
|
|
|
|
self.inner.contains_key(&TypeId::of::<T>())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns `true` if the environment has no values.
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
|
self.inner.is_empty()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Number of typed values in the environment.
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
|
self.inner.len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if the environment contains a value with the given `TypeId`.
|
|
|
|
|
///
|
|
|
|
|
/// Type-erased version of [`contains`](Self::contains) — used by
|
|
|
|
|
/// `ServiceRegistry::inject_into` to skip keys already present.
|
|
|
|
|
pub fn contains_type_id(&self, type_id: TypeId) -> bool {
|
|
|
|
|
self.inner.contains_key(&type_id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Builder for constructing an [`Environment`].
|
|
|
|
|
///
|
|
|
|
|
/// Allows inserting/replacing typed values before freezing into an immutable `Environment`.
|
|
|
|
|
pub struct EnvironmentBuilder {
|
|
|
|
|
map: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EnvironmentBuilder {
|
|
|
|
|
/// Create an empty builder.
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
map: HashMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a builder pre-populated with values from an existing environment.
|
|
|
|
|
///
|
|
|
|
|
/// This enables copy-on-write overrides: clone the parent's map, modify, then freeze.
|
|
|
|
|
/// Cloning entries is cheap — each value is `Arc`-wrapped.
|
|
|
|
|
pub fn from_env(env: &Environment) -> Self {
|
|
|
|
|
let map = env.inner.as_ref().clone();
|
|
|
|
|
Self { map }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Insert or replace a typed value.
|
|
|
|
|
pub fn set<T: Any + Send + Sync>(mut self, value: T) -> Self {
|
|
|
|
|
self.map.insert(TypeId::of::<T>(), Arc::new(value));
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Insert or replace a typed value (mutable reference version).
|
|
|
|
|
pub fn set_mut<T: Any + Send + Sync>(&mut self, value: T) -> &mut Self {
|
|
|
|
|
self.map.insert(TypeId::of::<T>(), Arc::new(value));
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Insert a type-erased value by `TypeId`.
|
|
|
|
|
///
|
|
|
|
|
/// Used by `ServiceRegistry::inject_into` to merge pre-built bindings
|
|
|
|
|
/// without knowing concrete types at compile time.
|
|
|
|
|
pub fn set_raw(&mut self, type_id: TypeId, value: Arc<dyn Any + Send + Sync>) -> &mut Self {
|
|
|
|
|
self.map.insert(type_id, value);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Freeze the builder into an immutable `Environment`.
|
|
|
|
|
pub fn build(self) -> Environment {
|
|
|
|
|
Environment {
|
|
|
|
|
inner: Arc::new(self.map),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for EnvironmentBuilder {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Well-Known Environment Keys ─────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Milliseconds since runtime creation when this actor was spawned.
|
|
|
|
|
/// Injected by StdExtension (opt-in at runtime level). Read via `ctx.env::<SpawnTimestamp>()`.
|
|
|
|
|
/// Uses the same time base as `SystemInfo::uptime_ms`.
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
pub struct SpawnTimestamp(pub u64);
|
|
|
|
|
|
|
|
|
|
/// Logical name assigned via `spawn_named()`. Read via `ctx.env::<LogicalName>()`.
|
|
|
|
|
/// None for unnamed actors.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
|
pub struct LogicalName(pub String);
|
|
|
|
|
|
|
|
|
|
impl LogicalName {
|
|
|
|
|
pub fn as_str(&self) -> &str {
|
|
|
|
|
&self.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A typed service binding stored in the environment.
|
|
|
|
|
///
|
|
|
|
|
/// `S` is a zero-sized marker type that identifies the service (e.g., `struct Datastore;`).
|
|
|
|
|
/// Stored via `ServiceRegistry` and read via `ctx.resource::<S>()`.
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct ServiceBinding<S: 'static + Send + Sync> {
|
|
|
|
|
pub addr: ActorAddress,
|
|
|
|
|
_marker: std::marker::PhantomData<S>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<S: 'static + Send + Sync> ServiceBinding<S> {
|
|
|
|
|
pub fn new(addr: ActorAddress) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
addr,
|
|
|
|
|
_marker: std::marker::PhantomData,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Capabilities ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Per-actor capability set controlling what operations the actor can perform.
|
|
|
|
|
///
|
|
|
|
|
/// When present in an actor's [`Environment`], enforcement is active — the actor
|
|
|
|
|
/// can only perform operations granted by the set. When absent, the actor is
|
|
|
|
|
/// unrestricted (backward compatible). Inherits from parent to child via normal
|
|
|
|
|
/// environment inheritance.
|
|
|
|
|
///
|
|
|
|
|
/// Built via fluent API: `CapabilitySet::new().with_send(addr).with_spawn()`.
|
|
|
|
|
#[derive(Clone, Default)]
|
|
|
|
|
pub struct CapabilitySet {
|
|
|
|
|
send_any: HashSet<ActorAddress, crate::delivery::AddrBuildHasher>,
|
|
|
|
|
send_typed: HashSet<(TypeId, ActorAddress)>,
|
|
|
|
|
can_spawn: bool,
|
|
|
|
|
service_types: HashSet<TypeId>,
|
|
|
|
|
monitor_targets: HashSet<ActorAddress, crate::delivery::AddrBuildHasher>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CapabilitySet {
|
|
|
|
|
pub fn new() -> Self { Self::default() }
|
|
|
|
|
|
|
|
|
|
// ── Builder methods (fluent) ──
|
|
|
|
|
pub fn with_send(mut self, addr: ActorAddress) -> Self {
|
|
|
|
|
self.send_any.insert(addr); self
|
|
|
|
|
}
|
|
|
|
|
pub fn with_send_typed<M: Message>(mut self, addr: ActorAddress) -> Self {
|
|
|
|
|
self.send_typed.insert((TypeId::of::<M>(), addr)); self
|
|
|
|
|
}
|
|
|
|
|
pub fn with_spawn(mut self) -> Self {
|
|
|
|
|
self.can_spawn = true; self
|
|
|
|
|
}
|
|
|
|
|
pub fn with_service<S: 'static + Send + Sync>(mut self) -> Self {
|
|
|
|
|
self.service_types.insert(TypeId::of::<S>()); self
|
|
|
|
|
}
|
|
|
|
|
pub fn with_monitor(mut self, addr: ActorAddress) -> Self {
|
|
|
|
|
self.monitor_targets.insert(addr); self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Mutable builder methods ──
|
|
|
|
|
pub fn grant_send(&mut self, addr: ActorAddress) -> &mut Self {
|
|
|
|
|
self.send_any.insert(addr); self
|
|
|
|
|
}
|
|
|
|
|
pub fn grant_send_typed<M: Message>(&mut self, addr: ActorAddress) -> &mut Self {
|
|
|
|
|
self.send_typed.insert((TypeId::of::<M>(), addr)); self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Check methods ──
|
|
|
|
|
pub fn check_send<M: Message>(&self, addr: ActorAddress) -> Result<(), crate::Error> {
|
|
|
|
|
if self.send_any.contains(&addr) { return Ok(()); }
|
|
|
|
|
if self.send_typed.contains(&(TypeId::of::<M>(), addr)) { return Ok(()); }
|
|
|
|
|
Err(crate::Error::from("capability denied: send"))
|
|
|
|
|
}
|
|
|
|
|
pub fn check_send_addr(&self, addr: ActorAddress) -> Result<(), crate::Error> {
|
|
|
|
|
if self.send_any.contains(&addr) { return Ok(()); }
|
|
|
|
|
Err(crate::Error::from("capability denied: send"))
|
|
|
|
|
}
|
|
|
|
|
pub fn check_spawn(&self) -> Result<(), crate::Error> {
|
|
|
|
|
if self.can_spawn { Ok(()) } else { Err(crate::Error::from("capability denied: spawn")) }
|
|
|
|
|
}
|
|
|
|
|
pub fn check_service<S: 'static + Send + Sync>(&self) -> Result<(), crate::Error> {
|
|
|
|
|
if self.service_types.contains(&TypeId::of::<S>()) { Ok(()) }
|
|
|
|
|
else { Err(crate::Error::from("capability denied: service")) }
|
|
|
|
|
}
|
|
|
|
|
pub fn check_monitor(&self, addr: ActorAddress) -> Result<(), crate::Error> {
|
|
|
|
|
if self.monitor_targets.contains(&addr) { Ok(()) }
|
|
|
|
|
else { Err(crate::Error::from("capability denied: monitor")) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Debug for CapabilitySet {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
f.debug_struct("CapabilitySet")
|
|
|
|
|
.field("send_any", &self.send_any.len())
|
|
|
|
|
.field("send_typed", &self.send_typed.len())
|
|
|
|
|
.field("can_spawn", &self.can_spawn)
|
|
|
|
|
.field("services", &self.service_types.len())
|
|
|
|
|
.field("monitors", &self.monitor_targets.len())
|
|
|
|
|
.finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── SpawnRequest ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Bundled arguments for spawning an actor.
|
|
|
|
|
///
|
|
|
|
|
/// Replaces the spawn channel 3-tuple to stop tuple growth as new fields are added.
|
|
|
|
|
pub struct SpawnRequest {
|
|
|
|
|
pub addr: ActorAddress,
|
|
|
|
|
pub actor: Box<dyn AnyActor>,
|
|
|
|
|
pub parent: Option<ActorAddress>,
|
|
|
|
|
pub env: Environment,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 14:45:19 +00:00
|
|
|
/// The actor process as represented in the Runtime — thin wrapper around user state.
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
pub struct Actor<A: ActorInterface> {
|
|
|
|
|
inner: A,
|
|
|
|
|
}
|
2026-01-25 13:38:34 +00:00
|
|
|
|
|
|
|
|
impl<A: ActorInterface> Actor<A> {
|
refactor: repack external bindings into their own crates (#19)
Split the monolithic crate into a Cargo workspace with the Python and Wasm bindings as separate member crates.
- Cargo.toml: declare a `[workspace]` with members `.`/`crates/swactor-python`/`crates/swactor-wasm`, remove the `python` feature and pyo3 dependency, and change root crate-type from `["cdylib","rlib"]` to `["rlib"]`
- crates/swactor-python: new cdylib crate re-exporting the PyO3 bindings (Runtime/RuntimeConfig/RuntimeHandle/Inbox/Ctx/ActorAddress/RuntimeStats), depending on `swactor` + pyo3; pyproject.toml and uv.lock relocated here from the root
- crates/swactor-wasm: new cdylib crate moved from top-level `wasm/`, depending on `swactor` with `no_random` features
- src/actor.rs: widen `Actor::new`, `AnyActor`, `ContextInner`, and `Ctx::raw_inner` to `pub` so the separate binding crates can drive the runtime
- src/lib.rs: delete the in-tree `python` module and `#[pymodule]`, and gate the `no_random` RNG behind `all(feature = "no_random", not(feature = "getrandom"))`
- tools/: relocate package.json/package-lock.json; drop the now-duplicate `wasm/Cargo.lock`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-07 17:39:02 +00:00
|
|
|
pub fn new(inner: A) -> Self {
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
Self { inner }
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 14:45:19 +00:00
|
|
|
/// Trait for type-erased actors — single-message handler.
|
2026-02-10 07:35:58 +00:00
|
|
|
///
|
2026-02-11 15:23:26 +00:00
|
|
|
/// Returns `Some(type_name)` if handled, `None` on type mismatch.
|
refactor: repack external bindings into their own crates (#19)
Split the monolithic crate into a Cargo workspace with the Python and Wasm bindings as separate member crates.
- Cargo.toml: declare a `[workspace]` with members `.`/`crates/swactor-python`/`crates/swactor-wasm`, remove the `python` feature and pyo3 dependency, and change root crate-type from `["cdylib","rlib"]` to `["rlib"]`
- crates/swactor-python: new cdylib crate re-exporting the PyO3 bindings (Runtime/RuntimeConfig/RuntimeHandle/Inbox/Ctx/ActorAddress/RuntimeStats), depending on `swactor` + pyo3; pyproject.toml and uv.lock relocated here from the root
- crates/swactor-wasm: new cdylib crate moved from top-level `wasm/`, depending on `swactor` with `no_random` features
- src/actor.rs: widen `Actor::new`, `AnyActor`, `ContextInner`, and `Ctx::raw_inner` to `pub` so the separate binding crates can drive the runtime
- src/lib.rs: delete the in-tree `python` module and `#[pymodule]`, and gate the `no_random` RNG behind `all(feature = "no_random", not(feature = "getrandom"))`
- tools/: relocate package.json/package-lock.json; drop the now-duplicate `wasm/Cargo.lock`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-07 17:39:02 +00:00
|
|
|
pub trait AnyActor: Send {
|
2026-02-11 15:23:26 +00:00
|
|
|
fn handle_any(&mut self, ctx: &Ctx, msg: Box<dyn Any + Send>) -> Option<&'static str>;
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
|
|
|
|
|
/// Called once after spawn, before first message. See [`ActorInterface::on_start`].
|
|
|
|
|
fn on_start(&mut self, _ctx: &Ctx) {}
|
|
|
|
|
|
|
|
|
|
/// Called on graceful stop, before removal. See [`ActorInterface::on_stop`].
|
|
|
|
|
fn on_stop(&mut self, _ctx: &Ctx) {}
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<A> AnyActor for Actor<A>
|
|
|
|
|
where
|
|
|
|
|
A: ActorInterface,
|
|
|
|
|
{
|
2026-02-11 15:23:26 +00:00
|
|
|
fn handle_any(&mut self, ctx: &Ctx, msg: Box<dyn Any + Send>) -> Option<&'static str> {
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
let msg = match msg.downcast::<A::Incoming>() {
|
|
|
|
|
Ok(typed) => {
|
|
|
|
|
self.inner.handle(ctx, *typed);
|
|
|
|
|
return Some(std::any::type_name::<A::Incoming>());
|
|
|
|
|
}
|
|
|
|
|
Err(msg) => msg,
|
|
|
|
|
};
|
2026-02-13 07:42:44 +00:00
|
|
|
let msg = match msg.downcast::<Down>() {
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
Ok(down) => {
|
|
|
|
|
self.inner.handle_down(ctx, *down);
|
2026-02-13 07:42:44 +00:00
|
|
|
return Some("swactor::actor::Down");
|
|
|
|
|
}
|
|
|
|
|
Err(msg) => msg,
|
|
|
|
|
};
|
|
|
|
|
match msg.downcast::<ActorExited>() {
|
|
|
|
|
Ok(exited) => {
|
|
|
|
|
self.inner.on_actor_exit(ctx, *exited);
|
|
|
|
|
Some("ActorExited")
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
}
|
|
|
|
|
Err(_) => None,
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
|
|
|
|
|
fn on_start(&mut self, ctx: &Ctx) {
|
|
|
|
|
self.inner.on_start(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_stop(&mut self, ctx: &Ctx) {
|
|
|
|
|
self.inner.on_stop(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Unique token identifying a monitor subscription.
|
|
|
|
|
///
|
|
|
|
|
/// Returned by [`Ctx::monitor`] and used with [`Ctx::demonitor`] to cancel.
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
|
pub struct MonitorRef(pub(crate) u64);
|
|
|
|
|
|
|
|
|
|
impl MonitorRef {
|
|
|
|
|
/// Construct a MonitorRef from a raw id. Used by extension crates.
|
|
|
|
|
pub fn from_raw(id: u64) -> Self {
|
|
|
|
|
Self(id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Reason an actor was removed from the runtime.
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
|
pub enum StopReason {
|
|
|
|
|
/// Graceful stop (via `ctx.stop_self()` or `Runtime::stop_actor()`).
|
|
|
|
|
Normal,
|
|
|
|
|
/// Actor panicked and could not be restarted.
|
|
|
|
|
Panicked,
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Actor stopped with a typed exit value (via [`Ctx::stop_with`]).
|
|
|
|
|
Completed,
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Death notification delivered as a normal message when a monitored actor dies.
|
|
|
|
|
///
|
|
|
|
|
/// Subscribe via [`Ctx::monitor`]. The `Down` message arrives in the watcher's
|
|
|
|
|
/// regular `handle()` method — no special callback needed.
|
2026-02-20 17:34:43 +00:00
|
|
|
#[derive(Debug, Clone)]
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
pub struct Down {
|
|
|
|
|
/// Address of the dead actor.
|
|
|
|
|
pub addr: ActorAddress,
|
|
|
|
|
/// Why it died.
|
|
|
|
|
pub reason: StopReason,
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Typed exit value if the actor called [`Ctx::stop_with`].
|
|
|
|
|
pub exit_value: Option<ExitValue>,
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
impl PartialEq for Down {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.addr == other.addr && self.reason == other.reason
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Eq for Down {}
|
|
|
|
|
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
/// Internal sentinel message for graceful actor stop.
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Not a `Message` (not Clone) — intercepted in `tick_all` / `deliver` before
|
|
|
|
|
/// reaching `handle_any`. Public so extension crates can construct it for
|
|
|
|
|
/// orphan cleanup, but users cannot send it via `ctx.send()`.
|
|
|
|
|
pub struct StopSignal;
|
|
|
|
|
|
|
|
|
|
/// Like [`StopSignal`] but carries a typed exit value.
|
|
|
|
|
/// Intercepted in `tick_all` / `deliver`.
|
|
|
|
|
pub struct StopWithSignal(pub ExitValue);
|
|
|
|
|
|
|
|
|
|
/// Internal sentinel message for resuming a suspended actor.
|
|
|
|
|
/// Intercepted in [`ActorPool::deliver`] — not delivered to user code.
|
|
|
|
|
pub(crate) struct ResumeSignal;
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
|
2026-02-07 10:36:45 +00:00
|
|
|
/// Object-safe inner trait for sending type-erased messages.
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
///
|
fix: mild refactor and test coverage (#37)
Extract the timer wheel and watch registry from the worker into a reusable std-extension crate, generalize the worker around a RuntimeExtension factory, and decompose the monolithic runtime test file into focused suites.
- crates/std: extract TimerWheel (deterministic tick-counted one-shot/interval timers) and WatchRegistry (target-to-watcher death-notification index) out of src/worker.rs into reusable modules
- crates/std: add Ctx extension traits (CtxMonitoring, CtxNaming, CtxWatching, CtxTimers) and Runtime extension traits (RuntimeNaming, RuntimeWatching, RuntimeGroups) wiring monitor/name/watch/timer/group support
- src/worker.rs: replace the hard-coded timer/watch fields with a generic RuntimeExtension factory and add route_to_pool_or_remote for message routing (local pool, then cross-worker address map, then external inboxes)
- tests: split the 4622-line tests/runtime_api.rs into focused suites (actor_lifecycle, message_delivery, runtime_stress, std_extension) plus a shared tests/common/mod.rs harness, and drop watch_api.rs
- benches/fuzz: add runtime_benchmarks and adjust the runtime fuzz target
- tools/docs: add fn_complexity.py and loc_analysis.py analysis scripts and refresh the runtime and worker-thread docs
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 15:00:53 +00:00
|
|
|
/// Minimal core interface: send, spawn, stop, and extension access.
|
|
|
|
|
/// Registry methods (naming, monitoring, groups) and timer scheduling
|
|
|
|
|
/// are provided by extension traits in `swactor-std`.
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
#[allow(private_interfaces)]
|
refactor: repack external bindings into their own crates (#19)
Split the monolithic crate into a Cargo workspace with the Python and Wasm bindings as separate member crates.
- Cargo.toml: declare a `[workspace]` with members `.`/`crates/swactor-python`/`crates/swactor-wasm`, remove the `python` feature and pyo3 dependency, and change root crate-type from `["cdylib","rlib"]` to `["rlib"]`
- crates/swactor-python: new cdylib crate re-exporting the PyO3 bindings (Runtime/RuntimeConfig/RuntimeHandle/Inbox/Ctx/ActorAddress/RuntimeStats), depending on `swactor` + pyo3; pyproject.toml and uv.lock relocated here from the root
- crates/swactor-wasm: new cdylib crate moved from top-level `wasm/`, depending on `swactor` with `no_random` features
- src/actor.rs: widen `Actor::new`, `AnyActor`, `ContextInner`, and `Ctx::raw_inner` to `pub` so the separate binding crates can drive the runtime
- src/lib.rs: delete the in-tree `python` module and `#[pymodule]`, and gate the `no_random` RNG behind `all(feature = "no_random", not(feature = "getrandom"))`
- tools/: relocate package.json/package-lock.json; drop the now-duplicate `wasm/Cargo.lock`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-07 17:39:02 +00:00
|
|
|
pub trait ContextInner {
|
2026-02-07 10:36:45 +00:00
|
|
|
fn send_any(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error>;
|
2026-02-20 17:34:43 +00:00
|
|
|
fn spawn_any(&self, request: SpawnRequest);
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
/// Request graceful stop for an actor. Takes effect after the current message.
|
|
|
|
|
fn request_stop(&self, addr: ActorAddress);
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Request graceful stop with a typed exit value. Takes effect after the current message.
|
|
|
|
|
fn request_stop_with(&self, addr: ActorAddress, value: ExitValue);
|
|
|
|
|
/// Request suspension for an actor. Takes effect after the current message.
|
|
|
|
|
fn request_suspend(&self, addr: ActorAddress);
|
|
|
|
|
/// Request resumption for a suspended actor. Sends a [`ResumeSignal`].
|
|
|
|
|
fn request_resume(&self, addr: ActorAddress);
|
fix: mild refactor and test coverage (#37)
Extract the timer wheel and watch registry from the worker into a reusable std-extension crate, generalize the worker around a RuntimeExtension factory, and decompose the monolithic runtime test file into focused suites.
- crates/std: extract TimerWheel (deterministic tick-counted one-shot/interval timers) and WatchRegistry (target-to-watcher death-notification index) out of src/worker.rs into reusable modules
- crates/std: add Ctx extension traits (CtxMonitoring, CtxNaming, CtxWatching, CtxTimers) and Runtime extension traits (RuntimeNaming, RuntimeWatching, RuntimeGroups) wiring monitor/name/watch/timer/group support
- src/worker.rs: replace the hard-coded timer/watch fields with a generic RuntimeExtension factory and add route_to_pool_or_remote for message routing (local pool, then cross-worker address map, then external inboxes)
- tests: split the 4622-line tests/runtime_api.rs into focused suites (actor_lifecycle, message_delivery, runtime_stress, std_extension) plus a shared tests/common/mod.rs harness, and drop watch_api.rs
- benches/fuzz: add runtime_benchmarks and adjust the runtime fuzz target
- tools/docs: add fn_complexity.py and loc_analysis.py analysis scripts and refresh the runtime and worker-thread docs
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 15:00:53 +00:00
|
|
|
/// Post a request to the per-worker extension (e.g., timer scheduling).
|
|
|
|
|
fn post_worker_request(&self, request: Box<dyn Any + Send>);
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
/// Access the runtime extension (if installed).
|
|
|
|
|
fn extension(&self) -> Option<&dyn crate::extension::RuntimeExtension>;
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Return system-level information (worker count, actor count, uptime).
|
|
|
|
|
fn system_info(&self) -> SystemInfo;
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Actor syscall interface — passed to `ActorInterface::handle()`.
|
|
|
|
|
///
|
|
|
|
|
/// Wraps a `&dyn ContextInner` to solve the object-safety problem while
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
/// providing a typed public API. Registry methods (naming, monitoring, groups)
|
|
|
|
|
/// are provided by extension traits in `swactor-std`.
|
2026-02-07 10:36:45 +00:00
|
|
|
pub struct Ctx<'a> {
|
|
|
|
|
inner: &'a dyn ContextInner,
|
|
|
|
|
self_addr: ActorAddress,
|
2026-02-20 17:34:43 +00:00
|
|
|
self_parent_addr: Option<ActorAddress>,
|
|
|
|
|
self_env: Environment,
|
|
|
|
|
self_messages_processed: u64,
|
|
|
|
|
self_mailbox_depth: usize,
|
|
|
|
|
self_msg_type_counts: Vec<(&'static str, u64)>,
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Ctx<'a> {
|
2026-02-20 17:34:43 +00:00
|
|
|
pub(crate) fn new(
|
|
|
|
|
inner: &'a dyn ContextInner,
|
|
|
|
|
self_addr: ActorAddress,
|
|
|
|
|
self_parent_addr: Option<ActorAddress>,
|
|
|
|
|
self_env: Environment,
|
|
|
|
|
self_messages_processed: u64,
|
|
|
|
|
self_mailbox_depth: usize,
|
|
|
|
|
self_msg_type_counts: Vec<(&'static str, u64)>,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
inner,
|
|
|
|
|
self_addr,
|
|
|
|
|
self_parent_addr,
|
|
|
|
|
self_env,
|
|
|
|
|
self_messages_processed,
|
|
|
|
|
self_mailbox_depth,
|
|
|
|
|
self_msg_type_counts,
|
|
|
|
|
}
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|
|
|
|
|
|
refactor: repack external bindings into their own crates (#19)
Split the monolithic crate into a Cargo workspace with the Python and Wasm bindings as separate member crates.
- Cargo.toml: declare a `[workspace]` with members `.`/`crates/swactor-python`/`crates/swactor-wasm`, remove the `python` feature and pyo3 dependency, and change root crate-type from `["cdylib","rlib"]` to `["rlib"]`
- crates/swactor-python: new cdylib crate re-exporting the PyO3 bindings (Runtime/RuntimeConfig/RuntimeHandle/Inbox/Ctx/ActorAddress/RuntimeStats), depending on `swactor` + pyo3; pyproject.toml and uv.lock relocated here from the root
- crates/swactor-wasm: new cdylib crate moved from top-level `wasm/`, depending on `swactor` with `no_random` features
- src/actor.rs: widen `Actor::new`, `AnyActor`, `ContextInner`, and `Ctx::raw_inner` to `pub` so the separate binding crates can drive the runtime
- src/lib.rs: delete the in-tree `python` module and `#[pymodule]`, and gate the `no_random` RNG behind `all(feature = "no_random", not(feature = "getrandom"))`
- tools/: relocate package.json/package-lock.json; drop the now-duplicate `wasm/Cargo.lock`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-07 17:39:02 +00:00
|
|
|
pub fn raw_inner(&self) -> &dyn ContextInner {
|
2026-02-07 10:36:45 +00:00
|
|
|
self.inner
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns the address of the actor currently being ticked.
|
|
|
|
|
pub fn self_addr(&self) -> ActorAddress {
|
|
|
|
|
self.self_addr
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Returns the address of the actor that spawned this one, or `None`
|
|
|
|
|
/// if this actor was spawned externally via `Runtime::spawn`.
|
|
|
|
|
pub fn parent(&self) -> Option<ActorAddress> {
|
|
|
|
|
self.self_parent_addr
|
|
|
|
|
}
|
|
|
|
|
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
/// Access the runtime extension (if installed).
|
|
|
|
|
pub fn extension(&self) -> Option<&dyn crate::extension::RuntimeExtension> {
|
|
|
|
|
self.inner.extension()
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Return system-level information (worker count, actor count, uptime).
|
|
|
|
|
pub fn system_info(&self) -> SystemInfo {
|
|
|
|
|
self.inner.system_info()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Total messages this actor has successfully processed (before the current tick).
|
|
|
|
|
pub fn messages_processed(&self) -> u64 {
|
|
|
|
|
self.self_messages_processed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Number of messages in this actor's mailbox at the start of the current tick.
|
|
|
|
|
pub fn mailbox_depth(&self) -> usize {
|
|
|
|
|
self.self_mailbox_depth
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Per-message-type counts for this actor, sorted descending by count.
|
|
|
|
|
pub fn message_type_counts(&self) -> &[(&'static str, u64)] {
|
|
|
|
|
&self.self_msg_type_counts
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if this actor has a capability set (i.e., is restricted).
|
|
|
|
|
fn capabilities(&self) -> Option<&CapabilitySet> {
|
|
|
|
|
self.self_env.get::<CapabilitySet>()
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:36:45 +00:00
|
|
|
/// Send a typed message to an actor address.
|
|
|
|
|
pub fn send<M: Message>(&self, addr: ActorAddress, msg: M) -> Result<(), Error> {
|
2026-02-24 09:12:28 +00:00
|
|
|
if let Some(caps) = self.capabilities()
|
|
|
|
|
&& addr != self.self_addr {
|
2026-02-20 17:34:43 +00:00
|
|
|
caps.check_send::<M>(addr)?;
|
|
|
|
|
}
|
2026-02-07 10:36:45 +00:00
|
|
|
self.inner.send_any(addr, Box::new(msg))
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Read a typed value from this actor's environment.
|
|
|
|
|
pub fn env<T: Any + Send + Sync>(&self) -> Option<&T> {
|
|
|
|
|
self.self_env.get::<T>()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Access this actor's full environment.
|
|
|
|
|
pub fn environment(&self) -> &Environment {
|
|
|
|
|
&self.self_env
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:36:45 +00:00
|
|
|
/// Spawn a new actor, returning its address.
|
2026-02-20 17:34:43 +00:00
|
|
|
///
|
|
|
|
|
/// The child inherits this actor's environment (Arc clone — zero allocation).
|
2026-02-07 10:36:45 +00:00
|
|
|
pub fn spawn<A: ActorInterface>(&self, actor: A) -> Result<ActorAddress, Error> {
|
2026-02-20 17:34:43 +00:00
|
|
|
if let Some(caps) = self.capabilities() {
|
|
|
|
|
caps.check_spawn()?;
|
|
|
|
|
}
|
2026-02-07 10:36:45 +00:00
|
|
|
let addr = ActorAddress::new_random();
|
|
|
|
|
let boxed: Box<dyn AnyActor> = Box::new(Actor::new(actor));
|
2026-02-20 17:34:43 +00:00
|
|
|
self.inner.spawn_any(SpawnRequest {
|
|
|
|
|
addr,
|
|
|
|
|
actor: boxed,
|
|
|
|
|
parent: Some(self.self_addr),
|
|
|
|
|
env: self.self_env.clone(),
|
|
|
|
|
});
|
2026-02-07 10:36:45 +00:00
|
|
|
Ok(addr)
|
|
|
|
|
}
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Create a [`SpawnBuilder`] to spawn an actor with environment overrides.
|
|
|
|
|
///
|
|
|
|
|
/// Common case (`ctx.spawn(actor)`) is unchanged — this is for when you
|
|
|
|
|
/// need to add or replace environment values for the child.
|
|
|
|
|
pub fn spawn_builder<A: ActorInterface>(&self, actor: A) -> SpawnBuilder<'_, A> {
|
|
|
|
|
SpawnBuilder {
|
|
|
|
|
ctx: self,
|
|
|
|
|
actor,
|
|
|
|
|
env_builder: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
/// Request graceful stop for this actor after the current message completes.
|
|
|
|
|
///
|
|
|
|
|
/// The actor's `on_stop()` hook is called and the actor is removed from the
|
|
|
|
|
/// worker pool. Pending messages in the mailbox are discarded.
|
|
|
|
|
pub fn stop_self(&self) {
|
|
|
|
|
self.inner.request_stop(self.self_addr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Send a graceful stop request to another actor.
|
|
|
|
|
///
|
|
|
|
|
/// The target actor will process any messages already in its mailbox before
|
|
|
|
|
/// the stop signal, then its `on_stop()` hook is called and it is removed.
|
|
|
|
|
/// Uses PoisonPill semantics — queued after existing messages.
|
|
|
|
|
pub fn stop_actor(&self, addr: ActorAddress) -> Result<(), Error> {
|
2026-02-20 17:34:43 +00:00
|
|
|
if let Some(caps) = self.capabilities() {
|
|
|
|
|
caps.check_send_addr(addr)?;
|
|
|
|
|
}
|
feat(std): add supervision, router, and registries
Adds the std crate on top of the core runtime: Supervisor with RestartPolicy
(Permanent/Transient/Temporary) and SupervisorStrategy (OneForOne/OneForAll/
RestForOne), Router with RoutingStrategy, name/monitor/group registries, StdExtension,
and Ctx/Runtime extension traits. Also extends core (worker, actor, delivery identity
hashing, config, stats), adds a fuzz target, a proptest suite, expands runtime_api
tests, and adds cfuzz cycle notes + benchmarks.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-13 07:11:24 +00:00
|
|
|
self.inner.send_any(addr, Box::new(StopSignal))
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Stop this actor with a typed exit value.
|
|
|
|
|
///
|
|
|
|
|
/// Like [`stop_self`](Self::stop_self), but the value is delivered to
|
|
|
|
|
/// monitors (in [`Down::exit_value`]) and watchers (in [`ActorExited::exit_value`]).
|
|
|
|
|
/// The stop reason is [`StopReason::Completed`].
|
|
|
|
|
pub fn stop_with<T: Any + Send + Sync + 'static>(&self, value: T) {
|
|
|
|
|
self.inner.request_stop_with(self.self_addr, ExitValue::new(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Suspend this actor. Messages continue to queue but are not processed
|
|
|
|
|
/// until a supervisor (or self) calls resume.
|
|
|
|
|
pub fn suspend_self(&self) {
|
|
|
|
|
self.inner.request_suspend(self.self_addr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── SpawnBuilder ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Builder for spawning an actor with environment overrides.
|
|
|
|
|
///
|
|
|
|
|
/// Created via [`Ctx::spawn_builder`]. Lazily clones the parent environment
|
|
|
|
|
/// on the first `.env()` call to avoid allocation when no overrides are needed.
|
|
|
|
|
pub struct SpawnBuilder<'a, A: ActorInterface> {
|
|
|
|
|
ctx: &'a Ctx<'a>,
|
|
|
|
|
actor: A,
|
|
|
|
|
env_builder: Option<EnvironmentBuilder>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, A: ActorInterface> SpawnBuilder<'a, A> {
|
|
|
|
|
/// Add or replace a typed environment value for the child.
|
|
|
|
|
///
|
|
|
|
|
/// On the first call, lazily clones the parent's environment map.
|
|
|
|
|
pub fn env<T: Any + Send + Sync>(mut self, value: T) -> Self {
|
|
|
|
|
let builder = self.env_builder.get_or_insert_with(|| {
|
|
|
|
|
EnvironmentBuilder::from_env(self.ctx.environment())
|
|
|
|
|
});
|
|
|
|
|
builder.set_mut(value);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Spawn the actor, returning its address.
|
|
|
|
|
pub fn finish(self) -> Result<ActorAddress, Error> {
|
|
|
|
|
if let Some(caps) = self.ctx.capabilities() {
|
|
|
|
|
caps.check_spawn()?;
|
|
|
|
|
}
|
|
|
|
|
let addr = ActorAddress::new_random();
|
|
|
|
|
let boxed: Box<dyn AnyActor> = Box::new(Actor::new(self.actor));
|
|
|
|
|
let env = match self.env_builder {
|
|
|
|
|
Some(builder) => builder.build(),
|
|
|
|
|
None => self.ctx.self_env.clone(),
|
|
|
|
|
};
|
|
|
|
|
self.ctx.inner.spawn_any(SpawnRequest {
|
|
|
|
|
addr,
|
|
|
|
|
actor: boxed,
|
|
|
|
|
parent: Some(self.ctx.self_addr),
|
|
|
|
|
env,
|
|
|
|
|
});
|
|
|
|
|
Ok(addr)
|
|
|
|
|
}
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|