2026-06-23 15:42:28 +00:00
|
|
|
use crate::Instant;
|
2026-02-06 11:25:37 +00:00
|
|
|
use std::any::Any;
|
|
|
|
|
use std::cell::RefCell;
|
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::collections::{HashMap, VecDeque};
|
2026-02-13 13:27:34 +00:00
|
|
|
use std::sync::Arc;
|
2026-08-09 09:50:33 +00:00
|
|
|
use std::sync::atomic::Ordering;
|
2026-02-06 11:25:37 +00:00
|
|
|
|
2026-06-23 15:42:28 +00:00
|
|
|
use crate::Error;
|
|
|
|
|
use crate::actor::{
|
|
|
|
|
ActorAddress, AnyActor, ContextInner, Ctx, Environment, ExitValue, ResumeSignal, SpawnRequest,
|
|
|
|
|
StopReason, StopSignal, StopWithSignal, SystemInfo,
|
|
|
|
|
};
|
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::{
|
|
|
|
|
ActorStatus, ActorSummary, AdminCommand, AdminError, AdminResult, InspectActorResponse,
|
|
|
|
|
ListActorsResponse, OperationResult,
|
|
|
|
|
};
|
2026-02-07 16:51:40 +00:00
|
|
|
use crate::channel::Receiver;
|
2026-08-11 12:08:06 +00:00
|
|
|
use crate::delivery::{AddrBuildHasher, AddrMap, Envelope, TickContext, WorkerId};
|
2026-02-11 15:23:26 +00:00
|
|
|
use crate::stats::{ActorSnapshot, TickTiming, WorkerStats};
|
2026-02-06 11:25:37 +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
|
|
|
use crate::extension::WorkerExtension;
|
2026-08-11 12:08:06 +00:00
|
|
|
use crate::runtime::RuntimeShared;
|
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-03-28 05:08:58 +00:00
|
|
|
/// Whether an actor should be skipped during `tick_all`.
|
|
|
|
|
pub(crate) fn should_skip_actor(poisoned: bool, stopping: bool, suspended: bool) -> bool {
|
|
|
|
|
poisoned || stopping || suspended
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether `on_stop` should fire for an actor being cleaned up.
|
|
|
|
|
pub(crate) fn is_on_stop_eligible(stopping: bool, poisoned: bool) -> bool {
|
|
|
|
|
stopping && !poisoned
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Determine the `StopReason` for a dead actor based on its flags.
|
|
|
|
|
pub(crate) fn determine_stop_reason(poisoned: bool, has_exit_value: bool) -> StopReason {
|
|
|
|
|
if poisoned {
|
|
|
|
|
StopReason::Panicked
|
|
|
|
|
} else if has_exit_value {
|
|
|
|
|
StopReason::Completed
|
|
|
|
|
} else {
|
|
|
|
|
StopReason::Normal
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Route a runtime-injected message (extension output, death notification) to
|
|
|
|
|
/// its destination: same-worker pool first, then the owning worker's transfer
|
|
|
|
|
/// queue, then the non-local (inbox/transport) seam.
|
|
|
|
|
fn route_runtime_message(
|
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
|
|
|
pool: &mut ActorPool,
|
|
|
|
|
tc: &TickContext,
|
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
|
|
|
dest: ActorAddress,
|
|
|
|
|
msg: Box<dyn Any + Send>,
|
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
|
|
|
) {
|
|
|
|
|
if pool.contains(&dest) {
|
|
|
|
|
pool.deliver(&dest, msg);
|
|
|
|
|
} else {
|
2026-08-11 12:08:06 +00:00
|
|
|
match tc.worker_of(&dest) {
|
|
|
|
|
Some(w) => tc.transfer_tx(w).send(Envelope::new(dest, msg)),
|
|
|
|
|
None => {
|
|
|
|
|
let _ = tc.route_nonlocal(dest, msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-13 07:42:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// A worker owns a disjoint set of actors and processes them via [`Worker::try_tick`].
|
|
|
|
|
///
|
|
|
|
|
/// Each worker is owned by exactly one execution host (a [`SingleThreadRuntime`]
|
|
|
|
|
/// or an engine driver) and requires only `Send`, not `Sync`.
|
|
|
|
|
pub struct Worker {
|
|
|
|
|
pub(crate) id: WorkerId,
|
|
|
|
|
pub(crate) shared: Arc<RuntimeShared>,
|
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(crate) pool: ActorPool,
|
2026-02-06 11:25:37 +00:00
|
|
|
transfer_rx: Receiver<Envelope>,
|
2026-02-20 17:34:43 +00:00
|
|
|
spawn_rx: Receiver<SpawnRequest>,
|
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
|
|
|
admin_rx: Receiver<AdminCommand>,
|
2026-08-11 12:08:06 +00:00
|
|
|
pub(crate) stats: Arc<WorkerStats>,
|
2026-02-11 15:23:26 +00:00
|
|
|
/// Reusable scratch buffer for building per-actor snapshots.
|
|
|
|
|
snapshot_buf: Vec<ActorSnapshot>,
|
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
|
|
|
/// Per-worker extension (e.g., timer wheel). Created by RuntimeExtension factory.
|
|
|
|
|
pub(crate) worker_ext: Option<Box<dyn WorkerExtension>>,
|
2026-02-25 11:11:03 +00:00
|
|
|
/// True if the previous tick did work — ensures one full tick follows a productive
|
|
|
|
|
/// tick so pending_local messages delivered to mailboxes get drained.
|
|
|
|
|
has_backlog: bool,
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Transfers whose address is mapped to this worker but whose spawn request
|
|
|
|
|
/// has not reached the pool yet.
|
|
|
|
|
deferred_transfers: VecDeque<Envelope>,
|
|
|
|
|
/// Targeted admin commands waiting for their mapped spawn to be installed.
|
|
|
|
|
deferred_admin: VecDeque<AdminCommand>,
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Worker {
|
2026-02-06 14:45:19 +00:00
|
|
|
pub(crate) fn new(
|
2026-08-11 12:08:06 +00:00
|
|
|
id: WorkerId,
|
|
|
|
|
shared: Arc<RuntimeShared>,
|
2026-02-06 11:25:37 +00:00
|
|
|
transfer_rx: Receiver<Envelope>,
|
2026-02-20 17:34:43 +00:00
|
|
|
spawn_rx: Receiver<SpawnRequest>,
|
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
|
|
|
admin_rx: Receiver<AdminCommand>,
|
2026-02-06 14:45:19 +00:00
|
|
|
stats: Arc<WorkerStats>,
|
2026-02-06 11:25:37 +00:00
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
2026-08-11 12:08:06 +00:00
|
|
|
id,
|
|
|
|
|
shared,
|
2026-03-28 05:08:58 +00:00
|
|
|
pool: ActorPool::new(),
|
2026-02-06 11:25:37 +00:00
|
|
|
transfer_rx,
|
|
|
|
|
spawn_rx,
|
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
|
|
|
admin_rx,
|
2026-02-06 14:45:19 +00:00
|
|
|
stats,
|
2026-02-11 15:23:26 +00:00
|
|
|
snapshot_buf: Vec::new(),
|
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
|
|
|
worker_ext: None,
|
2026-02-25 11:11:03 +00:00
|
|
|
has_backlog: false,
|
2026-08-11 12:08:06 +00:00
|
|
|
deferred_transfers: VecDeque::new(),
|
|
|
|
|
deferred_admin: VecDeque::new(),
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-15 10:48:13 +00:00
|
|
|
pub(crate) fn has_work(&self) -> bool {
|
|
|
|
|
self.has_backlog
|
|
|
|
|
|| !self.spawn_rx.is_empty()
|
|
|
|
|
|| !self.transfer_rx.is_empty()
|
2026-08-11 12:08:06 +00:00
|
|
|
|| !self.deferred_transfers.is_empty()
|
2026-07-15 10:48:13 +00:00
|
|
|
|| !self.admin_rx.is_empty()
|
2026-08-11 12:08:06 +00:00
|
|
|
|| !self.deferred_admin.is_empty()
|
2026-07-15 10:48:13 +00:00
|
|
|
|| self
|
|
|
|
|
.worker_ext
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(false, |e| e.has_pending_work())
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Run one synchronous worker pass. Returns `true` if any work was done.
|
|
|
|
|
///
|
|
|
|
|
/// This is the only core worker transition. The owning host calls it; the
|
|
|
|
|
/// worker never drives itself.
|
|
|
|
|
pub fn try_tick(&mut self) -> bool {
|
|
|
|
|
let wid = self.id;
|
|
|
|
|
|
|
|
|
|
// Fast idle path: skip the entire tick when nothing could have changed.
|
|
|
|
|
// Cost: ~3 atomic loads, zero syscalls, zero actor iteration.
|
|
|
|
|
if !self.has_work() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build the routing context from disjoint fields so the mutable
|
|
|
|
|
// per-pass state (pool, queues, extension) can still be borrowed below.
|
|
|
|
|
let shared = &self.shared;
|
|
|
|
|
let tc = TickContext {
|
|
|
|
|
address_map: &shared.address_map,
|
|
|
|
|
spawn_txs: &shared.spawn_txs,
|
|
|
|
|
transfer_txs: &shared.transfer_txs,
|
|
|
|
|
inbox_registry: &shared.inbox_registry,
|
|
|
|
|
config: &shared.config,
|
|
|
|
|
extension: shared.extension.get().map(|a| a.as_ref()),
|
|
|
|
|
process_output_observer: shared.process_output_observer.get(),
|
|
|
|
|
stats_hook: shared.stats_hook.get().map(|a| a.as_ref()),
|
|
|
|
|
worker_stats: &self.stats,
|
|
|
|
|
num_workers: shared.worker_stats.len(),
|
|
|
|
|
worker_id: wid,
|
|
|
|
|
created_at: shared.created_at,
|
|
|
|
|
#[cfg(feature = "transport")]
|
|
|
|
|
remote_sink: shared.remote_sink.get().map(|a| a.as_ref()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
let _span = tracing::trace_span!("worker.tick", worker_id = wid.index()).entered();
|
|
|
|
|
|
|
|
|
|
let mut did_work = false;
|
|
|
|
|
let t0 = Instant::now();
|
|
|
|
|
|
|
|
|
|
// 1. Drain spawn queue → add actors to pool
|
|
|
|
|
did_work |= Self::drain_spawns(
|
|
|
|
|
&mut self.pool,
|
|
|
|
|
&self.spawn_rx,
|
|
|
|
|
&tc,
|
|
|
|
|
tc.config.worker_ingress_budget,
|
|
|
|
|
);
|
|
|
|
|
let t1 = Instant::now();
|
|
|
|
|
|
|
|
|
|
// 2. Drain retained transfers first, then the transfer queue → deliver
|
|
|
|
|
// envelopes to actors or retain them until their mapped spawn installs.
|
|
|
|
|
did_work |= Self::drain_transfers(
|
|
|
|
|
&mut self.pool,
|
|
|
|
|
&self.transfer_rx,
|
|
|
|
|
&mut self.deferred_transfers,
|
|
|
|
|
&tc,
|
|
|
|
|
tc.config.worker_ingress_budget,
|
|
|
|
|
);
|
|
|
|
|
let t2 = Instant::now();
|
|
|
|
|
|
|
|
|
|
// 3. Drain admin queue → inspect or mutate worker-owned slots before handlers
|
|
|
|
|
did_work |= Self::drain_admin(
|
|
|
|
|
&mut self.pool,
|
|
|
|
|
&self.admin_rx,
|
|
|
|
|
&mut self.deferred_admin,
|
|
|
|
|
&tc,
|
|
|
|
|
tc.config.worker_ingress_budget,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 4. Fire per-worker extension (e.g., timers) → deliver before tick_all
|
|
|
|
|
let ext_msgs: Vec<_> = self
|
|
|
|
|
.worker_ext
|
|
|
|
|
.as_mut()
|
|
|
|
|
.map(|ext| ext.on_tick())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
for (dest, msg) in ext_msgs {
|
|
|
|
|
route_runtime_message(&mut self.pool, &tc, dest, msg);
|
|
|
|
|
did_work = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. Tick all actors with WorkerContext
|
|
|
|
|
let pending_local: RefCell<Vec<(ActorAddress, Box<dyn Any + Send>)>> =
|
|
|
|
|
RefCell::new(Vec::new());
|
|
|
|
|
let stop_requests: RefCell<Vec<ActorAddress>> = RefCell::new(Vec::new());
|
|
|
|
|
let stop_with_values: RefCell<Vec<(ActorAddress, ExitValue)>> = RefCell::new(Vec::new());
|
|
|
|
|
let suspend_requests: RefCell<Vec<ActorAddress>> = RefCell::new(Vec::new());
|
|
|
|
|
let worker_requests: RefCell<Vec<Box<dyn Any + Send>>> = RefCell::new(Vec::new());
|
|
|
|
|
|
|
|
|
|
let processed;
|
|
|
|
|
{
|
|
|
|
|
let worker_ctx = WorkerContext {
|
|
|
|
|
tc: &tc,
|
|
|
|
|
pending_local: &pending_local,
|
|
|
|
|
stop_requests: &stop_requests,
|
|
|
|
|
stop_with_values: &stop_with_values,
|
|
|
|
|
suspend_requests: &suspend_requests,
|
|
|
|
|
worker_requests: &worker_requests,
|
|
|
|
|
stats: &self.stats,
|
|
|
|
|
};
|
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
|
|
|
processed =
|
|
|
|
|
self.pool
|
|
|
|
|
.tick_all(&worker_ctx, &self.stats, tc.config.actor_message_budget);
|
2026-08-11 12:08:06 +00:00
|
|
|
if processed > 0 {
|
|
|
|
|
did_work = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let t3 = Instant::now();
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
if processed > 0 {
|
|
|
|
|
tracing::debug!(
|
|
|
|
|
worker_id = wid.index(),
|
|
|
|
|
messages_processed = processed,
|
|
|
|
|
"worker.tick_all"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. Drain spawn queue again — actors spawned during step 5
|
|
|
|
|
// must be in the pool before pending_local delivery.
|
|
|
|
|
did_work |= Self::drain_spawns(
|
|
|
|
|
&mut self.pool,
|
|
|
|
|
&self.spawn_rx,
|
|
|
|
|
&tc,
|
|
|
|
|
tc.config.worker_ingress_budget,
|
|
|
|
|
);
|
|
|
|
|
let t4 = Instant::now();
|
|
|
|
|
|
|
|
|
|
// 7. Drain pending_local buffer → deliver to local actors
|
|
|
|
|
let pending = pending_local.into_inner();
|
|
|
|
|
if !pending.is_empty() {
|
|
|
|
|
did_work = true;
|
|
|
|
|
}
|
|
|
|
|
for (addr, msg) in pending {
|
|
|
|
|
Self::deliver_or_defer_transfer(
|
|
|
|
|
&mut self.pool,
|
|
|
|
|
&tc,
|
|
|
|
|
&mut self.deferred_transfers,
|
|
|
|
|
Envelope::new(addr, msg),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7.5. Process worker extension requests from handlers (e.g., timer scheduling)
|
|
|
|
|
if let Some(ext) = &mut self.worker_ext {
|
|
|
|
|
for request in worker_requests.into_inner() {
|
|
|
|
|
ext.handle_request(request);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let t5 = Instant::now();
|
|
|
|
|
|
|
|
|
|
// 8. Publish stats (skip entirely when idle to avoid allocation + mutex)
|
|
|
|
|
if did_work {
|
|
|
|
|
self.stats
|
|
|
|
|
.num_actors
|
|
|
|
|
.store(self.pool.len(), Ordering::Relaxed);
|
|
|
|
|
self.stats
|
|
|
|
|
.total_mailbox_depth
|
|
|
|
|
.store(self.pool.total_mailbox_depth(), Ordering::Relaxed);
|
|
|
|
|
self.stats
|
|
|
|
|
.messages_processed
|
|
|
|
|
.fetch_add(processed as u64, Ordering::Relaxed);
|
|
|
|
|
|
|
|
|
|
if let Some(hook) = tc.stats_hook {
|
|
|
|
|
self.pool.mailbox_depths_into(&mut self.snapshot_buf);
|
|
|
|
|
hook.on_tick(wid.index(), &self.snapshot_buf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let t6 = Instant::now();
|
|
|
|
|
|
|
|
|
|
// Record tick timing
|
|
|
|
|
let timing = TickTiming {
|
|
|
|
|
phase_us: [
|
|
|
|
|
t1.duration_since(t0).as_micros() as u64,
|
|
|
|
|
t2.duration_since(t1).as_micros() as u64,
|
|
|
|
|
t3.duration_since(t2).as_micros() as u64,
|
|
|
|
|
t4.duration_since(t3).as_micros() as u64,
|
|
|
|
|
t5.duration_since(t4).as_micros() as u64,
|
|
|
|
|
t6.duration_since(t5).as_micros() as u64,
|
|
|
|
|
],
|
|
|
|
|
messages_processed: processed,
|
|
|
|
|
did_work,
|
|
|
|
|
};
|
|
|
|
|
self.stats.push_tick_timing(timing);
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
if did_work {
|
|
|
|
|
tracing::debug!(
|
|
|
|
|
worker_id = wid.index(),
|
|
|
|
|
num_actors = self.pool.len(),
|
|
|
|
|
mailbox_depth = self.pool.total_mailbox_depth(),
|
|
|
|
|
messages_processed = processed,
|
|
|
|
|
"worker.stats"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
Enforce actor-owned Myelin control flow
Add a repository-owned rustc wrapper that enforces execution ownership and dependency boundaries during ordinary Cargo commands, with compile-pass and compile-fail policy contracts.
Move scheduling, timers, provider polling, provisioning, recovery, supervision, and shutdown decisions behind engine and actor APIs. Add deterministic component properties, stateful Myelin lifecycle coverage, persisted regression cases, and the bounded CI workflow.
Tighten resource ownership by cancelling telemetry collectors, terminating reply observers, bounding dashboard projections, and releasing process file descriptors, child observers, and inode-verified Unix socket paths on every exit path.
2026-08-19 21:38:14 +00:00
|
|
|
// 9. Clean up poisoned and stopping actors. Cleanup changes the pool after
|
|
|
|
|
// phase 8 published its snapshot, so republish cardinality and mailbox
|
|
|
|
|
// state when actors were removed rather than leaving observability stale
|
|
|
|
|
// until unrelated work reaches this worker.
|
|
|
|
|
let cleaned_dead = Self::cleanup_dead_actors(
|
2026-08-11 12:08:06 +00:00
|
|
|
&mut self.pool,
|
|
|
|
|
&mut self.worker_ext,
|
|
|
|
|
&mut self.deferred_transfers,
|
|
|
|
|
&tc,
|
|
|
|
|
);
|
Enforce actor-owned Myelin control flow
Add a repository-owned rustc wrapper that enforces execution ownership and dependency boundaries during ordinary Cargo commands, with compile-pass and compile-fail policy contracts.
Move scheduling, timers, provider polling, provisioning, recovery, supervision, and shutdown decisions behind engine and actor APIs. Add deterministic component properties, stateful Myelin lifecycle coverage, persisted regression cases, and the bounded CI workflow.
Tighten resource ownership by cancelling telemetry collectors, terminating reply observers, bounding dashboard projections, and releasing process file descriptors, child observers, and inode-verified Unix socket paths on every exit path.
2026-08-19 21:38:14 +00:00
|
|
|
if cleaned_dead {
|
|
|
|
|
did_work = true;
|
|
|
|
|
self.stats
|
|
|
|
|
.num_actors
|
|
|
|
|
.store(self.pool.len(), Ordering::Relaxed);
|
|
|
|
|
self.stats
|
|
|
|
|
.total_mailbox_depth
|
|
|
|
|
.store(self.pool.total_mailbox_depth(), Ordering::Relaxed);
|
|
|
|
|
if let Some(hook) = tc.stats_hook {
|
|
|
|
|
self.pool.mailbox_depths_into(&mut self.snapshot_buf);
|
|
|
|
|
hook.on_tick(wid.index(), &self.snapshot_buf);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-11 12:08:06 +00:00
|
|
|
|
|
|
|
|
self.has_backlog = did_work;
|
|
|
|
|
did_work
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn drain_spawns(
|
|
|
|
|
pool: &mut ActorPool,
|
|
|
|
|
spawn_rx: &Receiver<SpawnRequest>,
|
|
|
|
|
tc: &TickContext,
|
|
|
|
|
budget: usize,
|
|
|
|
|
) -> bool {
|
2026-02-06 11:25:37 +00:00
|
|
|
let mut did_work = false;
|
2026-08-11 12:08:06 +00:00
|
|
|
let mut count = 0usize;
|
2026-02-09 09:04:57 +00:00
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
let mut spawn_count: usize = 0;
|
2026-08-11 12:08:06 +00:00
|
|
|
while let Some(mut req) = spawn_rx.try_recv() {
|
2026-02-20 17:34:43 +00:00
|
|
|
if let Some(ext) = tc.extension {
|
2026-06-23 15:42:28 +00:00
|
|
|
req.env = ext.on_spawn(
|
|
|
|
|
req.addr,
|
|
|
|
|
req.parent,
|
|
|
|
|
req.env,
|
|
|
|
|
tc.created_at.elapsed().as_millis() as u64,
|
|
|
|
|
);
|
2026-02-20 17:34:43 +00:00
|
|
|
}
|
2026-08-11 12:08:06 +00:00
|
|
|
pool.insert(req);
|
2026-02-09 09:04:57 +00:00
|
|
|
#[cfg(feature = "tracing")]
|
2026-06-23 15:42:28 +00:00
|
|
|
{
|
|
|
|
|
spawn_count += 1;
|
|
|
|
|
}
|
2026-02-06 11:25:37 +00:00
|
|
|
did_work = true;
|
2026-08-11 12:08:06 +00:00
|
|
|
count += 1;
|
|
|
|
|
if budget != 0 && count >= budget {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
2026-02-09 09:04:57 +00:00
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
if spawn_count > 0 {
|
2026-06-23 15:42:28 +00:00
|
|
|
tracing::debug!(
|
2026-08-11 12:08:06 +00:00
|
|
|
worker_id = tc.worker_id().index(),
|
2026-06-23 15:42:28 +00:00
|
|
|
count = spawn_count,
|
|
|
|
|
"worker.spawns_drained"
|
|
|
|
|
);
|
2026-02-09 09:04:57 +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
|
|
|
did_work
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
fn deliver_or_defer_transfer(
|
|
|
|
|
pool: &mut ActorPool,
|
|
|
|
|
tc: &TickContext,
|
|
|
|
|
deferred: &mut VecDeque<Envelope>,
|
|
|
|
|
envelope: Envelope,
|
|
|
|
|
) -> bool {
|
|
|
|
|
let dest = envelope.dest();
|
|
|
|
|
if pool.contains(&dest) {
|
|
|
|
|
pool.deliver(&dest, envelope.into_payload());
|
|
|
|
|
true
|
|
|
|
|
} else if tc.worker_of(&dest) == Some(tc.worker_id()) {
|
|
|
|
|
deferred.push_back(envelope);
|
|
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn drain_transfers(
|
|
|
|
|
pool: &mut ActorPool,
|
|
|
|
|
transfer_rx: &Receiver<Envelope>,
|
|
|
|
|
deferred: &mut VecDeque<Envelope>,
|
|
|
|
|
tc: &TickContext,
|
|
|
|
|
budget: usize,
|
|
|
|
|
) -> bool {
|
|
|
|
|
let mut did_work = false;
|
|
|
|
|
let mut count = 0usize;
|
|
|
|
|
|
|
|
|
|
while budget == 0 || count < budget {
|
|
|
|
|
let Some(envelope) = deferred.pop_front() else {
|
|
|
|
|
break;
|
|
|
|
|
};
|
|
|
|
|
if Self::deliver_or_defer_transfer(pool, tc, deferred, envelope) {
|
|
|
|
|
did_work = true;
|
|
|
|
|
}
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while budget == 0 || count < budget {
|
|
|
|
|
let Some(envelope) = transfer_rx.try_recv() else {
|
|
|
|
|
break;
|
|
|
|
|
};
|
|
|
|
|
did_work = true;
|
|
|
|
|
Self::deliver_or_defer_transfer(pool, tc, deferred, envelope);
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
did_work
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn admin_target(cmd: &AdminCommand) -> Option<ActorAddress> {
|
|
|
|
|
match cmd {
|
|
|
|
|
AdminCommand::ListActors { .. } => None,
|
|
|
|
|
AdminCommand::InspectActor { actor, .. }
|
|
|
|
|
| AdminCommand::GetActorState { actor, .. }
|
|
|
|
|
| AdminCommand::ReplaceActorState { actor, .. }
|
|
|
|
|
| AdminCommand::StopActor { actor, .. }
|
|
|
|
|
| AdminCommand::SuspendActor { actor, .. }
|
|
|
|
|
| AdminCommand::ResumeActor { actor, .. } => Some(*actor),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
|
|
|
fn should_defer_admin_command(pool: &ActorPool, tc: &TickContext, cmd: &AdminCommand) -> bool {
|
2026-08-11 12:08:06 +00:00
|
|
|
Self::admin_target(cmd).is_some_and(|actor| {
|
|
|
|
|
!pool.contains(&actor) && tc.worker_of(&actor) == Some(tc.worker_id())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_or_defer_admin_command(
|
|
|
|
|
pool: &mut ActorPool,
|
|
|
|
|
tc: &TickContext,
|
|
|
|
|
deferred: &mut VecDeque<AdminCommand>,
|
|
|
|
|
cmd: AdminCommand,
|
|
|
|
|
) -> bool {
|
|
|
|
|
if Self::should_defer_admin_command(pool, tc, &cmd) {
|
|
|
|
|
deferred.push_back(cmd);
|
|
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
Self::apply_admin_command(pool, tc, cmd);
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn drain_admin(
|
|
|
|
|
pool: &mut ActorPool,
|
|
|
|
|
admin_rx: &Receiver<AdminCommand>,
|
|
|
|
|
deferred: &mut VecDeque<AdminCommand>,
|
|
|
|
|
tc: &TickContext,
|
|
|
|
|
budget: usize,
|
|
|
|
|
) -> bool {
|
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 mut did_work = false;
|
2026-08-11 12:08:06 +00:00
|
|
|
let mut count = 0usize;
|
|
|
|
|
|
|
|
|
|
while budget == 0 || count < budget {
|
|
|
|
|
let Some(cmd) = deferred.pop_front() else {
|
|
|
|
|
break;
|
|
|
|
|
};
|
|
|
|
|
if Self::apply_or_defer_admin_command(pool, tc, deferred, cmd) {
|
|
|
|
|
did_work = true;
|
|
|
|
|
}
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while budget == 0 || count < budget {
|
|
|
|
|
let Some(cmd) = admin_rx.try_recv() else {
|
|
|
|
|
break;
|
|
|
|
|
};
|
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
|
|
|
did_work = true;
|
2026-08-11 12:08:06 +00:00
|
|
|
Self::apply_or_defer_admin_command(pool, tc, deferred, cmd);
|
|
|
|
|
count += 1;
|
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
|
|
|
|
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
|
|
|
did_work
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn send_admin_reply<T: crate::actor::Message>(
|
|
|
|
|
tc: &TickContext,
|
|
|
|
|
reply_to: ActorAddress,
|
|
|
|
|
result: AdminResult<T>,
|
|
|
|
|
) {
|
|
|
|
|
let _ = tc.inbox_registry.try_deliver(reply_to, Box::new(result));
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
fn apply_admin_command(pool: &mut ActorPool, tc: &TickContext, cmd: AdminCommand) {
|
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
|
|
|
match cmd {
|
|
|
|
|
AdminCommand::ListActors { acc } => {
|
|
|
|
|
let mut local = Vec::new();
|
2026-08-11 12:08:06 +00:00
|
|
|
pool.actor_summaries_into(&mut local, tc.worker_id);
|
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 mut summaries = acc.summaries.lock();
|
|
|
|
|
summaries.extend(local);
|
|
|
|
|
}
|
|
|
|
|
if acc.remaining.fetch_sub(1, Ordering::AcqRel) == 1 {
|
|
|
|
|
let actors = {
|
|
|
|
|
let mut summaries = acc.summaries.lock();
|
|
|
|
|
std::mem::take(&mut *summaries)
|
|
|
|
|
};
|
|
|
|
|
Self::send_admin_reply(tc, acc.reply_to, Ok(ListActorsResponse { actors }));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
AdminCommand::InspectActor { actor, reply_to } => {
|
2026-08-11 12:08:06 +00:00
|
|
|
let result = pool
|
|
|
|
|
.actor_summary(actor, tc.worker_id)
|
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
|
|
|
.map(|summary| InspectActorResponse { summary });
|
|
|
|
|
Self::send_admin_reply(tc, reply_to, result);
|
|
|
|
|
}
|
|
|
|
|
AdminCommand::GetActorState {
|
|
|
|
|
actor,
|
|
|
|
|
reply_to,
|
|
|
|
|
get,
|
|
|
|
|
not_found,
|
|
|
|
|
} => {
|
2026-08-11 12:08:06 +00:00
|
|
|
let boxed = match pool.get_actor_erased(actor) {
|
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
|
|
|
Some(erased) => get(actor, erased, erased.metadata()),
|
|
|
|
|
None => not_found(actor),
|
|
|
|
|
};
|
|
|
|
|
let _ = tc.inbox_registry.try_deliver(reply_to, boxed);
|
|
|
|
|
}
|
|
|
|
|
AdminCommand::ReplaceActorState {
|
|
|
|
|
actor,
|
|
|
|
|
reply_to,
|
|
|
|
|
replace,
|
|
|
|
|
} => {
|
2026-08-11 12:08:06 +00:00
|
|
|
let result = match pool.get_actor_erased_mut(actor) {
|
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
|
|
|
Some(erased) => {
|
|
|
|
|
let metadata = erased.metadata();
|
|
|
|
|
replace(erased, metadata)
|
|
|
|
|
}
|
|
|
|
|
None => Err(AdminError::ActorNotFound { actor }),
|
|
|
|
|
};
|
|
|
|
|
Self::send_admin_reply(tc, reply_to, result);
|
|
|
|
|
}
|
|
|
|
|
AdminCommand::StopActor { actor, reply_to } => {
|
2026-08-11 12:08:06 +00:00
|
|
|
let result = pool.stop_actor_admin(actor, tc.worker_stats);
|
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
|
|
|
Self::send_admin_reply(tc, reply_to, result);
|
|
|
|
|
}
|
|
|
|
|
AdminCommand::SuspendActor { actor, reply_to } => {
|
2026-08-11 12:08:06 +00:00
|
|
|
let result = pool.suspend_actor_admin(actor);
|
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
|
|
|
Self::send_admin_reply(tc, reply_to, result);
|
|
|
|
|
}
|
|
|
|
|
AdminCommand::ResumeActor { actor, reply_to } => {
|
2026-08-11 12:08:06 +00:00
|
|
|
let result = pool.resume_actor_admin(actor);
|
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
|
|
|
Self::send_admin_reply(tc, reply_to, result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Phase 9: clean up dead actors, deliver death notifications, GC extension state.
|
|
|
|
|
fn cleanup_dead_actors(
|
|
|
|
|
pool: &mut ActorPool,
|
|
|
|
|
worker_ext: &mut Option<Box<dyn WorkerExtension>>,
|
|
|
|
|
deferred_transfers: &mut VecDeque<Envelope>,
|
|
|
|
|
tc: &TickContext,
|
|
|
|
|
) -> bool {
|
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
|
|
|
let cleanup_pending: RefCell<Vec<(ActorAddress, Box<dyn Any + Send>)>> =
|
|
|
|
|
RefCell::new(Vec::new());
|
|
|
|
|
let cleanup_stops: RefCell<Vec<ActorAddress>> = RefCell::new(Vec::new());
|
2026-02-20 17:34:43 +00:00
|
|
|
let cleanup_stop_withs: RefCell<Vec<(ActorAddress, ExitValue)>> = RefCell::new(Vec::new());
|
|
|
|
|
let cleanup_suspends: RefCell<Vec<ActorAddress>> = RefCell::new(Vec::new());
|
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
|
|
|
let cleanup_requests: RefCell<Vec<Box<dyn Any + Send>>> = RefCell::new(Vec::new());
|
|
|
|
|
let dead = {
|
|
|
|
|
let cleanup_ctx = WorkerContext {
|
|
|
|
|
tc,
|
|
|
|
|
pending_local: &cleanup_pending,
|
|
|
|
|
stop_requests: &cleanup_stops,
|
2026-02-20 17:34:43 +00:00
|
|
|
stop_with_values: &cleanup_stop_withs,
|
|
|
|
|
suspend_requests: &cleanup_suspends,
|
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
|
|
|
worker_requests: &cleanup_requests,
|
2026-08-11 12:08:06 +00:00
|
|
|
stats: tc.worker_stats,
|
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
|
|
|
};
|
2026-08-11 12:08:06 +00:00
|
|
|
pool.cleanup_dead(&cleanup_ctx)
|
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
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let had_dead = !dead.is_empty();
|
|
|
|
|
if had_dead {
|
2026-02-20 17:34:43 +00:00
|
|
|
for (addr, _, _) in &dead {
|
|
|
|
|
tc.address_map.remove(addr);
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(ext) = tc.extension {
|
|
|
|
|
let notifications = ext.on_actor_death(&dead);
|
2026-02-20 17:34:43 +00:00
|
|
|
let dead_addrs: Vec<_> = dead.iter().map(|(a, _, _)| *a).collect();
|
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
|
|
|
ext.cleanup_dead(&dead_addrs);
|
|
|
|
|
for (dest, msg) in notifications {
|
2026-08-11 12:08:06 +00:00
|
|
|
route_runtime_message(pool, tc, dest, msg);
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
|
|
|
tc.worker_stats
|
|
|
|
|
.num_actors
|
|
|
|
|
.store(pool.len(), Ordering::Relaxed);
|
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
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
// Deliver any messages sent during on_stop callbacks.
|
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
|
|
|
for (addr, msg) in cleanup_pending.into_inner() {
|
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
|
|
|
Self::deliver_or_defer_transfer(pool, tc, deferred_transfers, Envelope::new(addr, msg));
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GC per-worker extension state for dead actors
|
2026-08-11 12:08:06 +00:00
|
|
|
if let Some(ext) = worker_ext {
|
2026-02-20 17:34:43 +00:00
|
|
|
let dead_addrs: Vec<ActorAddress> = dead.iter().map(|(a, _, _)| *a).collect();
|
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
|
|
|
ext.gc_dead(&dead_addrs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
had_dead
|
|
|
|
|
}
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
|
2026-08-09 09:50:33 +00:00
|
|
|
/// The `ContextInner` impl for in-worker sends.
|
2026-02-06 11:25:37 +00:00
|
|
|
///
|
2026-08-11 12:08:06 +00:00
|
|
|
/// Same-worker sends are staged in `pending_local` (eligible next pass).
|
|
|
|
|
/// Cross-worker sends move an `Envelope` into the target worker's transfer
|
|
|
|
|
/// queue. Non-actor addresses route to the inbox registry / transport seam.
|
2026-02-06 11:25:37 +00:00
|
|
|
struct WorkerContext<'a> {
|
2026-02-07 10:36:45 +00:00
|
|
|
tc: &'a TickContext<'a>,
|
2026-02-06 11:25:37 +00:00
|
|
|
pending_local: &'a RefCell<Vec<(ActorAddress, 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
|
|
|
stop_requests: &'a RefCell<Vec<ActorAddress>>,
|
2026-02-20 17:34:43 +00:00
|
|
|
stop_with_values: &'a RefCell<Vec<(ActorAddress, ExitValue)>>,
|
|
|
|
|
suspend_requests: &'a RefCell<Vec<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
|
|
|
worker_requests: &'a RefCell<Vec<Box<dyn Any + Send>>>,
|
2026-02-09 09:04:57 +00:00
|
|
|
stats: &'a WorkerStats,
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ContextInner for WorkerContext<'_> {
|
|
|
|
|
fn send_any(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error> {
|
2026-08-11 12:08:06 +00:00
|
|
|
match self.tc.worker_of(&addr) {
|
|
|
|
|
Some(w) if w == self.tc.worker_id() => {
|
|
|
|
|
self.stats.local_sends.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
self.pending_local.borrow_mut().push((addr, msg));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Some(w) => {
|
|
|
|
|
// Cross-worker: move the payload through shared memory.
|
|
|
|
|
self.stats.local_sends.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
self.tc.transfer_tx(w).send(Envelope::new(addr, msg));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
self.stats.inbox_sends.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
self.tc.route_nonlocal(addr, msg)
|
|
|
|
|
}
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
fn spawn_any(&self, request: SpawnRequest) {
|
2026-08-11 12:08:06 +00:00
|
|
|
// ctx.spawn pins the child to the current worker.
|
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
|
|
|
self.tc
|
|
|
|
|
.address_map
|
|
|
|
|
.insert(request.addr, self.tc.worker_id());
|
2026-08-11 12:08:06 +00:00
|
|
|
self.tc.spawn_tx(self.tc.worker_id()).send(request);
|
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 request_stop(&self, addr: ActorAddress) {
|
|
|
|
|
self.stop_requests.borrow_mut().push(addr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
fn request_stop_with(&self, addr: ActorAddress, value: ExitValue) {
|
|
|
|
|
self.stop_with_values.borrow_mut().push((addr, value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn request_suspend(&self, addr: ActorAddress) {
|
|
|
|
|
self.suspend_requests.borrow_mut().push(addr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn request_resume(&self, addr: ActorAddress) {
|
2026-06-23 15:42:28 +00:00
|
|
|
self.pending_local
|
|
|
|
|
.borrow_mut()
|
|
|
|
|
.push((addr, Box::new(ResumeSignal)));
|
2026-02-20 17:34:43 +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
|
|
|
fn post_worker_request(&self, request: Box<dyn Any + Send>) {
|
|
|
|
|
self.worker_requests.borrow_mut().push(request);
|
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 extension(&self) -> Option<&dyn crate::extension::RuntimeExtension> {
|
|
|
|
|
self.tc.extension
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
2026-02-20 17:34:43 +00:00
|
|
|
|
2026-06-06 17:53:25 +00:00
|
|
|
fn process_output_observer(
|
|
|
|
|
&self,
|
|
|
|
|
) -> Option<std::sync::Arc<dyn crate::process_observer::ProcessOutputObserver>> {
|
|
|
|
|
self.tc.process_output_observer.cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
fn system_info(&self) -> SystemInfo {
|
|
|
|
|
SystemInfo {
|
2026-08-11 12:08:06 +00:00
|
|
|
worker_id: self.tc.worker_id().index(),
|
|
|
|
|
num_workers: self.tc.num_workers,
|
|
|
|
|
total_actors: self.tc.address_map.len(),
|
2026-02-20 17:34:43 +00:00
|
|
|
uptime_ms: self.tc.created_at.elapsed().as_millis() as u64,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-06 14:45:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct ActorSlot {
|
|
|
|
|
mailbox: VecDeque<Box<dyn Any + Send>>,
|
|
|
|
|
actor: Box<dyn AnyActor>,
|
2026-02-10 07:35:58 +00:00
|
|
|
poisoned: bool,
|
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
|
|
|
/// Graceful stop requested (via StopSignal).
|
|
|
|
|
stopping: bool,
|
|
|
|
|
/// Whether on_start has been called for this actor.
|
|
|
|
|
started: bool,
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Actor is suspended — messages queue but are not processed.
|
|
|
|
|
suspended: bool,
|
2026-02-11 15:23:26 +00:00
|
|
|
last_msg_type: Option<&'static str>,
|
|
|
|
|
messages_processed: u64,
|
2026-02-13 14:42:10 +00:00
|
|
|
/// Per-message-type counters (bounded to 32 entries).
|
|
|
|
|
msg_type_counts: HashMap<&'static str, u64>,
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Address of the actor that spawned this one, or `None` for externally-spawned actors.
|
|
|
|
|
parent_addr: Option<ActorAddress>,
|
|
|
|
|
/// Inherited environment from parent (or empty for runtime-spawned actors).
|
|
|
|
|
env: Environment,
|
|
|
|
|
/// Typed exit value set by `ctx.stop_with()`.
|
|
|
|
|
exit_value: Option<ExitValue>,
|
2026-02-06 14:45:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Per-worker actor storage. Owns per-actor mailboxes.
|
2026-02-06 11:25:37 +00:00
|
|
|
pub(crate) struct ActorPool {
|
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
|
|
|
actors: AddrMap<ActorSlot>,
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ActorPool {
|
2026-03-28 05:08:58 +00:00
|
|
|
pub fn new() -> Self {
|
2026-02-06 11:25:37 +00:00
|
|
|
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
|
|
|
actors: HashMap::with_hasher(AddrBuildHasher),
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
pub fn insert(&mut self, req: SpawnRequest) {
|
2026-06-23 15:42:28 +00:00
|
|
|
self.actors.insert(
|
|
|
|
|
req.addr,
|
|
|
|
|
ActorSlot {
|
|
|
|
|
mailbox: VecDeque::with_capacity(16),
|
|
|
|
|
actor: req.actor,
|
|
|
|
|
poisoned: false,
|
|
|
|
|
stopping: false,
|
|
|
|
|
started: false,
|
|
|
|
|
suspended: false,
|
|
|
|
|
last_msg_type: None,
|
|
|
|
|
messages_processed: 0,
|
|
|
|
|
msg_type_counts: HashMap::new(),
|
|
|
|
|
parent_addr: req.parent,
|
|
|
|
|
env: req.env,
|
|
|
|
|
exit_value: None,
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Deliver a type-erased message to the actor at `addr`.
|
2026-03-28 05:08:58 +00:00
|
|
|
/// Returns `true` if the actor exists (message enqueued; type check deferred to tick).
|
2026-02-06 11:25:37 +00:00
|
|
|
pub fn deliver(&mut self, addr: &ActorAddress, msg: Box<dyn Any + Send>) -> bool {
|
2026-02-06 14:45:19 +00:00
|
|
|
if let Some(slot) = self.actors.get_mut(addr) {
|
2026-02-20 17:34:43 +00:00
|
|
|
// Intercept control signals for suspended actors: they skip tick_all
|
|
|
|
|
// so we must handle resume/stop at delivery time.
|
|
|
|
|
if slot.suspended {
|
|
|
|
|
if msg.is::<ResumeSignal>() {
|
|
|
|
|
slot.suspended = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if msg.is::<StopSignal>() {
|
|
|
|
|
slot.stopping = true;
|
|
|
|
|
slot.mailbox.clear();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if msg.is::<StopWithSignal>() {
|
|
|
|
|
if let Ok(sig) = msg.downcast::<StopWithSignal>() {
|
|
|
|
|
slot.exit_value = Some(sig.0);
|
|
|
|
|
}
|
|
|
|
|
slot.stopping = true;
|
|
|
|
|
slot.mailbox.clear();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-06 14:45:19 +00:00
|
|
|
slot.mailbox.push_back(msg);
|
|
|
|
|
true
|
2026-02-06 11:25:37 +00:00
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
fn get_actor_erased(&self, addr: ActorAddress) -> Option<&dyn AnyActor> {
|
|
|
|
|
self.actors.get(&addr).map(|slot| slot.actor.as_ref())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_actor_erased_mut(
|
|
|
|
|
&mut self,
|
|
|
|
|
addr: ActorAddress,
|
|
|
|
|
) -> Option<&mut (dyn AnyActor + 'static)> {
|
|
|
|
|
self.actors.get_mut(&addr).map(|slot| slot.actor.as_mut())
|
|
|
|
|
}
|
|
|
|
|
|
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
|
|
|
fn actor_summary_from_slot(
|
|
|
|
|
address: ActorAddress,
|
|
|
|
|
slot: &ActorSlot,
|
|
|
|
|
worker: WorkerId,
|
|
|
|
|
) -> ActorSummary {
|
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 metadata = slot.actor.metadata();
|
|
|
|
|
ActorSummary {
|
|
|
|
|
address,
|
|
|
|
|
actor_type: metadata.actor_type_name,
|
|
|
|
|
message_type: metadata.message_type_name,
|
2026-08-11 12:08:06 +00:00
|
|
|
worker_id: worker.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
|
|
|
parent: slot.parent_addr,
|
|
|
|
|
mailbox_depth: slot.mailbox.len(),
|
|
|
|
|
status: ActorStatus {
|
|
|
|
|
started: slot.started,
|
|
|
|
|
suspended: slot.suspended,
|
|
|
|
|
stopping: slot.stopping,
|
|
|
|
|
poisoned: slot.poisoned,
|
|
|
|
|
},
|
|
|
|
|
last_message_type: slot.last_msg_type,
|
|
|
|
|
messages_handled: slot.messages_processed,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
fn actor_summary(&self, addr: ActorAddress, worker: WorkerId) -> AdminResult<ActorSummary> {
|
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
|
|
|
self.actors
|
|
|
|
|
.get(&addr)
|
2026-08-11 12:08:06 +00:00
|
|
|
.map(|slot| Self::actor_summary_from_slot(addr, slot, worker))
|
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_or(AdminError::ActorNotFound { actor: addr })
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 12:08:06 +00:00
|
|
|
fn actor_summaries_into(&self, out: &mut Vec<ActorSummary>, worker: WorkerId) {
|
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
|
|
|
out.clear();
|
|
|
|
|
out.extend(
|
|
|
|
|
self.actors
|
|
|
|
|
.iter()
|
2026-08-11 12:08:06 +00:00
|
|
|
.map(|(&addr, slot)| Self::actor_summary_from_slot(addr, slot, worker)),
|
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
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn suspend_actor_admin(&mut self, addr: ActorAddress) -> AdminResult<OperationResult> {
|
|
|
|
|
match self.actors.get_mut(&addr) {
|
|
|
|
|
Some(slot) => {
|
|
|
|
|
slot.suspended = true;
|
|
|
|
|
Ok(OperationResult { applied: true })
|
|
|
|
|
}
|
|
|
|
|
None => Err(AdminError::ActorNotFound { actor: addr }),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn resume_actor_admin(&mut self, addr: ActorAddress) -> AdminResult<OperationResult> {
|
|
|
|
|
match self.actors.get_mut(&addr) {
|
|
|
|
|
Some(slot) => {
|
|
|
|
|
slot.suspended = false;
|
|
|
|
|
Ok(OperationResult { applied: true })
|
|
|
|
|
}
|
|
|
|
|
None => Err(AdminError::ActorNotFound { actor: addr }),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn stop_actor_admin(
|
|
|
|
|
&mut self,
|
|
|
|
|
addr: ActorAddress,
|
|
|
|
|
stats: &WorkerStats,
|
|
|
|
|
) -> AdminResult<OperationResult> {
|
|
|
|
|
match self.actors.get_mut(&addr) {
|
|
|
|
|
Some(slot) => {
|
|
|
|
|
if !slot.stopping {
|
|
|
|
|
stats.stops.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
}
|
|
|
|
|
slot.stopping = true;
|
|
|
|
|
slot.mailbox.clear();
|
|
|
|
|
Ok(OperationResult { applied: true })
|
|
|
|
|
}
|
|
|
|
|
None => Err(AdminError::ActorNotFound { actor: addr }),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
/// Tick all actors in the pool. Returns the number of messages processed.
|
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
|
|
|
///
|
|
|
|
|
/// Each actor processes up to `budget` messages per tick (0 = unlimited).
|
|
|
|
|
/// This prevents a single hot actor from starving others on the same worker.
|
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
|
|
|
fn tick_all(&mut self, wctx: &WorkerContext<'_>, stats: &WorkerStats, budget: usize) -> usize {
|
2026-02-06 14:45:19 +00:00
|
|
|
let mut count = 0;
|
|
|
|
|
for (&addr, slot) in self.actors.iter_mut() {
|
2026-03-28 05:08:58 +00:00
|
|
|
if should_skip_actor(slot.poisoned, slot.stopping, slot.suspended) {
|
|
|
|
|
// Discard all messages for poisoned/stopping actors (not suspended — those queue)
|
|
|
|
|
if slot.poisoned || slot.stopping {
|
|
|
|
|
slot.mailbox.clear();
|
|
|
|
|
}
|
2026-02-10 07:35:58 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
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-03-28 05:08:58 +00:00
|
|
|
debug_assert!(
|
|
|
|
|
!slot.poisoned && !slot.stopping && !slot.suspended,
|
|
|
|
|
"G4: non-processable actor reached processing"
|
|
|
|
|
);
|
2026-02-20 17:34:43 +00:00
|
|
|
|
2026-02-13 14:42:10 +00:00
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
let _actor_span = tracing::trace_span!("actor.tick", actor_addr = %addr).entered();
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
// Snapshot self-stats before creating Ctx
|
|
|
|
|
let snap_processed = slot.messages_processed;
|
|
|
|
|
let snap_depth = slot.mailbox.len();
|
|
|
|
|
let mut snap_type_counts: Vec<(&'static str, u64)> =
|
|
|
|
|
slot.msg_type_counts.iter().map(|(&k, &v)| (k, v)).collect();
|
|
|
|
|
snap_type_counts.sort_by(|a, b| b.1.cmp(&a.1));
|
|
|
|
|
|
2026-06-23 15:42:28 +00:00
|
|
|
let ctx = Ctx::new(
|
2026-08-09 09:50:33 +00:00
|
|
|
wctx,
|
2026-06-23 15:42:28 +00:00
|
|
|
addr,
|
|
|
|
|
slot.parent_addr,
|
|
|
|
|
slot.env.clone(),
|
|
|
|
|
snap_processed,
|
|
|
|
|
snap_depth,
|
|
|
|
|
snap_type_counts,
|
|
|
|
|
);
|
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
|
|
|
|
|
|
|
|
// Call on_start once, before first message
|
|
|
|
|
if !slot.started {
|
|
|
|
|
let start_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
|
|
|
slot.actor.on_start(&ctx);
|
|
|
|
|
}));
|
|
|
|
|
slot.started = true;
|
|
|
|
|
if start_result.is_err() {
|
|
|
|
|
stats.panics.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
eprintln!("swactor: actor {addr} panicked in on_start — poisoned");
|
|
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
tracing::error!(actor_addr = %addr, "actor.on_start_panicked");
|
|
|
|
|
slot.poisoned = true;
|
|
|
|
|
slot.mailbox.clear();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-02-20 17:34:43 +00:00
|
|
|
// Check if on_start requested stop or stop_with
|
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-09 09:50:33 +00:00
|
|
|
let stops = wctx.stop_requests.borrow();
|
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
|
|
|
if !stops.is_empty() && stops.contains(&addr) {
|
|
|
|
|
drop(stops);
|
|
|
|
|
slot.stopping = true;
|
|
|
|
|
stats.stops.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
slot.mailbox.clear();
|
2026-02-20 17:34:43 +00:00
|
|
|
// Check for stop_with value
|
2026-08-09 09:50:33 +00:00
|
|
|
let mut sws = wctx.stop_with_values.borrow_mut();
|
2026-02-20 17:34:43 +00:00
|
|
|
if let Some(pos) = sws.iter().position(|(a, _)| *a == addr) {
|
|
|
|
|
let (_, val) = sws.swap_remove(pos);
|
|
|
|
|
slot.exit_value = Some(val);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Check if on_start requested stop_with (without plain stop)
|
|
|
|
|
{
|
2026-08-09 09:50:33 +00:00
|
|
|
let mut sws = wctx.stop_with_values.borrow_mut();
|
2026-02-20 17:34:43 +00:00
|
|
|
if let Some(pos) = sws.iter().position(|(a, _)| *a == addr) {
|
|
|
|
|
let (_, val) = sws.swap_remove(pos);
|
|
|
|
|
slot.exit_value = Some(val);
|
|
|
|
|
slot.stopping = true;
|
|
|
|
|
stats.stops.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
slot.mailbox.clear();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Check if on_start requested suspend
|
|
|
|
|
{
|
2026-08-09 09:50:33 +00:00
|
|
|
let suspends = wctx.suspend_requests.borrow();
|
2026-02-20 17:34:43 +00:00
|
|
|
if !suspends.is_empty() && suspends.contains(&addr) {
|
|
|
|
|
drop(suspends);
|
|
|
|
|
slot.suspended = true;
|
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
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut actor_count = 0usize;
|
2026-02-09 07:24:16 +00:00
|
|
|
while let Some(msg) = slot.mailbox.pop_front() {
|
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
|
|
|
// Intercept StopSignal (from external runtime.stop_actor)
|
|
|
|
|
if msg.is::<StopSignal>() {
|
|
|
|
|
slot.stopping = true;
|
|
|
|
|
stats.stops.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
slot.mailbox.clear();
|
|
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
tracing::info!(actor_addr = %addr, "actor.stop_requested");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
// Intercept StopWithSignal (from external runtime)
|
|
|
|
|
if msg.is::<StopWithSignal>() {
|
|
|
|
|
if let Ok(sig) = msg.downcast::<StopWithSignal>() {
|
|
|
|
|
slot.exit_value = Some(sig.0);
|
|
|
|
|
}
|
|
|
|
|
slot.stopping = true;
|
|
|
|
|
stats.stops.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
slot.mailbox.clear();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 07:24:16 +00:00
|
|
|
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
2026-02-10 07:35:58 +00:00
|
|
|
slot.actor.handle_any(&ctx, msg)
|
2026-02-09 07:24:16 +00:00
|
|
|
}));
|
2026-02-10 07:35:58 +00:00
|
|
|
match result {
|
2026-02-11 15:23:26 +00:00
|
|
|
Ok(None) => {
|
2026-02-10 07:35:58 +00:00
|
|
|
stats.type_mismatches.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
}
|
|
|
|
|
Err(_) => {
|
|
|
|
|
stats.panics.fetch_add(1, Ordering::Relaxed);
|
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
|
|
|
slot.mailbox.clear();
|
2026-06-23 15:42:28 +00:00
|
|
|
eprintln!(
|
|
|
|
|
"swactor: actor {addr} panicked — poisoned, future messages will be discarded"
|
|
|
|
|
);
|
2026-02-10 07:35:58 +00:00
|
|
|
#[cfg(feature = "tracing")]
|
|
|
|
|
tracing::error!(actor_addr = %addr, "actor.panicked");
|
|
|
|
|
slot.poisoned = true;
|
2026-02-13 07:42:44 +00:00
|
|
|
slot.mailbox.clear();
|
2026-02-10 07:35:58 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2026-02-11 15:23:26 +00:00
|
|
|
Ok(Some(type_name)) => {
|
|
|
|
|
slot.last_msg_type = Some(type_name);
|
|
|
|
|
slot.messages_processed += 1;
|
2026-02-13 14:42:10 +00:00
|
|
|
// Track per-type counts (bounded to 32 distinct types)
|
2026-06-23 15:42:28 +00:00
|
|
|
if slot.msg_type_counts.len() < 32
|
|
|
|
|
|| slot.msg_type_counts.contains_key(type_name)
|
|
|
|
|
{
|
2026-02-13 14:42:10 +00:00
|
|
|
*slot.msg_type_counts.entry(type_name).or_insert(0) += 1;
|
|
|
|
|
}
|
2026-02-11 15:23:26 +00:00
|
|
|
}
|
2026-02-06 14:45:19 +00:00
|
|
|
}
|
2026-02-09 07:24:16 +00:00
|
|
|
count += 1;
|
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
|
|
|
actor_count += 1;
|
|
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
// Check if handler requested self-stop or stop_with
|
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-09 09:50:33 +00:00
|
|
|
let stops = wctx.stop_requests.borrow();
|
2026-02-20 17:34:43 +00:00
|
|
|
let has_stop = !stops.is_empty() && stops.contains(&addr);
|
|
|
|
|
drop(stops);
|
|
|
|
|
|
2026-08-09 09:50:33 +00:00
|
|
|
let mut sws = wctx.stop_with_values.borrow_mut();
|
2026-02-20 17:34:43 +00:00
|
|
|
let sw_pos = sws.iter().position(|(a, _)| *a == addr);
|
|
|
|
|
|
|
|
|
|
if has_stop || sw_pos.is_some() {
|
|
|
|
|
if let Some(pos) = sw_pos {
|
|
|
|
|
let (_, val) = sws.swap_remove(pos);
|
|
|
|
|
slot.exit_value = Some(val);
|
|
|
|
|
}
|
|
|
|
|
drop(sws);
|
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
|
|
|
slot.stopping = true;
|
|
|
|
|
stats.stops.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
slot.mailbox.clear();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-02-20 17:34:43 +00:00
|
|
|
drop(sws);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if handler requested suspend
|
|
|
|
|
{
|
2026-08-09 09:50:33 +00:00
|
|
|
let suspends = wctx.suspend_requests.borrow();
|
2026-02-20 17:34:43 +00:00
|
|
|
if !suspends.is_empty() && suspends.contains(&addr) {
|
|
|
|
|
drop(suspends);
|
|
|
|
|
slot.suspended = true;
|
|
|
|
|
break; // stop processing this actor's messages this tick
|
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if budget > 0 && actor_count >= budget {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-02-06 11:25:37 +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
|
|
|
count
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
|
self.actors.len()
|
|
|
|
|
}
|
2026-02-06 14:45:19 +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
|
|
|
pub fn contains(&self, addr: &ActorAddress) -> bool {
|
|
|
|
|
self.actors.contains_key(addr)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 14:45:19 +00:00
|
|
|
pub fn total_mailbox_depth(&self) -> usize {
|
|
|
|
|
self.actors.values().map(|slot| slot.mailbox.len()).sum()
|
|
|
|
|
}
|
2026-02-06 11:25:37 +00:00
|
|
|
|
2026-02-20 17:34:43 +00:00
|
|
|
/// Remove poisoned and stopping actors, returning their addresses, stop reasons,
|
|
|
|
|
/// and optional exit values.
|
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 after tick_all so the caller can clean up the address map.
|
|
|
|
|
///
|
|
|
|
|
/// For stopping actors: calls `on_stop()` before removal (wrapped in catch_unwind).
|
|
|
|
|
/// For poisoned actors: `on_stop()` is NOT called (state may be corrupt).
|
2026-06-23 15:42:28 +00:00
|
|
|
pub fn cleanup_dead(
|
|
|
|
|
&mut self,
|
|
|
|
|
inner: &dyn ContextInner,
|
|
|
|
|
) -> Vec<(ActorAddress, StopReason, Option<ExitValue>)> {
|
2026-02-20 17:34:43 +00:00
|
|
|
let dead_addrs: Vec<ActorAddress> = 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
|
|
|
.actors
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|(_, slot)| slot.poisoned || slot.stopping)
|
2026-02-20 17:34:43 +00:00
|
|
|
.map(|(&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
|
|
|
.collect();
|
2026-02-20 17:34:43 +00:00
|
|
|
let mut dead = Vec::with_capacity(dead_addrs.len());
|
|
|
|
|
for addr in dead_addrs {
|
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
|
|
|
if let Some(mut slot) = self.actors.remove(&addr) {
|
2026-03-28 05:08:58 +00:00
|
|
|
let reason = determine_stop_reason(slot.poisoned, slot.exit_value.is_some());
|
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
|
|
|
// Call on_stop for gracefully stopping actors only
|
2026-03-28 05:08:58 +00:00
|
|
|
debug_assert!(
|
|
|
|
|
slot.poisoned || slot.stopping,
|
|
|
|
|
"G4: non-dead actor reached cleanup_dead"
|
|
|
|
|
);
|
|
|
|
|
if is_on_stop_eligible(slot.stopping, slot.poisoned) {
|
2026-02-20 17:34:43 +00:00
|
|
|
let mut type_counts: Vec<(&'static str, u64)> =
|
|
|
|
|
slot.msg_type_counts.iter().map(|(&k, &v)| (k, v)).collect();
|
|
|
|
|
type_counts.sort_by(|a, b| b.1.cmp(&a.1));
|
|
|
|
|
let ctx = Ctx::new(
|
2026-06-23 15:42:28 +00:00
|
|
|
inner,
|
|
|
|
|
addr,
|
|
|
|
|
slot.parent_addr,
|
|
|
|
|
slot.env.clone(),
|
2026-02-20 17:34:43 +00:00
|
|
|
slot.messages_processed,
|
|
|
|
|
slot.mailbox.len(),
|
|
|
|
|
type_counts,
|
|
|
|
|
);
|
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 _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
|
|
|
slot.actor.on_stop(&ctx);
|
|
|
|
|
}));
|
|
|
|
|
}
|
2026-02-20 17:34:43 +00:00
|
|
|
dead.push((addr, reason, slot.exit_value.take()));
|
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
|
|
|
// slot is dropped here — actor resources freed
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dead
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 15:23:26 +00:00
|
|
|
/// Fill `out` with per-actor snapshots, reusing the existing allocation.
|
|
|
|
|
pub fn mailbox_depths_into(&self, out: &mut Vec<ActorSnapshot>) {
|
2026-02-10 07:35:58 +00:00
|
|
|
out.clear();
|
2026-02-11 15:23:26 +00:00
|
|
|
out.extend(self.actors.iter().map(|(&addr, slot)| {
|
2026-02-13 14:42:10 +00:00
|
|
|
let mut type_counts: Vec<(&'static str, u64)> =
|
|
|
|
|
slot.msg_type_counts.iter().map(|(&k, &v)| (k, v)).collect();
|
|
|
|
|
type_counts.sort_by(|a, b| b.1.cmp(&a.1));
|
2026-08-03 10:18:24 +00:00
|
|
|
let metadata = slot.actor.metadata();
|
2026-02-11 15:23:26 +00:00
|
|
|
ActorSnapshot {
|
|
|
|
|
address: addr,
|
|
|
|
|
mailbox_depth: slot.mailbox.len(),
|
|
|
|
|
last_msg_type: slot.last_msg_type,
|
2026-08-03 10:18:24 +00:00
|
|
|
actor_type: Some(metadata.actor_type_name),
|
|
|
|
|
message_type: Some(metadata.message_type_name),
|
2026-02-11 15:23:26 +00:00
|
|
|
messages_processed: slot.messages_processed,
|
|
|
|
|
poisoned: slot.poisoned,
|
2026-02-13 14:42:10 +00:00
|
|
|
message_type_counts: type_counts,
|
2026-02-11 15:23:26 +00:00
|
|
|
}
|
|
|
|
|
}));
|
2026-02-09 09:04:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-24 15:23:09 +00:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use crate::actor::StopReason;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn lifecycle_decision_helpers_cover_all_inputs() {
|
|
|
|
|
let mut skip_combinations = 0;
|
|
|
|
|
for poisoned in [false, true] {
|
|
|
|
|
for stopping in [false, true] {
|
|
|
|
|
for suspended in [false, true] {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
should_skip_actor(poisoned, stopping, suspended),
|
|
|
|
|
poisoned || stopping || suspended
|
|
|
|
|
);
|
|
|
|
|
skip_combinations += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(skip_combinations, 8);
|
|
|
|
|
|
|
|
|
|
let mut on_stop_combinations = 0;
|
|
|
|
|
for stopping in [false, true] {
|
|
|
|
|
for poisoned in [false, true] {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
is_on_stop_eligible(stopping, poisoned),
|
|
|
|
|
stopping && !poisoned
|
|
|
|
|
);
|
|
|
|
|
on_stop_combinations += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(on_stop_combinations, 4);
|
|
|
|
|
|
|
|
|
|
let mut reason_combinations = 0;
|
|
|
|
|
for poisoned in [false, true] {
|
|
|
|
|
for has_exit_value in [false, true] {
|
|
|
|
|
let expected = if poisoned {
|
|
|
|
|
StopReason::Panicked
|
|
|
|
|
} else if has_exit_value {
|
|
|
|
|
StopReason::Completed
|
|
|
|
|
} else {
|
|
|
|
|
StopReason::Normal
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(determine_stop_reason(poisoned, has_exit_value), expected);
|
|
|
|
|
reason_combinations += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(reason_combinations, 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn bounded_lifecycle_model_preserves_callback_invariants() {
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
|
enum Step {
|
|
|
|
|
Tick,
|
|
|
|
|
RequestStop,
|
|
|
|
|
Poison,
|
|
|
|
|
Suspend,
|
|
|
|
|
Resume,
|
|
|
|
|
Cleanup,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Default)]
|
|
|
|
|
struct ActorProbe {
|
|
|
|
|
started: bool,
|
|
|
|
|
stopping: bool,
|
|
|
|
|
poisoned: bool,
|
|
|
|
|
suspended: bool,
|
|
|
|
|
removed: bool,
|
|
|
|
|
on_start_count: u8,
|
|
|
|
|
handle_count: u8,
|
|
|
|
|
on_stop_count: u8,
|
|
|
|
|
handled_after_on_stop: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ActorProbe {
|
|
|
|
|
fn apply(&mut self, step: Step) {
|
|
|
|
|
if self.removed {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match step {
|
|
|
|
|
Step::Tick => {
|
|
|
|
|
let eligible =
|
|
|
|
|
!should_skip_actor(self.poisoned, self.stopping, self.suspended);
|
|
|
|
|
if !self.started && eligible {
|
|
|
|
|
self.started = true;
|
|
|
|
|
self.on_start_count += 1;
|
|
|
|
|
}
|
|
|
|
|
if self.started && eligible {
|
|
|
|
|
if self.on_stop_count > 0 {
|
|
|
|
|
self.handled_after_on_stop = true;
|
|
|
|
|
}
|
|
|
|
|
self.handle_count += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Step::RequestStop => {
|
|
|
|
|
self.stopping = true;
|
|
|
|
|
}
|
|
|
|
|
Step::Poison => {
|
|
|
|
|
self.poisoned = true;
|
|
|
|
|
}
|
|
|
|
|
Step::Suspend => {
|
|
|
|
|
self.suspended = true;
|
|
|
|
|
}
|
|
|
|
|
Step::Resume => {
|
|
|
|
|
self.suspended = false;
|
|
|
|
|
}
|
|
|
|
|
Step::Cleanup => {
|
|
|
|
|
if self.stopping || self.poisoned {
|
|
|
|
|
if is_on_stop_eligible(self.stopping, self.poisoned) {
|
|
|
|
|
self.on_stop_count += 1;
|
|
|
|
|
}
|
|
|
|
|
self.removed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn assert_invariants(self) {
|
|
|
|
|
assert!(self.on_start_count <= 1, "on_start fired more than once");
|
|
|
|
|
assert!(self.on_stop_count <= 1, "on_stop fired more than once");
|
|
|
|
|
assert!(
|
|
|
|
|
!self.handled_after_on_stop,
|
|
|
|
|
"handle fired after on_stop cleanup"
|
|
|
|
|
);
|
|
|
|
|
if self.handle_count > 0 {
|
|
|
|
|
assert!(self.started, "handle fired before on_start");
|
|
|
|
|
}
|
|
|
|
|
if self.on_stop_count > 0 {
|
|
|
|
|
assert!(self.removed, "on_stop fired without cleanup");
|
|
|
|
|
assert!(!self.poisoned, "poisoned actor ran on_stop");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn walk(depth: usize, state: ActorProbe, checked: &mut usize) {
|
|
|
|
|
state.assert_invariants();
|
|
|
|
|
*checked += 1;
|
|
|
|
|
|
|
|
|
|
if depth == 0 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for step in [
|
|
|
|
|
Step::Tick,
|
|
|
|
|
Step::RequestStop,
|
|
|
|
|
Step::Poison,
|
|
|
|
|
Step::Suspend,
|
|
|
|
|
Step::Resume,
|
|
|
|
|
Step::Cleanup,
|
|
|
|
|
] {
|
|
|
|
|
let mut next = state;
|
|
|
|
|
next.apply(step);
|
|
|
|
|
walk(depth - 1, next, checked);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut checked = 0;
|
|
|
|
|
walk(6, ActorProbe::default(), &mut checked);
|
|
|
|
|
assert_eq!(checked, 55_987);
|
|
|
|
|
}
|
|
|
|
|
}
|