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.
This commit is contained in:
Alex P 2025-11-10 16:53:25 +02:00
parent b9705f4bac
commit 9e95cc3f8a
2 changed files with 56 additions and 37 deletions

View File

@ -78,41 +78,31 @@ func startAudio() error {
return nil 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() { func stopAudio() {
audioMutex.Lock() audioMutex.Lock()
defer audioMutex.Unlock() outRelay := outputRelay
stopAudioLocked() 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() { func onWebRTCConnect() {
@ -169,8 +159,18 @@ func SetAudioOutputEnabled(enabled bool) error {
} }
} else { } else {
audioMutex.Lock() audioMutex.Lock()
stopOutputLocked() outRelay := outputRelay
outSource := outputSource
outputRelay = nil
outputSource = nil
audioMutex.Unlock() audioMutex.Unlock()
if outRelay != nil {
outRelay.Stop()
}
if outSource != nil {
outSource.Disconnect()
}
} }
return nil return nil
@ -188,8 +188,18 @@ func SetAudioInputEnabled(enabled bool) error {
} }
} else { } else {
audioMutex.Lock() audioMutex.Lock()
stopInputLocked() inRelay := inputRelay
inSource := inputSource
inputRelay = nil
inputSource = nil
audioMutex.Unlock() audioMutex.Unlock()
if inRelay != nil {
inRelay.Stop()
}
if inSource != nil {
inSource.Disconnect()
}
} }
return nil return nil

View File

@ -894,11 +894,20 @@ func rpcGetUsbDevices() (usbgadget.Devices, error) {
func updateUsbRelatedConfig(wasAudioEnabled bool) error { func updateUsbRelatedConfig(wasAudioEnabled bool) error {
ensureConfigLoaded() ensureConfigLoaded()
// Stop input audio before USB reconfiguration (input uses USB)
audioMutex.Lock() audioMutex.Lock()
stopInputLocked() inRelay := inputRelay
inSource := inputSource
inputRelay = nil
inputSource = nil
audioMutex.Unlock() audioMutex.Unlock()
if inRelay != nil {
inRelay.Stop()
}
if inSource != nil {
inSource.Disconnect()
}
if err := gadget.UpdateGadgetConfig(); err != nil { if err := gadget.UpdateGadgetConfig(); err != nil {
return fmt.Errorf("failed to write gadget config: %w", err) return fmt.Errorf("failed to write gadget config: %w", err)
} }