From 52011be7a5170796dd36ef0478d566997375c3d2 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 18 May 2026 22:45:01 -0400
Subject: [PATCH] track: Don't crash in MIX_StopTrack(t, 0) if a callback
destroys the track.
Fixes #852.
---
src/SDL_mixer.c | 12 +++++++++---
src/SDL_mixer_internal.h | 2 +-
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/SDL_mixer.c b/src/SDL_mixer.c
index dcb5ce28..7f61c2fc 100644
--- a/src/SDL_mixer.c
+++ b/src/SDL_mixer.c
@@ -576,7 +576,7 @@ static void SDLCALL MixerCallback(void *userdata, SDL_AudioStream *stream, int a
for (MIX_Track *track = group->tracks; track; track = next_track) {
next_track = track->group_next; // this won't save you from a callback going totally rogue, but it'll deal with the current track leaving the group.
- track->currently_mixing = true;
+ track->currently_inuse = true;
const int to_be_read = (additional_amount / SDL_AUDIO_FRAMESIZE(mixer->spec)) * SDL_AUDIO_FRAMESIZE(track->output_spec);
const int br = SDL_GetAudioStreamData(track->output_stream, getbuf, to_be_read);
@@ -610,7 +610,7 @@ static void SDLCALL MixerCallback(void *userdata, SDL_AudioStream *stream, int a
}
}
- track->currently_mixing = false;
+ track->currently_inuse = false;
if (track->destroy_requested) { // callback asked to destroy the track while we were still using it.
MIX_DestroyTrack(track); // actually kill it now.
}
@@ -1515,7 +1515,7 @@ void MIX_DestroyTrack(MIX_Track *track)
// handle the case where someone destroys a track during a mixer callback. :O
// tracks are not currently reference-counted like MIX_Audio objects are, but
// we'll catch this specific case for now.
- if (track->currently_mixing) {
+ if (track->currently_inuse) {
track->destroy_requested = true;
UnlockMixer(mixer);
return;
@@ -2399,7 +2399,9 @@ static void StopTrack(MIX_Track *track, Sint64 fadeOut)
if (track->internal_stream) {
SDL_ClearAudioStream(track->internal_stream); // make sure we don't leave old data hanging around.
}
+ track->currently_inuse = true;
TrackStopped(track);
+ track->currently_inuse = false;
} else {
track->total_fade_frames = fadeOut;
track->fade_frames = fadeOut;
@@ -2408,6 +2410,10 @@ static void StopTrack(MIX_Track *track, Sint64 fadeOut)
}
}
UnlockTrack(track);
+
+ if (track->destroy_requested) { // callback asked to destroy the track while we were still touching it.
+ MIX_DestroyTrack(track); // actually kill it now.
+ }
}
bool MIX_StopTrack(MIX_Track *track, Sint64 fade_out_frames)
diff --git a/src/SDL_mixer_internal.h b/src/SDL_mixer_internal.h
index 0ed73cd1..bccb5b38 100644
--- a/src/SDL_mixer_internal.h
+++ b/src/SDL_mixer_internal.h
@@ -148,7 +148,7 @@ struct MIX_Track
MIX_Audio *input_audio; // non-NULL if used with MIX_SetTrackAudioStream. Holds a reference.
SDL_IOStream *io; // used for MIX_SetTrackAudio and MIX_SetTrackIOStream. Might be owned by us (SDL_IOFromConstMem of MIX_Audio::precache), or owned by the app.
MIX_IoClamp ioclamp; // used for MIX_SetTrackAudio and MIX_SetTrackIOStream.
- bool currently_mixing; // true when the mixer is running on this track.
+ bool currently_inuse; // true when we want to delay an app's MIX_DestroyTrack request (they destroyed it from a mixer callback).
bool destroy_requested; // true if MIX_DestroyTrack called while the track is actively mixing.
bool closeio; // true if we should close `io` when changing track data.
bool halt_when_exhausted; // true if we should stop the track when input runs out.