s3: route versioned MPU-complete + lifecycle markers off the DLM

Both flip the .versions latest pointer off the object-key lock, racing routed
versioned/suspended writes (the per-path entry lock and the distributed lock
don't serialize against each other).

- completeMultipartUpload (versioning enabled) now routes its latest-pointer
  update through routedVersionedFinalize on the object's owner, carrying the
  precondition so the filer evaluates it atomically under the per-path lock.
  The completion then takes no distributed lock at all (mirroring the versioned
  PutObject path); only object-lock, suspended versioning, unsupported
  preconditions, or a missing ring view fall back to the lock. Suspended MPU
  only writes the main object entry (last-writer-wins, benign) and is unchanged.
- The lifecycle expiration delete-marker (enabled and suspended) resolves the
  object owner and routes its marker pointer flip the same way.

Both fall back to the lock-based updateLatestVersionInDirectory when the owner
isn't known.
This commit is contained in:
Chris Lu committed 2026-05-23 11:04:30 -07:00
1 parent 2d41f4ca8b
commit 7519f68ff1
2 files changed
+42 -14

No files matched your search

+7 -2
View File
@@ -69,9 +69,14 @@ func (s3a *S3ApiServer) lifecycleDispatch(ctx context.Context, req *s3_lifecycle
}
return retryLater("TRANSPORT_ERROR: versioning lookup: " + vErr.Error()), nil
}
// Route the pointer flip to the object's owner so it serializes on the
// object key against routed versioned/suspended writes (it would otherwise
// flip the latest pointer off that lock). Falls back to the lock path when
// the owner is unknown.
markerOwner := s3a.objectWriteOwner(req.Bucket, req.ObjectPath)
switch state {
case s3_constants.VersioningEnabled:
if _, err := s3a.createDeleteMarker(req.Bucket, req.ObjectPath, "", nil, ""); err != nil {
if _, err := s3a.createDeleteMarker(req.Bucket, req.ObjectPath, markerOwner, nil, ""); err != nil {
return retryLater("TRANSPORT_ERROR: createDeleteMarker: " + err.Error()), nil
}
return done(), nil
@@ -82,7 +87,7 @@ func (s3a *S3ApiServer) lifecycleDispatch(ctx context.Context, req *s3_lifecycle
return retryLater("TRANSPORT_ERROR: deleteNullVersion: " + err.Error()), nil
}
}
if _, err := s3a.createDeleteMarker(req.Bucket, req.ObjectPath, "", nil, ""); err != nil {
if _, err := s3a.createDeleteMarker(req.Bucket, req.ObjectPath, markerOwner, nil, ""); err != nil {
return retryLater("TRANSPORT_ERROR: createDeleteMarker: " + err.Error()), nil
}
return done(), nil