Skip to main content

media_pp/core/
driver.rs

1//! Background tasks that have no pads of their own.
2//!
3//! A [`Driver`] is a self-contained loop with nothing to push into and
4//! nothing to pull out of — whatever it produces or consumes it does through
5//! `Sink`/`Source` pairs it mints on the side, the way a WebRTC peer hands out
6//! per-track endpoints. [`DriverRunner`] runs one on a background thread.
7//!
8//! This is the deliberately smaller sibling of
9//! [`Pipeline`](crate::pipeline::Pipeline): no clock, no pause, no seek, no
10//! wiring callback, none of which mean anything for a connection that is not
11//! part of a dataflow graph. Anything that does have pads to wire belongs
12//! there instead.
13
14use std::{
15    sync::{
16        Arc, Mutex,
17        atomic::{AtomicBool, Ordering},
18    },
19    thread,
20};
21
22use crate::{
23    bus::{Bus, BusEvent, BusReceiver},
24    element::Element,
25    error::{Result, ThreadSpawnError},
26};
27
28/// Checked, not blocked on: a [`Driver`] owns a single self-contained loop
29/// with no downstream dataflow graph to cascade a stop through (unlike
30/// [`crate::pipeline::Pipeline`]'s `control` channel, which has to reach
31/// every `Sink` a `Queue` boundary away before it can call `Stop` fully
32/// handled). So [`DriverRunner::stop`] just flips a flag instead of
33/// sending something that has to be received and acked — nothing here can
34/// reproduce the deadlock that pattern is prone to when a receiver can
35/// legitimately go away without ever looping back to check it (see
36/// `Pipeline`'s own `control_rx` field docs for that history). Callers
37/// that need to know `run` has actually finished watch
38/// [`DriverRunner::bus`] instead, same convention as `Pipeline`.
39#[derive(Clone)]
40pub struct StopReceiver {
41    flag: Arc<AtomicBool>,
42}
43
44impl StopReceiver {
45    /// Returns whether the owning [`DriverRunner`] has requested shutdown.
46    pub fn is_stopped(&self) -> bool {
47        self.flag.load(Ordering::Acquire)
48    }
49}
50
51/// A background task with no `Sink`/`Source` ports of its own — nothing to
52/// push into, nothing to pull out of *this* object; whatever it produces
53/// or consumes happens through other `Sink`/`Source` pairs it mints on the
54/// side (e.g. `WebRtcPeer` handing out
55/// `WebRtcTrackSink`/`WebRtcTrackSource`). Reach for
56/// [`crate::pipeline::Pipeline`]/[`crate::element::SourceElement`] instead
57/// for anything that actually has a `src_pads()` dataflow graph to wire —
58/// `Driver` deliberately has no `Pause`/`Seek`/`Clock`, none of which have
59/// a sensible meaning for a connection that isn't part of one.
60pub trait Driver: Element {
61    /// Drives this task until it ends on its own or `stop.is_stopped()`
62    /// says to abandon — check it periodically, the same spirit as
63    /// [`crate::control::drain_control`] for a
64    /// [`crate::element::SourceElement`]. `bus` is this task's own way to
65    /// report a failure without necessarily ending itself over it — see
66    /// [`crate::element::SourceElement::run`]'s docs for the same
67    /// convention.
68    fn run(&mut self, stop: &StopReceiver, bus: &Bus) -> Result<()>;
69}
70
71/// Runs a [`Driver`] on its own background thread — the `Driver` analog of
72/// [`crate::pipeline::Pipeline`], minus everything that only makes sense
73/// for a dataflow graph (`Clock`, `Pause`, `Seek`, the `wire` callback).
74///
75/// `run()` is asynchronous, same as `Pipeline::run`: it starts the driver
76/// on a background thread and returns immediately, or returns a
77/// [`ThreadSpawnError`](crate::error::ThreadSpawnError) if the worker cannot
78/// be created. Watch
79/// [`DriverRunner::bus`] to learn when it's actually done — draining it
80/// blocks until every `Bus` sender has been dropped. The built-in drivers
81/// keep that sender only for the duration of their background `run` call,
82/// so this normally coincides with thread completion; a custom `Driver`
83/// that clones and retains `bus` extends the wait until its clone drops.
84pub struct DriverRunner {
85    driver: Mutex<Option<Box<dyn Driver>>>,
86    bus: Mutex<Option<Bus>>,
87    stop_flag: Arc<AtomicBool>,
88    bus_rx: BusReceiver,
89    running: AtomicBool,
90}
91
92impl DriverRunner {
93    /// Wraps `driver` in a stopped runner ready for one call to [`Self::run`].
94    pub fn new(driver: impl Driver + 'static) -> Arc<Self> {
95        let (bus, bus_rx) = Bus::new();
96        Arc::new(DriverRunner {
97            driver: Mutex::new(Some(Box::new(driver))),
98            bus: Mutex::new(Some(bus)),
99            stop_flag: Arc::new(AtomicBool::new(false)),
100            bus_rx,
101            running: AtomicBool::new(false),
102        })
103    }
104
105    /// Returns the receiver used to observe driver errors and completion.
106    pub fn bus(&self) -> &BusReceiver {
107        &self.bus_rx
108    }
109
110    /// Starts driving the task on a background thread and returns
111    /// immediately. A no-op if this `DriverRunner` is already running or
112    /// has already finished a previous run — same posture as
113    /// [`crate::pipeline::Pipeline::run`], not reusable afterward.
114    /// Returns a typed error if the worker thread cannot be created.
115    pub fn run(self: &Arc<Self>) -> Result<()> {
116        self.run_with_spawner(|thread_name, task| {
117            thread::Builder::new().name(thread_name).spawn(task)
118        })
119    }
120
121    fn run_with_spawner(
122        self: &Arc<Self>,
123        spawn: impl FnOnce(
124            String,
125            Box<dyn FnOnce() + Send + 'static>,
126        ) -> std::io::Result<thread::JoinHandle<()>>,
127    ) -> Result<()> {
128        let Some(mut driver) = self.driver.lock().unwrap().take() else {
129            return Ok(());
130        };
131        let Some(bus) = self.bus.lock().unwrap().take() else {
132            return Ok(());
133        };
134
135        self.running.store(true, Ordering::Release);
136        let stop = StopReceiver {
137            flag: self.stop_flag.clone(),
138        };
139        // A `Weak` back-reference, not `Arc::clone(self)`: the thread only
140        // needs it to flip `running` back off when the driver returns, and
141        // holding a strong ref here would mean the last *external*
142        // `Arc<DriverRunner>` going away could never bring the strong count
143        // to zero — `Drop` would never run, and nothing would ever flip
144        // `stop_flag` for a caller that just drops its handle (see `Drop`
145        // below, which depends on this being a `Weak`).
146        let this = Arc::downgrade(self);
147        let thread_name = "driver".to_owned();
148        let spawn_result = spawn(
149            thread_name.clone(),
150            Box::new(move || {
151                let name = driver.name();
152                let element_type = driver.element_type();
153                if let Err(error) = driver.run(&stop, &bus) {
154                    bus.post(
155                        driver.pp_log(),
156                        BusEvent::Error {
157                            element_type,
158                            name,
159                            error,
160                        },
161                    );
162                }
163                if let Some(this) = this.upgrade() {
164                    this.running.store(false, Ordering::Release);
165                }
166            }),
167        );
168        if let Err(source) = spawn_result {
169            self.running.store(false, Ordering::Release);
170            return Err(ThreadSpawnError::new(thread_name, source).into());
171        }
172        Ok(())
173    }
174
175    /// Requests an early stop — see [`StopReceiver`]'s own docs for why
176    /// this never blocks. A no-op if `run()` isn't currently in progress.
177    pub fn stop(&self) {
178        if !self.running.load(Ordering::Acquire) {
179            return;
180        }
181        self.stop_flag.store(true, Ordering::Release);
182    }
183}
184
185impl Drop for DriverRunner {
186    /// Same posture as [`crate::pipeline::Pipeline`]'s own `Drop`: dropping
187    /// the last handle stops the background work instead of leaking it.
188    /// Sets the flag directly rather than through `stop()` — by the time
189    /// this runs there's no `Arc<Self>` left to reach `&self` through one,
190    /// only the raw fields still being torn down.
191    fn drop(&mut self) {
192        self.stop_flag.store(true, Ordering::Release);
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use std::{sync::mpsc, time::Duration};
199
200    use crate::pp_log::PpLog;
201
202    use super::*;
203    use crate::element::{Element, ElementType, element_pp_log};
204
205    struct LoopingDriver {
206        pp_log: PpLog,
207        started: mpsc::Sender<()>,
208        stopped: mpsc::Sender<()>,
209    }
210
211    impl Element for LoopingDriver {
212        fn name(&self) -> Arc<str> {
213            "looping".into()
214        }
215
216        fn element_type(&self) -> ElementType {
217            ElementType::Other
218        }
219
220        fn pp_log(&self) -> &PpLog {
221            &self.pp_log
222        }
223
224        fn pp_log_mut(&mut self) -> &mut PpLog {
225            &mut self.pp_log
226        }
227    }
228
229    impl Driver for LoopingDriver {
230        fn run(&mut self, stop: &StopReceiver, _bus: &Bus) -> Result<()> {
231            let _ = self.started.send(());
232            while !stop.is_stopped() {
233                thread::sleep(Duration::from_millis(5));
234            }
235            let _ = self.stopped.send(());
236            Ok(())
237        }
238    }
239
240    /// Regression test: `run()` used to keep its own strong `Arc<Self>`
241    /// clone alive on the background thread for the entire loop, so the
242    /// last *external* handle going out of scope never actually dropped
243    /// the `DriverRunner` — `stop_flag` was never set, and the thread (and
244    /// whatever socket/session it holds) ran forever. `run` now hands the
245    /// thread a `Weak` instead, so this drop must reach `Drop::drop`.
246    #[test]
247    fn dropping_the_last_handle_stops_the_background_thread() {
248        let (started_tx, started_rx) = mpsc::channel();
249        let (stopped_tx, stopped_rx) = mpsc::channel();
250        let runner = DriverRunner::new(LoopingDriver {
251            started: started_tx,
252            stopped: stopped_tx,
253            pp_log: element_pp_log(ElementType::Other, "looping", None),
254        });
255        runner.run().unwrap();
256        started_rx
257            .recv_timeout(Duration::from_secs(1))
258            .expect("driver should start");
259
260        drop(runner);
261
262        stopped_rx
263            .recv_timeout(Duration::from_secs(1))
264            .expect("dropping the last DriverRunner handle should stop the background thread");
265    }
266
267    #[test]
268    fn thread_spawn_failure_is_returned_and_runner_is_not_left_running() {
269        let (started_tx, started_rx) = mpsc::channel();
270        let (stopped_tx, _stopped_rx) = mpsc::channel();
271        let runner = DriverRunner::new(LoopingDriver {
272            started: started_tx,
273            stopped: stopped_tx,
274            pp_log: element_pp_log(ElementType::Other, "looping", None),
275        });
276
277        let error = runner
278            .run_with_spawner(|_thread_name, _task| {
279                Err(std::io::Error::other("injected spawn failure"))
280            })
281            .expect_err("the injected spawn failure must be returned");
282
283        assert!(matches!(error, crate::Error::ThreadSpawnError(_)));
284        assert!(!runner.running.load(Ordering::Acquire));
285        assert!(started_rx.try_recv().is_err());
286    }
287}