From 73129b7d82147a528b85e6a1e6b441266db919ff Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 11:09:16 +0100 Subject: [PATCH 01/62] libs/base/idlesrc: minor format and comment fixups --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 01192fc82fe..8cba462329c 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -28,14 +28,17 @@ * @short_description: Base class for getrange based source elements * @see_also: #GstPushSrc, #GstBaseTransform, #GstBaseSink * - + * This is a generic base class for source elements that are mostly + * idle in its lifetime. + * + * Only push-mode is supported. + * TODO: Complete */ #ifdef HAVE_CONFIG_H # include "config.h" #endif -#include #include #include @@ -69,39 +72,36 @@ enum struct _GstBaseIdleSrcPrivate { /* if a stream-start event should be sent */ - gboolean stream_start_pending; /* STREAM_LOCK */ - + gboolean stream_start_pending; /* STREAM_LOCK */ /* if segment should be sent and a * seqnum if it was originated by a seek */ - gboolean segment_pending; /* OBJECT_LOCK */ - guint32 segment_seqnum; /* OBJECT_LOCK */ + gboolean segment_pending; /* OBJECT_LOCK */ + gboolean do_timestamp; /* OBJECT_LOCK */ + guint32 segment_seqnum; /* OBJECT_LOCK */ /* startup latency is the time it takes between going to PLAYING and producing * the first BUFFER with running_time 0. This value is included in the latency * reporting. */ - GstClockTime latency; /* OBJECT_LOCK */ + GstClockTime latency; /* OBJECT_LOCK */ /* timestamp offset, this is the offset add to the values of gst_times for * pseudo live sources */ - GstClockTimeDiff ts_offset; /* OBJECT_LOCK */ - - gboolean do_timestamp; /* OBJECT_LOCK */ - - /* QoS *//* with LOCK */ - gdouble proportion; /* OBJECT_LOCK */ - GstClockTime earliest_time; /* OBJECT_LOCK */ - - GstBufferPool *pool; /* OBJECT_LOCK */ - GstAllocator *allocator; /* OBJECT_LOCK */ - GstAllocationParams params; /* OBJECT_LOCK */ + GstClockTimeDiff ts_offset; /* OBJECT_LOCK */ + /* QoS */ + gdouble proportion; /* OBJECT_LOCK */ + GstClockTime earliest_time; /* OBJECT_LOCK */ + GstBufferPool *pool; /* OBJECT_LOCK */ + GstAllocator *allocator; /* OBJECT_LOCK */ GQueue *obj_queue; GThread *thread; + + GstAllocationParams params; /* OBJECT_LOCK */ }; static GstElementClass *parent_class = NULL; static gint private_offset = 0; - +/* TODO: Should this be an vmethod? */ static void gst_base_idle_src_start_task (GstBaseIdleSrc * src, gboolean wait); static void gst_base_idle_src_process_object_queue (GstBaseIdleSrc * src); @@ -1543,8 +1543,7 @@ gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, ret = GST_FLOW_OK; } else { - GST_WARNING_OBJECT (src, "Not trying to alloc %u bytes. Blocksize not set?", - size); + GST_WARNING_OBJECT (src, "Not trying to alloc %" G_GSIZE_FORMAT " bytes. Blocksize not set?", size); goto alloc_failed; } @@ -1559,7 +1558,7 @@ gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, /* ERRORS */ alloc_failed: { - GST_ERROR_OBJECT (src, "Failed to allocate %u bytes", size); + GST_ERROR_OBJECT (src, "Failed to allocate %" G_GSIZE_FORMAT " bytes", size); ret = GST_FLOW_ERROR; goto done; } @@ -1589,8 +1588,8 @@ gst_base_idle_src_class_init (GstBaseIdleSrcClass * klass) if (private_offset != 0) g_type_class_adjust_private_offset (klass, &private_offset); - GST_DEBUG_CATEGORY_INIT (gst_base_idle_src_debug, "idlesrc", 0, - "idlesrc element"); + GST_DEBUG_CATEGORY_INIT (gst_base_idle_src_debug, "baseidlesrc", 0, + "base idlesrc element"); parent_class = g_type_class_peek_parent (klass); @@ -1615,7 +1614,6 @@ gst_base_idle_src_class_init (GstBaseIdleSrcClass * klass) GST_DEBUG_FUNCPTR (gst_base_idle_src_decide_allocation_default); /* Registering debug symbols for function pointers */ - GST_DEBUG_REGISTER_FUNCPTR (gst_base_idle_src_event); GST_DEBUG_REGISTER_FUNCPTR (gst_base_idle_src_query); GST_DEBUG_REGISTER_FUNCPTR (gst_base_idle_src_fixate); From 030a2d93aeca391f799d75dc839ce4363c118090 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 11:19:42 +0100 Subject: [PATCH 02/62] libs/base/idlesrc: implement a dispose method --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 8cba462329c..a6473f3f598 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1564,6 +1564,19 @@ gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, } } +static void +gst_base_idle_src_dispose (GObject * object) +{ + GstBaseIdleSrc *src; + src = GST_BASE_IDLE_SRC (object); + (void) src; + + /* FIXME: empty this queue potentially... */ + // g_queue_clear (src->priv->obj_queue); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + static void gst_base_idle_src_finalize (GObject * object) { @@ -1571,7 +1584,6 @@ gst_base_idle_src_finalize (GObject * object) src = GST_BASE_IDLE_SRC (object); g_queue_free (src->priv->obj_queue); - /* FIXME: empty this queue potentially... */ G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -1593,6 +1605,7 @@ gst_base_idle_src_class_init (GstBaseIdleSrcClass * klass) parent_class = g_type_class_peek_parent (klass); + gobject_class->dispose = gst_base_idle_src_dispose; gobject_class->finalize = gst_base_idle_src_finalize; gobject_class->set_property = gst_base_idle_src_set_property; gobject_class->get_property = gst_base_idle_src_get_property; From 8f74b1e12c9b357032df586e7ea026ab4e9c8063 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 11:42:57 +0100 Subject: [PATCH 03/62] libs/base/idlesrc: assert format is either buffer/bytes --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index a6473f3f598..0b1db1d833c 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -111,7 +111,6 @@ gst_base_idle_src_get_instance_private (GstBaseIdleSrc * self) return (G_STRUCT_MEMBER_P (self, private_offset)); } -/* TODO: do we support anything other than _BUFFER/_BYTES ? */ /** * gst_base_idle_src_set_format: * @src: base source instance @@ -130,6 +129,7 @@ gst_base_idle_src_set_format (GstBaseIdleSrc * src, GstFormat format) { g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); g_return_if_fail (GST_STATE (src) <= GST_STATE_READY); + g_return_if_fail (GST_FORMAT_BUFFERS == format || GST_FORMAT_BYTES == format); GST_OBJECT_LOCK (src); gst_segment_init (&src->segment, format); From 85b17c2571be4a86bbc296056ede0aee26a48868 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 11:49:07 +0100 Subject: [PATCH 04/62] libs/base/idlesrc: change alloc_buffer size data-type --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 6 +++--- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 0b1db1d833c..4e33f311ea1 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1517,7 +1517,7 @@ gst_base_idle_src_submit_buffer_list (GstBaseIdleSrc * src, */ GstFlowReturn gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, - gsize size, GstBuffer ** buffer) + guint size, GstBuffer ** buffer) { GstFlowReturn ret; GstBaseIdleSrcPrivate *priv = src->priv; @@ -1543,7 +1543,7 @@ gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, ret = GST_FLOW_OK; } else { - GST_WARNING_OBJECT (src, "Not trying to alloc %" G_GSIZE_FORMAT " bytes. Blocksize not set?", size); + GST_WARNING_OBJECT (src, "Not trying to alloc %u bytes. Blocksize not set?", size); goto alloc_failed; } @@ -1558,7 +1558,7 @@ gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, /* ERRORS */ alloc_failed: { - GST_ERROR_OBJECT (src, "Failed to allocate %" G_GSIZE_FORMAT " bytes", size); + GST_ERROR_OBJECT (src, "Failed to allocate %u bytes", size); ret = GST_FLOW_ERROR; goto done; } diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h index cf9fea5660a..142589909ed 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h @@ -210,7 +210,7 @@ void gst_base_idle_src_submit_buffer_list (GstBaseIdleSrc * src, GST_BASE_API GstFlowReturn gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, - gsize size, + guint size, GstBuffer ** buffer); G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstBaseIdleSrc, gst_object_unref) From dcb24b9fae1653348fcbd41eea85b53b8868ade9 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 12:11:07 +0100 Subject: [PATCH 05/62] libs/base/idlesrc: standarize comments --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 4e33f311ea1..8bb260ec23d 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1210,16 +1210,17 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) GST_DEBUG_OBJECT (src, "About to push Buffer %" GST_PTR_FORMAT, buf); ret = gst_pad_push (pad, buf); - if (ret != GST_FLOW_OK) + if (ret != GST_FLOW_OK) { GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); + } } else if (GST_IS_EVENT (obj)) { GstEvent *event = GST_EVENT_CAST (obj); gboolean ret; - GST_DEBUG_OBJECT (src, "About to push Event %" GST_PTR_FORMAT, event); ret = gst_pad_push_event (pad, event); - if (!ret) - GST_ERROR ("HUUUUAA"); + if (!ret) { + GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); + } } else if (GST_IS_CAPS (obj)) { GST_DEBUG_OBJECT (src, "About to push Caps %" GST_PTR_FORMAT, obj); } From a66cda77576502b47b6a065fa9aba5f7a3195247 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 12:26:09 +0100 Subject: [PATCH 06/62] libs/base/idlesrc: remove unecessary check if the object queue has a caps object, as we only push events or buffers to it --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 8bb260ec23d..840fa232f67 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1195,13 +1195,13 @@ gst_base_idle_src_add_timestamp (GstBaseIdleSrc * src, GstBuffer * buf) static void gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) { + GstFlowReturn ret; GstPad *pad = src->srcpad; GST_PAD_STREAM_LOCK (pad); if (GST_IS_BUFFER (obj)) { GstBuffer *buf = GST_BUFFER_CAST (obj); - GstFlowReturn ret; if (src->priv->do_timestamp) { gst_base_idle_src_add_timestamp (src, buf); @@ -1221,8 +1221,6 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) if (!ret) { GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); } - } else if (GST_IS_CAPS (obj)) { - GST_DEBUG_OBJECT (src, "About to push Caps %" GST_PTR_FORMAT, obj); } GST_PAD_STREAM_UNLOCK (pad); From 317505852a6dea4d8a1065d3f5a61597e767469b Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 14:55:54 +0100 Subject: [PATCH 07/62] libs/base/idlesrc: properly support buffer lists --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 23 ++++++++++++- .../gstreamer/tests/check/libs/baseidlesrc.c | 34 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 840fa232f67..0c3552daded 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1192,6 +1192,13 @@ gst_base_idle_src_add_timestamp (GstBaseIdleSrc * src, GstBuffer * buf) GST_BUFFER_DTS (buf) = running_time; } +static gboolean +gst_base_idle_src_buffer_list_add_timestamp_func (GstBuffer ** buf, guint idx, gpointer user_data) +{ + GstBaseIdleSrc * src = GST_BASE_IDLE_SRC (user_data); + gst_base_idle_src_add_timestamp (src, *buf); +} + static void gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) { @@ -1213,6 +1220,19 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) if (ret != GST_FLOW_OK) { GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); } + } else if (GST_IS_BUFFER_LIST (obj)) { + GstBufferList *buf_list = GST_BUFFER_LIST_CAST (obj); + + if (src->priv->do_timestamp) { + gst_buffer_list_foreach (buf_list, gst_base_idle_src_buffer_list_add_timestamp_func, src); + } + + GST_DEBUG_OBJECT (src, "About to push BufferList %" GST_PTR_FORMAT, buf_list); + + ret = gst_pad_push (pad, buf_list); + if (ret != GST_FLOW_OK) { + GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); + } } else if (GST_IS_EVENT (obj)) { GstEvent *event = GST_EVENT_CAST (obj); gboolean ret; @@ -1221,6 +1241,8 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) if (!ret) { GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); } + } else { + GST_ERROR_OBJECT (src, "Unknown object %" GST_PTR_FORMAT " type", obj); } GST_PAD_STREAM_UNLOCK (pad); @@ -1503,7 +1525,6 @@ gst_base_idle_src_submit_buffer_list (GstBaseIdleSrc * src, gst_base_idle_src_start_task (src, FALSE); } - /** * gst_base_idle_src_alloc_buffer: * @src: a #GstBaseIdleSrc diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 7d7aa1a4a39..6fa267f7b04 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -96,6 +96,39 @@ GST_START_TEST (baseidlesrc_submit_buffer) GST_END_TEST; +GST_START_TEST (baseidlesrc_submit_buffer_list) +{ + GstElement *src; + GstHarness *h; + GstBaseIdleSrc *base_src; + GstBuffer *buf; + GstBufferList *buf_list; + guint i; + + src = g_object_new (test_idle_src_get_type (), NULL); + base_src = GST_BASE_IDLE_SRC (src); + + h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); + + gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_play (h); + + buf_list = gst_buffer_list_new_sized (20); + + for (i = 0; i < 5; i++) { + fail_unless_equals_int (GST_FLOW_OK, + gst_base_idle_src_alloc_buffer (base_src, 100, &buf)); + gst_buffer_list_insert (buf_list, -1, buf); + } + + gst_base_idle_src_submit_buffer_list (base_src, buf_list); + gst_buffer_list_unref (gst_harness_pull_list (h)); + + gst_harness_teardown (h); +} + +GST_END_TEST; + static Suite * baseidlesrc_suite (void) { @@ -105,6 +138,7 @@ baseidlesrc_suite (void) suite_add_tcase (s, tc); tcase_add_test (tc, baseidlesrc_up_and_down); tcase_add_test (tc, baseidlesrc_submit_buffer); + tcase_add_test (tc, baseidlesrc_submit_buffer_list); return s; } From 52b17dda24a630a4b98602854434678e8934c785 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 15:32:14 +0100 Subject: [PATCH 08/62] libs/base/idlesrc: correct flow-return on pushes --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 21 ++++----- .../gstreamer/tests/check/libs/baseidlesrc.c | 47 ++++++++++++++++++- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 0c3552daded..5aa92250548 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1215,11 +1215,8 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) } GST_DEBUG_OBJECT (src, "About to push Buffer %" GST_PTR_FORMAT, buf); - ret = gst_pad_push (pad, buf); - if (ret != GST_FLOW_OK) { - GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); - } + goto check_ret_error; } else if (GST_IS_BUFFER_LIST (obj)) { GstBufferList *buf_list = GST_BUFFER_LIST_CAST (obj); @@ -1228,23 +1225,25 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) } GST_DEBUG_OBJECT (src, "About to push BufferList %" GST_PTR_FORMAT, buf_list); - ret = gst_pad_push (pad, buf_list); - if (ret != GST_FLOW_OK) { - GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); - } + goto check_ret_error; } else if (GST_IS_EVENT (obj)) { GstEvent *event = GST_EVENT_CAST (obj); gboolean ret; + GST_DEBUG_OBJECT (src, "About to push Event %" GST_PTR_FORMAT, event); ret = gst_pad_push_event (pad, event); - if (!ret) { - GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); - } + goto check_ret_error; } else { GST_ERROR_OBJECT (src, "Unknown object %" GST_PTR_FORMAT " type", obj); } +check_ret_error: + if (ret != GST_FLOW_OK && ret != GST_FLOW_FLUSHING) { + // XXX: Should we proxy this into the bus in case or error or just ignore? + GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); + } + GST_PAD_STREAM_UNLOCK (pad); } diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 6fa267f7b04..e69db0aad5e 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -81,14 +81,19 @@ GST_START_TEST (baseidlesrc_submit_buffer) h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); - gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_set_sink_caps_str (h, "video/x-raw,format=RGB,width=1,height=1"); gst_harness_play (h); for (i = 0; i < 5; i++) { fail_unless_equals_int (GST_FLOW_OK, gst_base_idle_src_alloc_buffer (base_src, 100, &buf)); + GST_BUFFER_PTS (buf) = i * GST_MSECOND; gst_base_idle_src_submit_buffer (base_src, buf); - gst_buffer_unref (gst_harness_pull (h)); + + buf = gst_harness_pull (h); + fail_unless (buf != NULL); + fail_unless_equals_uint64 (GST_BUFFER_PTS (buf), i * GST_MSECOND); + gst_buffer_unref (buf); } gst_harness_teardown (h); @@ -129,6 +134,43 @@ GST_START_TEST (baseidlesrc_submit_buffer_list) GST_END_TEST; +static void +fail_unless_equals_event_type (const GstEvent * event, GstEventType expected_type) +{ + fail_unless (GST_EVENT_TYPE (event), expected_type, "'%s' expected, got '%s'", gst_event_type_get_name (expected_type), gst_event_type_get_name(GST_EVENT_TYPE (event))); +} + +GST_START_TEST (baseidlesrc_handle_eos) +{ + GstElement *src; + GstHarness *h; + GstBaseIdleSrc *base_src; + GstBuffer *buf; + + src = g_object_new (test_idle_src_get_type (), NULL); + base_src = GST_BASE_IDLE_SRC (src); + + h = gst_harness_new_with_element (src, NULL, "src"); + gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_play (h); + + /* push one buffer and then EOS */ + fail_unless_equals_int (GST_FLOW_OK, + gst_base_idle_src_alloc_buffer (base_src, 64, &buf)); + gst_base_idle_src_submit_buffer (base_src, buf); + + gst_element_send_event (src, gst_event_new_eos ()); + + GstEvent *event = gst_harness_pull_event (h); + fail_unless_equals_event_type (event, GST_EVENT_EOS); + gst_event_unref (event); + + gst_buffer_unref (gst_harness_pull (h)); + + gst_harness_teardown (h); +} +GST_END_TEST; + static Suite * baseidlesrc_suite (void) { @@ -139,6 +181,7 @@ baseidlesrc_suite (void) tcase_add_test (tc, baseidlesrc_up_and_down); tcase_add_test (tc, baseidlesrc_submit_buffer); tcase_add_test (tc, baseidlesrc_submit_buffer_list); + tcase_add_test (tc, baseidlesrc_handle_eos); return s; } From d2c236e74b9ada1c60a9bf3be043076c2eacabf6 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 15:50:41 +0100 Subject: [PATCH 09/62] libs/base/idlesrc: indent --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 36 ++++++++++--------- .../gstreamer/tests/check/libs/baseidlesrc.c | 8 +++-- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 5aa92250548..04448b2fbd2 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -72,30 +72,30 @@ enum struct _GstBaseIdleSrcPrivate { /* if a stream-start event should be sent */ - gboolean stream_start_pending; /* STREAM_LOCK */ + gboolean stream_start_pending; /* STREAM_LOCK */ /* if segment should be sent and a * seqnum if it was originated by a seek */ - gboolean segment_pending; /* OBJECT_LOCK */ - gboolean do_timestamp; /* OBJECT_LOCK */ - guint32 segment_seqnum; /* OBJECT_LOCK */ + gboolean segment_pending; /* OBJECT_LOCK */ + gboolean do_timestamp; /* OBJECT_LOCK */ + guint32 segment_seqnum; /* OBJECT_LOCK */ /* startup latency is the time it takes between going to PLAYING and producing * the first BUFFER with running_time 0. This value is included in the latency * reporting. */ - GstClockTime latency; /* OBJECT_LOCK */ + GstClockTime latency; /* OBJECT_LOCK */ /* timestamp offset, this is the offset add to the values of gst_times for * pseudo live sources */ - GstClockTimeDiff ts_offset; /* OBJECT_LOCK */ + GstClockTimeDiff ts_offset; /* OBJECT_LOCK */ /* QoS */ - gdouble proportion; /* OBJECT_LOCK */ - GstClockTime earliest_time; /* OBJECT_LOCK */ + gdouble proportion; /* OBJECT_LOCK */ + GstClockTime earliest_time; /* OBJECT_LOCK */ - GstBufferPool *pool; /* OBJECT_LOCK */ - GstAllocator *allocator; /* OBJECT_LOCK */ + GstBufferPool *pool; /* OBJECT_LOCK */ + GstAllocator *allocator; /* OBJECT_LOCK */ GQueue *obj_queue; GThread *thread; - GstAllocationParams params; /* OBJECT_LOCK */ + GstAllocationParams params; /* OBJECT_LOCK */ }; static GstElementClass *parent_class = NULL; @@ -1193,9 +1193,10 @@ gst_base_idle_src_add_timestamp (GstBaseIdleSrc * src, GstBuffer * buf) } static gboolean -gst_base_idle_src_buffer_list_add_timestamp_func (GstBuffer ** buf, guint idx, gpointer user_data) +gst_base_idle_src_buffer_list_add_timestamp_func (GstBuffer ** buf, guint idx, + gpointer user_data) { - GstBaseIdleSrc * src = GST_BASE_IDLE_SRC (user_data); + GstBaseIdleSrc *src = GST_BASE_IDLE_SRC (user_data); gst_base_idle_src_add_timestamp (src, *buf); } @@ -1221,10 +1222,12 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) GstBufferList *buf_list = GST_BUFFER_LIST_CAST (obj); if (src->priv->do_timestamp) { - gst_buffer_list_foreach (buf_list, gst_base_idle_src_buffer_list_add_timestamp_func, src); + gst_buffer_list_foreach (buf_list, + gst_base_idle_src_buffer_list_add_timestamp_func, src); } - GST_DEBUG_OBJECT (src, "About to push BufferList %" GST_PTR_FORMAT, buf_list); + GST_DEBUG_OBJECT (src, "About to push BufferList %" GST_PTR_FORMAT, + buf_list); ret = gst_pad_push (pad, buf_list); goto check_ret_error; } else if (GST_IS_EVENT (obj)) { @@ -1562,7 +1565,8 @@ gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, ret = GST_FLOW_OK; } else { - GST_WARNING_OBJECT (src, "Not trying to alloc %u bytes. Blocksize not set?", size); + GST_WARNING_OBJECT (src, "Not trying to alloc %u bytes. Blocksize not set?", + size); goto alloc_failed; } diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index e69db0aad5e..5fb0b130387 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -135,9 +135,12 @@ GST_START_TEST (baseidlesrc_submit_buffer_list) GST_END_TEST; static void -fail_unless_equals_event_type (const GstEvent * event, GstEventType expected_type) +fail_unless_equals_event_type (const GstEvent * event, + GstEventType expected_type) { - fail_unless (GST_EVENT_TYPE (event), expected_type, "'%s' expected, got '%s'", gst_event_type_get_name (expected_type), gst_event_type_get_name(GST_EVENT_TYPE (event))); + fail_unless (GST_EVENT_TYPE (event), expected_type, "'%s' expected, got '%s'", + gst_event_type_get_name (expected_type), + gst_event_type_get_name (GST_EVENT_TYPE (event))); } GST_START_TEST (baseidlesrc_handle_eos) @@ -169,6 +172,7 @@ GST_START_TEST (baseidlesrc_handle_eos) gst_harness_teardown (h); } + GST_END_TEST; static Suite * From 08b63a8cdbc90c81d8585506c3e1b2fe6eead119 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 16:01:21 +0100 Subject: [PATCH 10/62] base/libs/idlesrc: use correct push API --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 04448b2fbd2..9cc4f0d900b 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1228,7 +1228,7 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) GST_DEBUG_OBJECT (src, "About to push BufferList %" GST_PTR_FORMAT, buf_list); - ret = gst_pad_push (pad, buf_list); + ret = gst_pad_push_list (pad, buf_list); goto check_ret_error; } else if (GST_IS_EVENT (obj)) { GstEvent *event = GST_EVENT_CAST (obj); From b655934c6f410e72eb3a46b25633958515ba99df Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 25 Oct 2025 16:14:32 +0100 Subject: [PATCH 11/62] libs/base/idlesrc: log task lifetime --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 9cc4f0d900b..9a266c14f51 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1256,7 +1256,6 @@ gst_base_idle_src_process_object_queue (GstBaseIdleSrc * src) GstMiniObject *obj; GST_OBJECT_LOCK (src); while ((obj = g_queue_pop_head (src->priv->obj_queue))) { - GST_OBJECT_UNLOCK (src); gst_base_idle_src_process_object (src, obj); GST_OBJECT_LOCK (src); @@ -1290,14 +1289,18 @@ gst_base_idle_src_func (gpointer user_data) static void gst_base_idle_src_start_task (GstBaseIdleSrc * src, gboolean wait) { - GST_DEBUG_OBJECT (src, "Starting Task"); + GST_DEBUG_OBJECT (src, "Starting task"); /* if we already have an outstanding task, join it */ - if (src->priv->thread) + if (src->priv->thread) { + GST_DEBUG_OBJECT (src, "Waiting for outstanding task to finish"); g_thread_join (src->priv->thread); + } src->priv->thread = g_thread_new (GST_ELEMENT_NAME (src), gst_base_idle_src_func, src); + if (wait) { + GST_DEBUG_OBJECT (src, "Waiting for task to finish"); g_thread_join (src->priv->thread); src->priv->thread = NULL; } From 85098aa8274260df90e029ce36bdfec49a020cb3 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sun, 26 Oct 2025 10:50:49 +0000 Subject: [PATCH 12/62] lib/base/idlesrc: fix up uninitialized valgrind warning --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 9a266c14f51..9f86e0cd170 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1203,7 +1203,7 @@ gst_base_idle_src_buffer_list_add_timestamp_func (GstBuffer ** buf, guint idx, static void gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) { - GstFlowReturn ret; + GstFlowReturn ret = GST_FLOW_OK; GstPad *pad = src->srcpad; GST_PAD_STREAM_LOCK (pad); From d6a4aef8618560d99daf6c3ce3db192c0376328e Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sun, 26 Oct 2025 11:43:34 +0000 Subject: [PATCH 13/62] lib/base/idlesrc: fix up event test --- .../gstreamer/tests/check/libs/baseidlesrc.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 5fb0b130387..d79f795a266 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -138,17 +138,18 @@ static void fail_unless_equals_event_type (const GstEvent * event, GstEventType expected_type) { - fail_unless (GST_EVENT_TYPE (event), expected_type, "'%s' expected, got '%s'", - gst_event_type_get_name (expected_type), + fail_unless (GST_EVENT_TYPE (event) == expected_type, + "'%s' expected, got '%s'", gst_event_type_get_name (expected_type), gst_event_type_get_name (GST_EVENT_TYPE (event))); } -GST_START_TEST (baseidlesrc_handle_eos) +GST_START_TEST (baseidlesrc_handle_events) { GstElement *src; GstHarness *h; GstBaseIdleSrc *base_src; GstBuffer *buf; + GstEvent *event; src = g_object_new (test_idle_src_get_type (), NULL); base_src = GST_BASE_IDLE_SRC (src); @@ -157,15 +158,16 @@ GST_START_TEST (baseidlesrc_handle_eos) gst_harness_set_sink_caps_str (h, "foo/bar"); gst_harness_play (h); - /* push one buffer and then EOS */ fail_unless_equals_int (GST_FLOW_OK, gst_base_idle_src_alloc_buffer (base_src, 64, &buf)); gst_base_idle_src_submit_buffer (base_src, buf); - gst_element_send_event (src, gst_event_new_eos ()); + event = gst_harness_pull_event (h); + fail_unless_equals_event_type (event, GST_EVENT_STREAM_START); + gst_event_unref (event); - GstEvent *event = gst_harness_pull_event (h); - fail_unless_equals_event_type (event, GST_EVENT_EOS); + event = gst_harness_pull_event (h); + fail_unless_equals_event_type (event, GST_EVENT_SEGMENT); gst_event_unref (event); gst_buffer_unref (gst_harness_pull (h)); @@ -185,7 +187,7 @@ baseidlesrc_suite (void) tcase_add_test (tc, baseidlesrc_up_and_down); tcase_add_test (tc, baseidlesrc_submit_buffer); tcase_add_test (tc, baseidlesrc_submit_buffer_list); - tcase_add_test (tc, baseidlesrc_handle_eos); + tcase_add_test (tc, baseidlesrc_handle_events); return s; } From f949905a9c3674a07eeb3d19ae0127376de1d7c9 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sun, 26 Oct 2025 12:11:35 +0000 Subject: [PATCH 14/62] lib/base/idlesrc: rename buffer pool variable --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 114 +++++++++--------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 9f86e0cd170..b50974d6c7e 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -90,7 +90,7 @@ struct _GstBaseIdleSrcPrivate gdouble proportion; /* OBJECT_LOCK */ GstClockTime earliest_time; /* OBJECT_LOCK */ - GstBufferPool *pool; /* OBJECT_LOCK */ + GstBufferPool *buf_pool; /* OBJECT_LOCK */ GstAllocator *allocator; /* OBJECT_LOCK */ GQueue *obj_queue; GThread *thread; @@ -612,16 +612,16 @@ static void gst_base_idle_src_set_pool_flushing (GstBaseIdleSrc * src, gboolean flushing) { GstBaseIdleSrcPrivate *priv = src->priv; - GstBufferPool *pool; + GstBufferPool *buf_pool; GST_OBJECT_LOCK (src); - if ((pool = priv->pool)) - pool = gst_object_ref (pool); + if ((buf_pool = priv->buf_pool)) + buf_pool = gst_object_ref (buf_pool); GST_OBJECT_UNLOCK (src); - if (pool) { - gst_buffer_pool_set_flushing (pool, flushing); - gst_object_unref (pool); + if (buf_pool) { + gst_buffer_pool_set_flushing (buf_pool, flushing); + gst_object_unref (buf_pool); } } @@ -809,28 +809,28 @@ gst_base_idle_src_get_property (GObject * object, guint prop_id, GValue * value, static gboolean gst_base_idle_src_set_allocation (GstBaseIdleSrc * src, - GstBufferPool * pool, GstAllocator * allocator, + GstBufferPool * buf_pool, GstAllocator * allocator, const GstAllocationParams * params) { - GstAllocator *oldalloc; - GstBufferPool *oldpool; + GstAllocator *old_alloc; + GstBufferPool *old_buf_pool; GstBaseIdleSrcPrivate *priv = src->priv; - if (pool) { - GST_DEBUG_OBJECT (src, "activate pool"); - if (!gst_buffer_pool_set_active (pool, TRUE)) + if (buf_pool) { + GST_DEBUG_OBJECT (src, "activate buffer pool"); + if (!gst_buffer_pool_set_active (buf_pool, TRUE)) goto activate_failed; } GST_OBJECT_LOCK (src); - oldpool = priv->pool; - priv->pool = pool; + old_buf_pool = priv->buf_pool; + priv->buf_pool = buf_pool; - oldalloc = priv->allocator; + old_alloc = priv->allocator; priv->allocator = allocator; - if (priv->pool) - gst_object_ref (priv->pool); + if (priv->buf_pool) + gst_object_ref (priv->buf_pool); if (priv->allocator) gst_object_ref (priv->allocator); @@ -840,16 +840,16 @@ gst_base_idle_src_set_allocation (GstBaseIdleSrc * src, gst_allocation_params_init (&priv->params); GST_OBJECT_UNLOCK (src); - if (oldpool) { + if (old_buf_pool) { /* only deactivate if the pool is not the one we're using */ - if (oldpool != pool) { + if (old_buf_pool != buf_pool) { GST_DEBUG_OBJECT (src, "deactivate old pool"); - gst_buffer_pool_set_active (oldpool, FALSE); + gst_buffer_pool_set_active (old_buf_pool, FALSE); } - gst_object_unref (oldpool); + gst_object_unref (old_buf_pool); } - if (oldalloc) { - gst_object_unref (oldalloc); + if (old_alloc) { + gst_object_unref (old_alloc); } return TRUE; @@ -866,7 +866,7 @@ gst_base_idle_src_decide_allocation_default (GstBaseIdleSrc * src, GstQuery * query) { GstCaps *outcaps; - GstBufferPool *pool; + GstBufferPool *buf_pool; guint size, min, max; GstAllocator *allocator; GstAllocationParams params; @@ -888,40 +888,41 @@ gst_base_idle_src_decide_allocation_default (GstBaseIdleSrc * src, } if (gst_query_get_n_allocation_pools (query) > 0) { - gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); + gst_query_parse_nth_allocation_pool (query, 0, &buf_pool, &size, &min, + &max); - if (pool == NULL) { + if (buf_pool == NULL) { /* no pool, we can make our own */ - GST_DEBUG_OBJECT (src, "no pool, making new pool"); - pool = gst_buffer_pool_new (); + GST_DEBUG_OBJECT (src, "no buffer pool, making new one"); + buf_pool = gst_buffer_pool_new (); } } else { - pool = NULL; + buf_pool = NULL; size = min = max = 0; } /* now configure */ - if (pool) { - config = gst_buffer_pool_get_config (pool); + if (buf_pool) { + config = gst_buffer_pool_get_config (buf_pool); gst_buffer_pool_config_set_params (config, outcaps, size, min, max); gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); /* buffer pool may have to do some changes */ - if (!gst_buffer_pool_set_config (pool, config)) { - config = gst_buffer_pool_get_config (pool); + if (!gst_buffer_pool_set_config (buf_pool, config)) { + config = gst_buffer_pool_get_config (buf_pool); /* If change are not acceptable, fallback to generic pool */ if (!gst_buffer_pool_config_validate_params (config, outcaps, size, min, max)) { - GST_DEBUG_OBJECT (src, "unsupported pool, making new pool"); + GST_DEBUG_OBJECT (src, "unsupported buffer pool, making new one"); - gst_object_unref (pool); - pool = gst_buffer_pool_new (); + gst_object_unref (buf_pool); + buf_pool = gst_buffer_pool_new (); gst_buffer_pool_config_set_params (config, outcaps, size, min, max); gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); } - if (!gst_buffer_pool_set_config (pool, config)) + if (!gst_buffer_pool_set_config (buf_pool, config)) goto config_failed; } } @@ -933,9 +934,9 @@ gst_base_idle_src_decide_allocation_default (GstBaseIdleSrc * src, if (allocator) gst_object_unref (allocator); - if (pool) { - gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); - gst_object_unref (pool); + if (buf_pool) { + gst_query_set_nth_allocation_pool (query, 0, buf_pool, size, min, max); + gst_object_unref (buf_pool); } return TRUE; @@ -944,7 +945,7 @@ gst_base_idle_src_decide_allocation_default (GstBaseIdleSrc * src, GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, ("Failed to configure the buffer pool"), ("Configuration is most likely invalid, please report this issue.")); - gst_object_unref (pool); + gst_object_unref (buf_pool); return FALSE; } @@ -954,7 +955,7 @@ gst_base_idle_src_prepare_allocation (GstBaseIdleSrc * src, GstCaps * caps) GstBaseIdleSrcClass *bclass; gboolean result = TRUE; GstQuery *query; - GstBufferPool *pool = NULL; + GstBufferPool *buf_pool = NULL; GstAllocator *allocator = NULL; GstAllocationParams params; @@ -988,14 +989,14 @@ gst_base_idle_src_prepare_allocation (GstBaseIdleSrc * src, GstCaps * caps) } if (gst_query_get_n_allocation_pools (query) > 0) - gst_query_parse_nth_allocation_pool (query, 0, &pool, NULL, NULL, NULL); + gst_query_parse_nth_allocation_pool (query, 0, &buf_pool, NULL, NULL, NULL); - result = gst_base_idle_src_set_allocation (src, pool, allocator, ¶ms); + result = gst_base_idle_src_set_allocation (src, buf_pool, allocator, ¶ms); if (allocator) gst_object_unref (allocator); - if (pool) - gst_object_unref (pool); + if (buf_pool) + gst_object_unref (buf_pool); gst_query_unref (query); @@ -1134,8 +1135,8 @@ gst_base_idle_src_get_buffer_pool (GstBaseIdleSrc * src) g_return_val_if_fail (GST_IS_BASE_IDLE_SRC (src), NULL); GST_OBJECT_LOCK (src); - if (src->priv->pool) - ret = gst_object_ref (src->priv->pool); + if (src->priv->buf_pool) + ret = gst_object_ref (src->priv->buf_pool); GST_OBJECT_UNLOCK (src); return ret; @@ -1198,6 +1199,7 @@ gst_base_idle_src_buffer_list_add_timestamp_func (GstBuffer ** buf, guint idx, { GstBaseIdleSrc *src = GST_BASE_IDLE_SRC (user_data); gst_base_idle_src_add_timestamp (src, *buf); + return TRUE; } static void @@ -1546,21 +1548,21 @@ gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, { GstFlowReturn ret; GstBaseIdleSrcPrivate *priv = src->priv; - GstBufferPool *pool = NULL; + GstBufferPool *buf_pool = NULL; GstAllocator *allocator = NULL; GstAllocationParams params; GST_OBJECT_LOCK (src); - if (priv->pool) { - pool = gst_object_ref (priv->pool); + if (priv->buf_pool) { + buf_pool = gst_object_ref (priv->buf_pool); } else if (priv->allocator) { allocator = gst_object_ref (priv->allocator); } params = priv->params; GST_OBJECT_UNLOCK (src); - if (pool) { - ret = gst_buffer_pool_acquire_buffer (pool, buffer, NULL); + if (buf_pool) { + ret = gst_buffer_pool_acquire_buffer (buf_pool, buffer, NULL); } else if (size != -1) { *buffer = gst_buffer_new_allocate (allocator, size, ¶ms); if (G_UNLIKELY (*buffer == NULL)) @@ -1574,8 +1576,8 @@ gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, } done: - if (pool) - gst_object_unref (pool); + if (buf_pool) + gst_object_unref (buf_pool); if (allocator) gst_object_unref (allocator); From 76b13a1350e8009f6d87cbaaba03584e520c93a7 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Wed, 29 Oct 2025 19:35:17 +0900 Subject: [PATCH 15/62] libs/base/idlesrc: also support TIME format --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index b50974d6c7e..e88401df567 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -117,10 +117,7 @@ gst_base_idle_src_get_instance_private (GstBaseIdleSrc * self) * @format: the format to use * * Sets the default format of the source. This will be the format used - * for sending SEGMENT events and for performing seeks. - * - * If a format of GST_FORMAT_BYTES is set, the element will be able to - * operate in pull mode if the #GstBaseIdleSrcClass::is_seekable returns %TRUE. + * for sending SEGMENT events. * * This function must only be called in states < %GST_STATE_PAUSED. */ @@ -129,7 +126,8 @@ gst_base_idle_src_set_format (GstBaseIdleSrc * src, GstFormat format) { g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); g_return_if_fail (GST_STATE (src) <= GST_STATE_READY); - g_return_if_fail (GST_FORMAT_BUFFERS == format || GST_FORMAT_BYTES == format); + g_return_if_fail (GST_FORMAT_BUFFERS == format || GST_FORMAT_BYTES == format + || GST_FORMAT_TIME == format); GST_OBJECT_LOCK (src); gst_segment_init (&src->segment, format); From 7f298bb59015e97f9495b225c24a4251e5164752 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Wed, 29 Oct 2025 23:06:44 +0900 Subject: [PATCH 16/62] libs/base/idlesrc: actually set a flow error if we fail to push an event --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index e88401df567..a630c6df795 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1232,10 +1232,11 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) goto check_ret_error; } else if (GST_IS_EVENT (obj)) { GstEvent *event = GST_EVENT_CAST (obj); - gboolean ret; - GST_DEBUG_OBJECT (src, "About to push Event %" GST_PTR_FORMAT, event); - ret = gst_pad_push_event (pad, event); + if (!gst_pad_push_event (pad, event)) { + /* TODO: What is the right thing to do here?! */ + ret = GST_FLOW_CUSTOM_ERROR; + } goto check_ret_error; } else { GST_ERROR_OBJECT (src, "Unknown object %" GST_PTR_FORMAT " type", obj); From fa3fe03bb37fb7e23d8fa48688a4b1764208844e Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Wed, 29 Oct 2025 23:06:16 +0900 Subject: [PATCH 17/62] libs/base/idlesrc: implement basic use of shared thread-pools --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 104 ++++++++++++--- .../gstreamer/libs/gst/base/gstbaseidlesrc.h | 6 + .../gstreamer/tests/check/libs/baseidlesrc.c | 126 ++++++++++++++++++ 3 files changed, 217 insertions(+), 19 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index a630c6df795..7386444148c 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -93,7 +93,8 @@ struct _GstBaseIdleSrcPrivate GstBufferPool *buf_pool; /* OBJECT_LOCK */ GstAllocator *allocator; /* OBJECT_LOCK */ GQueue *obj_queue; - GThread *thread; + gpointer thread_handle; + GstTaskPool *thread_pool; /* OBJECT_LOCK */ GstAllocationParams params; /* OBJECT_LOCK */ }; @@ -1140,6 +1141,54 @@ gst_base_idle_src_get_buffer_pool (GstBaseIdleSrc * src) return ret; } +/** + * gst_base_idle_src_set_thread_pool: + * @src: a #GstBaseIdleSrc + * @thread_pool: (transfer full): a #GstTaskPool + * + * Sets the thread pool to be used internally + */ +void +gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, + GstTaskPool * thread_pool) +{ + g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); + g_return_if_fail (GST_IS_TASK_POOL (thread_pool)); + + GST_OBJECT_LOCK (src); + if (src->priv->thread_pool) { + GST_DEBUG_OBJECT (src, "Cleaning up old thread pool"); + gst_task_pool_cleanup (src->priv->thread_pool); + gst_object_unref (src->priv->thread_pool); + } + + GST_DEBUG_OBJECT (src, "Setting external thread pool %p", thread_pool); + src->priv->thread_pool = thread_pool; + GST_OBJECT_UNLOCK (src); +} + +/** + * gst_base_idle_src_get_thread_pool: + * @src: a #GstBaseIdleSrc + * + * Return: (transfer full): the instance of the #GstTaskPool used + * by the src; unref it after usage + */ +GstTaskPool * +gst_base_idle_src_get_thread_pool (GstBaseIdleSrc * src) +{ + GstTaskPool *ret = NULL; + + g_return_val_if_fail (GST_IS_BASE_IDLE_SRC (src), NULL); + + GST_OBJECT_LOCK (src); + if (src->priv->thread_pool) + ret = gst_object_ref (src->priv->thread_pool); + GST_OBJECT_UNLOCK (src); + + return ret; +} + /** * gst_base_idle_src_get_allocator: * @src: a #GstBaseIdleSrc @@ -1264,7 +1313,7 @@ gst_base_idle_src_process_object_queue (GstBaseIdleSrc * src) GST_OBJECT_UNLOCK (src); } -static gpointer +static void gst_base_idle_src_func (gpointer user_data) { GstBaseIdleSrc *src = GST_BASE_IDLE_SRC (user_data); @@ -1277,33 +1326,35 @@ gst_base_idle_src_func (gpointer user_data) GST_ELEMENT_FLOW_ERROR (src, GST_FLOW_NOT_NEGOTIATED); gst_pad_push_event (src->srcpad, gst_event_new_eos ()); GST_PAD_STREAM_UNLOCK (src->srcpad); - return NULL; + return; } } GST_PAD_STREAM_UNLOCK (src->srcpad); gst_base_idle_src_process_object_queue (src); - return NULL; + return; } static void -gst_base_idle_src_start_task (GstBaseIdleSrc * src, gboolean wait) +gst_base_idle_src_start_task (GstBaseIdleSrc * src, G_GNUC_UNUSED gboolean wait) { + GstBaseIdleSrcPrivate *priv = src->priv; + GST_DEBUG_OBJECT (src, "Starting task"); - /* if we already have an outstanding task, join it */ - if (src->priv->thread) { - GST_DEBUG_OBJECT (src, "Waiting for outstanding task to finish"); - g_thread_join (src->priv->thread); - } - src->priv->thread = - g_thread_new (GST_ELEMENT_NAME (src), gst_base_idle_src_func, src); + /* we don't care of previous result (if any) so simply unref the handler */ + gst_task_pool_dispose_handle (priv->thread_pool, priv->thread_handle); + priv->thread_handle = NULL; + + GError *error = NULL; + priv->thread_handle = + gst_task_pool_push (src->priv->thread_pool, gst_base_idle_src_func, src, + &error); if (wait) { - GST_DEBUG_OBJECT (src, "Waiting for task to finish"); - g_thread_join (src->priv->thread); - src->priv->thread = NULL; + gst_task_pool_join (priv->thread_pool, priv->thread_handle); + priv->thread_handle = NULL; } } @@ -1386,16 +1437,15 @@ static gboolean gst_base_idle_src_stop (GstBaseIdleSrc * src) { GstBaseIdleSrcClass *bclass; + GstBaseIdleSrcPrivate *priv = src->priv; GstMiniObject *obj; gboolean result = TRUE; GST_DEBUG_OBJECT (src, "stopping source"); src->running = FALSE; - if (src->priv->thread) { - g_thread_join (src->priv->thread); - src->priv->thread = NULL; - } + gst_task_pool_join (priv->thread_pool, priv->thread_handle); + priv->thread_handle = NULL; /* clean up any leftovers on the queue */ while ((obj = g_queue_pop_head (src->priv->obj_queue))) { @@ -1601,6 +1651,12 @@ gst_base_idle_src_dispose (GObject * object) /* FIXME: empty this queue potentially... */ // g_queue_clear (src->priv->obj_queue); + if (src->priv->thread_pool) { + gst_task_pool_cleanup (src->priv->thread_pool); + gst_object_unref (src->priv->thread_pool); + src->priv->thread_pool = NULL; + } + G_OBJECT_CLASS (parent_class)->dispose (object); } @@ -1692,6 +1748,16 @@ gst_base_idle_src_init (GstBaseIdleSrc * src, gpointer g_class) src->priv->obj_queue = g_queue_new (); + /* Create a shared task pool as default thread pool for this base class + * with a default thread per pool of 1. This would be suboptimal for most + * cases but only be biased to very rare pushes */ + GstTaskPool *thread_pool = gst_shared_task_pool_new (); + gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (thread_pool), 1); + src->priv->thread_pool = thread_pool; + + GError *error = NULL; + gst_task_pool_prepare (src->priv->thread_pool, &error); + GST_DEBUG_OBJECT (src, "init done"); } diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h index 142589909ed..a43b9395d72 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h @@ -195,6 +195,12 @@ gboolean gst_base_idle_src_set_caps (GstBaseIdleSrc *src, GstCaps GST_BASE_API GstBufferPool * gst_base_idle_src_get_buffer_pool (GstBaseIdleSrc *src); +GST_BASE_API +void gst_base_idle_src_set_thread_pool (GstBaseIdleSrc *src, GstTaskPool * thread_pool); + +GST_BASE_API +GstTaskPool * gst_base_idle_src_get_thread_pool (GstBaseIdleSrc *src); + GST_BASE_API void gst_base_idle_src_get_allocator (GstBaseIdleSrc *src, GstAllocator **allocator, diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index d79f795a266..823b2495301 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -177,6 +177,130 @@ GST_START_TEST (baseidlesrc_handle_events) GST_END_TEST; +GST_START_TEST (baseidlesrc_thread_pool_set_and_get) +{ + GstElement *src; + GstBaseIdleSrc *base_src; + + src = g_object_new (test_idle_src_get_type (), NULL); + base_src = GST_BASE_IDLE_SRC (src); + + GstTaskPool *thread_pool = gst_base_idle_src_get_thread_pool (base_src); + fail_unless (thread_pool); + gst_object_unref (thread_pool); + + GstTaskPool *new_thread_pool = gst_shared_task_pool_new (); + gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (new_thread_pool), + 2); + + gst_base_idle_src_set_thread_pool (base_src, new_thread_pool); + thread_pool = gst_base_idle_src_get_thread_pool (base_src); + fail_unless (thread_pool == new_thread_pool); + fail_unless_equals_int (2, + gst_shared_task_pool_get_max_threads (GST_SHARED_TASK_POOL + (thread_pool))); + + gst_object_unref (thread_pool); +} + +GST_END_TEST; + +#define MAX_SRCS 16 + +static gpointer +_push_func (gpointer data) +{ + GstBuffer *buf; + GstBufferList *buf_list; + guint i, j; + + GstHarness *h = data; + GstElement *e = h->element; + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (e); + + /* push some buffer lists */ + GST_LOG ("Pushing some buffer lists from source %s", + gst_element_get_name (e)); + for (i = 0; i < 5; i++) { + buf_list = gst_buffer_list_new_sized (20); + for (j = 0; j < 5; j++) { + fail_unless_equals_int (GST_FLOW_OK, + gst_base_idle_src_alloc_buffer (base_src, 100, &buf)); + gst_buffer_list_insert (buf_list, -1, buf); + } + gst_base_idle_src_submit_buffer_list (base_src, buf_list); + if (g_random_int_range (0, 100) == 3) { + GST_LOG ("Randomly yielding during buffer list push from source %s", + gst_element_get_name (e)); + g_thread_yield (); + } + } + + /* yield to cause some hadvoc */ + GST_LOG ("Yielding from source %s", gst_element_get_name (e)); + g_thread_yield (); + + /* push some buffers */ + GST_LOG ("Pushing some buffers from source %s", gst_element_get_name (e)); + fail_unless_equals_int (GST_FLOW_OK, gst_base_idle_src_alloc_buffer (base_src, + 100, &buf)); + for (i = 0; i < 100; i++) { + gst_base_idle_src_submit_buffer (base_src, gst_buffer_ref (buf)); + if (g_random_int_range (0, 100) == 3) { + GST_LOG ("Randomly yielding during buffer push from source %s", + gst_element_get_name (e)); + g_thread_yield (); + } + } + gst_buffer_unref (buf); +} + +GST_START_TEST (baseidlesrc_thread_pool_submit) +{ + GstHarness *hs[MAX_SRCS]; + GstElement *srcs[MAX_SRCS]; + GThread *threads[MAX_SRCS]; + GstBaseIdleSrc *base_src; + GstTaskPool *thread_pool; + GstBuffer *buf; + GstBufferList *buf_list; + guint i; + + GstTaskPool *pool = gst_shared_task_pool_new (); + gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (pool), + MAX_SRCS / 2); + + /* create all sources and harnesses in one go */ + for (i = 0; i < MAX_SRCS; i++) { + srcs[i] = g_object_new (test_idle_src_get_type (), NULL); + base_src = GST_BASE_IDLE_SRC (srcs[i]); + + gst_base_idle_src_set_thread_pool (base_src, pool); + + hs[i] = gst_harness_new_with_element (GST_ELEMENT (srcs[i]), NULL, "src"); + gst_harness_set_sink_caps_str (hs[i], "foo/bar"); + gst_harness_play (hs[i]); + } + + /* start pushing to from all sources */ + for (i = 0; i < MAX_SRCS; i++) { + char *thread_name = g_strdup_printf ("pusher-%d", i); + threads[i] = g_thread_new (thread_name, _push_func, hs[i]); + } + + /* wait for all sources to finish pushing */ + for (i = 0; i < MAX_SRCS; i++) { + g_thread_join (threads[i]); + } + + /* teardown */ + for (i = 0; i < MAX_SRCS; i++) { + gst_harness_teardown (hs[i]); + } +} + +GST_END_TEST; + static Suite * baseidlesrc_suite (void) { @@ -188,6 +312,8 @@ baseidlesrc_suite (void) tcase_add_test (tc, baseidlesrc_submit_buffer); tcase_add_test (tc, baseidlesrc_submit_buffer_list); tcase_add_test (tc, baseidlesrc_handle_events); + tcase_add_test (tc, baseidlesrc_thread_pool_set_and_get); + tcase_add_test (tc, baseidlesrc_thread_pool_submit); return s; } From 953675d1e0100031645107dcb0c0cd466960a71e Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 30 Oct 2025 22:42:22 +0900 Subject: [PATCH 18/62] libs/base/idlesrc: make alloc a vmethod Similar to the basesrc, buffer allocation could be a featured that is implemented on derived classes --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 13 ++----- .../gstreamer/libs/gst/base/gstbaseidlesrc.h | 14 +++++--- .../gstreamer/tests/check/libs/baseidlesrc.c | 34 +++++++++++-------- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 7386444148c..3a12d860c23 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1581,18 +1581,8 @@ gst_base_idle_src_submit_buffer_list (GstBaseIdleSrc * src, gst_base_idle_src_start_task (src, FALSE); } -/** - * gst_base_idle_src_alloc_buffer: - * @src: a #GstBaseIdleSrc - * @size: a gsize with the size of the buffer - * @buffer: (transfer full): a #GstBuffer - * - * Subclasses can call this to alloc a buffer. - * - * Since: 1.22 - */ GstFlowReturn -gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, +gst_base_idle_src_default_alloc (GstBaseIdleSrc * src, guint size, GstBuffer ** buffer) { GstFlowReturn ret; @@ -1706,6 +1696,7 @@ gst_base_idle_src_class_init (GstBaseIdleSrcClass * klass) klass->fixate = GST_DEBUG_FUNCPTR (gst_base_idle_src_default_fixate); klass->query = GST_DEBUG_FUNCPTR (gst_base_idle_src_default_query); klass->event = GST_DEBUG_FUNCPTR (gst_base_idle_src_default_event); + klass->alloc = GST_DEBUG_FUNCPTR (gst_base_idle_src_default_alloc); klass->decide_allocation = GST_DEBUG_FUNCPTR (gst_base_idle_src_decide_allocation_default); diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h index a43b9395d72..c853d9fdb53 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h @@ -125,6 +125,15 @@ struct _GstBaseIdleSrcClass { /* notify the subclass of new caps */ gboolean (*set_caps) (GstBaseIdleSrc *src, GstCaps *caps); + /** + * GstBaseIdleSrc::alloc: + * @buf: (out) (nullable): + * + * Ask the subclass to allocate an output buffer with @size, the default + * implementation will use the negotiated allocator. + */ + GstFlowReturn (*alloc) (GstBaseIdleSrc *src, guint size, GstBuffer **buf); + /* setup allocation query */ gboolean (*decide_allocation) (GstBaseIdleSrc *src, GstQuery *query); @@ -214,11 +223,6 @@ GST_BASE_API void gst_base_idle_src_submit_buffer_list (GstBaseIdleSrc * src, GstBufferList * buffer_list); -GST_BASE_API -GstFlowReturn gst_base_idle_src_alloc_buffer (GstBaseIdleSrc * src, - guint size, - GstBuffer ** buffer); - G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstBaseIdleSrc, gst_object_unref) G_END_DECLS diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 823b2495301..8b579d55824 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -42,6 +42,14 @@ static GType test_idle_src_get_type (void); G_DEFINE_TYPE (TestIdleSrc, test_idle_src, GST_TYPE_BASE_IDLE_SRC); +static GstFlowReturn +test_idle_src_alloc (TestIdleSrc * src, GstBuffer ** buf) +{ + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GstBaseIdleSrcClass *klass = GST_BASE_IDLE_SRC_GET_CLASS (base_src); + return klass->alloc (base_src, 100, buf); +} + static void test_idle_src_init (TestIdleSrc * src) { @@ -70,7 +78,7 @@ GST_END_TEST; GST_START_TEST (baseidlesrc_submit_buffer) { - GstElement *src; + TestIdleSrc *src; GstHarness *h; GstBaseIdleSrc *base_src; GstBuffer *buf; @@ -85,8 +93,7 @@ GST_START_TEST (baseidlesrc_submit_buffer) gst_harness_play (h); for (i = 0; i < 5; i++) { - fail_unless_equals_int (GST_FLOW_OK, - gst_base_idle_src_alloc_buffer (base_src, 100, &buf)); + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); GST_BUFFER_PTS (buf) = i * GST_MSECOND; gst_base_idle_src_submit_buffer (base_src, buf); @@ -103,7 +110,7 @@ GST_END_TEST; GST_START_TEST (baseidlesrc_submit_buffer_list) { - GstElement *src; + TestIdleSrc *src; GstHarness *h; GstBaseIdleSrc *base_src; GstBuffer *buf; @@ -121,8 +128,7 @@ GST_START_TEST (baseidlesrc_submit_buffer_list) buf_list = gst_buffer_list_new_sized (20); for (i = 0; i < 5; i++) { - fail_unless_equals_int (GST_FLOW_OK, - gst_base_idle_src_alloc_buffer (base_src, 100, &buf)); + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); gst_buffer_list_insert (buf_list, -1, buf); } @@ -145,7 +151,7 @@ fail_unless_equals_event_type (const GstEvent * event, GST_START_TEST (baseidlesrc_handle_events) { - GstElement *src; + TestIdleSrc *src; GstHarness *h; GstBaseIdleSrc *base_src; GstBuffer *buf; @@ -154,12 +160,11 @@ GST_START_TEST (baseidlesrc_handle_events) src = g_object_new (test_idle_src_get_type (), NULL); base_src = GST_BASE_IDLE_SRC (src); - h = gst_harness_new_with_element (src, NULL, "src"); + h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); gst_harness_set_sink_caps_str (h, "foo/bar"); gst_harness_play (h); - fail_unless_equals_int (GST_FLOW_OK, - gst_base_idle_src_alloc_buffer (base_src, 64, &buf)); + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); gst_base_idle_src_submit_buffer (base_src, buf); event = gst_harness_pull_event (h); @@ -216,7 +221,8 @@ _push_func (gpointer data) GstHarness *h = data; GstElement *e = h->element; - GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (e); + TestIdleSrc *src = (TestIdleSrc *) e; + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); /* push some buffer lists */ GST_LOG ("Pushing some buffer lists from source %s", @@ -224,8 +230,7 @@ _push_func (gpointer data) for (i = 0; i < 5; i++) { buf_list = gst_buffer_list_new_sized (20); for (j = 0; j < 5; j++) { - fail_unless_equals_int (GST_FLOW_OK, - gst_base_idle_src_alloc_buffer (base_src, 100, &buf)); + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); gst_buffer_list_insert (buf_list, -1, buf); } gst_base_idle_src_submit_buffer_list (base_src, buf_list); @@ -242,8 +247,7 @@ _push_func (gpointer data) /* push some buffers */ GST_LOG ("Pushing some buffers from source %s", gst_element_get_name (e)); - fail_unless_equals_int (GST_FLOW_OK, gst_base_idle_src_alloc_buffer (base_src, - 100, &buf)); + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); for (i = 0; i < 100; i++) { gst_base_idle_src_submit_buffer (base_src, gst_buffer_ref (buf)); if (g_random_int_range (0, 100) == 3) { From 9e690d0fa4b962952b1bae42074b7c44d0ed999c Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 30 Oct 2025 22:45:03 +0900 Subject: [PATCH 19/62] libs/base/idlesrc: drop unimplemented get_size Buffer allocation, and pushing is driven by derived classes Remove up until we have an use for it --- .../gstreamer/libs/gst/base/gstbaseidlesrc.h | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h index c853d9fdb53..4e0e4559c24 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h @@ -87,13 +87,9 @@ struct _GstBaseIdleSrc { * setting a fixate function on the source pad. * @set_caps: Notify subclass of changed output caps * @decide_allocation: configure the allocation query - * @start: Start processing. Subclasses should open resources and prepare - * to produce data. Implementation should call gst_base_idle_src_start_complete() - * when the operation completes, either from the current thread or any other - * thread that finishes the start operation asynchronously. + * @start: Start processing. Subclasses should open resources + * and prepare to produce data. * @stop: Stop processing. Subclasses should use this to close resources. - * @get_size: Return the total size of the resource, in the format set by - * gst_base_idle_src_set_format(). * @query: Handle a requested query. * @event: Override this to implement custom event handling. * @alloc: Ask the subclass to allocate a buffer with for offset and size. The @@ -141,17 +137,6 @@ struct _GstBaseIdleSrcClass { gboolean (*start) (GstBaseIdleSrc *src); gboolean (*stop) (GstBaseIdleSrc *src); - /** - * GstBaseIdleSrcClass::get_size: - * @size: (out): - * - * Get the total size of the resource in the format set by - * gst_base_idle_src_set_format(). - * - * Returns: %TRUE if the size is available and has been set. - */ - gboolean (*get_size) (GstBaseIdleSrc *src, guint64 *size); - /* notify subclasses of a query */ gboolean (*query) (GstBaseIdleSrc *src, GstQuery *query); From b418588175ce806e9fda7c8f4969e88544c64132 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 30 Oct 2025 22:50:35 +0900 Subject: [PATCH 20/62] libs/base/idlesrc: improve docstring and remove unused APIs --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 205 ++++++++++++------ .../gstreamer/libs/gst/base/gstbaseidlesrc.h | 15 +- 2 files changed, 138 insertions(+), 82 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 3a12d860c23..575cd9bb2bf 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1,6 +1,7 @@ /* GStreamer * Copyright (C) 1999,2000 Erik Walthinsen - * 2000,2005 Wim Taymans + * 2000 Wim Taymans + * 2005 Wim Taymans * 2023 Havard Graff * 2023 Camilo Celis Guzman * @@ -25,16 +26,142 @@ /** * SECTION:gstbaseidlesrc * @title: GstBaseIdleSrc - * @short_description: Base class for getrange based source elements - * @see_also: #GstPushSrc, #GstBaseTransform, #GstBaseSink + * @short_description: Base class for push-mode source elements that operate + * in idle-driven or controlled pacing + * @see_also: #GstBaseSrc, #GstPushSrc, #GstBaseSink * - * This is a generic base class for source elements that are mostly - * idle in its lifetime. + * #GstBaseIdleSrc is a base class for source elements that operate purely in + * push mode. Unlike #GstBaseSrc, it does not support pull-based scheduling or + * random access through #GstBaseSrcClass::create, and instead expects subclasses + * to push data downstream at their own pace or from an external producer. + * + * ## Purpose + * + * This base class is intended for elements that generate or forward data + * asynchronously, such as live capture devices, idle-loop sources, or + * externally-driven producers. It provides the fundamental mechanics of + * caps negotiation, allocation setup, buffer timestamping, and live-source + * handling, while delegating actual data generation to subclasses. + * + * The element manages a single source pad named `"src"` and operates only + * in push mode. + * + * ## Features + * + * - Push-only operation (no getrange/pull support) + * - Optional live-source mode using gst_base_idle_src_set_live() + * - Built-in buffer allocation via gst_base_idle_src_alloc() + * - Negotiation helpers for caps and allocation queries + * - Thread-pool support for external or shared producers + * - Optional automatic timestamping + * + * ## Subclass Responsibilities + * + * A typical subclass of #GstBaseIdleSrc needs to: + * + * 1. Install a static pad template for the `"src"` pad in `class_init()`. + * 2. Implement @start and @stop to open and close resources. + * 3. Produce and submit buffers using gst_base_idle_src_submit_buffer() or + * gst_base_idle_src_submit_buffer_list(). + * + * Optionally, the subclass can override virtual methods such as: + * + * - @get_caps, @fixate, @set_caps, @negotiate — for caps negotiation. + * - @decide_allocation — to customize memory allocation. + * - @query — to handle custom queries (e.g., latency, position). + * - @event — to handle upstream events such as flushes or EOS. + * - @alloc — to allocate output buffers. + * + * ## Live Sources + * + * Live sources are sources that produce data in real time and may discard + * data when paused. Typical examples are capture devices or sensors. Live + * sources should call gst_base_idle_src_set_live() during setup. + * + * A live source does not produce data while in the PAUSED state. The state + * change from READY to PAUSED will therefore return + * %GST_STATE_CHANGE_NO_PREROLL to indicate that no preroll data is available. + * + * When PLAYING, live sources may timestamp buffers using the current running + * time if gst_base_idle_src_set_do_timestamp() was enabled. This makes it + * easier to synchronize generated data to the pipeline clock. + * + * ## Thread Pool Management + * + * #GstBaseIdleSrc uses an internal #GstTaskPool to schedule buffer submission + * and auxiliary tasks. This mechanism helps to avoid the overhead of creating + * and destroying threads too frequently when data is submitted in rapid + * successions. + * + * By default, the internal pool is configured with a maximum of one thread, + * which provides a simple but efficient way to handle continuous or bursty + * submission patterns without incurring repeated thread creation and teardown + * costs. + * + * Applications or higher-level elements can override the default pool using + * gst_base_idle_src_set_thread_pool(). This allows sharing a thread pool + * among multiple similar slow-paced sources, reducing context-switch overhead + * and improving resource utilization when several sources push data at a + * modest rate. + * + * The currently configured thread pool can be retrieved with + * gst_base_idle_src_get_thread_pool(). + * + * ## Example Subclass + * + * |[ + * static void + * my_idle_src_class_init (GstMyIdleSrcClass *klass) + * { + * GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + * gst_element_class_add_static_pad_template (element_class, &srctemplate); + * + * gst_element_class_set_static_metadata (element_class, + * "My Idle Source", + * "Source/Push", + * "Example push-mode idle source", + * "Author "); + * + * klass->start = GST_DEBUG_FUNCPTR (my_idle_src_start); + * klass->stop = GST_DEBUG_FUNCPTR (my_idle_src_stop); + * } + * + * static GstFlowReturn + * my_idle_src_loop (GstBaseIdleSrc *src) + * { + * GstBuffer *buffer = NULL; + * gst_base_idle_src_alloc (src, FRAME_SIZE, &buffer); + * // Fill buffer data + * gst_base_idle_src_submit_buffer (src, buffer); + * return GST_FLOW_OK; + * } + * ]| + * + * ## Threading and Data Submission + * + * Data can be produced in any thread, including one managed by a custom + * #GstTaskPool. Use gst_base_idle_src_set_thread_pool() to configure a + * task pool and gst_base_idle_src_submit_buffer() or + * gst_base_idle_src_submit_buffer_list() to submit data. + * + * ## Notes + * + * - Only one source pad named "src" is supported. + * - Only push-mode operation is possible. + * - Pull-mode functions (e.g., create/getrange) are not implemented. + * - gst_base_idle_src_set_format() defines the active #GstFormat used for + * segments and events. + * - Subclasses should avoid blocking indefinitely in @start or @stop, as these + * are called during state changes. + * + * ## Typical Use Cases + * + * - Synthetic or idle data generators + * - Real-time data capture devices + * - Streaming relays or adapters that push data from another thread + * - Sources that wrap asynchronous external APIs or IO loops * - * Only push-mode is supported. - * TODO: Complete */ - #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -102,7 +229,6 @@ struct _GstBaseIdleSrcPrivate static GstElementClass *parent_class = NULL; static gint private_offset = 0; -/* TODO: Should this be an vmethod? */ static void gst_base_idle_src_start_task (GstBaseIdleSrc * src, gboolean wait); static void gst_base_idle_src_process_object_queue (GstBaseIdleSrc * src); @@ -227,59 +353,6 @@ gst_base_idle_src_get_do_timestamp (GstBaseIdleSrc * src) return res; } -/** - * gst_base_idle_src_new_segment: - * @src: a #GstBaseIdleSrc - * @segment: a pointer to a #GstSegment - * - * Prepare a new segment for emission downstream. This function must - * only be called by derived sub-classes, and only from the #GstBaseIdleSrcClass::create function, - * as the stream-lock needs to be held. - * - * The format for the @segment must be identical with the current format - * of the source, as configured with gst_base_idle_src_set_format(). - * - * The format of @src must not be %GST_FORMAT_UNDEFINED and the format - * should be configured via gst_base_idle_src_set_format() before calling this method. - * - * Returns: %TRUE if preparation of new segment succeeded. - * - * Since: 1.18 - */ -gboolean -gst_base_idle_src_new_segment (GstBaseIdleSrc * src, const GstSegment * segment) -{ - g_return_val_if_fail (GST_IS_BASE_IDLE_SRC (src), FALSE); - g_return_val_if_fail (segment != NULL, FALSE); - - GST_OBJECT_LOCK (src); - - if (src->segment.format == GST_FORMAT_UNDEFINED) { - /* subclass must set valid format before calling this method */ - GST_WARNING_OBJECT (src, "segment format is not configured yet, ignore"); - GST_OBJECT_UNLOCK (src); - return FALSE; - } - - if (src->segment.format != segment->format) { - GST_WARNING_OBJECT (src, "segment format mismatched, ignore"); - GST_OBJECT_UNLOCK (src); - return FALSE; - } - - gst_segment_copy_into (segment, &src->segment); - - /* Mark pending segment. Will be sent before next data */ - src->priv->segment_pending = TRUE; - src->priv->segment_seqnum = gst_util_seqnum_next (); - - GST_DEBUG_OBJECT (src, "Starting new segment %" GST_SEGMENT_FORMAT, segment); - - GST_OBJECT_UNLOCK (src); - - return TRUE; -} - static void gst_base_idle_src_queue_object (GstBaseIdleSrc * src, GstMiniObject * obj) { @@ -316,7 +389,6 @@ gst_base_idle_src_set_caps (GstBaseIdleSrc * src, GstCaps * caps) } else { if (bclass->set_caps) res = bclass->set_caps (src, caps); - if (res) { gst_base_idle_src_queue_object (src, (GstMiniObject *) gst_event_new_caps (caps)); @@ -709,7 +781,6 @@ gst_base_idle_src_update_qos (GstBaseIdleSrc * src, GST_OBJECT_UNLOCK (src); } - static gboolean gst_base_idle_src_default_event (GstBaseIdleSrc * src, GstEvent * event) { @@ -1518,7 +1589,6 @@ gst_base_idle_src_activate_mode (GstPad * pad, GstObject * parent, return res; } - /** * gst_base_idle_src_submit_buffer: * @src: a #GstBaseIdleSrc @@ -1542,7 +1612,6 @@ gst_base_idle_src_submit_buffer (GstBaseIdleSrc * src, GstBuffer * buffer) gst_base_idle_src_check_pending_segment (src); - /* we need it to be writable later in get_range() where we use get_writable */ gst_base_idle_src_queue_object (src, (GstMiniObject *) buffer); gst_base_idle_src_start_task (src, FALSE); @@ -1572,7 +1641,6 @@ gst_base_idle_src_submit_buffer_list (GstBaseIdleSrc * src, gst_base_idle_src_check_pending_segment (src); - /* we need it to be writable later in get_range() where we use get_writable */ gst_base_idle_src_queue_object (src, (GstMiniObject *) buffer_list); GST_LOG_OBJECT (src, "%u buffers submitted in buffer list", @@ -1638,9 +1706,6 @@ gst_base_idle_src_dispose (GObject * object) src = GST_BASE_IDLE_SRC (object); (void) src; - /* FIXME: empty this queue potentially... */ - // g_queue_clear (src->priv->obj_queue); - if (src->priv->thread_pool) { gst_task_pool_cleanup (src->priv->thread_pool); gst_object_unref (src->priv->thread_pool); diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h index 4e0e4559c24..e5dbffd86c6 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h @@ -2,6 +2,8 @@ * Copyright (C) 1999,2000 Erik Walthinsen * 2000 Wim Taymans * 2005 Wim Taymans + * 2023 Havard Graff + * 2023 Camilo Celis Guzman * * gstbaseidlesrc.h: * @@ -94,12 +96,9 @@ struct _GstBaseIdleSrc { * @event: Override this to implement custom event handling. * @alloc: Ask the subclass to allocate a buffer with for offset and size. The * default implementation will create a new buffer from the negotiated allocator. - * @fill: Ask the subclass to fill the buffer with data for offset and size. The - * passed buffer is guaranteed to hold the requested amount of bytes. * * Subclasses can override any of the available virtual methods or not, as - * needed. At the minimum, the @create method should be overridden to produce - * buffers. + * needed. */ struct _GstBaseIdleSrcClass { GstElementClass parent_class; @@ -160,10 +159,6 @@ gboolean gst_base_idle_src_is_live (GstBaseIdleSrc *src); GST_BASE_API void gst_base_idle_src_set_format (GstBaseIdleSrc *src, GstFormat format); -GST_BASE_API -void gst_base_idle_src_set_automatic_eos (GstBaseIdleSrc * src, gboolean automatic_eos); - - GST_BASE_API gboolean gst_base_idle_src_negotiate (GstBaseIdleSrc *src); @@ -179,10 +174,6 @@ void gst_base_idle_src_set_do_timestamp (GstBaseIdleSrc *src, gboolea GST_BASE_API gboolean gst_base_idle_src_get_do_timestamp (GstBaseIdleSrc *src); -GST_BASE_API -gboolean gst_base_idle_src_new_segment (GstBaseIdleSrc *src, - const GstSegment * segment); - GST_BASE_API gboolean gst_base_idle_src_set_caps (GstBaseIdleSrc *src, GstCaps *caps); From 3526eeafbfb4fab5c062405b9e87724fb2d878c1 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 31 Oct 2025 16:01:03 +0900 Subject: [PATCH 21/62] libs/base/idlesrc/test: remove unused variables --- subprojects/gstreamer/tests/check/libs/baseidlesrc.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 8b579d55824..0d131522cc3 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -265,9 +265,6 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) GstElement *srcs[MAX_SRCS]; GThread *threads[MAX_SRCS]; GstBaseIdleSrc *base_src; - GstTaskPool *thread_pool; - GstBuffer *buf; - GstBufferList *buf_list; guint i; GstTaskPool *pool = gst_shared_task_pool_new (); From 77ff1cfe4e499781e70609ae95c5521287a503f3 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 31 Oct 2025 17:46:56 +0900 Subject: [PATCH 22/62] libs/base/idlesrc: raise element errors on failures to push --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 575cd9bb2bf..bd7ca212cd4 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1354,18 +1354,17 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) GstEvent *event = GST_EVENT_CAST (obj); GST_DEBUG_OBJECT (src, "About to push Event %" GST_PTR_FORMAT, event); if (!gst_pad_push_event (pad, event)) { - /* TODO: What is the right thing to do here?! */ - ret = GST_FLOW_CUSTOM_ERROR; + GST_ELEMENT_ERROR (src, CORE, EVENT, + ("Failed to push event."), + ("failed to push event from queue during processing loop")); } - goto check_ret_error; } else { GST_ERROR_OBJECT (src, "Unknown object %" GST_PTR_FORMAT " type", obj); } check_ret_error: if (ret != GST_FLOW_OK && ret != GST_FLOW_FLUSHING) { - // XXX: Should we proxy this into the bus in case or error or just ignore? - GST_ERROR ("Got ret: %s", gst_flow_get_name (ret)); + GST_ELEMENT_FLOW_ERROR (src, ret); } GST_PAD_STREAM_UNLOCK (pad); From 62257db870f60222345be8952c83b836a2c7996c Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 31 Oct 2025 22:18:33 +0900 Subject: [PATCH 23/62] libs/base/idlesrc: remove _since_ from docstring this is just now introduced so no need to add a version to it --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index bd7ca212cd4..d8c5fdb3265 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1594,8 +1594,6 @@ gst_base_idle_src_activate_mode (GstPad * pad, GstObject * parent, * @buffer: (transfer full): a #GstBuffer * * Subclasses can call this to submit a buffer to be pushed out later. - * - * Since: 1.22 */ void gst_base_idle_src_submit_buffer (GstBaseIdleSrc * src, GstBuffer * buffer) @@ -1622,8 +1620,6 @@ gst_base_idle_src_submit_buffer (GstBaseIdleSrc * src, GstBuffer * buffer) * @buffer_list: (transfer full): a #GstBufferList * * Subclasses can call this to submit a buffer list to be pushed out later. - * - * Since: 1.22 */ void gst_base_idle_src_submit_buffer_list (GstBaseIdleSrc * src, From 1dc42f177d361b16523304eee1ec3515eeb58e4d Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 31 Oct 2025 23:08:42 +0900 Subject: [PATCH 24/62] libs/base/idlesrc: add missing static keyword to default_alloc --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index d8c5fdb3265..eae158c4da5 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1644,7 +1644,7 @@ gst_base_idle_src_submit_buffer_list (GstBaseIdleSrc * src, gst_base_idle_src_start_task (src, FALSE); } -GstFlowReturn +static GstFlowReturn gst_base_idle_src_default_alloc (GstBaseIdleSrc * src, guint size, GstBuffer ** buffer) { From cc6f56596853260c0afa83ca448dd513d75dc12a Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 31 Oct 2025 23:58:02 +0900 Subject: [PATCH 25/62] libs/base/idlesrc/test: comment out missing harness API call --- subprojects/gstreamer/tests/check/libs/baseidlesrc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 0d131522cc3..f228f986bf6 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -133,7 +133,8 @@ GST_START_TEST (baseidlesrc_submit_buffer_list) } gst_base_idle_src_submit_buffer_list (base_src, buf_list); - gst_buffer_list_unref (gst_harness_pull_list (h)); + // FIXME: Enable check once _pull_list API lands. + // gst_buffer_list_unref (gst_harness_pull_list (h)); gst_harness_teardown (h); } From 184f7c274215d8c5f8a3f37889dbcfd6065a74bf Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Sat, 1 Nov 2025 00:56:00 +0900 Subject: [PATCH 26/62] libs/base/idlesrc/test: add missing return from thread func --- subprojects/gstreamer/tests/check/libs/baseidlesrc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index f228f986bf6..b916d3bb33f 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -258,6 +258,8 @@ _push_func (gpointer data) } } gst_buffer_unref (buf); + + return NULL; } GST_START_TEST (baseidlesrc_thread_pool_submit) From ba5a2fba42fbbc965b86ff1522e9daa26a6f6b1d Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Mon, 3 Nov 2025 22:26:23 +0900 Subject: [PATCH 27/62] libs/base/idlesrc: avoid cleaning up the thread pool This pool could be owned externally, and this is only useful for tests anyways --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 14 ++++++-------- .../gstreamer/tests/check/libs/baseidlesrc.c | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index eae158c4da5..44fe9d30f4b 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1229,7 +1229,6 @@ gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, GST_OBJECT_LOCK (src); if (src->priv->thread_pool) { GST_DEBUG_OBJECT (src, "Cleaning up old thread pool"); - gst_task_pool_cleanup (src->priv->thread_pool); gst_object_unref (src->priv->thread_pool); } @@ -1419,7 +1418,7 @@ gst_base_idle_src_start_task (GstBaseIdleSrc * src, G_GNUC_UNUSED gboolean wait) GError *error = NULL; priv->thread_handle = - gst_task_pool_push (src->priv->thread_pool, gst_base_idle_src_func, src, + gst_task_pool_push (priv->thread_pool, gst_base_idle_src_func, src, &error); if (wait) { @@ -1701,12 +1700,6 @@ gst_base_idle_src_dispose (GObject * object) src = GST_BASE_IDLE_SRC (object); (void) src; - if (src->priv->thread_pool) { - gst_task_pool_cleanup (src->priv->thread_pool); - gst_object_unref (src->priv->thread_pool); - src->priv->thread_pool = NULL; - } - G_OBJECT_CLASS (parent_class)->dispose (object); } @@ -1718,6 +1711,11 @@ gst_base_idle_src_finalize (GObject * object) g_queue_free (src->priv->obj_queue); + if (src->priv->thread_pool) { + gst_object_unref (src->priv->thread_pool); + src->priv->thread_pool = NULL; + } + G_OBJECT_CLASS (parent_class)->finalize (object); } diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index b916d3bb33f..cd91bbfc580 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -279,7 +279,7 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) srcs[i] = g_object_new (test_idle_src_get_type (), NULL); base_src = GST_BASE_IDLE_SRC (srcs[i]); - gst_base_idle_src_set_thread_pool (base_src, pool); + gst_base_idle_src_set_thread_pool (base_src, gst_object_ref (pool)); hs[i] = gst_harness_new_with_element (GST_ELEMENT (srcs[i]), NULL, "src"); gst_harness_set_sink_caps_str (hs[i], "foo/bar"); From e7cf5a5edfb063a8ead5c0622ae8fd82ce58da09 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Mon, 3 Nov 2025 22:26:47 +0900 Subject: [PATCH 28/62] libs/base/idlesrc/test: unref source objects --- subprojects/gstreamer/tests/check/libs/baseidlesrc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index cd91bbfc580..88b89505b02 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -104,6 +104,7 @@ GST_START_TEST (baseidlesrc_submit_buffer) } gst_harness_teardown (h); + g_object_unref (src); } GST_END_TEST; @@ -137,6 +138,7 @@ GST_START_TEST (baseidlesrc_submit_buffer_list) // gst_buffer_list_unref (gst_harness_pull_list (h)); gst_harness_teardown (h); + g_object_unref (src); } GST_END_TEST; @@ -179,6 +181,7 @@ GST_START_TEST (baseidlesrc_handle_events) gst_buffer_unref (gst_harness_pull (h)); gst_harness_teardown (h); + g_object_unref (src); } GST_END_TEST; @@ -300,6 +303,7 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) /* teardown */ for (i = 0; i < MAX_SRCS; i++) { gst_harness_teardown (hs[i]); + g_object_unref (srcs[i]); } } From 76495c328bdffab293d8a022903af8a875836919 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Tue, 4 Nov 2025 00:12:20 +0900 Subject: [PATCH 29/62] libs/base/idlesrc: correct thread-pool cleanup & unref on tests --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 2 ++ subprojects/gstreamer/tests/check/libs/baseidlesrc.c | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 44fe9d30f4b..63d1a7b475f 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1229,6 +1229,7 @@ gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, GST_OBJECT_LOCK (src); if (src->priv->thread_pool) { GST_DEBUG_OBJECT (src, "Cleaning up old thread pool"); + gst_task_pool_cleanup (src->priv->thread_pool); gst_object_unref (src->priv->thread_pool); } @@ -1712,6 +1713,7 @@ gst_base_idle_src_finalize (GObject * object) g_queue_free (src->priv->obj_queue); if (src->priv->thread_pool) { + gst_task_pool_cleanup (src->priv->thread_pool); gst_object_unref (src->priv->thread_pool); src->priv->thread_pool = NULL; } diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 88b89505b02..3cdaf87f9e4 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -72,6 +72,7 @@ GST_START_TEST (baseidlesrc_up_and_down) h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); gst_harness_teardown (h); + g_object_unref (src); } GST_END_TEST; @@ -190,6 +191,7 @@ GST_START_TEST (baseidlesrc_thread_pool_set_and_get) { GstElement *src; GstBaseIdleSrc *base_src; + GError *err; src = g_object_new (test_idle_src_get_type (), NULL); base_src = GST_BASE_IDLE_SRC (src); @@ -201,6 +203,7 @@ GST_START_TEST (baseidlesrc_thread_pool_set_and_get) GstTaskPool *new_thread_pool = gst_shared_task_pool_new (); gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (new_thread_pool), 2); + gst_task_pool_prepare (new_thread_pool, &err); gst_base_idle_src_set_thread_pool (base_src, new_thread_pool); thread_pool = gst_base_idle_src_get_thread_pool (base_src); @@ -209,7 +212,9 @@ GST_START_TEST (baseidlesrc_thread_pool_set_and_get) gst_shared_task_pool_get_max_threads (GST_SHARED_TASK_POOL (thread_pool))); + gst_task_pool_cleanup (new_thread_pool); gst_object_unref (thread_pool); + g_object_unref (src); } GST_END_TEST; @@ -271,11 +276,13 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) GstElement *srcs[MAX_SRCS]; GThread *threads[MAX_SRCS]; GstBaseIdleSrc *base_src; + GError *err; guint i; GstTaskPool *pool = gst_shared_task_pool_new (); gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (pool), MAX_SRCS / 2); + gst_task_pool_prepare (pool, &err); /* create all sources and harnesses in one go */ for (i = 0; i < MAX_SRCS; i++) { @@ -293,6 +300,7 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) for (i = 0; i < MAX_SRCS; i++) { char *thread_name = g_strdup_printf ("pusher-%d", i); threads[i] = g_thread_new (thread_name, _push_func, hs[i]); + g_free (thread_name); } /* wait for all sources to finish pushing */ @@ -305,6 +313,8 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) gst_harness_teardown (hs[i]); g_object_unref (srcs[i]); } + gst_task_pool_cleanup (pool); + gst_object_unref (pool); } GST_END_TEST; From 6e6a2b43168ea2555e90c8f23febfd7d7ae06aa9 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:29:21 +0900 Subject: [PATCH 30/62] libs/base/idlesrc: join previous handle and propagate GError on start_task --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 63d1a7b475f..f5767bab2f3 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1407,22 +1407,34 @@ gst_base_idle_src_func (gpointer user_data) } static void -gst_base_idle_src_start_task (GstBaseIdleSrc * src, G_GNUC_UNUSED gboolean wait) +gst_base_idle_src_start_task (GstBaseIdleSrc * src, gboolean wait) { GstBaseIdleSrcPrivate *priv = src->priv; + GError *error = NULL; GST_DEBUG_OBJECT (src, "Starting task"); - /* we don't care of previous result (if any) so simply unref the handler */ - gst_task_pool_dispose_handle (priv->thread_pool, priv->thread_handle); - priv->thread_handle = NULL; + /* If a previous task is still outstanding, join it first so that buffer + * ordering is preserved and we never have two workers draining the queue + * concurrently (this matters for shared pools with max_threads > 1). */ + if (priv->thread_handle) { + gst_task_pool_join (priv->thread_pool, priv->thread_handle); + priv->thread_handle = NULL; + } - GError *error = NULL; priv->thread_handle = gst_task_pool_push (priv->thread_pool, gst_base_idle_src_func, src, &error); - if (wait) { + if (G_UNLIKELY (error != NULL)) { + GST_ERROR_OBJECT (src, "Failed to push task to pool: %s", error->message); + g_clear_error (&error); + /* nothing else we can do; the queued objects will be processed next time + * a task is successfully pushed, or drained in _stop(). */ + return; + } + + if (wait && priv->thread_handle) { gst_task_pool_join (priv->thread_pool, priv->thread_handle); priv->thread_handle = NULL; } From 14e39e8f7ade6f335a968a1aa7c491094e7aea2b Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:30:12 +0900 Subject: [PATCH 31/62] libs/base/idlesrc: don't cleanup() a possibly-shared pool & properly drain the old handle --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index f5767bab2f3..c27cc94e1f6 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1217,25 +1217,46 @@ gst_base_idle_src_get_buffer_pool (GstBaseIdleSrc * src) * @src: a #GstBaseIdleSrc * @thread_pool: (transfer full): a #GstTaskPool * - * Sets the thread pool to be used internally + * Sets the thread pool to be used internally for scheduling buffer/event + * pushes. The @thread_pool is expected to already have been prepared via + * gst_task_pool_prepare(). Ownership of @thread_pool is transferred to @src. + * + * This may be used to share a single pool between several slow-paced sources. + * The element will not call gst_task_pool_cleanup() on an externally provided + * pool — cleanup is the responsibility of the code that created it. */ void gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, GstTaskPool * thread_pool) { + GstBaseIdleSrcPrivate *priv; + GstTaskPool *old_pool = NULL; + gpointer old_handle = NULL; + g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); g_return_if_fail (GST_IS_TASK_POOL (thread_pool)); + priv = src->priv; + GST_OBJECT_LOCK (src); - if (src->priv->thread_pool) { - GST_DEBUG_OBJECT (src, "Cleaning up old thread pool"); - gst_task_pool_cleanup (src->priv->thread_pool); - gst_object_unref (src->priv->thread_pool); + old_pool = priv->thread_pool; + old_handle = priv->thread_handle; + priv->thread_pool = thread_pool; /* transfer full */ + priv->thread_handle = NULL; + GST_OBJECT_UNLOCK (src); + + /* Drain any pending work on the *previous* pool — the handle is only valid + * against the pool that produced it. Then drop our reference. Note we do + * NOT call gst_task_pool_cleanup() here: the old pool may be shared with + * other elements. */ + if (old_pool) { + if (old_handle) { + gst_task_pool_join (old_pool, old_handle); + } + gst_object_unref (old_pool); } - GST_DEBUG_OBJECT (src, "Setting external thread pool %p", thread_pool); - src->priv->thread_pool = thread_pool; - GST_OBJECT_UNLOCK (src); + GST_DEBUG_OBJECT (src, "Configured thread pool %p", thread_pool); } /** From fdef358c333b05b0f1e5a132550485c9d09b6d06 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:31:28 +0900 Subject: [PATCH 32/62] libs/base/idlesrc: don't cleanup() a possibly-shared pool & drop dead dispose --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index c27cc94e1f6..f55e886f316 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1727,30 +1727,30 @@ gst_base_idle_src_default_alloc (GstBaseIdleSrc * src, } } -static void -gst_base_idle_src_dispose (GObject * object) -{ - GstBaseIdleSrc *src; - src = GST_BASE_IDLE_SRC (object); - (void) src; - - G_OBJECT_CLASS (parent_class)->dispose (object); -} - static void gst_base_idle_src_finalize (GObject * object) { - GstBaseIdleSrc *src; - src = GST_BASE_IDLE_SRC (object); + GstBaseIdleSrc *src = GST_BASE_IDLE_SRC (object); + GstBaseIdleSrcPrivate *priv = src->priv; + GstMiniObject *obj; - g_queue_free (src->priv->obj_queue); + /* Drain anything that might still be queued. */ + while ((obj = g_queue_pop_head (priv->obj_queue))) + gst_mini_object_unref (obj); + g_queue_free (priv->obj_queue); + priv->obj_queue = NULL; - if (src->priv->thread_pool) { - gst_task_pool_cleanup (src->priv->thread_pool); - gst_object_unref (src->priv->thread_pool); - src->priv->thread_pool = NULL; + /* If we still hold a handle, drop it on the originating pool. */ + if (priv->thread_handle && priv->thread_pool) { + gst_task_pool_join (priv->thread_pool, priv->thread_handle); + priv->thread_handle = NULL; } + /* Just unref the pool. Never call gst_task_pool_cleanup() here: the pool + * may have been set by the user and shared with other elements. The pool's + * own finalize takes care of stopping its workers when the last ref drops. */ + g_clear_object (&priv->thread_pool); + G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -1771,7 +1771,6 @@ gst_base_idle_src_class_init (GstBaseIdleSrcClass * klass) parent_class = g_type_class_peek_parent (klass); - gobject_class->dispose = gst_base_idle_src_dispose; gobject_class->finalize = gst_base_idle_src_finalize; gobject_class->set_property = gst_base_idle_src_set_property; gobject_class->get_property = gst_base_idle_src_get_property; From 6f22493967313cd8adaa1148ed66be4f0223d5a2 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:34:18 +0900 Subject: [PATCH 33/62] libs/base/idlesrc: fix the -1 check for buffer allocation With size now a guint, size != -1 is effectively always true (only false at G_MAXUINT). The intent of the function is 'use the pool if there is one, otherwise allocate size bytes via the negotiated allocator', so the spurious check should just go. --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index f55e886f316..0e2854d6aa8 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1681,7 +1681,7 @@ static GstFlowReturn gst_base_idle_src_default_alloc (GstBaseIdleSrc * src, guint size, GstBuffer ** buffer) { - GstFlowReturn ret; + GstFlowReturn ret = GST_FLOW_OK; GstBaseIdleSrcPrivate *priv = src->priv; GstBufferPool *buf_pool = NULL; GstAllocator *allocator = NULL; @@ -1698,16 +1698,10 @@ gst_base_idle_src_default_alloc (GstBaseIdleSrc * src, if (buf_pool) { ret = gst_buffer_pool_acquire_buffer (buf_pool, buffer, NULL); - } else if (size != -1) { + } else { *buffer = gst_buffer_new_allocate (allocator, size, ¶ms); if (G_UNLIKELY (*buffer == NULL)) goto alloc_failed; - - ret = GST_FLOW_OK; - } else { - GST_WARNING_OBJECT (src, "Not trying to alloc %u bytes. Blocksize not set?", - size); - goto alloc_failed; } done: @@ -1718,13 +1712,10 @@ gst_base_idle_src_default_alloc (GstBaseIdleSrc * src, return ret; - /* ERRORS */ alloc_failed: - { - GST_ERROR_OBJECT (src, "Failed to allocate %u bytes", size); - ret = GST_FLOW_ERROR; - goto done; - } + GST_ERROR_OBJECT (src, "Failed to allocate %u bytes", size); + ret = GST_FLOW_ERROR; + goto done; } static void From 96f84a25f3e61471b4a7d7f7531959b9d160794e Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:35:15 +0900 Subject: [PATCH 34/62] libs/base/idlesrc: handle the GError from gst_task_pool_prepare() --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 0e2854d6aa8..12cb6029d2c 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1803,34 +1803,34 @@ gst_base_idle_src_init (GstBaseIdleSrc * src, gpointer g_class) GST_DEBUG_OBJECT (src, "creating src pad"); pad = gst_pad_new_from_template (pad_template, "src"); - - GST_DEBUG_OBJECT (src, "setting functions on src pad"); gst_pad_set_activatemode_function (pad, gst_base_idle_src_activate_mode); gst_pad_set_event_function (pad, gst_base_idle_src_event); gst_pad_set_query_function (pad, gst_base_idle_src_query); - /* hold pointer to pad */ src->srcpad = pad; - GST_DEBUG_OBJECT (src, "adding src pad"); gst_element_add_pad (GST_ELEMENT (src), pad); - /* we operate in BYTES by default */ gst_base_idle_src_set_format (src, GST_FORMAT_BYTES); src->priv->do_timestamp = DEFAULT_DO_TIMESTAMP; - GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_SOURCE); src->priv->obj_queue = g_queue_new (); - /* Create a shared task pool as default thread pool for this base class - * with a default thread per pool of 1. This would be suboptimal for most - * cases but only be biased to very rare pushes */ - GstTaskPool *thread_pool = gst_shared_task_pool_new (); - gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (thread_pool), 1); - src->priv->thread_pool = thread_pool; - - GError *error = NULL; - gst_task_pool_prepare (src->priv->thread_pool, &error); + /* Default internal pool — capped at 1 worker. */ + { + GstTaskPool *thread_pool = gst_shared_task_pool_new (); + GError *error = NULL; + + gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (thread_pool), + 1); + gst_task_pool_prepare (thread_pool, &error); + if (G_UNLIKELY (error != NULL)) { + GST_ERROR_OBJECT (src, "Failed to prepare default thread pool: %s", + error->message); + g_clear_error (&error); + } + src->priv->thread_pool = thread_pool; + } GST_DEBUG_OBJECT (src, "init done"); } From 78adc6c265eb2a0671c1bab0a1d81f920ca24415 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:37:05 +0900 Subject: [PATCH 35/62] libs/base/idlesrc: mirror behaviour for non-flow events: log and continue --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 12cb6029d2c..67bd27bce57 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1375,14 +1375,16 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) GstEvent *event = GST_EVENT_CAST (obj); GST_DEBUG_OBJECT (src, "About to push Event %" GST_PTR_FORMAT, event); if (!gst_pad_push_event (pad, event)) { - GST_ELEMENT_ERROR (src, CORE, EVENT, - ("Failed to push event."), - ("failed to push event from queue during processing loop")); + /* Mirror GstBaseSrc behaviour for non-flow events: log and continue. + * We deliberately do NOT post a message on the bus here — event push + * failures are typically transient (e.g. peer not linked yet) and not + * a fatal element error. Flow errors are still surfaced below via + * GST_ELEMENT_FLOW_ERROR(). */ + GST_WARNING_OBJECT (src, "Failed to push event %" GST_PTR_FORMAT, event); } } else { GST_ERROR_OBJECT (src, "Unknown object %" GST_PTR_FORMAT " type", obj); } - check_ret_error: if (ret != GST_FLOW_OK && ret != GST_FLOW_FLUSHING) { GST_ELEMENT_FLOW_ERROR (src, ret); From 68b99f331aaec28189db1b28776944a1015bcb0f Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:38:48 +0900 Subject: [PATCH 36/62] libs/base/idlesrc: add class-doc for @alloc and the inline gtk-doc block --- .../gstreamer/libs/gst/base/gstbaseidlesrc.h | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h index e5dbffd86c6..007a8f01751 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h @@ -94,8 +94,10 @@ struct _GstBaseIdleSrc { * @stop: Stop processing. Subclasses should use this to close resources. * @query: Handle a requested query. * @event: Override this to implement custom event handling. - * @alloc: Ask the subclass to allocate a buffer with for offset and size. The - * default implementation will create a new buffer from the negotiated allocator. + * @alloc: Ask the subclass to allocate an output buffer of @size bytes. The + * default implementation will use the negotiated #GstBufferPool when set, + * otherwise it falls back to gst_buffer_new_allocate() with the negotiated + * allocator and parameters. * * Subclasses can override any of the available virtual methods or not, as * needed. @@ -121,13 +123,19 @@ struct _GstBaseIdleSrcClass { gboolean (*set_caps) (GstBaseIdleSrc *src, GstCaps *caps); /** - * GstBaseIdleSrc::alloc: - * @buf: (out) (nullable): + * GstBaseIdleSrcClass::alloc: + * @src: a #GstBaseIdleSrc + * @size: the requested size of the buffer in bytes + * @buf: (out) (transfer full) (nullable): the resulting #GstBuffer * - * Ask the subclass to allocate an output buffer with @size, the default - * implementation will use the negotiated allocator. + * Ask the subclass to allocate an output buffer of @size bytes. The default + * implementation uses the negotiated #GstBufferPool or, when none is set, + * the negotiated #GstAllocator together with the configured + * #GstAllocationParams. + * + * Returns: a #GstFlowReturn */ - GstFlowReturn (*alloc) (GstBaseIdleSrc *src, guint size, GstBuffer **buf); + GstFlowReturn (*alloc) (GstBaseIdleSrc *src, guint size, GstBuffer **buf); /* setup allocation query */ gboolean (*decide_allocation) (GstBaseIdleSrc *src, GstQuery *query); From d8cb781b4b58d7eeebb371452faedb5bfe9b00cd Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:41:34 +0900 Subject: [PATCH 37/62] libs/base/idlesrc/test: initialize err, don't cleanup() after transfer --- .../gstreamer/tests/check/libs/baseidlesrc.c | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 3cdaf87f9e4..3a4215a6c07 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -191,32 +191,44 @@ GST_START_TEST (baseidlesrc_thread_pool_set_and_get) { GstElement *src; GstBaseIdleSrc *base_src; - GError *err; + GstTaskPool *thread_pool; + GstTaskPool *new_thread_pool; + GError *err = NULL; src = g_object_new (test_idle_src_get_type (), NULL); base_src = GST_BASE_IDLE_SRC (src); - GstTaskPool *thread_pool = gst_base_idle_src_get_thread_pool (base_src); - fail_unless (thread_pool); + /* The element must expose a default internal pool. */ + thread_pool = gst_base_idle_src_get_thread_pool (base_src); + fail_unless (thread_pool != NULL); gst_object_unref (thread_pool); - GstTaskPool *new_thread_pool = gst_shared_task_pool_new (); + /* Build a replacement pool and prepare it, checking for failure. */ + new_thread_pool = gst_shared_task_pool_new (); gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (new_thread_pool), 2); gst_task_pool_prepare (new_thread_pool, &err); + fail_unless (err == NULL, "task pool prepare failed: %s", + err ? err->message : "(no error)"); + + /* set_thread_pool() takes ownership (transfer full), so we ref-up for our + * own use here. */ + gst_base_idle_src_set_thread_pool (base_src, + gst_object_ref (new_thread_pool)); - gst_base_idle_src_set_thread_pool (base_src, new_thread_pool); thread_pool = gst_base_idle_src_get_thread_pool (base_src); fail_unless (thread_pool == new_thread_pool); fail_unless_equals_int (2, gst_shared_task_pool_get_max_threads (GST_SHARED_TASK_POOL (thread_pool))); + gst_object_unref (thread_pool); /* drop the get_thread_pool() ref */ + g_object_unref (src); /* this releases the element's ref */ + + /* Now we own the last ref; we may safely cleanup + unref our local one. */ gst_task_pool_cleanup (new_thread_pool); - gst_object_unref (thread_pool); - g_object_unref (src); + gst_object_unref (new_thread_pool); } - GST_END_TEST; #define MAX_SRCS 16 @@ -276,19 +288,23 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) GstElement *srcs[MAX_SRCS]; GThread *threads[MAX_SRCS]; GstBaseIdleSrc *base_src; - GError *err; + GError *err = NULL; + GstTaskPool *pool; guint i; - GstTaskPool *pool = gst_shared_task_pool_new (); + pool = gst_shared_task_pool_new (); gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (pool), MAX_SRCS / 2); gst_task_pool_prepare (pool, &err); + fail_unless (err == NULL, "task pool prepare failed: %s", + err ? err->message : "(no error)"); /* create all sources and harnesses in one go */ for (i = 0; i < MAX_SRCS; i++) { srcs[i] = g_object_new (test_idle_src_get_type (), NULL); base_src = GST_BASE_IDLE_SRC (srcs[i]); + /* transfer-full — give each element its own ref */ gst_base_idle_src_set_thread_pool (base_src, gst_object_ref (pool)); hs[i] = gst_harness_new_with_element (GST_ELEMENT (srcs[i]), NULL, "src"); @@ -296,27 +312,25 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) gst_harness_play (hs[i]); } - /* start pushing to from all sources */ for (i = 0; i < MAX_SRCS; i++) { char *thread_name = g_strdup_printf ("pusher-%d", i); threads[i] = g_thread_new (thread_name, _push_func, hs[i]); g_free (thread_name); } - /* wait for all sources to finish pushing */ - for (i = 0; i < MAX_SRCS; i++) { + for (i = 0; i < MAX_SRCS; i++) g_thread_join (threads[i]); - } - /* teardown */ for (i = 0; i < MAX_SRCS; i++) { gst_harness_teardown (hs[i]); g_object_unref (srcs[i]); } + + /* All elements have released their refs; we own the last one — safe to + * cleanup + unref. */ gst_task_pool_cleanup (pool); gst_object_unref (pool); } - GST_END_TEST; static Suite * From c1336715eda846284de2562bfa1aee289bb5589b Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:43:38 +0900 Subject: [PATCH 38/62] libs/base/idlesrc/test: fixup typo --- subprojects/gstreamer/tests/check/libs/baseidlesrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 3a4215a6c07..9b487378f69 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -262,7 +262,7 @@ _push_func (gpointer data) } } - /* yield to cause some hadvoc */ + /* yield to cause some havoc */ GST_LOG ("Yielding from source %s", gst_element_get_name (e)); g_thread_yield (); From 7925059a8250b3042c6ff52b328a2bba8961fa1f Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 17:47:54 +0900 Subject: [PATCH 39/62] libs/base/idlesrc: fix the bogus gst_base_idle_src_alloc() reference --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 67bd27bce57..1b24d9837d7 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -50,7 +50,8 @@ * * - Push-only operation (no getrange/pull support) * - Optional live-source mode using gst_base_idle_src_set_live() - * - Built-in buffer allocation via gst_base_idle_src_alloc() + * - Buffer allocation via the #GstBaseIdleSrcClass.alloc vfunc, which by + * default uses the negotiated #GstBufferPool / #GstAllocator * - Negotiation helpers for caps and allocation queries * - Thread-pool support for external or shared producers * - Optional automatic timestamping @@ -127,11 +128,19 @@ * } * * static GstFlowReturn - * my_idle_src_loop (GstBaseIdleSrc *src) + * my_idle_src_loop (GstMyIdleSrc *self) * { - * GstBuffer *buffer = NULL; - * gst_base_idle_src_alloc (src, FRAME_SIZE, &buffer); - * // Fill buffer data + * GstBaseIdleSrc *src = GST_BASE_IDLE_SRC (self); + * GstBaseIdleSrcClass *klass = GST_BASE_IDLE_SRC_GET_CLASS (src); + * GstBuffer *buffer = NULL; + * GstFlowReturn ret; + * + * ret = klass->alloc (src, FRAME_SIZE, &buffer); + * if (ret != GST_FLOW_OK) + * return ret; + * + * // ... fill buffer ... + * * gst_base_idle_src_submit_buffer (src, buffer); * return GST_FLOW_OK; * } From 59c38e2c0d604764222cb35cdf7e058a5a4eb855 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 18:21:42 +0900 Subject: [PATCH 40/62] libs/base/idlesrc: protect against invalid thread-pool handle priv->thread_handle may be NULL (e.g. if _stop runs right after _start's wait=TRUE join). Most pool implementations treat NULL defensively but it's documented as "the same value returned by gst_task_pool_push", so passing NULL is technically out-of-contract and GstSharedTaskPool does deref it. --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 1b24d9837d7..e3e9a2ee97e 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1558,8 +1558,10 @@ gst_base_idle_src_stop (GstBaseIdleSrc * src) GST_DEBUG_OBJECT (src, "stopping source"); src->running = FALSE; - gst_task_pool_join (priv->thread_pool, priv->thread_handle); - priv->thread_handle = NULL; + if (priv->thread_handle) { + gst_task_pool_join (priv->thread_pool, priv->thread_handle); + priv->thread_handle = NULL; + } /* clean up any leftovers on the queue */ while ((obj = g_queue_pop_head (src->priv->obj_queue))) { From dbcfa1eb1d7382f8f4d7bd1212e61a4151d43428 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 18:23:24 +0900 Subject: [PATCH 41/62] libs/base/idlesrc: fix race on thread-pool and thread-handle on start and set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gst_base_idle_src_start_task reads and writes priv->thread_pool and priv->thread_handle without holding OBJECT_LOCK, but set_thread_pool and finalize mutate both under the lock (and stop also reads them lock-free). Since producers call submit_buffer → start_task from arbitrary threads while the application can call set_thread_pool concurrently, there's a real data race + UAF window: * Thread A is in start_task, has just read priv->thread_pool into a local register, but hasn't called gst_task_pool_push yet. * Thread B calls set_thread_pool, swaps the pointer, unrefs the previous pool — the last reference goes to zero and the pool is finalized. * Thread A now calls gst_task_pool_push on a freed object. --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index e3e9a2ee97e..699c2ba78d1 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1442,34 +1442,55 @@ static void gst_base_idle_src_start_task (GstBaseIdleSrc * src, gboolean wait) { GstBaseIdleSrcPrivate *priv = src->priv; + GstTaskPool *pool; + gpointer prev_handle, new_handle; GError *error = NULL; - GST_DEBUG_OBJECT (src, "Starting task"); + GST_OBJECT_LOCK (src); + pool = priv->thread_pool ? gst_object_ref (priv->thread_pool) : NULL; + prev_handle = priv->thread_handle; + priv->thread_handle = NULL; + GST_OBJECT_UNLOCK (src); - /* If a previous task is still outstanding, join it first so that buffer - * ordering is preserved and we never have two workers draining the queue - * concurrently (this matters for shared pools with max_threads > 1). */ - if (priv->thread_handle) { - gst_task_pool_join (priv->thread_pool, priv->thread_handle); - priv->thread_handle = NULL; + if (G_UNLIKELY (pool == NULL)) { + GST_WARNING_OBJECT (src, "No thread pool configured"); + return; } - priv->thread_handle = - gst_task_pool_push (priv->thread_pool, gst_base_idle_src_func, src, - &error); + /* Join the *previous* handle on the pool it was issued on. Because we + * cleared priv->thread_handle above, set_thread_pool() will not also try + * to join it from the other side. */ + if (prev_handle) + gst_task_pool_join (pool, prev_handle); + new_handle = gst_task_pool_push (pool, gst_base_idle_src_func, src, &error); if (G_UNLIKELY (error != NULL)) { GST_ERROR_OBJECT (src, "Failed to push task to pool: %s", error->message); g_clear_error (&error); - /* nothing else we can do; the queued objects will be processed next time - * a task is successfully pushed, or drained in _stop(). */ + gst_object_unref (pool); return; } - if (wait && priv->thread_handle) { - gst_task_pool_join (priv->thread_pool, priv->thread_handle); - priv->thread_handle = NULL; + if (wait && new_handle) { + gst_task_pool_join (pool, new_handle); + new_handle = NULL; + } + + GST_OBJECT_LOCK (src); + /* Only install our handle if no concurrent set_thread_pool() happened + * meanwhile — if it did, the pool we used is no longer the active one + * and the handle is meaningless against the new pool. */ + if (priv->thread_pool == pool) { + priv->thread_handle = new_handle; + new_handle = NULL; } + GST_OBJECT_UNLOCK (src); + + /* If the pool was swapped under us, drain our handle now. */ + if (new_handle) + gst_task_pool_join (pool, new_handle); + + gst_object_unref (pool); } static void From 466950215ee9f5388efade06aae63d8572a7f2d3 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 18:25:47 +0900 Subject: [PATCH 42/62] libs/base/idlesrc: don't support switching pools mid-flow --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 699c2ba78d1..fc99a33d3d8 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1233,6 +1233,11 @@ gst_base_idle_src_get_buffer_pool (GstBaseIdleSrc * src) * This may be used to share a single pool between several slow-paced sources. * The element will not call gst_task_pool_cleanup() on an externally provided * pool — cleanup is the responsibility of the code that created it. + * + * This function must only be called when the element is not yet running + * (i.e. before transitioning to %GST_STATE_PAUSED). Swapping pools mid-flow + * is not supported because already-queued buffers would be orphaned on the + * previous pool. */ void gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, @@ -1245,6 +1250,14 @@ gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); g_return_if_fail (GST_IS_TASK_POOL (thread_pool)); + if (src->running) { + GST_WARNING_OBJECT (src, + "Refusing to swap thread pool while the element is running; " + "call set_thread_pool() before the element transitions to PAUSED."); + gst_object_unref (thread_pool); + return; + } + priv = src->priv; GST_OBJECT_LOCK (src); From 080a14468765904d437cd2f41d0577fe1bf80731 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 18:28:58 +0900 Subject: [PATCH 43/62] libs/base/idlesrc/tests: extra coverage --- .../gstreamer/tests/check/libs/baseidlesrc.c | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 9b487378f69..e0e07277f2f 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -333,6 +333,224 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) } GST_END_TEST; +GST_START_TEST (baseidlesrc_default_thread_pool_is_prepared) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GstTaskPool *pool = gst_base_idle_src_get_thread_pool (base_src); + GError *err = NULL; + gpointer handle; + + fail_unless (pool != NULL); + fail_unless (GST_IS_SHARED_TASK_POOL (pool)); + fail_unless_equals_int (1, + gst_shared_task_pool_get_max_threads (GST_SHARED_TASK_POOL (pool))); + + /* Already prepared → push must succeed without prepare(). */ + handle = gst_task_pool_push (pool, (GstTaskPoolFunction) g_thread_yield, + NULL, &err); + fail_unless (err == NULL, "default pool push failed: %s", + err ? err->message : ""); + if (handle) + gst_task_pool_join (pool, handle); + + gst_object_unref (pool); + g_object_unref (src); +} +GST_END_TEST; + +GST_START_TEST (baseidlesrc_replace_thread_pool_preserves_shared_pool) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GError *err = NULL; + GstTaskPool *shared, *other; + gpointer h; + + shared = gst_shared_task_pool_new (); + gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (shared), 2); + gst_task_pool_prepare (shared, &err); + fail_unless (err == NULL); + + gst_base_idle_src_set_thread_pool (base_src, gst_object_ref (shared)); + + other = gst_shared_task_pool_new (); + gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (other), 1); + gst_task_pool_prepare (other, &err); + fail_unless (err == NULL); + gst_base_idle_src_set_thread_pool (base_src, other); /* transfer full */ + + /* Would fail/UAF if shared had been cleaned up. */ + h = gst_task_pool_push (shared, (GstTaskPoolFunction) g_thread_yield, NULL, + &err); + fail_unless (err == NULL); + if (h) + gst_task_pool_join (shared, h); + + g_object_unref (src); + gst_task_pool_cleanup (shared); + gst_object_unref (shared); +} +GST_END_TEST; + +GST_START_TEST (baseidlesrc_finalize_one_keeps_shared_pool_alive) +{ + GError *err = NULL; + GstTaskPool *pool = gst_shared_task_pool_new (); + TestIdleSrc *a, *b; + GstHarness *hb; + GstBuffer *buf; + + gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (pool), 2); + gst_task_pool_prepare (pool, &err); + fail_unless (err == NULL); + + a = g_object_new (test_idle_src_get_type (), NULL); + b = g_object_new (test_idle_src_get_type (), NULL); + gst_base_idle_src_set_thread_pool (GST_BASE_IDLE_SRC (a), + gst_object_ref (pool)); + gst_base_idle_src_set_thread_pool (GST_BASE_IDLE_SRC (b), + gst_object_ref (pool)); + + hb = gst_harness_new_with_element (GST_ELEMENT (b), NULL, "src"); + gst_harness_set_sink_caps_str (hb, "foo/bar"); + gst_harness_play (hb); + + g_object_unref (a); /* must not break b */ + + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (b, &buf)); + gst_base_idle_src_submit_buffer (GST_BASE_IDLE_SRC (b), buf); + gst_buffer_unref (gst_harness_pull (hb)); + + gst_harness_teardown (hb); + g_object_unref (b); + + gst_task_pool_cleanup (pool); + gst_object_unref (pool); +} +GST_END_TEST; + +GST_START_TEST (baseidlesrc_submission_order_is_preserved) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GstHarness *h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); + const guint N = 200; + guint i; + + gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_play (h); + + for (i = 0; i < N; i++) { + GstBuffer *buf; + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); + GST_BUFFER_OFFSET (buf) = i; + gst_base_idle_src_submit_buffer (base_src, buf); + } + for (i = 0; i < N; i++) { + GstBuffer *buf = gst_harness_pull (h); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET (buf), i); + gst_buffer_unref (buf); + } + + gst_harness_teardown (h); + g_object_unref (src); +} +GST_END_TEST; + +GST_START_TEST (baseidlesrc_do_timestamp_on_buffer_list) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GstHarness *h; + GstBufferList *list; + guint i; + + gst_base_idle_src_set_do_timestamp (base_src, TRUE); + h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); + gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_play (h); + + list = gst_buffer_list_new_sized (4); + for (i = 0; i < 4; i++) { + GstBuffer *buf; + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); + gst_buffer_list_insert (list, -1, buf); + } + gst_base_idle_src_submit_buffer_list (base_src, list); + + for (i = 0; i < 4; i++) { + GstBuffer *buf = gst_harness_pull (h); + fail_unless (buf != NULL); + fail_unless (GST_BUFFER_PTS_IS_VALID (buf)); + gst_buffer_unref (buf); + } + + gst_harness_teardown (h); + g_object_unref (src); +} +GST_END_TEST; + +GST_START_TEST (baseidlesrc_set_format_rejects_invalid) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + + ASSERT_CRITICAL (gst_base_idle_src_set_format (base_src, GST_FORMAT_PERCENT)); + ASSERT_CRITICAL (gst_base_idle_src_set_format (base_src, GST_FORMAT_DEFAULT)); + + gst_base_idle_src_set_format (base_src, GST_FORMAT_BYTES); + gst_base_idle_src_set_format (base_src, GST_FORMAT_TIME); + gst_base_idle_src_set_format (base_src, GST_FORMAT_BUFFERS); + + g_object_unref (src); +} +GST_END_TEST; + +GST_START_TEST (baseidlesrc_submit_pull_loop_no_deadlock) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GstHarness *h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); + gint64 deadline; + guint i; + + gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_play (h); + deadline = g_get_monotonic_time () + 5 * G_TIME_SPAN_SECOND; + + for (i = 0; i < 100; i++) { + GstBuffer *buf; + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); + gst_base_idle_src_submit_buffer (base_src, buf); + gst_buffer_unref (gst_harness_pull (h)); + fail_unless (g_get_monotonic_time () < deadline, + "submit/pull loop deadlocked or stalled"); + } + + gst_harness_teardown (h); + g_object_unref (src); +} +GST_END_TEST; + +GST_START_TEST (baseidlesrc_submit_after_stop_is_safe) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GstHarness *h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); + GstBuffer *buf; + + gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_play (h); + gst_harness_teardown (h); + + fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); + gst_base_idle_src_submit_buffer (base_src, buf); /* must drop, not crash */ + + g_object_unref (src); +} +GST_END_TEST; + static Suite * baseidlesrc_suite (void) { @@ -346,6 +564,14 @@ baseidlesrc_suite (void) tcase_add_test (tc, baseidlesrc_handle_events); tcase_add_test (tc, baseidlesrc_thread_pool_set_and_get); tcase_add_test (tc, baseidlesrc_thread_pool_submit); + tcase_add_test (tc, baseidlesrc_default_thread_pool_is_prepared); + tcase_add_test (tc, baseidlesrc_replace_thread_pool_preserves_shared_pool); + tcase_add_test (tc, baseidlesrc_finalize_one_keeps_shared_pool_alive); + tcase_add_test (tc, baseidlesrc_submission_order_is_preserved); + tcase_add_test (tc, baseidlesrc_do_timestamp_on_buffer_list); + tcase_add_test (tc, baseidlesrc_set_format_rejects_invalid); + tcase_add_test (tc, baseidlesrc_submit_pull_loop_no_deadlock); + tcase_add_test (tc, baseidlesrc_submit_after_stop_is_safe); return s; } From 56fb769cf4bae3ee61a010fcbbabcc0ec6a9b319 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 23:01:15 +0900 Subject: [PATCH 44/62] libs/base/idlesrc: track ownership of own thread-pool --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 17 +++++++++++++++- .../gstreamer/tests/check/libs/baseidlesrc.c | 20 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index fc99a33d3d8..b2718fb0b46 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -231,6 +231,7 @@ struct _GstBaseIdleSrcPrivate GQueue *obj_queue; gpointer thread_handle; GstTaskPool *thread_pool; /* OBJECT_LOCK */ + gboolean owns_thread_pool; /* OBJECT_LOCK - TRUE iff we created it at init() */ GstAllocationParams params; /* OBJECT_LOCK */ }; @@ -1246,6 +1247,7 @@ gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, GstBaseIdleSrcPrivate *priv; GstTaskPool *old_pool = NULL; gpointer old_handle = NULL; + gboolean old_owned = FALSE; g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); g_return_if_fail (GST_IS_TASK_POOL (thread_pool)); @@ -1263,8 +1265,10 @@ gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, GST_OBJECT_LOCK (src); old_pool = priv->thread_pool; old_handle = priv->thread_handle; - priv->thread_pool = thread_pool; /* transfer full */ + old_owned = priv->owns_thread_pool; + priv->thread_pool = thread_pool; /* transfer full */ priv->thread_handle = NULL; + priv->owns_thread_pool = FALSE; /* externally provided, now */ GST_OBJECT_UNLOCK (src); /* Drain any pending work on the *previous* pool — the handle is only valid @@ -1275,6 +1279,10 @@ gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, if (old_handle) { gst_task_pool_join (old_pool, old_handle); } + /* Only cleanup if WE created the previous pool at init() */ + if (old_owned) { + gst_task_pool_cleanup (old_pool); + } gst_object_unref (old_pool); } @@ -1784,6 +1792,12 @@ gst_base_idle_src_finalize (GObject * object) priv->thread_handle = NULL; } + /* Only cleanup the pool if WE created it. An externally-provided pool + * may be shared with other elements; its owner is responsible for + * gst_task_pool_cleanup(). */ + if (priv->thread_pool && priv->owns_thread_pool) + gst_task_pool_cleanup (priv->thread_pool); + /* Just unref the pool. Never call gst_task_pool_cleanup() here: the pool * may have been set by the user and shared with other elements. The pool's * own finalize takes care of stopping its workers when the last ref drops. */ @@ -1877,6 +1891,7 @@ gst_base_idle_src_init (GstBaseIdleSrc * src, gpointer g_class) g_clear_error (&error); } src->priv->thread_pool = thread_pool; + src->priv->owns_thread_pool = TRUE; } GST_DEBUG_OBJECT (src, "init done"); diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index e0e07277f2f..dfb5a62d593 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -551,6 +551,25 @@ GST_START_TEST (baseidlesrc_submit_after_stop_is_safe) } GST_END_TEST; +GST_START_TEST (baseidlesrc_default_pool_is_cleaned_up_on_swap) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GError *err = NULL; + GstTaskPool *replacement = gst_shared_task_pool_new (); + + gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (replacement), 1); + gst_task_pool_prepare (replacement, &err); + fail_unless (err == NULL); + + /* Swap out the default pool — the previous (default) one is owned by us + * and must be cleaned up + unreffed inside set_thread_pool(). Valgrind + * will catch the leak if cleanup() is skipped. */ + gst_base_idle_src_set_thread_pool (GST_BASE_IDLE_SRC (src), replacement); + + g_object_unref (src); +} +GST_END_TEST; + static Suite * baseidlesrc_suite (void) { @@ -572,6 +591,7 @@ baseidlesrc_suite (void) tcase_add_test (tc, baseidlesrc_set_format_rejects_invalid); tcase_add_test (tc, baseidlesrc_submit_pull_loop_no_deadlock); tcase_add_test (tc, baseidlesrc_submit_after_stop_is_safe); + tcase_add_test (tc, baseidlesrc_default_pool_is_cleaned_up_on_swap); return s; } From 6c2e865ead723584e434de9fd9a7f1ca6dd8fb50 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 23:07:41 +0900 Subject: [PATCH 45/62] libs/base/idlesrc/test: add yield helper instead of cast --- subprojects/gstreamer/tests/check/libs/baseidlesrc.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index dfb5a62d593..a1e8af23583 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -333,6 +333,12 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) } GST_END_TEST; + static void + _yield_task (G_GNUC_UNUSED void *user_data) + { + g_thread_yield (); + } + GST_START_TEST (baseidlesrc_default_thread_pool_is_prepared) { TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); @@ -347,8 +353,7 @@ GST_START_TEST (baseidlesrc_default_thread_pool_is_prepared) gst_shared_task_pool_get_max_threads (GST_SHARED_TASK_POOL (pool))); /* Already prepared → push must succeed without prepare(). */ - handle = gst_task_pool_push (pool, (GstTaskPoolFunction) g_thread_yield, - NULL, &err); + handle = gst_task_pool_push (pool, _yield_task, NULL, &err); fail_unless (err == NULL, "default pool push failed: %s", err ? err->message : ""); if (handle) @@ -381,8 +386,7 @@ GST_START_TEST (baseidlesrc_replace_thread_pool_preserves_shared_pool) gst_base_idle_src_set_thread_pool (base_src, other); /* transfer full */ /* Would fail/UAF if shared had been cleaned up. */ - h = gst_task_pool_push (shared, (GstTaskPoolFunction) g_thread_yield, NULL, - &err); + h = gst_task_pool_push (shared, _yield_task, NULL, &err); fail_unless (err == NULL); if (h) gst_task_pool_join (shared, h); From 57a975e349cc044bc7276772027a1f4267714282 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 23:18:21 +0900 Subject: [PATCH 46/62] libs/base/idlesrc: protect running state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stop is the only code path touching priv fields without the object lock. The if (!src->running) early-return in submit_buffer is a TOCTOU check that does not save us from races/corruption: ``` Producer thread Stop thread --------------- ----------- submit_buffer(): if (!src->running) // FALSE src->running = FALSE g_queue_pop_head (obj_queue) ─┐ queue_object(): │ OBJECT_LOCK; │ g_queue_push_tail (obj_queue); <- concurrent mutation ────────┘ → GQueue list corruption / UAF start_task(): priv->thread_handle = handle; src->priv->thread_handle = NULL → missed join, leaked handle ``` Possible outcomes: * Queue corruption / double-free / UAF. * Missed join → leaked task handle (and possibly the worker still running past finalize → use-after-free on src). * gst_task_pool_join racing with set_thread_pool. Locking-order note: stop() is called with STREAM_LOCK held (via pad deactivation), so taking OBJECT_LOCK here is fine in terms of order. Joining the worker is the operation that must happen without OBJECT_LOCK, because the worker takes it itself. --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 57 +++++++++++++++---- .../gstreamer/tests/check/libs/baseidlesrc.c | 34 ++++++++++- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index b2718fb0b46..e6f65b8dd76 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1252,7 +1252,7 @@ gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); g_return_if_fail (GST_IS_TASK_POOL (thread_pool)); - if (src->running) { + if (g_atomic_int_get (&src->running)) { GST_WARNING_OBJECT (src, "Refusing to swap thread pool while the element is running; " "call set_thread_pool() before the element transitions to PAUSED."); @@ -1561,7 +1561,7 @@ gst_base_idle_src_start (GstBaseIdleSrc * src) gst_segment_init (&src->segment, src->segment.format); GST_OBJECT_UNLOCK (src); - src->running = TRUE; + g_atomic_int_set (&src->running, TRUE); src->priv->segment_pending = TRUE; src->priv->segment_seqnum = gst_util_seqnum_next (); @@ -1594,21 +1594,54 @@ gst_base_idle_src_stop (GstBaseIdleSrc * src) { GstBaseIdleSrcClass *bclass; GstBaseIdleSrcPrivate *priv = src->priv; + GstTaskPool *pool = NULL; + gpointer handle = NULL; + GQueue drained = G_QUEUE_INIT; GstMiniObject *obj; gboolean result = TRUE; GST_DEBUG_OBJECT (src, "stopping source"); - src->running = FALSE; - if (priv->thread_handle) { - gst_task_pool_join (priv->thread_pool, priv->thread_handle); - priv->thread_handle = NULL; - } + /* 1. Publish !running atomically so any producer that has not yet entered + * queue_object() bails out in submit_buffer*(). */ + g_atomic_int_set (&src->running, FALSE); - /* clean up any leftovers on the queue */ - while ((obj = g_queue_pop_head (src->priv->obj_queue))) { + /* 2. Snapshot pool + handle under the lock, clear them, and steal the + * queue contents in one shot so we can release the lock before joining + * (the worker takes the same lock in process_object_queue()). */ + GST_OBJECT_LOCK (src); + if (priv->thread_pool) + pool = gst_object_ref (priv->thread_pool); + handle = priv->thread_handle; + priv->thread_handle = NULL; + + /* Steal the queue: move all queued objects into a local GQueue while we + * own the lock, so further queue_object() calls (which take the lock) are + * either ordered before us (and we'll drain them) or land in an empty + * queue we no longer care about. The producer check on !running prevents + * the latter in well-behaved callers; even if it loses the race, we leak + * at most that buffer, not the whole list. */ + while ((obj = g_queue_pop_head (priv->obj_queue))) + g_queue_push_tail (&drained, obj); + GST_OBJECT_UNLOCK (src); + + /* 3. Join outside the lock. */ + if (handle && pool) + gst_task_pool_join (pool, handle); + g_clear_object (&pool); + + /* 4. Drop drained objects (no lock needed; they're local now). */ + while ((obj = g_queue_pop_head (&drained))) + gst_mini_object_unref (obj); + + /* 5. Any objects that the worker pushed back via queue_object during + * join? Drain them too under the lock. */ + GST_OBJECT_LOCK (src); + while ((obj = g_queue_pop_head (priv->obj_queue))) + g_queue_push_tail (&drained, obj); + GST_OBJECT_UNLOCK (src); + while ((obj = g_queue_pop_head (&drained))) gst_mini_object_unref (obj); - } bclass = GST_BASE_IDLE_SRC_GET_CLASS (src); if (bclass->stop) @@ -1689,7 +1722,7 @@ gst_base_idle_src_submit_buffer (GstBaseIdleSrc * src, GstBuffer * buffer) g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); g_return_if_fail (GST_IS_BUFFER (buffer)); - if (!src->running) { + if (!g_atomic_int_get (&src->running)) { GST_ERROR_OBJECT (src, "Sending buffer to stopped src is not valid"); gst_buffer_unref (buffer); return; @@ -1716,7 +1749,7 @@ gst_base_idle_src_submit_buffer_list (GstBaseIdleSrc * src, g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); g_return_if_fail (GST_IS_BUFFER_LIST (buffer_list)); - if (!src->running) { + if (!g_atomic_int_get (&src->running)) { GST_ERROR_OBJECT (src, "Sending bufferlist to stopped src is not valid"); gst_buffer_list_unref (buffer_list); return; diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index a1e8af23583..7cd4352d919 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -386,7 +386,8 @@ GST_START_TEST (baseidlesrc_replace_thread_pool_preserves_shared_pool) gst_base_idle_src_set_thread_pool (base_src, other); /* transfer full */ /* Would fail/UAF if shared had been cleaned up. */ - h = gst_task_pool_push (shared, _yield_task, NULL, &err); + h = gst_task_pool_push (shared, _yield_task, NULL, + &err); fail_unless (err == NULL); if (h) gst_task_pool_join (shared, h); @@ -574,6 +575,36 @@ GST_START_TEST (baseidlesrc_default_pool_is_cleaned_up_on_swap) } GST_END_TEST; +GST_START_TEST (baseidlesrc_stop_races_with_submit) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GstHarness *h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); + GThread *producer; + struct { GstBaseIdleSrc *src; gint stop; } ctx = { base_src, 0 }; + + gpointer producer_fn (gpointer data) { + typeof (ctx) *c = data; + while (!g_atomic_int_get (&c->stop)) { + GstBuffer *buf = gst_buffer_new_allocate (NULL, 64, NULL); + gst_base_idle_src_submit_buffer (c->src, buf); + } + return NULL; + } + + gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_play (h); + + producer = g_thread_new ("producer", producer_fn, &ctx); + g_usleep (50 * 1000); /* let it churn */ + gst_harness_teardown (h); /* triggers stop() */ + g_atomic_int_set (&ctx.stop, 1); + g_thread_join (producer); + + g_object_unref (src); +} +GST_END_TEST; + static Suite * baseidlesrc_suite (void) { @@ -596,6 +627,7 @@ baseidlesrc_suite (void) tcase_add_test (tc, baseidlesrc_submit_pull_loop_no_deadlock); tcase_add_test (tc, baseidlesrc_submit_after_stop_is_safe); tcase_add_test (tc, baseidlesrc_default_pool_is_cleaned_up_on_swap); + tcase_add_test (tc, baseidlesrc_stop_races_with_submit); return s; } From f4430b32bc8761b83c1e946a97281cbbc9c66022 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 23:34:50 +0900 Subject: [PATCH 47/62] libs/base/idlesrc: correct comments --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index e6f65b8dd76..04093946af0 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1619,8 +1619,8 @@ gst_base_idle_src_stop (GstBaseIdleSrc * src) * own the lock, so further queue_object() calls (which take the lock) are * either ordered before us (and we'll drain them) or land in an empty * queue we no longer care about. The producer check on !running prevents - * the latter in well-behaved callers; even if it loses the race, we leak - * at most that buffer, not the whole list. */ + * the latter in well-behaved callers; even if it loses the race, we drain it + * on the second pass below */ while ((obj = g_queue_pop_head (priv->obj_queue))) g_queue_push_tail (&drained, obj); GST_OBJECT_UNLOCK (src); @@ -1831,9 +1831,6 @@ gst_base_idle_src_finalize (GObject * object) if (priv->thread_pool && priv->owns_thread_pool) gst_task_pool_cleanup (priv->thread_pool); - /* Just unref the pool. Never call gst_task_pool_cleanup() here: the pool - * may have been set by the user and shared with other elements. The pool's - * own finalize takes care of stopping its workers when the last ref drops. */ g_clear_object (&priv->thread_pool); G_OBJECT_CLASS (parent_class)->finalize (object); From 78198e07859c060222227b0c47e630db1fae0e25 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 23:39:13 +0900 Subject: [PATCH 48/62] libs/base/idlesrc: correct indentation --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 04093946af0..b37f07b1cba 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -231,7 +231,7 @@ struct _GstBaseIdleSrcPrivate GQueue *obj_queue; gpointer thread_handle; GstTaskPool *thread_pool; /* OBJECT_LOCK */ - gboolean owns_thread_pool; /* OBJECT_LOCK - TRUE iff we created it at init() */ + gboolean owns_thread_pool; /* OBJECT_LOCK - TRUE iff we created it at init() */ GstAllocationParams params; /* OBJECT_LOCK */ }; @@ -1406,10 +1406,10 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) GST_DEBUG_OBJECT (src, "About to push Event %" GST_PTR_FORMAT, event); if (!gst_pad_push_event (pad, event)) { /* Mirror GstBaseSrc behaviour for non-flow events: log and continue. - * We deliberately do NOT post a message on the bus here — event push - * failures are typically transient (e.g. peer not linked yet) and not - * a fatal element error. Flow errors are still surfaced below via - * GST_ELEMENT_FLOW_ERROR(). */ + * We deliberately do NOT post a message on the bus here — event push + * failures are typically transient (e.g. peer not linked yet) and not + * a fatal element error. Flow errors are still surfaced below via + * GST_ELEMENT_FLOW_ERROR(). */ GST_WARNING_OBJECT (src, "Failed to push event %" GST_PTR_FORMAT, event); } } else { From e59381bdb9c1f979c696b498b4cbc4c88f511437 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Thu, 4 Jun 2026 23:44:02 +0900 Subject: [PATCH 49/62] libs/base/idlesrc: use a struct-copy steal instead of the pop/push loop for internal queue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GQueue is just { GList *head; GList *tail; guint length; }, so copying it out and re-initializing the source detaches the whole list in O(1) without freeing/reallocating any GList nodes. ** Why this is safe? ** * GQueue has no pointer-to-self or heap-owned envelope; it's a value type whose contents are entirely in the head/tail GList chain. A struct copy moves ownership of the chain wholesale. * g_queue_init() resets head/tail to NULL and length to 0 — equivalent to a freshly declared GQueue drained = G_QUEUE_INIT;. After the swap, the source queue is genuinely empty; any subsequent g_queue_push_tail() call from a late producer behaves correctly (it starts a new list). * The drained GQueue is now a stack-local, owned entirely by this thread. The pop_head loop that follows still walks the list — but we have to walk it anyway to unref each GstMiniObject. The win is that we don't also free + re-allocate every GList node along the way (which the old pop_head + push_tail pair did via g_slice_*). ** Does it matter? ** Per stop(), with N queued objects the old code did 2·N g_slice_free/g_slice_alloc calls just to move the chain from one queue to another. The new version does zero! yes, ZERO! For a typical stop with a small queue it's noise; for stress tests that submit thousands of buffers and then tear down (or a pipeline with hundreds of idlesrc instances stopping concurrently) the difference is measurable and — more importantly — there's no longer a transient pressure spike on the slice allocator while holding OBJECT_LOCK. --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index b37f07b1cba..a82c0cdbb6c 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1615,14 +1615,15 @@ gst_base_idle_src_stop (GstBaseIdleSrc * src) handle = priv->thread_handle; priv->thread_handle = NULL; - /* Steal the queue: move all queued objects into a local GQueue while we - * own the lock, so further queue_object() calls (which take the lock) are - * either ordered before us (and we'll drain them) or land in an empty - * queue we no longer care about. The producer check on !running prevents - * the latter in well-behaved callers; even if it loses the race, we drain it - * on the second pass below */ - while ((obj = g_queue_pop_head (priv->obj_queue))) - g_queue_push_tail (&drained, obj); + /* Steal the queue in O(1): GQueue is a plain { head, tail, length } struct, + * so copying it out and re-initializing the source detaches the whole list + * without walking it or freeing/reallocating any GList nodes (which a + * pop_head + push_tail loop would do via the GList slice allocator). Any + * further queue_object() calls (which take OBJECT_LOCK) are either ordered + * before us — and we'll drain them here — or land in the now-empty queue + * and get caught by the second-pass drain below. */ + drained = *priv->obj_queue; + g_queue_init (priv->obj_queue); GST_OBJECT_UNLOCK (src); /* 3. Join outside the lock. */ @@ -1635,10 +1636,11 @@ gst_base_idle_src_stop (GstBaseIdleSrc * src) gst_mini_object_unref (obj); /* 5. Any objects that the worker pushed back via queue_object during - * join? Drain them too under the lock. */ + * join (or that a producer raced past the !running check)? Drain them + * too — same O(1) steal pattern. */ GST_OBJECT_LOCK (src); - while ((obj = g_queue_pop_head (priv->obj_queue))) - g_queue_push_tail (&drained, obj); + drained = *priv->obj_queue; + g_queue_init (priv->obj_queue); GST_OBJECT_UNLOCK (src); while ((obj = g_queue_pop_head (&drained))) gst_mini_object_unref (obj); @@ -1905,6 +1907,10 @@ gst_base_idle_src_init (GstBaseIdleSrc * src, gpointer g_class) src->priv->do_timestamp = DEFAULT_DO_TIMESTAMP; GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_SOURCE); + /* Plain g_queue_new(): stop() steals the queue contents via a struct copy, + * which assumes no GDestroyNotify is attached. If we ever need one, switch + * to g_queue_init() of an embedded GQueue and clear via g_queue_clear_full() + * after the steal. */ src->priv->obj_queue = g_queue_new (); /* Default internal pool — capped at 1 worker. */ From 0138caf10c1831e13589dcdad9ca1ef3f6833771 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 15:38:47 +0900 Subject: [PATCH 50/62] libs/base/idlesrc/test: cleanup other taks pool --- .../gstreamer/tests/check/libs/baseidlesrc.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 7cd4352d919..382dd9d0929 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -383,16 +383,19 @@ GST_START_TEST (baseidlesrc_replace_thread_pool_preserves_shared_pool) gst_shared_task_pool_set_max_threads (GST_SHARED_TASK_POOL (other), 1); gst_task_pool_prepare (other, &err); fail_unless (err == NULL); - gst_base_idle_src_set_thread_pool (base_src, other); /* transfer full */ + gst_base_idle_src_set_thread_pool (base_src, gst_object_ref (other)); /* Would fail/UAF if shared had been cleaned up. */ - h = gst_task_pool_push (shared, _yield_task, NULL, - &err); + h = gst_task_pool_push (shared, _yield_task, NULL, &err); fail_unless (err == NULL); if (h) gst_task_pool_join (shared, h); g_object_unref (src); + + gst_task_pool_cleanup (other); + gst_object_unref (other); + gst_task_pool_cleanup (shared); gst_object_unref (shared); } @@ -569,7 +572,11 @@ GST_START_TEST (baseidlesrc_default_pool_is_cleaned_up_on_swap) /* Swap out the default pool — the previous (default) one is owned by us * and must be cleaned up + unreffed inside set_thread_pool(). Valgrind * will catch the leak if cleanup() is skipped. */ - gst_base_idle_src_set_thread_pool (GST_BASE_IDLE_SRC (src), replacement); + gst_base_idle_src_set_thread_pool (GST_BASE_IDLE_SRC (src), + gst_object_ref (replacement)); + + gst_task_pool_cleanup (replacement); + gst_object_unref (replacement); g_object_unref (src); } From fbb45ae5f068b3a3a54b25bc33498ba461916e63 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 15:41:34 +0900 Subject: [PATCH 51/62] libs/base/idlesrc: unref unknown obj --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index a82c0cdbb6c..57bde0e8b71 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1414,6 +1414,7 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) } } else { GST_ERROR_OBJECT (src, "Unknown object %" GST_PTR_FORMAT " type", obj); + gst_mini_object_unref (obj); } check_ret_error: if (ret != GST_FLOW_OK && ret != GST_FLOW_FLUSHING) { From 5aca70d3995dbb7e51f1759e1481b8dcd4782453 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 15:48:32 +0900 Subject: [PATCH 52/62] libs/base/idlesrc: fixup header alignment --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h index 007a8f01751..d16a83e155e 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.h @@ -189,10 +189,10 @@ GST_BASE_API GstBufferPool * gst_base_idle_src_get_buffer_pool (GstBaseIdleSrc *src); GST_BASE_API -void gst_base_idle_src_set_thread_pool (GstBaseIdleSrc *src, GstTaskPool * thread_pool); +void gst_base_idle_src_set_thread_pool (GstBaseIdleSrc *src, GstTaskPool *thread_pool); GST_BASE_API -GstTaskPool * gst_base_idle_src_get_thread_pool (GstBaseIdleSrc *src); +GstTaskPool * gst_base_idle_src_get_thread_pool (GstBaseIdleSrc *src); GST_BASE_API void gst_base_idle_src_get_allocator (GstBaseIdleSrc *src, From 09e0919852a9f0c9df02aafb9fe97285fe25dd97 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 15:49:47 +0900 Subject: [PATCH 53/62] libs/base/idlesrc: re-check running flag on start_task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The producer-side running check in submit_buffer*() is TOCTOU vs. stop(). Re-check after acquiring the pool ref, before pushing — joining any leftover prev_handle still happens unconditionally so we don't leak a worker --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 57bde0e8b71..d5543d65b13 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1479,12 +1479,28 @@ gst_base_idle_src_start_task (GstBaseIdleSrc * src, gboolean wait) return; } - /* Join the *previous* handle on the pool it was issued on. Because we - * cleared priv->thread_handle above, set_thread_pool() will not also try - * to join it from the other side. */ + /* Join the *previous* handle on the pool it was issued on, regardless of + * whether we go on to push a new one. Because we cleared + * priv->thread_handle above, set_thread_pool() will not also try to join + * it from the other side. */ if (prev_handle) gst_task_pool_join (pool, prev_handle); + /* Re-check running after the snapshot: a concurrent stop() may have + * cleared it after our caller's submit_*() check. If so, do not push a + * new task — the queue is being drained by stop() and any new task would + * race against teardown and potentially push on a deactivated pad. + * + * A residual race remains (running may flip to FALSE between this check + * and the push), but it is benign: stop() snapshots thread_handle under + * OBJECT_LOCK, so it will either see and join our newly installed handle + * below, or finalize will. */ + if (!g_atomic_int_get (&src->running)) { + GST_DEBUG_OBJECT (src, "Not running, skipping task push"); + gst_object_unref (pool); + return; + } + new_handle = gst_task_pool_push (pool, gst_base_idle_src_func, src, &error); if (G_UNLIKELY (error != NULL)) { GST_ERROR_OBJECT (src, "Failed to push task to pool: %s", error->message); @@ -1500,8 +1516,7 @@ gst_base_idle_src_start_task (GstBaseIdleSrc * src, gboolean wait) GST_OBJECT_LOCK (src); /* Only install our handle if no concurrent set_thread_pool() happened - * meanwhile — if it did, the pool we used is no longer the active one - * and the handle is meaningless against the new pool. */ + * meanwhile (defensive: set_thread_pool() rejects mid-flow swaps). */ if (priv->thread_pool == pool) { priv->thread_handle = new_handle; new_handle = NULL; From 486664ae0c8c843c9876b7b39b434878e6148ece Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 16:13:54 +0900 Subject: [PATCH 54/62] libs/base/idlesrc: gst-indent --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 4 +- .../gstreamer/tests/check/libs/baseidlesrc.c | 64 ++++++++++++------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index d5543d65b13..43c02e4e07d 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1266,9 +1266,9 @@ gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, old_pool = priv->thread_pool; old_handle = priv->thread_handle; old_owned = priv->owns_thread_pool; - priv->thread_pool = thread_pool; /* transfer full */ + priv->thread_pool = thread_pool; /* transfer full */ priv->thread_handle = NULL; - priv->owns_thread_pool = FALSE; /* externally provided, now */ + priv->owns_thread_pool = FALSE; /* externally provided, now */ GST_OBJECT_UNLOCK (src); /* Drain any pending work on the *previous* pool — the handle is only valid diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 382dd9d0929..6afcaccdda7 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -222,13 +222,14 @@ GST_START_TEST (baseidlesrc_thread_pool_set_and_get) gst_shared_task_pool_get_max_threads (GST_SHARED_TASK_POOL (thread_pool))); - gst_object_unref (thread_pool); /* drop the get_thread_pool() ref */ - g_object_unref (src); /* this releases the element's ref */ + gst_object_unref (thread_pool); /* drop the get_thread_pool() ref */ + g_object_unref (src); /* this releases the element's ref */ /* Now we own the last ref; we may safely cleanup + unref our local one. */ gst_task_pool_cleanup (new_thread_pool); gst_object_unref (new_thread_pool); } + GST_END_TEST; #define MAX_SRCS 16 @@ -331,13 +332,14 @@ GST_START_TEST (baseidlesrc_thread_pool_submit) gst_task_pool_cleanup (pool); gst_object_unref (pool); } + GST_END_TEST; - static void - _yield_task (G_GNUC_UNUSED void *user_data) - { - g_thread_yield (); - } +static void +_yield_task (G_GNUC_UNUSED void *user_data) +{ + g_thread_yield (); +} GST_START_TEST (baseidlesrc_default_thread_pool_is_prepared) { @@ -362,6 +364,7 @@ GST_START_TEST (baseidlesrc_default_thread_pool_is_prepared) gst_object_unref (pool); g_object_unref (src); } + GST_END_TEST; GST_START_TEST (baseidlesrc_replace_thread_pool_preserves_shared_pool) @@ -399,6 +402,7 @@ GST_START_TEST (baseidlesrc_replace_thread_pool_preserves_shared_pool) gst_task_pool_cleanup (shared); gst_object_unref (shared); } + GST_END_TEST; GST_START_TEST (baseidlesrc_finalize_one_keeps_shared_pool_alive) @@ -424,7 +428,7 @@ GST_START_TEST (baseidlesrc_finalize_one_keeps_shared_pool_alive) gst_harness_set_sink_caps_str (hb, "foo/bar"); gst_harness_play (hb); - g_object_unref (a); /* must not break b */ + g_object_unref (a); /* must not break b */ fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (b, &buf)); gst_base_idle_src_submit_buffer (GST_BASE_IDLE_SRC (b), buf); @@ -436,6 +440,7 @@ GST_START_TEST (baseidlesrc_finalize_one_keeps_shared_pool_alive) gst_task_pool_cleanup (pool); gst_object_unref (pool); } + GST_END_TEST; GST_START_TEST (baseidlesrc_submission_order_is_preserved) @@ -464,6 +469,7 @@ GST_START_TEST (baseidlesrc_submission_order_is_preserved) gst_harness_teardown (h); g_object_unref (src); } + GST_END_TEST; GST_START_TEST (baseidlesrc_do_timestamp_on_buffer_list) @@ -497,6 +503,7 @@ GST_START_TEST (baseidlesrc_do_timestamp_on_buffer_list) gst_harness_teardown (h); g_object_unref (src); } + GST_END_TEST; GST_START_TEST (baseidlesrc_set_format_rejects_invalid) @@ -513,6 +520,7 @@ GST_START_TEST (baseidlesrc_set_format_rejects_invalid) g_object_unref (src); } + GST_END_TEST; GST_START_TEST (baseidlesrc_submit_pull_loop_no_deadlock) @@ -539,6 +547,7 @@ GST_START_TEST (baseidlesrc_submit_pull_loop_no_deadlock) gst_harness_teardown (h); g_object_unref (src); } + GST_END_TEST; GST_START_TEST (baseidlesrc_submit_after_stop_is_safe) @@ -553,10 +562,11 @@ GST_START_TEST (baseidlesrc_submit_after_stop_is_safe) gst_harness_teardown (h); fail_unless_equals_int (GST_FLOW_OK, test_idle_src_alloc (src, &buf)); - gst_base_idle_src_submit_buffer (base_src, buf); /* must drop, not crash */ + gst_base_idle_src_submit_buffer (base_src, buf); /* must drop, not crash */ g_object_unref (src); } + GST_END_TEST; GST_START_TEST (baseidlesrc_default_pool_is_cleaned_up_on_swap) @@ -580,36 +590,46 @@ GST_START_TEST (baseidlesrc_default_pool_is_cleaned_up_on_swap) g_object_unref (src); } + GST_END_TEST; +typedef struct +{ + GstBaseIdleSrc *src; + gint stop; /* atomic */ +} StopRaceCtx; + +static gpointer +_stop_race_producer (gpointer data) +{ + StopRaceCtx *c = data; + while (!g_atomic_int_get (&c->stop)) { + GstBuffer *buf = gst_buffer_new_allocate (NULL, 64, NULL); + gst_base_idle_src_submit_buffer (c->src, buf); + } + return NULL; +} + GST_START_TEST (baseidlesrc_stop_races_with_submit) { TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); GstHarness *h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); + StopRaceCtx ctx = { base_src, 0 }; GThread *producer; - struct { GstBaseIdleSrc *src; gint stop; } ctx = { base_src, 0 }; - - gpointer producer_fn (gpointer data) { - typeof (ctx) *c = data; - while (!g_atomic_int_get (&c->stop)) { - GstBuffer *buf = gst_buffer_new_allocate (NULL, 64, NULL); - gst_base_idle_src_submit_buffer (c->src, buf); - } - return NULL; - } gst_harness_set_sink_caps_str (h, "foo/bar"); gst_harness_play (h); - producer = g_thread_new ("producer", producer_fn, &ctx); - g_usleep (50 * 1000); /* let it churn */ - gst_harness_teardown (h); /* triggers stop() */ + producer = g_thread_new ("producer", _stop_race_producer, &ctx); + g_usleep (50 * 1000); /* let it churn */ + gst_harness_teardown (h); /* triggers stop() */ g_atomic_int_set (&ctx.stop, 1); g_thread_join (producer); g_object_unref (src); } + GST_END_TEST; static Suite * From 49c56980c958ebe785e9077aee6402e0b78cb182 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 16:28:21 +0900 Subject: [PATCH 55/62] libs/base/idlesrc: protect against failures to prepare pool --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 43c02e4e07d..f8bbb4039fe 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1941,9 +1941,11 @@ gst_base_idle_src_init (GstBaseIdleSrc * src, gpointer g_class) GST_ERROR_OBJECT (src, "Failed to prepare default thread pool: %s", error->message); g_clear_error (&error); + gst_object_unref (thread_pool); + thread_pool = NULL; } src->priv->thread_pool = thread_pool; - src->priv->owns_thread_pool = TRUE; + src->priv->owns_thread_pool = (thread_pool != NULL); } GST_DEBUG_OBJECT (src, "init done"); From 52736093f42ae04d9af454a1664d0640755934c4 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 16:29:08 +0900 Subject: [PATCH 56/62] libs/base/indlesrc: fix type on docstring --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index f8bbb4039fe..011434fa6e4 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -92,7 +92,7 @@ * #GstBaseIdleSrc uses an internal #GstTaskPool to schedule buffer submission * and auxiliary tasks. This mechanism helps to avoid the overhead of creating * and destroying threads too frequently when data is submitted in rapid - * successions. + * succession. * * By default, the internal pool is configured with a maximum of one thread, * which provides a simple but efficient way to handle continuous or bursty From 54bad74a6d5d9980c04e56e6195ba3f5b1e59882 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 16:30:34 +0900 Subject: [PATCH 57/62] libs/base/idlesrc: make sure to cleanup running state --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 5 ++ .../gstreamer/tests/check/libs/baseidlesrc.c | 67 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 011434fa6e4..48076ae0988 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1597,6 +1597,11 @@ gst_base_idle_src_start (GstBaseIdleSrc * src) could_not_start: { + /* Subclass refused to start — roll back the running flag so any + * in-flight submit_buffer*() (and any future ones until the next start) + * bails out, rather than queueing buffers into a source whose + * activation failed. */ + g_atomic_int_set (&src->running, FALSE); GST_DEBUG_OBJECT (src, "could not start"); /* subclass is supposed to post a message but we post one as a fallback * just in case. We don't have to call _stop. */ diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index 6afcaccdda7..af001de13b9 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -632,6 +632,72 @@ GST_START_TEST (baseidlesrc_stop_races_with_submit) GST_END_TEST; +/* A subclass whose start() vfunc deliberately fails. Used to verify that + * the base class clears `running` when subclass activation fails, so that + * subsequent submit_buffer*() calls correctly drop buffers rather than + * queueing them into a source that never started. + * + * Regression test for: + * https://github.com/pexip/gstreamer/pull/45#discussion_r3361051394 + */ +typedef GstBaseIdleSrc FailingStartIdleSrc; +typedef GstBaseIdleSrcClass FailingStartIdleSrcClass; + +static GType failing_start_idle_src_get_type (void); +G_DEFINE_TYPE (FailingStartIdleSrc, failing_start_idle_src, + GST_TYPE_BASE_IDLE_SRC); + +static gboolean +failing_start_idle_src_start (G_GNUC_UNUSED GstBaseIdleSrc * src) +{ + return FALSE; +} + +static void +failing_start_idle_src_init (G_GNUC_UNUSED FailingStartIdleSrc * src) +{ +} + +static void +failing_start_idle_src_class_init (FailingStartIdleSrcClass * klass) +{ + GstBaseIdleSrcClass *bclass = GST_BASE_IDLE_SRC_CLASS (klass); + + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), + &src_template); + bclass->start = failing_start_idle_src_start; +} + +GST_START_TEST (baseidlesrc_failed_start_clears_running) +{ + GstBaseIdleSrc *src = g_object_new (failing_start_idle_src_get_type (), NULL); + GstBuffer *buf; + + /* Activating the pad invokes start(), which our subclass forces to + * return FALSE. The base class must roll back `running` so the element + * is not left in a half-started state. */ + fail_if (gst_pad_set_active (GST_BASE_IDLE_SRC_PAD (src), TRUE), + "pad activation must fail when subclass start() returns FALSE"); + + /* Invariant: running was rolled back. */ + fail_unless (!g_atomic_int_get (&src->running), + "running must be cleared after start() failure"); + + /* Behaviour: submit_buffer drops the (transfer-full) ref rather than + * queueing. We take an extra ref so we can observe what happened: + * - dropped (running == FALSE, correct): refcount goes from 2 to 1. + * - queued (running == TRUE, wrong): refcount stays at 2. */ + buf = gst_buffer_new_allocate (NULL, 64, NULL); + gst_buffer_ref (buf); + gst_base_idle_src_submit_buffer (src, buf); + fail_unless_equals_int (1, GST_MINI_OBJECT_REFCOUNT_VALUE (buf)); + gst_buffer_unref (buf); + + g_object_unref (src); +} + +GST_END_TEST; + static Suite * baseidlesrc_suite (void) { @@ -655,6 +721,7 @@ baseidlesrc_suite (void) tcase_add_test (tc, baseidlesrc_submit_after_stop_is_safe); tcase_add_test (tc, baseidlesrc_default_pool_is_cleaned_up_on_swap); tcase_add_test (tc, baseidlesrc_stop_races_with_submit); + tcase_add_test (tc, baseidlesrc_failed_start_clears_running); return s; } From 76fabc51263dcb2d593b62b3a11c3d047b47569d Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 16:32:16 +0900 Subject: [PATCH 58/62] libs/base/idlesrc: init pending segment within lock before setting running state --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 48076ae0988..7bbc8a66bd0 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1572,14 +1572,20 @@ gst_base_idle_src_start (GstBaseIdleSrc * src) GST_DEBUG_OBJECT (src, "Starting"); + /* Initialize segment + pending state under OBJECT_LOCK *before* publishing + * running=TRUE. A producer thread that observes running=TRUE and enters + * submit_buffer*() must see a consistent segment_pending/segment_seqnum, + * not a torn or stale snapshot. */ GST_OBJECT_LOCK (src); - gst_segment_init (&src->segment, src->segment.format); + src->priv->segment_pending = TRUE; + src->priv->segment_seqnum = gst_util_seqnum_next (); GST_OBJECT_UNLOCK (src); + /* Publish running=TRUE with release semantics — producers' atomic load + * acts as the matching acquire and is guaranteed to observe the segment + * state written above. */ g_atomic_int_set (&src->running, TRUE); - src->priv->segment_pending = TRUE; - src->priv->segment_seqnum = gst_util_seqnum_next (); bclass = GST_BASE_IDLE_SRC_GET_CLASS (src); if (bclass->start) From a4968b2145c9fd504fb3e524e350018068057a7a Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 17:14:43 +0900 Subject: [PATCH 59/62] libs/base/idlesrc: re-work queue drain on error and normal cases --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 164 +++++++++++------- 1 file changed, 105 insertions(+), 59 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index 7bbc8a66bd0..a6450475f01 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -364,11 +364,18 @@ gst_base_idle_src_get_do_timestamp (GstBaseIdleSrc * src) } static void -gst_base_idle_src_queue_object (GstBaseIdleSrc * src, GstMiniObject * obj) +gst_base_idle_src_queue_object_unlocked (GstBaseIdleSrc * src, + GstMiniObject * obj) { GST_LOG_OBJECT (src, "Queuing: %" GST_PTR_FORMAT, obj); - GST_OBJECT_LOCK (src); g_queue_push_tail (src->priv->obj_queue, obj); +} + +static void +gst_base_idle_src_queue_object (GstBaseIdleSrc * src, GstMiniObject * obj) +{ + GST_OBJECT_LOCK (src); + gst_base_idle_src_queue_object_unlocked (src, obj); GST_OBJECT_UNLOCK (src); } @@ -1246,16 +1253,31 @@ gst_base_idle_src_set_thread_pool (GstBaseIdleSrc * src, { GstBaseIdleSrcPrivate *priv; GstTaskPool *old_pool = NULL; + GstState state = GST_STATE_VOID_PENDING; gpointer old_handle = NULL; gboolean old_owned = FALSE; g_return_if_fail (GST_IS_BASE_IDLE_SRC (src)); g_return_if_fail (GST_IS_TASK_POOL (thread_pool)); - if (g_atomic_int_get (&src->running)) { + /* Thread pool may only be swapped while the + * element is in NULL or READY. + * + * A `running`-only check is insufficient: during teardown stop() clears + * `running` while the element is still in PAUSED and may still be + * joining/draining inside drain_and_join(). A concurrent set_thread_pool() + * in that window would swap pools while the worker still references + * the old one. */ + state = GST_STATE (src); + if (state > GST_STATE_READY || GST_STATE_PENDING (src) > GST_STATE_READY + || g_atomic_int_get (&src->running)) { GST_WARNING_OBJECT (src, - "Refusing to swap thread pool while the element is running; " - "call set_thread_pool() before the element transitions to PAUSED."); + "Refusing to swap thread pool: element must be in NULL or READY " + "(state=%s, pending=%s, running=%d). Call set_thread_pool() " + "before transitioning to PAUSED.", + gst_element_state_get_name (state), + gst_element_state_get_name (GST_STATE_PENDING (src)), + g_atomic_int_get (&src->running)); gst_object_unref (thread_pool); return; } @@ -1533,18 +1555,27 @@ gst_base_idle_src_start_task (GstBaseIdleSrc * src, gboolean wait) static void gst_base_idle_src_check_pending_segment (GstBaseIdleSrc * src) { - GST_DEBUG_OBJECT (src, "Checking pending segment"); - /* push events to close/start our segment before we push the buffer. */ + /* Build the SEGMENT event under OBJECT_LOCK so the check-and-clear of + * segment_pending, the seqnum update, and the queue insert all happen + * atomically. Without this, two concurrent producers can both observe + * segment_pending=TRUE, both enqueue a SEGMENT event, and both increment + * segment_seqnum — yielding duplicate segments and a torn seqnum. + * + * gst_event_new_segment / gst_event_set_seqnum / gst_util_seqnum_next + * do not call back into the element, so it is safe to hold the lock + * across them. */ + GST_OBJECT_LOCK (src); if (G_UNLIKELY (src->priv->segment_pending)) { GstEvent *seg_event = gst_event_new_segment (&src->segment); gst_event_set_seqnum (seg_event, src->priv->segment_seqnum); src->priv->segment_seqnum = gst_util_seqnum_next (); - gst_base_idle_src_queue_object (src, (GstMiniObject *) seg_event); - GST_DEBUG_OBJECT (src, "Queing segment event %" GST_PTR_FORMAT, seg_event); - src->priv->segment_pending = FALSE; + + GST_DEBUG_OBJECT (src, "Queuing segment event %" GST_PTR_FORMAT, seg_event); + gst_base_idle_src_queue_object_unlocked (src, (GstMiniObject *) seg_event); } + GST_OBJECT_UNLOCK (src); } static void @@ -1564,6 +1595,54 @@ gst_base_idle_src_add_stream_start (GstBaseIdleSrc * src) g_free (stream_id); } +/* Atomically detach any installed task handle and steal the queued objects, + * then join the worker outside the lock and unref the drained miniobjects. + * + * Shared between gst_base_idle_src_stop() and the failure path in + * gst_base_idle_src_start(): both must tear down any background activity + * that a concurrent producer may have started during the brief running=TRUE + * window, without leaking the buffers it queued. + * + * Callers must have already published running=FALSE atomically so no *new* + * producers can pile in after this function returns. */ +static void +gst_base_idle_src_drain_and_join (GstBaseIdleSrc * src) +{ + GstBaseIdleSrcPrivate *priv = src->priv; + GstTaskPool *pool = NULL; + gpointer handle = NULL; + GQueue drained = G_QUEUE_INIT; + GstMiniObject *obj; + + /* Snapshot pool + handle and steal the queue under the lock. */ + GST_OBJECT_LOCK (src); + if (priv->thread_pool) + pool = gst_object_ref (priv->thread_pool); + handle = priv->thread_handle; + priv->thread_handle = NULL; + /* O(1) steal — see commentary at the original use site in stop(). */ + drained = *priv->obj_queue; + g_queue_init (priv->obj_queue); + GST_OBJECT_UNLOCK (src); + + /* Join outside the lock (worker takes the same lock itself). */ + if (handle && pool) + gst_task_pool_join (pool, handle); + g_clear_object (&pool); + + while ((obj = g_queue_pop_head (&drained))) + gst_mini_object_unref (obj); + + /* Second pass: catch anything the worker pushed back via queue_object() + * during the join, or that a producer raced past the !running check. */ + GST_OBJECT_LOCK (src); + drained = *priv->obj_queue; + g_queue_init (priv->obj_queue); + GST_OBJECT_UNLOCK (src); + while ((obj = g_queue_pop_head (&drained))) + gst_mini_object_unref (obj); +} + static gboolean gst_base_idle_src_start (GstBaseIdleSrc * src) { @@ -1603,11 +1682,20 @@ gst_base_idle_src_start (GstBaseIdleSrc * src) could_not_start: { - /* Subclass refused to start — roll back the running flag so any - * in-flight submit_buffer*() (and any future ones until the next start) - * bails out, rather than queueing buffers into a source whose - * activation failed. */ + /* Subclass refused to start. Roll back the running flag and tear down + * any work a producer may have queued (and any task it may have pushed) + * during the brief running=TRUE window before the subclass returned. + * Without this, late producers would leak miniobjects into priv->obj_queue + * and a subsequent successful start() would push those stale objects + * downstream as if they had just been submitted. */ g_atomic_int_set (&src->running, FALSE); + + GST_OBJECT_LOCK (src); + src->priv->segment_pending = FALSE; + GST_OBJECT_UNLOCK (src); + + gst_base_idle_src_drain_and_join (src); + GST_DEBUG_OBJECT (src, "could not start"); /* subclass is supposed to post a message but we post one as a fallback * just in case. We don't have to call _stop. */ @@ -1620,57 +1708,15 @@ static gboolean gst_base_idle_src_stop (GstBaseIdleSrc * src) { GstBaseIdleSrcClass *bclass; - GstBaseIdleSrcPrivate *priv = src->priv; - GstTaskPool *pool = NULL; - gpointer handle = NULL; - GQueue drained = G_QUEUE_INIT; - GstMiniObject *obj; gboolean result = TRUE; GST_DEBUG_OBJECT (src, "stopping source"); - /* 1. Publish !running atomically so any producer that has not yet entered - * queue_object() bails out in submit_buffer*(). */ + /* Publish !running atomically so any producer that has not yet entered + * queue_object() bails out in submit_buffer*(). */ g_atomic_int_set (&src->running, FALSE); - /* 2. Snapshot pool + handle under the lock, clear them, and steal the - * queue contents in one shot so we can release the lock before joining - * (the worker takes the same lock in process_object_queue()). */ - GST_OBJECT_LOCK (src); - if (priv->thread_pool) - pool = gst_object_ref (priv->thread_pool); - handle = priv->thread_handle; - priv->thread_handle = NULL; - - /* Steal the queue in O(1): GQueue is a plain { head, tail, length } struct, - * so copying it out and re-initializing the source detaches the whole list - * without walking it or freeing/reallocating any GList nodes (which a - * pop_head + push_tail loop would do via the GList slice allocator). Any - * further queue_object() calls (which take OBJECT_LOCK) are either ordered - * before us — and we'll drain them here — or land in the now-empty queue - * and get caught by the second-pass drain below. */ - drained = *priv->obj_queue; - g_queue_init (priv->obj_queue); - GST_OBJECT_UNLOCK (src); - - /* 3. Join outside the lock. */ - if (handle && pool) - gst_task_pool_join (pool, handle); - g_clear_object (&pool); - - /* 4. Drop drained objects (no lock needed; they're local now). */ - while ((obj = g_queue_pop_head (&drained))) - gst_mini_object_unref (obj); - - /* 5. Any objects that the worker pushed back via queue_object during - * join (or that a producer raced past the !running check)? Drain them - * too — same O(1) steal pattern. */ - GST_OBJECT_LOCK (src); - drained = *priv->obj_queue; - g_queue_init (priv->obj_queue); - GST_OBJECT_UNLOCK (src); - while ((obj = g_queue_pop_head (&drained))) - gst_mini_object_unref (obj); + gst_base_idle_src_drain_and_join (src); bclass = GST_BASE_IDLE_SRC_GET_CLASS (src); if (bclass->stop) From 02c22cd564dd2cb233617833fd303ecf58c84967 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 17:17:37 +0900 Subject: [PATCH 60/62] libs/base/idlesrc/test: extend coverage for edge-cases --- .../gstreamer/tests/check/libs/baseidlesrc.c | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c index af001de13b9..d4bebe5d0d7 100644 --- a/subprojects/gstreamer/tests/check/libs/baseidlesrc.c +++ b/subprojects/gstreamer/tests/check/libs/baseidlesrc.c @@ -696,6 +696,136 @@ GST_START_TEST (baseidlesrc_failed_start_clears_running) g_object_unref (src); } +GST_END_TEST; + + /* The documented contract is that set_thread_pool() must be called before + * the element transitions to PAUSED. Verify that a swap attempted while + * the element is playing is rejected and that the original pool is + * preserved (i.e. the rejected one was not silently adopted). */ +GST_START_TEST (baseidlesrc_set_thread_pool_rejected_when_playing) +{ + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GstHarness *h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); + GstTaskPool *original; + GstTaskPool *rejected; + GstTaskPool *current; + GError *err = NULL; + + /* Capture the default pool the element installed at init. */ + original = gst_base_idle_src_get_thread_pool (base_src); + fail_unless (original != NULL); + + gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_play (h); /* element now in PAUSED (or beyond) */ + + rejected = gst_shared_task_pool_new (); + gst_task_pool_prepare (rejected, &err); + fail_unless (err == NULL); + + /* Should be refused — the contract requires NULL/READY. */ + gst_base_idle_src_set_thread_pool (base_src, gst_object_ref (rejected)); + + /* Pool must be unchanged. */ + current = gst_base_idle_src_get_thread_pool (base_src); + fail_unless (current == original, "set_thread_pool() must not adopt the " + "new pool while element is past READY"); + gst_object_unref (current); + + gst_harness_teardown (h); + g_object_unref (src); + + /* We still own our refs to both pools. */ + gst_object_unref (original); + gst_task_pool_cleanup (rejected); + gst_object_unref (rejected); +} + +GST_END_TEST; + +typedef struct +{ + GstBaseIdleSrc *src; + GAsyncQueue *start_gate; +} SegmentRaceCtx; + +static gpointer +_segment_race_producer (gpointer data) +{ + SegmentRaceCtx *c = data; + GstBuffer *buf; + + /* Block until the test thread releases the gate to maximize concurrency. */ + g_async_queue_pop (c->start_gate); + + buf = gst_buffer_new_allocate (NULL, 64, NULL); + gst_base_idle_src_submit_buffer (c->src, buf); + return NULL; +} + + /* Concurrent producers must observe a single SEGMENT event downstream, + * not one per producer that won the segment_pending check-and-clear race. + * Pre-fix, the unlocked check_pending_segment() could enqueue N duplicate + * SEGMENT events and tear the segment_seqnum. + * + * The test is necessarily probabilistic: with the bug it triggers most of + * the time on multi-core hardware (and always under TSan); with the fix it + * always passes. */ +GST_START_TEST (baseidlesrc_concurrent_submit_emits_one_segment) +{ +#define N_PRODUCERS 16 + TestIdleSrc *src = g_object_new (test_idle_src_get_type (), NULL); + GstBaseIdleSrc *base_src = GST_BASE_IDLE_SRC (src); + GstHarness *h = gst_harness_new_with_element (GST_ELEMENT (src), NULL, "src"); + GAsyncQueue *gate; + GThread *threads[N_PRODUCERS]; + SegmentRaceCtx ctx; + GstEvent *event; + guint i, segment_count = 0; + + gst_harness_set_sink_caps_str (h, "foo/bar"); + gst_harness_play (h); + + gate = g_async_queue_new (); + ctx.src = base_src; + ctx.start_gate = gate; + + for (i = 0; i < N_PRODUCERS; i++) { + gchar *name = g_strdup_printf ("producer-%u", i); + threads[i] = g_thread_new (name, _segment_race_producer, &ctx); + g_free (name); + } + + /* Release all producers at once. */ + for (i = 0; i < N_PRODUCERS; i++) + g_async_queue_push (gate, GINT_TO_POINTER (1)); + + for (i = 0; i < N_PRODUCERS; i++) + g_thread_join (threads[i]); + + /* Drain all buffers — this also flushes any pending events ahead of them + * through the worker, so by the time the loop exits every event the + * element ever queued has been delivered to the harness. */ + for (i = 0; i < N_PRODUCERS; i++) { + GstBuffer *buf = gst_harness_pull (h); + fail_unless (buf != NULL); + gst_buffer_unref (buf); + } + + /* Now count SEGMENT events. Must be exactly one. */ + while ((event = gst_harness_try_pull_event (h)) != NULL) { + if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) + segment_count++; + gst_event_unref (event); + } + fail_unless_equals_int (1, segment_count); + + g_async_queue_unref (gate); + gst_harness_teardown (h); + g_object_unref (src); +#undef N_PRODUCERS +} + GST_END_TEST; static Suite * @@ -722,6 +852,8 @@ baseidlesrc_suite (void) tcase_add_test (tc, baseidlesrc_default_pool_is_cleaned_up_on_swap); tcase_add_test (tc, baseidlesrc_stop_races_with_submit); tcase_add_test (tc, baseidlesrc_failed_start_clears_running); + tcase_add_test (tc, baseidlesrc_set_thread_pool_rejected_when_playing); + tcase_add_test (tc, baseidlesrc_concurrent_submit_emits_one_segment); return s; } From 2c92248d9368f07334ba3dacffa209e276fd2e50 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 22:54:46 +0900 Subject: [PATCH 61/62] libs/base/idlesrc: reduce races on finalize and cleanup work finalize() drains priv->obj_queue without taking GST_OBJECT_LOCK(), but the worker/producers mutate the same queue under that lock (e.g. via gst_base_idle_src_queue_object()). If the element is finalized while a task is still running, this can race and corrupt the queue / crash. Join/drain via gst_base_idle_src_drain_and_join() before freeing the queue. After this small refactor: * We drop the old "If we still hold a handle, drop it on the originating pool" block because drain_and_join() already handles that under the lock. * The g_assert makes the invariant explicit. * The pop-loop now runs after drain_and_join() and is purely defensive. * g_atomic_int_set (&src->running, FALSE) before drain_and_join() ensures any racing producer's submit_buffer*() will bail in its initial running check rather than queueing into a soon-to-be-freed queue. --- .../gstreamer/libs/gst/base/gstbaseidlesrc.c | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index a6450475f01..ff6d5e0975b 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1888,17 +1888,30 @@ gst_base_idle_src_finalize (GObject * object) GstBaseIdleSrcPrivate *priv = src->priv; GstMiniObject *obj; - /* Drain anything that might still be queued. */ + /* Defensively tear down any work still in flight. In a well-behaved + * pipeline stop() will already have done this, but finalize is the + * unconditional fallback: nothing prevents a subclass from dropping + * the last ref while `running` is still TRUE (e.g. if the element + * was never activated, or if a wrapper holds extra refs past stop()) */ + g_atomic_int_set (&src->running, FALSE); + gst_base_idle_src_drain_and_join (src); + + /* The queue is now guaranteed empty (drain_and_join() emptied it in + * its second pass under the lock, and no producer can have added to + * it since: the last ref to `src` is being dropped here, so by + * definition no other thread holds a pointer to call submit_*() with). + * Free the GQueue itself. + * + * Note: we still pop-and-unref in a loop as a belt-and-braces guard + * in case a subclass override somehow re-queued something synchronously + * from its own dispose chain. */ while ((obj = g_queue_pop_head (priv->obj_queue))) gst_mini_object_unref (obj); g_queue_free (priv->obj_queue); priv->obj_queue = NULL; - /* If we still hold a handle, drop it on the originating pool. */ - if (priv->thread_handle && priv->thread_pool) { - gst_task_pool_join (priv->thread_pool, priv->thread_handle); - priv->thread_handle = NULL; - } + /* drain_and_join() already cleared priv->thread_handle. */ + g_assert (priv->thread_handle == NULL); /* Only cleanup the pool if WE created it. An externally-provided pool * may be shared with other elements; its owner is responsible for From 5dd2f60dadcb0050aab2d590aab31f219fbc7125 Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Fri, 5 Jun 2026 23:18:16 +0900 Subject: [PATCH 62/62] libs/base/idlesrc: keep an event ref a bit longer until we log a possible warning --- subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c index ff6d5e0975b..7a982945334 100644 --- a/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c +++ b/subprojects/gstreamer/libs/gst/base/gstbaseidlesrc.c @@ -1425,7 +1425,15 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) goto check_ret_error; } else if (GST_IS_EVENT (obj)) { GstEvent *event = GST_EVENT_CAST (obj); + GST_DEBUG_OBJECT (src, "About to push Event %" GST_PTR_FORMAT, event); + /* Hold a ref across the push so the event remains valid for the + * failure log below: gst_pad_push_event() consumes our queue's ref + * unconditionally (success and failure). The extra ref-pair is a + * cold-path price — pushing an event is already expensive and the + * warning only fires on failure — and it lets us log the full + * GST_PTR_FORMAT */ + gst_event_ref (event); if (!gst_pad_push_event (pad, event)) { /* Mirror GstBaseSrc behaviour for non-flow events: log and continue. * We deliberately do NOT post a message on the bus here — event push @@ -1434,6 +1442,7 @@ gst_base_idle_src_process_object (GstBaseIdleSrc * src, GstMiniObject * obj) * GST_ELEMENT_FLOW_ERROR(). */ GST_WARNING_OBJECT (src, "Failed to push event %" GST_PTR_FORMAT, event); } + gst_event_unref (event); } else { GST_ERROR_OBJECT (src, "Unknown object %" GST_PTR_FORMAT " type", obj); gst_mini_object_unref (obj);