From ef2e5e24a63d57ed379a130d1164dcf21e892d07 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Fri, 13 Nov 2026 17:59:49 -0500 Subject: [PATCH] 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 --- runtime/audio_mixer.c | 10 +++++++--- tests/audio_mixer_check.c | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/runtime/audio_mixer.c b/runtime/audio_mixer.c index 3b71e55c..a5e604ed 100644 --- a/runtime/audio_mixer.c +++ b/runtime/audio_mixer.c @@ -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); } diff --git a/tests/audio_mixer_check.c b/tests/audio_mixer_check.c index 5cd7b1e1..33c7d7fb 100644 --- a/tests/audio_mixer_check.c +++ b/tests/audio_mixer_check.c @@ -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"; }