use std::fmt::Debug;
use std::future::Future;
use std::hash::{Hash, Hasher};
use acton_ern::Ern;
use async_trait::async_trait;
use dashmap::DashMap;
use std::sync::Arc;
use tokio::sync::{mpsc, watch, SetOnce};
use tokio_util::task::TaskTracker;
use tracing::{error, instrument, trace, warn};
use crate::actor::{
status_channel, ActorConfig, ChildBlueprint, ChildSpawner, Idle, ManagedActor, RestartGeneration,
SupervisedChild, SupervisionError, SupervisionState, SupervisionStatus, TypedSpawner,
};
use crate::common::{ActorRuntime, ActorSender, BrokerRef, OutboundEnvelope};
use crate::message::{
BrokerRequest, CascadeTerminate, MessageAddress, RegisterSupervisedChild, RegistrationOutcome,
ReleaseOutcome, SystemSignal, UnregisterSupervisedChild,
};
use crate::prelude::ActonMessage;
use crate::traits::{ActorHandleInterface, Broadcaster, Subscriber};
#[derive(Debug, Clone)]
pub struct ActorHandle {
pub(crate) id: Ern,
pub(crate) outbox: ActorSender,
tracker: TaskTracker,
pub parent: Option<Box<Self>>,
pub broker: Box<Option<Self>>,
children: DashMap<String, Self>,
pub(crate) cancellation_token: tokio_util::sync::CancellationToken,
}
impl ActorHandle {
#[inline]
pub(crate) fn new(id: Ern, outbox: ActorSender) -> Self {
Self {
id,
outbox,
tracker: TaskTracker::new(),
parent: None,
broker: Box::new(None),
children: DashMap::new(),
cancellation_token: tokio_util::sync::CancellationToken::new(),
}
}
#[inline]
pub(crate) fn placeholder() -> Self {
use crate::common::config::CONFIG;
let dummy_channel_size = CONFIG.limits.dummy_channel_size;
let (outbox, _) = mpsc::channel(dummy_channel_size);
Self {
id: Ern::default(),
outbox,
tracker: TaskTracker::new(),
parent: None,
broker: Box::new(None),
children: DashMap::new(),
cancellation_token: tokio_util::sync::CancellationToken::new(),
}
}
}
impl Default for ActorHandle {
fn default() -> Self {
Self::placeholder()
}
}
impl Subscriber for ActorHandle {
fn get_broker(&self) -> Option<BrokerRef> {
*self.broker.clone() }
}
impl PartialEq for ActorHandle {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for ActorHandle {}
impl Hash for ActorHandle {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl ActorHandle {
#[instrument(skip(self, child))] pub async fn supervise<State: Default + Send + Debug + 'static>(
&self,
child: ManagedActor<Idle, State>,
) -> anyhow::Result<Self> {
let child_id = child.id().clone(); let restart_policy = child.restart_policy; trace!("Supervising child actor with id: {}", child_id);
let handle = child.start().await; trace!(
"Child actor {} started, adding to parent {} children map",
child_id,
self.id
);
self.children.insert(handle.id.to_string(), handle.clone());
let (status, _unwatched) = watch::channel(SupervisionStatus::new(
child_id.clone(),
Some(handle.clone()),
RestartGeneration::FIRST,
SupervisionState::Starting,
0,
));
self.send(RegisterSupervisedChild {
child: child_id,
handle: handle.clone(),
spawner: None,
restart_policy,
limiter: None,
status,
outcome: None,
})
.await;
Ok(handle)
}
async fn stop_with_signal<M: ActonMessage + 'static>(
&self,
signal: M,
) -> anyhow::Result<()> {
let tracker = self.tracker();
let self_envelope = self.create_envelope(Some(self.reply_address()));
trace!(actor = %self.id, signal = ?signal, "Sending stop signal");
self_envelope.send(signal).await;
tracker.wait().await;
trace!(actor = %self.id, "Actor terminated successfully.");
Ok(())
}
pub(crate) async fn stop_for_parent_shutdown(&self) -> anyhow::Result<()> {
self.stop_with_signal(CascadeTerminate).await
}
}
impl ActorHandle {
pub async fn supervise_with<S: Default + Send + Debug + 'static>(
&self,
runtime: &ActorRuntime,
config: ActorConfig,
configure: impl Fn(&mut ManagedActor<Idle, S>) + Send + Sync + 'static,
) -> Result<SupervisedChild, SupervisionError> {
let blueprint: Arc<ChildBlueprint<S>> = Arc::new(configure);
let limiter = config.restart_limiter_config().cloned();
let spawner: Arc<dyn ChildSpawner> = Arc::new(TypedSpawner::new(config, blueprint));
let child_id = spawner.child_id().clone();
let restart_policy = spawner.restart_policy();
let handle = spawner.spawn(runtime.clone(), self.clone()).await?;
let (status, mut receiver) = status_channel(&child_id, Some(handle.clone()));
let outcome: RegistrationOutcome = Arc::new(SetOnce::new());
self.send(RegisterSupervisedChild {
child: child_id.clone(),
handle: handle.clone(),
spawner: Some(spawner),
restart_policy,
limiter,
status,
outcome: Some(Arc::clone(&outcome)),
})
.await;
let result = {
let closed = async {
while receiver.changed().await.is_ok() {}
};
tokio::select! {
answer = outcome.wait() => answer.clone(),
() = self.cancellation_token.cancelled() => Err(SupervisionError::SupervisorStopped {
supervisor: self.id.clone(),
}),
() = closed => outcome.get().map_or_else(
|| Err(SupervisionError::RegistrationLost { child: child_id.clone() }),
Clone::clone,
),
}
};
if let Err(error) = result {
let _ = handle.stop().await;
return Err(error);
}
Ok(SupervisedChild::new(child_id, self.id.clone(), receiver))
}
pub async fn unsupervise(&self, child: &Ern) -> Result<(), SupervisionError> {
let released = self.retire_child(child, true).await?;
if let Some(handle) = released {
let _ = handle.stop().await;
}
Ok(())
}
pub async fn release(&self, child: &Ern) -> Result<Option<Self>, SupervisionError> {
self.retire_child(child, false).await
}
async fn retire_child(
&self,
child: &Ern,
stopping: bool,
) -> Result<Option<Self>, SupervisionError> {
let outcome: ReleaseOutcome = Arc::new(SetOnce::new());
let (liveness, mut alive) = watch::channel(());
self.send(UnregisterSupervisedChild {
child: child.clone(),
stopping,
outcome: Arc::clone(&outcome),
liveness,
})
.await;
tokio::select! {
answer = outcome.wait() => answer.clone(),
() = self.cancellation_token.cancelled() => Err(SupervisionError::SupervisorStopped {
supervisor: self.id.clone(),
}),
() = async { while alive.changed().await.is_ok() {} } => {
outcome.get().map_or_else(
|| Err(SupervisionError::ReleaseLost { child: child.clone() }),
Clone::clone,
)
}
}
}
}
impl Broadcaster for ActorHandle {
fn broadcast(&self, message: impl ActonMessage) -> impl Future<Output = ()> + Send + Sync + '_ {
trace!("Attempting broadcast via handle: {}", self.id);
async move {
if let Some(broker_handle) = self.broker.as_ref() {
trace!("Broker found for handle {}, sending BrokerRequest", self.id);
broker_handle.send(BrokerRequest::new(message)).await;
} else {
error!(
"No broker configured for actor handle {}, cannot broadcast.",
self.id
);
}
}
}
}
#[async_trait]
impl ActorHandleInterface for ActorHandle {
#[inline]
fn reply_address(&self) -> MessageAddress {
MessageAddress::new(self.outbox.clone(), self.id.clone())
}
#[instrument(skip(self))]
fn create_envelope(&self, recipient_address: Option<MessageAddress>) -> OutboundEnvelope {
let return_address = self.reply_address();
trace!(sender = %return_address.sender.root(), recipient = ?recipient_address.as_ref().map(|r| r.sender.root().as_str()), "Creating envelope");
if let Some(recipient) = recipient_address {
OutboundEnvelope::new_with_recipient(
return_address,
recipient,
self.cancellation_token.clone(),
)
} else {
OutboundEnvelope::new(return_address, self.cancellation_token.clone())
}
}
#[inline]
fn children(&self) -> &DashMap<String, ActorHandle> {
&self.children
}
#[instrument(skip(self))]
fn find_child(&self, ern: &Ern) -> Option<Self> {
trace!("Searching for child with ERN: {}", ern);
self.children.get(&ern.to_string()).map(
|entry| entry.value().clone(), )
}
#[inline]
fn tracker(&self) -> TaskTracker {
self.tracker.clone()
}
#[inline]
fn id(&self) -> Ern {
self.id.clone()
}
#[inline]
fn name(&self) -> String {
self.id.root().to_string()
}
#[inline]
fn clone_ref(&self) -> ActorHandle {
self.clone()
}
#[allow(clippy::manual_async_fn)] #[instrument(skip(self))]
fn stop(&self) -> impl Future<Output = anyhow::Result<()>> + Send + Sync + '_ {
self.stop_with_signal(SystemSignal::Terminate)
}
#[cfg(feature = "ipc")]
#[instrument(skip(self, message))]
fn send_boxed(
&self,
message: Box<dyn ActonMessage + Send + Sync>,
) -> impl Future<Output = anyhow::Result<()>> + Send + Sync + '_ {
use std::sync::Arc;
async move {
let envelope = self.create_envelope(Some(self.reply_address()));
trace!(recipient = %self.id, "Sending boxed message via IPC");
let arc_message: Arc<dyn ActonMessage + Send + Sync> = Arc::from(message);
envelope.send_arc(arc_message).await;
Ok(())
}
}
#[cfg(feature = "ipc")]
#[instrument(skip(self, message, reply_to))]
fn send_boxed_with_reply_to(
&self,
message: Box<dyn ActonMessage + Send + Sync>,
reply_to: MessageAddress,
) -> impl Future<Output = anyhow::Result<()>> + Send + Sync + '_ {
use std::sync::Arc;
async move {
let envelope = OutboundEnvelope::new_with_recipient(
reply_to, self.reply_address(), self.cancellation_token.clone(),
);
trace!(
recipient = %self.id,
reply_to = ?envelope.return_address.sender.root().as_str(),
"Sending boxed message with custom reply-to via IPC"
);
let arc_message: Arc<dyn ActonMessage + Send + Sync> = Arc::from(message);
envelope.send_arc(arc_message).await;
Ok(())
}
}
#[cfg(feature = "ipc")]
#[instrument(skip(self, message))]
fn try_send_boxed(
&self,
message: Box<dyn ActonMessage + Send + Sync>,
) -> Result<(), crate::common::ipc::IpcError> {
use std::sync::Arc;
let envelope = self.create_envelope(Some(self.reply_address()));
trace!(recipient = %self.id, "Trying to send boxed message via IPC (backpressure-aware)");
let arc_message: Arc<dyn ActonMessage + Send + Sync> = Arc::from(message);
envelope.try_send_arc(arc_message)
}
#[cfg(feature = "ipc")]
#[instrument(skip(self, message, reply_to))]
fn try_send_boxed_with_reply_to(
&self,
message: Box<dyn ActonMessage + Send + Sync>,
reply_to: MessageAddress,
) -> Result<(), crate::common::ipc::IpcError> {
use std::sync::Arc;
let envelope = OutboundEnvelope::new_with_recipient(
reply_to, self.reply_address(), self.cancellation_token.clone(),
);
trace!(
recipient = %self.id,
reply_to = ?envelope.return_address.sender.root().as_str(),
"Trying to send boxed message with custom reply-to via IPC (backpressure-aware)"
);
let arc_message: Arc<dyn ActonMessage + Send + Sync> = Arc::from(message);
envelope.try_send_arc(arc_message)
}
}