From d1a631123f4aae39e7b5cc460bc98a016753901d Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 11 Mar 2026 13:42:43 -0700 Subject: [PATCH] fix(s3api): allow bucket recreation when orphaned collection exists (#8605) * fix(s3api): allow bucket recreation when orphaned collection exists (#8601) When a bucket is deleted, its filer directory is removed but the underlying collection/volumes may not be fully cleaned up yet. If the bucket is immediately recreated, PutBucketHandler was returning ErrBucketAlreadyExists due to the orphaned collection, blocking bucket recreation and causing subsequent uploads to fail with InternalError. Allow bucket creation to proceed when a collection exists without a corresponding bucket directory, since this is a transient orphaned state from a previous deletion. * fix(s3api): handle concurrent bucket creation race in mkdir On mkdir failure, re-check whether the bucket directory now exists and return BucketAlreadyExists instead of InternalError when another request created the bucket concurrently. --- weed/s3api/s3api_bucket_handlers.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/weed/s3api/s3api_bucket_handlers.go b/weed/s3api/s3api_bucket_handlers.go index 249933035..3a3e64763 100644 --- a/weed/s3api/s3api_bucket_handlers.go +++ b/weed/s3api/s3api_bucket_handlers.go @@ -251,11 +251,11 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) } } - // If collection exists but bucket directory doesn't, this is an inconsistent state + // If collection exists but bucket directory doesn't, this is an orphaned state + // from a previous bucket deletion where volumes haven't been fully cleaned up yet. + // Allow the bucket to be recreated by proceeding with directory creation. if collectionExists { - glog.Errorf("PutBucketHandler: collection exists but bucket directory missing for %s", bucket) - s3err.WriteErrorResponse(w, r, s3err.ErrBucketAlreadyExists) - return + glog.Warningf("PutBucketHandler: collection exists but bucket directory missing for %s, recreating bucket directory", bucket) } // Check for x-amz-bucket-object-lock-enabled header BEFORE creating bucket @@ -297,6 +297,13 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) } } }); err != nil { + // If mkdir failed because another request created the bucket concurrently, + // return BucketAlreadyExists instead of InternalError. + if exist, checkErr := s3a.exists(s3a.option.BucketsPath, bucket, true); checkErr == nil && exist { + glog.V(3).Infof("PutBucketHandler: bucket %s was created concurrently", bucket) + s3err.WriteErrorResponse(w, r, s3err.ErrBucketAlreadyExists) + return + } glog.Errorf("PutBucketHandler mkdir: %v", err) s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) return