Skip to content

GstEnginePipeline: Add fader mutex#2129

Open
jonaski wants to merge 1 commit into
masterfrom
fader_mutex
Open

GstEnginePipeline: Add fader mutex#2129
jonaski wants to merge 1 commit into
masterfrom
fader_mutex

Conversation

@jonaski

@jonaski jonaski commented Jun 1, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability of fade transitions by safely snapshotting timeline state when playback state changes asynchronously.
    • Prevented stale timeline callbacks from affecting the currently active fade, avoiding incorrect resume/stop behavior after quick open/switch actions.
    • Enhanced fade volume consistency on disconnect, timeline completion, and timeout by applying the correct snapshot-based fade direction and timing, even during teardown.

Copilot AI review requested due to automatic review settings June 1, 2026 20:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds explicit synchronization around the GstEnginePipeline fader (QTimeLine) pointer to avoid concurrent access/destruction issues when the pipeline is manipulated across threads.

Changes:

  • Introduces mutex_fader_ to guard reads/writes of the fader_ shared pointer.
  • Refactors multiple call sites to copy fader_ under lock and operate on the local copy outside the lock.
  • Adds an explanatory comment about queued SetStateAsyncSlot invocation safety on object destruction.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/engine/gstenginepipeline.h Adds mutex_fader_ to synchronize access to the fader timeline pointer.
src/engine/gstenginepipeline.cpp Uses mutex_fader_ to guard fader_ access across disconnect/init/state-change/fader lifecycle paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/engine/gstenginepipeline.cpp
Comment thread src/engine/gstenginepipeline.cpp
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change synchronizes GstEnginePipeline fader access with mutex-protected snapshots across timeline creation, teardown, state handling, resumption, and callbacks.

Changes

Fader thread-safety refactor

Layer / File(s) Summary
Fader state declaration and lifecycle
src/engine/gstenginepipeline.h, src/engine/gstenginepipeline.cpp
Adds the fader mutex and coordinates timeline creation, publication, stopping, and conditional clearing.
Protected fader usage and callbacks
src/engine/gstenginepipeline.cpp
Uses snapshots for initialization, state resumption, asynchronous resume, stale-callback filtering, completion, and timeout handling. SetStateAsync() documents queued-call cleanup during pipeline destruction.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding mutex protection around GstEnginePipeline fader state.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fader_mutex

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/engine/gstenginepipeline.cpp (1)

2460-2465: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Custom deleter calls stop() directly — potential cross-thread control.

The deleter runs on whatever thread drops the last SharedPtr reference. Calling timeline->state()/timeline->stop() there can touch the QTimeLine off its owning thread. Since deleteLater() already defers destruction to the owning thread and ~QTimeLine stops the timeline itself, the explicit stop() is redundant and is the only cross-thread-unsafe part; consider dropping it and relying on deleteLater() alone.

♻️ Proposed simplification
   SharedPtr<QTimeLine> fader(new QTimeLine(static_cast<int>(duration_msec)), [](QTimeLine *timeline) {
-    if (timeline->state() != QTimeLine::State::NotRunning) {
-      timeline->stop();
-    }
     timeline->deleteLater();
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/engine/gstenginepipeline.cpp` around lines 2460 - 2465, The custom
deleter for QTimeLine in the fader setup is doing thread-unsafe control by
calling state() and stop() from whichever thread releases the last SharedPtr.
Update the deleter in the QTimeLine creation logic to remove the explicit stop()
check entirely and rely on deleteLater() for cleanup, since QTimeLine will stop
during destruction on its owning thread. Keep the fix localized to the fader
SharedPtr/QTimeLine deleter so the timeline is only deferred for deletion
without direct cross-thread manipulation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/engine/gstenginepipeline.cpp`:
- Around line 2460-2465: The custom deleter for QTimeLine in the fader setup is
doing thread-unsafe control by calling state() and stop() from whichever thread
releases the last SharedPtr. Update the deleter in the QTimeLine creation logic
to remove the explicit stop() check entirely and rely on deleteLater() for
cleanup, since QTimeLine will stop during destruction on its owning thread. Keep
the fix localized to the fader SharedPtr/QTimeLine deleter so the timeline is
only deferred for deletion without direct cross-thread manipulation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 184dfcca-695d-4c55-adcb-9229bd8d22cf

📥 Commits

Reviewing files that changed from the base of the PR and between 41c311f and b0b74d0.

📒 Files selected for processing (2)
  • src/engine/gstenginepipeline.cpp
  • src/engine/gstenginepipeline.h

@jonaski
jonaski force-pushed the fader_mutex branch 3 times, most recently from 36a88c2 to c98fce1 Compare July 15, 2026 23:46
@jonaski
jonaski requested a review from Copilot July 15, 2026 23:49
@jonaski

jonaski commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread src/engine/gstenginepipeline.cpp Outdated
Comment thread src/engine/gstenginepipeline.cpp Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/engine/gstenginepipeline.cpp:2589

  • FaderTimelineStateChanged() drops mutex_fader_ before updating fader_running_. Another thread can replace fader_ in the gap, allowing a stale timeline signal to update fader_running_ for the new fade. Keep the update inside the same critical section used to validate sender().
  {
    QMutexLocker l(&mutex_fader_);
    // ResumeFaderAsync() keeps a superseded QTimeLine alive with a SharedPtr until its queued resume() runs, so it can still emit stateChanged after StartFader() replaced fader_ with a new one; ignore it in that case.
    if (!fader_ || sender() != &*fader_) {
      return;
    }
  }

  qLog(Debug) << "Pipeline" << id() << "fader state changed to" << (state == QTimeLine::State::Running ? "running" : state == QTimeLine::State::Paused ? "paused" : "not running");

  fader_running_ = state == QTimeLine::State::Running;

Comment thread src/engine/gstenginepipeline.cpp
Comment thread src/engine/gstenginepipeline.cpp
Comment thread src/engine/gstenginepipeline.cpp

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment on lines +2617 to +2639
SharedPtr<QTimeLine> fader;
{
QMutexLocker l(&mutex_fader_);
fader = fader_;
}
if (volume_fading_ && fader) {
qLog(Debug) << "Pipeline" << id() << "setting volume" << (fader->direction() == QTimeLine::Direction::Forward ? 1.0 : 0.0);
g_object_set(G_OBJECT(volume_fading_), "volume", fader->direction() == QTimeLine::Direction::Forward ? 1.0 : 0.0, nullptr);
}

{
QMutexLocker l(&mutex_fader_);
// Only clear fader_ if it still refers to the fader captured above: StartFader() may have already replaced it with a newer one in the meantime.
if (fader_ == fader) {
fader_.reset();
}
}

FaderTimelineFinished();
qLog(Debug) << "Pipeline" << id() << "finished fading";

ClearFaderState();

}
Comment on lines +427 to +429
// Stop these unconditionally: a fader timeout may already be pending in the event queue even if there's no live fader_ above (e.g. it fired concurrently and is about to run FaderTimelineTimeout()/ClearFaderState()), which would otherwise restart the fudge timer and emit FaderFinished for a pipeline that is being torn down.
timer_fader_timeout_->stop();
timer_fader_fudge_->stop();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants