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}