-
Notifications
You must be signed in to change notification settings - Fork 10.4k
[Blazor] Adds support for pausing and resuming circuits with support for pushing the state to the client #62271
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
Changes from all commits
8f3d70b
658753f
dfeea17
913957e
eeaa848
4a5034d
4499ec6
c240d64
dd7959d
ffcc8e1
6eb17b1
63e12e3
f184179
b3fd9df
bbee217
c1aa891
ef52ce9
b6632dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -323,7 +323,19 @@ public async ValueTask<string> ResumeCircuit( | |
return null; | ||
} | ||
} | ||
else if (!RootComponentIsEmpty(rootComponents) || !string.IsNullOrEmpty(applicationState)) | ||
else if (!RootComponentIsEmpty(rootComponents) && !string.IsNullOrEmpty(applicationState)) | ||
{ | ||
persistedCircuitState = _circuitPersistenceManager.FromProtectedState(rootComponents, applicationState); | ||
if (persistedCircuitState == null) | ||
{ | ||
// If we couldn't deserialize the persisted state, signal that. | ||
Log.InvalidInputData(_logger); | ||
await NotifyClientError(Clients.Caller, "The root components or application state provided are invalid."); | ||
Context.Abort(); | ||
return null; | ||
} | ||
} | ||
else | ||
{ | ||
Log.InvalidInputData(_logger); | ||
await NotifyClientError( | ||
|
@@ -335,12 +347,6 @@ await NotifyClientError( | |
Context.Abort(); | ||
return null; | ||
} | ||
else | ||
{ | ||
// For now abort, since we currently don't support resuming circuits persisted to the client. | ||
Context.Abort(); | ||
return null; | ||
} | ||
|
||
try | ||
{ | ||
|
@@ -389,6 +395,39 @@ static bool RootComponentIsEmpty(string rootComponents) => | |
string.IsNullOrEmpty(rootComponents) || rootComponents == "[]"; | ||
} | ||
|
||
// Client initiated pauses work as follows: | ||
// * The client calls PauseCircuit, we dissasociate the circuit from the connection. | ||
// * We trigger the circuit pause to collect the current root components and dispose the current circuit. | ||
// * We push the current root components and application state to the client. | ||
// * If that succeeds, the client receives the state and we are done. | ||
// * If that fails, we will fall back to the server-side cache storage. | ||
// * The client will disconnect after receiving the state or after a 30s timeout. | ||
// * From that point on, it can choose to resume the circuit by calling ResumeCircuit with or without the state | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if client calls the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We push to the client when the client initiates the pause explicitly, so at that point the client must have called |
||
// depending on whether the transfer was successful. | ||
// * Most of the time we expect the state push to succeed, if that fails, the possibilites are: | ||
// * Client tries to resume before the state has been saved to the server-side cache storage. | ||
// * Resumption fails as the state is not there. | ||
// * The state eventually makes it to the server-side cache storage, but the client will have already given up and | ||
// the state will eventually go away by virtue of the cache expiration policy on it. | ||
// * The state has been saved to the server-side cache storage. This is what we expect to happen most of the time in the | ||
// rare event that the client push fails. | ||
// * This case becomes equivalent to the "ungraceful pause" case, where the client has no state and the server has the state. | ||
public async ValueTask<bool> PauseCircuit() | ||
{ | ||
var circuitHost = await GetActiveCircuitAsync(); | ||
if (circuitHost == null) | ||
{ | ||
return false; | ||
} | ||
|
||
_ = _circuitRegistry.PauseCircuitAsync(circuitHost, Context.ConnectionId); | ||
|
||
// This only signals that pausing the circuit has started. | ||
// The client will receive the root components and application state in a separate message | ||
// from the server. | ||
return true; | ||
} | ||
|
||
public async ValueTask BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) | ||
{ | ||
var circuitHost = await GetActiveCircuitAsync(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we avoid encoding/decoding root component state by serializing it directly to a
string
rather than to abyte[]
first viaPersistedComponentState.PersistAsJson()
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will serialize it into a byte array in any case.
I don't think it's super important given that this won't be a super common thing and that we already serialize/stringify 2 or 3 times more than necessary.
I feel that it's better to not do this right now and wait for a time to optimize serialization paths all the way through as a separate piece of work (post 10)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've filed #62312 to cover going through this stuff across the board