Skip to content

Commit

Permalink
Merged PR 36408: [release/8.0] [Blazor] Auto render mode improvements
Browse files Browse the repository at this point in the history
# Auto render mode improvements

Backport of dotnet/aspnetcore#53159

Improves the Auto render mode so that components are more responsive and have a decreased initial time to interactivity when WebAssembly resources are not already cached.

## Description

One of the goals of the Auto render mode was to allow apps to become interactive as quickly as possible via Server interactivity, while WebAssembly bits were downloaded in the background for use on future visits to the site. However, since WebAssembly resources were being downloaded with maximal parallelism, the quality of the websocket connection required for Server interactivity was negatively impacted, often to the extent that the websocket wouldn't connect until WebAssembly resources had finished downloading completely, largely defeating the purpose of the Auto render mode.

This PR makes the following improvements:
* Removes a problematic timeout on loading the WebAssembly boot config. This fixes a problem where Server interactivity was always being used when the boot config took too long to load.
* Introduces a limit to the maximum parallel WebAssembly resource downloads when an Auto component initiates the startup of the WebAssembly runtime. This limit is set to 1 and overrides any user-specified limit.
* Fixes an issue where the circuit sometimes remains open even if WebAssembly gets selected for Auto interactivity.

I provided a preview of these changes in dotnet/aspnetcore#52154 (comment) so that customers could try them out, and the feedback so far has been very positive.

Fixes dotnet/aspnetcore#52154

## Customer Impact

A significant number of customers reported being affected by this problem in issues like dotnet/aspnetcore#52154. I supplied customers with a preview of the fix that they could patch it into their app, and many indicated that their problems were resolved by the fix. The Auto render mode was one of the key features released in .NET 8, so it's important that it works in the way we've been advertising.
 
## Regression?

- [ ] Yes
- [X] No

## Risk

- [ ] High
- [ ] Medium
- [X] Low

The core Auto render mode functionality is unaffected by this change - we added small tweaks to adjust the throttling amount and remove a problematic timeout. Additional tests were added to verify the changes in behavior, and we've been testing these changes manually to ensure they work well in real-world scenarios (various device types and connection qualities).

## Verification

- [X] Manual (required)
- [X] Automated

## Packaging changes reviewed?

- [ ] Yes
- [ ] No
- [X] N/A
  • Loading branch information
Mackinnon Buck committed Jan 16, 2024
1 parent 4caae77 commit 11d458e
Show file tree
Hide file tree
Showing 4 changed files with 19 additions and 20 deletions.
2 changes: 1 addition & 1 deletion dist/Release/blazor.web.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion src/Boot.Web.ts
Expand Up @@ -37,7 +37,6 @@ function boot(options?: Partial<WebStartOptions>) : Promise<void> {
started = true;
options = options || {};
options.logLevel ??= LogLevel.Error;
Blazor._internal.loadWebAssemblyQuicklyTimeout = 3000;
Blazor._internal.isBlazorWeb = true;

// Defined here to avoid inadvertently imported enhanced navigation
Expand Down
1 change: 0 additions & 1 deletion src/GlobalExports.ts
Expand Up @@ -79,7 +79,6 @@ export interface IBlazor {
receiveWebAssemblyDotNetDataStream?: (streamId: number, data: any, bytesRead: number, errorMessage: string) => void;
receiveWebViewDotNetDataStream?: (streamId: number, data: any, bytesRead: number, errorMessage: string) => void;
attachWebRendererInterop?: typeof attachWebRendererInterop;
loadWebAssemblyQuicklyTimeout?: number;
isBlazorWeb?: boolean;

// JSExport APIs
Expand Down
35 changes: 18 additions & 17 deletions src/Services/WebRootComponentManager.ts
Expand Up @@ -9,7 +9,6 @@ import { disposeCircuit, hasStartedServer, isCircuitAvailable, startCircuit, sta
import { hasLoadedWebAssemblyPlatform, hasStartedLoadingWebAssemblyPlatform, hasStartedWebAssembly, isFirstUpdate, loadWebAssemblyPlatformIfNotStarted, resolveInitialUpdate, setWaitForRootComponents, startWebAssembly, updateWebAssemblyRootComponents, waitForBootConfigLoaded } from '../Boot.WebAssembly.Common';
import { MonoConfig } from 'dotnet';
import { RootComponentManager } from './RootComponentManager';
import { Blazor } from '../GlobalExports';
import { getRendererer } from '../Rendering/Renderer';
import { isPageLoading } from './NavigationEnhancement';
import { setShouldPreserveContentOnInteractiveComponentDisposal } from '../Rendering/BrowserRenderer';
Expand Down Expand Up @@ -100,12 +99,18 @@ export class WebRootComponentManager implements DescriptorHandler, RootComponent
return;
}

if (descriptor.type === 'auto' || descriptor.type === 'webassembly') {
// Eagerly start loading the WebAssembly runtime, even though we're not
// activating the component yet. This is becuase WebAssembly resources
// may take a long time to load, so starting to load them now potentially reduces
// the time to interactvity.
// When encountering a component with a WebAssembly or Auto render mode,
// start loading the WebAssembly runtime, even though we're not
// activating the component yet. This is becuase WebAssembly resources
// may take a long time to load, so starting to load them now potentially reduces
// the time to interactvity.
if (descriptor.type === 'webassembly') {
this.startLoadingWebAssemblyIfNotStarted();
} else if (descriptor.type === 'auto') {
// If the WebAssembly runtime starts downloading because an Auto component was added to
// the page, we limit the maximum number of parallel WebAssembly resource downloads to 1
// so that the performance of any Blazor Server circuit is minimally impacted.
this.startLoadingWebAssemblyIfNotStarted(/* maxParallelDownloadsOverride */ 1);
}

const ssrComponentId = this._nextSsrComponentId++;
Expand All @@ -120,26 +125,20 @@ export class WebRootComponentManager implements DescriptorHandler, RootComponent
this.circuitMayHaveNoRootComponents();
}

private async startLoadingWebAssemblyIfNotStarted() {
private async startLoadingWebAssemblyIfNotStarted(maxParallelDownloadsOverride?: number) {
if (hasStartedLoadingWebAssemblyPlatform()) {
return;
}

setWaitForRootComponents();

const loadWebAssemblyPromise = loadWebAssemblyPlatformIfNotStarted();

// If WebAssembly resources can't be loaded within some time limit,
// we take note of this fact so that "auto" components fall back
// to using Blazor Server.
setTimeout(() => {
if (!hasLoadedWebAssemblyPlatform()) {
this.onWebAssemblyFailedToLoadQuickly();
}
}, Blazor._internal.loadWebAssemblyQuicklyTimeout);

const bootConfig = await waitForBootConfigLoaded();

if (maxParallelDownloadsOverride !== undefined) {
bootConfig.maxParallelDownloads = maxParallelDownloadsOverride;
}

if (!areWebAssemblyResourcesLikelyCached(bootConfig)) {
// Since WebAssembly resources aren't likely cached,
// they will probably need to be fetched over the network.
Expand Down Expand Up @@ -299,6 +298,8 @@ export class WebRootComponentManager implements DescriptorHandler, RootComponent
this.updateWebAssemblyRootComponents(batchJson);
}
}

this.circuitMayHaveNoRootComponents();
}

private updateWebAssemblyRootComponents(operationsJson: string) {
Expand Down

0 comments on commit 11d458e

Please sign in to comment.