-
Notifications
You must be signed in to change notification settings - Fork 472
fix: resolve race condition in file state verification and improve error handling #501
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: master
Are you sure you want to change the base?
Changes from 1 commit
fc571f4
ce235f8
ff65c41
97ffb2e
d1cc3f3
b036ac5
d12ed8b
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 |
|---|---|---|
|
|
@@ -118,13 +118,15 @@ func (s *Server) getTagHandler(w http.ResponseWriter, r *http.Request) error { | |
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| d, err := s.tags.Get(tag) | ||
| if err == tagclient.ErrTagNotFound { | ||
| return handler.ErrorStatus(http.StatusNotFound) | ||
| } | ||
| if err != nil { | ||
| if err == tagclient.ErrTagNotFound { | ||
| return handler.ErrorStatus(http.StatusNotFound) | ||
| } | ||
| return handler.Errorf("get tag: %s", err) | ||
| } | ||
|
|
||
| io.WriteString(w, d.String()) | ||
| return nil | ||
| } | ||
|
|
@@ -139,23 +141,38 @@ func (s *Server) downloadBlobHandler(w http.ResponseWriter, r *http.Request) err | |
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| f, err := s.cads.Cache().GetFileReader(d.Hex()) | ||
| if err != nil { | ||
| if os.IsNotExist(err) || s.cads.InDownloadError(err) { | ||
| if err := s.sched.Download(namespace, d); err != nil { | ||
| if err == scheduler.ErrTorrentNotFound { | ||
| return handler.ErrorStatus(http.StatusNotFound) | ||
| } | ||
| return handler.Errorf("download torrent: %s", err) | ||
| } | ||
| f, err = s.cads.Cache().GetFileReader(d.Hex()) | ||
| if err != nil { | ||
| return handler.Errorf("store: %s", err) | ||
| } | ||
| } else { | ||
| return handler.Errorf("store: %s", err) | ||
|
|
||
| // Happy path: file already exists in cache | ||
|
Collaborator
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. Can we remove these comments that repeat the code logic? IMO they get stale with time and the cost of maintaining them is higher than the value they provide by clarifying the code. Were they written by AI and forgotten after? I think this happens a lot with AI code :D
Collaborator
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. Also love the nesting reduction here too! Code is much more readable like this
Collaborator
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. Removed |
||
| if err == nil { | ||
| if _, err := io.Copy(w, f); err != nil { | ||
| return fmt.Errorf("copy file: %s", err) | ||
|
hweawer marked this conversation as resolved.
Outdated
|
||
| } | ||
| return nil | ||
|
hweawer marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // If error is not recoverable, return error | ||
| if !os.IsNotExist(err) && !s.cads.InDownloadError(err) { | ||
| return handler.Errorf("store: %s", err) | ||
|
hweawer marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // File doesn't exist or is in wrong state, trigger P2P download | ||
| if err := s.sched.Download(namespace, d); err != nil { | ||
| if err == scheduler.ErrTorrentNotFound { | ||
| return handler.ErrorStatus(http.StatusNotFound) | ||
| } | ||
| return handler.Errorf("download torrent: %s", err) | ||
|
hweawer marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // Get file reader after download completes | ||
| // Use Any() to check both download and cache directories, as the file | ||
| // might still be in the process of being moved from download to cache. | ||
| f, err = s.cads.Any().GetFileReader(d.Hex()) | ||
|
Collaborator
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.
Collaborator
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.
But |
||
| if err != nil { | ||
| return handler.Errorf("store: %s", err) | ||
|
hweawer marked this conversation as resolved.
|
||
| } | ||
|
|
||
| if _, err := io.Copy(w, f); err != nil { | ||
| return fmt.Errorf("copy file: %s", err) | ||
|
hweawer marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,47 +40,111 @@ func NewReadOnlyTransferer( | |
| stats tally.Scope, | ||
| cads *store.CADownloadStore, | ||
| tags tagclient.Client, | ||
| sched scheduler.Scheduler) *ReadOnlyTransferer { | ||
|
|
||
| sched scheduler.Scheduler, | ||
| ) *ReadOnlyTransferer { | ||
| stats = stats.Tagged(map[string]string{ | ||
| "module": "rotransferer", | ||
| }) | ||
|
|
||
| return &ReadOnlyTransferer{stats, cads, tags, sched} | ||
| } | ||
|
|
||
| // mapSchedulerError converts scheduler errors to appropriate transferer errors. | ||
| func mapSchedulerError(err error, d core.Digest) error { | ||
| switch err { | ||
| case scheduler.ErrTorrentNotFound: | ||
| return ErrBlobNotFound{ | ||
| Digest: d.Hex(), | ||
| Reason: "torrent not found in tracker", | ||
| } | ||
| case scheduler.ErrTorrentTimeout: | ||
| return ErrBlobNotFound{ | ||
| Digest: d.Hex(), | ||
| Reason: "download timed out", | ||
| } | ||
| case scheduler.ErrTorrentRemoved: | ||
| return ErrBlobNotFound{ | ||
| Digest: d.Hex(), | ||
| Reason: "torrent was removed", | ||
| } | ||
| case scheduler.ErrSchedulerStopped: | ||
| return fmt.Errorf("scheduler unavailable: scheduler is stopped") | ||
| case scheduler.ErrSendEventTimedOut: | ||
| return fmt.Errorf("scheduler unavailable: event loop busy") | ||
| default: | ||
| return fmt.Errorf("scheduler download failed: %w", err) | ||
| } | ||
| } | ||
|
|
||
| // Stat returns blob info from local cache, and triggers download if the blob is | ||
| // not available locally. | ||
| func (t *ReadOnlyTransferer) Stat(namespace string, d core.Digest) (*core.BlobInfo, error) { | ||
| fi, err := t.cads.Cache().GetFileStat(d.Hex()) | ||
| if os.IsNotExist(err) || t.cads.InDownloadError(err) { | ||
| if err := t.sched.Download(namespace, d); err != nil { | ||
| return nil, fmt.Errorf("scheduler: %s", err) | ||
| } | ||
| fi, err = t.cads.Cache().GetFileStat(d.Hex()) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("stat cache: %s", err) | ||
|
|
||
| // Happy path: file already exists in cache | ||
|
Collaborator
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. Same note about these comments as above
Collaborator
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. Removed |
||
| if err == nil { | ||
| return core.NewBlobInfo(fi.Size()), nil | ||
| } | ||
|
|
||
| // If error is not recoverable, return error | ||
| if !os.IsNotExist(err) && !t.cads.InDownloadError(err) { | ||
| return nil, fmt.Errorf("stat cache: %w", err) | ||
| } | ||
|
|
||
| // File doesn't exist or is in wrong state, trigger P2P download | ||
| if err := t.sched.Download(namespace, d); err != nil { | ||
| return nil, mapSchedulerError(err, d) | ||
| } | ||
|
|
||
| // Stat file after download completes | ||
| // Use Any() to check both download and cache directories, as the file | ||
| // might still be in the process of being moved from download to cache. | ||
| fi, err = t.cads.Any().GetFileStat(d.Hex()) | ||
| if err == nil { | ||
| return core.NewBlobInfo(fi.Size()), nil | ||
| } | ||
| if os.IsNotExist(err) { | ||
| return nil, ErrBlobNotFound{ | ||
| Digest: d.Hex(), | ||
| Reason: "file not found after download", | ||
| } | ||
| } else if err != nil { | ||
| return nil, fmt.Errorf("stat cache: %s", err) | ||
| } | ||
| return core.NewBlobInfo(fi.Size()), nil | ||
| return nil, fmt.Errorf("stat cache after download: %w", err) | ||
| } | ||
|
|
||
| // Download downloads blobs as torrent. | ||
| func (t *ReadOnlyTransferer) Download(namespace string, d core.Digest) (store.FileReader, error) { | ||
| f, err := t.cads.Cache().GetFileReader(d.Hex()) | ||
| if os.IsNotExist(err) || t.cads.InDownloadError(err) { | ||
| if err := t.sched.Download(namespace, d); err != nil { | ||
| return nil, fmt.Errorf("scheduler: %s", err) | ||
| } | ||
| f, err = t.cads.Cache().GetFileReader(d.Hex()) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("cache: %s", err) | ||
|
|
||
| // Happy path: file already exists in cache | ||
| if err == nil { | ||
| return f, nil | ||
| } | ||
|
|
||
| // If error is not recoverable, return error | ||
| if !os.IsNotExist(err) && !t.cads.InDownloadError(err) { | ||
| return nil, fmt.Errorf("get cache file: %w", err) | ||
| } | ||
|
|
||
| // File doesn't exist or is in wrong state, trigger P2P download | ||
| if err := t.sched.Download(namespace, d); err != nil { | ||
| return nil, mapSchedulerError(err, d) | ||
| } | ||
|
|
||
| // Get file reader after download completes | ||
| // Use Any() to check both download and cache directories, as the file | ||
| // might still be in the process of being moved from download to cache. | ||
| f, err = t.cads.Any().GetFileReader(d.Hex()) | ||
| if err != nil { | ||
| if os.IsNotExist(err) { | ||
| return nil, ErrBlobNotFound{ | ||
| Digest: d.Hex(), | ||
| Reason: "file not found after download", | ||
|
hweawer marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
| } else if err != nil { | ||
| return nil, fmt.Errorf("cache: %s", err) | ||
| return nil, fmt.Errorf("get file reader after download: %w", err) | ||
| } | ||
|
|
||
| return f, nil | ||
| } | ||
|
|
||
|
|
@@ -92,15 +156,18 @@ func (t *ReadOnlyTransferer) Upload(namespace string, d core.Digest, blob store. | |
| // GetTag gets manifest digest for tag. | ||
| func (t *ReadOnlyTransferer) GetTag(tag string) (core.Digest, error) { | ||
| d, err := t.tags.Get(tag) | ||
| if err != nil { | ||
| if err == tagclient.ErrTagNotFound { | ||
| t.stats.Counter("tag_not_found").Inc(1) | ||
| return core.Digest{}, ErrTagNotFound | ||
| if err == nil { | ||
| return d, nil | ||
| } | ||
| if err == tagclient.ErrTagNotFound { | ||
| t.stats.Counter("tag_not_found").Inc(1) | ||
| return core.Digest{}, ErrTagNotFound{ | ||
| Tag: tag, | ||
| Reason: "not found in build-index", | ||
| } | ||
| t.stats.Counter("get_tag_error").Inc(1) | ||
| return core.Digest{}, fmt.Errorf("client get tag: %s", err) | ||
| } | ||
| return d, nil | ||
| t.stats.Counter("get_tag_error").Inc(1) | ||
| return core.Digest{}, fmt.Errorf("client get tag: %w", err) | ||
| } | ||
|
|
||
| // PutTag is not supported. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.