Skip to content

Fix crossfade actor isolation crash - #83

Merged
Mag1cByt3s merged 2 commits into
Evil-Project:mainfrom
Mag1cByt3s:fix/crossfade-actor-isolation
Jul 30, 2026
Merged

Fix crossfade actor isolation crash#83
Mag1cByt3s merged 2 commits into
Evil-Project:mainfrom
Mag1cByt3s:fix/crossfade-actor-isolation

Conversation

@Mag1cByt3s

@Mag1cByt3s Mag1cByt3s commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • run the crossfade volume ramp on the main queue alongside the main-actor-isolated audio engine
  • finalize the crossfade directly on the same actor
  • add a regression test that plays two generated audio files through the crossfade completion path

Root cause

Swift 6 detected the main-actor-isolated crossfade timer closure running on the custom crossfade-ramp dispatch queue and stopped in _dispatch_assert_queue_fail as a song reached the transition point.

Impact

Songs can now reach the end and crossfade into the next queued song without triggering the Swift executor assertion.

Validation

  • reproduced and identified the crash from the LLDB backtrace
  • confirmed the fix on a physical device
  • fresh Debug iOS Simulator build
  • focused AVEngine playback regression suite: passed
  • complete iOS unit suite: passed

Summary by Sourcery

Ensure crossfade volume ramp execution stays on the main actor to prevent isolation-related crashes when transitioning between songs.

Bug Fixes:

  • Prevent Swift 6 executor assertion by running the crossfade timer and completion on the main actor instead of a custom dispatch queue.

Tests:

  • Add a regression test that crossfades between two generated audio files and verifies the crossfade ramp and completion occur correctly on the audio engine actor.

Summary by CodeRabbit

  • Bug Fixes

    • Improved crossfade completion handling for smoother, more reliable audio transitions.
    • Prevented stale crossfade callbacks from affecting playback.
  • Tests

    • Added regression coverage to verify crossfade operations complete correctly and leave playback in a stable state.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Mag1cByt3s, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c3f7bbd-c785-4119-a81f-eed96f804b1a

📥 Commits

Reviewing files that changed from the base of the PR and between 616daef and 3733eb6.

📒 Files selected for processing (2)
  • Twinskaraoke/Services/Audio/AVEnginePlayback.swift
  • TwinskaraokeTests/AVEnginePlaybackRegressionTests.swift
📝 Walkthrough

Walkthrough

Crossfade ramp timers now run on the main queue, directly validate active generation and crossfade state, and finalize without an additional actor handoff. A regression test verifies main-actor ramp completion and cleanup.

Changes

Crossfade actor isolation

Layer / File(s) Summary
Main-queue crossfade ramp and regression coverage
Twinskaraoke/Services/Audio/AVEnginePlayback.swift, TwinskaraokeTests/AVEnginePlaybackRegressionTests.swift
Crossfade ramp timers use the main queue, validate the current generation and crossfade state before finalization, and are covered by an async completion regression test.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: xiaoyuan151

🚥 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 clearly and concisely matches the main change: fixing a crossfade actor-isolation crash.
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

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.

@sourcery-ai

sourcery-ai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This pull request fixes a Swift 6 main-actor isolation crash in the crossfade path by ensuring the crossfade ramp timer and completion handling both execute on the main actor, and adds a regression test to verify the behavior using generated audio files.

Sequence diagram for crossfade ramp and completion on main actor

sequenceDiagram
    actor Test
    participant AVEnginePlayback
    participant DispatchSourceTimer
    participant MainActor

    Test->>AVEnginePlayback: play(url:onReady:)
    Test->>AVEnginePlayback: beginCrossfade(url:duration:ramp:)

    AVEnginePlayback->>DispatchSourceTimer: DispatchSource.makeTimerSource(queue:.main)
    AVEnginePlayback->>DispatchSourceTimer: timer.schedule(deadline:repeating:leeway:)
    AVEnginePlayback->>DispatchSourceTimer: timer.setEventHandler(handler:)
    AVEnginePlayback->>DispatchSourceTimer: timer.resume()

    loop [timer event handler]
        MainActor->>AVEnginePlayback: finalizeCrossfade()
        AVEnginePlayback-->>Test: onCrossfadeCompleted
    end
Loading

File-Level Changes

Change Details Files
Run the crossfade ramp timer and its handler on the main actor instead of a custom dispatch queue to avoid actor isolation violations.
  • Remove the dedicated crossfade ramp DispatchQueue used for the DispatchSource timer.
  • Create the crossfade DispatchSource timer on the main queue so the ramp callback runs on the main actor.
  • Document that AVEnginePlayback and its player nodes are main-actor isolated and that the ramp handler is kept on that executor.
Twinskaraoke/Services/Audio/AVEnginePlayback.swift
Finalize the crossfade directly on the AVEnginePlayback actor from the ramp callback rather than re-dispatching to the main queue.
  • Replace the nested DispatchQueue.main.async + MainActor.assumeIsolated block with a direct guard and call to finalizeCrossfade() from the timer handler.
  • Retain the generation and isCrossfading guards to ensure finalizeCrossfade only runs for valid, in-progress crossfades.
  • Simplify the completion dispatch logic by removing the extra asynchronous hop back to the main queue.
Twinskaraoke/Services/Audio/AVEnginePlayback.swift
Add a regression test that exercises the crossfade ramp and completion on the AVEnginePlayback actor using generated silent audio files.
  • Create two temporary silent WAV files using the existing helper and clean them up via defer.
  • Start playback of the source file and wait for readiness using withCheckedContinuation before beginning the crossfade.
  • Use a confirmation helper and an onCrossfadeCompleted closure to verify crossfade completion is invoked and awaited on the audio engine actor.
  • Assert that isCrossfading is false after completion and stop the engine at the end of the test.
TwinskaraokeTests/AVEnginePlaybackRegressionTests.swift

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@Mag1cByt3s
Mag1cByt3s marked this pull request as ready for review July 30, 2026 11:25

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The new timer now relies on DispatchQueue.main to align with the main-actor-isolated engine; consider explicitly annotating AVEnginePlayback (or the relevant APIs) with @MainActor to make the executor guarantees clear and avoid future coupling to the dispatch queue choice.
  • The regression test crossfadeRampUsesMainActor currently only verifies crossfade completion and state, not that the ramp actually executes on the audio engine actor; consider either tightening the assertions to check executor behavior or renaming the test to more accurately reflect what it validates.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new timer now relies on `DispatchQueue.main` to align with the main-actor-isolated engine; consider explicitly annotating `AVEnginePlayback` (or the relevant APIs) with `@MainActor` to make the executor guarantees clear and avoid future coupling to the dispatch queue choice.
- The regression test `crossfadeRampUsesMainActor` currently only verifies crossfade completion and state, not that the ramp actually executes on the audio engine actor; consider either tightening the assertions to check executor behavior or renaming the test to more accurately reflect what it validates.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
TwinskaraokeTests/AVEnginePlaybackRegressionTests.swift (1)

94-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen the crossfade completion assertions.

The test confirms the callback fired and isCrossfading became false, but it does not verify that the incoming URL became current or that the pending marker was cleared. Add assertions for engine.currentURL == incomingURL and !engine.isCrossfadePending; expose a test-only volume snapshot if ramp endpoints must also be verified.

🤖 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 `@TwinskaraokeTests/AVEnginePlaybackRegressionTests.swift` around lines 94 -
120, Strengthen crossfadeRampUsesMainActor by asserting that engine.currentURL
equals incomingURL and engine.isCrossfadePending is false after completion,
alongside the existing completion and isCrossfading checks. Only add a test-only
volume snapshot if needed to validate ramp endpoints.
🤖 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.

Inline comments:
In `@Twinskaraoke/Services/Audio/AVEnginePlayback.swift`:
- Around line 1038-1042: Move the self, crossfadeRampGeneration, and
isCrossfading guard to the beginning of the timer handler, before any volume
writes or player-node mutations; keep finalizeCrossfade() conditional on that
same validated state.

---

Nitpick comments:
In `@TwinskaraokeTests/AVEnginePlaybackRegressionTests.swift`:
- Around line 94-120: Strengthen crossfadeRampUsesMainActor by asserting that
engine.currentURL equals incomingURL and engine.isCrossfadePending is false
after completion, alongside the existing completion and isCrossfading checks.
Only add a test-only volume snapshot if needed to validate ramp endpoints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2dad630a-1883-432f-86d0-16bb48965b7d

📥 Commits

Reviewing files that changed from the base of the PR and between f45451c and 616daef.

📒 Files selected for processing (2)
  • Twinskaraoke/Services/Audio/AVEnginePlayback.swift
  • TwinskaraokeTests/AVEnginePlaybackRegressionTests.swift

Comment thread Twinskaraoke/Services/Audio/AVEnginePlayback.swift Outdated
@Mag1cByt3s
Mag1cByt3s merged commit 4aa61b0 into Evil-Project:main Jul 30, 2026
4 checks passed
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.

1 participant