Skip to content

Fix/ensure folder race condition#1770

Draft
aramB wants to merge 11 commits into
pnp:devfrom
aramB:fix/ensure-folder-race-condition
Draft

Fix/ensure folder race condition#1770
aramB wants to merge 11 commits into
pnp:devfrom
aramB:fix/ensure-folder-race-condition

Conversation

@aramB

@aramB aramB commented May 27, 2026

Copy link
Copy Markdown

Fix race condition in EnsureFolderAsync under concurrent access

When multiple concurrent calls to EnsureFolderAsync target the same folder path, both can receive a 404 from GetFolderByServerRelativeUrlAsync and then both attempt AddFolderAsync. The second call fails with HTTP 400 "A file or folder with the name already exists."

Changes

  • Added AddFolderHandleRaceAsync helper that catches the "already exists" error (server error code -2130575257) and re-fetches the folder instead of propagating the exception
  • Added ErrorIndicatesFolderAlreadyExists helper (mirrors existing ErrorIndicatesFolderDoesNotExists pattern)
  • Both code paths in EnsureFolderAsync now route through the race-safe helper

Tests

  • EnsureListFolderIdempotentTest — sequential idempotency
  • EnsureListFolderConcurrentTest — parallel calls to the same path

Fixes #1765

Aram Barzanjeh added 5 commits May 26, 2026 11:22
Introduce `AddFolderHandleRaceAsync` to handle race conditions during folder creation by re-fetching the folder if it already exists. Replace direct calls to `AddFolderAsync` with `AddFolderHandleRaceAsync`. Add helper method `ErrorIndicatesFolderAlreadyExists` to identify specific error codes for existing folders.
Added `EnsureListFolderIdempotentTest` and `EnsureListFolderConcurrentTest` to validate folder creation behavior and race condition handling. Improved cleanup of mock folders from the recycle bin in test cases. Refactored `QueryableExtensions.FirstOrDefaultAsync` calls for readability and consistency. Enhanced test assertions for folder properties and adjusted formatting.
@Adam-it Adam-it self-assigned this May 27, 2026

@Adam-it Adam-it left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@aramB Awesome work so far. I had a quick check on the changes done in the Folder.cs file and IMO they are ok. The only problem left is MockData which were created for all folder tests.
In your case you should uncomment the TestCommon.Instance.Mocking = false; only in the tests you created and modifed and run tests then. With this the mock data would be generated only for those tests.

Image

Please check this step in the guide which explains it
https://pnp.github.io/pnpcore/contributing/writing%20tests.html#run-a-single-test-live

may I kindly ask you to remove the mocks and recreate the mock data only for the tests you modified/created.
After that I think we may proceed and merge 👍
Let me know if you need any help and sorry for the late review.

@Adam-it Adam-it marked this pull request as draft June 3, 2026 19:47
@aramB

aramB commented Jun 4, 2026

Copy link
Copy Markdown
Author

@Adam-it, thank you very much for the review!

I realized I misunderstood this instruction: "Once your test is ready, delete the generated mock data files (see the .response files in the MockData folder) and run your test once more to regenerate them" (from the docs). I initially thought it meant I should delete all .response files 🤦🏼‍♂️

No worries 👍

I’ve updated the PR, reverted the unintended changes, and the new .response files are now only added for the two new tests I introduced.

@aramB aramB requested a review from Adam-it June 4, 2026 07:24
@aramB aramB marked this pull request as ready for review June 4, 2026 07:25
@Adam-it

Adam-it commented Jun 4, 2026

Copy link
Copy Markdown
Member

@Adam-it, thank you very much for the review!

I realized I misunderstood this instruction: "Once your test is ready, delete the generated mock data files (see the .response files in the MockData folder) and run your test once more to regenerate them" (from the docs). I initially thought it meant I should delete all .response files 🤦🏼‍♂️

No worries 👍

I’ve updated the PR, reverted the unintended changes, and the new .response files are now only added for the two new tests I introduced.

Boy that was fast!
Will try to take a look at it today over the night.

You Rock 🤩

@codecov-commenter

codecov-commenter commented Jun 4, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.24%. Comparing base (63545f3) to head (1ffbce7).
⚠️ Report is 2966 commits behind head on dev.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #1770      +/-   ##
==========================================
- Coverage   82.42%   81.24%   -1.19%     
==========================================
  Files         416      637     +221     
  Lines       28590    45390   +16800     
  Branches        0     4779    +4779     
==========================================
+ Hits        23565    36876   +13311     
- Misses       5025     7104    +2079     
- Partials        0     1410    +1410     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes a concurrency race in SharePoint folder creation by making EnsureFolderAsync resilient when concurrent callers attempt to create the same folder and one loses the create race (“already exists” error), and adds mock-backed tests to validate sequential idempotency and concurrent behavior.

Changes:

  • Introduced a race-safe helper around AddFolderAsync to handle “already exists” by re-fetching the folder.
  • Added a SharePoint error discriminator for “folder already exists” (server error code -2130575257).
  • Added tests (plus mock responses) for sequential idempotency and concurrent EnsureFolderAsync calls.

Reviewed changes

Copilot reviewed 49 out of 49 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/sdk/PnP.Core/Model/SharePoint/Core/Internal/Folder.cs Routes folder creation through a race-safe helper and adds “already exists” error detection.
src/sdk/PnP.Core.Test/SharePoint/FoldersTests.cs Adds sequential idempotency and concurrent ensure-folder tests.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00000.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00001.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00002.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00003.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00004.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00005.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00006.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00007.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00008.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderIdempotentTest-0-00009.response.json Mock response for EnsureListFolderIdempotentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00000.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00001.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00002.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00003.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00004.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00005.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00006.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00007.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00008.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00009.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00010.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00011.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00012.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00013.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00014.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00015.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00016.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00017.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00018.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00019.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00020.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00021.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00022.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00023.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00024.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00025.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00026.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00027.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00028.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00029.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00030.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00031.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00032.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00033.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00034.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00035.response.json Mock response for EnsureListFolderConcurrentTest.
src/sdk/PnP.Core.Test/SharePoint/MockData/FoldersTests/EnsureListFolderConcurrentTest-0-00036.response.json Mock response for EnsureListFolderConcurrentTest.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/sdk/PnP.Core/Model/SharePoint/Core/Internal/Folder.cs
Comment thread src/sdk/PnP.Core/Model/SharePoint/Core/Internal/Folder.cs

@Adam-it Adam-it left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@aramB I performed a small fixup and it seems to be perfect.
But I am having some trouble running the EnsureListFolderConcurrentTest and it always seems to fail on my end

Image

Could we recheck this part before the merge and ensure it is working

@aramB aramB marked this pull request as draft June 8, 2026 07:50
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.

Race condition in EnsureFolderAsync can cause "folder already exists" exception under concurrent requests

4 participants