fix: add synchronization to User audio fields to prevent data races
User.AudioSource, Boost, Volume, and LocallyMuted were accessed from both the OnAudioStream audio goroutine and the UI goroutine without synchronization, a data race under the Go memory model. Replace direct field access with thread-safe getter/setter methods protected by a per-user mutex: - SetAudioSource/GetAudioSource for the OpenAL source pointer - SetBoost/Boost for the audio boost multiplier - SetVolume/Volume for the volume level - SetLocallyMuted/LocallyMuted for the local mute state Update all call sites across config/, barnard.go, client.go, ui_tree.go, and stream.go.
This commit is contained in:
committed by
Brandon McGinty
parent
1bdd7ac52e
commit
9dd0137975
+68
-7
@@ -1,6 +1,8 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -31,9 +33,6 @@ type User struct {
|
||||
PrioritySpeaker bool
|
||||
// Is the user recording audio?
|
||||
Recording bool
|
||||
// Has the user been locally muted by the client?
|
||||
LocallyMuted bool
|
||||
|
||||
// The user's comment. Contains the empty string if the user does not have a
|
||||
// comment, or if the comment needs to be requested.
|
||||
Comment string
|
||||
@@ -58,14 +57,76 @@ type User struct {
|
||||
audioSequence int64
|
||||
audioSequenceValid bool
|
||||
|
||||
AudioSource *openal.Source
|
||||
Boost uint16
|
||||
Volume float32
|
||||
// audioMu protects audio-related fields accessed from both the
|
||||
// audio processing goroutine (OnAudioStream) and the UI goroutine.
|
||||
audioMu sync.Mutex
|
||||
audioSource *openal.Source
|
||||
boost uint16
|
||||
volume float32
|
||||
locallyMuted bool
|
||||
}
|
||||
|
||||
// SetAudioSource sets the user's OpenAL audio source (thread-safe).
|
||||
func (u *User) SetAudioSource(src *openal.Source) {
|
||||
u.audioMu.Lock()
|
||||
u.audioSource = src
|
||||
u.audioMu.Unlock()
|
||||
}
|
||||
|
||||
// AudioSource returns the user's OpenAL audio source (thread-safe).
|
||||
// The caller must not retain the pointer across unlock boundaries;
|
||||
// it is only valid while the caller ensures the source is not deleted.
|
||||
func (u *User) AudioSource() *openal.Source {
|
||||
u.audioMu.Lock()
|
||||
defer u.audioMu.Unlock()
|
||||
return u.audioSource
|
||||
}
|
||||
|
||||
// SetBoost sets the user's audio boost multiplier (thread-safe).
|
||||
func (u *User) SetBoost(b uint16) {
|
||||
u.audioMu.Lock()
|
||||
u.boost = b
|
||||
u.audioMu.Unlock()
|
||||
}
|
||||
|
||||
// Boost returns the user's audio boost multiplier (thread-safe).
|
||||
func (u *User) Boost() uint16 {
|
||||
u.audioMu.Lock()
|
||||
defer u.audioMu.Unlock()
|
||||
return u.boost
|
||||
}
|
||||
|
||||
// SetVolume sets the user's volume level (thread-safe).
|
||||
func (u *User) SetVolume(v float32) {
|
||||
u.audioMu.Lock()
|
||||
u.volume = v
|
||||
u.audioMu.Unlock()
|
||||
}
|
||||
|
||||
// Volume returns the user's volume level (thread-safe).
|
||||
func (u *User) Volume() float32 {
|
||||
u.audioMu.Lock()
|
||||
defer u.audioMu.Unlock()
|
||||
return u.volume
|
||||
}
|
||||
|
||||
// SetLocallyMuted sets whether the user is locally muted (thread-safe).
|
||||
func (u *User) SetLocallyMuted(m bool) {
|
||||
u.audioMu.Lock()
|
||||
u.locallyMuted = m
|
||||
u.audioMu.Unlock()
|
||||
}
|
||||
|
||||
// LocallyMuted returns whether the user is locally muted (thread-safe).
|
||||
func (u *User) LocallyMuted() bool {
|
||||
u.audioMu.Lock()
|
||||
defer u.audioMu.Unlock()
|
||||
return u.locallyMuted
|
||||
}
|
||||
|
||||
// IsMuted returns true if the user is muted either server-side or locally
|
||||
func (u *User) IsMuted() bool {
|
||||
return u.Muted || u.LocallyMuted
|
||||
return u.Muted || u.LocallyMuted()
|
||||
}
|
||||
|
||||
func (u *User) GetClient() *Client {
|
||||
|
||||
@@ -236,13 +236,13 @@ func (s *Stream) SetMicVolume(change float32, relative bool) {
|
||||
func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
go func(e *gumble.AudioStreamEvent) {
|
||||
var source = openal.NewSource()
|
||||
e.User.AudioSource = &source
|
||||
e.User.SetAudioSource(&source)
|
||||
|
||||
// Set initial gain based on volume and mute state
|
||||
if e.User.LocallyMuted {
|
||||
e.User.AudioSource.SetGain(0)
|
||||
if e.User.LocallyMuted() {
|
||||
source.SetGain(0)
|
||||
} else {
|
||||
e.User.AudioSource.SetGain(e.User.Volume)
|
||||
source.SetGain(e.User.Volume())
|
||||
}
|
||||
|
||||
bufferCount := e.Client.Config.Buffers
|
||||
@@ -263,17 +263,17 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
|
||||
for packet := range e.C {
|
||||
// Skip processing if user is locally muted
|
||||
if e.User.LocallyMuted {
|
||||
if e.User.LocallyMuted() {
|
||||
continue
|
||||
}
|
||||
|
||||
var boost uint16 = uint16(1)
|
||||
samples := len(packet.AudioBuffer)
|
||||
if samples > cap(raw)/2 {
|
||||
continue
|
||||
}
|
||||
|
||||
boost = e.User.Boost
|
||||
boost := e.User.Boost()
|
||||
userVolume := e.User.Volume()
|
||||
recorder := s.getRecorder()
|
||||
var recordBuffer []int16
|
||||
recordPtr := 0
|
||||
@@ -306,7 +306,7 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
}
|
||||
}
|
||||
if recorder != nil {
|
||||
recordBuffer[recordPtr] = scaleForRecording(sample, e.User.Volume)
|
||||
recordBuffer[recordPtr] = scaleForRecording(sample, userVolume)
|
||||
recordPtr++
|
||||
}
|
||||
binary.LittleEndian.PutUint16(raw[rawPtr:], uint16(sample))
|
||||
@@ -325,7 +325,7 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
}
|
||||
}
|
||||
if recorder != nil {
|
||||
recordBuffer[recordPtr] = scaleForRecording(sample, e.User.Volume)
|
||||
recordBuffer[recordPtr] = scaleForRecording(sample, userVolume)
|
||||
recordPtr++
|
||||
}
|
||||
binary.LittleEndian.PutUint16(raw[rawPtr:], uint16(sample))
|
||||
@@ -346,7 +346,7 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
}
|
||||
}
|
||||
if recorder != nil {
|
||||
recordSample := scaleForRecording(sample, e.User.Volume)
|
||||
recordSample := scaleForRecording(sample, userVolume)
|
||||
recordBuffer[recordPtr] = recordSample
|
||||
recordBuffer[recordPtr+1] = recordSample
|
||||
recordPtr += 2
|
||||
|
||||
Reference in New Issue
Block a user