Skip to content

Fix concurrent FallbackStorageProvider calls failing after the first success - #22596

Merged
maxkatz6 merged 2 commits into
AvaloniaUI:mainfrom
izakusilov:22559-concurrent-istorageprovider-trygetfolderfrompathasync-calls-fail-after-first-success
Oct 25, 2026
Merged

maxkatz6 merged 2 commits into
AvaloniaUI:mainfrom
izakusilov:22559-concurrent-istorageprovider-trygetfolderfrompathasync-calls-fail-after-first-success

Conversation

@izakusilov

Copy link
Copy Markdown
Contributor

What does the pull request do?

Fixes concurrent IStorageProvider calls (TryGetFolderFromPathAsync, TryGetFileFromPathAsync, etc.) failing on Linux when the first storage provider in the fallback chain is unavailable, e.g. in a Dev Container without an XDG desktop portal.

What is the current behavior?

On X11 and Wayland, IStorageProvider is a FallbackStorageProvider that lazily tries a list of provider factories (DBus portal, GTK, managed). It tracks the next factory to try in _nextProviderFactory, but only increments it after the awaited factory completes.

When several calls run concurrently, they all await the same factory. If that factory returns null asynchronously (the DBus portal does when no portal is running), each caller increments the index when it resumes. The first caller moves on to the next factory, while the others find the index already past the end of the list and return null. As a result, one call succeeds and the rest fail:

await Task.WhenAll(
    storageProvider.TryGetFolderFromPathAsync(folderPath1),
    storageProvider.TryGetFolderFromPathAsync(folderPath2));

When the first factory does return a provider, concurrent calls add it to the list more than once and skip the factories after it.

What is the updated/expected behavior with this PR?

All concurrent calls see the same providers and succeed.

To test: on Linux without an XDG desktop portal (e.g. the Dev Container from the issue), call TryGetFolderFromPathAsync several times in parallel. All calls return the folder.

How was the solution implemented (if it's not obvious)?

  • FallbackStorageProvider now caches the task returned by each factory, so each factory is only invoked once and concurrent callers await the same task instead of advancing a shared index.
  • Access to the cache is synchronized, since callers can resume on different threads (e.g. when called from a background thread).
  • If a factory fails, its cached task is discarded so that the next call retries it, as before.
  • Reset replaces the factories and the cache together; an enumeration in progress keeps using the arrays it started with.

Added FallbackStorageProviderTests; the two concurrency tests fail without the fix. Also verified with an X11 app on Xvfb with a DBus session but no desktop portal: without the fix one of three parallel TryGetFolderFromPathAsync calls fails, with the fix all three succeed.

Checklist

Breaking changes

None.

Obsoletions / Deprecations

None.

Fixed issues

Fixes #22559

@maxkatz6 maxkatz6 added bug os-linux backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Oct 25, 2026
@cla-avalonia

cla-avalonia commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator
  • All contributors have signed the CLA.

@izakusilov

Copy link
Copy Markdown
Contributor Author

@cla-avalonia agree

@MrJul
MrJul enabled auto-merge October 25, 2026 16:26
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

NuGet packages

The packages for this pull request have been published (workflow run):

…erfrompathasync-calls-fail-after-first-success
@MrJul
MrJul added this pull request to the merge queue Oct 25, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 25, 2026
@maxkatz6
maxkatz6 added this pull request to the merge queue Oct 25, 2026
Merged via the queue into AvaloniaUI:main with commit 584ae6f Oct 25, 2026
11 checks passed
@izakusilov
izakusilov deleted the 22559-concurrent-istorageprovider-trygetfolderfrompathasync-calls-fail-after-first-success branch October 25, 2026 22:45
MrJul pushed a commit that referenced this pull request Nov 2, 2026
…success (#22596)

Co-authored-by: Max Katz <maxkatz6@outlook.com>
@MrJul MrJul added backported-12.1.x and removed backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Concurrent IStorageProvider.TryGetFolderFromPathAsync() calls fail after first success

4 participants