threadshare: standalone rtprecv example: fix deadlock

Consider the following sequence:

1. The pipeline is being started. This occurs under the main thread.
2. A source just pushed a buffer. This occured under a ts-Context thread, let's
   call it ts-ctx-1.
3. The above buffer reaches `rtprecv`, which creates a src pad, which calls the
   'pad-added' callback (under ts-ctx-1).
4. The 'pad-added' callback adds elements, link them and sync their state with
   the pipeline (under ts-ctx-1).

A deadlock can occur if:

A. The pipeline on-going starting process picks an element that was just added
   by `pad-added`, but not synced yet => the pipeline acquires the element's
   mutex (under the main thread).
B. 'pad-added' reaches `sync_with_parent()` for the element => tries to acquire
   the element's mutex (under ts-ctx-1).
C. The `prepare()` method for the element attempts to spawn a ts-Task on
   ts-ctx-1, which is blocked. So the ts-Task spawned notification is not
   triggered.
D. Because the `prepare()` method was executed on the main thread, which is not
   a ts-Context thread, the ts-Task spawned notification is awaited
   synchronously. So the element state transition is blocked, keeping the
   element mutex, keeping ts-ctx-1 blocked.

I couldn't find an easy way to automatically detect these conditions. The
workaround here is to use a different ts-Context for the elements which are
added in 'pad-added', their ts-Tasks will be spawned on a ts-Context which can't
to be blocked by the starting process.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/2518>
This commit is contained in:
François Laignel 2025-08-28 18:16:39 +02:00
parent f553bd6495
commit ac86e0590e

View file

@ -112,6 +112,8 @@ fn main() {
return;
}
let sub_ctx_name = format!("standalone {}.sub", i % args.groups);
let depay = gst::ElementFactory::make("rtpL16depay")
.name(format!("depay-{i}").as_str())
.build()
@ -119,7 +121,7 @@ fn main() {
let queue = gst::ElementFactory::make("ts-queue")
.name(format!("queue-sink-{i}").as_str())
.property("context", &ctx_name)
.property("context", &sub_ctx_name)
.property("context-wait", args.wait)
.property("max-size-buffers", 0u32)
.property("max-size-bytes", 0u32)
@ -132,7 +134,7 @@ fn main() {
let sink = gst::ElementFactory::make(args.sink.element_name())
.name(format!("sink-{i}").as_str())
.property("context", &ctx_name)
.property("context", &sub_ctx_name)
.property("context-wait", args.wait)
.build()
.unwrap();