mirror of https://github.com/jetkvm/kvm.git
Fix critical deadlock when switching audio sources
Problem: When switching audio sources (USB to HDMI or vice versa), the application would hang indefinitely. This was caused by a deadlock between Go and C layers: 1. Main thread calls SetAudioOutputSource() → stopOutputAudio() 2. stopOutputAudio() calls outputRelay.Stop() which waits for goroutine 3. Goroutine is blocked in ReadMessage() holding Go mutex 4. ReadMessage() calls blocking C function jetkvm_audio_read_encode() 5. C function is blocked reading from ALSA device 6. Disconnect() can't acquire Go mutex to clean up 7. Deadlock: Main thread waiting for goroutine, goroutine waiting for ALSA Solution: Release the Go mutex BEFORE calling blocking C functions in ReadMessage() and WriteMessage(). The C layer has its own pthread mutex protection and handles stop requests via atomic flags. This allows: - Disconnect() to acquire the mutex immediately - C layer to detect stop request and return quickly - Goroutines to exit cleanly - Audio source switching to work flawlessly Fixes: - internal/audio/cgo_source.go:ReadMessage() - Release mutex before C call - internal/audio/cgo_source.go:WriteMessage() - Release mutex before C call This fix eliminates the hang when switching between USB and HDMI audio sources.
This commit is contained in:
parent
a305217e04
commit
051950f220
|
|
@ -167,17 +167,21 @@ func (c *CgoSource) IsConnected() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CgoSource) ReadMessage() (uint8, []byte, error) {
|
func (c *CgoSource) ReadMessage() (uint8, []byte, error) {
|
||||||
|
// Check connection status with mutex
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if !c.connected {
|
if !c.connected {
|
||||||
|
c.mu.Unlock()
|
||||||
return 0, nil, fmt.Errorf("not connected")
|
return 0, nil, fmt.Errorf("not connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.direction != "output" {
|
if c.direction != "output" {
|
||||||
|
c.mu.Unlock()
|
||||||
return 0, nil, fmt.Errorf("ReadMessage only supported for output direction")
|
return 0, nil, fmt.Errorf("ReadMessage only supported for output direction")
|
||||||
}
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
// Call C function without holding mutex to avoid deadlock
|
||||||
|
// The C layer has its own locking and handles stop requests
|
||||||
opusSize := C.jetkvm_audio_read_encode(unsafe.Pointer(&c.opusBuf[0]))
|
opusSize := C.jetkvm_audio_read_encode(unsafe.Pointer(&c.opusBuf[0]))
|
||||||
if opusSize < 0 {
|
if opusSize < 0 {
|
||||||
return 0, nil, fmt.Errorf("jetkvm_audio_read_encode failed: %d", opusSize)
|
return 0, nil, fmt.Errorf("jetkvm_audio_read_encode failed: %d", opusSize)
|
||||||
|
|
@ -195,16 +199,18 @@ func (c *CgoSource) ReadMessage() (uint8, []byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CgoSource) WriteMessage(msgType uint8, payload []byte) error {
|
func (c *CgoSource) WriteMessage(msgType uint8, payload []byte) error {
|
||||||
|
// Check connection status and validate parameters with mutex
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if !c.connected {
|
if !c.connected {
|
||||||
|
c.mu.Unlock()
|
||||||
return fmt.Errorf("not connected")
|
return fmt.Errorf("not connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.direction != "input" {
|
if c.direction != "input" {
|
||||||
|
c.mu.Unlock()
|
||||||
return fmt.Errorf("WriteMessage only supported for input direction")
|
return fmt.Errorf("WriteMessage only supported for input direction")
|
||||||
}
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
if msgType != ipcMsgTypeOpus {
|
if msgType != ipcMsgTypeOpus {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -218,6 +224,8 @@ func (c *CgoSource) WriteMessage(msgType uint8, payload []byte) error {
|
||||||
return fmt.Errorf("opus packet too large: %d bytes (max 1500)", len(payload))
|
return fmt.Errorf("opus packet too large: %d bytes (max 1500)", len(payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call C function without holding mutex to avoid deadlock
|
||||||
|
// The C layer has its own locking and handles stop requests
|
||||||
rc := C.jetkvm_audio_decode_write(unsafe.Pointer(&payload[0]), C.int(len(payload)))
|
rc := C.jetkvm_audio_decode_write(unsafe.Pointer(&payload[0]), C.int(len(payload)))
|
||||||
if rc < 0 {
|
if rc < 0 {
|
||||||
return fmt.Errorf("jetkvm_audio_decode_write failed: %d", rc)
|
return fmt.Errorf("jetkvm_audio_decode_write failed: %d", rc)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue