#1149·miniaudio

Deadlock in WASAPI notification callback

Author: JaythewayCreated Aug 18, 2026Updated Aug 21, 2026

There's a deadlock in WASAPI notification callback when rerouting to a device that has an explicit owner.

Found on master branch (0.11.25), but the relevant code path seems to be unchanged in dev and dev-0.12.

100% reproduceable, with simple_playback_sine as well.

The deadlock is verified by a call stack trace showing that the blocking IAudioClient::Initialize call is executing directly on an OS-managed thread pool worker ntdll.dll.

Documentation for IMMNotificationClient interface states:

In implementing the IMMNotificationClient interface, the client should observe these rules to avoid deadlocks and undefined behavior:

I suspect IAudioClient::Initialize inside the notification callback waits for Audio Service, while Audio Service waits for the notification callback to return and we get a deadlock.

Here's the callstack (not from sine playback example):

ma_IAudioClient_Initialize(ma_IAudioClient * pThis, MA_AUDCLNT_SHAREMODE shareMode, unsigned long streamFlags, __int64 bufferDuration, __int64 periodicity, const MA_WAVEFORMATEX * pFormat, const _GUID * pAudioSessionGuid) Line 22059
	at miniaudio.h(22059)
ma_device_init_internal__wasapi(ma_context * pContext, ma_device_type deviceType, const ma_device_id * pDeviceID, ma_device_init_internal_data__wasapi * pData) Line 23985
	at miniaudio.h(23985)
ma_device_reinit__wasapi(ma_device * pDevice, ma_device_type deviceType) Line 24178
	at miniaudio.h(24178)
ma_device_reroute__wasapi(ma_device * pDevice, ma_device_type deviceType) Line 24524
	at miniaudio.h(24524)
ma_IMMNotificationClient_OnDefaultDeviceChanged(ma_IMMNotificationClient * pThis, ma_EDataFlow dataFlow, ma_ERole role, const wchar_t * pDefaultDeviceID) Line 22541
	at miniaudio.h(22541)

Sine playback example test

Steps to reproduce:

  1. Run sine playback example
  2. In Windows mixer make sure it's routed to a spare device (doesn't matter which one)
  3. Run another application using WASAPI that can take device in exclusive mode
  4. In Windows mixer route sure the second application is using device that allows exclusive mode
  5. In Windows mixer reroute the sine playback example to the same device the second app is currently using in exclusive mode.
  6. Deadlock☠️

I'm using Voicemeter as the "second app", which connects in exclusive mode to output device if using WDM, or shared mode if using MME.

Output log

Here's the output log from sine playback example that hit the deadlock: with /DMA_DEBUG_OUTPUT, the interesting part is near the end:

PS D:\software dev\third party\miniaudio\examples\build\bin> ./simple_playback_sine.exe
DEBUG: Loading library: user32.dll
DEBUG: Loading symbol: GetForegroundWindow
DEBUG: Loading symbol: GetDesktopWindow
DEBUG: Loading library: advapi32.dll
DEBUG: Loading symbol: RegOpenKeyExA
DEBUG: Loading symbol: RegCloseKey
DEBUG: Loading symbol: RegQueryValueExA
DEBUG: Loading library: ole32.dll
DEBUG: Loading symbol: CoInitialize
DEBUG: Loading symbol: CoInitializeEx
DEBUG: Loading symbol: CoUninitialize
DEBUG: Loading symbol: CoCreateInstance
DEBUG: Loading symbol: CoTaskMemFree
DEBUG: Loading symbol: PropVariantClear
DEBUG: Loading symbol: StringFromGUID2
DEBUG: Attempting to initialize WASAPI backend...
DEBUG: Loading library: kernel32.dll
DEBUG: Loading symbol: VerifyVersionInfoW
DEBUG: Loading symbol: VerSetConditionMask
DEBUG: Loading library: avrt.dll
DEBUG: Loading symbol: AvSetMmThreadCharacteristicsA
DEBUG: Loading symbol: AvRevertMmThreadCharacteristics
DEBUG: System Architecture:
DEBUG:   Endian: LE
DEBUG:   SSE2:   YES
DEBUG:   AVX2:   YES
DEBUG:   NEON:   NO
DEBUG: [WASAPI] Trying IAudioClient3_InitializeSharedAudioStream(actualPeriodInFrames=480)
DEBUG:     defaultPeriodInFrames=480
DEBUG:     fundamentalPeriodInFrames=1
DEBUG:     minPeriodInFrames=128
DEBUG:     maxPeriodInFrames=480
DEBUG: [WASAPI] Using IAudioClient3
DEBUG:     periodSizeInFramesOut=480
INFO: [WASAPI]
INFO:   Voicemeeter In 7.1 (VB-Audio Voicemeeter VAIO) (Playback)
INFO:     Format:      32-bit IEEE Floating Point -> 32-bit IEEE Floating Point
INFO:     Channels:    2 -> 8
INFO:     Sample Rate: 48000 -> 48000
INFO:     Buffer Size: 480*3 (1440)
INFO:     Conversion:
INFO:       Pre Format Conversion:  NO
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             NO
INFO:       Passthrough:            NO
INFO:       Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
INFO:       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_LFE CHANNEL_BACK_LEFT CHANNEL_BACK_RIGHT CHANNEL_SIDE_LEFT CHANNEL_SIDE_RIGHT}
Device Name: Voicemeeter In 7.1 (VB-Audio Voicemeeter VAIO)
Press Enter to quit...
DEBUG: === CHANGING DEVICE ===
DEBUG: [WASAPI] Trying IAudioClient3_InitializeSharedAudioStream(actualPeriodInFrames=480)
DEBUG:     defaultPeriodInFrames=480
DEBUG:     fundamentalPeriodInFrames=480
DEBUG:     minPeriodInFrames=480
DEBUG:     maxPeriodInFrames=480
  • I first reroute sine playback example to to Voicemeter 7.1 virtual device, to free up the system default device. It doesn't matter to which device sine playback example is switched, reproduceable with any.
  • Then from Voicemeter settings I reroute it to a device that allows exclusive mode via WDM (WASAPI)
  • Then from Windows volume miser I reroute sine playback example to the same device (=== CHANGING DEVICE ===
  • And we are stuck.

There's another possibly related issue. If another thread then tries to ma_device_uninit the ma_devcie that's stuck holding rerouteLock indefinitely inside of the notification callback, then that thread gets a deadlock as well on releasing IMMDeviceEnumerator. This happens probably not because of the lock itself, but because the Audio Service is blocked.

Here's the callstack (not from sine playback example):

ma_device_uninit__wasapi(ma_device * pDevice) Line 23547
	at miniaudio.h(23547)
ma_device_uninit(ma_device * pDevice) Line 44138
	at miniaudio.h(44138)

It possibly happens for the same reason - lock in OS notification thread. But also rerouteLock seems to be not regarded on device uninitialization.


Workaround

For now my workaroud is to set noAutoStreamRouting= MA_TRUE and initialize my own notification client where I just set a flag on reroute, which is then consumed on the main thread that initialized the device.

Possible solution

Perhaps miniaudio could defer device reinitialization to outside of the notification callback.