Skip to content

Commit 8f4cc8a

Browse files
committed
fix: race conditions when updating config
1 parent 66cdfc5 commit 8f4cc8a

File tree

25 files changed

+152
-90
lines changed

25 files changed

+152
-90
lines changed

internal/config/config.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,30 @@ import (
55
"os"
66
"path/filepath"
77
"runtime"
8+
"sync/atomic"
89

910
"gabe565.com/utils/colorx"
1011
"gabe565.com/utils/slogx"
1112
"github.com/spf13/pflag"
1213
)
1314

1415
type Config struct {
15-
File string `toml:"-"`
16-
Flags *pflag.FlagSet `toml:"-"`
17-
Version string `toml:"-"`
18-
callbacks []func() `toml:"-"`
16+
File string
17+
Flags *pflag.FlagSet
18+
Version string
19+
callbacks []func()
20+
data atomic.Pointer[Data]
21+
}
22+
23+
func (conf *Config) Data() Data {
24+
d := conf.data.Load()
25+
if d != nil {
26+
return *d
27+
}
28+
return Data{}
29+
}
1930

31+
type Data struct {
2032
Title string `toml:"title" comment:"Tray title."`
2133
URL string `toml:"url" comment:"Nightscout URL. (required)"`
2234
Token string `toml:"token" comment:"Nightscout token. Using an access token is recommended instead of the API secret."`

internal/config/default.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
const SocketFormatCSV = "csv"
1515

1616
func New(opts ...Option) *Config {
17-
conf := &Config{
17+
data := &Data{
1818
Title: "Nightscout",
1919
Units: UnitMgdl,
2020
DynamicIcon: DynamicIcon{
@@ -49,11 +49,14 @@ func New(opts ...Option) *Config {
4949

5050
switch runtime.GOOS {
5151
case "darwin":
52-
conf.DynamicIcon.Enabled = false
52+
data.DynamicIcon.Enabled = false
5353
case "windows":
54-
conf.DynamicIcon.FontColor = colorx.Hex{Color: color.Black}
54+
data.DynamicIcon.FontColor = colorx.Hex{Color: color.Black}
5555
}
5656

57+
conf := &Config{}
58+
conf.data.Store(data)
59+
5760
conf.Flags = flag.NewFlagSet("", flag.ContinueOnError)
5861
conf.RegisterFlags()
5962

internal/config/load.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,23 @@ func (conf *Config) Load() error {
5757
return err
5858
}
5959

60-
if err := k.UnmarshalWithConf("", &conf, koanf.UnmarshalConf{Tag: "toml"}); err != nil {
60+
data := conf.Data()
61+
62+
if err := k.UnmarshalWithConf("", &data, koanf.UnmarshalConf{Tag: "toml"}); err != nil {
6163
return err
6264
}
6365

64-
if err := conf.Write(); err != nil {
66+
if err := conf.Write(data); err != nil {
6567
return err
6668
}
6769

6870
conf.InitLog(os.Stderr)
71+
6972
slog.Info("Loaded config", "file", conf.File)
7073
return nil
7174
}
7275

73-
func (conf *Config) Write() error {
76+
func (conf *Config) Write(data Data) error {
7477
// Find config file
7578
if conf.File == "" {
7679
cfgDir, err := GetDir()
@@ -92,7 +95,7 @@ func (conf *Config) Write() error {
9295
}
9396
}
9497

95-
newCfg, err := toml.Marshal(conf)
98+
newCfg, err := toml.Marshal(&data)
9699
if err != nil {
97100
return err
98101
}
@@ -114,6 +117,7 @@ func (conf *Config) Write() error {
114117
}
115118
}
116119

120+
conf.data.Store(&data)
117121
return nil
118122
}
119123

internal/config/log.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import (
1212
)
1313

1414
func (conf *Config) InitLog(w io.Writer) {
15-
InitLog(w, conf.Log.Level, conf.Log.Format)
15+
data := conf.Data()
16+
InitLog(w, data.Log.Level, data.Log.Format)
1617
}
1718

1819
func InitLog(w io.Writer, level slogx.Level, format slogx.Format) {

internal/config/options.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@ func WithVersion(version string) Option {
77
conf.Version = version
88
}
99
}
10+
11+
func WithData(data Data) Option {
12+
return func(conf *Config) {
13+
conf.data.Store(&data)
14+
}
15+
}

internal/dynamicicon/dynamicicon.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@ func (d *DynamicIcon) Generate(p *nightscout.Properties) ([]byte, error) {
4747
d.mu.Lock()
4848
defer d.mu.Unlock()
4949

50+
data := d.config.Data()
51+
5052
if d.font == nil {
5153
var b []byte
52-
if d.config.DynamicIcon.FontFile == "" {
54+
if data.DynamicIcon.FontFile == "" {
5355
b = defaultFont
5456
} else {
55-
path := util.ResolvePath(d.config.DynamicIcon.FontFile)
57+
path := util.ResolvePath(data.DynamicIcon.FontFile)
5658

5759
if !filepath.IsAbs(path) {
5860
dir, err := config.GetDir()
@@ -69,7 +71,7 @@ func (d *DynamicIcon) Generate(p *nightscout.Properties) ([]byte, error) {
6971
return nil, err
7072
}
7173

72-
path, findErr := findfont.Find(d.config.DynamicIcon.FontFile)
74+
path, findErr := findfont.Find(data.DynamicIcon.FontFile)
7375
if findErr != nil {
7476
return nil, errors.Join(err, findErr)
7577
}
@@ -89,7 +91,7 @@ func (d *DynamicIcon) Generate(p *nightscout.Properties) ([]byte, error) {
8991
}
9092

9193
start := time.Now()
92-
bgnow := p.Bgnow.DisplayBg(d.config.Units)
94+
bgnow := p.Bgnow.DisplayBg(data.Units)
9395

9496
var face font.Face
9597
defer func() {
@@ -101,10 +103,10 @@ func (d *DynamicIcon) Generate(p *nightscout.Properties) ([]byte, error) {
101103
img := image.NewRGBA(image.Rect(0, 0, width, height))
102104
drawer := &font.Drawer{
103105
Dst: img,
104-
Src: image.NewUniform(d.config.DynamicIcon.FontColor),
106+
Src: image.NewUniform(data.DynamicIcon.FontColor),
105107
}
106108

107-
fontSize := d.config.DynamicIcon.MaxFontSize * 2
109+
fontSize := data.DynamicIcon.MaxFontSize * 2
108110
for {
109111
var err error
110112
if face, err = opentype.NewFace(d.font, &opentype.FaceOptions{

internal/fetch/fetch.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"net/http"
1313
"net/url"
1414
"path"
15+
"sync"
1516
"time"
1617

1718
"gabe565.com/nightscout-menu-bar/internal/config"
@@ -36,6 +37,7 @@ func NewFetch(conf *config.Config) *Fetch {
3637
}
3738

3839
type Fetch struct {
40+
mu sync.Mutex
3941
config *config.Config
4042
client *http.Client
4143
url string
@@ -44,10 +46,13 @@ type Fetch struct {
4446
}
4547

4648
func (f *Fetch) Do(ctx context.Context) (*nightscout.Properties, error) {
49+
f.mu.Lock()
50+
defer f.mu.Unlock()
51+
4752
start := time.Now()
4853

4954
if f.url == "" {
50-
if err := f.UpdateURL(); err != nil {
55+
if err := f.updateURLLocked(); err != nil {
5156
return nil, err
5257
}
5358
}
@@ -101,7 +106,15 @@ func (f *Fetch) Do(ctx context.Context) (*nightscout.Properties, error) {
101106
}
102107

103108
func (f *Fetch) UpdateURL() error {
104-
u, err := BuildURL(f.config)
109+
f.mu.Lock()
110+
defer f.mu.Unlock()
111+
return f.updateURLLocked()
112+
}
113+
114+
func (f *Fetch) updateURLLocked() error {
115+
data := f.config.Data()
116+
117+
u, err := BuildURL(data)
105118
if err != nil {
106119
return err
107120
}
@@ -110,7 +123,7 @@ func (f *Fetch) UpdateURL() error {
110123
f.url = u.String()
111124
slog.Debug("Generated URL", "value", f.url)
112125

113-
if token := f.config.Token; token != "" {
126+
if token := data.Token; token != "" {
114127
rawChecksum := sha1.Sum([]byte(token)) //nolint:gosec
115128
f.tokenChecksum = hex.EncodeToString(rawChecksum[:])
116129
slog.Debug("Generated token checksum", "value", f.tokenChecksum)
@@ -122,21 +135,24 @@ func (f *Fetch) UpdateURL() error {
122135
}
123136

124137
func (f *Fetch) Reset() {
138+
f.mu.Lock()
139+
defer f.mu.Unlock()
140+
125141
slog.Debug("Resetting fetch cache")
126142
f.url = ""
127143
f.tokenChecksum = ""
128144
f.etag = ""
129145
}
130146

131-
func BuildURL(conf *config.Config) (*url.URL, error) {
147+
func BuildURL(conf config.Data) (*url.URL, error) {
132148
if conf.URL == "" {
133149
return nil, ErrNoURL
134150
}
135151

136152
return url.Parse(conf.URL)
137153
}
138154

139-
func BuildURLWithToken(conf *config.Config) (*url.URL, error) {
155+
func BuildURLWithToken(conf config.Data) (*url.URL, error) {
140156
u, err := BuildURL(conf)
141157
if err != nil {
142158
return u, err

internal/fetch/fetch_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,26 @@ func TestFetch_Do(t *testing.T) {
6464
},
6565
{
6666
"success",
67-
fields{config: &config.Config{URL: server.URL}},
67+
fields{config: config.New(config.WithData(config.Data{URL: server.URL}))},
6868
args{t.Context()},
6969
testproperties.Properties,
7070
testproperties.Etag,
7171
require.NoError,
7272
},
7373
{
7474
"same etag",
75-
fields{config: &config.Config{URL: server.URL}, etag: testproperties.Etag},
75+
fields{config: config.New(config.WithData(config.Data{URL: server.URL})), etag: testproperties.Etag},
7676
args{t.Context()},
7777
nil,
7878
testproperties.Etag,
7979
require.Error,
8080
},
8181
{
8282
"different etag",
83-
fields{config: &config.Config{URL: server.URL}, etag: etag.Generate([]byte("test"), true)},
83+
fields{
84+
config: config.New(config.WithData(config.Data{URL: server.URL})),
85+
etag: etag.Generate([]byte("test"), true),
86+
},
8487
args{t.Context()},
8588
testproperties.Properties,
8689
testproperties.Etag,

internal/generate/config_example/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ func main() {
1818
func createConfig() error {
1919
conf := config.New()
2020
conf.InitLog(os.Stderr)
21+
data := conf.Data()
2122

2223
f, err := os.Create("config_example.toml")
2324
if err != nil {
2425
return err
2526
}
2627

27-
if err := toml.NewEncoder(f).Encode(conf); err != nil {
28+
if err := toml.NewEncoder(f).Encode(&data); err != nil {
2829
return err
2930
}
3031

internal/nightscout/properties.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ type Properties struct {
1111
Direction Direction `json:"direction"`
1212
}
1313

14-
func (p Properties) String(conf *config.Config) string {
15-
result := p.Bgnow.DisplayBg(conf.Units) +
16-
" " + p.Bgnow.Arrow(conf.Arrows)
17-
if delta := p.Delta.Display(conf.Units); delta != "" {
18-
result += " " + p.Delta.Display(conf.Units)
14+
func (p Properties) String(data config.Data) string {
15+
result := p.Bgnow.DisplayBg(data.Units) +
16+
" " + p.Bgnow.Arrow(data.Arrows)
17+
if delta := p.Delta.Display(data.Units); delta != "" {
18+
result += " " + p.Delta.Display(data.Units)
1919
}
20-
if rel := p.Bgnow.Mills.Relative(conf.Advanced.RoundAge); rel != "" {
21-
result += " [" + p.Bgnow.Mills.Relative(conf.Advanced.RoundAge) + "]"
20+
if rel := p.Bgnow.Mills.Relative(data.Advanced.RoundAge); rel != "" {
21+
result += " [" + p.Bgnow.Mills.Relative(data.Advanced.RoundAge) + "]"
2222
}
2323
return result
2424
}

0 commit comments

Comments
 (0)