Skip to content

[Blazor] Improve auto render mode selection strategy #49858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 8, 2023

Conversation

MackinnonBuck
Copy link
Member

Improve auto render mode selection strategy

This PR introduces an improved auto render mode selection strategy that estimates whether WebAssembly resources are already cached.

Description

The current auto mode implementation uses a purely timeout-based mechanism for determining whether Blazor Server or Blazor WebAssembly should be used for an auto component. This PR improves the auto mode implementation to factor in whether WebAssembly resources are already cached by checking the resources.hash value in blazor.boot.json. A larger, 3 second timeout still exists in case that estimation turns out to be incorrect and Blazor WebAssembly takes too long to load.

Here's a summary of the changes in this PR:

  • Added some logic in Boot.Web.ts that uses a hash in localStorage to determine if WebAssembly resources are likely cached
    • That information is used when deciding whether to start Blazor WebAssembly or Blazor Server when an auto component is encountered
  • Moved the core auto mode decision making logic to RootComponentManager and simplified the rules:
    • For any given auto component:
      • If the WebAssembly runtime has started, use WebAssembly
      • If we've determined that WebAssembly resources would take too long to load, use Server
      • Otherwise, don't activate the component yet
    • This mostly limits the job of Boot.Web.ts to be determining the conditions under which to start the WebAssembly or Server runtimes
  • Improved RootComponentManager so it can determine by itself whether an update is pending for SSR'd interactive root components
    • The previous implementation relied on Boot.Web.ts calling rootComponentManager.handleUpdatedComponentDescriptors() everywhere
  • Added auto mode E2E tests

Fixes #49580

@MackinnonBuck MackinnonBuck requested a review from a team as a code owner August 3, 2023 23:45
@ghost ghost added the area-blazor Includes: Blazor, Razor Components label Aug 3, 2023
Comment on lines 45 to 46
this.updateAllRootComponentsAfterRendererAttached(WebRendererId.Server);
this.updateAllRootComponentsAfterRendererAttached(WebRendererId.WebAssembly);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of these calls will wait for rendererAttached and then call updateAllRootComponents.
So technically, if there are both webassembly and server interactive root components, all will flicker twice? I am just wondering, if it will make sense to wait a little bit longer and call the updateRootComponents only once?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No flickering happens here. What these calls do is wait for the specific renderer to attach (which indicates that the platform is fully initialized), then call updateAllRootComponents(). The updateAllRootComponents() function looks at each root component to determine if:

  • It's never been seen before, in which case it gets enabled for interactivity
  • Or, it's been seen before, but it's parameters have changed, in which case send the updated parameters to .NET
  • Or, it's been seen before, but now it's been removed from the page in an SSR update, so we dispose it in .NET
  • Or, none of the above cases happen, in which case it's a no-op for the given component

It can kind of be thought of as calling StateHasChanged(), except for that it doesn't always trigger a re-render. There isn't really a downside to calling it in cases where we don't end up needing to.

return 'server';
}

return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will the null result be interpreted by the callers here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The null indicates that a render mode could not be determined yet. This prevents the component from becoming interactive. At some point in the future (if WebAssembly starts up or fails to load quickly enough), the component will be activated via a call to updateAllRootComponents().

Copy link
Contributor

@mkArtakMSFT mkArtakMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @MackinnonBuck.
I sign off but please wait for at least somebody else to also review this and approve.

Copy link
Member

@surayya-MS surayya-MS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very familiar with this area of code, but everything looks very logical and in line with the previous discussions regarding this issue. Also, great work on the e2e tests!

@MackinnonBuck
Copy link
Member Author

MackinnonBuck commented Aug 8, 2023

Thanks for the reviews, @surayya-MS, @pavelsavara and @mkArtakMSFT.

I've just made some further improvements. If you feel inclined to re-review, I would appreciate it. Here's what I changed:

  • Boot.Web.ts is minimal, and logic around starting .NET runtimes and picking an auto mode has been moved to WebRootComponentManager
  • Unified the "root component" representation with a RootComponentManager interface so that we didn't have two sets of logic (one for a fixed set of root components and one for a mutable set).
  • Changed the auto mode logic so that WebAssembly will be used if WebAssembly resources are loaded, not just if the WebAssembly runtime has started
    • This makes it so that, in the case of stream rendering, WebAssembly gets used if WebAssembly resources finish loading by the time stream rendering completes
  • A Blazor Server circuit does not get created until a Blazor Server component attempts to get activated
    • Prior to this change, the circuit would get created as soon as a Blazor Server component got discovered, even if it wasn't activated for interactivity yet

Copy link
Member

@surayya-MS surayya-MS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the code looks cleaner now. As for the changed logic for using WebAssembly when the resources are loaded and for creating a circuit for Server later seems logical to me.

@MackinnonBuck MackinnonBuck merged commit 0b35f03 into main Aug 8, 2023
@MackinnonBuck MackinnonBuck deleted the mbuck/auto-mode branch August 8, 2023 21:37
@ghost ghost added this to the 8.0-rc1 milestone Aug 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Deterministically resolve the "auto" render mode to "server" or "webassembly"
4 participants