crcbl_shell/waker.rs
1//! Waking a blocked [`wait_events`](crate::Shell::wait_events) from another
2//! thread.
3//!
4//! A program whose work arrives off the loop thread — a D-Bus reply, a socket,
5//! a timer thread, a finished download — needs the loop to notice it while the
6//! loop is asleep in [`wait_events`](crate::Shell::wait_events). A
7//! [`ShellWaker`] is the handle that does it: cloned and sent to any thread,
8//! where [`wake`](ShellWaker::wake) makes the current wait return, or the next
9//! one return at once if none is running.
10//!
11//! **A wake carries nothing.** It is a doorbell, not a message: the program
12//! puts its data in its own channel and then rings, and the loop, once
13//! [`wait_events`](crate::Shell::wait_events) has returned, drains that
14//! channel. That keeps every backend's mechanism the same size — a byte on a
15//! pipe, a signalled event, one posted event — and no event type grows a
16//! variant for data the shell never looks at.
17//!
18//! **Wakes coalesce.** Any number of wakes between two waits end at most the
19//! next one or two of them early; none is ever lost, and none piles up. A loop
20//! that wakes, drains its channel and waits again therefore never misses work
21//! sent while it was draining.
22
23use core::fmt;
24use std::sync::Arc;
25
26/// What a backend's wake mechanism does when rung. One implementation per
27/// backend; [`ShellWaker`] is the type every caller sees.
28pub(crate) trait WakeSignal: Send + Sync + fmt::Debug {
29 /// Makes the shell's current or next wait return. Must not block, and must
30 /// be callable from any thread, any number of times.
31 fn wake(&self);
32}
33
34/// Wakes one shell's [`wait_events`](crate::Shell::wait_events) from any
35/// thread — see the [module docs](self).
36///
37/// From [`Shell::waker`](crate::Shell::waker). Cheap to clone, `Send` and
38/// `Sync`, and valid for as long as anyone holds it: a wake after the shell is
39/// gone does nothing.
40#[derive(Clone)]
41pub struct ShellWaker {
42 signal: Arc<dyn WakeSignal>,
43}
44
45impl ShellWaker {
46 pub(crate) fn new(signal: Arc<dyn WakeSignal>) -> Self {
47 Self { signal }
48 }
49
50 /// Makes the shell's current wait return, or its next one return at once.
51 ///
52 /// Never blocks, and is safe to call from any thread at any rate.
53 pub fn wake(&self) {
54 self.signal.wake();
55 }
56}
57
58/// A backend's [`waker`](crate::Shell::waker): the doorbell in `slot`, made
59/// by `make` on the first ask and shared by every handle after.
60///
61/// `None`, logged, if `make` fails — the process out of descriptors or
62/// handles — and asked again next time, since that can pass.
63#[cfg_attr(
64 not(any(desktop_unix, target_os = "macos", target_os = "windows")),
65 allow(dead_code, reason = "only the native window-system backends make one")
66)]
67pub(crate) fn lazy_waker<S: WakeSignal + 'static>(
68 slot: &mut Option<Arc<S>>,
69 backend: &str,
70 make: impl FnOnce() -> std::io::Result<S>,
71) -> Option<ShellWaker> {
72 if slot.is_none() {
73 match make() {
74 Ok(signal) => *slot = Some(Arc::new(signal)),
75 Err(error) => {
76 crcbl_core::log::error!("no wake signal for the {backend} shell: {error}");
77 return None;
78 }
79 }
80 }
81 let signal = Arc::clone(slot.as_ref()?);
82 Some(ShellWaker::new(signal))
83}
84
85impl fmt::Debug for ShellWaker {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.debug_tuple("ShellWaker").field(&self.signal).finish()
88 }
89}