1#![cfg_attr(not(feature = "udp"), allow(dead_code, unused_imports, unused_mut))]
5
6use crate::event_loop::{build, ListenerSlot, LoopHandle, LoopSetup, UdpSlot};
7use crate::handler::{Acceptor, UdpService};
8use crate::offload::{OffloadConfig, OffloadPool};
9use crate::pool::{PoolStats, DEFAULT_CLASSES};
10use simoom_sys::driver::{Backend, DriverConfig, DriverStats};
11use simoom_sys::net::{ListenOptions, Socket, UdpOptions};
12use std::io;
13use std::net::{SocketAddr, ToSocketAddrs};
14use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
15use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock, PoisonError};
16use std::thread::JoinHandle;
17use std::time::Duration;
18
19#[derive(Clone, Debug)]
21pub struct Config {
22 pub threads: usize,
24 pub pin_threads: bool,
26 pub cpus: Option<Vec<usize>>,
28 pub driver: DriverConfig,
30 pub buffer_classes: [usize; 3],
32 pub pool_cache: usize,
34 pub recv_buffers: usize,
36 pub max_input: usize,
38 pub max_connections: usize,
40 pub max_connections_per_loop: usize,
42 pub write_high_water: usize,
44 pub write_low_water: usize,
46 pub timer_tick: Duration,
48 pub offload: OffloadConfig,
50 pub listen: ListenOptions,
56 pub thread_name: String,
58 pub lazy_loops: bool,
71 pub linger: Duration,
76 pub linger_bytes: usize,
79 pub reserve_fds: usize,
84 pub stack_size: Option<usize>,
87 pub leave_cpu_for_network: bool,
96}
97
98impl Default for Config {
99 fn default() -> Self {
100 Config {
101 threads: 0,
102 pin_threads: false,
103 cpus: None,
104 driver: DriverConfig::default(),
105 buffer_classes: DEFAULT_CLASSES,
106 pool_cache: 1024,
107 recv_buffers: 64,
108 max_input: 64 * 1024,
109 max_connections: 1 << 20,
110 max_connections_per_loop: 1 << 20,
111 write_high_water: 256 * 1024,
112 write_low_water: 64 * 1024,
113 timer_tick: Duration::from_millis(1),
114 offload: OffloadConfig::default(),
115 listen: ListenOptions { defer_accept: Some(1), ..ListenOptions::default() },
116 thread_name: "simoom-loop".into(),
117 stack_size: None,
118 reserve_fds: 1024,
119 linger: Duration::from_secs(2),
120 linger_bytes: 256 * 1024,
121 lazy_loops: false,
122 leave_cpu_for_network: true,
123 }
124 }
125}
126
127#[derive(Default)]
128pub(crate) struct LoopStats {
129 waits: AtomicU64,
130 recv_calls: AtomicU64,
131 send_calls: AtomicU64,
132 accept_calls: AtomicU64,
133 other_calls: AtomicU64,
134 sqes: AtomicU64,
135 os_events: AtomicU64,
136 sleeps: AtomicU64,
137 spin_polls: AtomicU64,
138 accepted: AtomicU64,
139 rejected: AtomicU64,
140 closed: AtomicU64,
141 active: AtomicU64,
142 pool_allocated: AtomicU64,
143 pool_reused: AtomicU64,
144 pool_cached: AtomicU64,
145 pub(crate) timers: AtomicU64,
146}
147
148impl LoopStats {
149 pub(crate) fn store(&self, d: DriverStats, p: PoolStats, accepted: u64, rejected: u64, closed: u64, active: u64) {
150 let r = Ordering::Relaxed;
151 self.waits.store(d.waits, r);
152 self.recv_calls.store(d.recv_calls, r);
153 self.send_calls.store(d.send_calls, r);
154 self.accept_calls.store(d.accept_calls, r);
155 self.other_calls.store(d.other_calls, r);
156 self.sqes.store(d.sqes, r);
157 self.os_events.store(d.os_events, r);
158 self.sleeps.store(d.sleeps, r);
159 self.spin_polls.store(d.spin_polls, r);
160 self.accepted.store(accepted, r);
161 self.rejected.store(rejected, r);
162 self.closed.store(closed, r);
163 self.active.store(active, r);
164 self.pool_allocated.store(p.allocated, r);
165 self.pool_reused.store(p.reused, r);
166 self.pool_cached.store(p.cached as u64, r);
167 }
168
169 fn load(&self) -> LoopSnapshot {
170 let r = Ordering::Relaxed;
171 LoopSnapshot {
172 driver: DriverStats {
173 waits: self.waits.load(r),
174 recv_calls: self.recv_calls.load(r),
175 send_calls: self.send_calls.load(r),
176 accept_calls: self.accept_calls.load(r),
177 other_calls: self.other_calls.load(r),
178 sqes: self.sqes.load(r),
179 os_events: self.os_events.load(r),
180 sleeps: self.sleeps.load(r),
181 spin_polls: self.spin_polls.load(r),
182 },
183 accepted: self.accepted.load(r),
184 rejected: self.rejected.load(r),
185 closed: self.closed.load(r),
186 active: self.active.load(r),
187 pool_allocated: self.pool_allocated.load(r),
188 pool_reused: self.pool_reused.load(r),
189 pool_cached: self.pool_cached.load(r),
190 timers: self.timers.load(r),
191 }
192 }
193}
194
195type Grow = Box<dyn FnOnce() + Send>;
196
197pub(crate) struct Shared {
198 pub(crate) stop: AtomicBool,
199 pub(crate) grace_ns: AtomicU64,
200 pub(crate) active: AtomicUsize,
201 pub(crate) loop_active: Vec<AtomicUsize>,
202 pub(crate) loops: Vec<LoopStats>,
203 handles: Mutex<Vec<LoopHandle>>,
205 pub(crate) started: AtomicUsize,
207 failed: AtomicUsize,
209 threads: Mutex<Vec<JoinHandle<()>>>,
211 grow: Mutex<Option<Grow>>,
213 pub(crate) growing: AtomicBool,
215 boot: Mutex<Option<(Backend, Option<String>)>>,
217 error: Mutex<Option<io::Error>>,
219 boot_cv: Condvar,
220}
221
222#[inline(never)]
224fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
225 m.lock().unwrap_or_else(PoisonError::into_inner)
226}
227
228impl Shared {
229 pub(crate) fn register(&self, h: LoopHandle) {
232 let mut v = lock(&self.handles);
233 let at = v.iter().position(|l| l.index > h.index).unwrap_or(v.len());
235 v.insert(at, h.clone());
236 self.started.store(v.len(), Ordering::Release);
237 if self.stop.load(Ordering::Acquire) {
238 h.queue.waker.wake();
239 }
240 drop(v);
241 drop(lock(&self.boot));
243 self.boot_cv.notify_all();
244 }
245
246 pub(crate) fn handles(&self) -> Vec<LoopHandle> {
248 lock(&self.handles).clone()
249 }
250
251 fn booted(&self, b: (Backend, Option<String>)) {
252 *lock(&self.boot) = Some(b);
253 self.boot_cv.notify_all();
254 }
255
256 fn boot(&self) -> (Backend, Option<String>) {
258 let mut g = lock(&self.boot);
259 loop {
260 if let Some(b) = &*g {
261 return b.clone();
262 }
263 g = self.boot_cv.wait(g).unwrap_or_else(PoisonError::into_inner);
264 }
265 }
266
267 pub(crate) fn grow(&self) {
269 let f = lock(&self.grow).take();
270 if let Some(f) = f {
271 self.growing.store(true, Ordering::Release);
272 f();
273 }
274 }
275
276 fn grown(&self) {
278 self.growing.store(false, Ordering::Release);
279 for h in lock(&self.handles).iter() {
280 h.queue.waker.wake();
281 }
282 }
283}
284
285#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
287pub struct LoopSnapshot {
288 pub driver: DriverStats,
290 pub accepted: u64,
292 pub rejected: u64,
294 pub closed: u64,
296 pub active: u64,
298 pub pool_allocated: u64,
300 pub pool_reused: u64,
302 pub pool_cached: u64,
304 pub timers: u64,
306}
307
308fn default_loops(cfg: &Config, cpus: usize) -> usize {
311 loops_for(cfg.leave_cpu_for_network, cpus, simoom_sys::cpu::net_rx_concentrated)
312}
313
314fn loops_for(leave: bool, cpus: usize, concentrated: impl FnOnce() -> bool) -> usize {
316 if leave && cpus >= 4 && concentrated() {
317 cpus - 1
318 } else {
319 cpus
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::loops_for;
326
327 #[test]
328 fn a_cpu_is_left_to_a_concentrated_receive_path_from_four_cpus() {
329 assert_eq!(loops_for(true, 4, || true), 3);
330 assert_eq!(loops_for(true, 16, || true), 15);
331 assert_eq!(loops_for(true, 4, || false), 4, "receive work spread: every CPU");
332 assert_eq!(loops_for(true, 3, || panic!("not read below 4 CPUs")), 3);
333 assert_eq!(loops_for(false, 8, || panic!("not read when turned off")), 8);
334 }
335}
336
337#[derive(Clone, Debug, Default)]
339pub struct Stats {
340 pub loops: Vec<LoopSnapshot>,
342 pub active: usize,
344}
345
346impl Stats {
347 pub fn total(&self) -> LoopSnapshot {
349 let mut t = LoopSnapshot::default();
350 for l in &self.loops {
351 t.driver.waits += l.driver.waits;
352 t.driver.recv_calls += l.driver.recv_calls;
353 t.driver.send_calls += l.driver.send_calls;
354 t.driver.accept_calls += l.driver.accept_calls;
355 t.driver.other_calls += l.driver.other_calls;
356 t.driver.sqes += l.driver.sqes;
357 t.driver.os_events += l.driver.os_events;
358 t.driver.sleeps += l.driver.sleeps;
359 t.driver.spin_polls += l.driver.spin_polls;
360 t.accepted += l.accepted;
361 t.rejected += l.rejected;
362 t.closed += l.closed;
363 t.active += l.active;
364 t.pool_allocated += l.pool_allocated;
365 t.pool_reused += l.pool_reused;
366 t.pool_cached += l.pool_cached;
367 t.timers += l.timers;
368 }
369 t
370 }
371}
372
373enum ListenSource {
374 Addr(io::Result<Vec<SocketAddr>>),
375 Socket(Socket),
376}
377
378pub struct Builder {
380 cfg: Config,
381 listeners: Vec<(ListenSource, Arc<dyn Acceptor>)>,
382 udps: Vec<(io::Result<Vec<SocketAddr>>, UdpOptions, Arc<dyn UdpService>)>,
383}
384
385impl std::fmt::Debug for Builder {
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 f.debug_struct("Builder").field("cfg", &self.cfg).field("listeners", &self.listeners.len()).field("udps", &self.udps.len()).finish()
388 }
389}
390
391impl Default for Builder {
392 fn default() -> Self {
393 Builder::new()
394 }
395}
396
397impl Builder {
398 pub fn new() -> Builder {
400 Builder { cfg: Config::default(), listeners: Vec::new(), udps: Vec::new() }
401 }
402
403 pub fn config(mut self, cfg: Config) -> Builder {
405 self.cfg = cfg;
406 self
407 }
408
409 pub fn configure(mut self, f: impl FnOnce(&mut Config)) -> Builder {
411 f(&mut self.cfg);
412 self
413 }
414
415 pub fn threads(mut self, n: usize) -> Builder {
417 self.cfg.threads = n;
418 self
419 }
420
421 pub fn backend(mut self, b: Backend) -> Builder {
423 self.cfg.driver.backend = b;
424 self
425 }
426
427 pub fn listen(self, addr: impl ToSocketAddrs, acceptor: impl Acceptor) -> Builder {
432 let addrs = addr.to_socket_addrs().map(|a| a.collect());
433 self.listen_addrs(addrs, Arc::new(acceptor))
434 }
435
436 fn listen_addrs(mut self, addrs: io::Result<Vec<SocketAddr>>, acceptor: Arc<dyn Acceptor>) -> Builder {
437 let addrs = addrs.map(|v| {
439 let (mut v4, v6): (Vec<SocketAddr>, Vec<SocketAddr>) = v.into_iter().partition(SocketAddr::is_ipv4);
440 v4.extend(v6);
441 v4
442 });
443 self.listeners.push((ListenSource::Addr(addrs), acceptor));
444 self
445 }
446
447 pub fn listen_on(mut self, listener: std::net::TcpListener, acceptor: impl Acceptor) -> Builder {
450 let _ = listener.set_nonblocking(true);
451 self.listeners.push((ListenSource::Socket(Socket::from(listener)), Arc::new(acceptor)));
452 self
453 }
454
455 #[cfg(feature = "udp")]
458 pub fn udp(mut self, addr: impl ToSocketAddrs, opts: UdpOptions, service: impl UdpService) -> Builder {
459 let addrs = addr.to_socket_addrs().map(|a| a.collect());
460 self.udps.push((addrs, opts, Arc::new(service)));
461 self
462 }
463
464 pub fn start(self) -> io::Result<RuntimeHandle> {
466 let mut cfg = self.cfg;
467 cfg.lazy_loops &= self.udps.is_empty();
471 let cpus = cfg.cpus.clone().unwrap_or_else(simoom_sys::cpu::allowed_cpus);
472 let quota_later = cfg.lazy_loops && cfg.threads == 0 && cfg.cpus.is_none();
475 let n = match (cfg.threads, &cfg.cpus) {
476 (0, Some(_)) => cpus.len().max(1),
477 (0, None) if quota_later => cpus.len().max(1),
478 (0, None) => default_loops(&cfg, simoom_sys::cpu::cpu_count()),
479 (t, _) => t,
480 };
481 cfg.threads = n;
482 if cfg.write_low_water > cfg.write_high_water {
483 cfg.write_low_water = cfg.write_high_water;
484 }
485 let per_loop = cfg!(target_os = "linux");
486 simoom_sys::net::reserve_descriptors(cfg.reserve_fds);
490
491 let mut per_loop_listeners: Vec<Vec<ListenerSlot>> = (0..n).map(|_| Vec::new()).collect();
493 let mut addrs = Vec::new();
494 for (src, acceptor) in self.listeners {
495 match src {
496 ListenSource::Socket(s) => {
497 addrs.push(s.local_addr()?);
498 for slots in per_loop_listeners.iter_mut() {
499 slots.push(ListenerSlot::new(acceptor.clone(), None, true));
500 }
501 per_loop_listeners[0].last_mut().unwrap().socket = Some(s);
502 }
503 ListenSource::Addr(a) => {
504 let list = a?;
505 let first = *list.first().ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "no address to listen on"))?;
506 let mut opts = cfg.listen.clone();
507 opts.reuse_port = per_loop;
508 let s0 = Socket::tcp_listen(first, &opts)?;
509 let bound = s0.local_addr()?;
510 addrs.push(bound);
511 let mut s0 = Some(s0);
514 for (i, slots) in per_loop_listeners.iter_mut().enumerate() {
515 let mut slot = ListenerSlot::new(acceptor.clone(), s0.take(), !per_loop);
516 if per_loop && i > 0 {
517 slot.bind = Some((bound, opts.clone()));
518 }
519 slots.push(slot);
520 }
521 }
522 }
523 }
524
525 let mut per_loop_udps: Vec<Vec<UdpSlot>> = (0..n).map(|_| Vec::new()).collect();
527 let mut udp_addrs = Vec::new();
528 #[cfg(feature = "udp")]
529 for (b, (a, opts, service)) in self.udps.into_iter().enumerate() {
530 let list = a?;
531 let first = *list.first().ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "no address to bind"))?;
532 let mut opts = opts;
533 opts.reuse_port = per_loop;
534 let s0 = Socket::udp_bind(first, &opts)?;
535 let bound = s0.local_addr()?;
536 udp_addrs.push(bound);
537 per_loop_udps[0].push(UdpSlot::new(service.clone(), b, Some(s0), opts.gro));
538 if per_loop {
539 for slots in per_loop_udps.iter_mut().skip(1) {
540 let mut slot = UdpSlot::new(service.clone(), b, None, opts.gro);
541 slot.bind = Some((bound, opts.clone()));
542 slots.push(slot);
543 }
544 }
545 }
546
547 let shared = Arc::new(Shared {
548 stop: AtomicBool::new(false),
549 grace_ns: AtomicU64::new(0),
550 active: AtomicUsize::new(0),
551 loop_active: (0..n).map(|_| AtomicUsize::new(0)).collect(),
552 loops: (0..n).map(|_| LoopStats::default()).collect(),
553 handles: Mutex::new(Vec::with_capacity(n)),
554 started: AtomicUsize::new(0),
555 failed: AtomicUsize::new(0),
556 threads: Mutex::new(Vec::with_capacity(n + 1)),
557 grow: Mutex::new(None),
558 growing: AtomicBool::new(false),
559 boot: Mutex::new(None),
560 error: Mutex::new(None),
561 boot_cv: Condvar::new(),
562 });
563 let mut ocfg = cfg.offload.clone();
564 if ocfg.name.is_empty() {
565 ocfg.name = "simoom-offload".into();
566 }
567 let offload = OffloadPool::new(ocfg);
568 let cfg = Arc::new(cfg);
569 let mut setups: Vec<LoopSetup> = per_loop_listeners
570 .into_iter()
571 .zip(per_loop_udps)
572 .enumerate()
573 .map(|(i, (listeners, udps))| LoopSetup { index: i, cfg: cfg.clone(), shared: shared.clone(), offload: offload.clone(), listeners, udps })
574 .collect();
575 let pin_of = {
576 let cpus = cpus.clone();
577 let pin = cfg.pin_threads;
578 move |i: usize| if pin && !cpus.is_empty() { Some(cpus[i % cpus.len()]) } else { None }
579 };
580 if cfg.lazy_loops {
581 let later = setups.split_off(1);
585 let setup = setups.pop().expect("loop 0");
586 let t = spawn_loop(&cfg, setup, pin_of(0), shared.clone())?;
587 lock(&shared.threads).push(t);
588 if !later.is_empty() {
589 let cfg = cfg.clone();
590 let weak = Arc::downgrade(&shared);
591 *lock(&shared.grow) = Some(Box::new(move || {
592 let w = weak.clone();
595 let helper = crate::spawn(std::thread::Builder::new().name(format!("{}-start", cfg.thread_name)), Box::new(move || {
596 let mut later = later;
597 if quota_later {
598 later.truncate(default_loops(&cfg, simoom_sys::cpu::cpu_count()).saturating_sub(1));
599 }
600 let mut spawned = 0;
601 for setup in later {
602 let Some(shared) = w.upgrade() else { return };
603 if shared.stop.load(Ordering::Acquire) {
604 break;
605 }
606 let i = setup.index;
607 match spawn_loop(&cfg, setup, pin_of(i), shared.clone()) {
608 Ok(t) => {
609 lock(&shared.threads).push(t);
610 spawned += 1;
611 }
612 Err(e) => log_failure(i, "failed to start", &e),
613 }
614 }
615 if let Some(shared) = w.upgrade() {
617 let mut g = lock(&shared.boot);
618 while shared.started.load(Ordering::Acquire) + shared.failed.load(Ordering::Acquire) < 1 + spawned {
619 g = shared.boot_cv.wait(g).unwrap_or_else(PoisonError::into_inner);
620 }
621 drop(g);
622 shared.grown();
623 }
624 }));
625 match (helper, weak.upgrade()) {
626 (Ok(t), Some(shared)) => lock(&shared.threads).push(t),
627 (Err(e), Some(shared)) => {
628 eprintln!("simoom: the loops could not be started: {e}");
629 shared.grown();
630 }
631 _ => {}
632 }
633 }));
634 }
635 return Ok(RuntimeHandle { shared, addrs, udp_addrs, offload, backend: OnceLock::new(), lazy: true });
636 }
637 let now = setups.len();
638
639 for setup in setups {
642 let i = setup.index;
643 match spawn_loop(&cfg, setup, pin_of(i), shared.clone()) {
644 Ok(t) => lock(&shared.threads).push(t),
645 Err(e) => {
646 *lock(&shared.error) = Some(e);
647 break;
648 }
649 }
650 }
651 let mut g = lock(&shared.boot);
652 let spawned = lock(&shared.threads).len();
653 while shared.started.load(Ordering::Acquire) + shared.failed.load(Ordering::Acquire) < spawned {
654 g = shared.boot_cv.wait(g).unwrap_or_else(PoisonError::into_inner);
655 }
656 drop(g);
657 let mut handle = RuntimeHandle { shared, addrs, udp_addrs, offload, backend: OnceLock::new(), lazy: false };
658 if spawned < now || handle.shared.failed.load(Ordering::Acquire) != 0 {
659 handle.stop(Duration::ZERO);
661 handle.join_threads();
662 let e = lock(&handle.shared.error).take();
663 return Err(e.unwrap_or_else(|| io::Error::other("a loop thread died during start")));
664 }
665 Ok(handle)
666 }
667}
668
669struct Starting {
672 shared: Arc<Shared>,
673 done: bool,
674}
675
676impl Drop for Starting {
677 fn drop(&mut self) {
678 if !self.done {
679 self.shared.failed.fetch_add(1, Ordering::AcqRel);
680 drop(lock(&self.shared.boot));
681 self.shared.boot_cv.notify_all();
682 }
683 }
684}
685
686fn spawn_loop(cfg: &Config, setup: LoopSetup, pin: Option<usize>, shared: Arc<Shared>) -> io::Result<JoinHandle<()>> {
689 let i = setup.index;
690 let log = cfg.lazy_loops;
691 crate::spawn(loop_thread(cfg, i), Box::new(move || {
692 if let Some(cpu) = pin {
693 let _ = simoom_sys::cpu::pin_current_thread(cpu);
694 } else {
695 simoom_sys::cpu::keep_current_thread_off_net_rx();
697 }
698 let mut starting = Starting { shared, done: false };
699 let shared = &starting.shared;
700 let (mut core, queue) = match build(setup) {
701 Ok(x) => x,
702 Err(e) => {
703 if log {
704 log_failure(i, "failed to start", &e);
705 }
706 if i == 0 {
707 shared.booted((Backend::Auto, Some(format!("loop 0 failed to start: {e}"))));
708 }
709 lock(&shared.error).get_or_insert(e);
710 return;
711 }
712 };
713 if i == 0 {
714 shared.booted(core.backend());
715 }
716 shared.register(LoopHandle { index: i, queue });
717 starting.done = true;
718 drop(starting);
719 if let Err(e) = core.run() {
720 log_failure(i, "failed", &e);
721 }
722 }))
723}
724
725#[cold]
727#[inline(never)]
728fn log_failure(i: usize, what: &str, e: &io::Error) {
729 eprintln!("simoom loop {i} {what}: {e}");
730}
731
732fn loop_thread(cfg: &Config, i: usize) -> std::thread::Builder {
733 let tb = std::thread::Builder::new().name(format!("{}-{i}", cfg.thread_name));
734 match cfg.stack_size {
735 Some(sz) => tb.stack_size(sz),
736 None => tb,
737 }
738}
739
740pub struct RuntimeHandle {
743 shared: Arc<Shared>,
744 addrs: Vec<SocketAddr>,
745 udp_addrs: Vec<SocketAddr>,
746 offload: OffloadPool,
747 backend: OnceLock<(Backend, Option<String>)>,
749 lazy: bool,
750}
751
752impl std::fmt::Debug for RuntimeHandle {
753 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
754 f.debug_struct("RuntimeHandle").field("loops", &self.shared.started.load(Ordering::Relaxed)).field("addrs", &self.addrs).field("lazy", &self.lazy).finish()
755 }
756}
757
758impl RuntimeHandle {
759 pub fn local_addrs(&self) -> &[SocketAddr] {
761 &self.addrs
762 }
763
764 pub fn udp_addrs(&self) -> &[SocketAddr] {
766 &self.udp_addrs
767 }
768
769 pub fn loops(&self) -> Vec<LoopHandle> {
772 self.shared.handles()
773 }
774
775 fn boot(&self) -> &(Backend, Option<String>) {
776 self.backend.get_or_init(|| self.shared.boot())
777 }
778
779 pub fn backend(&self) -> Backend {
782 self.boot().0
783 }
784
785 pub fn fallback_reason(&self) -> Option<&str> {
787 self.boot().1.as_deref()
788 }
789
790 pub fn offload(&self) -> &OffloadPool {
792 &self.offload
793 }
794
795 pub fn stats(&self) -> Stats {
797 Stats { loops: self.shared.loops.iter().map(LoopStats::load).collect(), active: self.shared.active.load(Ordering::Relaxed) }
798 }
799
800 pub fn stop(&self, grace: Duration) {
804 self.shared.grace_ns.store(grace.as_nanos().min(u64::MAX as u128) as u64, Ordering::Release);
805 self.shared.stop.store(true, Ordering::Release);
806 drop(lock(&self.shared.grow).take());
808 for l in lock(&self.shared.handles).iter() {
809 l.queue.waker.wake();
810 }
811 }
812
813 pub fn join(mut self) {
816 self.join_threads();
817 }
818
819 fn join_threads(&mut self) {
820 loop {
822 let t = lock(&self.shared.threads).pop();
823 match t {
824 Some(t) => {
825 let _ = t.join();
826 }
827 None => break,
828 }
829 }
830 self.offload.shutdown(Duration::from_secs(5));
831 }
832}
833
834impl Drop for RuntimeHandle {
835 fn drop(&mut self) {
836 let running = !lock(&self.shared.threads).is_empty();
837 if running {
838 if !self.shared.stop.load(Ordering::Acquire) {
839 self.stop(Duration::ZERO);
840 }
841 self.join_threads();
842 }
843 }
844}