yandex: fix 500 errors by waiting for uploads to complete before setting modtime

After PUTting a file to the upload URL, Yandex keeps the file locked for
writing until the upload operation finishes committing on the server. The
PUT returned before this happened, so the following SetModTime (and any
listing) raced the still-in-progress write and got spurious 500 Internal
Server Error responses.

Capture the operation_id returned with the upload URL and poll the
operation status until it reports success before returning, so the file
is fully committed before we touch it.
This commit is contained in:
Nick Craig-Wood
2026-06-18 17:48:28 +01:00
parent 3732e010e2
commit e3b8219a9e
2 changed files with 16 additions and 4 deletions
+4 -3
View File
@@ -52,9 +52,10 @@ type ResourceListResponse struct {
// AsyncInfo struct is returned by the API for various async operations.
type AsyncInfo struct {
HRef string `json:"href"`
Method string `json:"method"`
Templated bool `json:"templated"`
HRef string `json:"href"`
Method string `json:"method"`
Templated bool `json:"templated"`
OperationID string `json:"operation_id"`
}
// AsyncStatus is returned when requesting the status of an async operations. Possible values in-progress, success, failure
+12 -1
View File
@@ -577,7 +577,7 @@ func (f *Fs) waitForJob(ctx context.Context, location string) (err error) {
}
switch status.Status {
case "failure":
case "failure", "failed":
return fmt.Errorf("async operation returned %q", status.Status)
case "success":
return nil
@@ -1124,6 +1124,17 @@ func (o *Object) upload(ctx context.Context, in io.Reader, overwrite bool, mimeT
resp, err = o.fs.srv.Call(ctx, &opts)
return shouldRetry(ctx, resp, err)
})
if err != nil {
return err
}
// The PUT returns before Yandex has finished committing the file
// to disk - the file remains locked for writing until the upload
// operation reports success. Wait for it so that a following
// SetModTime (or list) doesn't race the write and get a 500.
if ur.OperationID != "" {
err = o.fs.waitForJob(ctx, rootURL+"/operations/"+ur.OperationID)
}
return err
}