Skip to content

Implement Circuit.RequestCircuitPauseAsync#66265

Merged
ilonatommy merged 13 commits intomainfrom
fix-66244
Apr 22, 2026
Merged

Implement Circuit.RequestCircuitPauseAsync#66265
ilonatommy merged 13 commits intomainfrom
fix-66244

Conversation

@ilonatommy
Copy link
Copy Markdown
Member

@ilonatommy ilonatommy commented Apr 10, 2026

Design doc: #66244

Test scenarios covered: #66244 (comment). G2 remains uncovered: the scenario describes a correct existing behavior that can't be deterministically tested without a framework-internal hook in JS, which we chose not to add.

Fixes #62327.

New API

 namespace Microsoft.AspNetCore.Components.Server.Circuits;
 
 public sealed class Circuit
 {
     /// <summary>
     /// Requests that the connected client begin the graceful circuit-pause flow.
     /// </summary>
     /// <remarks>
     /// The operation is idempotent. Observe completion through
     /// CircuitHandler.OnConnectionDownAsync and CircuitHandler.OnCircuitClosedAsync.
     /// </remarks>
     public ValueTask<bool> RequestCircuitPauseAsync(CancellationToken cancellationToken = default);

How it works

Dispatcher scheduling for sync work: RequestCircuitPauseAsync dispatches the JS.RequestPause send onto the circuit's dispatcher via Renderer.Dispatcher.InvokeAsync. This serializes it with any current sync work (rendering, synchronous event handlers) — the pause message is never sent while a render batch is being produced.

Client hook for async work: The dispatcher cannot reliably detect completion of async work (event handlers at await points, async component lifecycle methods). A child component's async OnParametersSetAsync, for example, creates a new renderer work item outside the scope of the original inbound handler. Instead of tracking this on the server, the client receives JS.RequestPause and can defer the actual pause via an optional onPauseRequested callback in CircuitStartOptions:

Blazor.start({
    circuit: {
        onPauseRequested: async () => {
            await currentPayment; // wait for critical work
            return true;          // now safe to pause
        }
    }
});

If the callback returns false, the pause is deferred — the client does not call PauseCircuit. The server's RequestCircuitPauseAsync still returns true (the message was accepted and sent). Without a callback, the client pauses immediately upon receiving JS.RequestPause.

State validation: The method checks four conditions before accepting:

  • Circuit is not disposed
  • Circuit completed initialization (_initialized)
  • OnConnectionUpAsync has fired (_onConnectionUpFired)
  • Client transport is connected

Telemetry: 4 log events — ServerPauseRequested (120), ServerPauseAccepted (121), ServerPauseRejected (122), ServerPauseFailed (123). OperationCanceledException logs as rejected, other exceptions log as failed.

@ilonatommy ilonatommy requested a review from a team as a code owner April 10, 2026 11:55
Copilot AI review requested due to automatic review settings April 10, 2026 11:55
@ilonatommy ilonatommy self-assigned this Apr 10, 2026
@github-actions github-actions Bot added the area-blazor Includes: Blazor, Razor Components label Apr 10, 2026
@ilonatommy ilonatommy added the feature-circuit-lifecycle Issues to do with blazor server lifecycle events label Apr 10, 2026
@ilonatommy ilonatommy added this to the 11.0-preview4 milestone Apr 10, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a server-initiated entry point for Blazor Server’s existing graceful pause/resume flow by introducing a new Circuit.RequestCircuitPauseAsync API and wiring a new JS.RequestPause SignalR message that triggers the client-side pause UI/handshake.

Changes:

  • Introduces public API Circuit.RequestCircuitPauseAsync(...) and implements server-side dispatch via CircuitHost.RequestPauseAsync(...).
  • Adds client-side handler for JS.RequestPause that triggers the existing CircuitManager.pause(remote: true) path.
  • Adds new unit and E2E coverage plus a test page/handler in Components.TestServer to exercise server-triggered pause/resume scenarios.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts Handles JS.RequestPause by invoking client pause flow
src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PersistentState/ServerPauseTest.razor Test page + JS-invokable entry to trigger server pause by circuit id
src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs Registers a circuit handler used to track circuits for the test page
src/Components/test/testassets/Components.TestServer/PauseTrackingHandler.cs Tracks active circuits so the test can find the correct Circuit instance
src/Components/test/E2ETest/ServerExecutionTests/ServerTriggeredPauseTest.cs New E2E tests validating server-triggered pause behavior and UI
src/Components/Server/test/Circuits/CircuitRegistryTest.cs Adds coverage for stale connection-id pause behavior after reconnect
src/Components/Server/test/Circuits/CircuitHostTest.cs Adds unit coverage for server pause request acceptance/rejection paths
src/Components/Server/src/PublicAPI.Unshipped.txt Declares the newly added public API for validation
src/Components/Server/src/Circuits/CircuitHost.cs Implements server-side pause request dispatch and logging
src/Components/Server/src/Circuits/Circuit.cs Exposes the new RequestCircuitPauseAsync public API

Comment thread src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts Outdated
Comment thread src/Components/test/testassets/Components.TestServer/PauseTrackingHandler.cs Outdated
Comment thread src/Components/test/E2ETest/ServerExecutionTests/ServerTriggeredPauseTest.cs Outdated
Comment thread src/Components/test/E2ETest/ServerExecutionTests/ServerTriggeredPauseTest.cs Outdated
Comment thread src/Components/Server/test/Circuits/CircuitHostTest.cs
Copy link
Copy Markdown
Member

@oroztocil oroztocil left a comment

Choose a reason for hiding this comment

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

Good stuff 👍

Comment thread src/Components/Server/src/Circuits/CircuitHost.cs Outdated
Comment thread src/Components/Server/src/Circuits/CircuitHost.cs
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Comment thread src/Components/Server/src/Circuits/CircuitHost.cs Outdated
Comment thread src/Components/Server/src/Circuits/CircuitHost.cs Outdated
Comment thread src/Components/Server/src/Circuits/CircuitHost.cs Outdated
Comment thread src/Components/Server/src/Circuits/CircuitHost.cs
Comment thread src/Components/Server/test/Circuits/CircuitHostTest.cs
Comment thread src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts Outdated
The callback controls timing, not permission. The pause always
happens once the promise resolves. This matches javiercn's feedback
that the framework owns the decision, the app owns the timing.

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

@javiercn javiercn left a comment

Choose a reason for hiding this comment

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

Looks great!

I have to take a second look later, but I don't want to block on it.

@ilonatommy ilonatommy merged commit 048709d into main Apr 22, 2026
25 checks passed
@ilonatommy ilonatommy deleted the fix-66244 branch April 22, 2026 14:37
@wtgodbe wtgodbe modified the milestones: 11.0-preview4, 11.0-preview5 Apr 24, 2026
@ilonatommy
Copy link
Copy Markdown
Member Author

/backport to release/11.0-preview4

@github-actions
Copy link
Copy Markdown
Contributor

Started backporting to release/11.0-preview4 (link to workflow run)

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 feature-circuit-lifecycle Issues to do with blazor server lifecycle events

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Blazor] Trigger state persistence from the server

5 participants