dotnet / dotnet/runtime

[browser][coreCLR] RuntimeAsync AsyncProfilerTests FailFast crashes System.Threading.Tasks.Tests (IsRuntimeAsyncSupported gate doesn't exclude browser)

Open
#132,311 3 comments 0 reactions 1 assignee Claimed by @pavelsavara View on GitHub
arch-wasm area-System.Threading os-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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.