use crate::Identity;
use std::time::Duration;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Error {
Send {
identity: Identity,
details: &'static str,
},
ChannelFull {
identity: Identity,
channel: &'static str,
},
Receive {
identity: Identity,
details: &'static str,
},
Timeout {
identity: Identity,
timeout: Duration,
operation: &'static str,
},
Downcast {
identity: Identity,
expected_type: &'static str,
},
Runtime {
identity: Identity,
details: String,
source: Option<std::sync::Arc<dyn std::error::Error + Send + Sync>>,
},
MailboxCapacity {
message: &'static str,
},
Join {
identity: Identity,
source: std::sync::Arc<tokio::task::JoinError>,
},
PriorityChannelNotEnabled {
identity: Identity,
},
IdleChannelNotEnabled {
identity: Identity,
},
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Send { identity, details } => {
write!(f, "Failed to send message to actor {identity}: {details}")
}
Error::ChannelFull { identity, channel } => {
write!(
f,
"Bounded channel '{channel}' for actor {identity} is at capacity"
)
}
Error::Receive { identity, details } => {
write!(
f,
"Failed to receive reply from actor {identity}: {details}"
)
}
Error::Timeout {
identity,
timeout,
operation,
} => {
write!(
f,
"{operation} operation to actor {identity} timed out after {timeout:?}"
)
}
Error::Downcast {
identity,
expected_type,
} => {
write!(
f,
"Failed to downcast reply from actor {identity} to expected type '{expected_type}'"
)
}
Error::Runtime {
identity, details, ..
} => {
write!(f, "Runtime error in actor {identity}: {details}")
}
Error::MailboxCapacity { message } => {
write!(f, "Mailbox capacity error: {message}")
}
Error::Join { identity, source } => {
write!(
f,
"Failed to join spawned task from actor {identity}: {source}"
)
}
Error::PriorityChannelNotEnabled { identity } => {
write!(
f,
"Priority channel is not enabled for actor {identity}: enable it via SpawnOptions::with_priority()"
)
}
Error::IdleChannelNotEnabled { identity } => {
write!(
f,
"Idle channel is not enabled for actor {identity}: enable it via SpawnOptions::with_idle()"
)
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Join { source, .. } => Some(source.as_ref()),
Error::Runtime {
source: Some(source),
..
} => Some(source.as_ref()),
_ => None,
}
}
}
impl Error {
#[must_use]
pub fn is_retryable(&self) -> bool {
matches!(self, Error::Timeout { .. } | Error::ChannelFull { .. })
}
#[must_use]
pub fn debugging_tips(&self) -> &'static [&'static str] {
match self {
Error::Send { .. } => &[
"Verify the actor is still running with `actor_ref.is_alive()`",
"The actor's mailbox is closed - the actor has terminated",
"Consider using `ActorWeak` for long-lived references",
],
Error::ChannelFull { .. } => &[
"Transient failure - the actor is alive and its runtime loop drains the buffer",
"Retry only from OUTSIDE the target actor: the buffer drains between handler \
invocations, so a retry loop inside the actor's own on_start/handler spins \
forever and cannot be kill()ed",
"For subscribe_idle specifically, spawn with SpawnOptions::with_idle_capacity(n) \
if you regularly fan out more than 32 streams, merge sources into one stream \
(futures::stream::select_all), or batch subscriptions across handler invocations",
"The rejected stream is returned in IdleSubscribeError - reuse it for the retry",
],
Error::Receive { .. } => &[
"The actor dropped the reply channel before responding",
"Check if the message handler panicked or returned early",
"Verify the handler correctly awaits async operations",
],
Error::Timeout { .. } => &[
"Consider increasing the timeout duration",
"Check if the actor is processing a slow operation",
"Verify there's no deadlock in the message handler",
"Use `tell` instead if you don't need a response",
],
Error::Downcast { .. } => &[
"The handler returned a different type than expected",
"Verify the Message trait impl returns correct Reply type",
"This usually indicates a bug in handler implementation",
],
Error::Runtime { .. } => &[
"Emitted when a blocking_* call fails to build or run its dedicated temporary runtime",
"Match on `Error::Runtime { source, .. }` and inspect `source` for the underlying cause (e.g. std::io::Error)",
"This usually indicates OS resource exhaustion (unable to spawn a thread or build a runtime)",
"Prefer calling blocking_* from within an existing multi-thread runtime, or use the async ask()/tell() APIs",
],
Error::MailboxCapacity { .. } => &[
"Mailbox capacity must be greater than 0",
"set_default_mailbox_capacity() can only be called once",
"Call it early in main() before spawning actors",
],
Error::Join { .. } => &[
"The spawned task panicked or was cancelled by the runtime",
"Run with RUST_BACKTRACE=1 or RUST_BACKTRACE=full for panic details",
"Match on `Error::Join { source, .. }` and use `source.is_panic()` / `source.is_cancelled()` to distinguish the cause",
"Check for unwrap(), expect(), or panic!() calls in actor code",
"Verify tokio runtime wasn't shut down while actor was running",
],
Error::PriorityChannelNotEnabled { .. } => &[
"Spawn the actor with SpawnOptions::new().with_priority() via spawn_with_options()",
"Use ActorRef::has_priority_channel() to check before sending priority messages",
"If priority is not required, use the regular tell()/ask() methods instead",
],
Error::IdleChannelNotEnabled { .. } => &[
"Spawn the actor with SpawnOptions::new().with_idle() via spawn_with_options()",
"Use ActorRef::has_idle_channel() to check before calling subscribe_idle()",
"on_idle is only driven when the idle channel is enabled",
],
}
}
}
pub type Result<T> = std::result::Result<T, Error>;