-
Notifications
You must be signed in to change notification settings - Fork 29
feat: add dynamic timeout retry with file-size-based parameters #468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -57,11 +57,30 @@ type ProgressBar struct { | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| type progressBar struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| *mpbv8.Bar | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| size int64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| size int64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // msgMu guards msg, which is read by mpb's render goroutine (via the | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // prepend decorator) while being written by Placeholder/Complete from | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // transfer goroutines. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgMu sync.RWMutex | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| msg string | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| startTime time.Time | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // setMsg updates the bar's prepended message under lock. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (b *progressBar) setMsg(msg string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| b.msgMu.Lock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| b.msg = msg | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| b.msgMu.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // msgText returns the bar's prepended message under lock. It is called from | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // mpb's render goroutine, so it must never block on anything but msgMu. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (b *progressBar) msgText() string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| b.msgMu.RLock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer b.msgMu.RUnlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return b.msg | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // NewProgressBar creates a new progress bar. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| func NewProgressBar(writers ...io.Writer) *ProgressBar { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| opts := []mpbv8.ContainerOption{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -113,7 +132,7 @@ func (p *ProgressBar) Add(prompt, name string, size int64, reader io.Reader) io. | |||||||||||||||||||||||||||||||||||||||||||||||||||
| mpbv8.BarFillerOnComplete("|"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| mpbv8.PrependDecorators( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| decor.Any(func(s decor.Statistics) string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return newBar.msg | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return newBar.msgText() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, decor.WCSyncSpaceR), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| mpbv8.AppendDecorators( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -140,6 +159,28 @@ func (p *ProgressBar) Add(prompt, name string, size int64, reader io.Reader) io. | |||||||||||||||||||||||||||||||||||||||||||||||||||
| return reader | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Placeholder creates or resets a progress bar entry without a reader. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // It is used during retry backoff to keep a visible bar for the item. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (p *ProgressBar) Placeholder(name string, prompt string, size int64) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if disableProgress.Load() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.RLock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| existing := p.bars[name] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.RUnlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If the bar already exists, just reset its message. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if existing != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| existing.setMsg(fmt.Sprintf("%s %s", prompt, name)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| existing.Bar.SetCurrent(0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create a new placeholder bar. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.Add(prompt, name, size, nil) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+169
to
+181
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a data race condition here. The To fix this, you should use a write lock to protect both the read from the
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Get returns the progress bar. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (p *ProgressBar) Get(name string) *progressBar { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.RLock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -156,7 +197,7 @@ func (p *ProgressBar) Complete(name string, msg string) { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.RUnlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ok { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bar.msg = msg | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bar.setMsg(msg) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bar.Bar.SetCurrent(bar.size) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| /* | ||
| * Copyright 2024 The ModelPack Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package pb | ||
|
|
||
| import ( | ||
| "io" | ||
| "sync" | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| // TestProgressBarMsgConcurrency exercises concurrent message updates against | ||
| // the read path used by mpb's render goroutine. Placeholder and Complete are | ||
| // fired from transfer goroutines (e.g. on retry backoff) while the bar is being | ||
| // rendered, so progressBar.msg is read and written concurrently. Run with | ||
| // -race; before msg was guarded this reproduces the reported data race. | ||
| func TestProgressBarMsgConcurrency(t *testing.T) { | ||
| // Render to a discarded writer so the auto-refresh goroutine still runs the | ||
| // prepend decorator (which reads msg) without touching the test output. | ||
| pb := NewProgressBar(io.Discard) | ||
| pb.Start() | ||
|
|
||
| const name = "sha256:deadbeef" | ||
| pb.Add("Copying blob", name, 1024, nil) | ||
|
|
||
| var wg sync.WaitGroup | ||
| stop := make(chan struct{}) | ||
|
|
||
| // Writers: simulate retry backoff resets (Placeholder) and completion. | ||
| for i := 0; i < 4; i++ { | ||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
| for { | ||
| select { | ||
| case <-stop: | ||
| return | ||
| default: | ||
| } | ||
| pb.Placeholder(name, "Copying blob (retry)", 1024) | ||
| pb.Complete(name, "done") | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| // Readers: mirror the render goroutine's read of the message. | ||
| for i := 0; i < 4; i++ { | ||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
| for { | ||
| select { | ||
| case <-stop: | ||
| return | ||
| default: | ||
| } | ||
| if b := pb.Get(name); b != nil { | ||
| _ = b.msgText() | ||
| } | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| time.Sleep(150 * time.Millisecond) | ||
| close(stop) | ||
| wg.Wait() | ||
|
|
||
| // Leave the bar completed so Stop()'s mpb.Shutdown() does not block. | ||
| pb.Complete(name, "done") | ||
| pb.Stop() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
Placeholdermethod should encapsulate the full reset logic used during retries, including setting the refill value and resetting the EWMA speed calculation. This ensures consistency across different transfer paths and avoids manual bar manipulation in the backend packages.