Reconnection Handling
SignalR's withAutomaticReconnect() provides built-in retry with configurable delays. Flow Studio layers on top of this with UI state indicators, event replay on reconnect, and graceful degradation to polling when all reconnection attempts fail.
Automatic Reconnect Configuration
The retry delay sequence is configured when the HubConnection is built. Flow Studio uses four escalating delays before giving up:
// useExecutionSignalR.ts
const connection = new HubConnectionBuilder()
.withUrl('/hubs/execution', {
accessTokenFactory: () => getAuthToken()
})
.withAutomaticReconnect([1000, 2000, 5000, 10000]) // ms delays between attempts
.configureLogging(LogLevel.Warning)
.build();
The four configured delays total 18 seconds of retry time. After the fourth attempt fails, SignalR fires the onclose callback and does not retry further. At that point, the polling fallback activates automatically.
Reconnection State Machine
Connection Lost
Network interruption or server restart drops the WebSocket. SignalR fires onreconnecting(error).
Retry Attempts (1s → 2s → 5s → 10s)
SignalR automatically retries. The reconnecting banner in the Observer Panel shows the attempt count. Execution continues on the server during this window — events are queued server-side.
Reconnect Succeeded
SignalR fires onreconnected(connectionId). The client re-joins the execution group and performs an event replay catch-up to fill any gaps.
All Retries Exhausted → Polling Fallback
SignalR fires onclose(error). The polling fallback activates and the "Live updates unavailable" banner replaces the reconnecting indicator.
Reconnection Event Handlers
// useExecutionSignalR.ts — lifecycle hooks
connection.onreconnecting((error) => {
setConnectionState('reconnecting');
uiStore.showReconnectingBanner(error?.message);
// Don't clear existing node states — they remain visible during reconnect
});
connection.onreconnected(async (connectionId) => {
setConnectionState('connected');
uiStore.hideReconnectingBanner();
// Re-join the execution group (server forgets group membership on disconnect)
await connection.invoke('JoinExecutionGroup', executionId);
// Catch up on events missed during the disconnect window
await replayMissedEvents(executionId, lastReceivedEventTimestamp);
});
connection.onclose((error) => {
setConnectionState('disconnected');
// Activate polling fallback
setPollingEnabled(true);
uiStore.showPollingFallbackBanner();
});
Event Replay on Reconnect
When the connection is re-established, the client may have missed events that occurred during the disconnection window. The replayMissedEvents function fetches a snapshot from the REST API and reconciles it with current UI state:
// executionReplay.ts
async function replayMissedEvents(
executionId: string,
since: string // ISO 8601 timestamp of last received event
) {
const snapshot = await fetch(
`/api/v1/process-engine/executions/${executionId}/snapshot?since=${since}`
).then(r => r.json());
// Apply each node's current status from the snapshot
for (const node of snapshot.nodes) {
designerModeStore.setNodeStatus(node.nodeId, {
status: node.status,
durationMs: node.durationMs,
output: node.output
});
}
// If the execution finished during the disconnect, handle completion
if (['completed', 'failed', 'cancelled'].includes(snapshot.executionStatus)) {
designerModeStore.setExecutionComplete(snapshot.executionStatus);
}
}
/snapshot?since= endpoint returns the current state of all nodes in the execution, not a list of events. This is intentional — replaying individual events in order would be complex and error-prone. Instead, the snapshot gives a consistent point-in-time view that the client applies as a batch update.
UI Indicators During Reconnection
| State | Banner Text | Color | Node Canvas |
|---|---|---|---|
| Reconnecting (attempt 1–4) | "Reconnecting… (attempt N of 4)" | Yellow | Node states frozen; no new updates |
| Reconnected | "Connection restored" (auto-dismisses after 3s) | Green | Snapshot applied; states updated |
| All retries failed | "Live updates unavailable — polling every 2s" | Red | Polling takes over; slower updates |
Server-Side Behaviour During Disconnect
The execution continues running on the server regardless of whether the client is connected. SignalR buffers outgoing messages for a configurable duration (default 30 seconds). If the client reconnects within the buffer window, no events are lost. If the buffer expires, the client uses the snapshot endpoint to catch up.
connection.invoke('JoinExecutionGroup', executionId) in the onreconnected handler. Failure to do so means the client reconnects to the hub but receives no execution events.