s3: route non-conditional suspended DELETE off the DLM

A suspended DELETE removes the null version (the main object entry) and adds a
delete marker. Now that suspended PUT routes on the object key, a DELETE left on
the distributed lock wouldn't serialize against it (the per-path entry lock and
the distributed lock are independent). Route it on the object key too.

FinalizeVersionedWrite gains an optional delete_path it removes under the same
lock before flipping the pointer, so "delete null + add marker" is one atomic
step on the owner. createDeleteMarker threads it through (empty for the
versioning-enabled marker path). The DeleteObjectHandler routes a no-versionId
suspended delete when there is no If-Match — that condition targets the main
object, not the .versions pointer the op evaluates, so a conditional suspended
delete stays on the lock path. Object-lock can't apply (it requires versioning
enabled, never suspended).
This commit is contained in:
Chris Lu committed 2026-05-23 10:17:45 -07:00
1 parent d7854ce88e
commit 2d41f4ca8b
9 files changed
+56 -12

No files matched your search

+2 -2
View File
@@ -198,7 +198,7 @@ type ObjectVersion struct {
// otherwise it uses the lock-based updateLatestVersionInDirectory. A failed
// routed finalize rolls back the marker file and returns
// errVersionedPreconditionFailed for a precondition miss.
func (s3a *S3ApiServer) createDeleteMarker(bucket, object string, routedOwner pb.ServerAddress, cond *filer_pb.WriteCondition) (string, error) {
func (s3a *S3ApiServer) createDeleteMarker(bucket, object string, routedOwner pb.ServerAddress, cond *filer_pb.WriteCondition, deleteNullPath string) (string, error) {
// Clean up the object path first
cleanObject := strings.TrimPrefix(object, "/")
@@ -252,7 +252,7 @@ func (s3a *S3ApiServer) createDeleteMarker(bucket, object string, routedOwner pb
if routedOwner != "" {
// Routed: precondition + demote + pointer flip run atomically on the
// .versions owner. Roll back the marker file if it does not succeed.
if code := s3a.routedVersionedFinalize(routedOwner, bucket, cleanObject, versionId, versionFileName, deleteMarkerEntry, cond); code != s3err.ErrNone {
if code := s3a.routedVersionedFinalize(routedOwner, bucket, cleanObject, versionId, versionFileName, deleteMarkerEntry, cond, deleteNullPath); code != s3err.ErrNone {
if rbErr := s3a.rmObject(versionsDir, versionFileName, true, false); rbErr != nil {
glog.Errorf("createDeleteMarker: rollback %s/%s: %v", versionsDir, versionFileName, rbErr)
}