swactor/src/admin.rs

164 lines
4 KiB
Rust
Raw Normal View History

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::actor::{ActorAddress, ActorInterface, ActorTypeMetadata, AnyActor, Message};
use crate::runtime::{Inbox, Runtime, SingleThreadRuntime};
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 parking_lot::Mutex;
use std::any::Any;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
pub type AdminResult<T> = Result<T, AdminError>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AdminError {
ActorNotFound {
actor: ActorAddress,
},
AddressMismatch {
requested: ActorAddress,
snapshot: ActorAddress,
},
TypeMismatch {
expected_actor_type: &'static str,
expected_message_type: &'static str,
actual_actor_type: &'static str,
actual_message_type: &'static str,
},
Timeout,
}
pub struct RuntimeAdmin<'a> {
pub(crate) runtime: &'a Runtime,
}
pub struct Admin<T: Message> {
pub(crate) inbox: Inbox<AdminResult<T>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OperationResult {
pub applied: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActorStatus {
pub started: bool,
pub suspended: bool,
pub stopping: bool,
pub poisoned: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActorSummary {
pub address: ActorAddress,
pub actor_type: &'static str,
pub message_type: &'static str,
pub worker_id: usize,
pub parent: Option<ActorAddress>,
pub mailbox_depth: usize,
pub status: ActorStatus,
pub last_message_type: Option<&'static str>,
pub messages_handled: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListActorsResponse {
pub actors: Vec<ActorSummary>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InspectActorResponse {
pub summary: ActorSummary,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActorStateSnapshot<A> {
pub actor: ActorAddress,
pub actor_type: &'static str,
pub message_type: &'static str,
pub actor_instance: A,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetActorStateResponse<A> {
pub state: ActorStateSnapshot<A>,
}
impl<A: ActorInterface> ActorStateSnapshot<A> {
pub fn new(actor: ActorAddress, actor_instance: A) -> Self {
Self {
actor,
actor_type: std::any::type_name::<A>(),
message_type: std::any::type_name::<A::Incoming>(),
actor_instance,
}
}
}
impl<T: Message> Admin<T> {
pub(crate) fn new(inbox: Inbox<AdminResult<T>>) -> Self {
Self { inbox }
}
pub fn try_recv(&self) -> Option<AdminResult<T>> {
self.inbox.try_recv()
}
pub fn recv_ticking(&self, host: &mut SingleThreadRuntime, max_ticks: usize) -> AdminResult<T> {
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
for _ in 0..max_ticks {
host.try_tick();
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
if let Some(resp) = self.inbox.try_recv() {
return resp;
}
}
Err(AdminError::Timeout)
}
pub fn reply_addr(&self) -> &ActorAddress {
self.inbox.addr()
}
}
pub(crate) type AdminBoxedReply = Box<dyn Any + Send>;
pub(crate) enum AdminCommand {
ListActors {
acc: Arc<ListActorsAccumulator>,
},
InspectActor {
actor: ActorAddress,
reply_to: ActorAddress,
},
GetActorState {
actor: ActorAddress,
reply_to: ActorAddress,
get: Box<
dyn FnOnce(ActorAddress, &dyn AnyActor, ActorTypeMetadata) -> AdminBoxedReply + Send,
>,
not_found: Box<dyn FnOnce(ActorAddress) -> AdminBoxedReply + Send>,
},
ReplaceActorState {
actor: ActorAddress,
reply_to: ActorAddress,
replace: Box<
dyn FnOnce(&mut dyn AnyActor, ActorTypeMetadata) -> AdminResult<OperationResult> + Send,
>,
},
StopActor {
actor: ActorAddress,
reply_to: ActorAddress,
},
SuspendActor {
actor: ActorAddress,
reply_to: ActorAddress,
},
ResumeActor {
actor: ActorAddress,
reply_to: ActorAddress,
},
}
pub(crate) struct ListActorsAccumulator {
pub(crate) remaining: AtomicUsize,
pub(crate) summaries: Mutex<Vec<ActorSummary>>,
pub(crate) reply_to: ActorAddress,
}