From e8e9b36680716111a39e365d1493ae08184c11af Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Sun, 19 Apr 2026 22:13:09 -0700 Subject: [PATCH] fix(pb): make disableProgress race-free with sync/atomic.Bool internal/pb package used an unprotected package-level `disableProgress` bool that's read by `(*ProgressBar).Add` from the concurrent pull/push worker goroutines while `SetDisableProgress` flips it on startup. `go test -race` reports the expected data race, and the Go memory model leaves the behaviour undefined in practice. Swap the bool for an atomic.Bool. Store from SetDisableProgress, Load from Add. No public-API change - callers continue to pass plain bool to SetDisableProgress. Fixes #493 Signed-off-by: SAY-5 --- internal/pb/pb.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/pb/pb.go b/internal/pb/pb.go index 8303a968..eafea9cd 100644 --- a/internal/pb/pb.go +++ b/internal/pb/pb.go @@ -21,6 +21,7 @@ import ( "io" "os" "sync" + "sync/atomic" "time" humanize "github.com/dustin/go-humanize" @@ -30,13 +31,16 @@ import ( ) var ( - // disableProgress is the flag to disable progress bar. - disableProgress bool + // disableProgress is the flag to disable progress bar. Read from the + // progress-bar hot path by concurrent pull/push workers while a single + // goroutine may flip it via SetDisableProgress, so the access is guarded + // by sync/atomic to stay race-free under `go test -race`. + disableProgress atomic.Bool ) // SetDisableProgress disables the progress bar. func SetDisableProgress(disable bool) { - disableProgress = disable + disableProgress.Store(disable) } // NormalizePrompt normalizes the prompt string. @@ -85,7 +89,7 @@ func NewProgressBar(writers ...io.Writer) *ProgressBar { // Add adds a new progress bar. func (p *ProgressBar) Add(prompt, name string, size int64, reader io.Reader) io.Reader { // Return the reader directly if progress is disabled. - if disableProgress { + if disableProgress.Load() { return reader }