Skip to content

Fix single-instance mutex race during app restart handoff - #50

Merged
Balragon merged 1 commit into
mainfrom
codex/fix-restart-mutex-race
Jul 3, 2026
Merged

Fix single-instance mutex race during app restart handoff#50
Balragon merged 1 commit into
mainfrom
codex/fix-restart-mutex-race

Conversation

@Balragon

@Balragon Balragon commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Problem

RestartApplicationAsync (settings "Save & Restart") starts the replacement process before the old process finishes ShutdownApplicationAsync (disconnect, tray dispose, Application.Shutdown). App.Main exits immediately when the single-instance mutex is already held, so the relaunched instance could silently quit while the old one was still shutting down — the restart button would just close the app.

Fix

  • New SingleInstanceGate.TryAcquireExistingMutex: bounded WaitOne that treats AbandonedMutexException (previous process died without releasing) as a successful acquire.
  • App.Main now waits up to 5 seconds for the previous instance to release the mutex before giving up. Genuine duplicate launches still exit as before, after the bounded wait. Both paths are logged.

No change to update/installer handoff behavior: the Inno Setup /CLOSEAPPLICATIONS relaunch path already waits for the old process to exit, and it also benefits from the extra tolerance.

Testing

5 new SingleInstanceGateTests cover: unowned mutex, mutex held past the timeout (returns false), holder releasing within the timeout, abandoned mutex, and null argument.

Validation

  • dotnet build iMirror.sln -c Release — passed, 0 warnings, 0 errors.
  • dotnet test MacMirrorReceiver.Tests\MacMirrorReceiver.Tests.csproj -c Release — passed: 72 tests, 0 failed.

Independent of #49; can merge in any order (trivial CHANGELOG adjacency with other Unreleased entries).

🤖 Generated with Claude Code

Settings "Save & Restart" starts the replacement iMirror process before
the old process finishes disconnecting and shutting down. The new
process could lose the single-instance mutex race and silently exit,
leaving the user with no running iMirror.

App.Main now waits up to 5 seconds for the previous holder to release
the mutex (via SingleInstanceGate), treating an abandoned mutex as a
successful acquire. Genuine duplicate launches still exit, just after a
bounded wait. Adds 5 SingleInstanceGate tests covering unowned, held,
released-within-timeout, and abandoned mutex cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Balragon
Balragon force-pushed the codex/fix-restart-mutex-race branch from 3dabd17 to a509287 Compare July 3, 2026 10:38
@Balragon
Balragon merged commit cc8bb76 into main Jul 3, 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