Skip to content

[browser] SemaphoreSlim.Wait(0) throws PlatformNotSupportedException on single-threaded WASM even when acquisition would succeed without blocking #131859

Description

@buvinghausen

Description

On single-threaded browser-wasm (.NET 11 preview), SemaphoreSlim.Wait(0) — a zero-timeout,
non-blocking try-acquire — throws PlatformNotSupportedException whenever the semaphore has an
available count. Only the fail-fast branch escapes.

In SemaphoreSlim.WaitCore the order of checks is:

  1. if (millisecondsTimeout == 0 && m_currentCount == 0) return false; — fast-fail path, no guard
  2. RuntimeFeature.ThrowIfMultithreadingIsNotSupported(); — hit unconditionally otherwise

So Wait(0) against an available semaphore — an operation that by definition cannot block —
walks into the multithreading guard and throws:

System.PlatformNotSupportedException: Arg_PlatformNotSupported
  at System.Runtime.CompilerServices.RuntimeFeature.ThrowIfMultithreadingIsNotSupported()
  at System.Threading.SemaphoreSlim.WaitCore(Int64 millisecondsTimeout, CancellationToken cancellationToken)
  at System.Threading.SemaphoreSlim.Wait(Int32 millisecondsTimeout)

Reproduction

Blazor WebAssembly app on .NET 11 preview (observed on 11.0.100-preview.6 SDK, browser-wasm,
single-threaded):

new SemaphoreSlim(1).Wait(0); // throws PlatformNotSupportedException; returned true on net10 wasm

Impact

Grpc.Net.Client ≥ 2.x calls _connectSemaphore.Wait(0) in
Grpc.Net.Client.Balancer.Subchannel.ConnectTransportAsync on every channel's first connect
(load balancing is compiled into the TFM build Blazor serves, and BalancerHttpHandler wraps every
channel). On the first connect the semaphore is always available → the guard always throws → the
exception dies inside grpc-dotnet's fire-and-forget connect task (_ = RunCall(...)) → every
gRPC-Web call from Blazor WebAssembly hangs forever with zero observable errors
: no console
output, no network request, an await that never completes. The same package versions work on
desktop and on the .NET 10 WASM runtime.

Expected behavior

A zero-timeout try-acquire cannot block and should not be gated on multithreading support:
the guard should sit behind the immediate-acquire fast path (mirror of the existing
fail-fast path), i.e. Wait(0) should return true/false on browser-wasm exactly as it did
through .NET 10.

Notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions