diff --git a/backend/azure/azure.go b/backend/azure/azure.go index 7806315c..ab30f292 100644 --- a/backend/azure/azure.go +++ b/backend/azure/azure.go @@ -1764,12 +1764,18 @@ func (az *Azure) ListMultipartUploads(ctx context.Context, input *s3.ListMultipa } } - // Sort once: Key asc, Initiated asc - sort.SliceStable(uploads, func(i, j int) bool { + // Sort once: Key asc, Initiated asc, UploadID asc + // UploadID is a tie-breaker for uploads that share both key and + // initiated time, since azure reports the same identical creation + // time for uploads created back to back. + sort.Slice(uploads, func(i, j int) bool { if uploads[i].Key != uploads[j].Key { return uploads[i].Key < uploads[j].Key } - return uploads[i].Initiated.Before(uploads[j].Initiated) + if !uploads[i].Initiated.Equal(uploads[j].Initiated) { + return uploads[i].Initiated.Before(uploads[j].Initiated) + } + return uploads[i].UploadID < uploads[j].UploadID }) result, err := backend.ListMultipartUploads(uploads, prefix, delimiter, keyMarker, uploadIDMarker, maxUploads) diff --git a/backend/posix/posix.go b/backend/posix/posix.go index cc96662c..94928c73 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -3147,12 +3147,18 @@ func (p *Posix) ListMultipartUploads(ctx context.Context, mpu *s3.ListMultipartU } } - // Sort once: Key asc, Initiated asc - sort.SliceStable(uploads, func(i, j int) bool { + // Sort once: Key asc, Initiated asc, UploadID asc + // UploadID is a tie-breaker for uploads that share both key and + // initiated time, since directory mtime resolution isn't always + // fine-grained enough to distinguish uploads created back to back. + sort.Slice(uploads, func(i, j int) bool { if uploads[i].Key != uploads[j].Key { return uploads[i].Key < uploads[j].Key } - return uploads[i].Initiated.Before(uploads[j].Initiated) + if !uploads[i].Initiated.Equal(uploads[j].Initiated) { + return uploads[i].Initiated.Before(uploads[j].Initiated) + } + return uploads[i].UploadID < uploads[j].UploadID }) result, err := backend.ListMultipartUploads(uploads, prefix, delimiter, keyMarker, uploadIDMarker, maxUploads) diff --git a/tests/integration/ListMultipartUploads.go b/tests/integration/ListMultipartUploads.go index b92f3b69..f163875e 100644 --- a/tests/integration/ListMultipartUploads.go +++ b/tests/integration/ListMultipartUploads.go @@ -302,12 +302,12 @@ func ListMultipartUploads_keyMarker_not_from_list(s *S3Conf) error { UploadId: out.UploadId, StorageClass: types.StorageClassStandard, }) - if s.azureTests { - // add an artificial delay for azure tests - // as azure uploads all these mps with the same - // identical creation time - time.Sleep(time.Second) - } + // add an artificial delay, since some backends + // (e.g. azure, or posix on filesystems with coarse + // mtime resolution) can report the same identical + // creation/initiated time for uploads created back + // to back, making the resulting order non-deterministic + time.Sleep(time.Second) } } @@ -323,7 +323,7 @@ func ListMultipartUploads_keyMarker_not_from_list(s *S3Conf) error { } if !compareMultipartUploads(uploads[3:], out.Uploads) { - return fmt.Errorf("expected the mp list to be %v, instead got %v", uploads[:3], out.Uploads) + return fmt.Errorf("expected the mp list to be %v, instead got %v", uploads[3:], out.Uploads) } // should start the listing after the specified uploadId marker