Skip a change-event slot whose handle is not stored yet when a channel change signals the list.

The review of the one-event-per-kind change (E-11) found a narrow window kept from before: change_event_list counts a
slot (atomic_fetch_add on n_change_events) before it stores the handle there. audio_channels_live on the present path
running between the two read the slot's 0 and passed it to kern_event_signal, which ends the process with "not a
writable event handle". It needs a kind's first request to meet a channel-setting change.

change_event_list now stores the handle with release order, and audio_channels_live loads each slot with acquire order
and skips a slot that still reads 0; that slot's event is signalled by the next channel change.

Not reproduced before the change in a game run. tests/audio_mixer_check.c counts a slot without storing it and changes
the channels: no signal may reach handle 0, and the listed events are signalled again. Against the previous
audio_mixer.c it fails with handle 0 signalled once. The check passes, and the compile check of audio_mixer.c (also with
-Wall -Wextra) adds no warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 17:59:49 -05:00
parent d8c046acc4
commit ef2e5e24a6
2 changed files with 15 additions and 3 deletions

View File

@@ -1354,13 +1354,14 @@ static const char device_name[] = "AudioTvOutput";
// The events the game took to hear of a change of the output device (IAudioDevice's, its two notifications' and the
// virtual device manager's). audio_channels_live signals them all: the game then asks for the channel count again, as
// it does on a console whose output moves from the TV to headphones.
// it does on a console whose output moves from the TV to headphones. A slot is counted before its handle is stored, so
// audio_channels_live skips a slot that still reads 0 (kern_event_signal ends the process on a bad handle).
static uint32_t change_events[16];
static _Atomic int n_change_events;
static void change_event_list(uint32_t ev) {
int i = atomic_fetch_add(&n_change_events, 1);
if (i < 16) change_events[i] = ev;
if (i < 16) __atomic_store_n(&change_events[i], ev, __ATOMIC_RELEASE);
}
// One event per kind, made at the kind's first request; every request gets its own readable handle to it. Made per
@@ -1396,7 +1397,10 @@ void audio_channels_live(void) {
if (now == was) return;
int n = atomic_load(&n_change_events);
if (n > 16) n = 16;
for (int i = 0; i < n; i++) kern_event_signal(change_events[i]);
for (int i = 0; i < n; i++) {
uint32_t e = __atomic_load_n(&change_events[i], __ATOMIC_ACQUIRE);
if (e) kern_event_signal(e);
}
rt_log("audio: NX2NAVI_AUDIO_CHANNELS=%s: the output goes from %s to %s; the game is told the device changed "
"(%d events)", want, was == 6 ? "5.1" : "stereo", now == 6 ? "5.1" : "stereo", n);
}

View File

@@ -413,6 +413,14 @@ static void change_events_once(void) {
for (int i = 0; i < n && i < 16; i++) once += signalled[change_events[i]] == 1;
CHECK(once == n, "a channel change signals each listed event once (%d of %d)", once, n);
CHECK(!signal_closed, "no closed event is signalled");
/* a slot counted whose handle is not stored yet (a kind's first request on another thread): skipped, never
signalled as handle 0, which the kernel answers with a fatal error */
atomic_fetch_add(&n_change_events, 1);
channels_setting = "stereo";
audio_channels_live();
CHECK(output_channels() == 2, "back to stereo");
CHECK(!signalled[0], "a slot not stored yet is skipped (handle 0 signalled %d times)", signalled[0]);
CHECK(signalled[change_events[0]] == 2, "the listed events are signalled again (%d)", signalled[change_events[0]]);
audio_mode = "none", channels_setting = "stereo";
}