Browse Source

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.
Ryan C. Gordon 2 weeks ago
parent
commit
65e462a6f2
1 changed files with 20 additions and 4 deletions
  1. 20 4
      src/audio/SDL_audio.c

+ 20 - 4
src/audio/SDL_audio.c

@@ -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.
-void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
+static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata)
 {
-    if (!device) {
-        return;
-    }
+    SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
+    SDL_assert(device != NULL);
 
     // 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
@@ -808,6 +807,23 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *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);
+    }
 }