SDL: audio: Better handling of ProvidesOwnCallbackThread backends.

From a8323ebe680e23ce585de33cc36e74d5617259e3 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 3 Jul 2023 11:37:40 -0400
Subject: [PATCH] audio: Better handling of ProvidesOwnCallbackThread backends.

---
 src/audio/SDL_audio.c    | 20 ++++++++++++++------
 src/audio/SDL_sysaudio.h |  5 ++++-
 2 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index abcd2219fabc..77f5c2d86c80 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -388,7 +388,7 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
     }
 
     // if there's an audio thread, don't free until thread is terminating, otherwise free stuff now.
-    const SDL_bool should_destroy = (device->thread == NULL);
+    const SDL_bool should_destroy = SDL_AtomicGet(&device->thread_alive) ? SDL_FALSE : SDL_TRUE;
     SDL_UnlockMutex(device->lock);
 
     // Post the event, if we haven't tried to before and if it's desired
@@ -641,6 +641,7 @@ void SDL_QuitAudio(void)
 
 void SDL_AudioThreadFinalize(SDL_AudioDevice *device)
 {
+    SDL_assert(SDL_AtomicGet(&device->thread_alive));
     if (SDL_AtomicGet(&device->condemned)) {
         if (device->thread) {
             SDL_DetachThread(device->thread);  // no one is waiting for us, just detach ourselves.
@@ -648,6 +649,7 @@ void SDL_AudioThreadFinalize(SDL_AudioDevice *device)
         }
         DestroyPhysicalAudioDevice(device);
     }
+    SDL_AtomicSet(&device->thread_alive, 0);
 }
 
 // Output device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort.
@@ -1053,15 +1055,18 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec)
 // this expects the device lock to be held.
 static void ClosePhysicalAudioDevice(SDL_AudioDevice *device)
 {
-    if (device->thread != NULL) {
+    SDL_assert(current_audio.impl.ProvidesOwnCallbackThread || ((device->thread == NULL) == (SDL_AtomicGet(&device->thread_alive) == 0)));
+
+    if (SDL_AtomicGet(&device->thread_alive)) {
         SDL_AtomicSet(&device->shutdown, 1);
-        SDL_WaitThread(device->thread, NULL);
-        device->thread = NULL;
-        SDL_AtomicSet(&device->shutdown, 0);
+        if (device->thread != NULL) {
+            SDL_WaitThread(device->thread, NULL);
+            device->thread = NULL;
+        }
     }
 
     if (device->is_opened) {
-        current_audio.impl.CloseDevice(device);
+        current_audio.impl.CloseDevice(device);  // if ProvidesOwnCallbackThread, this must join on any existing device thread before returning!
         device->is_opened = SDL_FALSE;
     }
 
@@ -1073,6 +1078,7 @@ static void ClosePhysicalAudioDevice(SDL_AudioDevice *device)
     SDL_memcpy(&device->spec, &device->default_spec, sizeof (SDL_AudioSpec));
     device->sample_frames = 0;
     device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format);
+    SDL_AtomicSet(&device->shutdown, 0);  // ready to go again.
 }
 
 void SDL_CloseAudioDevice(SDL_AudioDeviceID devid)
@@ -1204,6 +1210,7 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec
     }
 
     // Start the audio thread if necessary
+    SDL_AtomicSet(&device->thread_alive, 1);
     if (!current_audio.impl.ProvidesOwnCallbackThread) {
         const size_t stacksize = 0;  // just take the system default, since audio streams might have callbacks.
         char threadname[64];
@@ -1211,6 +1218,7 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec
         device->thread = SDL_CreateThreadInternal(device->iscapture ? CaptureAudioThread : OutputAudioThread, threadname, stacksize, device);
 
         if (device->thread == NULL) {
+            SDL_AtomicSet(&device->thread_alive, 0);
             ClosePhysicalAudioDevice(device);
             return SDL_SetError("Couldn't create audio thread");
         }
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index a727805493ed..ca71097fb7c5 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -118,7 +118,7 @@ typedef struct SDL_AudioDriverImpl
     void (*Deinitialize)(void);
 
     /* Some flags to push duplicate code into the core and reduce #ifdefs. */
-    SDL_bool ProvidesOwnCallbackThread;
+    SDL_bool ProvidesOwnCallbackThread;  // !!! FIXME: rename this, it's not a callback thread anymore.
     SDL_bool HasCaptureSupport;
     SDL_bool OnlyHasDefaultOutputDevice;
     SDL_bool OnlyHasDefaultCaptureDevice;
@@ -246,6 +246,9 @@ struct SDL_AudioDevice
     /* non-zero if this was a disconnected default device and we're waiting for its replacement. */
     SDL_AtomicInt zombie;
 
+    /* non-zero if this has a thread running (which might be `thread` or something provided by the backend!) */
+    SDL_AtomicInt thread_alive;
+
     /* SDL_TRUE if this is a capture device instead of an output device */
     SDL_bool iscapture;