use crate::actor_ref::ActorRef;
use crate::{ActorResult, ActorWeak, ControlSignal, FailurePhase, MailboxMessage};
use futures::stream::{BoxStream, SelectAll, StreamExt};
use std::{fmt::Debug, future::Future};
use tokio::sync::mpsc;
use tracing::{debug, error, Instrument};
pub(crate) type IdleEventStream<T> = BoxStream<'static, <T as Actor>::IdleEvent>;
macro_rules! run_with_actor_scope {
($actor_id:expr, $fut:expr) => {{
#[cfg(feature = "deadlock-detection")]
{
crate::CURRENT_ACTOR.scope($actor_id, $fut).await
}
#[cfg(not(feature = "deadlock-detection"))]
{
$fut.await
}
}};
}
macro_rules! process_envelope {
(
actor_id = $actor_id:expr,
actor = $actor:expr,
payload = $payload:expr,
reply_channel = $reply_channel:expr,
actor_ref = $actor_ref:expr,
span_name = $span_name:literal,
guard = $guard_type:ident $(,)?
) => {{
#[cfg(feature = "tracing")]
let msg_span = tracing::debug_span!($span_name);
#[cfg(not(feature = "tracing"))]
let msg_span = tracing::Span::none();
#[cfg(feature = "tracing")]
let start_time = std::time::Instant::now();
#[cfg(feature = "metrics")]
let metrics_collector = $actor_ref.metrics.clone();
#[cfg(feature = "metrics")]
let _metrics_guard = crate::metrics::collector::$guard_type::new(&metrics_collector);
run_with_actor_scope!(
$actor_id,
$payload
.handle_message(&mut $actor, $actor_ref, $reply_channel)
.instrument(msg_span)
);
#[cfg(feature = "tracing")]
debug!(
"Actor {} processed {} in {:?}",
$actor_id,
$span_name,
start_time.elapsed()
);
}};
}
pub trait Actor: Sized + Send + 'static {
type Args: Send;
type Error: Send + Debug;
type IdleEvent: Send + 'static;
fn on_start(
args: Self::Args,
actor_ref: &ActorRef<Self>,
) -> impl Future<Output = std::result::Result<Self, Self::Error>> + Send;
#[allow(unused_variables)]
fn on_idle(
&mut self,
event: Self::IdleEvent,
actor_weak: &ActorWeak<Self>,
) -> impl Future<Output = std::result::Result<(), Self::Error>> + Send {
async { Ok(()) }
}
#[allow(unused_variables)]
fn on_stop(
&mut self,
actor_weak: &ActorWeak<Self>,
killed: bool,
) -> impl Future<Output = std::result::Result<(), Self::Error>> + Send {
async { Ok(()) }
}
}
pub trait Message<T: Send + 'static>: Actor {
type Reply: Send + 'static;
fn handle(
&mut self,
msg: T,
actor_ref: &ActorRef<Self>,
) -> impl Future<Output = Self::Reply> + Send;
fn on_tell_result(_result: &Self::Reply, _actor_ref: &ActorRef<Self>) {
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(
level = "debug",
name = "actor_lifecycle",
fields(
actor_id = %actor_ref.identity(),
actor_type = %std::any::type_name::<T>()
),
skip_all
))]
pub(crate) async fn run_actor_lifecycle<T: Actor>(
args: T::Args,
actor_ref: ActorRef<T>,
mut receiver: mpsc::Receiver<MailboxMessage<T>>,
mut priority_receiver: Option<mpsc::Receiver<MailboxMessage<T>>>,
mut terminate_receiver: mpsc::Receiver<ControlSignal>,
mut idle_subscribe_receiver: Option<mpsc::Receiver<IdleEventStream<T>>>,
) -> ActorResult<T> {
let actor_id = actor_ref.identity();
#[cfg(feature = "tracing")]
let on_start_span = tracing::debug_span!("actor_on_start");
#[cfg(not(feature = "tracing"))]
let on_start_span = tracing::Span::none();
let mut actor = match run_with_actor_scope!(
actor_id,
T::on_start(args, &actor_ref).instrument(on_start_span)
) {
Ok(actor) => {
debug!("Actor {actor_id} on_start completed successfully.");
actor
}
Err(e) => {
error!("Actor {actor_id} on_start failed: {e:?}");
return ActorResult::Failed {
actor: None,
error: e,
secondary_error: None,
phase: FailurePhase::OnStart,
killed: false,
};
}
};
debug!("Actor {actor_id} runtime starting - entering main processing loop.");
let actor_weak = ActorRef::downgrade(&actor_ref);
drop(actor_ref);
let mut killed = false;
let mut idle_streams: SelectAll<IdleEventStream<T>> = SelectAll::new();
if let Some(rx) = idle_subscribe_receiver.as_mut() {
while let Ok(stream) = rx.try_recv() {
idle_streams.push(stream);
}
}
loop {
tokio::select! {
biased;
maybe_terminate = terminate_receiver.recv() => {
match maybe_terminate {
Some(_) => {
#[cfg(feature = "tracing")]
debug!("Actor termination via kill() method");
killed = true;
}
None => {
#[cfg(feature = "tracing")]
debug!("Actor termination due to all actor_ref instances being dropped");
killed = false;
}
}
#[cfg(feature = "tracing")]
let on_stop_span = tracing::debug_span!("actor_on_stop", killed);
#[cfg(not(feature = "tracing"))]
let on_stop_span = tracing::Span::none();
if let Err(e) = run_with_actor_scope!(
actor_id,
actor.on_stop(&actor_weak, killed).instrument(on_stop_span)
) {
error!("Actor {actor_id} on_stop failed during termination: {e:?}");
return ActorResult::Failed {
actor: Some(actor),
error: e,
secondary_error: None,
phase: FailurePhase::OnStop,
killed,
};
}
break; }
maybe_priority = async {
match priority_receiver.as_mut() {
Some(rx) => rx.recv().await,
None => std::future::pending::<Option<MailboxMessage<T>>>().await,
}
} => {
match maybe_priority {
Some(MailboxMessage::Envelope { payload, reply_channel, actor_ref }) => {
process_envelope!(
actor_id = actor_id,
actor = actor,
payload = payload,
reply_channel = reply_channel,
actor_ref = actor_ref,
span_name = "actor_process_priority_message",
guard = PriorityMessageProcessingGuard,
);
}
Some(MailboxMessage::StopGracefully(_)) => {
debug_assert!(
false,
"MailboxMessage::StopGracefully observed on the priority channel; \
priority channel must only carry Envelope variants"
);
}
None => {
priority_receiver = None;
}
}
}
maybe_subscription = async {
match idle_subscribe_receiver.as_mut() {
Some(rx) => rx.recv().await,
None => std::future::pending::<Option<IdleEventStream<T>>>().await,
}
} => {
match maybe_subscription {
Some(stream) => {
idle_streams.push(stream);
}
None => {
idle_subscribe_receiver = None;
}
}
}
maybe_message = receiver.recv() => {
match maybe_message {
Some(MailboxMessage::Envelope { payload, reply_channel, actor_ref }) => {
process_envelope!(
actor_id = actor_id,
actor = actor,
payload = payload,
reply_channel = reply_channel,
actor_ref = actor_ref,
span_name = "actor_process_message",
guard = MessageProcessingGuard,
);
}
_msg @ (Some(MailboxMessage::StopGracefully(_)) | None) => {
#[cfg(feature = "tracing")]
match &_msg {
Some(_) => debug!("Actor termination: explicit graceful stop"),
None => debug!("Actor termination: all strong refs dropped"),
}
if let Some(rx) = priority_receiver.as_mut() {
rx.close();
while let Ok(msg) = rx.try_recv() {
match msg {
MailboxMessage::Envelope { payload, reply_channel, actor_ref } => {
process_envelope!(
actor_id = actor_id,
actor = actor,
payload = payload,
reply_channel = reply_channel,
actor_ref = actor_ref,
span_name = "actor_drain_priority_message",
guard = PriorityMessageProcessingGuard,
);
}
MailboxMessage::StopGracefully(_) => {
debug_assert!(
false,
"MailboxMessage::StopGracefully observed during \
priority drain; priority channel must only \
carry Envelope variants"
);
}
}
}
}
#[cfg(feature = "tracing")]
let on_stop_span = tracing::debug_span!("actor_on_stop", killed = false);
#[cfg(not(feature = "tracing"))]
let on_stop_span = tracing::Span::none();
if let Err(e) = run_with_actor_scope!(
actor_id,
actor.on_stop(&actor_weak, false).instrument(on_stop_span)
) {
error!("Actor {actor_id} on_stop failed during graceful stop: {e:?}");
return ActorResult::Failed {
actor: Some(actor),
error: e,
secondary_error: None,
phase: FailurePhase::OnStop,
killed: false,
};
}
break;
}
}
}
Some(event) = idle_streams.next(), if !idle_streams.is_empty() => {
#[cfg(feature = "tracing")]
let on_idle_span = tracing::debug_span!("actor_on_idle");
#[cfg(not(feature = "tracing"))]
let on_idle_span = tracing::Span::none();
let result = run_with_actor_scope!(
actor_id,
actor.on_idle(event, &actor_weak).instrument(on_idle_span)
);
if let Err(e) = result {
error!("Actor {actor_id} on_idle error: {e:?}");
#[cfg(feature = "tracing")]
let on_stop_span = tracing::debug_span!("actor_on_stop", killed = false);
#[cfg(not(feature = "tracing"))]
let on_stop_span = tracing::Span::none();
let (phase, secondary_error) = match run_with_actor_scope!(
actor_id,
actor.on_stop(&actor_weak, false).instrument(on_stop_span)
) {
Err(stop_err) => {
error!(
"Actor {actor_id} on_stop failed during on_idle error cleanup: {stop_err:?}"
);
(FailurePhase::OnIdleThenOnStop, Some(stop_err))
}
Ok(()) => (FailurePhase::OnIdle, None),
};
return ActorResult::Failed {
actor: Some(actor),
error: e,
secondary_error,
phase,
killed,
};
}
}
}
}
receiver.close(); terminate_receiver.close(); if let Some(rx) = priority_receiver.as_mut() {
rx.close(); }
if let Some(rx) = idle_subscribe_receiver.as_mut() {
rx.close(); }
debug!("Actor {actor_id} lifecycle completed - exiting runtime loop.");
ActorResult::Completed { actor, killed }
}