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)) }