Fix progress bar getting permanently stuck after an overshooting Add/Set call - #239
Open
dualfroz wants to merge 1 commit into
Open
Fix progress bar getting permanently stuck after an overshooting Add/Set call#239dualfroz wants to merge 1 commit into
dualfroz wants to merge 1 commit into
Conversation
Add64 checked currentNum > max only after already mutating currentNum and currentBytes, so a single call that overshoots max returned an error but left the bar stuck: currentNum frozen above max, percent stuck above 100%, and every later Add/Set call failing with the same error forever, since the top-of-function currentNum < max guard could never pass again. Check the bound before mutating state instead, so an overshooting call is rejected as a whole and leaves currentNum/currentBytes/currentPercent exactly as they were. The now-unreachable post-hoc check is removed. Add TestAddExceedsMaxDoesNotCorruptState covering the corrupted-state case and that the bar keeps accepting valid progress afterwards.
dualfroz
force-pushed
the
dualfroz/fix-add-exceeds-max-corruption
branch
from
September 5, 2026 22:44
05e18ff to
7408154
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Problem
Add64(progressbar.go:724, reached byAdd,Add64,SetandSet64) is meant to reject acall that would push the counter past
maxand leave the bar unchanged:The bounds check runs after
currentNum(andcurrentBytes) have already been mutated, so asingle call that overshoots
maxis not rejected atomically: the error is returned, but theinvalid, over-max state is kept. Once that happens the bar is stuck:
currentNumnever advancesagain, because the guard at the top of the function (
currentNum < max) is now permanently false,and
currentPercent/the saucer width stay frozen above 100%. Every subsequentAddorSetcall,even ones that would otherwise be perfectly valid, keeps returning "current number exceeds max"
forever, with no way to recover short of
Reset().Reproduced on a clean checkout with:
This is reachable through the public API with nothing exotic: any caller whose increments do not
sum to exactly
max(a very common case, e.g. an estimated total that is off by a few bytes, ora
Set()racing another goroutine'sAdd()) will hit this on the final update, at 95-105% of theway through, and never see the bar reach 100% or accept further progress again.
Fix
Move the bounds check before the mutation, so an overshooting call is rejected as a whole and
leaves
currentNum/currentBytes/currentPercentexactly as they were before the call:The
ignoreLengthmode is left out of the new guard on purpose: it advances with(currentNum + num) % max, which by construction wraps and never exceedsmax, so the checkwould never trigger there anyway; excluding it keeps that code path untouched. The old post-hoc
check at the end of the function is now unreachable in the non-ignoreLength path (the mutation it
used to guard can no longer produce an out-of-range value) and is removed rather than left as dead
code.
Behavior change worth flagging: previously, once
currentNumreached exactlymax, furtherAdd(n)calls withn > 0were silently absorbed with no error (currentNum simply stoppedadvancing, though currentBytes kept drifting upward unnoticed). With this fix such calls now
consistently return the same "current number exceeds max" error as any other overshoot, which
matches what the error message already claims and is the only existing test's expectation
(
TestBar, which only asserts that an error is returned, not any particular resulting state).Added
TestAddExceedsMaxDoesNotCorruptStatein progressbar_test.go, next to the existingTestBarovershoot test, asserting that a rejected
AddleavescurrentNum/currentPercentunchanged andthat the bar keeps accepting valid progress afterwards.
Verification
go build ./...: exit 0.go vet .(this package): exit 0.go vet ./...(whole repo) exits 1, but solely because of apre-existing, unrelated issue in
examples/download-unknown/main.go:14("using resp beforechecking for errors"), confirmed present before this change via
git stash; not touched here.gofmt -l .: no output (clean).go test ./... -count=1 -v: exit 0, 33 top-level PASS, 0 FAIL, 0 SKIP.Add64fix (keeping the new test) fails it withexpected currentNum to stay at 95 after a rejected Add, got 105, matching the reproductionabove; re-applying the fix restores a clean pass.