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}