Skip to content

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

@aramB

Description

@aramB

Category

  • Bug

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

  1. Execute multiple concurrent calls to EnsureFolderAsync targeting the same folder path.
  2. 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;
}
  1. 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)

  • 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:

    • Issue occurs intermittently under concurrent requests.
    • No deterministic reproduction yet.
    • Discussion context suggested this may be a race condition between GetFolderByServerRelativeUrlAsync and AddFolderAsync.

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.

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions