Category
Describe the bug
We are intermittently seeing a race condition in EnsureFolderAsync when multiple concurrent requests attempt to create the same folder structure.
The issue appears to occur in the following logic inside Folder.EnsureFolderAsync:
try
{
currentFolder = await currentFolder.PnPContext.Web
.GetFolderByServerRelativeUrlAsync(currentUrl, expressions)
.ConfigureAwait(false);
}
catch (SharePointRestServiceException)
{
currentFolder = await currentFolder
.AddFolderAsync(folderName)
.ConfigureAwait(false);
currentFolderWasCreated = true;
}
Under concurrent execution, two threads/processes can both receive a 404 from GetFolderByServerRelativeUrlAsync, after which both attempt AddFolderAsync.
One succeeds, while the second fails with:
HttpResponseCode: 400
Code: Microsoft.SharePoint.SPException
Message: A file or folder with the name ... already exists.
Observed sequence:
Thread A: GET folder -> 404
Thread B: GET folder -> 404
Thread A: POST AddFolder -> success
Thread B: POST AddFolder -> 400 already exists
This makes EnsureFolderAsync non-idempotent under concurrent access even though the final desired state ("folder exists") is actually achieved.
The issue is intermittent and difficult to reproduce consistently, but logs strongly indicate a race condition between the GET and POST operations.
Steps to reproduce
- Execute multiple concurrent calls to
EnsureFolderAsync targeting the same folder path.
- Example code:
public async Task<string?> EnsureFolderAsync(string siteUrl, string listTitle, string folderPath)
{
using PnPContext pnpContext = await _pnpContextFactory.CreateAsync(siteUrl);
IList list = await GetList(pnpContext, listTitle);
IFolder? folder = await list.RootFolder.EnsureFolderAsync(folderPath);
return folder?.ServerRelativeUrl;
}
-
Under concurrent load, intermittent failures occur where:
GetFolderByServerRelativeUrlAsync returns 404
AddFolderAsync subsequently throws 400 already exists
Observed exception:
HttpResponseCode: 400
Code: Microsoft.SharePoint.SPException
Message: A file or folder with the name sites/C7/SE/C7/#3705-1/4379 already exists.
ClientRequestId: 90a5e1a1-801c-e000-887f-4fc30128cff1
SPClientServiceRequestDuration: 157
X-SharePointHealthScore: 3
X-SP-SERVERSTATE: ReadOnly=0
Expected behavior
EnsureFolderAsync should behave safely under concurrent execution.
If folder creation fails because another concurrent request already created the folder, the implementation could re-fetch the folder instead of propagating the exception.
Conceptually:
GET -> 404
POST -> "already exists"
=> re-fetch folder and continue
Environment details (development & target environment)
Additional context
Based on discussion in this thread: #1710, a possible mitigation would be handling the specific "already exists" exception from AddFolderAsync and then re-fetching the folder instead of failing the operation.
Category
Describe the bug
We are intermittently seeing a race condition in
EnsureFolderAsyncwhen multiple concurrent requests attempt to create the same folder structure.The issue appears to occur in the following logic inside
Folder.EnsureFolderAsync:Under concurrent execution, two threads/processes can both receive a
404fromGetFolderByServerRelativeUrlAsync, after which both attemptAddFolderAsync.One succeeds, while the second fails with:
Observed sequence:
This makes
EnsureFolderAsyncnon-idempotent under concurrent access even though the final desired state ("folder exists") is actually achieved.The issue is intermittent and difficult to reproduce consistently, but logs strongly indicate a race condition between the GET and POST operations.
Steps to reproduce
EnsureFolderAsynctargeting the same folder path.Under concurrent load, intermittent failures occur where:
GetFolderByServerRelativeUrlAsyncreturns404AddFolderAsyncsubsequently throws400 already existsObserved exception:
Expected behavior
EnsureFolderAsyncshould behave safely under concurrent execution.If folder creation fails because another concurrent request already created the folder, the implementation could re-fetch the folder instead of propagating the exception.
Conceptually:
Environment details (development & target environment)
SDK version: 1.14
OS: Windows
SDK used in: ASP.NET Web App
Framework: .NET 8
Browser(s): N/A
Tooling: Visual Studio 2022
Additional details:
GetFolderByServerRelativeUrlAsyncandAddFolderAsync.Additional context
Based on discussion in this thread: #1710, a possible mitigation would be handling the specific "already exists" exception from
AddFolderAsyncand then re-fetching the folder instead of failing the operation.