Skip to content

Commit b9cc4ca

Browse files
aksOpsclaude
andcommitted
fix(sysmon): guard Monitor sampling with a mutex for the race detector
The TUI shares a single *sysmon.Monitor across bubbletea's value-copied Model and samples it from tick-driven Update handling, so the CPU-rate state (prevCPUUsec/prevAt/havePrev) was read and written from more than one goroutine over the program's lifetime. `go test -race` flagged it in the new CI race job. Serialize Sample() with a mutex. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4bfbb6a commit b9cc4ca

1 file changed

Lines changed: 8 additions & 0 deletions

File tree

internal/sysmon/sysmon.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"runtime"
1414
"strconv"
1515
"strings"
16+
"sync"
1617
"syscall"
1718
"time"
1819
)
@@ -42,6 +43,11 @@ type Monitor struct {
4243
diskPath string // filesystem to statfs for the disk gauge
4344
numCPU int // host CPU count, used when no CPU quota is set
4445

46+
// mu guards the CPU rate state below. The TUI shares a single *Monitor across
47+
// bubbletea's value-copied Model, and Sample() runs from tick-driven Update
48+
// handling, so the prev* fields are read/written from more than one goroutine
49+
// over the program's life — the lock makes that access data-race-free.
50+
mu sync.Mutex
4551
prevCPUUsec int64 // cgroup cpu usage (microseconds) at last sample
4652
prevAt time.Time // wall clock at last sample
4753
havePrev bool
@@ -61,6 +67,8 @@ func New(diskPath string) *Monitor {
6167
// Sample reads the current CPU/memory/disk usage. CPU is the average over the
6268
// interval since the previous Sample; the first call returns CPU.Known=false.
6369
func (m *Monitor) Sample() Snapshot {
70+
m.mu.Lock()
71+
defer m.mu.Unlock()
6472
now := time.Now()
6573
var s Snapshot
6674
s.Mem = m.memory()

0 commit comments

Comments
 (0)