From 9e95cc3f8ac840c1937c6f999841bfcd13fe12ae Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 10 Nov 2025 16:53:25 +0200 Subject: [PATCH] fix: prevent audio disconnect from blocking new WebRTC sessions When an old connection closed while a new one started, the audio cleanup would hold audioMutex for up to 37 seconds during CGO disconnect calls, blocking the new session from initializing. Use capture-clear-release pattern to minimize mutex hold time: - Capture references to sources/relays while holding mutex - Clear globals immediately so new sessions can proceed - Release mutex before calling blocking Stop/Disconnect operations This eliminates the 37-second hang during connection transitions. --- audio.go | 80 ++++++++++++++++++++++++++++++------------------------ jsonrpc.go | 13 +++++++-- 2 files changed, 56 insertions(+), 37 deletions(-) diff --git a/audio.go b/audio.go index 374a98b7..ae12be46 100644 --- a/audio.go +++ b/audio.go @@ -78,41 +78,31 @@ func startAudio() error { return nil } -// stopOutputLocked stops output audio (assumes mutex is held) -func stopOutputLocked() { - if outputRelay != nil { - outputRelay.Stop() - outputRelay = nil - } - if outputSource != nil { - outputSource.Disconnect() - outputSource = nil - } -} - -// stopInputLocked stops input audio (assumes mutex is held) -func stopInputLocked() { - if inputRelay != nil { - inputRelay.Stop() - inputRelay = nil - } - if inputSource != nil { - inputSource.Disconnect() - inputSource = nil - } -} - -// stopAudioLocked stops all audio (assumes mutex is held) -func stopAudioLocked() { - stopOutputLocked() - stopInputLocked() -} - -// stopAudio stops all audio func stopAudio() { audioMutex.Lock() - defer audioMutex.Unlock() - stopAudioLocked() + outRelay := outputRelay + outSource := outputSource + inRelay := inputRelay + inSource := inputSource + outputRelay = nil + outputSource = nil + inputRelay = nil + inputSource = nil + audioMutex.Unlock() + + // Disconnect outside mutex to avoid blocking new sessions during CGO calls + if outRelay != nil { + outRelay.Stop() + } + if outSource != nil { + outSource.Disconnect() + } + if inRelay != nil { + inRelay.Stop() + } + if inSource != nil { + inSource.Disconnect() + } } func onWebRTCConnect() { @@ -169,8 +159,18 @@ func SetAudioOutputEnabled(enabled bool) error { } } else { audioMutex.Lock() - stopOutputLocked() + outRelay := outputRelay + outSource := outputSource + outputRelay = nil + outputSource = nil audioMutex.Unlock() + + if outRelay != nil { + outRelay.Stop() + } + if outSource != nil { + outSource.Disconnect() + } } return nil @@ -188,8 +188,18 @@ func SetAudioInputEnabled(enabled bool) error { } } else { audioMutex.Lock() - stopInputLocked() + inRelay := inputRelay + inSource := inputSource + inputRelay = nil + inputSource = nil audioMutex.Unlock() + + if inRelay != nil { + inRelay.Stop() + } + if inSource != nil { + inSource.Disconnect() + } } return nil diff --git a/jsonrpc.go b/jsonrpc.go index 6abaa012..2ee0f5a8 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -894,11 +894,20 @@ func rpcGetUsbDevices() (usbgadget.Devices, error) { func updateUsbRelatedConfig(wasAudioEnabled bool) error { ensureConfigLoaded() - // Stop input audio before USB reconfiguration (input uses USB) audioMutex.Lock() - stopInputLocked() + inRelay := inputRelay + inSource := inputSource + inputRelay = nil + inputSource = nil audioMutex.Unlock() + if inRelay != nil { + inRelay.Stop() + } + if inSource != nil { + inSource.Disconnect() + } + if err := gadget.UpdateGadgetConfig(); err != nil { return fmt.Errorf("failed to write gadget config: %w", err) }