dotnet / dotnet/roslyn

Workspace APIs unusable on Blazor WebAssembly under .NET 11: AddProject takes a blocking SemaphoreSlim.Wait

Open
#84,615 4 comments 0 reactions 0 assignees View on GitHub
Area-IDE
Dominant language
C#
Stars
20.7k
Forks
4.3k
PR merge metrics
PR metrics pending

Description

**Version Used**: `Microsoft.CodeAnalysis.CSharp.Workspaces` 5.3.0 (also reproduces on 5.6.0)

Minimal repro: https://github.com/SimonCropp/roslyn-wasm-blocking-wait

## Summary

`AdhocWorkspace.AddProject` throws `PlatformNotSupportedException` on single-threaded Blazor WebAssembly under .NET 11. The same call, with the same Roslyn version, succeeds on .NET 10. Since `AddProject` is the entry point to every workspace scenario, this means no Roslyn workspace can be constructed on browser-wasm at all under .NET 11.

## Steps to Reproduce

Adding one empty project is enough — no documents, no compilation, no completion:

```csharp
var workspace = new AdhocWorkspace();
workspace.AddProject(ProjectInfo.Create(
ProjectId.CreateNewId(), VersionStamp.Default, "Probe", "Probe", LanguageNames.CSharp));
```

The repro is two Blazor WebAssembly projects with `Workspace.cs` and `BlazorMain.cs` linked into both, so the code under test is byte-identical and the target framework is the only difference:

| Project | TFM | Roslyn | Result |
|---|---|---|---|
| `Net10` | net10.0 | 5.3.0 | AddProject succeeds |
| `Net11` | net11.0 | 5.3.0 | **`PlatformNotSupportedException`** |

```
dotnet run --project Net11/Net11.csproj
```

## Expected Behavior

`AddProject` succeeds, as it does on .NET 10.

## Actual Behavior

```
System.PlatformNotSupportedException: Arg_PlatformNotSupported
at System.Runtime.CompilerServices.RuntimeFeature.ThrowIfMultithreadingIsNotSupported()
at System.Threading.SemaphoreSlim.WaitCore(Int64 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.SemaphoreSlim.Wait(CancellationToken cancellationToken)
at Roslyn.Utilities.SemaphoreSlimExtensions.DisposableWait(SemaphoreSlim semaphore, CancellationToken cancellationToken)
at Microsoft.CodeAnalysis.ProjectDependencyGraph.GetProjectsThatTransitivelyDependOnThisProject(ProjectId projectId)
at Microsoft.CodeAnalysis.SolutionCompilationState.AddProjects(ArrayBuilder`1 projectInfos)
at Microsoft.CodeAnalysis.Solution.AddProjects(ArrayBuilder`1 projectInfos)
at Microsoft.CodeAnalysis.Solution.AddProject(ProjectInfo projectInfo)
at Microsoft.CodeAnalysis.Workspace.CheckAndAddProject(Solution newSolution, ProjectInfo project)
...
at Microsoft.CodeAnalysis.Workspace.OnProjectAdded(ProjectInfo projectInfo)
at Microsoft.CodeAnalysis.AdhocWorkspace.AddProject(ProjectInfo projectInfo)
```

## What changed on the runtime side

`SemaphoreSlim.WaitCore` gained an unconditional multithreading check in .NET 11.

release/10.0 — guarded, so it only applied to the threaded wasm build:

```csharp
CheckDispose();
#if FEATURE_WASM_MANAGED_THREADS
Thread.AssureBlockingPossible();
#endif
```

main — unconditional, so it now also fires in the single-threaded build that Blazor WebAssembly uses by default:

```csharp
RuntimeFeature.ThrowIfMultithreadingIsNotSupported();
```

To be fair to the runtime side: `SemaphoreSlim.Wait` and `WaitCore` carry `[UnsupportedOSPlatform("browser")]` in release/10.0 too. That annotation is not new, so .NET 10 was not offering a supported guarantee — it simply never enforced the restriction outside the threaded build, and code taking an uncontended wait kept working.

That is why I am raising this here rather than only against the runtime: Roslyn is calling an API annotated unsupported-on-browser, on a path that browser-hosted callers cannot avoid.

## Question

Is it feasible for `ProjectDependencyGraph.GetProjectsThatTransitivelyDependOnThisProject` (and the `SetCurrentSolution` path that reaches it) to avoid a blocking `SemaphoreSlim.Wait`? The wait appears to be uncontended in the single-threaded case — it never actually blocks, which is precisely why this worked on .NET 10.

The documented alternative of enabling `WasmEnableThreads`, which would satisfy the runtime check, is not currently viable either: the .NET 11 runtime fails to start with threads enabled — dotnet/runtime#131311 (repro: https://github.com/SimonCropp/wasm-threads-startup-crash).

## Context

The affected app is the in-browser query explorer in [Scry](https://github.com/Papyrine/Scry), which hosts Roslyn in Blazor WebAssembly for completion over a synthesized query model. It works on .NET 10 and cannot initialize on .NET 11.

Environment: SDK 11.0.100-preview.6.26359.118, runtimes 11.0.0-preview.6.26359.118 and 10.0.9, Windows 11 x64, Chromium 148. Only .NET 11 preview 6 was tested; it is the latest published preview at time of writing.

Contributor guide

Open the contributing guide

Research direction

Start with ProjectDependencyGraph.GetProjectsThatTransitivelyDependOnThisProject and the SetCurrentSolution path named in the report, then run dotnet run --project Net11/Net11.csproj from the linked minimal repro. Compare the net10.0 and net11.0 behavior and trace the DisposableWait call. Done means AdhocWorkspace.AddProject succeeds in single-threaded Blazor WebAssembly under .NET 11 without requiring wasm threads.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, wasm
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.