use crate::{mpsc, pinning, Actor, SendError, TrySendError};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc as ctrl; use std::thread;
use std::time::{Duration, Instant};
const DEFAULT_MAILBOX_CAPACITY: usize = 1024;
thread_local! {
static CURRENT_CORE: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
}
pub(crate) fn current_core() -> Option<usize> {
CURRENT_CORE.with(|c| c.get())
}
#[derive(Clone, Copy, Debug, Default)]
pub enum IdleStrategy {
#[default]
BusySpin,
Backoff {
spins: u32,
yields: u32,
park: Duration,
},
}
impl IdleStrategy {
pub fn backoff() -> Self {
IdleStrategy::Backoff {
spins: 128,
yields: 128,
park: Duration::from_micros(50),
}
}
#[inline]
fn idle(&self, count: u32) {
match *self {
IdleStrategy::BusySpin => std::hint::spin_loop(),
IdleStrategy::Backoff {
spins,
yields,
park,
} => {
if count < spins {
std::hint::spin_loop();
} else if count < spins + yields {
std::thread::yield_now();
} else {
std::thread::sleep(park);
}
}
}
}
}
enum PollOutcome {
Worked,
Empty,
Panicked,
}
enum LifecycleOutcome {
Ok,
Panicked,
}
trait ErasedActor: Send {
fn start(&mut self) -> LifecycleOutcome;
fn poll_one(&mut self) -> PollOutcome;
fn closed_and_empty(&mut self) -> bool;
fn stop(&mut self) -> LifecycleOutcome;
}
#[derive(Clone, Copy, Debug)]
pub enum RestartPolicy {
Never,
Limited { max_restarts: u32, within: Duration },
Always,
}
impl RestartPolicy {
pub fn default_supervised() -> Self {
RestartPolicy::Limited {
max_restarts: 5,
within: Duration::from_secs(10),
}
}
}
#[derive(Default)]
struct RestartState {
window_start: Option<Instant>,
count: u32,
}
struct Cell<A: Actor> {
actor: A,
rx: mpsc::Receiver<A::Message>,
factory: Option<Box<dyn Fn() -> A + Send>>,
restart_policy: RestartPolicy,
restarts: RestartState,
metrics: Option<std::sync::Arc<crate::metrics::LatencyHistogram>>,
}
impl<A: Actor> Cell<A> {
fn allow_restart(&mut self) -> bool {
match self.restart_policy {
RestartPolicy::Never => false,
RestartPolicy::Always => true,
RestartPolicy::Limited { max_restarts, within } => {
let now = Instant::now();
match self.restarts.window_start {
Some(start) if now.duration_since(start) <= within => {
self.restarts.count += 1;
}
_ => {
self.restarts.window_start = Some(now);
self.restarts.count = 1;
}
}
self.restarts.count <= max_restarts
}
}
}
}
impl<A: Actor> ErasedActor for Cell<A> {
fn start(&mut self) -> LifecycleOutcome {
let actor = &mut self.actor;
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| actor.on_start())) {
Ok(()) => LifecycleOutcome::Ok,
Err(_) => LifecycleOutcome::Panicked,
}
}
fn poll_one(&mut self) -> PollOutcome {
match self.rx.try_recv() {
Some(msg) => {
let started = self.metrics.as_ref().map(|_| std::time::Instant::now());
let actor = &mut self.actor;
let result =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| actor.handle(msg)));
if let (Some(m), Some(t0)) = (&self.metrics, started) {
m.record(t0.elapsed().as_nanos() as u64);
}
match result {
Ok(()) => PollOutcome::Worked,
Err(_) => {
if self.factory.is_some() && self.allow_restart() {
let make = self.factory.as_ref().unwrap();
let rebuilt = std::panic::catch_unwind(std::panic::AssertUnwindSafe(
|| {
let mut a = make();
a.on_restart();
a
},
));
match rebuilt {
Ok(a) => {
self.actor = a;
PollOutcome::Worked
}
Err(_) => PollOutcome::Panicked,
}
} else {
PollOutcome::Panicked
}
}
}
}
None => PollOutcome::Empty,
}
}
fn closed_and_empty(&mut self) -> bool {
!self.rx.producers_alive() && self.rx.is_empty()
}
fn stop(&mut self) -> LifecycleOutcome {
let actor = &mut self.actor;
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| actor.on_stop())) {
Ok(()) => LifecycleOutcome::Ok,
Err(_) => LifecycleOutcome::Panicked,
}
}
}
enum Control {
Add(Box<dyn ErasedActor>),
Shutdown,
}
struct CoreHandle {
control: ctrl::Sender<Control>,
join: Option<thread::JoinHandle<()>>,
}
pub struct System {
cores: Vec<CoreHandle>,
next: AtomicUsize, }
impl System {
pub fn with_cores(n: usize) -> System {
System::with_cores_idle(n, IdleStrategy::default())
}
pub fn with_cores_idle(n: usize, idle: IdleStrategy) -> System {
assert!(n >= 1, "System needs at least 1 core");
let mut cores = Vec::with_capacity(n);
for core_index in 0..n {
let (control, rx) = ctrl::channel::<Control>();
let join = thread::Builder::new()
.name(format!("aether-core-{core_index}"))
.spawn(move || core_loop(core_index, rx, idle))
.expect("spawn core thread");
cores.push(CoreHandle {
control,
join: Some(join),
});
}
System {
cores,
next: AtomicUsize::new(0),
}
}
pub fn num_cores(&self) -> usize {
self.cores.len()
}
pub fn spawn_on<A: Actor>(&self, core: usize, actor: A) -> ActorRef<A> {
self.spawn_on_with(core, actor, DEFAULT_MAILBOX_CAPACITY)
}
pub fn spawn_on_with<A: Actor>(
&self,
core: usize,
actor: A,
mailbox_capacity: usize,
) -> ActorRef<A> {
self.install(core, mailbox_capacity, actor, None, RestartPolicy::Never, None)
}
pub fn spawn<A: Actor>(&self, actor: A) -> ActorRef<A> {
let core = self.next.fetch_add(1, Ordering::Relaxed) % self.cores.len();
self.spawn_on(core, actor)
}
pub fn spawn_on_supervised<A: Actor>(
&self,
core: usize,
make: impl Fn() -> A + Send + 'static,
) -> ActorRef<A> {
self.spawn_on_supervised_with(core, make, DEFAULT_MAILBOX_CAPACITY)
}
pub fn spawn_on_supervised_with<A: Actor>(
&self,
core: usize,
make: impl Fn() -> A + Send + 'static,
mailbox_capacity: usize,
) -> ActorRef<A> {
let actor = make();
self.install(
core,
mailbox_capacity,
actor,
Some(Box::new(make)),
RestartPolicy::default_supervised(),
None,
)
}
pub fn build<A: Actor, F: Fn() -> A + Send + 'static>(&self, make: F) -> SpawnBuilder<'_, A, F> {
SpawnBuilder {
sys: self,
make,
core: None,
mailbox: DEFAULT_MAILBOX_CAPACITY,
supervised: false,
restart_policy: None,
instrumented: false,
}
}
fn install<A: Actor>(
&self,
core: usize,
mailbox: usize,
actor: A,
factory: Option<Box<dyn Fn() -> A + Send>>,
restart_policy: RestartPolicy,
metrics: Option<std::sync::Arc<crate::metrics::LatencyHistogram>>,
) -> ActorRef<A> {
assert!(core < self.cores.len(), "core index {core} out of range");
let (tx, rx) = mpsc::channel::<A::Message>(mailbox);
let cell: Box<dyn ErasedActor> = Box::new(Cell {
actor,
rx,
factory,
restart_policy,
restarts: RestartState::default(),
metrics: metrics.clone(),
});
self.cores[core]
.control
.send(Control::Add(cell))
.unwrap_or_else(|_| panic!("core {core} thread is not running"));
ActorRef { tx, metrics, core }
}
pub fn shutdown(self) {
}
}
impl Drop for System {
fn drop(&mut self) {
for c in &self.cores {
let _ = c.control.send(Control::Shutdown);
}
for c in &mut self.cores {
if let Some(j) = c.join.take() {
let _ = j.join();
}
}
}
}
fn core_loop(core_index: usize, control: ctrl::Receiver<Control>, idle: IdleStrategy) {
pinning::pin_current_thread_to(core_index); CURRENT_CORE.with(|c| c.set(Some(core_index)));
let mut actors: Vec<Box<dyn ErasedActor>> = Vec::new();
let mut shutting_down = false;
let mut idle_count: u32 = 0;
loop {
let mut ctrl_activity = false;
loop {
match control.try_recv() {
Ok(Control::Add(mut cell)) => {
match cell.start() {
LifecycleOutcome::Ok => actors.push(cell),
LifecycleOutcome::Panicked => drop(cell),
}
ctrl_activity = true;
}
Ok(Control::Shutdown) => shutting_down = true,
Err(ctrl::TryRecvError::Empty) => break,
Err(ctrl::TryRecvError::Disconnected) => {
shutting_down = true;
break;
}
}
}
let mut did_work = false;
let mut i = 0;
while i < actors.len() {
match actors[i].poll_one() {
PollOutcome::Worked => {
did_work = true;
i += 1;
}
PollOutcome::Panicked => {
drop(actors.swap_remove(i));
did_work = true; }
PollOutcome::Empty => {
if actors[i].closed_and_empty() {
let mut cell = actors.swap_remove(i);
let _ = cell.stop();
} else {
i += 1;
}
}
}
}
if shutting_down {
for mut cell in actors.drain(..) {
let mut panicked = false;
loop {
match cell.poll_one() {
PollOutcome::Worked => {}
PollOutcome::Empty => break,
PollOutcome::Panicked => {
panicked = true;
break;
}
}
}
if !panicked {
let _ = cell.stop();
}
}
break;
}
if did_work || ctrl_activity {
idle_count = 0;
} else {
idle.idle(idle_count);
idle_count = idle_count.saturating_add(1);
}
}
}
pub struct ActorRef<A: Actor> {
tx: mpsc::Sender<A::Message>,
metrics: Option<std::sync::Arc<crate::metrics::LatencyHistogram>>,
core: usize,
}
impl<A: Actor> ActorRef<A> {
pub(crate) fn would_deadlock_calling_core(&self) -> bool {
current_core() == Some(self.core)
}
}
impl<A: Actor> ActorRef<A> {
pub fn try_send(&self, msg: A::Message) -> Result<(), TrySendError<A::Message>> {
self.tx.try_send(msg)
}
pub fn send_blocking(&self, msg: A::Message) -> Result<(), SendError<A::Message>> {
assert!(
!self.would_deadlock_calling_core(),
"send_blocking from within a handler to a same-core actor would deadlock; use try_send"
);
let mut item = msg;
loop {
match self.tx.try_send(item) {
Ok(()) => return Ok(()),
Err(TrySendError::Full(returned)) => {
item = returned;
std::hint::spin_loop();
}
Err(TrySendError::Closed(returned)) => return Err(SendError::Closed(returned)),
}
}
}
pub fn mailbox_depth(&self) -> usize {
self.tx.depth()
}
pub fn total_sent(&self) -> usize {
self.tx.total_enqueued()
}
pub fn total_processed(&self) -> usize {
self.tx.total_dequeued()
}
pub fn latency(&self) -> Option<crate::metrics::LatencySnapshot> {
self.metrics.as_ref().map(|m| m.snapshot())
}
}
impl<A: Actor> Clone for ActorRef<A> {
fn clone(&self) -> Self {
ActorRef {
tx: self.tx.clone(),
metrics: self.metrics.clone(),
core: self.core,
}
}
}
pub struct SpawnBuilder<'s, A: Actor, F: Fn() -> A + Send + 'static> {
sys: &'s System,
make: F,
core: Option<usize>,
mailbox: usize,
supervised: bool,
restart_policy: Option<RestartPolicy>,
instrumented: bool,
}
impl<'s, A: Actor, F: Fn() -> A + Send + 'static> SpawnBuilder<'s, A, F> {
pub fn core(mut self, core: usize) -> Self {
self.core = Some(core);
self
}
pub fn mailbox(mut self, capacity: usize) -> Self {
self.mailbox = capacity;
self
}
pub fn supervised(mut self) -> Self {
self.supervised = true;
self
}
pub fn restart_policy(mut self, policy: RestartPolicy) -> Self {
self.supervised = true;
self.restart_policy = Some(policy);
self
}
pub fn instrumented(mut self) -> Self {
self.instrumented = true;
self
}
pub fn spawn(self) -> ActorRef<A> {
let core = self
.core
.unwrap_or_else(|| self.sys.next.fetch_add(1, Ordering::Relaxed) % self.sys.cores.len());
let metrics = if self.instrumented {
Some(std::sync::Arc::new(crate::metrics::LatencyHistogram::new()))
} else {
None
};
let actor = (self.make)();
let (factory, restart_policy): (Option<Box<dyn Fn() -> A + Send>>, RestartPolicy) =
if self.supervised {
(
Some(Box::new(self.make)),
self.restart_policy
.unwrap_or_else(RestartPolicy::default_supervised),
)
} else {
(None, RestartPolicy::Never)
};
self.sys
.install(core, self.mailbox, actor, factory, restart_policy, metrics)
}
}