use aetherflow::{
Actor, ActorRef, AskError, Responder, RestartPolicy, SendError, System, TrySendError,
};
use std::sync::mpsc;
use std::time::Duration;
#[test]
fn ask_from_handler_to_same_core_returns_error_not_hang() {
struct Callee;
impl Actor for Callee {
type Message = Responder<u64>;
fn handle(&mut self, r: Responder<u64>) {
r.reply(1);
}
}
struct Caller {
callee: ActorRef<Callee>,
out: mpsc::Sender<Result<u64, AskError>>,
}
impl Actor for Caller {
type Message = ();
fn handle(&mut self, _m: ()) {
let r = self.callee.ask(|resp| resp);
self.out.send(r).unwrap();
}
}
let sys = System::with_cores(1);
let callee = sys.spawn_on(0, Callee);
let (out, rx) = mpsc::channel();
let caller = sys.spawn_on(0, Caller {
callee: callee.clone(),
out,
});
caller.send_blocking(()).unwrap(); assert_eq!(rx.recv().unwrap(), Err(AskError::WouldBlockCallingCore));
drop(caller);
drop(callee);
sys.shutdown();
}
#[test]
fn panic_in_one_actor_does_not_kill_others_on_same_core() {
struct Bomb;
impl Actor for Bomb {
type Message = bool;
fn handle(&mut self, boom: bool) {
if boom {
panic!("boom"); }
}
}
struct Survivor {
out: mpsc::Sender<u32>,
}
impl Actor for Survivor {
type Message = u32;
fn handle(&mut self, m: u32) {
self.out.send(m).unwrap();
}
}
let sys = System::with_cores(1);
let bomb = sys.spawn_on(0, Bomb);
let (out, rx) = mpsc::channel();
let survivor = sys.spawn_on(0, Survivor { out });
bomb.send_blocking(true).unwrap(); survivor.send_blocking(42).unwrap(); assert_eq!(rx.recv().unwrap(), 42);
drop(bomb);
drop(survivor);
sys.shutdown();
}
#[test]
fn send_blocking_to_dead_actor_returns_closed_not_hang() {
struct Bomb;
impl Actor for Bomb {
type Message = ();
fn handle(&mut self, _m: ()) {
panic!("die"); }
}
let sys = System::with_cores(1);
let addr = sys.spawn_on(0, Bomb);
addr.send_blocking(()).unwrap(); loop {
match addr.try_send(()) {
Err(TrySendError::Closed(_)) => break,
_ => std::hint::spin_loop(),
}
}
assert!(matches!(addr.send_blocking(()), Err(SendError::Closed(_))));
drop(addr);
sys.shutdown();
}
#[test]
fn panic_in_on_start_does_not_kill_core() {
struct StartBomb;
impl Actor for StartBomb {
type Message = ();
fn on_start(&mut self) {
panic!("on_start boom"); }
fn handle(&mut self, _m: ()) {}
}
struct Survivor {
out: mpsc::Sender<u32>,
}
impl Actor for Survivor {
type Message = u32;
fn handle(&mut self, m: u32) {
self.out.send(m).unwrap();
}
}
let sys = System::with_cores(1);
let bomb = sys.spawn_on(0, StartBomb); let (out, rx) = mpsc::channel();
let survivor = sys.spawn_on(0, Survivor { out });
survivor.send_blocking(7).unwrap(); assert_eq!(rx.recv().unwrap(), 7);
drop(bomb);
drop(survivor);
sys.shutdown();
}
#[test]
fn instrumented_builder_records_latency() {
struct Work;
impl Actor for Work {
type Message = u64;
fn handle(&mut self, _m: u64) {}
}
let sys = System::with_cores(2);
let addr = sys.build(|| Work).core(0).instrumented().spawn();
assert!(addr.latency().is_some());
const N: usize = 5_000;
for i in 0..N {
addr.send_blocking(i as u64).unwrap();
}
while addr.total_processed() < N {
std::hint::spin_loop();
}
while addr.latency().unwrap().count < N as u64 {
std::hint::spin_loop();
}
let snap = addr.latency().unwrap();
assert_eq!(snap.count, N as u64);
let plain = sys.spawn_on(1, Work);
assert!(plain.latency().is_none());
drop(addr);
drop(plain);
sys.shutdown();
}
#[test]
fn observability_counters() {
struct Nop;
impl Actor for Nop {
type Message = u64;
fn handle(&mut self, _m: u64) {}
}
let sys = System::with_cores(1);
let addr = sys.spawn_on(0, Nop);
const N: usize = 10_000;
for i in 0..N {
addr.send_blocking(i as u64).unwrap();
}
assert_eq!(addr.total_sent(), N);
while addr.total_processed() < N {
std::hint::spin_loop();
}
assert_eq!(addr.total_processed(), N);
assert_eq!(addr.mailbox_depth(), 0);
drop(addr);
sys.shutdown();
}
#[test]
fn supervised_actor_restarts_after_panic() {
struct Counter {
n: u64,
out: mpsc::Sender<u64>,
}
impl Actor for Counter {
type Message = bool; fn handle(&mut self, boom: bool) {
if boom {
panic!("boom");
}
self.n += 1;
self.out.send(self.n).unwrap();
}
}
let sys = System::with_cores(1);
let (out, rx) = mpsc::channel();
let addr = sys.spawn_on_supervised(0, move || Counter { n: 0, out: out.clone() });
addr.send_blocking(false).unwrap(); addr.send_blocking(false).unwrap(); assert_eq!(rx.recv().unwrap(), 1);
assert_eq!(rx.recv().unwrap(), 2);
addr.send_blocking(true).unwrap(); addr.send_blocking(false).unwrap(); assert_eq!(rx.recv().unwrap(), 1);
drop(addr);
sys.shutdown();
}
#[test]
fn supervised_restart_limit_stops_after_exceeding() {
struct AlwaysPanic;
impl Actor for AlwaysPanic {
type Message = ();
fn handle(&mut self, _m: ()) {
panic!("always"); }
}
let sys = System::with_cores(1);
let addr = sys
.build(|| AlwaysPanic)
.core(0)
.restart_policy(RestartPolicy::Limited {
max_restarts: 2,
within: Duration::from_secs(10),
})
.spawn();
for _ in 0..5 {
let _ = addr.try_send(()); }
loop {
match addr.try_send(()) {
Err(TrySendError::Closed(_)) => break,
_ => std::hint::spin_loop(),
}
}
drop(addr);
sys.shutdown();
}
#[test]
fn ask_to_panicking_actor_returns_no_reply() {
struct Panicker;
impl Actor for Panicker {
type Message = Responder<u64>;
fn handle(&mut self, _resp: Responder<u64>) {
panic!("nope");
}
}
let sys = System::with_cores(1);
let addr = sys.spawn_on(0, Panicker);
assert_eq!(addr.ask(|resp| resp), Err(AskError::NoReply));
drop(addr);
sys.shutdown();
}
#[test]
fn stateful_actor_processes_in_order() {
struct Counter {
total: u64,
out: mpsc::Sender<u64>,
}
impl Actor for Counter {
type Message = u64;
fn handle(&mut self, msg: u64) {
self.total += msg;
self.out.send(self.total).unwrap();
}
}
let sys = System::with_cores(1);
let (out, results) = mpsc::channel();
let addr = sys.spawn_on(0, Counter { total: 0, out });
for i in 1..=100u64 {
addr.send_blocking(i).unwrap();
}
drop(addr);
sys.shutdown();
let mut expected = 0u64;
for i in 1..=100u64 {
expected += i;
assert_eq!(results.recv().unwrap(), expected);
}
assert!(results.recv().is_err());
}
#[test]
fn lifecycle_hooks_fire_once() {
struct L {
out: mpsc::Sender<&'static str>,
}
impl Actor for L {
type Message = ();
fn handle(&mut self, _msg: ()) {
self.out.send("handle").unwrap();
}
fn on_start(&mut self) {
self.out.send("start").unwrap();
}
fn on_stop(&mut self) {
self.out.send("stop").unwrap();
}
}
let sys = System::with_cores(1);
let (out, ev) = mpsc::channel();
let addr = sys.spawn_on(0, L { out });
addr.send_blocking(()).unwrap();
drop(addr);
sys.shutdown();
assert_eq!(ev.recv().unwrap(), "start");
assert_eq!(ev.recv().unwrap(), "handle");
assert_eq!(ev.recv().unwrap(), "stop");
}
#[test]
fn moves_owned_non_copy_messages() {
struct Sink {
out: mpsc::Sender<String>,
}
impl Actor for Sink {
type Message = String;
fn handle(&mut self, msg: String) {
self.out.send(msg).unwrap();
}
}
let sys = System::with_cores(1);
let (out, results) = mpsc::channel();
let addr = sys.spawn_on(0, Sink { out });
addr.send_blocking(String::from("owned")).unwrap();
addr.send_blocking(String::from("moved")).unwrap();
drop(addr);
sys.shutdown();
assert_eq!(results.recv().unwrap(), "owned");
assert_eq!(results.recv().unwrap(), "moved");
}
#[test]
fn cross_core_routing() {
let sys = System::with_cores(4);
struct Aggregator {
remaining: u32,
total: u64,
out: mpsc::Sender<u64>,
}
impl Actor for Aggregator {
type Message = u64;
fn handle(&mut self, partial: u64) {
self.total += partial;
self.remaining -= 1;
if self.remaining == 0 {
self.out.send(self.total).unwrap();
}
}
}
struct Worker {
agg: aetherflow::ActorRef<Aggregator>,
}
impl Actor for Worker {
type Message = u64;
fn handle(&mut self, n: u64) {
let _ = self.agg.try_send(n * 2);
}
}
let (out, result) = mpsc::channel();
let agg = sys.spawn_on(0, Aggregator {
remaining: 12,
total: 0,
out,
});
let workers: Vec<_> = (0..3)
.map(|k| sys.spawn_on(k + 1, Worker { agg: agg.clone() }))
.collect();
for w in &workers {
for n in 1..=4u64 {
w.send_blocking(n).unwrap();
}
}
let total = result.recv().unwrap();
assert_eq!(total, 60);
drop(workers);
drop(agg);
sys.shutdown();
}
#[test]
fn round_robin_placement_runs() {
struct Echo {
out: mpsc::Sender<u32>,
}
impl Actor for Echo {
type Message = u32;
fn handle(&mut self, m: u32) {
self.out.send(m).unwrap();
}
}
let sys = System::with_cores(3);
let (out, results) = mpsc::channel();
let addrs: Vec<_> = (0..9).map(|_| sys.spawn(Echo { out: out.clone() })).collect();
for (i, a) in addrs.iter().enumerate() {
a.send_blocking(i as u32).unwrap();
}
drop(addrs);
drop(out);
sys.shutdown();
let mut got: Vec<u32> = results.iter().collect();
got.sort_unstable();
assert_eq!(got, (0..9).collect::<Vec<_>>());
}