Skip to content

Fix crash on New Project while master effects ring - #21

Merged
nooga merged 1 commit into
masterfrom
fix-master-fx-crash
Oct 15, 2026
Merged

nooga merged 1 commit into
masterfrom
fix-master-fx-crash

Conversation

@nooga

@nooga nooga commented Oct 15, 2026

Copy link
Copy Markdown
Owner

The crash

Pressing New Project, or opening another project, while the current project's master effects were still sounding could segfault on the audio thread:

Segmentation fault in std.atomic load
  handoff.zig:24 adopt ← fy_raw_machine.zig:1046 adoptAssets ← adoptImpl
  ← engine.zig:594 adoptTrack (master's audioEffects) ← adoptMachines ← render

It reproduced after playing a song with a long verb2 cathedral tail and a master limiter2, then pressing New Project.

Cause

document.apply replaced the master's effect chain by calling deinit on the old effects in place. Every other path (removeEffect, replaceEffect, replaceMachine) hands old machines to retired_machines, which frees them only after the audio thread acknowledges the new chain. Regular tracks don't hit this because they're replaced whole while the device is stopped. The master is restored in place.

When the engine is still ringing at restart, Track.adoptMachines(fade=true) keeps the old chain for one more fade-out chunk. So adoptTrack called adopt_updates on the freed effect states.

Fix

  • document.apply now removes the master's effects with Track.removeEffect, so they retire like any other removed effect.
  • The UI loop now collects the master's retired machines every frame, as it does for the tracks through publishSnapshot. The old chain's memory is freed as soon as audio moves past it, not at the next master edit.

Verification

  • A new test, realtime: a project load retires the master's effects until audio adopts the new chain, fails before the fix (the effect is freed while the audio side still holds it) and passes after. It covers the fading adoption, the switch, and the free afterwards.
  • zig build test passes.
  • zig build bench -- --all --check --no-sheets is bit-exact on all 164 golden cases.
  • The GUI repro itself (play, then New Project) has not been re-run by hand.

🤖 Generated with Claude Code

… them

A project load replaced the master's effect chain by freeing the old
effects in place. When the old project was still ringing, the engine's
fading adoption renders the old chain for one more chunk, and the audio
thread read the freed states (segfault in adoptAssets on New Project).
The load now removes them through Track.removeEffect, so they retire and
free only after audio adopts the new chain, and the UI loop collects the
master's retired machines every frame like the tracks'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nooga
nooga merged commit 470a874 into master Oct 15, 2026
1 check 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