[browser][coreCLR] RuntimeAsync AsyncProfilerTests FailFast crashes System.Threading.Tasks.Tests (IsRuntimeAsyncSupported gate doesn't exclude browser)
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
On `browser-wasm` **CoreCLR**, one of the `AsyncProfilerTests` runtime-async "unhandled exception" tests crashes the process via `Environment.FailFast` instead of the exception being observed/caught. Because it's a `FailFast`, the entire `System.Threading.Tasks.Tests` work item dies with **no xUnit results** — so it also masks any other failures in that assembly.
Observed on the `LibraryTestsCoreCLR_SmokeFirefoxV8` legs of the R2R bring-up PR #129634 (build [1551759](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1551759), head `d3f76ed6`), on **V8 and Firefox**, exit code 71.
```
console.error: Process terminated.
console.error: ThreadPool.BackgroundJobHandler failed
at System.Environment.FailFast(StackCrawlMark ByRef, String, Exception, String)
at System.Environment.FailFast(String, Exception)
at System.Threading.ThreadPool.BackgroundJobHandler()
console.error: System.InvalidOperationException: deep unhandled
at System.Threading.Tasks.Tests.AsyncProfilerTests.RuntimeAsync_DeepUnhandledInnerThrows()
WASM EXIT 1
```
Tests that started right before the crash (from the console): `RuntimeAsync_CallstackSimulation_UnhandledException`, `RuntimeAsync_CustomSyncContext_EmitsContextEventsAndCallstack`, `StateMachineAsync_KeywordGatekeeping`. The throwing frame belongs to the `RuntimeAsync_UnhandledExceptionUnwind` chain: `..._Marker -> RuntimeAsync_DeepUnhandledOuter -> RuntimeAsync_DeepUnhandledMiddle -> RuntimeAsync_DeepUnhandledInnerThrows` (which does `await Task.Yield(); throw new InvalidOperationException("deep unhandled");`), and is expected to be caught by `RuntimeAsync_UnhandledExceptionUnwind_Catcher_Marker`'s `try/catch (InvalidOperationException)` (`src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV2Tests.cs`).
### Primary hint — the test gate does not match the build gate
The runtime-async **compiler feature** (`Features=…;runtime-async=on`) is enabled per the MSBuild `RuntimeAsyncSupported` property, which **excludes browser/wasi/android/apple-mobile/Mono** (`eng/testing/tests.targets`, `src/libraries/Directory.Build.targets`):
```xml
true
```
But the **test-time** gate does not exclude browser (`src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs:477`):
```csharp
public static bool IsRuntimeAsyncSupported => !IsMonoRuntime;
```
`IsMonoRuntime` is `false` on browser **CoreCLR**, so `IsRuntimeAsyncSupported` is `true` and every `[ConditionalFact(nameof(IsRuntimeAsyncSupported))]` in `AsyncProfilerTests` runs on a build where the runtime-async feature was **not** turned on. That inconsistency (test gate `!IsMonoRuntime` vs. build gate that excludes `browser`) is the most likely reason these tests execute on browser CoreCLR at all, and is the first thing to check.
### Mechanism
The exception is thrown after `await Task.Yield()`, i.e. on the resumed continuation, which on single-threaded wasm runs on the `ThreadPool.BackgroundJobHandler` pump. Instead of being propagated back into the awaiting Task (and caught by the enclosing `try/catch`), it escapes as an **unhandled thread-pool exception**, which correctly triggers `Environment.FailFast`. So either:
- the continuation-resume path isn't wrapping the body in the exception-capturing handler on browser CoreCLR (a runtime bug if runtime-async is meant to work here), or
- the methods aren't actually getting runtime-async codegen on browser (feature off), and the test's expectations/exception plumbing don't hold — in which case the test simply shouldn't run here.
### Suggested independent-triage steps
1. **Confirm whether `runtime-async=on` is applied** to `System.Threading.Tasks.Tests` for `TargetOS=browser` (per the targets above it should be **off**). If off, these `[RuntimeAsyncMethodGeneration(true)]` methods don't get runtime-async semantics, and the unwind/EventPipe expectations don't apply.
2. **Fastest CI unblock:** align the test gate with the build — make `PlatformDetection.IsRuntimeAsyncSupported` also exclude browser/wasi/android (mirror the MSBuild `RuntimeAsyncSupported` predicate), or `[ActiveIssue]`-skip the `AsyncProfilerTests` runtime-async tests on browser. This also stops the FailFast from masking the rest of `System.Threading.Tasks.Tests`.
3. **If runtime-async is intended on browser CoreCLR:** reproduce a minimal `[RuntimeAsyncMethodGeneration(true)] async Task M() { await Task.Yield(); throw new InvalidOperationException(); }` awaited under a `try/catch`, and check whether the exception is delivered to the catch or leaks to `ThreadPool.BackgroundJobHandler`. `DOTNET_RuntimeAsync=1` is the runtime feature switch used by the coreclr runtime-async tests.
4. **Note the blast radius:** because this `FailFast`s, the whole `System.Threading.Tasks.Tests` assembly reports no results; fixing the gate/crash may reveal or clear additional failures in that suite.
### Configuration
- `-os browser -a wasm`, CoreCLR, Release; V8 and Firefox.
- CoreCLR-on-browser specific; not observed on Mono (where `IsRuntimeAsyncSupported` is already `false`).
> [!NOTE]
> This issue was researched and drafted with the assistance of GitHub Copilot.
Contributor guide
Assessment
This issue has not been assessed yet.