GstEnginePipeline: Fix possible race condition in pipeline destructor

Wait for ongoing state changes to complete before setting pipeline to NULL.
This prevents race conditions with async state transitions that can cause crashes in GStreamer elements.

Fixes #1875
This commit is contained in:
Jonas Kvinge
2025-12-20 01:28:53 +01:00
parent 527ccd212a
commit b9b70399d8
2 changed files with 34 additions and 0 deletions

View File

@@ -26,6 +26,7 @@
#include <cstdint>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <glib.h>
#include <glib-object.h>
@@ -215,6 +216,23 @@ GstEnginePipeline::~GstEnginePipeline() {
if (pipeline_) {
// Wait for any ongoing state changes for this pipeline to complete before setting to NULL.
// This prevents race conditions with async state transitions.
{
// Copy futures to local list to avoid holding mutex during waitForFinished()
QList<QFuture<GstStateChangeReturn>> futures_to_wait;
{
QMutexLocker locker(&mutex_pending_state_changes_);
futures_to_wait = pending_state_changes_;
pending_state_changes_.clear();
}
// Wait for all pending futures to complete
for (QFuture<GstStateChangeReturn> &future : futures_to_wait) {
future.waitForFinished();
}
}
gst_element_set_state(pipeline_, GST_STATE_NULL);
GstElement *audiobin = nullptr;
@@ -1869,6 +1887,12 @@ QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state)
QFuture<GstStateChangeReturn> future = QtConcurrent::run(shared_state_threadpool(), &gst_element_set_state, pipeline_, state);
watcher->setFuture(future);
// Track this future so destructor can wait for it
{
QMutexLocker locker(&mutex_pending_state_changes_);
pending_state_changes_.append(future);
}
return future;
}
@@ -1878,6 +1902,12 @@ void GstEnginePipeline::SetStateFinishedSlot(const GstState state, const GstStat
last_set_state_in_progress_ = GST_STATE_VOID_PENDING;
--set_state_in_progress_;
// Remove finished futures from tracking list to prevent unbounded growth
{
QMutexLocker locker(&mutex_pending_state_changes_);
pending_state_changes_.erase(std::remove_if(pending_state_changes_.begin(), pending_state_changes_.end(), [](const QFuture<GstStateChangeReturn> &f) { return f.isFinished(); }), pending_state_changes_.end());
}
switch (state_change_return) {
case GST_STATE_CHANGE_SUCCESS:
case GST_STATE_CHANGE_ASYNC: