use crate::{
ActorId, AskError, MailboxCapacity, ReplyTo,
mailbox::{Mailbox, MailboxHandle, TerminatedSink, Watcher, WatcherRegistry, make_mailbox},
};
use derive_more::Debug;
use std::{
any::type_name,
error::Error,
hash::{Hash, Hasher},
sync::Arc,
time::Duration,
};
use tokio::{sync::oneshot, time::timeout};
use tracing::warn;
#[derive(Debug)]
pub struct ActorRef<M> {
actor_id: ActorId,
#[debug(skip)]
mailbox_handle: MailboxHandle<M>,
}
impl<M> ActorRef<M> {
pub fn actor_id(&self) -> ActorId {
self.actor_id
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn tell(&self, message: M) {
if let Err(error) = self.mailbox_handle.try_send_message(message) {
self.dead_letter(&error);
}
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub async fn ask<R, F>(&self, within: Duration, make_message: F) -> Result<R, AskError>
where
F: FnOnce(ReplyTo<R>) -> M,
R: Send + 'static,
{
let actor_id = self.actor_id;
let (reply_tx, reply_rx) = oneshot::channel();
let reply_to = ReplyTo::new(move |reply| {
if reply_tx.send(reply).is_err() {
warn!(
%actor_id,
reply_type = type_name::<R>(),
error = "asker no longer awaits the reply",
"dead letter"
);
}
});
self.mailbox_handle
.try_send_message(make_message(reply_to))?;
match timeout(within, reply_rx).await {
Ok(reply) => reply.map_err(|_| AskError::NoReply),
Err(_) => Err(AskError::Timeout(within)),
}
}
pub(crate) fn watcher_registry(&self) -> &WatcherRegistry {
self.mailbox_handle.watcher_registry()
}
fn new(actor_id: ActorId, mailbox_handle: MailboxHandle<M>) -> Self {
Self {
actor_id,
mailbox_handle,
}
}
fn dead_letter(&self, error: &dyn Error) {
warn!(
actor_id = %self.actor_id,
message_type = type_name::<M>(),
%error,
source = error.source(),
"dead letter"
);
}
}
impl<M> PartialEq for ActorRef<M> {
fn eq(&self, other: &Self) -> bool {
self.actor_id == other.actor_id
}
}
impl<M> Eq for ActorRef<M> {}
impl<M> Hash for ActorRef<M> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.actor_id.hash(state);
}
}
impl<M> Clone for ActorRef<M> {
fn clone(&self) -> Self {
Self {
actor_id: self.actor_id,
mailbox_handle: self.mailbox_handle.clone(),
}
}
}
#[derive(Debug)]
pub(crate) struct SelfRef<M> {
actor_ref: ActorRef<M>,
#[debug(skip)]
terminated_sink: Arc<dyn TerminatedSink>,
}
impl<M> SelfRef<M> {
pub(crate) fn new(actor_id: ActorId, mailbox_capacity: MailboxCapacity) -> (Self, Mailbox<M>)
where
M: Send + 'static,
{
let (mailbox_handle, mailbox) = make_mailbox(mailbox_capacity);
let terminated_sink = mailbox_handle.terminated_sink();
let actor_ref = ActorRef::new(actor_id, mailbox_handle);
(
Self {
actor_ref,
terminated_sink,
},
mailbox,
)
}
pub(crate) fn actor_ref(&self) -> &ActorRef<M> {
&self.actor_ref
}
pub(crate) fn make_watcher(&self) -> Watcher {
Watcher::new(self.actor_ref.actor_id, self.terminated_sink.clone())
}
pub(crate) fn send_terminated(&self, actor_id: ActorId) {
self.terminated_sink
.send_terminated(actor_id)
.expect("the actor's own mailbox outlives its context");
}
}
impl<M> Clone for SelfRef<M> {
fn clone(&self) -> Self {
Self {
actor_ref: self.actor_ref.clone(),
terminated_sink: self.terminated_sink.clone(),
}
}
}
#[cfg(test)]
mod tests {
use crate::{ActorId, ActorRef, MailboxCapacity, actor_ref::SelfRef};
use std::hash::{DefaultHasher, Hash, Hasher};
#[test]
fn references_are_equal_and_hash_by_id() {
let (self_ref, _mailbox) = SelfRef::<()>::new(ActorId::new(), MailboxCapacity::Unbounded);
let (other_ref, _other_mailbox) =
SelfRef::<()>::new(ActorId::new(), MailboxCapacity::Unbounded);
let actor_ref = self_ref.actor_ref().clone();
assert_eq!(&actor_ref, self_ref.actor_ref());
assert_eq!(hash_of(&actor_ref), hash_of(self_ref.actor_ref()));
assert_ne!(&actor_ref, other_ref.actor_ref());
}
fn hash_of<M>(actor_ref: &ActorRef<M>) -> u64 {
let mut hasher = DefaultHasher::new();
actor_ref.hash(&mut hasher);
hasher.finish()
}
}