Skip to content

Fix appsrc state regression when adding source bins during pipeline startup#1293

Merged
milos-lk merged 2 commits into
mainfrom
state-race-fix
Jul 14, 2026
Merged

Fix appsrc state regression when adding source bins during pipeline startup#1293
milos-lk merged 2 commits into
mainfrom
state-race-fix

Conversation

@milos-lk

Copy link
Copy Markdown
Contributor

When a source bin (appsrc) is added dynamically, linkPeersLocked forced it to PLAYING with SetState(gst.StatePlaying). This races with the pipeline's own NULL → PLAYING transition: dynamic linking is enabled at StateStarted, which is set before pipeline.SetState(PLAYING) is called, so a track can be added while the pipeline is still NULL or mid-transition. GStreamer's state propagation (gst_bin_element_set_state) always recurses into child bins - the "already at target state" skip does not apply to them. A source bin manually set to PLAYING therefore gets forced back down to READY by the pipeline's NULL → READY step. That downward transition deactivates the appsrc's pads and stops its streaming task, and depending on interleaving (e.g. a stale cached ASYNC_START causing subsequent upward steps to skip the child) the bin can be stranded below PLAYING while the rest of the pipeline reaches PLAYING. nothing is pushed downstream anymore.

Using gst_element_sync_state_with_parent() instead of forcing PLAYING. It targets the parent's pending state (the final target of an in-flight transition), falling back to its current state:

  • Pipeline not yet transitioning (NULL): no-op; the pipeline's upcoming transition brings the bin up along with all other children
  • Pipeline mid NULL → PLAYING: commands PLAYING, but never above the pipeline's target, so it converges with the transition instead of being dragged down from a state ahead of it.
  • Pipeline already PLAYING: commands PLAYING directly, same as before.

This should fix one of the biggest remaining pipeline frozen errors and eliminate the need to forcefully re-add flushing source.

…tartup

When a source bin (appsrc) is added dynamically, `linkPeersLocked` forced it to PLAYING with `SetState(gst.StatePlaying)`. This races with the pipeline's own NULL → PLAYING transition: dynamic linking is enabled at `StateStarted`, which is set before `pipeline.SetState(PLAYING)` is called, so a track can be added while the pipeline is still NULL or mid-transition.
GStreamer's state propagation (`gst_bin_element_set_state`) always recurses into child bins - the "already at target state" skip does not apply to them. A source bin manually set to PLAYING therefore gets forced back down to READY by the pipeline's NULL → READY step. That downward transition deactivates the appsrc's pads and stops its streaming task, and depending on interleaving (e.g. a stale cached ASYNC_START causing subsequent upward steps to skip the child) the bin can be stranded below PLAYING while the rest of the pipeline reaches PLAYING. nothing is pushed downstream anymore.

Using `gst_element_sync_state_with_parent()` instead of forcing PLAYING. It targets the parent's pending state (the final target of an in-flight transition), falling back to its current state:
- Pipeline not yet transitioning (NULL): no-op; the pipeline's upcoming transition brings the bin up along with all other children
- Pipeline mid NULL → PLAYING: commands PLAYING, but never above the pipeline's target, so it converges with the transition instead of being dragged down from a state ahead of it.
- Pipeline already PLAYING: commands PLAYING directly, same as before.

This should fix one of the biggest remaining pipeline frozen errors and eliminate the need to forcefully re-add flushing source.
@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

AV-sync stats summary: view in run #29199604527

@boks1971

Copy link
Copy Markdown
Contributor

What happens on NULL -> READY transition in the new scheme? I guess the parent is going to be READY. Is READY higher than PLAYING?

The first paragraph talks about this transition and how the bin can be forced back to READY from PLAYING and get stuck, but the listing does not talk bout NULL -> READY transition. I am sure I am missing something obvious here.

@milos-lk

Copy link
Copy Markdown
Contributor Author

What happens on NULL -> READY transition in the new scheme? I guess the parent is going to be READY. Is READY higher than PLAYING?

The first paragraph talks about this transition and how the bin can be forced back to READY from PLAYING and get stuck, but the listing does not talk bout NULL -> READY transition. I am sure I am missing something obvious here.

Quick gstreamer context: states are ordered NULL < READY < PAUSED < PLAYING and that's valid for new as well as existing logic, so READY is actually below PLAYING - that's why getting forced from PLAYING back to READY (the bug in the first paragraph) is a harmful downgrade that shuts off the source's data flow. The listing doesn't mention NULL -> READY because we never target READY: the pipeline only ever gets one command, SetState(PLAYING), and NULL -> READY is just an internal stepping stone gstreamer walks through on the way there. sync_state_with_parent() keys off the parent's final target state, not whatever rung it's currently on, so mid-transition it returns PLAYING, and if the transition hasn't started yet it returns NULL (a no-op, letting the bin ride up with everyone else). In other words, there's no moment where the sync call would ever set the bin to READY. Or I didn't get the question?

@boks1971

Copy link
Copy Markdown
Contributor

transition

Thank you @milos-lk for the explanation. That was the answer to my question. I missed the internal/external bit and parent state is tracking the intended state.

@milos-lk milos-lk merged commit 270e11d into main Jul 14, 2026
19 checks passed
@milos-lk milos-lk deleted the state-race-fix branch July 14, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants