Skip to main content

media_pp\core/
driver.rs

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