Skip to content

Add visibility change beacon and circuit cleanup heuristic for Blazor Server #62789

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

oroztocil
Copy link
Member

@oroztocil oroztocil commented Jul 17, 2025

Changes

  • Replaces the unload event with pagehide for sending disconnect beacon from the Blazor client to the server.
  • Adds a new beacon message that gets sent from the client when the visibilitychange event fires on the document.
    • The message payload contains the circuit ID and the new page visibility state.
    • The server stores a PageHiddenAt timestamp of the last change to the hidden state for the session.
    • The timestamp is stored on the current CircuitHost instance for the session.
    • The server sets the timestamp to null when receiving a beacon with the visible state. Null value indicates that the page is currently visible and active (as far as the server knows).
  • Adds a new resource optimization path in CircuitRegistry.DisconnectAsync (which is called when the SignalR connection breaks).
    • If the page has been recently hidden and then the SignalR connection broke, we terminate and dispose the circuit data immediately instead of moving it to the CircuitRegistry.DisconnectedCircuits memory cache (and later into the persistent state storage).

Motivation

We replace the unload event with pagehide because the two should fire in (practically) the same situations but the latter is not deprecated and does not cause warnings for the users.

Reasoning behind the visibility-based optimization is as such:

  • The visibilitychange event is the only one that fires somewhat reliably on mobile devices, in particular when switching tabs in the browser, switching to another app, or going to the home screen.
  • In these situations, the tab with the Blazor app (or the entire browser) is often discarded
    and no other events are processed.
  • From the server's point of view, this manifests as such: 1) the server receives the visbility change beacon with the hidden state, 2) depending on the timeout settings, the server recognizes at some point that the SignalR connection has disconnected.
  • Previously, unless we received the disconnect beacon (based on the unload/pagehide event), we would move the disconnected circuit into the DisconnectedCircuits memory cache, and later into the persistent state storage. This is wasteful for the mobile scenarios described above as circuits from closed/discarded tabs would never be restored anyway.
  • However, there are other browser-side optimizations (particularly on desktop) such as background tab freezing or throttling that might lead to the SignalR connection breaking while the page was hidden. In these situations we don't want to dispose the circuits prematurely because they may be restored when the user switches back to the Blazor tab. For this reason, the PR implementation disposes only circuits for recently hidden tabs as these optimizations typically kick in after some time.

Risks

  1. By adding the visibility change beacon, we are increasing traffic and use some additional server resources.
  2. On mobile this change can help significantly. However, on desktop, this does not help much because there the pagehide event fires mostly in the same situations as the visibilitychange - taking into account only situations where we actually want to dispose the circuit (closing the tab, navigating away, hard refresh, closing the browser).
  3. Inevitably, there is a scenario which we make worse: user switches tabs (but the Blazor app is still loaded and communicates with server), then the device loses network connection. Previously, we would move the circuit into DisconnectedCircuits, now we terminate and dispose it (because the server sees the same order of events as e.g. in the mobile app switch scenario) even though the circuit would be eligible for restoration when the device regains network connection and the user foregrounds the Blazor tab.

Questions

  1. Do we want the time threshold, i.e. only dispose circuits for recently hidden pages? If yes, how should we set the threshold? Should it be configurable e.g. via CircuitOptions? What should be the default value? Or, should we compute it based on some HubOptions value (e.g. the client timeout)?
  2. More generally, should this be opt-in or opt-out? How?
  3. Is there a client-side configuration for this that we want to expose?

Fixes #54793

@github-actions github-actions bot added the area-blazor Includes: Blazor, Razor Components label Jul 17, 2025
@oroztocil oroztocil changed the title Add visibility change beacon and circuit cleanup heuristic Add visibility change beacon and circuit cleanup heuristic for Blazor Server Jul 17, 2025
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.

Remove usage of the unload event
1 participant
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy