Skip to content

Handle BucketAlreadyExists races and bump arsenal for MPU afterEach fix#6222

Open
benzekrimaha wants to merge 3 commits into
development/9.3from
improvement/CLDSRV-919-afterEach-flakiness-internal-error-fix
Open

Handle BucketAlreadyExists races and bump arsenal for MPU afterEach fix#6222
benzekrimaha wants to merge 3 commits into
development/9.3from
improvement/CLDSRV-919-afterEach-flakiness-internal-error-fix

Conversation

@benzekrimaha

@benzekrimaha benzekrimaha commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Context

ARSN-613 changes MongoDB createBucket() to return
BucketAlreadyExists when another request wins a concurrent metastore upsert.
Cloudserver must handle this explicit result instead of surfacing an
InternalError.

This fixes an intermittent MPU test-cleanup failure where concurrent
ListMultipartUploads calls race while creating the hidden MPU shadow bucket.

Paired Arsenal change:
Arsenal #2672.

Arsenal dependency

Cloudserver is temporarily pinned to Arsenal commit:

506014399741d3fd011c25288fc38c985337babe

Before release, this SHA will be replaced with the Arsenal release tag containing
ARSN-613.

Issue: CLDSRV-919

@bert-e

bert-e commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Hello benzekrimaha,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request TBA
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@bert-e

bert-e commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Incorrect fix version

The Fix Version/s in issue CLDSRV-919 contains:

  • None

Considering where you are trying to merge, I ignored possible hotfix versions and I expected to find:

  • 9.4.0

Please check the Fix Version/s of CLDSRV-919, or the target
branch of this pull request.

@benzekrimaha
benzekrimaha force-pushed the improvement/CLDSRV-919-afterEach-flakiness-internal-error-fix branch 2 times, most recently from ab63d85 to 2cae9bc Compare July 8, 2026 15:31
@benzekrimaha
benzekrimaha changed the base branch from development/9.4 to development/9.3 July 8, 2026 15:31
@benzekrimaha benzekrimaha changed the title CLDSRV-919: bump arsenal to 8.5.7 for MPU afterEach flake fix Bump arsenal with MPU afterEach flakiness fix Jul 8, 2026
@scality scality deleted a comment from bert-e Jul 8, 2026
@scality scality deleted a comment from bert-e Jul 8, 2026
@bert-e

bert-e commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Request integration branches

Waiting for integration branch creation to be requested by the user.

To request integration branches, please comment on this pull request with the following command:

/create_integration_branches

Alternatively, the /approve and /create_pull_requests commands will automatically
create the integration branches.

@scality scality deleted a comment from bert-e Jul 8, 2026
@bert-e

bert-e commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

The following options are set: create_integration_branches

@bert-e

bert-e commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

The following options are set: create_pull_requests, create_integration_branches

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.91620% with 27 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.03%. Comparing base (055ae1b) to head (63926ab).
⚠️ Report is 2 commits behind head on development/9.3.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
lib/services.js 80.00% 24 Missing ⚠️
lib/api/apiUtils/bucket/bucketCreation.js 94.91% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

Files with missing lines Coverage Δ
lib/api/apiUtils/bucket/bucketCreation.js 93.54% <94.91%> (+0.15%) ⬆️
lib/services.js 88.06% <80.00%> (+0.63%) ⬆️

... and 4 files with indirect coverage changes

@@                 Coverage Diff                 @@
##           development/9.3    #6222      +/-   ##
===================================================
+ Coverage            84.93%   85.03%   +0.09%     
===================================================
  Files                  206      206              
  Lines                13366    13372       +6     
===================================================
+ Hits                 11353    11371      +18     
+ Misses                2013     2001      -12     
Flag Coverage Δ
file-ft-tests 68.42% <70.39%> (+0.03%) ⬆️
file-ft-tests-null-compat 68.89% <70.94%> (-0.02%) ⬇️
kmip-ft-tests 28.29% <36.31%> (+<0.01%) ⬆️
mongo-v0-ft-tests 69.61% <72.06%> (-0.01%) ⬇️
mongo-v1-ft-tests 69.59% <72.62%> (-0.03%) ⬇️
multiple-backend 36.76% <32.96%> (-0.01%) ⬇️
s3c-ft-tests-v0 64.10% <74.86%> (-0.02%) ⬇️
s3c-ft-tests-v0-null-compat 64.16% <74.86%> (-0.02%) ⬇️
s3c-ft-tests-v1 64.08% <74.86%> (-0.02%) ⬇️
sur-tests 35.82% <51.39%> (-0.83%) ⬇️
sur-tests-inflights 37.66% <53.07%> (-0.01%) ⬇️
unit 70.53% <78.77%> (+0.02%) ⬆️
utapi-v2-tests 34.54% <56.42%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@benzekrimaha
benzekrimaha requested a review from tcarmet July 8, 2026 15:39
@benzekrimaha
benzekrimaha force-pushed the improvement/CLDSRV-919-afterEach-flakiness-internal-error-fix branch from 2cae9bc to 6178ad8 Compare July 9, 2026 15:30
@benzekrimaha benzekrimaha changed the title Bump arsenal with MPU afterEach flakiness fix Handle BucketAlreadyExists races and bump arsenal for MPU afterEach fix Jul 9, 2026
@scality scality deleted a comment from bert-e Jul 9, 2026
@scality scality deleted a comment from bert-e Jul 9, 2026
@scality scality deleted a comment from bert-e Jul 9, 2026
@scality scality deleted a comment from bert-e Jul 9, 2026
@scality scality deleted a comment from bert-e Jul 9, 2026
@scality scality deleted a comment from bert-e Jul 9, 2026
@bert-e

bert-e commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Request integration branches

Waiting for integration branch creation to be requested by the user.

To request integration branches, please comment on this pull request with the following command:

/create_integration_branches

Alternatively, the /approve and /create_pull_requests commands will automatically
create the integration branches.

@benzekrimaha
benzekrimaha force-pushed the improvement/CLDSRV-919-afterEach-flakiness-internal-error-fix branch from b8c7b85 to 19a2bd2 Compare July 10, 2026 06:51

@francoisferrand francoisferrand left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is exactly the kind of PR where prettier is really adding noise... 5-10 lines of "real" change, but prettier fully rewrote 2 large files...

} else {
log.trace('created bucket in metadata');
}
return addToUsersBucket(canonicalID, bucketName, bucket, log, err => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should not be be done if bucket already exists, to avoid race and/or incorrect content?

@SylvainSenechal SylvainSenechal Jul 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't understand why bucket already exists error does not cause the function to return early with an error ?

More generally i don't understand why this wasn't catched before in the err ?

I would've expect current behavior to be :

CreateBucket(bucketName) => return err because collision in the db on bucket name, then return callback(err)

I think I'm missing some context information about : what trigger this bug precisely, cuz the pr talks about MPU but here we are fixing a function called freshStartCreateBucket, is this not called all the time to create any bucket ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On BucketAlreadyExists we now only run addToUsersBucket and skip removeTransientOrDeletedLabel / updateBucket with our local BucketInfo, so we don't overwrite metadata the concurrent winner already wrote. The winner path still runs full cleanUpBucket when we actually created the metastore entry. No extra metastore read.

@benzekrimaha benzekrimaha Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARSN-613 changes metadata.createBucket() to return BucketAlreadyExists on a metastore duplicate instead of silent success. Without handling it we surface InternalError. Previously cloudserver always continued because there was no err to catch.

The MPU flake is in getMPUBucket (InitiateMultipartUpload / ListMultipartUploads). freshStartCreateBucket is the normal CreateBucket path when two requests both see NoBucketYet and race on metadata.createBucket — same contract change.

We don't return BucketAlreadyExists to the S3 client here: the parallel check already decided this is our bucket being created. On duplicate we only ensure the users-bucket listing; we skip updateBucket with our local object.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On BucketAlreadyExists we now only run addToUsersBucket

why do we still need to run addToUsersBucket ? Should be done by the "successful" bucket creation?

Comment thread lib/services.js Outdated
return metadata.createBucket(MPUBucketName, mpuBucket, log,
err => {
if (err?.is?.BucketAlreadyExists) {
return metadata.getBucket(MPUBucketName, log,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this function getMPUBucket() called a lot (it is not an s3 api, not sure if it is called everywhere in mpu code, or just in a few places) : if that is the case, maybe we should plan another improvement to let createBucket return the bucket data as well it case it already exists.

...or maybe we could just return the (existing) mpuBucket variable? for actual buckets, there may be more in the BucketInfo -tags, lifecycle rules...-, but likely not the case for mpu's BucketInfo ?

@benzekrimaha benzekrimaha Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMPUBucket is only called from initiateMultipartUpload and listMultipartUploads. On BucketAlreadyExists we now return the local mpuBucket , MPU shadow BucketInfo is minimal (no tags/lifecycle), so that should be enough and avoids an extra getBucket. Longer term, Arsenal could return existing bucket data on duplicate so all callers benefit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arsenal could return existing bucket data on duplicate

that would not be createBucket anymore (and the semantics of "error, it already exists but here is the data" is not very nice to map) ; and not just an arsenal / mongoclientinterface : it would change the whole "metadata" interface, and require consistent changes in MetadataWrapper and all implementation (in-mem, mongo, metadata).

since "BucketInfo is minimal [...] should be enough", no reason to go there I think.

(another change could be the opposite actually: let getMPUBucket only retrieve what is there, use it for read -e.g. in listMultipartUploads, handling the error appropriately- and call createBucket if/when needed -e.g. in initiateMultipartUpload, handling the already created error. But likely some impact still, and certainly out of scope of this ticket)

Comment thread lib/services.js
* upload bucket or error if any
* @return {undefined}
*/
getMPUBucket(destinationBucket, bucketName, log, cb) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhh. I find this very weird :

It's called
GetMpuBucket
It currently does :

GetBucket, if not found, create it.
So ok this is like a "get or create if not found".

But then with the new code changes, this function becomes

GetBucket, if not found, create it. When creating it, if it already exists, getBucket
But if the bucket was not found in the first place, why would we get an "alreadyExists" error when creating it 🤔 ? Race condition ? But then, this is infinite, because in the last getBucket you can also get a notExist error 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two concurrent MPU calls can both get NoSuchBucket, one wins createBucket, the other gets BucketAlreadyExists. We return the local mpuBucket we built for the create attempt — same owner/displayName as the winner's shadow bucket, no second read. Not a loop: BucketAlreadyExists means the metastore entry exists; we don't refetch.

@SylvainSenechal
SylvainSenechal self-requested a review July 10, 2026 17:44
Comment thread package.json Outdated
Comment thread tests/unit/lib/services.spec.js Outdated
@benzekrimaha
benzekrimaha force-pushed the improvement/CLDSRV-919-afterEach-flakiness-internal-error-fix branch 2 times, most recently from 922f83d to e9a6dbe Compare July 13, 2026 12:13
Comment thread lib/api/apiUtils/bucket/bucketCreation.js Outdated
@benzekrimaha
benzekrimaha force-pushed the improvement/CLDSRV-919-afterEach-flakiness-internal-error-fix branch from e9a6dbe to 84a0d65 Compare July 13, 2026 12:19
@delthas

delthas commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Removing self as there are already 3 other reviewers, more involved in the issue. Feel free to add me again if needed.

@delthas
delthas removed their request for review July 15, 2026 12:38
log.trace('created bucket in metadata');
// Ensure users-bucket listing only; do not updateBucket with our
// local BucketInfo when a concurrent request won createBucket.
return addToUsersBucket(canonicalID, bucketName, bucket, log, callback);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have much knowledge of this, but isn't it problematic to run addToUsersBucket when the bucket already exists ? or is it idempotent or something ? But even then, isn't addToUsersBucket already called when the bucket was created ?

return removeTransientOrDeletedLabel(bucket, log, callback);
});
log.trace('created bucket in metadata');
return cleanUpBucket(bucket, canonicalID, log, callback);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really know this functions but yeah it's hard to understand what's happening here, why are we dropping removeTransientOrDeletedLabel and using cleanUpBucket ?

@SylvainSenechal SylvainSenechal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the pr is fine but some changes are complicated to understand (#6222 (comment)), also the linter with some commits not squashed yet is very hard to read

log.debug('error from metadata', { error: err });
return callback(err);
}
if (err?.is?.BucketAlreadyExists) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems weird to do this ONLY when BucketAlreadyExists...

  • this was always done previously, especially on success
  • in case of error, some other process created the bucket, and should handle addToUsersBucket ?
Suggested change
if (err?.is?.BucketAlreadyExists) {
if (!err?.is?.BucketAlreadyExists) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in f9b8e38: addToUsersBucket and transient cleanup run only after a successful createBucket; BucketAlreadyExists now returns success without side effects, leaving cleanup to the winning request. I did not apply the literal suggestion because inverting this condition would skip the successful cleanup path and let the race error fall through.

Treat a concurrent creator as the owner of normal bucket cleanup and return the local minimal MPU bucket without another metadata read.

Issue: CLDSRV-919
Keep the Prettier migration isolated from the functional race fix so reviewers can inspect the behavior independently.

Issue: CLDSRV-919
@benzekrimaha
benzekrimaha force-pushed the improvement/CLDSRV-919-afterEach-flakiness-internal-error-fix branch from 84a0d65 to a5ee5bb Compare July 20, 2026 08:14
Use Arsenal 506014399741d3fd011c25288fc38c985337babe for paired testing until ARSN-613 is merged and published in a release tag.

Issue: CLDSRV-919
@benzekrimaha
benzekrimaha force-pushed the improvement/CLDSRV-919-afterEach-flakiness-internal-error-fix branch from a5ee5bb to 63926ab Compare July 20, 2026 09:22
Comment thread lib/api/apiUtils/bucket/bucketCreation.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants