Skip to content

[wasm] Reload failed force-cache assets through existing retries - #136527

Open
pavelsavara wants to merge 10 commits into
dotnet:mainfrom
pavelsavara:browser-boot-cache-recovery
Open

pavelsavara wants to merge 10 commits into
dotnet:mainfrom
pavelsavara:browser-boot-cache-recovery

Conversation

@pavelsavara

@pavelsavara pavelsavara commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Summary

Recover from unusable browser HTTP-cache entries for fingerprinted boot resources by changing the existing download retries to cache: "reload", rather than adding a second recovery/retry mechanism.

This is an alternative to #135925. Server-side prevention for missing manifest assets is in dotnet/aspnetcore#70062.

  • Leave healthy force-cache requests unchanged.
  • Promote failed force-cache downloads to reload before applying terminal-status handling. A cached 404 gets a network recovery attempt; a visible 404 from that recovery remains terminal.
  • Retain the existing maximum of three attempts, scheduling and delay, and honor enableDownloadRetry=false.
  • Allow cached main-Wasm failures to recover, with bootstrap priority so retries cannot wait behind assembly download slots that require its memory.
  • Preserve native symbols' no-retry behavior. Startup awaits symbols before queuing the other downloads, so putting a failed symbol fetch behind the download-queue barrier would deadlock.
  • Reject explicitly empty cached binary responses and empty buffered bodies without adding streaming-body reads. Preserve valid empty VFS/symbol resources.
  • Consume buffered error responses without retaining them as successful asset buffers, and release failed streaming-download slots.
  • Chain Mono's progress promise and report each resource once to avoid stray unhandled rejections or progress beyond the total.

Browser coverage

The cache-recovery matrix uses the static cache-seed.html page and hosted test server to seed real, cacheable invalid responses. Chrome starts the application using the ordinary loader and HTTP cache; no Playwright routing substitutes those responses.

The matrix covers empty responses, cached 404s, integrity mismatches, integrity checking disabled, transient and permanent reload failures, terminal reload 404s, disabled retries, ordinary no-cache requests, buffered ICU data, and main-Wasm recovery with default scheduling and one download slot. It checks exact fetch modes, attempt counts, server requests, startup outcome, and progress bounds. Assembly selection follows UseWebcil, covering Webcil and DLL resources.

Small client-assembly cache reuse is asserted on the following boot. Large Debug native-Wasm cache retention is discretionary; those cases instead verify startup, retry modes/counts, scheduling, and one healthy force-cache attempt on the following boot with at most one network request. Earlier non-fingerprinted healthy-cache controls also verified conditional revalidation (304).

The generated client explicitly selects the current built runtime and SDK packs, even when Blazor targets the previous framework. CoreCLR uses the existing client-configuration helper. Mono imports UseCurrentMonoRuntime.targets into the copied client and sets WBTMonoRuntimePackVersion; the test no longer embeds the MSBuild target in C#. The shared client project remains unchanged.

Every recovery case verifies resolved runtime-pack identity/version and loader bytes against an independent extracted SDK pack when available, or against the local nupkg if the pack is absent or would compare a file with itself. An extracted-pack comparison checks SDK agreement, not package freshness. Runtime packages and constructed SDKs were rebuilt after the .NET 12 transition to avoid an old manifest incorrectly mapping net11.0 to the current package.

Separate ModuleConfigTests.NativeSymbolsDownloadDoesNotHang cases publish an actual fingerprinted native symbol map and require the diagnostics module. A fetch wrapper injects HTTP 404s and rejected network requests. The five cases check required/optional failures and healthy startup, assert exactly one force-cache symbol attempt, and verify startup succeeds or fails rather than hanging. Optional HTTP failures permit startup; rejected fetches preserve their previous fatal-error behavior.

Validation

Full Debug browser baselines were previously built for both runtimes. The following latest validation was run locally on November 2, 2026, from the corresponding CoreCLR or Mono worktree root, using:

$env:NuGetAudit='false'
$env:UseSharedCompilation='false'
$env:SKIP_PROJECT_CLEANUP='1'

Latest review follow-up

The native-symbol HTTP 404 and network-failure cases both reproduced the startup hang against the unfixed local CoreCLR runtime: one force-cache attempt, followed by a 10-second timeout waiting for WASM EXIT.

.\dotnet.cmd build -bl /p:TargetOS=browser /p:TargetArchitecture=wasm /p:RuntimeFlavor=CoreCLR /p:Configuration=Debug /p:EnableHotReloadInRuntimeConfigDevFile=false /p:InstallChromeForTests=false /p:TestUsingWorkloads=true /p:InstallWorkloadForTesting=false /p:XUnitMethodName=Wasm.Build.Tests.ModuleConfigTests.NativeSymbolsDownloadDoesNotHang /t:Test .\src\mono\wasm\Wasm.Build.Tests

Result before the loader fix: failed as expected: both required-symbol failure cases timed out. These reached the symbol fetch; they were not SDK/restore/setup failures.

Rebuilt the affected CoreCLR browser runtime/native outputs and packages:

.\build.cmd -bl -os browser -subset clr.runtime+clr.corelib+libs.native+host.native+libs.pretest+packs -c Debug /p:RuntimeFlavor=CoreCLR /p:EnableHotReloadInRuntimeConfigDevFile=false

Result: passed. Verified that the new runtime nupkg contains the fixed retry gate.

Recreated the constructed workload SDK from those artifacts:

.\dotnet.cmd build -bl /p:TargetOS=browser /p:TargetArchitecture=wasm /p:RuntimeFlavor=CoreCLR /p:Configuration=Debug /p:EnableHotReloadInRuntimeConfigDevFile=false /p:TestUsingWorkloads=true /t:InstallWorkloadUsingArtifacts .\src\mono\wasm\Wasm.Build.Tests

Result: passed.

Ran the five symbol cases and full CoreCLR cache matrix together in real Chrome:

.\dotnet.cmd build -bl /p:TargetOS=browser /p:TargetArchitecture=wasm /p:RuntimeFlavor=CoreCLR /p:Configuration=Debug /p:EnableHotReloadInRuntimeConfigDevFile=false /p:InstallChromeForTests=false /p:TestUsingWorkloads=true /p:InstallWorkloadForTesting=false '/p:XUnitMethodName=Wasm.Build.Tests.ModuleConfigTests.NativeSymbolsDownloadDoesNotHang -method Wasm.Build.Tests.Blazor.AssetCachingTests.*' /t:Test .\src\mono\wasm\Wasm.Build.Tests

Result: 22 passed, 0 failed, 0 skipped, including all five symbol cases and all 17 cache cases. Native-Wasm cache recovery still works with default and one-slot scheduling.

Validated the extracted Mono runtime-selection target using the previously refreshed Mono workload SDK:

.\dotnet.cmd build -bl /p:TargetOS=browser /p:TargetArchitecture=wasm /p:RuntimeFlavor=Mono /p:Configuration=Debug /p:EnableHotReloadInRuntimeConfigDevFile=false /p:InstallChromeForTests=false /p:TestUsingWorkloads=true /p:InstallWorkloadForTesting=false /p:XUnitClassName=Wasm.Build.Tests.Blazor.AssetCachingTests /t:Test .\src\mono\wasm\Wasm.Build.Tests

Result: 17 passed, 0 failed, 0 skipped (Mono, Webcil).

.\dotnet.cmd build -bl /p:TargetOS=browser /p:TargetArchitecture=wasm /p:RuntimeFlavor=Mono /p:Configuration=Debug /p:EnableHotReloadInRuntimeConfigDevFile=false /p:InstallChromeForTests=false /p:TestUsingWorkloads=true /p:InstallWorkloadForTesting=false /p:WasmEnableWebcil=false /p:XUnitClassName=Wasm.Build.Tests.Blazor.AssetCachingTests /t:Test .\src\mono\wasm\Wasm.Build.Tests

Initial result: 16 passed, 1 failed, 0 skipped (Mono, DLL). All recovery rows passed. The existing healthy-cache control timed out on its first cold load; its native-Wasm HTTP transfer took about 27 seconds against a 30-second startup deadline.

Reran that unchanged control in isolation:

.\dotnet.cmd build -bl /p:TargetOS=browser /p:TargetArchitecture=wasm /p:RuntimeFlavor=Mono /p:Configuration=Debug /p:EnableHotReloadInRuntimeConfigDevFile=false /p:InstallChromeForTests=false /p:TestUsingWorkloads=true /p:InstallWorkloadForTesting=false /p:WasmEnableWebcil=false /p:XUnitMethodName=Wasm.Build.Tests.Blazor.AssetCachingTests.BlazorApp_BasedOnFingerprinting_LoadsWasmAssetsFromCache /t:Test .\src\mono\wasm\Wasm.Build.Tests

Result: 1 passed, 0 failed, 0 skipped. No timeout increase, suppression, or test disabling was added. Across these runs, all 56 distinct selected cases passed, but the initial DLL matrix was not entirely green.

Loader ESLint, test-JavaScript syntax checking, shared-target XML parsing, and git diff --check also passed.

Local prerequisites and scope

  • EnableHotReloadInRuntimeConfigDevFile=false is a command-line-only workaround for an unrelated SDK DotNetWatch static-assets endpoint mismatch.
  • NuGetAudit=false was set only in the local test-command environment because the existing fixture's pinned dependencies produce audit warnings as errors. No package-version change or audit suppression is included.
  • UseSharedCompilation=false avoids compiler-server locks while refreshing the isolated SDKs.
  • Applications use the prebuilt runtime host; this PR does not change application relinking or per-app R2R code. No multithreaded, Firefox, or service-worker scenarios were run.

Resolves dotnet/aspnetcore#69488

Note

This PR and its description were implemented/updated with GitHub Copilot. The listed builds and tests were run locally.

Promote failed force-cache downloads to reload within the existing bounded
retry policy, including cached terminal statuses. Preserve the remaining
retry budget and the download-retry opt-out.

Keep the main Wasm download runnable while assemblies await its memory,
avoid retaining failed response bodies as asset buffers, and report Mono
download progress once per resource across retries.

Exercise real browser HTTP-cache recovery for assemblies, buffered ICU
data, and the main Wasm module on both runtimes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara pavelsavara added arch-wasm WebAssembly architecture area-Host os-browser Browser variant of arch-wasm and removed area-Build-mono labels Oct 27, 2026
@pavelsavara
pavelsavara deployed to copilot-pat-pool October 27, 2026 17:29 — with GitHub Actions Active
@pavelsavara pavelsavara added this to the 12.0.0 milestone Oct 27, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

Select the client assembly extension from the existing UseWebcil setting
for cache seeding and both healthy-cache request assertions. DLL-based
CI lanes must exercise the assembly instead of searching for missing
Webcil files or filtering out successful DLL requests.

Document the no-Webcil and no-fingerprint validation switches.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pavelsavara and others added 2 commits October 29, 2026 12:49
Move the HTTP cache seed page into a static HTML test asset and rely on
the existing client-configuration helper for CoreCLR runtime selection.

Use the extracted SDK loader as an expected source when independent,
retaining package verification when the SDK pack is absent or resolves
to the same file. Document that distinction and the cache-retention
boundary for large native Wasm resources.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pavelsavara and others added 4 commits October 29, 2026 19:18
Keep the hosted Blazor client test asset unchanged and rely on the
existing test configuration and constructed workload SDK.

Validated the cache suite on CoreCLR Webcil and Mono DLLs, including
runtime-pack identity, version and loader-byte checks, plus both
non-fingerprinted healthy-cache controls.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve the cache-test publish-options conflict by preserving main's
explicit Blazor template target framework together with the existing
client configuration and cache-test publish flags. Apply that framework
selection to the recovery test as well.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Configure the generated Mono client to resolve the current runtime and
WebAssembly SDK packs even when the Blazor application targets the
previous framework. Keep the shared test asset unchanged and preserve
the runtime-pack identity, version and loader-byte checks.

Reproduced all 16 CI pack-mismatch failures using a freshly constructed
SDK before the fix. Validated 53 browser cases after the fix against
fresh .NET 12 runtime packs and workload SDKs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep native symbols outside cache-recovery retries so startup cannot wait
for the download-queue barrier before the other assets have been queued.
Cover emitted fingerprinted symbols with required and optional HTTP/network
failures and a healthy startup control.

Move the generated Mono client's runtime-selection target into a shared
targets file without changing the shared client asset or selected packs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

This branch was successfully deployed

1 active (outdated) deployment
copilot-pat-pool — e3b8d81f Deployed Oct 27, 2026 by pavelsavara via conclusion #11261
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Host os-browser Browser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[blazor-wasm] A cached empty or error response for a boot resource is reused forever (force-cache); the app never boots again, even after a hard reload

3 participants