GstBin `async-handling` mode is quite broken
The async-handling
setting on GstBin
is supposed to make a bin inside the pipeline hide internal asynchronous state changes from the outside world. It does that primarily by absorbing async-start
and async-done
messages and not forwarding them to the parent bin, and by attempting to "continue state" itself on async-done
as if it were a toplevel bin (where most elements rely on their parent bin to continue any in-progress state change on async-done
.
However, there's a big problem: the bin's state change method still returns GST_STATE_CHANGE_ASYNC
to the parent bin. The effect of that is that the top-level bin receives GST_STATE_CHANGE_ASYNC
, and goes looking to see if any child elements are still changing state asynchronously or if they finished already. Since it doesn't find any incomplete stored async-start
messages (the child bin with async-handling=true
never posted one), it assumes the reported async state change is complete, and tries to continue to the next state... receives GST_STATE_CHANGE_ASYNC
from the child bin, and iterates consuming 100% CPU until the underlying state change finally does complete.
The net effect is that the top-level pipeline still ends up waiting for the underlying async state change, despite async-handling=true
, but it does so busy-waiting at 100% CPU instead of waiting quietly for an async-done
message like it normally would.
Now, if we try changing the bin state-change function to not return GST_STATE_CHANGE_ASYNC
, what happens is that the bin and the top-level pipeline instead immediately proceed to the final state (PLAYING, for example). Inside the async-handling
bin, when it does finally receive async-done
, the logic now looks only at the bin's state, see that it's already in the final state, and does not proceed to change the state of any async child elements - they get left at the old state that was happening async (usually PAUSED).
Also, along the way, the bin will continue trying to change the state of each child element with each state change step. Some children might be async and stop at READY->PAUSED returning GST_STATE_CHANGE_ASYNC
. Most elements will change state synchronously all the way to the final state, but because any state change might be async, we could also end up in a situation where one element inside the bin is changing state asynchronously from NULL->READY, and another from READY->PAUSED. Really in that situation, the elements inside the bin should all continue to change state step-by-step together, waiting as necessary for async changes. That would require some more separate book keeping about what the 'internal state' of the bin is, and completely changing the way bins handle things gst_bin_change_state_func
when async-handling=true
Finally though, there's a bigger fundamental problem with having async-handling=true
hide child state changes from the top-level bin, which is that the top-level pipeline can no longer do step-by-step state changes topologically and guarantee that a downstream element will have reached a state where it is able to receive data before data starts flowing in. Fixing that requires external knowledge from the application, or a fundamental change in how data-flow is activated between all elements in a pipeline.