-
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
4e65858
92c9ca3
cf1bcf2
be391fd
2fcdba2
4cdd8ae
5047acc
fcdbcb0
f155ccb
514f604
973f508
fc3ac13
4eaa8b4
4cb2b96
a31fbb9
7e3f3e8
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -136,6 +136,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 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.RLock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| existing := p.bars[name] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.RUnlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If the bar already exists, just reset its message. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if existing != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| existing.msg = 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
+146
to
+158
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() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,18 +19,20 @@ package backend | |
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "sync" | ||
| "time" | ||
|
|
||
| "github.com/bmatcuk/doublestar/v4" | ||
| legacymodelspec "github.com/dragonflyoss/model-spec/specs-go/v1" | ||
| modelspec "github.com/modelpack/model-spec/specs-go/v1" | ||
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" | ||
| "github.com/sirupsen/logrus" | ||
| "golang.org/x/sync/errgroup" | ||
|
|
||
| internalpb "github.com/modelpack/modctl/internal/pb" | ||
| "github.com/modelpack/modctl/pkg/backend/remote" | ||
| "github.com/modelpack/modctl/pkg/config" | ||
| "github.com/modelpack/modctl/pkg/retrypolicy" | ||
| ) | ||
|
|
||
| // Fetch fetches partial files to the output. | ||
|
|
@@ -74,10 +76,7 @@ func (b *backend) Fetch(ctx context.Context, target string, cfg *config.Fetch) e | |
| for _, layer := range manifest.Layers { | ||
| for _, pattern := range cfg.Patterns { | ||
| if anno := layer.Annotations; anno != nil { | ||
| path := anno[modelspec.AnnotationFilepath] | ||
| if path == "" { | ||
| path = anno[legacymodelspec.AnnotationFilepath] | ||
| } | ||
| path := getAnnotationFilepath(anno) | ||
| // Use doublestar.PathMatch for pattern matching to support ** recursive matching | ||
| // PathMatch uses the system's native path separator (like filepath.Match) while | ||
| // also supporting recursive patterns like **/*.json | ||
|
|
@@ -101,9 +100,12 @@ func (b *backend) Fetch(ctx context.Context, target string, cfg *config.Fetch) e | |
| pb.Start() | ||
| defer pb.Stop() | ||
|
|
||
| g, ctx := errgroup.WithContext(ctx) | ||
| g := new(errgroup.Group) | ||
| g.SetLimit(cfg.Concurrency) | ||
|
|
||
| var mu sync.Mutex | ||
| var errs []error | ||
|
|
||
| logrus.Infof("fetch: fetching %d matched layers", len(layers)) | ||
| for _, layer := range layers { | ||
| g.Go(func() error { | ||
|
|
@@ -113,17 +115,37 @@ func (b *backend) Fetch(ctx context.Context, target string, cfg *config.Fetch) e | |
| default: | ||
| } | ||
|
|
||
| annoFilepath := getAnnotationFilepath(layer.Annotations) | ||
|
|
||
| logrus.Debugf("fetch: processing layer %s", layer.Digest) | ||
| if err := pullAndExtractFromRemote(ctx, pb, internalpb.NormalizePrompt("Fetching blob"), client, cfg.Output, layer); err != nil { | ||
| return err | ||
| if err := retrypolicy.Do(ctx, func(rctx context.Context) error { | ||
| return pullAndExtractFromRemote(rctx, pb, internalpb.NormalizePrompt("Fetching blob"), client, cfg.Output, layer) | ||
| }, retrypolicy.DoOpts{ | ||
| FileSize: layer.Size, | ||
| FileName: annoFilepath, | ||
| OnRetry: func(attempt uint, reason string, backoff time.Duration) { | ||
| if bar := pb.Get(layer.Digest.String()); bar != nil { | ||
| bar.SetRefill(bar.Current()) | ||
| bar.SetCurrent(0) | ||
| bar.EwmaSetCurrent(0, time.Second) | ||
| } | ||
| }, | ||
|
Comment on lines
+126
to
+132
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. Use the OnRetry: func(attempt uint, reason string, backoff time.Duration) {
pb.Placeholder(layer.Digest.String(), internalpb.NormalizePrompt("Fetching blob"), layer.Size)
}, |
||
| }); err != nil { | ||
| mu.Lock() | ||
| errs = append(errs, err) | ||
| mu.Unlock() | ||
| } else { | ||
| logrus.Debugf("fetch: successfully processed layer %s", layer.Digest) | ||
| } | ||
|
|
||
| logrus.Debugf("fetch: successfully processed layer %s", layer.Digest) | ||
| return nil | ||
| }) | ||
| } | ||
|
|
||
| if err := g.Wait(); err != nil { | ||
| _ = g.Wait() | ||
| if ctx.Err() != nil { | ||
| return fmt.Errorf("fetch cancelled: %w", ctx.Err()) | ||
| } | ||
| if err := errors.Join(errs...); err != nil { | ||
| return err | ||
| } | ||
|
|
||
|
|
||
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.