1use std::sync::{
2 Arc, Mutex, MutexGuard, Weak,
3 atomic::{AtomicBool, AtomicU64, Ordering},
4};
5
6use crate::pp_log::{PpLog, pp_info};
7
8use crate::{
9 buffer::MediaBuffer,
10 bus::BusEvent,
11 control::ControlMsg,
12 element::{Context, Element, ElementType, Sink, element_pp_log},
13 error::Result,
14 graph::{BranchId, ElementId, GraphError, PlannedEdge, PortRef, log_topology},
15 pad::SrcPad,
16 pipeline::{ChainBuilder, DetachedBranch},
17};
18
19pub struct Tee {
36 pp_log: PpLog,
37 id: ElementId,
38 name: Arc<str>,
39 shared: Arc<TeeShared>,
40}
41
42struct TeeShared {
43 branches: Mutex<Vec<Arc<TeeBranch>>>,
44 next_pad_id: AtomicU64,
45 context: Arc<Context>,
46}
47
48struct TeeBranch {
49 id: Option<BranchId>,
50 root_id: ElementId,
51 active: AtomicBool,
52 pad: Mutex<SrcPad>,
53}
54
55fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
60 match mutex.lock() {
61 Ok(guard) => guard,
62 Err(poisoned) => poisoned.into_inner(),
63 }
64}
65
66pub struct TeeBuilder {
72 tee: Tee,
73 handle: TeeHandle,
74 initial_branches: Vec<DetachedBranch>,
75}
76
77#[derive(Clone)]
84pub struct TeeHandle {
85 id: ElementId,
86 name: Arc<str>,
87 pp_log: PpLog,
91 shared: Weak<TeeShared>,
92}
93
94impl Tee {
95 fn new(name: impl Into<String>, context: Arc<Context>) -> (Self, TeeHandle) {
96 let name: Arc<str> = name.into().into();
97 let pp_log = element_pp_log(ElementType::Tee, &name, Some(&context.pipeline_id));
98 pp_info!(pp_log: &pp_log, "created");
99 let id = context.graph.reserve_element_id();
100 let shared = Arc::new(TeeShared {
101 branches: Mutex::new(Vec::new()),
102 next_pad_id: AtomicU64::new(0),
103 context,
104 });
105 (
106 Self {
107 id,
108 name: name.clone(),
109 pp_log: pp_log.clone(),
110 shared: shared.clone(),
111 },
112 TeeHandle {
113 id,
114 name,
115 pp_log,
116 shared: Arc::downgrade(&shared),
117 },
118 )
119 }
120
121 fn report_branch_error(
132 &self,
133 root_id: ElementId,
134 peer: Option<(ElementType, Arc<str>)>,
135 error: crate::error::Error,
136 ) {
137 let (element_type, name) = peer.unwrap_or((ElementType::Tee, self.name.clone()));
138 self.shared.context.bus.for_element(root_id).post(
139 &self.pp_log,
140 BusEvent::Error {
141 element_type,
142 name,
143 error,
144 },
145 );
146 }
147}
148
149impl TeeShared {
150 fn next_pad(&self, tee_name: &str) -> SrcPad {
151 let id = self.next_pad_id.fetch_add(1, Ordering::Relaxed);
152 SrcPad::new(format!("{tee_name}_src{id}"))
153 }
154}
155
156impl TeeBuilder {
157 pub fn new(name: impl Into<String>, context: Arc<Context>) -> Self {
159 let (tee, handle) = Tee::new(name, context);
160 Self {
161 tee,
162 handle,
163 initial_branches: Vec::new(),
164 }
165 }
166
167 pub fn branch(mut self, branch: DetachedBranch) -> Self {
169 self.initial_branches.push(branch);
170 self
171 }
172
173 pub fn build(self) -> Result<DetachedBranch> {
175 self.finish().map(|(branch, _handle)| branch)
176 }
177
178 pub fn build_dynamic(self) -> Result<(DetachedBranch, TeeHandle)> {
181 self.finish()
182 }
183
184 fn finish(self) -> Result<(DetachedBranch, TeeHandle)> {
185 let Self {
186 tee,
187 handle,
188 initial_branches,
189 } = self;
190 let tee_id = tee.id;
191 let tee_name = tee.name.clone();
192 let shared = tee.shared.clone();
193 let context = shared.context.clone();
194 let mut tee_branch = context.branch().to(Box::new(tee))?;
195 let mut runtime_branches = lock_unpoisoned(&shared.branches);
196
197 for branch in initial_branches {
198 let mut pad = shared.next_pad(&tee_name);
199 let from_port: Arc<str> = pad.name().into();
200 let DetachedBranch { root, plan } = branch;
201 let root_id = plan.root;
202
203 tee_branch.plan.edges.push(PlannedEdge {
204 from: PortRef {
205 element: tee_id,
206 port: from_port,
207 },
208 to: PortRef {
209 element: root_id,
210 port: "sink".into(),
211 },
212 });
213 tee_branch.plan.nodes.extend(plan.nodes);
214 tee_branch.plan.edges.extend(plan.edges);
215
216 pad.link(root);
217 runtime_branches.push(Arc::new(TeeBranch {
218 id: None,
219 root_id,
220 active: AtomicBool::new(true),
221 pad: Mutex::new(pad),
222 }));
223 }
224 drop(runtime_branches);
225
226 Ok((tee_branch, handle))
227 }
228}
229
230impl TeeHandle {
231 pub fn branch(&self) -> Option<ChainBuilder> {
238 let shared = self.shared.upgrade()?;
239 Some(shared.context.branch())
240 }
241
242 pub fn attach(&self, branch: DetachedBranch) -> Result<BranchId> {
245 let shared = self
246 .shared
247 .upgrade()
248 .ok_or(GraphError::ParentNotAttached(self.id))?;
249 let mut branches = lock_unpoisoned(&shared.branches);
250 let mut pad = shared.next_pad(&self.name);
251 let from_port: Arc<str> = pad.name().into();
252 let DetachedBranch { root, plan } = branch;
253 let root_id = plan.root;
254 let branch_id =
255 shared
256 .context
257 .graph
258 .attach_with(self.id, from_port, plan, |branch_id| {
259 pad.link(root);
260 branches.push(Arc::new(TeeBranch {
261 id: Some(branch_id),
262 root_id,
263 active: AtomicBool::new(true),
264 pad: Mutex::new(pad),
265 }));
266 Ok(())
267 })?;
268 let snapshot =
269 crate::log::enabled(crate::log::Level::Info).then(|| shared.context.graph.snapshot());
270 drop(branches);
271 if let Some(snapshot) = snapshot {
272 log_topology(&self.pp_log, "attach", &snapshot);
273 }
274 Ok(branch_id)
275 }
276
277 pub fn detach(&self, branch_id: BranchId) -> Result<()> {
281 let shared = self
282 .shared
283 .upgrade()
284 .ok_or(GraphError::BranchNotAttached(branch_id))?;
285 let mut branches = lock_unpoisoned(&shared.branches);
286 let index = branches
287 .iter()
288 .position(|branch| branch.id == Some(branch_id))
289 .ok_or(GraphError::BranchNotAttached(branch_id))?;
290 let mut removed = None;
291 shared.context.graph.detach_with(branch_id, || {
292 let branch = branches.remove(index);
293 branch.active.store(false, Ordering::Release);
294 removed = Some(branch);
295 Ok(())
296 })?;
297 let snapshot =
298 crate::log::enabled(crate::log::Level::Info).then(|| shared.context.graph.snapshot());
299 drop(branches);
300 drop(removed);
304 if let Some(snapshot) = snapshot {
305 log_topology(&self.pp_log, "detach", &snapshot);
306 }
307 Ok(())
308 }
309
310 pub fn detach_branch_containing(&self, element: ElementId) -> Result<()> {
314 let shared = self
315 .shared
316 .upgrade()
317 .ok_or(GraphError::ParentNotAttached(self.id))?;
318 let branch_id = shared
319 .context
320 .graph
321 .branch_containing(element)
322 .ok_or(GraphError::ParentNotAttached(element))?;
323 self.detach(branch_id)
324 }
325
326 pub fn sink_count(&self) -> usize {
330 self.shared
331 .upgrade()
332 .map(|shared| lock_unpoisoned(&shared.branches).len())
333 .unwrap_or(0)
334 }
335}
336
337impl Element for Tee {
338 fn name(&self) -> Arc<str> {
339 self.name.clone()
340 }
341
342 fn element_type(&self) -> ElementType {
343 ElementType::Tee
344 }
345
346 fn graph_id(&self) -> Option<ElementId> {
347 Some(self.id)
348 }
349
350 fn pp_log(&self) -> &PpLog {
351 &self.pp_log
352 }
353
354 fn pp_log_mut(&mut self) -> &mut PpLog {
355 &mut self.pp_log
356 }
357}
358
359impl Sink for Tee {
360 fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
361 let branches = lock_unpoisoned(&self.shared.branches).clone();
362 for branch in branches {
369 if !branch.active.load(Ordering::Acquire) {
370 continue;
371 }
372 let mut pad = lock_unpoisoned(&branch.pad);
373 if !branch.active.load(Ordering::Acquire) {
376 continue;
377 }
378 let peer = pad.peer_identity();
379 let outcome = pad.push(buf.clone());
380 drop(pad);
381 if let Err(error) = outcome {
382 self.report_branch_error(branch.root_id, peer, error);
383 }
384 }
385 Ok(())
386 }
387
388 fn control(&mut self, msg: ControlMsg) -> Result<()> {
389 let branches = lock_unpoisoned(&self.shared.branches).clone();
393 for branch in branches {
394 if !branch.active.load(Ordering::Acquire) {
395 continue;
396 }
397 let mut pad = lock_unpoisoned(&branch.pad);
398 if !branch.active.load(Ordering::Acquire) {
399 continue;
400 }
401 let peer = pad.peer_identity();
402 let outcome = pad.control(msg);
403 drop(pad);
404 if let Err(error) = outcome {
405 self.report_branch_error(branch.root_id, peer, error);
406 }
407 }
408 Ok(())
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use std::{
415 panic::{AssertUnwindSafe, catch_unwind},
416 sync::{
417 Barrier,
418 atomic::{AtomicBool, AtomicUsize, Ordering},
419 mpsc,
420 },
421 thread,
422 time::Duration,
423 };
424
425 use super::*;
426 use crate::{bus::Bus, graph::PipelineGraph};
427
428 fn packet() -> MediaBuffer {
429 MediaBuffer::Packet(Arc::new(ffmpeg_next::Packet::empty()))
430 }
431
432 struct CountingSink {
433 pp_log: PpLog,
434 name: &'static str,
435 count: Arc<AtomicUsize>,
436 }
437
438 impl Element for CountingSink {
439 fn name(&self) -> Arc<str> {
440 self.name.into()
441 }
442
443 fn element_type(&self) -> ElementType {
444 ElementType::Other
445 }
446
447 fn pp_log(&self) -> &PpLog {
448 &self.pp_log
449 }
450
451 fn pp_log_mut(&mut self) -> &mut PpLog {
452 &mut self.pp_log
453 }
454 }
455
456 impl Sink for CountingSink {
457 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
458 self.count.fetch_add(1, Ordering::SeqCst);
459 Ok(())
460 }
461
462 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
463 Ok(())
464 }
465 }
466
467 struct AlwaysFailSink {
468 pp_log: PpLog,
469 }
470
471 impl Element for AlwaysFailSink {
472 fn name(&self) -> Arc<str> {
473 "always-fail".into()
474 }
475
476 fn element_type(&self) -> ElementType {
477 ElementType::Other
478 }
479
480 fn pp_log(&self) -> &PpLog {
481 &self.pp_log
482 }
483
484 fn pp_log_mut(&mut self) -> &mut PpLog {
485 &mut self.pp_log
486 }
487 }
488
489 impl Sink for AlwaysFailSink {
490 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
491 Err(crate::error::Error::Other(
492 "simulated branch failure".into(),
493 ))
494 }
495
496 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
497 Ok(())
498 }
499 }
500
501 struct ControlObservingSink {
502 pp_log: PpLog,
503 name: &'static str,
504 count: Arc<AtomicUsize>,
505 fail: bool,
506 }
507
508 impl Element for ControlObservingSink {
509 fn name(&self) -> Arc<str> {
510 self.name.into()
511 }
512
513 fn element_type(&self) -> ElementType {
514 ElementType::Other
515 }
516
517 fn pp_log(&self) -> &PpLog {
518 &self.pp_log
519 }
520
521 fn pp_log_mut(&mut self) -> &mut PpLog {
522 &mut self.pp_log
523 }
524 }
525
526 impl Sink for ControlObservingSink {
527 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
528 Ok(())
529 }
530
531 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
532 self.count.fetch_add(1, Ordering::SeqCst);
533 if self.fail {
534 Err(crate::error::Error::Other(
535 "simulated control failure".into(),
536 ))
537 } else {
538 Ok(())
539 }
540 }
541 }
542
543 struct PanicOnceSink {
544 pp_log: PpLog,
545 panicked: bool,
546 successful: Arc<AtomicUsize>,
547 }
548
549 impl Element for PanicOnceSink {
550 fn name(&self) -> Arc<str> {
551 "panic-once".into()
552 }
553
554 fn element_type(&self) -> ElementType {
555 ElementType::Other
556 }
557
558 fn pp_log(&self) -> &PpLog {
559 &self.pp_log
560 }
561
562 fn pp_log_mut(&mut self) -> &mut PpLog {
563 &mut self.pp_log
564 }
565 }
566
567 impl Sink for PanicOnceSink {
568 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
569 if !self.panicked {
570 self.panicked = true;
571 panic!("simulated downstream panic");
572 }
573 self.successful.fetch_add(1, Ordering::SeqCst);
574 Ok(())
575 }
576
577 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
578 Ok(())
579 }
580 }
581
582 struct BlockingSink {
583 pp_log: PpLog,
584 entered: Option<mpsc::Sender<()>>,
585 release: mpsc::Receiver<()>,
586 }
587
588 impl Element for BlockingSink {
589 fn name(&self) -> Arc<str> {
590 "blocking".into()
591 }
592
593 fn element_type(&self) -> ElementType {
594 ElementType::Other
595 }
596
597 fn pp_log(&self) -> &PpLog {
598 &self.pp_log
599 }
600
601 fn pp_log_mut(&mut self) -> &mut PpLog {
602 &mut self.pp_log
603 }
604 }
605
606 impl Sink for BlockingSink {
607 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
608 if let Some(entered) = self.entered.take() {
609 let _ = entered.send(());
610 }
611 let _ = self.release.recv();
612 Ok(())
613 }
614
615 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
616 Ok(())
617 }
618 }
619
620 struct GraphInspectingDropSink {
621 pp_log: PpLog,
622 graph: PipelineGraph,
623 dropped: Option<mpsc::Sender<()>>,
624 }
625
626 impl Element for GraphInspectingDropSink {
627 fn name(&self) -> Arc<str> {
628 "graph-inspecting-drop".into()
629 }
630
631 fn element_type(&self) -> ElementType {
632 ElementType::Other
633 }
634
635 fn pp_log(&self) -> &PpLog {
636 &self.pp_log
637 }
638
639 fn pp_log_mut(&mut self) -> &mut PpLog {
640 &mut self.pp_log
641 }
642 }
643
644 impl Sink for GraphInspectingDropSink {
645 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
646 Ok(())
647 }
648
649 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
650 Ok(())
651 }
652 }
653
654 impl Drop for GraphInspectingDropSink {
655 fn drop(&mut self) {
656 let _ = self.graph.snapshot();
657 if let Some(dropped) = self.dropped.take() {
658 let _ = dropped.send(());
659 }
660 }
661 }
662
663 #[test]
672 fn a_failing_branch_does_not_block_its_siblings() {
673 let (bus, bus_rx) = Bus::new();
674 let graph = PipelineGraph::new();
675 let source_id = graph.add_source(ElementType::Other, "source".into());
676 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
677 let before_count = Arc::new(AtomicUsize::new(0));
678 let after_count = Arc::new(AtomicUsize::new(0));
679 let before = context
680 .branch()
681 .to(Box::new(CountingSink {
682 name: "before",
683 count: before_count.clone(),
684 pp_log: element_pp_log(ElementType::Other, "before", None),
685 }))
686 .unwrap();
687 let failing = context
688 .branch()
689 .to(Box::new(AlwaysFailSink {
690 pp_log: element_pp_log(ElementType::Other, "always-fail", None),
691 }))
692 .unwrap();
693 let after = context
694 .branch()
695 .to(Box::new(CountingSink {
696 name: "after",
697 count: after_count.clone(),
698 pp_log: element_pp_log(ElementType::Other, "after", None),
699 }))
700 .unwrap();
701 let tee_branch = TeeBuilder::new("tee", context.clone())
702 .branch(before)
703 .branch(failing)
704 .branch(after)
705 .build()
706 .unwrap();
707 let mut upstream = SrcPad::new("source_src");
708 context.attach_pad(&mut upstream, tee_branch).unwrap();
709
710 for _ in 0..3 {
711 upstream
712 .push(packet())
713 .expect("a branch failing must not surface as an error from Tee::consume");
714 }
715
716 assert_eq!(before_count.load(Ordering::SeqCst), 3);
717 assert_eq!(after_count.load(Ordering::SeqCst), 3);
718
719 drop(upstream);
720 drop(context);
721 let errors: Vec<_> = bus_rx
722 .iter()
723 .filter(|e| matches!(e, BusEvent::Error { .. }))
724 .collect();
725 assert_eq!(
726 errors.len(),
727 3,
728 "expected one Error event per failed push, not a fatal short-circuit"
729 );
730 assert!(
731 errors.iter().all(|e| matches!(
732 e,
733 BusEvent::Error { name, .. } if &**name == "always-fail"
734 )),
735 "each Error event should be attributed to the branch that actually \
736 failed, not to Tee itself — that's what lets a caller call \
737 TeeHandle::detach(branch_id) straight off the bus; got {errors:?}"
738 );
739 }
740
741 #[test]
742 fn a_failing_control_branch_does_not_block_its_siblings() {
743 let (bus, bus_rx) = Bus::new();
744 let graph = PipelineGraph::new();
745 let source_id = graph.add_source(ElementType::Other, "source".into());
746 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
747 let failing_count = Arc::new(AtomicUsize::new(0));
748 let healthy_count = Arc::new(AtomicUsize::new(0));
749 let failing = context
750 .branch()
751 .to(Box::new(ControlObservingSink {
752 name: "control-fail",
753 count: failing_count.clone(),
754 fail: true,
755 pp_log: element_pp_log(ElementType::Other, "control-fail", None),
756 }))
757 .unwrap();
758 let healthy = context
759 .branch()
760 .to(Box::new(ControlObservingSink {
761 name: "control-ok",
762 count: healthy_count.clone(),
763 fail: false,
764 pp_log: element_pp_log(ElementType::Other, "control-ok", None),
765 }))
766 .unwrap();
767 let tee_branch = TeeBuilder::new("tee", context.clone())
768 .branch(failing)
769 .branch(healthy)
770 .build()
771 .unwrap();
772 let mut upstream = SrcPad::new("source_src");
773 context.attach_pad(&mut upstream, tee_branch).unwrap();
774
775 upstream
776 .control(ControlMsg::Pause)
777 .expect("a branch control failure should be reported, not short-circuit Tee");
778
779 assert_eq!(failing_count.load(Ordering::SeqCst), 1);
780 assert_eq!(healthy_count.load(Ordering::SeqCst), 1);
781 let message = bus_rx
782 .try_recv_message()
783 .expect("the failing control branch should post an Error event");
784 assert!(matches!(
785 message.event,
786 BusEvent::Error { name, .. } if &*name == "control-fail"
787 ));
788 assert!(bus_rx.try_recv_message().is_none());
789 }
790
791 #[test]
792 fn a_poisoned_branch_pad_can_be_used_after_the_panic_is_caught() {
793 let (bus, _bus_rx) = Bus::new();
794 let graph = PipelineGraph::new();
795 let source_id = graph.add_source(ElementType::Other, "source".into());
796 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
797 let successful = Arc::new(AtomicUsize::new(0));
798 let panic_once = context
799 .branch()
800 .to(Box::new(PanicOnceSink {
801 panicked: false,
802 successful: successful.clone(),
803 pp_log: element_pp_log(ElementType::Other, "panic-once", None),
804 }))
805 .unwrap();
806 let tee_branch = TeeBuilder::new("tee", context.clone())
807 .branch(panic_once)
808 .build()
809 .unwrap();
810 let mut upstream = SrcPad::new("source_src");
811 context.attach_pad(&mut upstream, tee_branch).unwrap();
812
813 let first = catch_unwind(AssertUnwindSafe(|| upstream.push(packet())));
814 assert!(
815 first.is_err(),
816 "the original downstream panic must propagate"
817 );
818 upstream
819 .push(packet())
820 .expect("the poisoned branch pad should be recovered on the next push");
821 assert_eq!(successful.load(Ordering::SeqCst), 1);
822 }
823
824 #[test]
825 fn a_poisoned_branch_list_does_not_break_attach_or_detach() {
826 let (bus, _bus_rx) = Bus::new();
827 let graph = PipelineGraph::new();
828 let source_id = graph.add_source(ElementType::Other, "source".into());
829 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
830 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
831 .build_dynamic()
832 .unwrap();
833 let mut upstream = SrcPad::new("source_src");
834 context.attach_pad(&mut upstream, tee_branch).unwrap();
835 let shared = handle.shared.upgrade().unwrap();
836
837 let poisoned = catch_unwind(AssertUnwindSafe(|| {
838 let _branches = shared.branches.lock().unwrap();
839 panic!("poison the branch-list lock");
840 }));
841 assert!(poisoned.is_err());
842 assert_eq!(handle.sink_count(), 0);
843
844 let branch = handle
845 .branch()
846 .unwrap()
847 .to(Box::new(CountingSink {
848 name: "after-poison",
849 count: Arc::new(AtomicUsize::new(0)),
850 pp_log: element_pp_log(ElementType::Other, "after-poison", None),
851 }))
852 .unwrap();
853 let branch_id = handle.attach(branch).unwrap();
854 assert_eq!(handle.sink_count(), 1);
855 handle.detach(branch_id).unwrap();
856 assert_eq!(handle.sink_count(), 0);
857 }
858
859 #[test]
860 fn blocked_downstream_does_not_block_unrelated_attach_or_detach() {
861 let (bus, _bus_rx) = Bus::new();
862 let graph = PipelineGraph::new();
863 let source_id = graph.add_source(ElementType::Other, "source".into());
864 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
865 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
866 .build_dynamic()
867 .unwrap();
868 let mut upstream = SrcPad::new("source_src");
869 context.attach_pad(&mut upstream, tee_branch).unwrap();
870
871 let (entered_tx, entered_rx) = mpsc::channel();
872 let (release_tx, release_rx) = mpsc::channel();
873 let blocking = handle
874 .branch()
875 .unwrap()
876 .to(Box::new(BlockingSink {
877 entered: Some(entered_tx),
878 release: release_rx,
879 pp_log: element_pp_log(ElementType::Other, "blocking", None),
880 }))
881 .unwrap();
882 let blocking_id = handle.attach(blocking).unwrap();
883
884 let push_thread = thread::spawn(move || upstream.push(packet()));
885 entered_rx
886 .recv_timeout(Duration::from_secs(1))
887 .expect("blocking branch was never entered");
888
889 let new_branch = handle
890 .branch()
891 .unwrap()
892 .to(Box::new(CountingSink {
893 name: "new",
894 count: Arc::new(AtomicUsize::new(0)),
895 pp_log: element_pp_log(ElementType::Other, "new", None),
896 }))
897 .unwrap();
898 let (attach_tx, attach_rx) = mpsc::channel();
899 let attach_handle = handle.clone();
900 let attach_thread = thread::spawn(move || {
901 let _ = attach_tx.send(attach_handle.attach(new_branch));
902 });
903
904 let (detach_tx, detach_rx) = mpsc::channel();
905 let detach_handle = handle.clone();
906 let detach_thread = thread::spawn(move || {
907 let _ = detach_tx.send(detach_handle.detach(blocking_id));
908 });
909
910 let attach_before_release = attach_rx.recv_timeout(Duration::from_millis(250)).ok();
911 let detach_before_release = detach_rx.recv_timeout(Duration::from_millis(250)).ok();
912 let attach_completed_while_blocked = attach_before_release.is_some();
913 let detach_completed_while_blocked = detach_before_release.is_some();
914 let _ = release_tx.send(());
915
916 push_thread.join().unwrap().unwrap();
917 attach_thread.join().unwrap();
918 detach_thread.join().unwrap();
919 let attach_result = attach_before_release
920 .unwrap_or_else(|| attach_rx.recv_timeout(Duration::from_secs(1)).unwrap());
921 let detach_result = detach_before_release
922 .unwrap_or_else(|| detach_rx.recv_timeout(Duration::from_secs(1)).unwrap());
923 attach_result.unwrap();
924 detach_result.unwrap();
925
926 assert!(
927 attach_completed_while_blocked,
928 "an unrelated attach waited for the blocked downstream"
929 );
930 assert!(
931 detach_completed_while_blocked,
932 "detach waited for an already-running downstream call"
933 );
934 }
935
936 #[test]
937 fn concurrent_push_attach_and_detach_stays_consistent_under_stress() {
938 const MIN_PUSHES: usize = 10_000;
939 const MUTATIONS: usize = 500;
940
941 let (bus, _bus_rx) = Bus::new();
942 let graph = PipelineGraph::new();
943 let source_id = graph.add_source(ElementType::Other, "source".into());
944 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
945 let initial_count = Arc::new(AtomicUsize::new(0));
946 let initial = context
947 .branch()
948 .to(Box::new(CountingSink {
949 name: "initial",
950 count: initial_count.clone(),
951 pp_log: element_pp_log(ElementType::Other, "initial", None),
952 }))
953 .unwrap();
954 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
955 .branch(initial)
956 .build_dynamic()
957 .unwrap();
958 let mut upstream = SrcPad::new("source_src");
959 context.attach_pad(&mut upstream, tee_branch).unwrap();
960
961 let start = Arc::new(Barrier::new(2));
962 let mutating = Arc::new(AtomicBool::new(true));
963 let (push_done_tx, push_done_rx) = mpsc::channel();
964 let push_start = start.clone();
965 let push_mutating = mutating.clone();
966 let push_thread = thread::spawn(move || {
967 push_start.wait();
968 let packet = packet();
969 let mut pushed = 0;
970 let mut outcome = Ok(());
971 while push_mutating.load(Ordering::Acquire) || pushed < MIN_PUSHES {
972 if let Err(error) = upstream.push(packet.clone()) {
973 outcome = Err(error.to_string());
974 break;
975 }
976 pushed += 1;
977 if pushed % 32 == 0 {
978 thread::yield_now();
979 }
980 }
981 let _ = push_done_tx.send((upstream, outcome, pushed));
982 });
983
984 let (mutation_done_tx, mutation_done_rx) = mpsc::channel();
985 let mutation_start = start;
986 let mutation_handle = handle.clone();
987 let mutation_thread = thread::spawn(move || {
988 mutation_start.wait();
989 let outcome = (|| -> std::result::Result<(), String> {
990 for _ in 0..MUTATIONS {
991 let branch = mutation_handle
992 .branch()
993 .ok_or_else(|| "Tee disappeared during stress test".to_owned())?
994 .to(Box::new(CountingSink {
995 name: "dynamic",
996 count: Arc::new(AtomicUsize::new(0)),
997 pp_log: element_pp_log(ElementType::Other, "dynamic", None),
998 }))
999 .map_err(|error| error.to_string())?;
1000 let branch_id = mutation_handle
1001 .attach(branch)
1002 .map_err(|error| error.to_string())?;
1003 thread::yield_now();
1004 mutation_handle
1005 .detach(branch_id)
1006 .map_err(|error| error.to_string())?;
1007 }
1008 Ok(())
1009 })();
1010 mutating.store(false, Ordering::Release);
1011 let _ = mutation_done_tx.send(outcome);
1012 });
1013
1014 mutation_done_rx
1015 .recv_timeout(Duration::from_secs(10))
1016 .expect("attach/detach stress thread timed out")
1017 .expect("attach/detach stress thread failed");
1018 let (upstream, push_outcome, pushed) = push_done_rx
1019 .recv_timeout(Duration::from_secs(10))
1020 .expect("push stress thread timed out");
1021 push_outcome.expect("push stress thread failed");
1022 push_thread.join().unwrap();
1023 mutation_thread.join().unwrap();
1024
1025 assert!(pushed >= MIN_PUSHES);
1026 assert_eq!(initial_count.load(Ordering::SeqCst), pushed);
1027 assert_eq!(handle.sink_count(), 1);
1028 let graph = context.graph.snapshot();
1029 assert_eq!(graph.nodes.len(), 3);
1030 assert_eq!(graph.edges.len(), 2);
1031 assert_eq!(graph.revision, 2 + (MUTATIONS as u64 * 2));
1032 drop(upstream);
1033 }
1034
1035 #[test]
1036 fn detached_sink_is_dropped_outside_the_graph_lock() {
1037 let (bus, _bus_rx) = Bus::new();
1038 let graph = PipelineGraph::new();
1039 let source_id = graph.add_source(ElementType::Other, "source".into());
1040 let context = Arc::new(Context::for_test(bus, "test", graph.clone(), source_id));
1041 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
1042 .build_dynamic()
1043 .unwrap();
1044 let mut upstream = SrcPad::new("source_src");
1045 context.attach_pad(&mut upstream, tee_branch).unwrap();
1046
1047 let (dropped_tx, dropped_rx) = mpsc::channel();
1048 let branch = handle
1049 .branch()
1050 .unwrap()
1051 .to(Box::new(GraphInspectingDropSink {
1052 graph,
1053 dropped: Some(dropped_tx),
1054 pp_log: element_pp_log(ElementType::Other, "graph-inspecting-drop", None),
1055 }))
1056 .unwrap();
1057 let branch_id = handle.attach(branch).unwrap();
1058 let (done_tx, done_rx) = mpsc::channel();
1059 let detach_thread = thread::spawn(move || {
1060 let _ = done_tx.send(handle.detach(branch_id));
1061 });
1062
1063 dropped_rx
1064 .recv_timeout(Duration::from_secs(1))
1065 .expect("sink Drop deadlocked while inspecting the graph");
1066 done_rx
1067 .recv_timeout(Duration::from_secs(1))
1068 .expect("detach did not finish")
1069 .unwrap();
1070 detach_thread.join().unwrap();
1071 drop(upstream);
1072 }
1073
1074 #[test]
1075 fn retained_handle_does_not_keep_tee_context_or_bus_alive() {
1076 let (bus, bus_rx) = Bus::new();
1077 let graph = PipelineGraph::new();
1078 let source_id = graph.add_source(ElementType::Other, "source".into());
1079 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
1080 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
1081 .build_dynamic()
1082 .unwrap();
1083
1084 drop(context);
1085 drop(tee_branch);
1086
1087 assert!(handle.branch().is_none());
1088 assert_eq!(handle.sink_count(), 0);
1089 assert!(
1090 bus_rx.iter().next().is_none(),
1091 "a retained TeeHandle must not keep the Bus sender alive"
1092 );
1093 }
1094}