From d7854ce88ed65632fbbca7a232ffc1c285173fbf Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 23 May 2026 10:08:22 -0700 Subject: [PATCH] s3: keep object-lock buckets fully on the distributed lock objectWriteOwner now returns "" for object-lock (WORM) buckets, so versioned PutObject / copy / delete-marker no longer route them off the lock. Routed writes serialize on the owner's per-path entry lock while retention-checked deletes use the distributed lock, and those two locks don't serialize against each other; an object-lock bucket that routed some writes and not others would split-brain on the same object. Retention enforcement is gateway-side and not part of the per-path-locked filer ops, so the whole bucket stays on the distributed lock and remains internally consistent. The now-redundant gate is dropped from routedObjectOwner. --- weed/s3api/s3api_object_routed_write.go | 5 ++--- weed/s3api/s3api_object_versioned_finalize.go | 9 +++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/weed/s3api/s3api_object_routed_write.go b/weed/s3api/s3api_object_routed_write.go index 6a91bbed3..3cfd19531 100644 --- a/weed/s3api/s3api_object_routed_write.go +++ b/weed/s3api/s3api_object_routed_write.go @@ -28,9 +28,8 @@ func (s3a *S3ApiServer) routedObjectOwner(bucket, object string) (pb.ServerAddre if enabled, err := s3a.isVersioningEnabled(bucket); err != nil || enabled { return "", false } - if locked, err := s3a.isObjectLockEnabled(bucket); err != nil || locked { - return "", false - } + // objectWriteOwner already excludes object-lock buckets (they stay fully on + // the distributed lock), returning "" for them. owner := s3a.objectWriteOwner(bucket, object) if owner == "" { return "", false diff --git a/weed/s3api/s3api_object_versioned_finalize.go b/weed/s3api/s3api_object_versioned_finalize.go index 110b72991..1dd0daa1d 100644 --- a/weed/s3api/s3api_object_versioned_finalize.go +++ b/weed/s3api/s3api_object_versioned_finalize.go @@ -43,6 +43,15 @@ func (s3a *S3ApiServer) objectWriteOwner(bucket, object string) pb.ServerAddress if s3a.objectWriteLockClient == nil { return "" } + // Object-lock (WORM) buckets keep ALL of their writes on the distributed + // lock. Retention enforcement is a gateway-side check that the per-path-locked + // filer ops don't perform, so routing only some of such a bucket's writes + // would split serialization between the entry lock and the distributed lock + // (they don't serialize against each other). Excluding the bucket entirely + // keeps it internally consistent. + if locked, err := s3a.isObjectLockEnabled(bucket); err != nil || locked { + return "" + } return s3a.objectWriteLockClient.PrimaryForKey("s3.object.write:" + s3a.toFilerPath(bucket, object)) }