playsink: Fix race condition in stream synchronizer pad cleanup during state changes

Prevent race condition where gst_play_sink_do_reconfigure() could be called
from a pad probe while stream synchronizer pads are being released during
GST_STATE_CHANGE_PAUSED_TO_READY transition.

The race occurred when:
1. State change starts releasing stream synchronizer pads
2. Pads are unblocked earlier in the state change, allowing events to flow
3. A streaming thread triggers sinkpad_blocked_cb -> gst_play_sink_do_reconfigure
4. Reconfiguration tries to use already-released pad pointers
5. New pad creation fails with assertion in gst_pad_iterate_internal_links

The fix adds GST_PLAY_SINK_LOCK around the pad cleanup to ensure atomic
cleanup and prevent concurrent access during state transitions.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9233>
This commit is contained in:
Thibault Saunier 2025-06-05 17:03:12 +02:00 committed by GStreamer Marge Bot
parent 743c425f64
commit e799d79025
2 changed files with 31 additions and 14 deletions

View File

@ -5069,35 +5069,50 @@ gst_play_sink_change_state (GstElement * element, GstStateChange transition)
playsink->need_async_start = TRUE;
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:{
GList *pads_to_release = NULL;
gboolean remove_audio_ssync_queue = FALSE;
GST_PLAY_SINK_LOCK (playsink);
if (playsink->video_sinkpad_stream_synchronizer) {
gst_element_release_request_pad (GST_ELEMENT_CAST
(playsink->stream_synchronizer),
pads_to_release = g_list_prepend (pads_to_release,
playsink->video_sinkpad_stream_synchronizer);
gst_object_unref (playsink->video_sinkpad_stream_synchronizer);
pads_to_release = g_list_prepend (pads_to_release,
playsink->video_srcpad_stream_synchronizer);
playsink->video_sinkpad_stream_synchronizer = NULL;
gst_object_unref (playsink->video_srcpad_stream_synchronizer);
playsink->video_srcpad_stream_synchronizer = NULL;
}
if (playsink->audio_sinkpad_stream_synchronizer) {
gst_element_release_request_pad (GST_ELEMENT_CAST
(playsink->stream_synchronizer),
pads_to_release = g_list_prepend (pads_to_release,
playsink->audio_sinkpad_stream_synchronizer);
gst_object_unref (playsink->audio_sinkpad_stream_synchronizer);
pads_to_release = g_list_prepend (pads_to_release,
playsink->audio_srcpad_stream_synchronizer);
playsink->audio_sinkpad_stream_synchronizer = NULL;
gst_object_unref (playsink->audio_srcpad_stream_synchronizer);
playsink->audio_srcpad_stream_synchronizer = NULL;
gst_play_sink_remove_audio_ssync_queue (playsink);
remove_audio_ssync_queue = TRUE;
}
if (playsink->text_sinkpad_stream_synchronizer) {
gst_element_release_request_pad (GST_ELEMENT_CAST
(playsink->stream_synchronizer),
pads_to_release = g_list_prepend (pads_to_release,
playsink->text_sinkpad_stream_synchronizer);
gst_object_unref (playsink->text_sinkpad_stream_synchronizer);
pads_to_release = g_list_prepend (pads_to_release,
playsink->text_srcpad_stream_synchronizer);
playsink->text_sinkpad_stream_synchronizer = NULL;
gst_object_unref (playsink->text_srcpad_stream_synchronizer);
playsink->text_srcpad_stream_synchronizer = NULL;
}
GST_PLAY_SINK_UNLOCK (playsink);
for (GList * l = pads_to_release; l; l = l->next) {
GstPad *pad = GST_PAD (l->data);
if (GST_PAD_IS_SINK (pad)) {
gst_element_release_request_pad (GST_ELEMENT_CAST
(playsink->stream_synchronizer), pad);
}
gst_object_unref (pad);
}
g_list_free (pads_to_release);
if (remove_audio_ssync_queue) {
gst_play_sink_remove_audio_ssync_queue (playsink);
}
}
/* fall through */
case GST_STATE_CHANGE_READY_TO_NULL:

View File

@ -363,6 +363,7 @@ gst_stream_synchronizer_iterate_internal_links (GstPad * pad,
GstIterator *it = NULL;
GstPad *opad;
GST_STREAM_SYNCHRONIZER_LOCK (parent);
opad =
gst_stream_get_other_pad_from_pad (GST_STREAM_SYNCHRONIZER (parent), pad);
if (opad) {
@ -374,6 +375,7 @@ gst_stream_synchronizer_iterate_internal_links (GstPad * pad,
g_value_unset (&value);
gst_object_unref (opad);
}
GST_STREAM_SYNCHRONIZER_UNLOCK (parent);
return it;
}