[mono][wasm] Fix AOT + BlazorWebAssemblyLazyLoad startup crash#131020
Conversation
When an app is published with AOT and uses BlazorWebAssemblyLazyLoad, the Mono runtime crashes during startup (appdomain.c assertion / interp.c NIY 'should not be reached'). Root cause: an AOT image hard-binds to every assembly it references when the image is loaded (see load_aot_module in aot-runtime.c, which eagerly calls load_image for all referenced images unless aot-lazy-assembly-load is set). A lazy-loaded assembly is not present at runtime startup, so any AOT image that references it is marked 'unusable because dependency <asm> is not found' and its methods fall back to the interpreter, which then hits an unsupported construct and aborts. Enable the runtime's 'aot-lazy-assembly-load' option automatically from the WebAssembly SDK when AOT is combined with BlazorWebAssemblyLazyLoad, so referenced lazy assemblies are only bound when they are actually loaded. Also add AOT + lazy-load regression coverage to LazyLoadingTests, which previously only exercised the interpreter (Debug) configuration. Fixes #125794
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
|
/backport to release/10.0 |
|
Started backporting to |
|
@lewing backporting to |
There was a problem hiding this comment.
Pull request overview
This PR updates the WebAssembly SDK’s browser targets to automatically enable Mono’s --aot-lazy-assembly-load runtime option when AOT is used together with BlazorWebAssemblyLazyLoad, and adds an integration test intended to prevent regressions in that AOT + lazy-load startup path.
Changes:
- Append
--aot-lazy-assembly-loadto runtime options whenRunAOTCompilation=trueand at least oneBlazorWebAssemblyLazyLoaditem exists. - Add a new
native-monotest that publishes with AOT and exercises lazy-loading at runtime.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs | Adds an AOT + publish + lazy-load integration test. |
| src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets | Automatically injects --aot-lazy-assembly-load into runtime options under AOT + lazy-load conditions. |
|
/backport to release/10.0 |
|
Started backporting to |
|
@lewing backporting to git am output$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch
Applying: [wasm] Fix AOT + BlazorWebAssemblyLazyLoad startup crash
Using index info to reconstruct a base tree...
M src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
M src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
CONFLICT (content): Merge conflict in src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
Auto-merging src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs
CONFLICT (content): Merge conflict in src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 [wasm] Fix AOT + BlazorWebAssemblyLazyLoad startup crash
Error: The process '/usr/bin/git' failed with exit code 128 |
…up crash (#131038) Backport of #131020 to release/10.0 /cc @lewing 1. Fixes Issue #125794 main PR: #131020 # Description AOT-published Blazor WebAssembly apps that use `BlazorWebAssemblyLazyLoad` crash during Mono runtime startup — reported as an `appdomain.c` assertion (`condition '<disabled>' not met`) and, in other configurations, an `interp.c` `NIY … should not be reached` abort. An AOT image hard-binds to every assembly it references when the image is loaded (`load_aot_module` in `aot-runtime.c` eagerly calls `load_image` for all referenced images unless the `aot-lazy-assembly-load` runtime option is set). A `BlazorWebAssemblyLazyLoad` assembly is not present at runtime startup (it is downloaded on demand), so when the main app's AOT image loads, resolving the lazy dependency fails, the image is marked unusable, and every method in it falls back to the interpreter, which then hits an unsupported construct and aborts. The fix automatically enables the runtime's existing `aot-lazy-assembly-load` option from the WebAssembly SDK when AOT is combined with `BlazorWebAssemblyLazyLoad`. This defers binding of a referenced lazy assembly until it is actually loaded, keeping the referencing AOT image usable. The change is scoped to `RunAOTCompilation=true` + at least one `BlazorWebAssemblyLazyLoad` item, so non-AOT and non-lazy builds are unaffected. # Customer Impact Any Blazor WebAssembly app that combines AOT compilation with `BlazorWebAssemblyLazyLoad` crashes on startup and is completely unusable. Lazy loading is a common technique to reduce initial download size, and AOT is a common way to improve runtime performance, so customers who adopt both features together are blocked with no workaround other than disabling one of them. # Regression No. This is a long-standing interaction between AOT and lazy loading, not a regression introduced in the most recent release. # Testing Added `LoadLazyAssemblyWithAOT` to `LazyLoadingTests` (`native-mono`, Release + AOT + lazy load), which previously only covered the interpreter (Debug) configuration. The new test reproduces the crash without the fix and passes with it. Verified locally by building the browser (Mono + CoreCLR runtime packs) and workload and running the new test: - Without the fix: `module WasmBasicTestApp is unusable because dependency Json is not found` → `interp.c:4135` assertion → timed out waiting for `WASM EXIT`. - With the fix: `AOT: image 'WasmBasicTestApp' found.`, `firstJsonLoad=true`, `{"FirstName":"John","LastName":"Doe"}`, `WASM EXIT 0`. # Risk Low. The change is a single, narrowly-scoped MSBuild property that only appends `--aot-lazy-assembly-load` when `RunAOTCompilation=true` and at least one `BlazorWebAssemblyLazyLoad` item is present. Builds that do not combine AOT with lazy loading are unaffected, and the option simply defers assembly binding that would otherwise fail. > [!NOTE] > This backport pull request was authored with GitHub Copilot.
Summary
Fixes #125794. AOT-published Blazor WebAssembly apps that use
BlazorWebAssemblyLazyLoadcrash during Mono runtime startup — reported asappdomain.cassertion (condition '<disabled>' not met) and, in other configurations,interp.cNIY … should not be reached.Root cause
An AOT image hard-binds to every assembly it references when the image is loaded.
load_aot_moduleinsrc/mono/mono/mini/aot-runtime.ceagerly callsload_imagefor all referenced images unless theaot-lazy-assembly-loadruntime option is set:A
BlazorWebAssemblyLazyLoadassembly is not present at runtime startup (it is downloaded on demand). So when the main app's AOT image is loaded, resolving its lazy dependency fails and the image is marked "unusable because dependency<asm>is not found". Every method in that image then falls back to the interpreter, which hits an unsupported construct and aborts:Fix
Automatically enable the runtime's existing
aot-lazy-assembly-loadoption from the WebAssembly SDK when AOT is combined withBlazorWebAssemblyLazyLoad. This defers binding of a referenced lazy assembly until it is actually loaded, so the referencing AOT image stays usable. The option flows throughruntimeOptionsin the boot config →mono_wasm_parse_runtime_options→mono_options_parse_options.The change is scoped to
RunAOTCompilation=true+ at least oneBlazorWebAssemblyLazyLoaditem, so non-AOT and non-lazy builds are unaffected.Test
LazyLoadingTestspreviously only covered the interpreter (Debug) configuration. AddedLoadLazyAssemblyWithAOT(native-mono, Release + AOT + lazy load) which reproduces the crash without the fix and passes with it.Verification
Built the browser (Mono + CoreCLR runtime packs) and workload locally and ran the new test.
module WasmBasicTestApp is unusable because dependency Json is not found→interp.c:4135assertion → timed out waiting forWASM EXIT. ❌AOT: image 'WasmBasicTestApp' found.,firstJsonLoad=true,{"FirstName":"John","LastName":"Doe"},WASM EXIT 0. ✅Note
This pull request was authored with GitHub Copilot.