Skip to content

Fixed a recursive call to the async::processMessages#32853

Open
igorkorsukov wants to merge 1 commit intomusescore:masterfrom
igorkorsukov:w/ioc/step_37
Open

Fixed a recursive call to the async::processMessages#32853
igorkorsukov wants to merge 1 commit intomusescore:masterfrom
igorkorsukov:w/ioc/step_37

Conversation

@igorkorsukov
Copy link
Copy Markdown
Contributor

@igorkorsukov igorkorsukov commented Mar 30, 2026

Resolves: #32819

Summary by CodeRabbit

  • Bug Fixes
    • Reduced noisy debug logging in the audio engine unless debug is enabled.
    • Added safety checks to async message handling to avoid invoking null callbacks.
    • Introduced a guard to task processing to prevent re-entrancy during message dispatch.
    • Switched export scheduling to the next event-loop iteration for more reliable deferred exports.
    • Simplified audio export generation to rely on channel processing for cancellation and responsiveness.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

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

Added a FluidSynth compile-time debug flag; tightened async callable construction/invocation; added a re-entrancy guard to RpcPort::process; switched deferred export scheduling to QTimer::singleShot; removed per-chunk async message pumping in soundtrack generation.

Changes

Cohort / File(s) Summary
FluidSynth logging
src/framework/audio/engine/internal/synthesizers/fluidsynth/fluidsynth.cpp
Added static constexpr bool FLUID_DEBUG = false and guarded FLUID_DBG case so debug logs emit only when the flag is true.
Async callable safety
src/framework/global/thirdparty/kors_async/async/async.h
Func constructor no longer accepts a default null callback (asserts non-null) and call() only invokes func() if non-null.
RPC re-entrancy guard
src/framework/global/thirdparty/kors_async/async/internal/rpcqueue.h
Added private bool m_isProcessing = false and an RAII guard at start of RpcPort::process() to prevent re-entrancy; returns immediately if already processing.
Export scheduling & soundtrack generation
src/project/qml/MuseScore/Project/internal/Export/exportdialogmodel.cpp, src/framework/audio/engine/internal/export/soundtrackwriter.cpp
Replaced async::Async::call(nullptr, ...) with QTimer::singleShot(0, ...) for deferred export scheduling (added <QTimer>); removed per-chunk async::processMessages(thisThId) and rely on rpcChannel()->process() for responsiveness.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description references the linked issue but lacks comprehensive details about the problem, solution, and verification steps required by the template. Expand the description to explain the root cause of the crash, how the changes fix the recursive call issue, and confirm testing was performed.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fixed a recursive call to the async::processMessages' clearly describes the main change addressing the crash issue resolved by this PR.
Linked Issues check ✅ Passed The code changes address the recursive async::processMessages issue by removing direct calls and implementing guard mechanisms, which should prevent the MP3 export crash.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the recursive async::processMessages call and its consequences in the async framework and audio export code.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/framework/global/thirdparty/kors_async/async/internal/rpcqueue.h (1)

103-114: ⚠️ Potential issue | 🟠 Major

Make m_isProcessing reset scope-bound.

m_isProcessing is cleared manually at Line 114. If control exits earlier while iterating handlers, the flag can stay true and future process() calls will keep returning early.

Suggested fix
     void process()
     {
         // try send pending
         sendPending();

         assert(m_connPort);

         if (m_isProcessing) {
             return;
         }

-        assert(!m_isProcessing && "Recursive processing of messages is not allowed");
         m_isProcessing = true;
+        struct ProcessingGuard {
+            bool& flag;
+            ~ProcessingGuard() { flag = false; }
+        } processingGuard { m_isProcessing };

         // receive messages
         m_buffer.clear();
         bool ok = m_connPort->m_queue.tryPopAll(m_buffer);
         if (ok && m_handler) {
             for (const T& item : m_buffer) {
                 m_handler(item);
             }
         }
-
-        m_isProcessing = false;
     }
src/framework/global/thirdparty/kors_async/async/async.h (1)

115-122: ⚠️ Potential issue | 🟠 Major

Add runtime guards for callback validation instead of assert-only checks.

The assertions at lines 115 and 141 are stripped in release builds (NDEBUG is set in SetupBuildEnvironment.cmake). If an empty callback reaches line 146, func() will crash without detection. While current usages always pass valid lambdas, the default parameter in the Func constructor permits invalid input. Add runtime validation to make invalid construction impossible and guard the call.

Suggested fix
         struct Func : public ICallable
         {
             Call func;
-            Func(const Call& f = nullptr)
-                : func(f)
-            {
-                assert(func && "callback is nullptr");
-            }
+            explicit Func(const Call& f)
+                : func(f) {}

             void call(const void*) override
             {
-                func();
+                if (func) {
+                    func();
+                }
             }
         };

+        if (!func) {
+            return;
+        }
+
         CallMsg m;
         m.receiver = caller;
         m.func = std::make_shared<Func>(func);

Also applies to: 138-142, 146-146


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a4017c81-bfc3-4d1d-a769-18b8a2d02f2f

📥 Commits

Reviewing files that changed from the base of the PR and between c3842f5 and 3731683.

📒 Files selected for processing (4)
  • src/framework/audio/engine/internal/synthesizers/fluidsynth/fluidsynth.cpp
  • src/framework/global/thirdparty/kors_async/async/async.h
  • src/framework/global/thirdparty/kors_async/async/internal/rpcqueue.h
  • src/project/qml/MuseScore/Project/internal/Export/exportdialogmodel.cpp

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/framework/audio/engine/internal/export/soundtrackwriter.cpp (1)

151-167: 🧹 Nitpick | 🔵 Trivial

Unused variable and commented-out code should be cleaned up.

The variable thisThId on line 151 is now unused since its only reference is in the commented-out code on line 166. This will likely generate a compiler warning.

If the async::processMessages call is intentionally being removed as part of this fix, consider removing both the commented code and the unused variable entirely rather than leaving dead code.

♻️ Proposed cleanup
-    const std::thread::id thisThId = std::this_thread::get_id();
     while (inputBufferOffset < inputBufferMaxOffset && !m_isAborted) {
         m_source->process(m_intermBuffer.data(), m_renderStep);

         size_t samplesToCopy = std::min(m_intermBuffer.size(), inputBufferMaxOffset - inputBufferOffset);

         std::copy(m_intermBuffer.begin(),
                   m_intermBuffer.begin() + samplesToCopy,
                   m_inputBuffer.begin() + inputBufferOffset);

         inputBufferOffset += samplesToCopy;
         sendStepProgress(PREPARE_STEP, inputBufferOffset, inputBufferMaxOffset);

         //! NOTE It is necessary for cancellation to work
         //! and for information about the audio signal to be transmitted.
-        //async::processMessages(thisThId);
         rpcChannel()->process();
     }
src/framework/global/thirdparty/kors_async/async/internal/rpcqueue.h (2)

98-110: ⚠️ Potential issue | 🟠 Major

Re-entrancy guard relies on assert which is disabled in release builds.

The assert(!m_isProcessing) on line 98 only fires in debug builds. In release builds where NDEBUG is defined, assert() is a no-op, meaning the function will still execute recursively without any protection.

If re-entrancy truly should never happen (as the assertion message indicates), consider adding an early return after the assert to protect release builds as well:

🛡️ Proposed fix to protect release builds
         assert(!m_isProcessing && "Recursive processing of messages is not allowed");
+        if (m_isProcessing) {
+            return;
+        }
         m_isProcessing = true;

91-111: ⚠️ Potential issue | 🟡 Minor

Guard flag is not exception-safe.

If m_handler(item) throws an exception (line 106), m_isProcessing will remain true and never be reset, causing all subsequent process() calls to be blocked or skipped (depending on whether the early-return fix above is applied).

Consider using RAII to ensure the flag is always reset:

🛡️ Proposed fix using RAII pattern
     void process()
     {
         // try send pending
         sendPending();

         assert(m_connPort);

         assert(!m_isProcessing && "Recursive processing of messages is not allowed");
+        if (m_isProcessing) {
+            return;
+        }
-        m_isProcessing = true;
+        
+        struct Guard {
+            bool& flag;
+            Guard(bool& f) : flag(f) { flag = true; }
+            ~Guard() { flag = false; }
+        } guard(m_isProcessing);

         // receive messages
         m_buffer.clear();
         bool ok = m_connPort->m_queue.tryPopAll(m_buffer);
         if (ok && m_handler) {
             for (const T& item : m_buffer) {
                 m_handler(item);
             }
         }
-
-        m_isProcessing = false;
     }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 02f24880-a72a-4810-8927-8ac170712fcf

📥 Commits

Reviewing files that changed from the base of the PR and between 3731683 and 3e9947d.

📒 Files selected for processing (5)
  • src/framework/audio/engine/internal/export/soundtrackwriter.cpp
  • src/framework/audio/engine/internal/synthesizers/fluidsynth/fluidsynth.cpp
  • src/framework/global/thirdparty/kors_async/async/async.h
  • src/framework/global/thirdparty/kors_async/async/internal/rpcqueue.h
  • src/project/qml/MuseScore/Project/internal/Export/exportdialogmodel.cpp

@igorkorsukov igorkorsukov force-pushed the w/ioc/step_37 branch 2 times, most recently from 3766dba to f67a840 Compare March 30, 2026 13:38
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 17de9e1e-8cfa-4ac0-857c-b933b85af75a

📥 Commits

Reviewing files that changed from the base of the PR and between 3e9947d and f67a840.

📒 Files selected for processing (5)
  • src/framework/audio/engine/internal/export/soundtrackwriter.cpp
  • src/framework/audio/engine/internal/synthesizers/fluidsynth/fluidsynth.cpp
  • src/framework/global/thirdparty/kors_async/async/async.h
  • src/framework/global/thirdparty/kors_async/async/internal/rpcqueue.h
  • src/project/qml/MuseScore/Project/internal/Export/exportdialogmodel.cpp
💤 Files with no reviewable changes (1)
  • src/framework/audio/engine/internal/export/soundtrackwriter.cpp

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 78dee4d3-8bad-45e4-922f-e03f5ee35490

📥 Commits

Reviewing files that changed from the base of the PR and between f67a840 and 0337c8b.

📒 Files selected for processing (5)
  • src/framework/audio/engine/internal/export/soundtrackwriter.cpp
  • src/framework/audio/engine/internal/synthesizers/fluidsynth/fluidsynth.cpp
  • src/framework/global/thirdparty/kors_async/async/async.h
  • src/framework/global/thirdparty/kors_async/async/internal/rpcqueue.h
  • src/project/qml/MuseScore/Project/internal/Export/exportdialogmodel.cpp
💤 Files with no reviewable changes (1)
  • src/framework/audio/engine/internal/export/soundtrackwriter.cpp

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.

Master: Crash on mp3 export

2 participants