Repository navigation
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, 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):
<RuntimeAsyncSupported Condition="'$(TargetOS)' != 'browser'
and '$(TargetOS)' != 'wasi'
and '$(TargetOS)' != 'android'
and '$(TargetsAppleMobile)' != 'true'
and '$(RuntimeFlavor)' != 'Mono'">true</RuntimeAsyncSupported>But the test-time gate does not exclude browser (src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs:477):
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
- Confirm whether
runtime-async=onis applied toSystem.Threading.Tasks.TestsforTargetOS=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. - Fastest CI unblock: align the test gate with the build — make
PlatformDetection.IsRuntimeAsyncSupportedalso exclude browser/wasi/android (mirror the MSBuildRuntimeAsyncSupportedpredicate), or[ActiveIssue]-skip theAsyncProfilerTestsruntime-async tests on browser. This also stops the FailFast from masking the rest ofSystem.Threading.Tasks.Tests. - If runtime-async is intended on browser CoreCLR: reproduce a minimal
[RuntimeAsyncMethodGeneration(true)] async Task M() { await Task.Yield(); throw new InvalidOperationException(); }awaited under atry/catch, and check whether the exception is delivered to the catch or leaks toThreadPool.BackgroundJobHandler.DOTNET_RuntimeAsync=1is the runtime feature switch used by the coreclr runtime-async tests. - Note the blast radius: because this
FailFasts, the wholeSystem.Threading.Tasks.Testsassembly 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
IsRuntimeAsyncSupportedis alreadyfalse).
Note
This issue was researched and drafted with the assistance of GitHub Copilot.
Activity
dotnet-policy-service commentedon Aug 14, 2026
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.
dotnet-policy-service commentedon Aug 14, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @VSadov
See info in area-owners.md if you want to be subscribed.
lewing commentedon Sep 10, 2026
Consolidating the controlled R2R follow-up here rather than maintaining duplicate #133625. The browser CoreCLR Tasks annotations now reference this existing issue, gated narrowly by
PlatformDetection.IsBrowserReadyToRun.With aggressive full trimming held enabled, all ten
RuntimeAsync_KeywordGatekeepingrows pass OFF; the isolated ON run starts two rows and aborts withThreadPool.BackgroundJobHandler failed/InvalidOperationException: inner. It still aborts when only TestUtilities.dll is kept interpreted. Grouped execution has also exposeddeep unhandledinRuntimeAsync_UnhandledExceptionUnwind,deep innerinRuntimeAsync_CallstackNativeIPDeltaRoundtrip, andvaluetask inner throwinRuntimeAsync_ValueTask_HandledException_EmitsUnwindAndComplete. All three additional methods pass OFF. Some throwing scenarios reach diagnostic assertions instead when isolated; the exact EH defect remains unproven.The four method-level EH annotations therefore reference this issue. Two methods also have diagnostic failures tracked by #133626. The separate WhenAny transition trap is tracked by #133627; it is not attributed to this exception escape.
The completed full-suite control with these quarantine conditions gives R2R ON 332 passed / 427 existing skips / 0 failed and OFF 379 passed / 427 skips / 0 failed. Exactly 47 cases across all 38 annotated methods are omitted ON and pass OFF; unaffected outcomes match. These results support preserving browser interpreter coverage rather than disabling runtime async on browser globally.
Note
This update was generated by GitHub Copilot from local ON/OFF logs and result XML.
lewing commentedon Sep 21, 2026
#133547 fixed the GC-mode safety problem across catch resumption, and #133656 removed the associated browser Wasm R2R exception-path quarantines after validating them with matched rebuilt artifacts and the final function-table ordering fix. The separately tracked
WhenAnytransition failure remains open as #133627.Note
This closure comment was generated with GitHub Copilot assistance.