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 tokio::sync::mpsc;
use tokio_util::task::TaskTracker;
use tracing::{error, instrument, trace, warn};
use crate::actor::{Idle, ManagedActor};
use crate::common::{ActorSender, BrokerRef, OutboundEnvelope};
use crate::message::{BrokerRequest, MessageAddress, SystemSignal};
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(); 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()); Ok(handle)
}
}
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 + '_ {
async move {
let tracker = self.tracker();
let self_envelope = self.create_envelope(Some(self.reply_address()));
trace!(actor = %self.id, "Sending Terminate signal");
self_envelope.send(SystemSignal::Terminate).await;
tracker.wait().await;
trace!(actor = %self.id, "Actor terminated successfully.");
Ok(())
}
}
#[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)
}
}