//! gen_server lifetime is the actor's, not its refs' (OTP: a pid is an //! address, a process lives until it stops, is shut down, or is killed). //! //! - Dropping the last `GenServerRef` does NOT end the server. It ends via //! `StopHandle::stop`, `request_shutdown` / `GenServerRef::shutdown`, //! `request_stop`, or a handler panic. //! - `GenServerBuilder::named(N).run()` runs the loop inline as the *current* //! actor, so a server is a direct `ChildSpec` child: the supervisor's //! shutdown reaches it as `handle_shutdown`, a restart re-binds the name, //! and by-name `call`/`cast` reach whichever incarnation is live. use smarm::gen_server::{ self, GenServer, GenServerBuilder, GenServerCtx, GenServerName, ShutdownAction, StopHandle, }; use smarm::registry::RegisterError; use smarm::supervisor::{ChildSpec, OneForOne, Restart, Shutdown}; use smarm::{monitor, request_shutdown, run, sleep, spawn, DownReason}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; #[derive(Default, Clone)] struct Log(Arc>>); impl Log { fn push(&self, s: impl Into) { self.0.lock().unwrap().push(s.into()); } fn get(&self) -> Vec { self.0.lock().unwrap().clone() } } struct Counter { log: Log, n: u64, trap: bool, stop: Option>, } enum Call { Get, } enum Cast { Inc, Stop, } impl GenServer for Counter { type Call = Call; type Reply = u64; type Cast = Cast; type Info = (); type Timer = (); fn init(&mut self, ctx: &GenServerCtx) { if self.trap { ctx.trap_exit(); } self.stop = Some(ctx.stop_handle()); self.log.push("init"); } fn handle_call(&mut self, Call::Get: Call) -> u64 { self.n } fn handle_cast(&mut self, c: Cast) { match c { Cast::Inc => self.n += 1, Cast::Stop => self.stop.as_ref().unwrap().stop(), } } fn handle_shutdown(&mut self) -> ShutdownAction { self.log.push("handle_shutdown"); ShutdownAction::Exit } fn terminate(&mut self) { self.log.push("terminate"); } } fn counter(log: &Log, trap: bool) -> Counter { Counter { log: log.clone(), n: 0, trap, stop: None, } } // --------------------------------------------------------------------------- // Refs are addresses: dropping the last one does not end the server. // --------------------------------------------------------------------------- #[test] fn dropping_last_ref_does_not_end_server() { let log = Log::default(); let l = log.clone(); run(move || { let srv = gen_server::start(counter(&l, true)); let pid = srv.pid(); srv.cast(Cast::Inc).unwrap(); assert_eq!(srv.call(Call::Get).unwrap(), 1); let mon = monitor(pid); drop(srv); sleep(Duration::from_millis(30)); assert!( mon.rx.try_recv().unwrap().is_none(), "server must outlive its last ref" ); assert_eq!(l.get(), vec!["init"], "terminate must not have run"); // Explicit teardown still works, and is what ends it. request_shutdown(pid); let down = mon.rx.recv().unwrap(); assert_eq!(down.reason, DownReason::Exit); }); assert_eq!(log.get(), vec!["init", "handle_shutdown", "terminate"]); } #[test] fn ref_shutdown_is_the_explicit_close() { let log = Log::default(); let l = log.clone(); run(move || { let srv = gen_server::start(counter(&l, true)); srv.call(Call::Get).unwrap(); // sync: init (and trap_exit) has run srv.shutdown(); // graceful, waits assert_eq!(l.get(), vec!["init", "handle_shutdown", "terminate"]); }); } #[test] fn forgotten_server_is_shut_down_at_root_exit() { // A ref-less server is not a hung run: root exit shuts it down. let log = Log::default(); let l = log.clone(); run(move || { let srv = gen_server::start(counter(&l, false)); drop(srv); }); assert_eq!(log.get(), vec!["init", "terminate"]); } // --------------------------------------------------------------------------- // Inline run: a gen_server as a direct ChildSpec child. // --------------------------------------------------------------------------- const COUNTER: GenServerName = GenServerName::new("lifetime-counter"); #[test] fn named_run_is_a_direct_supervised_child_and_gets_shutdown() { let log = Log::default(); let l = log.clone(); run(move || { let l2 = l.clone(); let sup = spawn(move || { let l3 = l2.clone(); OneForOne::new() .child( ChildSpec::new(Restart::Permanent, move || { GenServerBuilder::new(counter(&l3, true)) .named(COUNTER) .run() .expect("name free"); }) .shutdown(Shutdown::Infinity), ) .run(); }); sleep(Duration::from_millis(10)); gen_server::cast(COUNTER, Cast::Inc).unwrap(); assert_eq!(gen_server::call(COUNTER, Call::Get).unwrap(), 1); request_shutdown(sup.pid()); sup.join() .expect("ordered shutdown, supervisor returns normally"); assert_eq!(l.get(), vec!["init", "handle_shutdown", "terminate"]); assert!(gen_server::whereis_server(COUNTER).is_none()); }); } #[test] fn named_run_child_restarts_and_rebinds_name() { let log = Log::default(); let inits = Arc::new(AtomicUsize::new(0)); let l = log.clone(); let i = inits.clone(); run(move || { let l2 = l.clone(); let i2 = i.clone(); let sup = spawn(move || { let l3 = l2.clone(); let i3 = i2.clone(); OneForOne::new() .child(ChildSpec::new(Restart::Permanent, move || { i3.fetch_add(1, Ordering::SeqCst); GenServerBuilder::new(counter(&l3, false)) .named(COUNTER) .run() .expect("name free on (re)start"); })) .run(); }); sleep(Duration::from_millis(10)); gen_server::cast(COUNTER, Cast::Inc).unwrap(); assert_eq!(gen_server::call(COUNTER, Call::Get).unwrap(), 1); // Normal self-exit → Permanent restarts it, fresh state, same name. gen_server::cast(COUNTER, Cast::Stop).unwrap(); sleep(Duration::from_millis(30)); assert_eq!(i.load(Ordering::SeqCst), 2, "restarted once"); assert_eq!(gen_server::call(COUNTER, Call::Get).unwrap(), 0); request_shutdown(sup.pid()); sup.join().unwrap(); }); assert_eq!(log.get(), vec!["init", "terminate", "init", "terminate"]); } #[test] fn named_run_name_clash_fails_before_init() { let log = Log::default(); let l = log.clone(); run(move || { let first = GenServerBuilder::new(counter(&l, false)) .named(COUNTER) .start() .unwrap(); let l2 = l.clone(); let res = Arc::new(Mutex::new(None)); let r2 = res.clone(); let first_pid = first.pid(); spawn(move || { let r = GenServerBuilder::new(counter(&l2, false)) .named(COUNTER) .run(); *r2.lock().unwrap() = Some(r); }) .join() .unwrap(); assert_eq!( *res.lock().unwrap(), Some(Err(RegisterError::NameTaken { holder: first_pid })) ); assert_eq!(l.get(), vec!["init"], "clashing server never ran init"); first.shutdown(); }); }