use std::any::TypeId;
use std::collections::HashMap;
use std::fmt::Debug;
use std::future::Future;
use std::marker::PhantomData;
use std::mem;
use std::sync::atomic::AtomicBool;
use acton_ern::Ern;
use tokio::sync::mpsc::channel;
use tokio_util::task::TaskTracker;
use tracing::{error, instrument, trace};
use crate::actor::supervision::SupervisionRegistry;
use crate::actor::{ActorConfig, Escalation, ManagedActor, RestartPolicy, Started, SupervisionStrategy};
use crate::common::{
ActorHandle, ActorRuntime, Envelope, FutureBox, OutboundEnvelope, ReactorItem,
};
use crate::message::MessageContext;
use crate::prelude::ActonMessage;
use crate::traits::{ActonMessageReply, ActorHandleInterface};
#[cfg(feature = "ipc")]
fn ipc_name_for(ern: &Ern) -> String {
let prefix = ern.root().name().prefix();
if ern.parts().is_empty() {
prefix.to_string()
} else {
format!("{prefix}/{}", ern.parts())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Idle;
impl<State: Default + Send + Debug + 'static> ManagedActor<Idle, State> {
#[instrument(skip(self, message_processor), level = "debug")]
pub fn mutate_on<M>(
&mut self,
message_processor: impl for<'a> Fn(&'a mut ManagedActor<Started, State>, &'a mut MessageContext<M>) -> FutureBox
+ Send
+ Sync
+ 'static,
) -> &mut Self
where
M: ActonMessage + Clone + Send + Sync + 'static,
{
let type_id = TypeId::of::<M>();
trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding mutable message handler");
let handler_box = Box::new(
move |actor: &mut ManagedActor<Started, State>, envelope: &mut Envelope| -> FutureBox {
if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
trace!(
"Downcast successful for message type: {}",
std::any::type_name::<M>()
);
let mut msg_context = {
let origin_envelope = OutboundEnvelope::new_with_recipient(
envelope.reply_to.clone(),
envelope.recipient.clone(),
actor.handle.cancellation_token.clone(),
);
let reply_envelope = OutboundEnvelope::new_with_recipient(
envelope.recipient.clone(),
envelope.reply_to.clone(),
actor.handle.cancellation_token.clone(),
);
MessageContext {
message: concrete_msg.clone(),
origin_envelope,
reply_envelope,
}
};
message_processor(actor, &mut msg_context)
} else {
error!(
type_name = std::any::type_name::<M>(),
"Message handler called with incompatible message type (downcast failed)"
);
Box::pin(async {})
}
},
);
self.message_handlers
.insert(type_id, ReactorItem::Mutable(handler_box));
self
}
#[instrument(skip(self, message_processor), level = "debug")]
pub fn mutate_on_sync<M>(
&mut self,
message_processor: impl for<'a> Fn(&'a mut ManagedActor<Started, State>, &'a mut MessageContext<M>)
+ Send
+ Sync
+ 'static,
) -> &mut Self
where
M: ActonMessage + Clone + Send + Sync + 'static,
{
let type_id = TypeId::of::<M>();
trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding sync mutable message handler");
let handler_box = Box::new(
move |actor: &mut ManagedActor<Started, State>, envelope: &mut Envelope| {
if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
trace!(
"Downcast successful for message type: {}",
std::any::type_name::<M>()
);
let mut msg_context = {
let origin_envelope = OutboundEnvelope::new_with_recipient(
envelope.reply_to.clone(),
envelope.recipient.clone(),
actor.handle.cancellation_token.clone(),
);
let reply_envelope = OutboundEnvelope::new_with_recipient(
envelope.recipient.clone(),
envelope.reply_to.clone(),
actor.handle.cancellation_token.clone(),
);
MessageContext {
message: concrete_msg.clone(),
origin_envelope,
reply_envelope,
}
};
message_processor(actor, &mut msg_context);
} else {
error!(
type_name = std::any::type_name::<M>(),
"Sync message handler called with incompatible message type (downcast failed)"
);
}
},
);
self.message_handlers
.insert(type_id, ReactorItem::MutableSync(handler_box));
self
}
pub fn on_error<M, E>(
&mut self,
error_handler: impl for<'a, 'b> Fn(
&'a mut ManagedActor<Started, State>,
&'b mut MessageContext<M>,
&'b E,
) -> FutureBox
+ Send
+ Sync
+ 'static,
) -> &mut Self
where
M: ActonMessage + Clone + Send + Sync + 'static,
E: std::error::Error + 'static,
{
use std::any::TypeId;
let message_type_id = TypeId::of::<M>();
let error_type_id = TypeId::of::<E>();
let handler_box: Box<crate::common::ErrorHandler<State>> =
Box::new(move |actor, envelope, err| {
if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
if let Some(specific_err) = err.downcast_ref::<E>() {
let mut msg_context = {
let origin_envelope = OutboundEnvelope::new_with_recipient(
envelope.reply_to.clone(),
envelope.recipient.clone(),
actor.handle.cancellation_token.clone(),
);
let reply_envelope = OutboundEnvelope::new_with_recipient(
envelope.recipient.clone(),
envelope.reply_to.clone(),
actor.handle.cancellation_token.clone(),
);
MessageContext {
message: concrete_msg.clone(),
origin_envelope,
reply_envelope,
}
};
error_handler(actor, &mut msg_context, specific_err)
} else {
Box::pin(async {})
}
} else {
Box::pin(async {})
}
});
self.error_handler_map
.insert((message_type_id, error_type_id), handler_box);
self
}
#[instrument(skip(self, message_processor), level = "debug")]
pub fn act_on<M>(
&mut self,
message_processor: impl for<'a> Fn(&'a ManagedActor<Started, State>, &'a mut MessageContext<M>) -> FutureBox
+ Send
+ Sync
+ 'static,
) -> &mut Self
where
M: ActonMessage + Clone + Send + Sync + 'static,
{
let type_id = TypeId::of::<M>();
trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding read-only message handler");
let handler_box = Box::new(
move |actor: &ManagedActor<Started, State>, envelope: &mut Envelope| -> FutureBox {
if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
trace!(
"Downcast successful for message type: {}",
std::any::type_name::<M>()
);
let mut msg_context = {
let origin_envelope = OutboundEnvelope::new_with_recipient(
envelope.reply_to.clone(),
envelope.recipient.clone(),
actor.handle.cancellation_token.clone(),
);
let reply_envelope = OutboundEnvelope::new_with_recipient(
envelope.recipient.clone(),
envelope.reply_to.clone(),
actor.handle.cancellation_token.clone(),
);
MessageContext {
message: concrete_msg.clone(),
origin_envelope,
reply_envelope,
}
};
message_processor(actor, &mut msg_context)
} else {
error!(
type_name = std::any::type_name::<M>(),
"Read-only message handler called with incompatible message type (downcast failed)"
);
Box::pin(async {})
}
},
);
self.read_only_handlers
.insert(type_id, ReactorItem::ReadOnly(handler_box));
self
}
#[instrument(skip(self, message_processor), level = "debug")]
pub fn act_on_sync<M>(
&mut self,
message_processor: impl for<'a> Fn(&'a ManagedActor<Started, State>, &'a mut MessageContext<M>)
+ Send
+ Sync
+ 'static,
) -> &mut Self
where
M: ActonMessage + Clone + Send + Sync + 'static,
{
let type_id = TypeId::of::<M>();
trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding sync read-only message handler");
let handler_box = Box::new(
move |actor: &ManagedActor<Started, State>, envelope: &mut Envelope| {
if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
trace!(
"Downcast successful for message type: {}",
std::any::type_name::<M>()
);
let mut msg_context = {
let origin_envelope = OutboundEnvelope::new_with_recipient(
envelope.reply_to.clone(),
envelope.recipient.clone(),
actor.handle.cancellation_token.clone(),
);
let reply_envelope = OutboundEnvelope::new_with_recipient(
envelope.recipient.clone(),
envelope.reply_to.clone(),
actor.handle.cancellation_token.clone(),
);
MessageContext {
message: concrete_msg.clone(),
origin_envelope,
reply_envelope,
}
};
message_processor(actor, &mut msg_context);
} else {
error!(
type_name = std::any::type_name::<M>(),
"Sync read-only message handler called with incompatible message type (downcast failed)"
);
}
},
);
self.read_only_handlers
.insert(type_id, ReactorItem::ReadOnlySync(handler_box));
self
}
#[instrument(skip(self, message_processor), level = "debug")]
pub fn try_act_on<M, T, E>(
&mut self,
message_processor: impl for<'a> Fn(
&'a ManagedActor<Started, State>,
&'a mut MessageContext<M>,
) -> std::pin::Pin<
Box<dyn Future<Output = Result<T, E>> + Send + Sync + 'static>,
> + Send
+ Sync
+ 'static,
) -> &mut Self
where
M: ActonMessage + Clone + Send + Sync + 'static,
T: ActonMessageReply + 'static,
E: std::error::Error + Send + Sync + 'static,
{
let type_id = TypeId::of::<M>();
trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding read-only Result-returning message handler");
let handler_box = Box::new(
move |actor: &ManagedActor<Started, State>,
envelope: &mut Envelope|
-> crate::common::FutureBoxResult {
if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
trace!(
"Downcast successful for message type: {}",
std::any::type_name::<M>()
);
let mut msg_context = {
let origin_envelope = OutboundEnvelope::new_with_recipient(
envelope.reply_to.clone(),
envelope.recipient.clone(),
actor.handle.cancellation_token.clone(),
);
let reply_envelope = OutboundEnvelope::new_with_recipient(
envelope.recipient.clone(),
envelope.reply_to.clone(),
actor.handle.cancellation_token.clone(),
);
MessageContext {
message: concrete_msg.clone(),
origin_envelope,
reply_envelope,
}
};
let fut = message_processor(actor, &mut msg_context);
Box::pin(async move {
match fut.await {
Ok(val) => {
let boxed: Box<dyn ActonMessageReply + Send> = Box::new(val);
Ok(boxed)
}
Err(e) => {
let error_type_id = TypeId::of::<E>();
let boxed_err: Box<dyn std::error::Error + Send + Sync> =
Box::new(e);
Err((boxed_err, error_type_id))
}
}
})
} else {
error!(
type_name = std::any::type_name::<M>(),
"Read-only Result handler called with incompatible message type (downcast failed)"
);
Box::pin(async {
let boxed: Box<dyn ActonMessageReply + Send> = Box::new(());
Ok(boxed)
})
}
},
);
self.read_only_handlers
.insert(type_id, ReactorItem::ReadOnlyFallible(handler_box));
self
}
pub fn try_mutate_on<M, T, E>(
&mut self,
message_processor: impl for<'a> Fn(
&'a mut ManagedActor<Started, State>,
&'a mut MessageContext<M>,
) -> std::pin::Pin<
Box<dyn Future<Output = Result<T, E>> + Send + Sync + 'static>,
> + Send
+ Sync
+ 'static,
) -> &mut Self
where
M: ActonMessage + Clone + Send + Sync + 'static,
T: ActonMessageReply + 'static,
E: std::error::Error + Send + Sync + 'static,
{
let type_id = TypeId::of::<M>();
trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding Result-returning message handler");
let handler_box = Box::new(
move |actor: &mut ManagedActor<Started, State>,
envelope: &mut Envelope|
-> crate::common::FutureBoxResult {
if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
trace!(
"Downcast successful for message type: {}",
std::any::type_name::<M>()
);
let mut msg_context = {
let origin_envelope = OutboundEnvelope::new_with_recipient(
envelope.reply_to.clone(),
envelope.recipient.clone(),
actor.handle.cancellation_token.clone(),
);
let reply_envelope = OutboundEnvelope::new_with_recipient(
envelope.recipient.clone(),
envelope.reply_to.clone(),
actor.handle.cancellation_token.clone(),
);
MessageContext {
message: concrete_msg.clone(),
origin_envelope,
reply_envelope,
}
};
let fut = message_processor(actor, &mut msg_context);
Box::pin(async move {
match fut.await {
Ok(val) => {
let boxed: Box<dyn ActonMessageReply + Send> = Box::new(val);
Ok(boxed)
}
Err(e) => {
let error_type_id = TypeId::of::<E>();
let boxed_err: Box<dyn std::error::Error + Send + Sync> =
Box::new(e);
Err((boxed_err, error_type_id))
}
}
})
} else {
error!(
type_name = std::any::type_name::<M>(),
"Result handler called with incompatible message type (downcast failed)"
);
Box::pin(async {
let boxed: Box<dyn ActonMessageReply + Send> = Box::new(());
Ok(boxed)
})
}
},
);
self.message_handlers
.insert(type_id, ReactorItem::MutableFallible(handler_box));
self
}
pub fn after_start<F, Fut>(&mut self, f: F) -> &mut Self
where
F: for<'b> Fn(&'b ManagedActor<Started, State>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + Sync + 'static,
{
self.after_start = Some(Box::new(move |actor| Box::pin(f(actor))));
self
}
pub fn before_start<F, Fut>(&mut self, f: F) -> &mut Self
where
F: for<'b> Fn(&'b ManagedActor<Started, State>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + Sync + 'static,
{
self.before_start = Some(Box::new(move |actor| Box::pin(f(actor))));
self
}
pub fn after_stop<F, Fut>(&mut self, f: F) -> &mut Self
where
F: for<'b> Fn(&'b ManagedActor<Started, State>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + Sync + 'static,
{
self.after_stop = Some(Box::new(move |actor| Box::pin(f(actor))));
self
}
pub fn before_stop<F, Fut>(&mut self, f: F) -> &mut Self
where
F: for<'b> Fn(&'b ManagedActor<Started, State>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + Sync + 'static,
{
self.before_stop = Some(Box::new(move |actor| Box::pin(f(actor))));
self
}
#[cfg(feature = "ipc")]
pub const fn expose_for_ipc(&mut self) -> &mut Self {
self.expose_for_ipc = true;
self
}
#[instrument(skip(self))]
pub fn create_child(&self, name: String) -> anyhow::Result<Self> {
let config = ActorConfig::for_supervised_child(
name,
self.handle.clone(), Some(self.runtime.broker()), )?;
Ok(Self::new(Some(self.runtime()), Some(&config)))
}
#[instrument]
pub(crate) fn new(runtime: Option<&ActorRuntime>, config: Option<&ActorConfig>) -> Self {
let mut managed_actor: Self = Self::default();
if let Some(app) = runtime {
managed_actor.broker = app.0.broker.clone();
managed_actor.handle.broker = Box::new(Some(app.0.broker.clone()));
managed_actor.cancellation_token = Some(app.0.cancellation_token.child_token());
}
if let Some(config) = &config {
managed_actor.handle.id = config.id();
managed_actor.parent = config.parent().cloned();
managed_actor.handle.broker = Box::new(config.get_broker().cloned());
if let Some(broker) = config.get_broker().cloned() {
managed_actor.broker = broker;
}
if let Some(capacity) = config.inbox_capacity() {
let (outbox, inbox) = channel(capacity);
managed_actor.handle.outbox = outbox;
managed_actor.inbox = inbox;
}
managed_actor.restart_policy = config.restart_policy();
managed_actor.supervision_strategy = config.supervision_strategy();
managed_actor.escalation = config.escalation();
managed_actor.restart_limiter_config = config.restart_limiter_config().cloned();
}
debug_assert!(
!managed_actor.inbox.is_closed(),
"Actor mailbox is closed in new"
);
trace!("NEW ACTOR: {}", &managed_actor.handle.id());
assert!(
runtime.is_some(),
"ActorRuntime must be provided to ManagedActor::new"
);
let runtime = runtime.unwrap().clone();
managed_actor.runtime = runtime;
managed_actor.id = managed_actor.handle.id();
managed_actor
}
#[cfg_attr(
feature = "ipc",
doc = "5. If [`expose_for_ipc()`](Self::expose_for_ipc) was called, registers the actor for IPC access."
)]
#[instrument(skip(self))]
pub async fn start(mut self) -> ActorHandle {
trace!("Starting actor: {}", self.id());
trace!("Model state before start: {:?}", self.model);
let message_handlers = mem::take(&mut self.message_handlers);
let read_only_handlers = mem::take(&mut self.read_only_handlers);
let actor_ref = self.handle.clone();
#[cfg(feature = "ipc")]
let should_expose_for_ipc = self.expose_for_ipc;
#[cfg(feature = "ipc")]
let ipc_name = ipc_name_for(&self.id);
#[cfg(feature = "ipc")]
let runtime_for_ipc = self.runtime.clone();
let mut active_actor: ManagedActor<Started, State> = self.into();
if let Some(ref hook) = active_actor.before_start {
trace!("Executing before_start hook for actor: {}", active_actor.id());
hook(&active_actor).await;
}
trace!("Spawning main task (wake) for actor: {}", active_actor.id());
actor_ref.tracker().spawn(async move {
active_actor
.wake(message_handlers, read_only_handlers)
.await;
});
actor_ref.tracker().close();
#[cfg(feature = "ipc")]
if should_expose_for_ipc {
trace!("Exposing actor '{}' for IPC access", ipc_name);
if let Err(conflict) = runtime_for_ipc.ipc_expose(&ipc_name, actor_ref.clone()) {
error!(
ipc_name = %ipc_name,
wanted_by = %actor_ref.id(),
held_by = %conflict.held_by(),
"IPC name already claimed; this actor will not be reachable under it. \
Give one of the two actors a different name, or use ActorRuntime::ipc_expose \
to choose the name explicitly."
);
}
}
trace!("Actor {} started successfully.", actor_ref.id());
actor_ref }
}
pub fn downcast_message<T: ActonMessage + 'static>(msg: &dyn ActonMessage) -> Option<&T> {
msg.as_any().downcast_ref::<T>()
}
impl<State: Default + Send + Debug + 'static> From<ManagedActor<Idle, State>>
for ManagedActor<Started, State>
{
fn from(value: ManagedActor<Idle, State>) -> Self {
assert!(
value.cancellation_token.is_some(),
"Cannot transition to ManagedActor<Started, State> without a cancellation_token"
);
Self {
handle: value.handle,
parent: value.parent,
halt_signal: value.halt_signal,
id: value.id,
runtime: value.runtime,
model: value.model,
start_tasks: value.start_tasks,
inbox: value.inbox,
before_start: value.before_start,
after_start: value.after_start,
before_stop: value.before_stop,
after_stop: value.after_stop,
broker: value.broker,
message_handlers: value.message_handlers,
read_only_handlers: value.read_only_handlers,
error_handler_map: value.error_handler_map, cancellation_token: value.cancellation_token,
restart_policy: value.restart_policy,
supervision_strategy: value.supervision_strategy,
escalation: value.escalation,
restart_limiter_config: value.restart_limiter_config,
expose_for_ipc: value.expose_for_ipc,
supervision: value.supervision,
_actor_state: PhantomData,
}
}
}
impl<State: Default + Send + Debug + 'static> Default for ManagedActor<Idle, State> {
fn default() -> Self {
use crate::common::config::CONFIG;
let capacity = CONFIG.limits.actor_inbox_capacity;
let (outbox, inbox) = channel(capacity);
let id = Ern::default();
let handle = ActorHandle::new(id.clone(), outbox);
Self {
handle,
id,
inbox,
before_start: None,
after_start: None,
before_stop: None,
after_stop: None,
model: State::default(),
broker: ActorHandle::placeholder(),
error_handler_map: HashMap::new(),
parent: Option::default(),
runtime: ActorRuntime::default(),
halt_signal: AtomicBool::default(),
start_tasks: TaskTracker::default(),
cancellation_token: Option::default(),
message_handlers: HashMap::new(),
read_only_handlers: HashMap::new(),
restart_policy: RestartPolicy::default(),
supervision_strategy: SupervisionStrategy::default(),
escalation: Escalation::default(),
restart_limiter_config: None,
expose_for_ipc: false,
supervision: SupervisionRegistry::default(),
_actor_state: PhantomData,
}
}
}