2026-06-23 15:42:28 +00:00
|
|
|
use crate::Instant;
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
use std::any::{Any, TypeId};
|
2026-08-09 09:50:33 +00:00
|
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
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
|
|
|
use std::sync::{Arc, OnceLock};
|
2026-01-25 13:38:34 +00:00
|
|
|
|
2026-06-23 15:42:28 +00:00
|
|
|
use crate::actor::{
|
2026-08-11 12:08:06 +00:00
|
|
|
Actor, ActorAddress, ActorInterface, ActorTypeMetadata, AnyActor, Environment,
|
|
|
|
|
Message, SpawnRequest, StopSignal,
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
};
|
|
|
|
|
use crate::admin::{
|
|
|
|
|
ActorStateSnapshot, Admin, AdminCommand, AdminError, AdminResult, GetActorStateResponse,
|
|
|
|
|
InspectActorResponse, ListActorsAccumulator, ListActorsResponse, OperationResult, RuntimeAdmin,
|
2026-06-23 15:42:28 +00:00
|
|
|
};
|
2026-02-06 11:25:37 +00:00
|
|
|
use crate::channel::{Receiver, Sender};
|
|
|
|
|
// Re-export config types so existing code using `runtime::RuntimeConfig` still works
|
2026-03-28 05:08:58 +00:00
|
|
|
pub use crate::config::RuntimeConfig;
|
2026-08-11 12:08:06 +00:00
|
|
|
use crate::delivery::{AddressMap, Envelope, InboxRegistry, WorkerId};
|
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
|
|
|
use crate::extension::RuntimeExtension;
|
2026-08-11 12:08:06 +00:00
|
|
|
use crate::stats::{RuntimeStats, StatsHook, WorkerInfo, WorkerStats};
|
2026-02-07 16:51:40 +00:00
|
|
|
// Re-export stats types so existing code using `runtime::*` still works
|
2026-06-23 15:42:28 +00:00
|
|
|
use crate::Error;
|
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
|
|
|
use crate::worker::Worker;
|
2026-02-06 11:25:37 +00:00
|
|
|
|
2026-01-25 13:38:34 +00:00
|
|
|
/// Generic message inbox for receiving messages outside of the runtime.
|
|
|
|
|
pub struct Inbox<M: Message> {
|
|
|
|
|
addr: ActorAddress,
|
|
|
|
|
inner: Receiver<M>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<M: Message> Inbox<M> {
|
|
|
|
|
pub fn addr(&self) -> &ActorAddress {
|
|
|
|
|
&self.addr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn try_recv(&self) -> Option<M> {
|
|
|
|
|
self.inner.try_recv()
|
|
|
|
|
}
|
2026-08-09 09:50:33 +00:00
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Poll up to `max_ticks` times, driving `host` once per attempt, returning
|
|
|
|
|
/// the first received message or `None` on timeout.
|
|
|
|
|
pub fn recv_ticking(&self, host: &mut SingleThreadRuntime, max_ticks: usize) -> Option<M> {
|
2026-08-09 09:50:33 +00:00
|
|
|
for _ in 0..max_ticks {
|
2026-08-11 12:08:06 +00:00
|
|
|
host.try_tick();
|
2026-08-09 09:50:33 +00:00
|
|
|
if let Some(msg) = self.inner.try_recv() {
|
|
|
|
|
return Some(msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
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
|
|
|
/// Pending ask response — wraps an inbox with convenience recv methods.
|
|
|
|
|
///
|
|
|
|
|
/// Created by [`Runtime::ask`]. Provides `try_recv()` for polling and
|
|
|
|
|
/// `recv_ticking()` for automatic tick-until-response.
|
|
|
|
|
pub struct Ask<R: Message> {
|
|
|
|
|
inbox: Inbox<R>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<R: Message> Ask<R> {
|
|
|
|
|
pub fn try_recv(&self) -> Option<R> {
|
|
|
|
|
self.inbox.try_recv()
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
pub fn recv_ticking(&self, host: &mut SingleThreadRuntime, max_ticks: usize) -> Option<R> {
|
|
|
|
|
self.inbox.recv_ticking(host, max_ticks)
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 16:51:40 +00:00
|
|
|
// Re-export Ctx for backwards compatibility
|
2026-06-23 15:42:28 +00:00
|
|
|
pub use crate::actor::Ctx;
|
2026-02-06 11:25:37 +00:00
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
// ─── RuntimeShared ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Runtime-wide shared state, owned via `Arc` by the [`Runtime`] handle and
|
|
|
|
|
/// every [`Worker`]. Contains only `Sync` data: routing tables, per-worker
|
|
|
|
|
/// producer handles, atomics, and the shared extension/observer hooks.
|
|
|
|
|
///
|
|
|
|
|
/// `Runtime` holds no worker state and needs no `unsafe Sync` justification.
|
|
|
|
|
pub(crate) struct RuntimeShared {
|
|
|
|
|
pub(crate) config: RuntimeConfig,
|
|
|
|
|
pub(crate) address_map: AddressMap,
|
|
|
|
|
pub(crate) inbox_registry: InboxRegistry,
|
|
|
|
|
pub(crate) extension: OnceLock<Arc<dyn RuntimeExtension>>,
|
|
|
|
|
/// Per-worker transfer/spawn/admin producers, indexed by `WorkerId`.
|
|
|
|
|
pub(crate) transfer_txs: Vec<Sender<Envelope>>,
|
|
|
|
|
pub(crate) spawn_txs: Vec<Sender<SpawnRequest>>,
|
|
|
|
|
pub(crate) admin_txs: Vec<Sender<AdminCommand>>,
|
|
|
|
|
pub(crate) worker_stats: Vec<Arc<WorkerStats>>,
|
|
|
|
|
/// Round-robin cursor for runtime-handle spawns.
|
|
|
|
|
pub(crate) rr_worker: AtomicUsize,
|
|
|
|
|
pub(crate) stats_hook: OnceLock<Arc<dyn StatsHook>>,
|
|
|
|
|
pub(crate) process_output_observer: OnceLock<Arc<dyn crate::process_observer::ProcessOutputObserver>>,
|
|
|
|
|
pub(crate) created_at: Instant,
|
|
|
|
|
#[cfg(feature = "transport")]
|
|
|
|
|
pub(crate) remote_sink: OnceLock<Arc<dyn RemoteSink>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RuntimeShared {
|
|
|
|
|
/// Number of logical workers in this runtime.
|
|
|
|
|
pub(crate) fn worker_count(&self) -> usize {
|
|
|
|
|
self.worker_stats.len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pick the next worker for a runtime-handle spawn (round-robin).
|
|
|
|
|
pub(crate) fn next_worker(&self) -> WorkerId {
|
|
|
|
|
let n = self.worker_count();
|
|
|
|
|
// fetch_add grows monotonically; wrap with modular arithmetic. `n >= 1`
|
|
|
|
|
// is guaranteed at construction, so this never divides by zero.
|
|
|
|
|
let idx = self.rr_worker.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
WorkerId(idx % n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Route a message whose destination is not a local actor address:
|
|
|
|
|
/// process-local inbox registry first, then the remote transport seam.
|
|
|
|
|
pub(crate) fn route_nonlocal(
|
|
|
|
|
&self,
|
|
|
|
|
addr: ActorAddress,
|
|
|
|
|
msg: Box<dyn Any + Send>,
|
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
|
#[cfg(feature = "transport")]
|
|
|
|
|
{
|
|
|
|
|
if self.inbox_registry.contains(&addr) {
|
|
|
|
|
return self.inbox_registry.try_deliver(addr, msg);
|
|
|
|
|
}
|
|
|
|
|
if let Some(sink) = self.remote_sink.get() {
|
|
|
|
|
return sink.send(addr, msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.inbox_registry.try_deliver(addr, msg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 11:25:37 +00:00
|
|
|
// ─── Runtime ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// The cloneable shared handle for a swactor runtime.
|
2026-08-09 09:50:33 +00:00
|
|
|
///
|
2026-08-11 12:08:06 +00:00
|
|
|
/// `Runtime` routes and spawns; it owns no executable worker state and has no
|
|
|
|
|
/// `tick()` / `try_tick()`. Worker progression is owned by exactly one
|
|
|
|
|
/// execution host ([`SingleThreadRuntime`] or an engine that consumes
|
|
|
|
|
/// [`RuntimeParts`]).
|
2026-08-09 09:50:33 +00:00
|
|
|
///
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Core is a transition-only state machine. Each call mutates shared routing
|
|
|
|
|
/// state and returns immediately, holding no control flow between calls.
|
|
|
|
|
#[derive(Clone)]
|
2026-02-06 11:25:37 +00:00
|
|
|
pub struct Runtime {
|
2026-08-11 12:08:06 +00:00
|
|
|
pub(crate) shared: Arc<RuntimeShared>,
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 09:29:07 +00:00
|
|
|
/// Core's only hook for delivering a message to a **non-local** address.
|
|
|
|
|
///
|
2026-08-09 09:50:33 +00:00
|
|
|
/// Implemented outside core (e.g., `swactor-transport`'s `CodecRemoteSink`),
|
2026-06-09 09:29:07 +00:00
|
|
|
/// which owns all codec/transport concerns. Core stays codec-free: it hands the
|
|
|
|
|
/// sink a type-erased message and an address, and nothing more. `Send + Sync`
|
2026-08-09 09:50:33 +00:00
|
|
|
/// because the sink is stored in an `Arc` and shared across threads.
|
2026-06-09 09:29:07 +00:00
|
|
|
#[cfg(feature = "transport")]
|
|
|
|
|
pub trait RemoteSink: Send + Sync {
|
|
|
|
|
fn send(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error>;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 15:23:26 +00:00
|
|
|
/// Globally unique identity of a swactor runtime instance.
|
|
|
|
|
/// Pure identity — no networking info. A runtime can exist on any device,
|
|
|
|
|
/// any protocol, or no network at all.
|
|
|
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
|
|
|
pub struct RuntimeAddress(pub [u8; 32]);
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for RuntimeAddress {
|
|
|
|
|
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}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RuntimeAddress {
|
|
|
|
|
pub fn new_random() -> Self {
|
|
|
|
|
let mut bytes = [0u8; 32];
|
|
|
|
|
crate::get_random(&mut bytes);
|
|
|
|
|
Self(bytes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 04:44:46 +00:00
|
|
|
/// A cloneable, `Send + Sync` handle for injecting messages into actor mailboxes
|
|
|
|
|
/// from any thread — including non-actor I/O threads.
|
|
|
|
|
///
|
|
|
|
|
/// Created via [`Runtime::create_sender`]. The primary use case is bridging
|
2026-08-11 12:08:06 +00:00
|
|
|
/// background I/O (e.g., pipe readers, network listeners) with the actor system.
|
2026-02-23 04:44:46 +00:00
|
|
|
pub struct ExternalSender {
|
2026-08-11 12:08:06 +00:00
|
|
|
shared: Arc<RuntimeShared>,
|
2026-02-23 04:44:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Clone for ExternalSender {
|
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
|
Self {
|
2026-08-11 12:08:06 +00:00
|
|
|
shared: self.shared.clone(),
|
2026-02-23 04:44:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ExternalSender {
|
2026-08-09 09:50:33 +00:00
|
|
|
/// Send a typed message to an actor address.
|
2026-02-23 04:44:46 +00:00
|
|
|
///
|
2026-03-28 05:08:58 +00:00
|
|
|
/// Returns `Ok(())` if the message was accepted for routing. This does **not**
|
|
|
|
|
/// guarantee delivery — the recipient may stop before processing it. If
|
|
|
|
|
/// delivery confirmation is needed, implement an application-level ACK.
|
|
|
|
|
///
|
2026-02-23 04:44:46 +00:00
|
|
|
/// Returns `Err` if the address is not found in the runtime's address map.
|
|
|
|
|
pub fn send_to<M: Message>(&self, addr: ActorAddress, msg: M) -> Result<(), Error> {
|
2026-08-11 12:08:06 +00:00
|
|
|
if let Some(w) = self.shared.address_map.worker_of(&addr) {
|
|
|
|
|
self.shared.transfer_txs[w.index()].send(Envelope::new(addr, Box::new(msg)));
|
2026-08-09 09:50:33 +00:00
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::from("Address not found"))
|
2026-02-23 04:44:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
// ─── RuntimeParts ────────────────────────────────────────────────────────────
|
2026-08-09 09:50:33 +00:00
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// The linear construction bundle: one [`Runtime`] handle and the [`Worker`]
|
|
|
|
|
/// values it routes to. Consumed by exactly one execution host.
|
|
|
|
|
///
|
|
|
|
|
/// Configuration that creates per-worker state — especially
|
|
|
|
|
/// [`RuntimeExtension::create_worker_extension`] — must be installed (via
|
|
|
|
|
/// [`RuntimeParts::with_extension`]) before the parts are consumed by a host.
|
|
|
|
|
pub struct RuntimeParts {
|
|
|
|
|
runtime: Runtime,
|
|
|
|
|
workers: Vec<Worker>,
|
|
|
|
|
}
|
2026-08-09 09:50:33 +00:00
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
impl RuntimeParts {
|
|
|
|
|
/// Build a runtime with `config.worker_count` logical workers, each with its
|
|
|
|
|
/// own transfer/spawn/admin queues and `WorkerStats`.
|
|
|
|
|
///
|
|
|
|
|
/// Panics if `config.worker_count == 0`.
|
|
|
|
|
pub fn new(config: RuntimeConfig) -> Self {
|
|
|
|
|
assert!(
|
|
|
|
|
config.worker_count >= 1,
|
|
|
|
|
"swactor: RuntimeConfig.worker_count must be >= 1"
|
|
|
|
|
);
|
|
|
|
|
let n = config.worker_count;
|
|
|
|
|
let max_actors = config.max_actors;
|
|
|
|
|
let channel_buffer_size = config.channel_buffer_size;
|
|
|
|
|
|
|
|
|
|
let mut transfer_rxs = Vec::with_capacity(n);
|
|
|
|
|
let mut transfer_txs = Vec::with_capacity(n);
|
|
|
|
|
let mut spawn_rxs = Vec::with_capacity(n);
|
|
|
|
|
let mut spawn_txs = Vec::with_capacity(n);
|
|
|
|
|
let mut admin_rxs = Vec::with_capacity(n);
|
|
|
|
|
let mut admin_txs = Vec::with_capacity(n);
|
|
|
|
|
let mut worker_stats = Vec::with_capacity(n);
|
|
|
|
|
for _ in 0..n {
|
|
|
|
|
let transfer_rx = Receiver::<Envelope>::new(channel_buffer_size);
|
|
|
|
|
transfer_txs.push(transfer_rx.new_sender());
|
|
|
|
|
transfer_rxs.push(transfer_rx);
|
|
|
|
|
|
|
|
|
|
let spawn_rx = Receiver::<SpawnRequest>::new(max_actors);
|
|
|
|
|
spawn_txs.push(spawn_rx.new_sender());
|
|
|
|
|
spawn_rxs.push(spawn_rx);
|
|
|
|
|
|
|
|
|
|
let admin_rx = Receiver::<AdminCommand>::new(channel_buffer_size);
|
|
|
|
|
admin_txs.push(admin_rx.new_sender());
|
|
|
|
|
admin_rxs.push(admin_rx);
|
|
|
|
|
|
|
|
|
|
worker_stats.push(Arc::new(WorkerStats::new()));
|
|
|
|
|
}
|
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-08-11 12:08:06 +00:00
|
|
|
let shared = Arc::new(RuntimeShared {
|
2026-02-09 07:24:16 +00:00
|
|
|
config,
|
2026-08-11 12:08:06 +00:00
|
|
|
address_map: AddressMap::with_capacity(max_actors),
|
|
|
|
|
inbox_registry: InboxRegistry::new(),
|
|
|
|
|
extension: OnceLock::new(),
|
|
|
|
|
transfer_txs,
|
|
|
|
|
spawn_txs,
|
|
|
|
|
admin_txs,
|
2026-02-09 07:24:16 +00:00
|
|
|
worker_stats,
|
2026-08-11 12:08:06 +00:00
|
|
|
rr_worker: AtomicUsize::new(0),
|
|
|
|
|
stats_hook: OnceLock::new(),
|
2026-06-06 17:53:25 +00:00
|
|
|
process_output_observer: OnceLock::new(),
|
2026-02-11 15:23:26 +00:00
|
|
|
created_at: Instant::now(),
|
2026-02-09 19:05:37 +00:00
|
|
|
#[cfg(feature = "transport")]
|
2026-08-11 12:08:06 +00:00
|
|
|
remote_sink: OnceLock::new(),
|
|
|
|
|
});
|
2026-02-09 09:04:57 +00:00
|
|
|
|
|
|
|
|
#[cfg(feature = "tracing")]
|
2026-08-11 12:08:06 +00:00
|
|
|
tracing::info!(max_actors, worker_count = n, "runtime.created");
|
|
|
|
|
|
|
|
|
|
let workers: Vec<Worker> = transfer_rxs
|
|
|
|
|
.into_iter()
|
|
|
|
|
.zip(spawn_rxs)
|
|
|
|
|
.zip(admin_rxs)
|
|
|
|
|
.enumerate()
|
|
|
|
|
.map(|(i, ((trx, srx), arx))| {
|
|
|
|
|
Worker::new(
|
|
|
|
|
WorkerId(i),
|
|
|
|
|
shared.clone(),
|
|
|
|
|
trx,
|
|
|
|
|
srx,
|
|
|
|
|
arx,
|
|
|
|
|
shared.worker_stats[i].clone(),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
runtime: Runtime { shared },
|
|
|
|
|
workers,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Borrow the cloneable runtime handle.
|
|
|
|
|
pub fn runtime(&self) -> &Runtime {
|
|
|
|
|
&self.runtime
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Install a runtime extension and create one `WorkerExtension` per worker.
|
|
|
|
|
///
|
|
|
|
|
/// Must be called before the parts are consumed by a host.
|
|
|
|
|
pub fn with_extension(mut self, ext: Arc<dyn RuntimeExtension>) -> Self {
|
|
|
|
|
for w in &mut self.workers {
|
|
|
|
|
if let Some(wext) = ext.create_worker_extension() {
|
|
|
|
|
w.worker_ext = Some(wext);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let _ = self.runtime.shared.extension.set(ext);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Install a stats hook across all workers. Must be called before the parts
|
|
|
|
|
/// are consumed by a host.
|
|
|
|
|
pub fn with_stats_hook(self, hook: Arc<dyn StatsHook>) -> Self {
|
|
|
|
|
let _ = self.runtime.shared.stats_hook.set(hook);
|
|
|
|
|
self
|
|
|
|
|
}
|
2026-02-09 09:04:57 +00:00
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Consume the parts, returning the workers for an execution host to own.
|
|
|
|
|
///
|
|
|
|
|
/// The runtime handle must be cloned beforehand via [`runtime`](Self::runtime).
|
|
|
|
|
pub fn into_workers(self) -> Vec<Worker> {
|
|
|
|
|
self.workers
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
2026-08-11 12:08:06 +00:00
|
|
|
}
|
2026-01-25 13:38:34 +00:00
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
impl Runtime {
|
|
|
|
|
/// Spawn an actor, returns its address.
|
|
|
|
|
///
|
|
|
|
|
/// Runtime-handle spawns are assigned round-robin across workers.
|
2026-01-25 13:38:34 +00:00
|
|
|
pub fn spawn<A: ActorInterface>(&self, actor: A) -> Result<ActorAddress, Error> {
|
|
|
|
|
let addr = ActorAddress::new_random();
|
2026-08-11 12:08:06 +00:00
|
|
|
let worker = self.shared.next_worker();
|
|
|
|
|
self.shared.address_map.insert(addr, worker);
|
2026-02-06 14:45:19 +00:00
|
|
|
let boxed: Box<dyn AnyActor> = Box::new(Actor::new(actor));
|
2026-08-11 12:08:06 +00:00
|
|
|
self.shared.spawn_txs[worker.index()].send(SpawnRequest {
|
2026-06-23 15:42:28 +00:00
|
|
|
addr,
|
|
|
|
|
actor: boxed,
|
|
|
|
|
parent: None,
|
|
|
|
|
env: Environment::new(),
|
|
|
|
|
});
|
2026-02-20 17:34:43 +00:00
|
|
|
|
|
|
|
|
#[cfg(feature = "tracing")]
|
2026-08-11 12:08:06 +00:00
|
|
|
tracing::info!(actor_addr = %addr, "actor.spawned");
|
2026-02-20 17:34:43 +00:00
|
|
|
|
|
|
|
|
Ok(addr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Spawn an actor with a pre-built environment, returns its address.
|
2026-06-23 15:42:28 +00:00
|
|
|
pub fn spawn_with_env<A: ActorInterface>(
|
|
|
|
|
&self,
|
|
|
|
|
actor: A,
|
|
|
|
|
env: Environment,
|
|
|
|
|
) -> Result<ActorAddress, Error> {
|
2026-02-20 17:34:43 +00:00
|
|
|
let addr = ActorAddress::new_random();
|
2026-08-11 12:08:06 +00:00
|
|
|
let worker = self.shared.next_worker();
|
|
|
|
|
self.shared.address_map.insert(addr, worker);
|
2026-02-20 17:34:43 +00:00
|
|
|
let boxed: Box<dyn AnyActor> = Box::new(Actor::new(actor));
|
2026-08-11 12:08:06 +00:00
|
|
|
self.shared.spawn_txs[worker.index()].send(SpawnRequest {
|
2026-06-23 15:42:28 +00:00
|
|
|
addr,
|
|
|
|
|
actor: boxed,
|
|
|
|
|
parent: None,
|
|
|
|
|
env,
|
|
|
|
|
});
|
2026-02-09 09:04:57 +00:00
|
|
|
|
|
|
|
|
#[cfg(feature = "tracing")]
|
2026-08-11 12:08:06 +00:00
|
|
|
tracing::info!(actor_addr = %addr, "actor.spawned");
|
2026-02-09 09:04:57 +00:00
|
|
|
|
2026-01-25 13:38:34 +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
|
|
|
/// Access the installed runtime extension (if any).
|
|
|
|
|
pub fn extension(&self) -> Option<&dyn RuntimeExtension> {
|
2026-08-11 12:08:06 +00:00
|
|
|
self.shared.extension.get().map(|a| a.as_ref())
|
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
|
|
|
}
|
|
|
|
|
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
pub fn admin(&self) -> RuntimeAdmin<'_> {
|
|
|
|
|
RuntimeAdmin { runtime: 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
|
|
|
/// Send a request and get a handle for the response.
|
|
|
|
|
///
|
|
|
|
|
/// Creates a temporary inbox, calls `msg_builder` with the inbox's address
|
|
|
|
|
/// (so you can embed it as `reply_to`), sends the message, and returns an
|
|
|
|
|
/// [`Ask`] handle for receiving the response.
|
|
|
|
|
pub fn ask<Req: Message, Resp: Message>(
|
|
|
|
|
&self,
|
|
|
|
|
addr: ActorAddress,
|
|
|
|
|
msg_builder: impl FnOnce(ActorAddress) -> Req,
|
|
|
|
|
) -> Result<Ask<Resp>, Error> {
|
|
|
|
|
let inbox = self.new_inbox::<Resp>()?;
|
|
|
|
|
let msg = msg_builder(*inbox.addr());
|
|
|
|
|
self.send_to(addr, msg)?;
|
|
|
|
|
Ok(Ask { inbox })
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 05:08:58 +00:00
|
|
|
/// Send a message to an actor address.
|
|
|
|
|
///
|
|
|
|
|
/// Returns `Ok(())` if the message was accepted for routing. This does **not**
|
|
|
|
|
/// guarantee delivery — the recipient may stop before processing it. If
|
|
|
|
|
/// delivery confirmation is needed, implement an application-level ACK.
|
|
|
|
|
///
|
|
|
|
|
/// Returns `Err` if the address is unknown to the runtime.
|
2026-01-25 13:38:34 +00:00
|
|
|
pub fn send_to<M: Message>(&self, addr: ActorAddress, msg: M) -> Result<(), Error> {
|
2026-08-11 12:08:06 +00:00
|
|
|
let boxed: Box<dyn Any + Send> = Box::new(msg);
|
|
|
|
|
let result = match self.shared.address_map.worker_of(&addr) {
|
|
|
|
|
Some(w) => {
|
|
|
|
|
self.shared.transfer_txs[w.index()].send(Envelope::new(addr, boxed));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
None => self.shared.route_nonlocal(addr, boxed),
|
|
|
|
|
};
|
2026-02-09 09:04:57 +00:00
|
|
|
|
|
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
tracing::trace!(dest = %addr, "message.sent");
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
result
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create an external inbox for receiving messages in the outer process containing the runtime
|
|
|
|
|
pub fn new_inbox<M: Message>(&self) -> Result<Inbox<M>, Error> {
|
|
|
|
|
let addr = ActorAddress::new_random();
|
2026-08-11 12:08:06 +00:00
|
|
|
let receiver = Receiver::<M>::new(self.shared.config.channel_buffer_size);
|
2026-01-25 13:38:34 +00:00
|
|
|
let sender = receiver.new_sender();
|
2026-08-11 12:08:06 +00:00
|
|
|
self.shared.inbox_registry.register(addr, Arc::new(sender));
|
2026-01-25 13:38:34 +00:00
|
|
|
Ok(Inbox {
|
|
|
|
|
addr,
|
|
|
|
|
inner: receiver,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 04:44:46 +00:00
|
|
|
/// Create an [`ExternalSender`] handle for injecting messages from any thread.
|
|
|
|
|
///
|
|
|
|
|
/// The returned handle is `Clone + Send + Sync` and can be moved into
|
|
|
|
|
/// background I/O threads to bridge external events into the actor system.
|
|
|
|
|
pub fn create_sender(&self) -> ExternalSender {
|
|
|
|
|
ExternalSender {
|
2026-08-11 12:08:06 +00:00
|
|
|
shared: self.shared.clone(),
|
2026-02-09 19:05:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 14:45:19 +00:00
|
|
|
/// Returns a snapshot of runtime stats: actor placements and per-worker info.
|
|
|
|
|
pub fn stats(&self) -> RuntimeStats {
|
2026-08-11 12:08:06 +00:00
|
|
|
let s = &self.shared;
|
|
|
|
|
let num_workers = s.worker_count();
|
|
|
|
|
let workers: Vec<WorkerInfo> = s
|
|
|
|
|
.worker_stats
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.map(|(i, ws)| ws.snapshot(i))
|
|
|
|
|
.collect();
|
|
|
|
|
let actors = s
|
2026-06-23 15:42:28 +00:00
|
|
|
.address_map
|
2026-08-11 12:08:06 +00:00
|
|
|
.placements()
|
2026-06-23 15:42:28 +00:00
|
|
|
.into_iter()
|
2026-08-11 12:08:06 +00:00
|
|
|
.map(|(a, w)| (a, w.index()))
|
2026-02-06 14:45:19 +00:00
|
|
|
.collect();
|
2026-08-11 12:08:06 +00:00
|
|
|
let tick_timings: Vec<_> = s
|
|
|
|
|
.worker_stats
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|ws| ws.drain_tick_timings())
|
|
|
|
|
.collect();
|
|
|
|
|
let uptime_ms = s.created_at.elapsed().as_millis() as u64;
|
2026-02-11 15:23:26 +00:00
|
|
|
|
2026-06-23 15:42:28 +00:00
|
|
|
RuntimeStats {
|
2026-08-11 12:08:06 +00:00
|
|
|
num_workers,
|
2026-06-23 15:42:28 +00:00
|
|
|
uptime_ms,
|
|
|
|
|
actors,
|
|
|
|
|
workers,
|
|
|
|
|
actor_details: Vec::new(),
|
|
|
|
|
tick_timings,
|
|
|
|
|
}
|
2026-02-06 13:35:46 +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
|
|
|
/// Request an actor to stop gracefully.
|
|
|
|
|
///
|
|
|
|
|
/// The actor's `on_stop()` hook is called before removal. Pending messages
|
2026-08-11 12:08:06 +00:00
|
|
|
/// in the mailbox are discarded. The stop takes effect on the owning
|
|
|
|
|
/// worker's next pass.
|
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
|
|
|
///
|
|
|
|
|
/// Returns `Err` if the actor address is not found in the runtime.
|
|
|
|
|
pub fn stop_actor(&self, addr: ActorAddress) -> Result<(), Error> {
|
2026-08-11 12:08:06 +00:00
|
|
|
if let Some(w) = self.shared.address_map.worker_of(&addr) {
|
|
|
|
|
self.shared.transfer_txs[w.index()].send(Envelope::new(addr, Box::new(StopSignal)));
|
2026-08-09 09:50:33 +00:00
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::from("Actor not found"))
|
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-01-25 13:38:34 +00:00
|
|
|
}
|
2026-02-09 19:05:37 +00:00
|
|
|
|
2026-02-11 15:23:26 +00:00
|
|
|
/// Set a stats hook to receive per-actor snapshots from workers.
|
|
|
|
|
///
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Must be called before the runtime is driven.
|
|
|
|
|
pub fn set_stats_hook(&self, hook: Arc<dyn StatsHook>) {
|
|
|
|
|
let _ = self.shared.stats_hook.set(hook);
|
2026-02-11 15:23:26 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 09:29:07 +00:00
|
|
|
/// Set the sink for non-local (remote) message delivery.
|
|
|
|
|
///
|
|
|
|
|
/// The sink owns all codec/transport concerns; core only knows how to hand
|
|
|
|
|
/// it a type-erased message destined for a non-local address.
|
2026-02-09 19:05:37 +00:00
|
|
|
#[cfg(feature = "transport")]
|
2026-08-11 12:08:06 +00:00
|
|
|
pub fn set_remote_sink(&self, sink: Arc<dyn RemoteSink>) {
|
|
|
|
|
let _ = self.shared.remote_sink.set(sink);
|
2026-02-09 19:05:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Deliver a raw deserialized message into the runtime.
|
|
|
|
|
///
|
2026-06-09 09:29:07 +00:00
|
|
|
/// Whoever owns the socket decodes the wire bytes outside core and calls
|
|
|
|
|
/// this to inject the resulting message for a local actor or inbox.
|
2026-02-09 19:05:37 +00:00
|
|
|
#[cfg(feature = "transport")]
|
2026-06-23 15:42:28 +00:00
|
|
|
pub fn deliver_raw(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error> {
|
2026-08-11 12:08:06 +00:00
|
|
|
if let Some(w) = self.shared.address_map.worker_of(&addr) {
|
|
|
|
|
self.shared.transfer_txs[w.index()].send(Envelope::new(addr, msg));
|
2026-08-09 09:50:33 +00:00
|
|
|
Ok(())
|
|
|
|
|
} else {
|
2026-08-11 12:08:06 +00:00
|
|
|
self.shared.inbox_registry.try_deliver(addr, msg)
|
2026-02-09 19:05:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
2026-02-06 11:25:37 +00:00
|
|
|
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
impl RuntimeAdmin<'_> {
|
|
|
|
|
fn new_admin<T: Message>(&self) -> Result<(Admin<T>, ActorAddress), Error> {
|
|
|
|
|
let inbox = self.runtime.new_inbox::<AdminResult<T>>()?;
|
|
|
|
|
let reply_to = *inbox.addr();
|
|
|
|
|
Ok((Admin::new(inbox), reply_to))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn ready<T: Message>(&self, result: AdminResult<T>) -> Result<Admin<T>, Error> {
|
|
|
|
|
let (admin, reply_to) = self.new_admin::<T>()?;
|
|
|
|
|
let _ = self
|
|
|
|
|
.runtime
|
2026-08-11 12:08:06 +00:00
|
|
|
.shared
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
.inbox_registry
|
|
|
|
|
.try_deliver(reply_to, Box::new(result));
|
|
|
|
|
Ok(admin)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Borrow the producer that owns `actor`'s worker, or `None` if unknown.
|
|
|
|
|
fn admin_tx_for(&self, actor: ActorAddress) -> Option<&Sender<AdminCommand>> {
|
|
|
|
|
self.runtime
|
|
|
|
|
.shared
|
|
|
|
|
.address_map
|
|
|
|
|
.worker_of(&actor)
|
|
|
|
|
.map(|w| &self.runtime.shared.admin_txs[w.index()])
|
|
|
|
|
}
|
|
|
|
|
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
pub fn list_actors(&self) -> Result<Admin<ListActorsResponse>, Error> {
|
|
|
|
|
let (admin, reply_to) = self.new_admin::<ListActorsResponse>()?;
|
2026-08-11 12:08:06 +00:00
|
|
|
let n = self.runtime.shared.worker_count();
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
let acc = Arc::new(ListActorsAccumulator {
|
2026-08-11 12:08:06 +00:00
|
|
|
remaining: AtomicUsize::new(n),
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
summaries: parking_lot::Mutex::new(Vec::new()),
|
|
|
|
|
reply_to,
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
// Broadcast to every worker; the last to finish aggregates and replies.
|
|
|
|
|
for tx in &self.runtime.shared.admin_txs {
|
|
|
|
|
tx.send(AdminCommand::ListActors { acc: acc.clone() });
|
|
|
|
|
}
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
|
|
|
|
|
Ok(admin)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn inspect_actor(&self, actor: ActorAddress) -> Result<Admin<InspectActorResponse>, Error> {
|
2026-08-11 12:08:06 +00:00
|
|
|
let Some(tx) = self.admin_tx_for(actor) else {
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
return self.ready::<InspectActorResponse>(Err(AdminError::ActorNotFound { actor }));
|
2026-08-11 12:08:06 +00:00
|
|
|
};
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
let (admin, reply_to) = self.new_admin::<InspectActorResponse>()?;
|
2026-08-11 12:08:06 +00:00
|
|
|
tx.send(AdminCommand::InspectActor { actor, reply_to });
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
Ok(admin)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_actor_state<A>(
|
|
|
|
|
&self,
|
|
|
|
|
actor: ActorAddress,
|
|
|
|
|
) -> Result<Admin<GetActorStateResponse<A>>, Error>
|
|
|
|
|
where
|
|
|
|
|
A: ActorInterface + Clone + Sync,
|
|
|
|
|
{
|
2026-08-11 12:08:06 +00:00
|
|
|
let Some(tx) = self.admin_tx_for(actor) else {
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
return self
|
|
|
|
|
.ready::<GetActorStateResponse<A>>(Err(AdminError::ActorNotFound { actor }));
|
2026-08-11 12:08:06 +00:00
|
|
|
};
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
let (admin, reply_to) = self.new_admin::<GetActorStateResponse<A>>()?;
|
|
|
|
|
|
|
|
|
|
let get = Box::new(
|
|
|
|
|
|actor: ActorAddress,
|
|
|
|
|
erased: &dyn AnyActor,
|
|
|
|
|
metadata: ActorTypeMetadata|
|
|
|
|
|
-> Box<dyn Any + Send> {
|
|
|
|
|
let expected_actor_type = std::any::type_name::<A>();
|
|
|
|
|
let expected_message_type = std::any::type_name::<A::Incoming>();
|
|
|
|
|
if metadata.actor_type_id != TypeId::of::<A>()
|
|
|
|
|
|| metadata.message_type_id != TypeId::of::<A::Incoming>()
|
|
|
|
|
{
|
|
|
|
|
return Box::new(Err::<GetActorStateResponse<A>, AdminError>(
|
|
|
|
|
AdminError::TypeMismatch {
|
|
|
|
|
expected_actor_type,
|
|
|
|
|
expected_message_type,
|
|
|
|
|
actual_actor_type: metadata.actor_type_name,
|
|
|
|
|
actual_message_type: metadata.message_type_name,
|
|
|
|
|
},
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Some(typed) = erased.as_any().downcast_ref::<Actor<A>>() else {
|
|
|
|
|
return Box::new(Err::<GetActorStateResponse<A>, AdminError>(
|
|
|
|
|
AdminError::TypeMismatch {
|
|
|
|
|
expected_actor_type,
|
|
|
|
|
expected_message_type,
|
|
|
|
|
actual_actor_type: metadata.actor_type_name,
|
|
|
|
|
actual_message_type: metadata.message_type_name,
|
|
|
|
|
},
|
|
|
|
|
));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Box::new(Ok::<GetActorStateResponse<A>, AdminError>(
|
|
|
|
|
GetActorStateResponse {
|
|
|
|
|
state: ActorStateSnapshot {
|
|
|
|
|
actor,
|
|
|
|
|
actor_type: metadata.actor_type_name,
|
|
|
|
|
message_type: metadata.message_type_name,
|
|
|
|
|
actor_instance: typed.inner().clone(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
))
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
let not_found = Box::new(|actor| {
|
|
|
|
|
Box::new(Err::<GetActorStateResponse<A>, AdminError>(
|
|
|
|
|
AdminError::ActorNotFound { actor },
|
|
|
|
|
)) as Box<dyn Any + Send>
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
tx.send(AdminCommand::GetActorState {
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
actor,
|
|
|
|
|
reply_to,
|
|
|
|
|
get,
|
|
|
|
|
not_found,
|
|
|
|
|
});
|
|
|
|
|
Ok(admin)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn replace_actor_state<A>(
|
|
|
|
|
&self,
|
|
|
|
|
actor: ActorAddress,
|
|
|
|
|
state: ActorStateSnapshot<A>,
|
|
|
|
|
) -> Result<Admin<OperationResult>, Error>
|
|
|
|
|
where
|
|
|
|
|
A: ActorInterface,
|
|
|
|
|
{
|
|
|
|
|
if state.actor != actor {
|
|
|
|
|
return self.ready::<OperationResult>(Err(AdminError::AddressMismatch {
|
|
|
|
|
requested: actor,
|
|
|
|
|
snapshot: state.actor,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
let Some(tx) = self.admin_tx_for(actor) else {
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
return self.ready::<OperationResult>(Err(AdminError::ActorNotFound { actor }));
|
2026-08-11 12:08:06 +00:00
|
|
|
};
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
let (admin, reply_to) = self.new_admin::<OperationResult>()?;
|
|
|
|
|
|
|
|
|
|
let actor_instance = state.actor_instance;
|
|
|
|
|
let replace = Box::new(
|
|
|
|
|
move |erased: &mut dyn AnyActor,
|
|
|
|
|
metadata: ActorTypeMetadata|
|
|
|
|
|
-> AdminResult<OperationResult> {
|
|
|
|
|
let expected_actor_type = std::any::type_name::<A>();
|
|
|
|
|
let expected_message_type = std::any::type_name::<A::Incoming>();
|
|
|
|
|
if metadata.actor_type_id != TypeId::of::<A>()
|
|
|
|
|
|| metadata.message_type_id != TypeId::of::<A::Incoming>()
|
|
|
|
|
{
|
|
|
|
|
return Err(AdminError::TypeMismatch {
|
|
|
|
|
expected_actor_type,
|
|
|
|
|
expected_message_type,
|
|
|
|
|
actual_actor_type: metadata.actor_type_name,
|
|
|
|
|
actual_message_type: metadata.message_type_name,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Some(typed) = erased.as_any_mut().downcast_mut::<Actor<A>>() else {
|
|
|
|
|
return Err(AdminError::TypeMismatch {
|
|
|
|
|
expected_actor_type,
|
|
|
|
|
expected_message_type,
|
|
|
|
|
actual_actor_type: metadata.actor_type_name,
|
|
|
|
|
actual_message_type: metadata.message_type_name,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
typed.replace_inner(actor_instance);
|
|
|
|
|
Ok(OperationResult { applied: true })
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
tx.send(AdminCommand::ReplaceActorState {
|
|
|
|
|
actor,
|
|
|
|
|
reply_to,
|
|
|
|
|
replace,
|
|
|
|
|
});
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
Ok(admin)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn stop_actor(&self, actor: ActorAddress) -> Result<Admin<OperationResult>, Error> {
|
2026-08-11 12:08:06 +00:00
|
|
|
let Some(tx) = self.admin_tx_for(actor) else {
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
return self.ready::<OperationResult>(Err(AdminError::ActorNotFound { actor }));
|
2026-08-11 12:08:06 +00:00
|
|
|
};
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
let (admin, reply_to) = self.new_admin::<OperationResult>()?;
|
2026-08-11 12:08:06 +00:00
|
|
|
tx.send(AdminCommand::StopActor { actor, reply_to });
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
Ok(admin)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn suspend_actor(&self, actor: ActorAddress) -> Result<Admin<OperationResult>, Error> {
|
2026-08-11 12:08:06 +00:00
|
|
|
let Some(tx) = self.admin_tx_for(actor) else {
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
return self.ready::<OperationResult>(Err(AdminError::ActorNotFound { actor }));
|
2026-08-11 12:08:06 +00:00
|
|
|
};
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
let (admin, reply_to) = self.new_admin::<OperationResult>()?;
|
2026-08-11 12:08:06 +00:00
|
|
|
tx.send(AdminCommand::SuspendActor { actor, reply_to });
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
Ok(admin)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn resume_actor(&self, actor: ActorAddress) -> Result<Admin<OperationResult>, Error> {
|
2026-08-11 12:08:06 +00:00
|
|
|
let Some(tx) = self.admin_tx_for(actor) else {
|
|
|
|
|
return self.ready::<OperationResult>(Err(AdminError::ActorNotFound { actor }));
|
|
|
|
|
};
|
|
|
|
|
let (admin, reply_to) = self.new_admin::<OperationResult>()?;
|
|
|
|
|
tx.send(AdminCommand::ResumeActor { actor, reply_to });
|
|
|
|
|
Ok(admin)
|
|
|
|
|
}
|
feat: actor direct control via runtime admin
Add a RuntimeAdmin control plane for out-of-band inspection and mutation of live actors, bypassing their normal message handlers.
- src/admin.rs: add RuntimeAdmin + Admin<T> reply handle, the AdminCommand enum (ListActors, InspectActor, GetActorState, ReplaceActorState, StopActor, SuspendActor, ResumeActor), typed result types (ActorSummary/ActorStatus/ActorStateSnapshot/ListActorsResponse), and AdminError (ActorNotFound/AddressMismatch/TypeMismatch/Timeout)
- src/actor.rs: expose type-erased accessors on AnyActor — metadata() returning ActorTypeMetadata plus as_any()/as_any_mut() for downcasting — and Actor::inner()/replace_inner() so admin can snapshot or swap concrete actor state
- src/runtime.rs: wire a per-worker admin channel (admin_txs), expose Runtime::admin(), and implement the RuntimeAdmin ops that resolve an address to its owning worker, send the command, notify that worker, and return an Admin<T> handle; Admin::recv_ticking drives ticks and collects the reply
- src/worker.rs: add an admin_rx receiver and a drain_admin tick phase run before actor handlers, plus ActorPool helpers (actor_summary, get_actor_erased[_mut], suspend/resume/stop_actor_admin) that apply commands on the worker thread; fold admin_rx into the fast-idle has_work/tick_once checks
- tests/runtime_admin.rs: add 608 lines of end-to-end coverage for list/inspect/get/replace/stop/suspend/resume and the type-mismatch and not-found error paths
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-08 15:23:03 +00:00
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
// ─── SingleThreadRuntime ────────────────────────────────────────────────────
|
2026-02-06 11:25:37 +00:00
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// The explicit manual host: consumes [`RuntimeParts`] and sequentially advances
|
|
|
|
|
/// every worker once per [`try_tick`](Self::try_tick), without locks.
|
|
|
|
|
///
|
|
|
|
|
/// External handles retain only a cloned [`Runtime`]; the host owns the workers.
|
|
|
|
|
pub struct SingleThreadRuntime {
|
|
|
|
|
runtime: Runtime,
|
|
|
|
|
workers: Vec<Worker>,
|
|
|
|
|
}
|
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-08-11 12:08:06 +00:00
|
|
|
impl SingleThreadRuntime {
|
|
|
|
|
/// Consume `parts` and own its workers for manual progression.
|
|
|
|
|
pub fn new(parts: RuntimeParts) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
runtime: parts.runtime,
|
|
|
|
|
workers: parts.workers,
|
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-08-11 12:08:06 +00:00
|
|
|
/// Borrow the cloneable runtime handle.
|
|
|
|
|
pub fn runtime(&self) -> &Runtime {
|
|
|
|
|
&self.runtime
|
2026-02-20 17:34:43 +00:00
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Return whether any owned worker currently has schedulable work.
|
|
|
|
|
pub fn has_work(&self) -> bool {
|
|
|
|
|
self.workers.iter().any(|w| w.has_work())
|
2026-02-20 17:34:43 +00:00
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Advance every owned worker exactly once, combining their results.
|
|
|
|
|
///
|
|
|
|
|
/// Does not short-circuit: later workers are still ticked after an earlier
|
|
|
|
|
/// productive one, so cross-worker backlogs drain on the same call.
|
|
|
|
|
pub fn try_tick(&mut self) -> bool {
|
|
|
|
|
let mut did_work = false;
|
|
|
|
|
for w in &mut self.workers {
|
|
|
|
|
if w.try_tick() {
|
|
|
|
|
did_work = true;
|
|
|
|
|
}
|
2026-02-20 17:34:43 +00:00
|
|
|
}
|
2026-08-11 12:08:06 +00:00
|
|
|
did_work
|
2026-02-20 17:34:43 +00:00
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Drive one pass of every worker (ignoring whether work was done).
|
|
|
|
|
pub fn tick(&mut self) {
|
|
|
|
|
let _ = self.try_tick();
|
2026-02-20 17:34:43 +00:00
|
|
|
}
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|