Skip to content

fix: guard Debug.proc against concurrent CPU-percent reads#8

Open
aguung wants to merge 1 commit into
enowdev:mainfrom
aguung:fix/debug-cpu-percent-race
Open

fix: guard Debug.proc against concurrent CPU-percent reads#8
aguung wants to merge 1 commit into
enowdev:mainfrom
aguung:fix/debug-cpu-percent-race

Conversation

@aguung

@aguung aguung commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • /api/debug's cpu_percent could report wildly inflated, nonsensical values (spotted in practice around 96.6%, peaking well over 100%) under normal usage.
  • Root cause: a data race in Debug.proc, a single shared *process.Process, caused by concurrent calls to Percent(0).
  • Fix: serialize access to proc with a mutex.

Problem

  • server/handlers/api_debug.go's Debug.Get calls h.proc.Percent(0) (gopsutil) to compute CPU%. Percent tracks the previous CPU-time reading as unsynchronized fields on the *process.Process itself (see gopsutil/v4/process/process.go's PercentWithContext) — there's no internal locking.
  • /api/debug is polled every 2s by the TopBar widget (web/src/os/SystemStats.tsx) in every open tab. With more than one tab open, or a response that overlaps the next 2s tick, two calls to Get can run concurrently and race on that shared state.
  • Reproduced directly: 20 goroutines concurrently hitting Debug.Get produced a cpu_percent up to 135758.9%, and go test -race flags a genuine data race at the exact read/write in PercentWithContext.

Solution

  • Add mu sync.Mutex to Debug and serialize the h.proc.Percent(0) / h.proc.MemoryInfo() calls inside Get with it. Both read/mutate state on the same *process.Process, so both are covered.

Changed files

File Change
server/handlers/api_debug.go Adds mu sync.Mutex to Debug; locks around the h.proc calls in Get.
server/handlers/api_debug_test.go New TestDebugConcurrentAccess — fires 20 goroutines × 20 concurrent calls to Get; meaningful under go test -race (fails with "WARNING: DATA RACE" before this fix, clean after).

Test plan

  • Before the fix: go test -race -run TestDebugConcurrentAccess ./server/handlers/... reliably reports a data race.
  • After the fix: same command passes cleanly, and 5 repeated plain (non-race) runs all pass.
  • go build ./... and go test ./... pass with no regressions (41 packages).
  • Manually verified against a running instance: 8 concurrent curl requests to /api/debug while idle all returned sane cpu_percent values (0–1.2%), no crash, no corrupted output.

gopsutil's Process.Percent tracks the previous CPU-time reading on the
*Process itself with no internal locking. /api/debug is polled every
2s by the TopBar widget in every open tab, so overlapping calls (e.g.
more than one tab open, or a slow response overlapping the next tick)
raced on that state and could report a corrupted, wildly inflated
cpu_percent (reproduced up to ~135000% under concurrent load; -race
confirms the data race). Serialize access to proc with a mutex.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant