mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-02 10:17:10 +01:00
s3: abort completed multipart uploads metadata-only (#11385)
* s3: abort a completed upload's leftover directory metadata-only A .uploads/<id> directory can outlive the object it completed into when the commit's metadata-only removal failed or the gateway died in between; the restored part entries then share chunks with the published object. AbortMultipartUpload deleted the directory recursively, chunks and all, so aborting such a leftover destroyed a committed object (#11382). Run the same check s3.clean.uploads gained in #11375 before deleting: when the object entry or a version file under <key>.versions carries the upload id, remove .uploads/<id> metadata-only and answer the abort; when the lookup cannot decide, refuse with InternalError rather than risk live chunks. * s3: apply the completed-upload check to lifecycle MPU abort lifecycleAbortMPU ran the same destructive recursive delete on .uploads/<id>. Reuse uploadCompleted so a leftover whose object entry or version file carries the upload id is removed metadata-only, and an undecidable lookup retries later instead of freeing live chunks. * s3: serialize abort's upload-dir delete with the object's commit The completed check alone leaves a race: abort can read completed=false, then an in-flight completion publishes the object over the same part chunks before the recursive delete frees them. Run the check and delete inside the object write lock, which non-routed completions hold for their whole finalize. With an owner, send the data delete as an ObjectTransaction on the object's lock key — a routed commit then either loses its upload-exists precondition after our delete or has already stamped the object, which the transaction's IF_EXTENDED_NOT_EQUAL condition detects and falls back to a metadata-only remove. lifecycleAbortMPU shares removeUploadDir so both callers get the same ordering. * s3: check for an empty object before resolving its write owner * s3: check completion at the abort's resolved object key An upload record missing ExtMultipartObjectKey skipped the completed check entirely even though the request's Key names the object.
This commit is contained in:
1 parent
0ca1c19821
commit
87ee3b63a2
3 files changed
+396
-20
No files matched your search
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/s3_lifecycle_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3lifecycle"
|
||||
stats_collect "github.com/seaweedfs/seaweedfs/weed/stats"
|
||||
)
|
||||
@@ -172,24 +173,24 @@ func (s3a *S3ApiServer) lifecycleAbortMPU(ctx context.Context, req *s3_lifecycle
|
||||
// Pre-check existence: filer.DeleteEntry suppresses ErrNotFound and
|
||||
// returns success, so without this check an already-aborted upload
|
||||
// would report DONE instead of the correct NOOP_RESOLVED.
|
||||
exists, err := s3a.exists(uploadsFolder, uploadID, true)
|
||||
uploadEntry, err := s3a.getEntry(uploadsFolder, uploadID)
|
||||
if err != nil {
|
||||
if errors.Is(err, filer_pb.ErrNotFound) {
|
||||
return noopResolved("NOT_FOUND"), nil
|
||||
}
|
||||
return retryLater("TRANSPORT_ERROR: exists: " + err.Error()), nil
|
||||
return retryLater("TRANSPORT_ERROR: getEntry: " + err.Error()), nil
|
||||
}
|
||||
if !exists {
|
||||
if uploadEntry == nil {
|
||||
return noopResolved("NOT_FOUND"), nil
|
||||
}
|
||||
if err := s3a.rm(ctx, uploadsFolder, uploadID, true, true); err != nil {
|
||||
if errors.Is(err, filer_pb.ErrNotFound) {
|
||||
return noopResolved("NOT_FOUND_AT_DELETE"), nil
|
||||
}
|
||||
glog.V(1).Infof("lifecycle abort_mpu %s/%s: %v", req.Bucket, req.ObjectPath, err)
|
||||
return retryLater("TRANSPORT_ERROR: rm: " + err.Error()), nil
|
||||
object := string(uploadEntry.Extended[s3_constants.ExtMultipartObjectKey])
|
||||
code := s3a.withObjectWriteLock(req.Bucket, object, nil, func() s3err.ErrorCode {
|
||||
return s3a.removeUploadDir(req.Bucket, uploadID, object)
|
||||
})
|
||||
if code == s3err.ErrNone {
|
||||
return done(), nil
|
||||
}
|
||||
return done(), nil
|
||||
return retryLater("TRANSPORT_ERROR: removeUploadDir: " + s3err.GetAPIError(code).Code), nil
|
||||
}
|
||||
|
||||
// checkSoleSurvivorMarker returns nil to proceed with the delete, or a
|
||||
|
||||
Reference in new issue
Block a user