From the 2026-07-03 comprehensive review.
WasapiDevice::framesPlayed() is the master clock (via AudioRateSource::now(), sampled on editor and show threads — the timeline genlocks to it per Engine.cpp:704 setAudioRateSource). It reads plain Impl* m_impl (WasapiDevice.hpp:38) and dereferences impl->clock->GetPosition (WasapiDevice.cpp:264-267). During stop(), the render thread Reset()s the COM objects, deletes impl, and nulls m_impl (:235-241) with no synchronization against a concurrent reader.
Failure: device stop / app shutdown / default-device change while the editor or show thread samples the clock → use-after-free on the master clock path.
Fix: make m_impl std::atomic<Impl*>, load-acquire once per call; ensure stop() orders the null-store before the delete (or defer the delete until readers cannot hold the pointer). Initial publish via the promise/future is fine.
Related, lower priority: no recovery on AUDCLNT_E_DEVICE_INVALIDATED (:209, :217) — unplugging headphones silently kills audio until restart; a media server should re-acquire the default endpoint.
From the 2026-07-03 comprehensive review.
WasapiDevice::framesPlayed()is the master clock (via AudioRateSource::now(), sampled on editor and show threads — the timeline genlocks to it per Engine.cpp:704 setAudioRateSource). It reads plainImpl* m_impl(WasapiDevice.hpp:38) and dereferencesimpl->clock->GetPosition(WasapiDevice.cpp:264-267). During stop(), the render thread Reset()s the COM objects, deletes impl, and nulls m_impl (:235-241) with no synchronization against a concurrent reader.Failure: device stop / app shutdown / default-device change while the editor or show thread samples the clock → use-after-free on the master clock path.
Fix: make m_impl
std::atomic<Impl*>, load-acquire once per call; ensure stop() orders the null-store before the delete (or defer the delete until readers cannot hold the pointer). Initial publish via the promise/future is fine.Related, lower priority: no recovery on AUDCLNT_E_DEVICE_INVALIDATED (:209, :217) — unplugging headphones silently kills audio until restart; a media server should re-acquire the default endpoint.