audio: Handle device disconnects on the main thread.

This avoids situations like:

- PulseAudio holds its own lock in the hotplug thread.
- The hotplug thread notices a device went away.
- The hotplug thread calls SDL_AudioDeviceDisconnected().
- SDL_AudioDeviceDisconnected() tries to grab the device lock.
- The device thread is holding the device lock...
- ...but is currently waiting on PulseAudio's lock to release.

In short: deadlock. It's better to queue this work to run on the main thread,
where we can guarantee a start with _none_ of the audio subsystem locks held.
This commit is contained in:
Ryan C. Gordon
2025-09-25 16:02:19 -04:00
parent f71348f38b
commit 65e462a6f2

View File

@@ -734,11 +734,10 @@ SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_
} }
// Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. // Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread.
void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata)
{ {
if (!device) { SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
return; SDL_assert(device != NULL);
}
// Save off removal info in a list so we can send events for each, next // Save off removal info in a list so we can send events for each, next
// time the event queue pumps, in case something tries to close a device // time the event queue pumps, in case something tries to close a device
@@ -808,6 +807,23 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
UnrefPhysicalAudioDevice(device); UnrefPhysicalAudioDevice(device);
} }
// We always ref this in SDL_AudioDeviceDisconnected(), so if multiple attempts
// to disconnect are queued, the pointer stays valid until the last one comes
// through.
UnrefPhysicalAudioDevice(device);
}
void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
{
// lots of risk of various audio backends deadlocking because they're calling
// this while holding a backend-specific lock, which causes problems when we
// want to obtain the device lock while its audio thread is also waiting for
// that lock to be released. So just queue the work on the main thread.
if (device) {
RefPhysicalAudioDevice(device);
SDL_RunOnMainThread(SDL_AudioDeviceDisconnected_OnMainThread, device, false);
}
} }