diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index d642016541..eb5f6456e2 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -739,6 +739,21 @@ SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_ return device; } +// you must hold the device lock when calling this! +static void SetAudioDeviceZombieFunctions(SDL_AudioDevice *device) +{ + // Swap in "Zombie" versions of the usual platform interfaces, so the device will keep + // making progress until the app closes it. Otherwise, streams might continue to + // accumulate waste data that never drains, apps that depend on audio callbacks to + // progress will freeze, etc. + device->WaitDevice = ZombieWaitDevice; + device->GetDeviceBuf = ZombieGetDeviceBuf; + device->PlayDevice = ZombiePlayDevice; + device->WaitRecordingDevice = ZombieWaitDevice; + device->RecordDevice = ZombieRecordDevice; + device->FlushRecording = ZombieFlushRecording; +} + // Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata) { @@ -760,18 +775,10 @@ static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata) const bool is_default_device = ((devid == current_audio.default_playback_device_id) || (devid == current_audio.default_recording_device_id)); SDL_UnlockRWLock(current_audio.subsystem_rwlock); - const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 1); + // zombie==2 means "we've handled the disconnect events". 1=="we marked this as dead from a random thread but haven't done anything else" 0==we think we're still alive. + const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 2) || SDL_CompareAndSwapAtomicInt(&device->zombie, 1, 2); if (first_disconnect) { // if already disconnected this device, don't do it twice. - // Swap in "Zombie" versions of the usual platform interfaces, so the device will keep - // making progress until the app closes it. Otherwise, streams might continue to - // accumulate waste data that never drains, apps that depend on audio callbacks to - // progress will freeze, etc. - device->WaitDevice = ZombieWaitDevice; - device->GetDeviceBuf = ZombieGetDeviceBuf; - device->PlayDevice = ZombiePlayDevice; - device->WaitRecordingDevice = ZombieWaitDevice; - device->RecordDevice = ZombieRecordDevice; - device->FlushRecording = ZombieFlushRecording; + SetAudioDeviceZombieFunctions(device); // in case we beat the device thread to this. // on default devices, dump any logical devices that explicitly opened this device. Things that opened the system default can stay. // on non-default devices, dump everything. @@ -822,12 +829,15 @@ static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata) void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) { + //SDL_Log("AUDIO DEVICE DISCONNECTED %p '%s'", device, device ? device->name : NULL); + // 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_CompareAndSwapAtomicInt(&device->zombie, 0, 1); // note that we're (un)dead right now, if we haven't already, but leave the event notifications for the main thread. SDL_RunOnMainThread(SDL_AudioDeviceDisconnected_OnMainThread, device, false); } } @@ -1183,6 +1193,11 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) return false; // we're done, shut it down. } + if (SDL_GetAtomicInt(&device->zombie) == 1) { + // we've been marked as (un)dead but not fully processed. Set up the zombie functions so we stop talking to the real backend. + SetAudioDeviceZombieFunctions(device); + } + bool failed = false; int buffer_size = device->buffer_size; Uint8 *device_buffer = device->GetDeviceBuf(device, &buffer_size); @@ -1349,6 +1364,11 @@ bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device) return false; // we're done, shut it down. } + if (SDL_GetAtomicInt(&device->zombie) == 1) { + // we've been marked as (un)dead but not fully processed. Set up the zombie functions so we stop talking to the real backend. + SetAudioDeviceZombieFunctions(device); + } + bool failed = false; if (!device->logical_devices) {