From a741f0582995fc4fb5bb6bb8bad492b8f9efcf99 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 3 Sep 2025 17:08:19 +0000 Subject: [PATCH] fix(audio): add proper locking for config cache updates Add mutex locking around config cache expiration checks to prevent race conditions. The cache now properly checks initialization status before attempting updates. --- internal/audio/cgo_audio.go | 70 +++++++++++++++++++++++++++++++++---- internal/audio/input_ipc.go | 10 +++++- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/internal/audio/cgo_audio.go b/internal/audio/cgo_audio.go index 997294b2..35187858 100644 --- a/internal/audio/cgo_audio.go +++ b/internal/audio/cgo_audio.go @@ -865,7 +865,15 @@ func cgoAudioReadEncode(buf []byte) (int, error) { // Fast path: Use AudioConfigCache to avoid GetConfig() in hot path cache := GetCachedConfig() // Only update cache if expired - avoid unnecessary overhead - if time.Since(cache.lastUpdate) > cache.cacheExpiry { + // Use proper locking to avoid race condition + if cache.initialized.Load() { + cache.mutex.RLock() + cacheExpired := time.Since(cache.lastUpdate) > cache.cacheExpiry + cache.mutex.RUnlock() + if cacheExpired { + cache.Update() + } + } else { cache.Update() } @@ -933,7 +941,15 @@ func cgoAudioDecodeWrite(buf []byte) (n int, err error) { // Fast validation with AudioConfigCache cache := GetCachedConfig() // Only update cache if expired - avoid unnecessary overhead - if time.Since(cache.lastUpdate) > cache.cacheExpiry { + // Use proper locking to avoid race condition + if cache.initialized.Load() { + cache.mutex.RLock() + cacheExpired := time.Since(cache.lastUpdate) > cache.cacheExpiry + cache.mutex.RUnlock() + if cacheExpired { + cache.Update() + } + } else { cache.Update() } @@ -1092,7 +1108,15 @@ func ReadEncodeWithPooledBuffer() ([]byte, int, error) { // Get cached config cache := GetCachedConfig() // Only update cache if expired - avoid unnecessary overhead - if time.Since(cache.lastUpdate) > cache.cacheExpiry { + // Use proper locking to avoid race condition + if cache.initialized.Load() { + cache.mutex.RLock() + cacheExpired := time.Since(cache.lastUpdate) > cache.cacheExpiry + cache.mutex.RUnlock() + if cacheExpired { + cache.Update() + } + } else { cache.Update() } @@ -1131,7 +1155,15 @@ func DecodeWriteWithPooledBuffer(data []byte) (int, error) { // Get cached config cache := GetCachedConfig() // Only update cache if expired - avoid unnecessary overhead - if time.Since(cache.lastUpdate) > cache.cacheExpiry { + // Use proper locking to avoid race condition + if cache.initialized.Load() { + cache.mutex.RLock() + cacheExpired := time.Since(cache.lastUpdate) > cache.cacheExpiry + cache.mutex.RUnlock() + if cacheExpired { + cache.Update() + } + } else { cache.Update() } @@ -1158,7 +1190,15 @@ func BatchReadEncode(batchSize int) ([][]byte, error) { // Get cached config cache := GetCachedConfig() // Only update cache if expired - avoid unnecessary overhead - if time.Since(cache.lastUpdate) > cache.cacheExpiry { + // Use proper locking to avoid race condition + if cache.initialized.Load() { + cache.mutex.RLock() + cacheExpired := time.Since(cache.lastUpdate) > cache.cacheExpiry + cache.mutex.RUnlock() + if cacheExpired { + cache.Update() + } + } else { cache.Update() } @@ -1237,7 +1277,15 @@ func BatchDecodeWrite(frames [][]byte) error { // Get cached config cache := GetCachedConfig() // Only update cache if expired - avoid unnecessary overhead - if time.Since(cache.lastUpdate) > cache.cacheExpiry { + // Use proper locking to avoid race condition + if cache.initialized.Load() { + cache.mutex.RLock() + cacheExpired := time.Since(cache.lastUpdate) > cache.cacheExpiry + cache.mutex.RUnlock() + if cacheExpired { + cache.Update() + } + } else { cache.Update() } @@ -1312,7 +1360,15 @@ func cgoAudioDecodeWriteWithBuffers(opusData []byte, pcmBuffer []byte) (int, err // Get cached config cache := GetCachedConfig() // Only update cache if expired - avoid unnecessary overhead - if time.Since(cache.lastUpdate) > cache.cacheExpiry { + // Use proper locking to avoid race condition + if cache.initialized.Load() { + cache.mutex.RLock() + cacheExpired := time.Since(cache.lastUpdate) > cache.cacheExpiry + cache.mutex.RUnlock() + if cacheExpired { + cache.Update() + } + } else { cache.Update() } diff --git a/internal/audio/input_ipc.go b/internal/audio/input_ipc.go index 5ceb078d..6a42998f 100644 --- a/internal/audio/input_ipc.go +++ b/internal/audio/input_ipc.go @@ -514,7 +514,15 @@ func (ais *AudioInputServer) processOpusFrame(data []byte) error { // Get cached config for optimal performance cache := GetCachedConfig() // Only update cache if expired - avoid unnecessary overhead - if time.Since(cache.lastUpdate) > cache.cacheExpiry { + // Use proper locking to avoid race condition + if cache.initialized.Load() { + cache.mutex.RLock() + cacheExpired := time.Since(cache.lastUpdate) > cache.cacheExpiry + cache.mutex.RUnlock() + if cacheExpired { + cache.Update() + } + } else { cache.Update() }