From b8ab04507e7f5ff7e50d36dc48e16cb2ade212b9 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 2 Sep 2026 06:57:36 +1000 Subject: [PATCH] [client] audio: complete LGMP shutdown release Treat the binding phase as the authoritative shutdown state after graceful stream release. Cover disconnect while the host drains and unbinds both streams. --- client/tests/lgmp_audio_test.c | 63 +++++++++++++++++++++++++++++++++- client/transports/LGMP/audio.c | 18 +++++++--- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/client/tests/lgmp_audio_test.c b/client/tests/lgmp_audio_test.c index 0b163e8a..c9f4c1b8 100644 --- a/client/tests/lgmp_audio_test.c +++ b/client/tests/lgmp_audio_test.c @@ -224,6 +224,12 @@ struct RecordStress atomic_uint successes; }; +struct DisconnectTask +{ + LGMPAudio * audio; + atomic_bool done; +}; + static void * recordStress(void * opaque) { struct RecordStress * stress = opaque; @@ -243,6 +249,14 @@ static void * removeStatusListener(void * opaque) return NULL; } +static void * disconnectAudio(void * opaque) +{ + struct DisconnectTask * task = opaque; + lgmpAudio_disconnect(task->audio); + atomic_store_explicit(&task->done, true, memory_order_release); + return NULL; +} + static void selfUnregisterStatus(void * opaque, const LG_AudioStatus * status) { @@ -267,6 +281,21 @@ static bool waitCount(struct Fixture * fixture, atomic_uint * value, return atomic_load_explicit(value, memory_order_acquire) >= expected; } +static bool waitDone(struct Fixture * fixture, atomic_bool * done, + unsigned timeoutMs) +{ + const uint64_t deadline = microtime() + (uint64_t)timeoutMs * 1000U; + while (microtime() < deadline) + { + lgmpHostProcess(fixture->host); + if (atomic_load_explicit(done, memory_order_acquire)) + return true; + const struct timespec delay = { .tv_nsec = 1000000L }; + nanosleep(&delay, NULL); + } + return atomic_load_explicit(done, memory_order_acquire); +} + static bool fixtureInit(struct Fixture * fixture) { memset(fixture, 0, sizeof(*fixture)); @@ -1210,6 +1239,35 @@ static bool testFaultReleaseReclaim(void) return true; } +static bool testBoundDisconnect(void) +{ + struct Fixture fixture; + CHECK(fixtureInit(&fixture)); + CHECK(beginClaim(&fixture, false)); + CHECK(createStreams(&fixture)); + CHECK(bindStreams(&fixture)); + CHECK(deliverBoundResponse(&fixture, &fixture.claim, true)); + + KVMFRAudioMessage message; + uint8_t payload[KVMFR_AUDIO_PCM_BYTES]; + CHECK(readC2HType(&fixture, KVMFR_AUDIO_MESSAGE_KEEPALIVE, + &message, payload, WAIT_MS)); + + struct DisconnectTask task = { .audio = fixture.audio }; + atomic_init(&task.done, false); + pthread_t thread; + CHECK(!pthread_create(&thread, NULL, disconnectAudio, &task)); + CHECK(readC2HType(&fixture, KVMFR_AUDIO_MESSAGE_RELEASE, + &message, payload, WAIT_MS)); + CHECK(!atomic_load_explicit(&task.done, memory_order_acquire)); + CHECK(unbindBoth(&fixture)); + CHECK(waitDone(&fixture, &task.done, WAIT_MS)); + CHECK(!pthread_join(thread, NULL)); + + fixtureCleanup(&fixture); + return true; +} + int main(void) { debug_init(); @@ -1230,6 +1288,9 @@ int main(void) if (!testFaultReleaseReclaim()) return 1; ++passed; - printf("PASS %u/5 client LGMP audio integration groups\n", passed); + if (!testBoundDisconnect()) + return 1; + ++passed; + printf("PASS %u/6 client LGMP audio integration groups\n", passed); return 0; } diff --git a/client/transports/LGMP/audio.c b/client/transports/LGMP/audio.c index 51acdbb8..d89a43db 100644 --- a/client/transports/LGMP/audio.c +++ b/client/transports/LGMP/audio.c @@ -1608,6 +1608,12 @@ static bool bindingGone(LGMPAudio * audio) return any; } +static bool bindingPhaseActiveNL(const LGMPAudio * audio) +{ + return audio->phase == AUDIO_PHASE_BOUND || + audio->phase == AUDIO_PHASE_RELEASING; +} + static void maintainBinding(LGMPAudio * audio) { const uint64_t now = microtime(); @@ -1716,8 +1722,7 @@ static void maintainBinding(LGMPAudio * audio) static void stopWorkerBinding(LGMPAudio * audio) { LG_LOCK(audio->lock); - const bool active = audio->phase == AUDIO_PHASE_BOUND || - audio->phase == AUDIO_PHASE_RELEASING; + const bool active = bindingPhaseActiveNL(audio); audio->phase = active ? AUDIO_PHASE_RELEASING : audio->phase; LG_UNLOCK(audio->lock); if (!active) @@ -1731,7 +1736,11 @@ static void stopWorkerBinding(LGMPAudio * audio) { drainInbound(audio); maintainBinding(audio); - if (bindingGone(audio) || !lgmpClientSessionValid(audio->client)) + + LG_LOCK(audio->lock); + const bool bindingActive = bindingPhaseActiveNL(audio); + LG_UNLOCK(audio->lock); + if (!bindingActive || !lgmpClientSessionValid(audio->client)) break; lgWaitEvent(audio->event, 1); } @@ -1770,8 +1779,7 @@ static int audioThread(void * opaque) if (atomic_load_explicit(&audio->stop, memory_order_acquire)) break; LG_LOCK(audio->lock); - const bool active = audio->phase == AUDIO_PHASE_BOUND || - audio->phase == AUDIO_PHASE_RELEASING; + const bool active = bindingPhaseActiveNL(audio); LG_UNLOCK(audio->lock); if (active) {