From 118799d112e60b4fdeea3a257f6e40cd66855fb4 Mon Sep 17 00:00:00 2001 From: "Brandon McGinty (chatgpt)" Date: Mon, 10 Aug 2026 00:18:42 -0400 Subject: [PATCH] Serialize configuration updates and writes --- config/user_config.go | 30 ++++++++++++++++++++++++++++-- config/user_config_save_test.go | 18 ++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/config/user_config.go b/config/user_config.go index 225d20a..0ebb63a 100644 --- a/config/user_config.go +++ b/config/user_config.go @@ -10,9 +10,11 @@ import ( "os" "strconv" "strings" + "sync" ) type Config struct { + mu sync.Mutex config *exportableConfig fn string } @@ -49,6 +51,12 @@ type eUser struct { // SaveConfig atomically replaces the persisted configuration. Errors are // returned so an unavailable directory cannot crash the client. func (c *Config) SaveConfig() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.saveConfigLocked() +} + +func (c *Config) saveConfigLocked() error { data, err := toml.Marshal(c.config) if err != nil { return err @@ -254,18 +262,24 @@ func (c *Config) findUser(address string, username string) *eUser { } func (c *Config) ToggleMute(u *gumble.User) { + c.mu.Lock() + defer c.mu.Unlock() j := c.findUser(u.GetClient().Config.Address, u.Name) j.LocallyMuted = !j.LocallyMuted u.SetLocallyMuted(j.LocallyMuted) - c.SaveConfig() + _ = c.saveConfigLocked() } func (c *Config) SetMicVolume(v float32) { + c.mu.Lock() + defer c.mu.Unlock() t := float32(v) c.config.MicVolume = &t } func (c *Config) GetMicVolume() float32 { + c.mu.Lock() + defer c.mu.Unlock() if c.config.MicVolume == nil { return 1.0 } @@ -308,6 +322,8 @@ func (c *Config) GetCertificate() *string { } func (c *Config) GetNoiseSuppressionEnabled() bool { + c.mu.Lock() + defer c.mu.Unlock() if c.config.NoiseSuppressionEnabled == nil { return false } @@ -315,11 +331,15 @@ func (c *Config) GetNoiseSuppressionEnabled() bool { } func (c *Config) SetNoiseSuppressionEnabled(enabled bool) { + c.mu.Lock() + defer c.mu.Unlock() c.config.NoiseSuppressionEnabled = &enabled - c.SaveConfig() + _ = c.saveConfigLocked() } func (c *Config) GetRecordingFormat() string { + c.mu.Lock() + defer c.mu.Unlock() if c.config.RecordingFormat == nil { return "flac" } @@ -327,6 +347,8 @@ func (c *Config) GetRecordingFormat() string { } func (c *Config) GetRecordingDirectory() string { + c.mu.Lock() + defer c.mu.Unlock() if c.config.RecordingDirectory == nil { return resolvePath("~/Audio") } @@ -334,6 +356,8 @@ func (c *Config) GetRecordingDirectory() string { } func (c *Config) UpdateUser(u *gumble.User) { + c.mu.Lock() + defer c.mu.Unlock() var j *eUser var uc *gumble.Client uc = u.GetClient() @@ -349,6 +373,8 @@ func (c *Config) UpdateUser(u *gumble.User) { } func (c *Config) UpdateConfig(u *gumble.User) { + c.mu.Lock() + defer c.mu.Unlock() var j *eUser j = c.findUser(u.GetClient().Config.Address, u.Name) j.Boost = u.Boost() diff --git a/config/user_config_save_test.go b/config/user_config_save_test.go index bcf65cd..4a1b8d5 100644 --- a/config/user_config_save_test.go +++ b/config/user_config_save_test.go @@ -2,6 +2,7 @@ package config import ( "path/filepath" + "sync" "testing" ) @@ -14,3 +15,20 @@ func TestSaveConfigReturnsWriteError(t *testing.T) { t.Fatal("expected configuration write error") } } + +func TestConcurrentConfigurationUpdatesAndWrites(t *testing.T) { + path := filepath.Join(t.TempDir(), "barnard.toml") + cfg := NewConfig(&path) + var wg sync.WaitGroup + for i := 0; i < 20; i++ { + wg.Add(1) + go func(enabled bool) { + defer wg.Done() + cfg.SetNoiseSuppressionEnabled(enabled) + if err := cfg.SaveConfig(); err != nil { + t.Errorf("SaveConfig: %v", err) + } + }(i%2 == 0) + } + wg.Wait() +}