Fix hard crash when toggling ASIO while sending a specific app

Local checkpoint - NOT for public release.

Repro (Ed): in applications send mode with an app being captured, turning the
ASIO driver off hard-crashed the process. No log and no crash-report file were
written - and MixingEngine.DisposeEntry swallows managed exceptions - which points
to a native access violation, not a managed throw.

Cause: the ASIO toggle rebuilds the capture backend (ApplyAsioMode -> ApplyAudioRuntime
-> ApplySendSources), which disposes the live ProcessLoopbackCapture. The old design
released the WASAPI COM objects from the disposing thread while the capture thread
could still be inside a native GetBuffer call - a classic use-after-free / AV.

Fix: the capture thread now owns the ENTIRE COM lifecycle. It activates, runs, and
releases every COM object itself, in its finally, only after the loop has exited.
StopRecording/Dispose merely signal and join (2s) - they never touch the COM objects.
If the thread ever wedges in a native call we leak it rather than free from outside
(a rare bounded leak beats a hard crash). The thread is also explicitly MTA, and
activation moved onto it, so the async-activation callback can't stall the UI thread.
bufferReady is volatile and only disposed once the thread has genuinely exited.

Also: ApplyAsioMode force-sets the WASAPI send-list visibility for the new mode, which
resurrected the loopback-outputs list in applications mode; re-assert ApplySendModeVisibility
at the end so the correct list stays shown after an ASIO toggle.

New self-test "Per-application capture lifecycle" runs real start/stop/dispose cycles
of the native capture against our own process on hardware - a bad teardown would AV
and fail the gate. Gate: 16/16.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-12 11:23:18 +01:00
co-authored by Claude Opus 4.8
parent f8a42f2806
commit ffeba4ad2b
3 changed files with 113 additions and 44 deletions
+4
View File
@@ -6566,6 +6566,10 @@ public sealed class MainForm : Form
// to re-tick to get audio back.
ApplyAudioRuntime();
ApplyReceiveDevices();
// The block above force-set the WASAPI send-list visibility for the new mode. Re-assert the
// per-application send view on top so the loopback-outputs list stays hidden (and the app list
// shown) when we're in applications mode — ASIO toggling must not resurrect the wrong list.
ApplySendModeVisibility();
if (wipedSomething) logFile.Event($"audio mode change wiped now-hidden device ticks");
}