Document SignalR stateful reconnect in spec docs#66771
Open
BrennanConroy wants to merge 1 commit into
Open
Conversation
Updates the SignalR spec docs to cover stateful reconnect (issue #53103). TransportProtocols.md: - Rename the negotiate parameter from useAck to useStatefulReconnect to match the shipped wire name. - Replace the dead [ack protocol](#todo) placeholder link with a real Stateful Reconnect section: negotiation handshake, WebSockets-only scope, and reconnect flow (reuse connectionToken on a new WebSocket, server reattaches instead of returning 409 Conflict, 404 if the connection has already been cleaned up). - Note in the WebSockets transport section that the 409 Conflict rule is relaxed for stateful-reconnect reattach. - Scope the transport's responsibility to reattach + notify the upper layer; explicitly defer buffering/ack/replay to upper layers (Hub protocol as an example). HubProtocol.md: - Link the existing Ack/Sequence message-table rows to the new semantics section. - Add a Stateful Reconnect section describing the trackable message set (HubInvocationMessage subtypes only), 1-based per-direction sequence IDs, duplicate-discard rule, periodic Ack semantics, and the Sequence-first reconnect handshake (carrying highest_sent + 1, sent even with no buffered messages). - Keep the section transport-agnostic: TransportProtocols.md is referenced only as an example transport binding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Updates the SignalR spec documentation to reflect the shipped “stateful reconnect” negotiate flag and to add explanatory text for the reconnect/ack/replay behavior (especially around Ack and Sequence messages) across transport and hub protocol docs.
Changes:
- Renames the negotiate property from
useAcktouseStatefulReconnectand replaces the old placeholder link with a new Stateful Reconnect section in the transport spec. - Extends the WebSockets transport section to describe reconnect/reattach behavior when stateful reconnect is enabled.
- Adds a Hub-protocol Stateful Reconnect section and cross-references from the
Ack/Sequencemessage table entries.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/SignalR/docs/specs/TransportProtocols.md | Updates negotiate flag naming and adds transport-level stateful reconnect documentation, including WebSockets reattach behavior. |
| src/SignalR/docs/specs/HubProtocol.md | Adds hub-protocol semantics documentation for Ack/Sequence and stateful reconnect behavior. |
Comments suppressed due to low confidence (2)
src/SignalR/docs/specs/TransportProtocols.md:218
- In this section, the opening description states that “each side buffers … acknowledges … and replays” messages, but the last paragraph says the transport itself does not buffer/ack/replay and that this is defined by the upper layer. Consider rewording the opening paragraph to attribute buffering/ack/replay to the upper layer (e.g., Hub protocol) to avoid implying the transport layer provides these semantics.
Stateful reconnect lets a client transparently recover from a transport-level
disconnect without losing in-flight messages. When enabled, each side buffers
the messages it has sent and the receiver acknowledges the messages it has
processed; on reconnect the unacknowledged messages are replayed in order.
src/SignalR/docs/specs/HubProtocol.md:214
- The duplicate-discard rule in this paragraph is incorrect/incomplete: after a reconnect, duplicates are identified relative to the receiver’s highest already-processed sequence ID (i.e., IDs
<= highestProcessedare ignored). In practice, replayed messages can have IDs >= theSequence.sequenceIdbut still be duplicates (e.g., replaying IDs that were already processed), so “strictly less than the new starting ID” is not the right condition.
The receiver uses the `sequenceId` from the incoming `Sequence` message to
align its inbound counter and discard duplicates: any subsequently
received trackable message with an ID strictly less than the new starting
ID is a duplicate from before the reconnect and must be ignored.
Comment on lines
+150
to
+152
| When the client opts in to stateful reconnect (see [Stateful Reconnect](#stateful-reconnect)) it should also append the `useStatefulReconnect=true` query string parameter when opening the WebSocket. This signals that the request is a reconnect attempt for an existing connection and, on a fresh connection, that subsequent transport-level disconnects should be recoverable. | ||
|
|
||
| Establishing a second WebSocket connection when there is already a WebSocket connection associated with the Endpoints connection is not permitted and will fail with a `409 Conflict` status code. The one exception is when stateful reconnect was negotiated on the original connection: in that case, opening a new WebSocket to `[endpoint-base]` with the same `id` reattaches to the existing connection and the previous WebSocket is closed. If the existing connection has already been cleaned up (for example, the reconnect grace period has elapsed) the server responds with `404 Not Found` instead. |
Comment on lines
+206
to
+214
| whose `sequenceId` is the ID it will use for the **next** trackable | ||
| message it sends. This is equivalent to one greater than the highest | ||
| trackable ID it has previously sent, regardless of whether those messages | ||
| were acknowledged. | ||
|
|
||
| The receiver uses the `sequenceId` from the incoming `Sequence` message to | ||
| align its inbound counter and discard duplicates: any subsequently | ||
| received trackable message with an ID strictly less than the new starting | ||
| ID is a duplicate from before the reconnect and must be ignored. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Issue #53103 points out that
src/SignalR/docs/specs/TransportProtocols.mdis out of date with respect to the stateful reconnect feature: it names the negotiate propertyuseAck, but the shipped wire name isuseStatefulReconnect, and it links to a literal[ack protocol](#todo)placeholder section that was never written.HubProtocol.mdalready had the wire formats for theAckandSequencemessages, but no description of when/why they are exchanged or how sequence IDs and replay actually work.What changed
TransportProtocols.mduseAcktouseStatefulReconnectto matchNegotiateProtocol.csandHttpConnectionDispatcher.cs.[ack protocol](#todo)link with a real Stateful Reconnect section: opt-in handshake, WebSockets-only scope, and the reconnect flow (client reusesconnectionTokenon a new WebSocket, server reattaches instead of returning409 Conflict,404if the connection has already been cleaned up).409 Conflictrule is relaxed for stateful-reconnect reattach.HubProtocol.mdAck/Sequencerows in the message-summary table to the new semantics section.src/SignalR/common/Shared/MessageBuffer.cs, documents:HubInvocationMessagesubtypes only (Invocation,StreamInvocation,StreamItem,Completion,CancelInvocation);Ping,Close,Ack,Sequenceand handshakes are not tracked.Acksemantics, including that a laterAcksupersedes earlier ones for the same direction.Sequence-first reconnect handshake:sequenceIdishighest_sent + 1, and the message is sent even when there are no buffered messages to replay so the peer knows where IDs continue.TransportProtocols.mdis mentioned only as an example transport binding.Fixes #53103
Notes for reviewers