use crate::actor::IdleEventStream;
use crate::error::{Error, Result};
use crate::Identity;
use crate::{Actor, ControlSignal, MailboxMessage, MailboxSender, Message};
use futures::stream::{Stream, StreamExt};
use std::time::Duration;
use tokio::runtime::{Handle, RuntimeFlavor};
use tokio::sync::{mpsc, oneshot};
use tracing::warn;
#[inline]
fn current_multi_thread_handle() -> Option<Handle> {
let handle = Handle::try_current().ok()?;
(handle.runtime_flavor() == RuntimeFlavor::MultiThread).then_some(handle)
}
fn run_blocking_with_runtime<F, R>(identity: Identity, panic_msg: &'static str, fut: F) -> Result<R>
where
F: std::future::Future<Output = Result<R>> + Send + 'static,
R: Send + 'static,
{
let join = std::thread::spawn(move || -> Result<R> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.map_err(|e| Error::Send {
identity,
details: format!("Failed to create runtime: {}", e),
})?;
runtime.block_on(fut)
});
join.join().unwrap_or_else(|_| {
Err(Error::Send {
identity,
details: panic_msg.to_string(),
})
})
}
#[cfg(feature = "tracing")]
use tracing::{debug, info};
#[cfg(feature = "metrics")]
use crate::metrics::MetricsCollector;
#[cfg(feature = "metrics")]
use std::sync::Arc;
#[derive(Debug)]
pub struct ActorRef<T: Actor> {
id: Identity,
sender: MailboxSender<T>,
pub(crate) priority_sender: Option<MailboxSender<T>>,
pub(crate) terminate_sender: mpsc::Sender<ControlSignal>,
pub(crate) idle_subscribe_sender: Option<mpsc::Sender<IdleEventStream<T>>>,
#[cfg(feature = "metrics")]
pub(crate) metrics: Arc<MetricsCollector>,
}
impl<T: Actor> ActorRef<T> {
pub(crate) fn new(
id: Identity,
sender: MailboxSender<T>,
priority_sender: Option<MailboxSender<T>>,
terminate_sender: mpsc::Sender<ControlSignal>,
idle_subscribe_sender: Option<mpsc::Sender<IdleEventStream<T>>>,
#[cfg(feature = "metrics")] metrics: Arc<MetricsCollector>,
) -> Self {
ActorRef {
id,
sender,
priority_sender,
terminate_sender,
idle_subscribe_sender,
#[cfg(feature = "metrics")]
metrics,
}
}
pub fn identity(&self) -> Identity {
self.id
}
#[inline]
pub fn has_priority_channel(&self) -> bool {
self.priority_sender.is_some()
}
#[inline]
pub fn has_idle_channel(&self) -> bool {
self.idle_subscribe_sender.is_some()
}
#[cfg(any(test, feature = "test-utils"))]
pub fn drop_priority_sender_for_test(&mut self) {
self.priority_sender = None;
}
#[inline]
pub fn is_alive(&self) -> bool {
!self.sender.is_closed() && !self.terminate_sender.is_closed()
}
pub fn downgrade(this: &Self) -> ActorWeak<T> {
ActorWeak {
id: this.id,
sender: this.sender.downgrade(),
priority_sender: this.priority_sender.as_ref().map(|s| s.downgrade()),
terminate_sender: this.terminate_sender.downgrade(),
idle_subscribe_sender: this.idle_subscribe_sender.as_ref().map(|s| s.downgrade()),
#[cfg(feature = "metrics")]
metrics: this.metrics.clone(),
}
}
pub fn subscribe_idle<S>(&self, stream: S) -> Result<()>
where
S: Stream<Item = T::IdleEvent> + Send + 'static,
{
let Some(idle_subscribe_sender) = self.idle_subscribe_sender.as_ref() else {
#[cfg(feature = "tracing")]
warn!("subscribe_idle called on actor without an idle channel");
return Err(Error::IdleChannelNotEnabled {
identity: self.identity(),
});
};
let boxed: IdleEventStream<T> = stream.boxed();
idle_subscribe_sender.try_send(boxed).map_err(|e| match e {
mpsc::error::TrySendError::Full(_) => Error::ChannelFull {
identity: self.identity(),
channel: "idle_subscribe",
},
mpsc::error::TrySendError::Closed(_) => Error::Send {
identity: self.identity(),
details: "Idle subscribe channel closed (actor is no longer alive)".to_string(),
},
})
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_tell",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>()
),
skip(self, msg)
))]
pub async fn tell<M>(&self, msg: M) -> Result<()>
where
M: Send + 'static,
T: Message<M>,
{
let envelope = MailboxMessage::Envelope {
payload: Box::new(msg),
reply_channel: None, actor_ref: self.clone(), };
#[cfg(feature = "tracing")]
debug!("Sending tell message (fire-and-forget)");
let result = if self.sender.send(envelope).await.is_err() {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ActorStopped,
"tell",
);
Err(Error::Send {
identity: self.identity(),
details: "Mailbox channel closed".to_string(),
})
} else {
Ok(())
};
#[cfg(feature = "tracing")]
match &result {
Ok(_) => debug!("Tell message sent successfully"),
Err(e) => warn!(error = %e, "Failed to send tell message"),
}
result
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_tell_with_timeout",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
timeout_ms = timeout.as_millis()
),
skip(self, msg)
))]
pub async fn tell_with_timeout<M>(&self, msg: M, timeout: Duration) -> Result<()>
where
T: Message<M>,
M: Send + 'static,
{
#[cfg(feature = "tracing")]
debug!(
timeout_ms = timeout.as_millis(),
"Sending tell message with timeout"
);
let result = tokio::time::timeout(timeout, self.tell(msg))
.await
.map_err(|_| {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::Timeout,
"tell",
);
Error::Timeout {
identity: self.identity(),
timeout,
operation: "tell".to_string(),
}
})?;
#[cfg(feature = "tracing")]
match &result {
Ok(_) => debug!("Tell with timeout completed successfully"),
Err(e) => warn!(error = %e, "Tell with timeout failed"),
}
result
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_ask",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
reply_type = %std::any::type_name::<T::Reply>()
),
skip(self, msg)
))]
pub async fn ask<M>(&self, msg: M) -> Result<T::Reply>
where
T: Message<M>,
M: Send + 'static,
T::Reply: Send + 'static,
{
#[cfg(feature = "deadlock-detection")]
let _guard = {
let caller = crate::CURRENT_ACTOR.try_with(|id| *id).ok();
if let Some(caller) = caller {
let callee = self.identity();
let mut graph = crate::wait_for_graph()
.lock()
.unwrap_or_else(|e| e.into_inner());
if caller.id == callee.id || crate::has_path(&graph, callee.id, caller.id) {
let cycle = crate::format_cycle_path(&graph, caller, callee);
drop(graph);
panic!(
"Deadlock detected: ask cycle {cycle}\n\
This is a design error. Use `tell` to break the cycle, \
or restructure actor dependencies."
);
}
graph.insert(caller.id, callee);
Some(crate::WaitForGuard(caller.id))
} else {
None
}
};
let (reply_tx, reply_rx) = oneshot::channel();
let envelope = MailboxMessage::Envelope {
payload: Box::new(msg),
reply_channel: Some(reply_tx),
actor_ref: self.clone(), };
#[cfg(feature = "tracing")]
debug!("Sending ask message and waiting for reply");
if self.sender.send(envelope).await.is_err() {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ActorStopped,
"ask",
);
#[cfg(feature = "tracing")]
warn!("Failed to send ask message: mailbox channel closed");
return Err(Error::Send {
identity: self.identity(),
details: "Mailbox channel closed".to_string(),
});
}
match reply_rx.await {
Ok(reply_any) => {
match reply_any.downcast::<T::Reply>() {
Ok(reply) => {
#[cfg(feature = "tracing")]
debug!("Ask reply received successfully");
Ok(*reply)
}
Err(_) => {
#[cfg(feature = "tracing")]
warn!(
expected_type = %std::any::type_name::<T::Reply>(),
"Ask reply type downcast failed"
);
Err(Error::Downcast {
identity: self.identity(),
expected_type: std::any::type_name::<T::Reply>().to_string(),
})
}
}
}
Err(_recv_err) => {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ReplyDropped,
"ask",
);
#[cfg(feature = "tracing")]
warn!("Ask reply channel closed unexpectedly");
Err(Error::Receive {
identity: self.identity(),
details: "Reply channel closed unexpectedly".to_string(),
})
}
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_ask_with_timeout",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
reply_type = %std::any::type_name::<T::Reply>(),
timeout_ms = timeout.as_millis()
),
skip(self, msg)
))]
pub async fn ask_with_timeout<M>(&self, msg: M, timeout: Duration) -> Result<T::Reply>
where
T: Message<M>,
M: Send + 'static,
T::Reply: Send + 'static,
{
#[cfg(feature = "tracing")]
debug!(
timeout_ms = timeout.as_millis(),
"Sending ask message with timeout"
);
let result = tokio::time::timeout(timeout, self.ask(msg))
.await
.map_err(|_| {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::Timeout,
"ask",
);
Error::Timeout {
identity: self.identity(),
timeout,
operation: "ask".to_string(),
}
})?;
#[cfg(feature = "tracing")]
match &result {
Ok(_) => debug!("Ask with timeout completed successfully"),
Err(e) => warn!(error = %e, "Ask with timeout failed"),
}
result
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_tell_priority",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
timeout_ms = timeout.as_millis()
),
skip(self, msg)
))]
pub async fn tell_priority<M>(&self, msg: M, timeout: Duration) -> Result<()>
where
M: Send + 'static,
T: Message<M>,
{
let Some(priority_sender) = self.priority_sender.as_ref() else {
#[cfg(feature = "tracing")]
warn!("tell_priority called on actor without a priority channel");
return Err(Error::PriorityChannelNotEnabled {
identity: self.identity(),
});
};
let envelope = MailboxMessage::Envelope {
payload: Box::new(msg),
reply_channel: None,
actor_ref: self.clone(),
};
#[cfg(feature = "tracing")]
debug!(
timeout_ms = timeout.as_millis(),
"Sending priority tell message"
);
let send_fut = priority_sender.send(envelope);
let result = match tokio::time::timeout(timeout, send_fut).await {
Ok(Ok(())) => Ok(()),
Ok(Err(_)) => {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ActorStopped,
"tell_priority",
);
Err(Error::Send {
identity: self.identity(),
details: "Priority channel closed".to_string(),
})
}
Err(_) => {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::Timeout,
"tell_priority",
);
Err(Error::Timeout {
identity: self.identity(),
timeout,
operation: "tell_priority".to_string(),
})
}
};
#[cfg(feature = "tracing")]
match &result {
Ok(_) => debug!("Priority tell message sent successfully"),
Err(e) => warn!(error = %e, "Priority tell failed"),
}
result
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_ask_priority",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
reply_type = %std::any::type_name::<T::Reply>(),
timeout_ms = timeout.as_millis()
),
skip(self, msg)
))]
pub async fn ask_priority<M>(&self, msg: M, timeout: Duration) -> Result<T::Reply>
where
T: Message<M>,
M: Send + 'static,
T::Reply: Send + 'static,
{
let Some(priority_sender) = self.priority_sender.as_ref() else {
#[cfg(feature = "tracing")]
warn!("ask_priority called on actor without a priority channel");
return Err(Error::PriorityChannelNotEnabled {
identity: self.identity(),
});
};
let result = tokio::time::timeout(timeout, async {
let (reply_tx, reply_rx) = oneshot::channel();
let envelope = MailboxMessage::Envelope {
payload: Box::new(msg),
reply_channel: Some(reply_tx),
actor_ref: self.clone(),
};
if priority_sender.send(envelope).await.is_err() {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ActorStopped,
"ask_priority",
);
return Err(Error::Send {
identity: self.identity(),
details: "Priority channel closed".to_string(),
});
}
match reply_rx.await {
Ok(reply_any) => match reply_any.downcast::<T::Reply>() {
Ok(reply) => Ok(*reply),
Err(_) => Err(Error::Downcast {
identity: self.identity(),
expected_type: std::any::type_name::<T::Reply>().to_string(),
}),
},
Err(_) => {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ReplyDropped,
"ask_priority",
);
Err(Error::Receive {
identity: self.identity(),
details: "Reply channel closed unexpectedly".to_string(),
})
}
}
})
.await;
match result {
Ok(inner) => inner,
Err(_) => {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::Timeout,
"ask_priority",
);
Err(Error::Timeout {
identity: self.identity(),
timeout,
operation: "ask_priority".to_string(),
})
}
}
}
pub fn blocking_tell_priority<M>(&self, msg: M, timeout: Duration) -> Result<()>
where
M: Send + 'static,
T: Message<M>,
{
let Some(priority_sender) = self.priority_sender.as_ref() else {
return Err(Error::PriorityChannelNotEnabled {
identity: self.identity(),
});
};
if let Some(handle) = current_multi_thread_handle() {
return tokio::task::block_in_place(|| {
handle.block_on(self.tell_priority(msg, timeout))
});
}
let envelope = MailboxMessage::Envelope {
payload: Box::new(msg),
reply_channel: None,
actor_ref: self.clone(),
};
let envelope = match priority_sender.try_send(envelope) {
Ok(()) => return Ok(()),
Err(mpsc::error::TrySendError::Closed(_)) => {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ActorStopped,
"tell_priority",
);
return Err(Error::Send {
identity: self.identity(),
details: "Priority channel closed".to_string(),
});
}
Err(mpsc::error::TrySendError::Full(env)) => env,
};
let identity = self.identity();
let priority_sender = priority_sender.clone();
run_blocking_with_runtime(
identity,
"Priority blocking thread terminated unexpectedly",
async move {
match tokio::time::timeout(timeout, priority_sender.send(envelope)).await {
Ok(Ok(())) => Ok(()),
Ok(Err(_)) => {
crate::dead_letter::record::<M>(
identity,
crate::dead_letter::DeadLetterReason::ActorStopped,
"tell_priority",
);
Err(Error::Send {
identity,
details: "Priority channel closed".to_string(),
})
}
Err(_) => {
crate::dead_letter::record::<M>(
identity,
crate::dead_letter::DeadLetterReason::Timeout,
"tell_priority",
);
Err(Error::Timeout {
identity,
timeout,
operation: "tell_priority".to_string(),
})
}
}
},
)
}
pub fn blocking_ask_priority<M>(&self, msg: M, timeout: Duration) -> Result<T::Reply>
where
T: Message<M>,
M: Send + 'static,
T::Reply: Send + 'static,
{
if self.priority_sender.is_none() {
return Err(Error::PriorityChannelNotEnabled {
identity: self.identity(),
});
}
if let Some(handle) = current_multi_thread_handle() {
return tokio::task::block_in_place(|| {
handle.block_on(self.ask_priority(msg, timeout))
});
}
let self_clone = self.clone();
run_blocking_with_runtime(
self.identity(),
"Priority blocking thread terminated unexpectedly",
async move { self_clone.ask_priority(msg, timeout).await },
)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "info", name = "actor_kill", skip(self))
)]
pub fn kill(&self) {
#[cfg(feature = "tracing")]
info!(actor_id = %self.identity(), "Killing actor");
match self.terminate_sender.try_send(ControlSignal::Terminate) {
Ok(_) => {
#[cfg(feature = "tracing")]
info!("Kill signal sent successfully");
}
Err(mpsc::error::TrySendError::Full(_)) => {
warn!("Failed to send Terminate to actor {}: terminate mailbox is full. Actor is likely already being terminated.", self.identity());
}
Err(mpsc::error::TrySendError::Closed(_)) => {
warn!("Failed to send Terminate to actor {}: terminate mailbox closed. Actor might already be stopped.", self.identity());
}
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "info", name = "actor_stop", skip(self))
)]
pub async fn stop(&self) {
match self
.sender
.send(MailboxMessage::StopGracefully(self.clone()))
.await
{
Ok(_) => {
#[cfg(feature = "tracing")]
info!(actor_id = %self.identity(), "Actor stop signal sent successfully");
}
Err(_) => {
warn!("Failed to send StopGracefully to actor {}: mailbox closed. Actor might already be stopped or stopping.", self.identity());
}
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_blocking_tell",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
timeout_ms = timeout.map(|t| t.as_millis())
),
skip(self, msg)
))]
pub fn blocking_tell<M>(&self, msg: M, timeout: Option<Duration>) -> Result<()>
where
M: Send + 'static,
T: Message<M>,
{
if let Some(handle) = current_multi_thread_handle() {
return tokio::task::block_in_place(|| {
handle.block_on(async {
match timeout {
Some(t) => self.tell_with_timeout(msg, t).await,
None => self.tell(msg).await,
}
})
});
}
match timeout {
Some(timeout_duration) => self.blocking_tell_with_timeout_impl(msg, timeout_duration),
None => self.blocking_tell_no_timeout(msg),
}
}
fn blocking_tell_no_timeout<M>(&self, msg: M) -> Result<()>
where
M: Send + 'static,
T: Message<M>,
{
let envelope = MailboxMessage::Envelope {
payload: Box::new(msg),
reply_channel: None, actor_ref: self.clone(), };
#[cfg(feature = "tracing")]
debug!("Sending blocking tell message (fire-and-forget)");
let result = self.sender.blocking_send(envelope).map_err(|_| {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ActorStopped,
"blocking_tell",
);
Error::Send {
identity: self.identity(),
details: "Mailbox channel closed".to_string(),
}
});
#[cfg(feature = "tracing")]
match &result {
Ok(_) => debug!("Blocking tell message sent successfully"),
Err(e) => warn!(error = %e, "Failed to send blocking tell message"),
}
result
}
fn blocking_tell_with_timeout_impl<M>(&self, msg: M, timeout: Duration) -> Result<()>
where
M: Send + 'static,
T: Message<M>,
{
let envelope = MailboxMessage::Envelope {
payload: Box::new(msg),
reply_channel: None,
actor_ref: self.clone(),
};
let envelope = match self.sender.try_send(envelope) {
Ok(()) => return Ok(()),
Err(mpsc::error::TrySendError::Closed(_)) => {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ActorStopped,
"blocking_tell",
);
return Err(Error::Send {
identity: self.identity(),
details: "Mailbox channel closed".to_string(),
});
}
Err(mpsc::error::TrySendError::Full(env)) => env,
};
let identity = self.identity();
let sender = self.sender.clone();
run_blocking_with_runtime(
identity,
"Timeout thread terminated unexpectedly",
async move {
match tokio::time::timeout(timeout, sender.send(envelope)).await {
Ok(Ok(())) => Ok(()),
Ok(Err(_)) => {
crate::dead_letter::record::<M>(
identity,
crate::dead_letter::DeadLetterReason::ActorStopped,
"blocking_tell",
);
Err(Error::Send {
identity,
details: "Mailbox channel closed".to_string(),
})
}
Err(_) => {
crate::dead_letter::record::<M>(
identity,
crate::dead_letter::DeadLetterReason::Timeout,
"blocking_tell",
);
Err(Error::Timeout {
identity,
timeout,
operation: "blocking_tell".to_string(),
})
}
}
},
)
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_blocking_ask",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
reply_type = %std::any::type_name::<T::Reply>(),
timeout_ms = timeout.map(|t| t.as_millis())
),
skip(self, msg)
))]
pub fn blocking_ask<M>(&self, msg: M, timeout: Option<Duration>) -> Result<T::Reply>
where
T: Message<M>,
M: Send + 'static,
T::Reply: Send + 'static,
{
if let Some(handle) = current_multi_thread_handle() {
return tokio::task::block_in_place(|| {
handle.block_on(async {
match timeout {
Some(t) => self.ask_with_timeout(msg, t).await,
None => self.ask(msg).await,
}
})
});
}
match timeout {
Some(timeout_duration) => self.blocking_ask_with_timeout_impl(msg, timeout_duration),
None => self.blocking_ask_no_timeout(msg),
}
}
fn blocking_ask_no_timeout<M>(&self, msg: M) -> Result<T::Reply>
where
T: Message<M>,
M: Send + 'static,
T::Reply: Send + 'static,
{
let (reply_tx, reply_rx) = oneshot::channel();
let envelope = MailboxMessage::Envelope {
payload: Box::new(msg),
reply_channel: Some(reply_tx),
actor_ref: self.clone(), };
#[cfg(feature = "tracing")]
debug!("Sending blocking ask message and waiting for reply");
self.sender.blocking_send(envelope).map_err(|_| {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ActorStopped,
"blocking_ask",
);
#[cfg(feature = "tracing")]
warn!("Failed to send blocking ask message: mailbox channel closed");
Error::Send {
identity: self.identity(),
details: "Mailbox channel closed".to_string(),
}
})?;
match reply_rx.blocking_recv() {
Ok(reply_any) => {
match reply_any.downcast::<T::Reply>() {
Ok(reply) => {
#[cfg(feature = "tracing")]
debug!("Blocking ask reply received successfully");
Ok(*reply)
}
Err(_) => {
#[cfg(feature = "tracing")]
warn!(
expected_type = %std::any::type_name::<T::Reply>(),
"Blocking ask reply type downcast failed"
);
Err(Error::Downcast {
identity: self.identity(),
expected_type: std::any::type_name::<T::Reply>().to_string(),
})
}
}
}
Err(_recv_err) => {
crate::dead_letter::record::<M>(
self.identity(),
crate::dead_letter::DeadLetterReason::ReplyDropped,
"blocking_ask",
);
#[cfg(feature = "tracing")]
warn!("Blocking ask reply channel closed unexpectedly");
Err(Error::Receive {
identity: self.identity(),
details: "Reply channel closed unexpectedly".to_string(),
})
}
}
}
fn blocking_ask_with_timeout_impl<M>(&self, msg: M, timeout: Duration) -> Result<T::Reply>
where
T: Message<M>,
M: Send + 'static,
T::Reply: Send + 'static,
{
let self_clone = self.clone();
let identity = self.identity();
run_blocking_with_runtime(
identity,
"Timeout thread terminated unexpectedly",
async move {
tokio::time::timeout(timeout, self_clone.ask(msg))
.await
.map_err(|_| {
crate::dead_letter::record::<M>(
identity,
crate::dead_letter::DeadLetterReason::Timeout,
"blocking_ask",
);
Error::Timeout {
identity,
timeout,
operation: "blocking_ask".to_string(),
}
})?
},
)
}
#[deprecated(
since = "0.10.0",
note = "Use `blocking_tell` instead. Timeout parameter is ignored."
)]
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_tell_blocking",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
timeout_ms = timeout.map(|t| t.as_millis())
),
skip(self, msg)
))]
pub fn tell_blocking<M>(&self, msg: M, timeout: Option<Duration>) -> Result<()>
where
T: Message<M>,
M: Send + 'static,
{
#[cfg(feature = "tracing")]
debug!("Executing deprecated tell_blocking, delegating to blocking_tell");
let _ = timeout;
self.blocking_tell(msg, None)
}
#[deprecated(
since = "0.10.0",
note = "Use `blocking_ask` instead. Timeout parameter is ignored."
)]
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_ask_blocking",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
reply_type = %std::any::type_name::<T::Reply>(),
timeout_ms = timeout.map(|t| t.as_millis())
),
skip(self, msg)
))]
pub fn ask_blocking<M>(&self, msg: M, timeout: Option<Duration>) -> Result<T::Reply>
where
T: Message<M>,
M: Send + 'static,
T::Reply: Send + 'static,
{
#[cfg(feature = "tracing")]
debug!("Executing deprecated ask_blocking, delegating to blocking_ask");
let _ = timeout;
self.blocking_ask(msg, None)
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_ask_join",
fields(
actor_id = %self.identity(),
message_type = %std::any::type_name::<M>(),
result_type = %std::any::type_name::<R>()
),
skip(self, msg)
))]
pub async fn ask_join<M, R>(&self, msg: M) -> crate::Result<R>
where
T: Message<M, Reply = tokio::task::JoinHandle<R>>,
M: Send + 'static,
R: Send + 'static,
{
#[cfg(feature = "tracing")]
tracing::debug!("Sending ask_join message and waiting for task completion");
let join_handle = self.ask(msg).await?;
#[cfg(feature = "tracing")]
tracing::debug!("Received JoinHandle, awaiting task completion");
let result = join_handle.await.map_err(|join_error| crate::Error::Join {
identity: self.identity(),
source: join_error,
})?;
#[cfg(feature = "tracing")]
tracing::debug!("Task completed successfully");
Ok(result)
}
#[cfg(feature = "metrics")]
pub fn metrics(&self) -> crate::MetricsSnapshot {
self.metrics.snapshot()
}
#[cfg(feature = "metrics")]
#[inline]
pub fn message_count(&self) -> u64 {
self.metrics.message_count()
}
#[cfg(feature = "metrics")]
#[inline]
pub fn avg_processing_time(&self) -> std::time::Duration {
self.metrics.avg_processing_time()
}
#[cfg(feature = "metrics")]
#[inline]
pub fn max_processing_time(&self) -> std::time::Duration {
self.metrics.max_processing_time()
}
#[cfg(feature = "metrics")]
#[inline]
pub fn priority_message_count(&self) -> u64 {
self.metrics.priority_message_count()
}
#[cfg(feature = "metrics")]
#[inline]
pub fn avg_priority_processing_time(&self) -> std::time::Duration {
self.metrics.avg_priority_processing_time()
}
#[cfg(feature = "metrics")]
#[inline]
pub fn max_priority_processing_time(&self) -> std::time::Duration {
self.metrics.max_priority_processing_time()
}
#[cfg(feature = "metrics")]
#[inline]
pub fn uptime(&self) -> std::time::Duration {
self.metrics.uptime()
}
#[cfg(feature = "metrics")]
#[inline]
pub fn last_activity(&self) -> Option<std::time::SystemTime> {
self.metrics.last_activity()
}
}
impl<T: Actor> Clone for ActorRef<T> {
#[inline]
fn clone(&self) -> Self {
ActorRef {
id: self.id,
sender: self.sender.clone(),
priority_sender: self.priority_sender.clone(),
terminate_sender: self.terminate_sender.clone(),
idle_subscribe_sender: self.idle_subscribe_sender.clone(),
#[cfg(feature = "metrics")]
metrics: self.metrics.clone(),
}
}
}
#[derive(Debug)]
pub struct ActorWeak<T: Actor> {
id: Identity,
sender: tokio::sync::mpsc::WeakSender<MailboxMessage<T>>,
priority_sender: Option<tokio::sync::mpsc::WeakSender<MailboxMessage<T>>>,
terminate_sender: tokio::sync::mpsc::WeakSender<ControlSignal>,
idle_subscribe_sender: Option<tokio::sync::mpsc::WeakSender<IdleEventStream<T>>>,
#[cfg(feature = "metrics")]
metrics: Arc<MetricsCollector>,
}
impl<T: Actor> ActorWeak<T> {
#[inline]
pub fn upgrade(&self) -> Option<ActorRef<T>> {
let sender = self.sender.upgrade()?;
let terminate_sender = self.terminate_sender.upgrade()?;
let priority_sender = self.priority_sender.as_ref().and_then(|s| s.upgrade());
let idle_subscribe_sender = self
.idle_subscribe_sender
.as_ref()
.and_then(|s| s.upgrade());
Some(ActorRef {
id: self.id,
sender,
priority_sender,
terminate_sender,
idle_subscribe_sender,
#[cfg(feature = "metrics")]
metrics: self.metrics.clone(),
})
}
pub fn identity(&self) -> Identity {
self.id
}
#[inline]
pub fn is_alive(&self) -> bool {
self.sender.strong_count() > 0 && self.terminate_sender.strong_count() > 0
}
}
impl<T: Actor> Clone for ActorWeak<T> {
#[inline]
fn clone(&self) -> Self {
ActorWeak {
id: self.id,
sender: self.sender.clone(),
priority_sender: self.priority_sender.clone(),
terminate_sender: self.terminate_sender.clone(),
idle_subscribe_sender: self.idle_subscribe_sender.clone(),
#[cfg(feature = "metrics")]
metrics: self.metrics.clone(),
}
}
}