@@ -18,6 +18,9 @@ import {assertExists, reportError} from '../base/logging';
1818import { EngineBase } from '../trace_processor/engine' ;
1919
2020const RPC_CONNECT_TIMEOUT_MS = 2000 ;
21+ const INITIAL_RETRY_DELAY_MS = 100 ;
22+ const MAX_RETRY_DELAY_MS = 30000 ;
23+ const BACKOFF_MULTIPLIER = 2 ;
2124
2225export interface HttpRpcState {
2326 connected : boolean ;
@@ -34,6 +37,8 @@ export class HttpRpcEngine extends EngineBase {
3437 private disposed = false ;
3538 private queue : Blob [ ] = [ ] ;
3639 private isProcessingQueue = false ;
40+ private retryDelayMs = INITIAL_RETRY_DELAY_MS ;
41+ private retryTimeoutId ?: ReturnType < typeof setTimeout > ;
3742
3843 // Can be changed by frontend/index.ts when passing ?rpc_port=1234 .
3944 static defaultRpcPort = '9001' ;
@@ -47,18 +52,8 @@ export class HttpRpcEngine extends EngineBase {
4752 }
4853
4954 rpcSendRequestBytes ( data : Uint8Array ) : void {
50- if ( this . websocket === undefined ) {
51- if ( this . disposed ) return ;
52- const wsUrl = `ws://${ HttpRpcEngine . getHostAndPort ( this . port ) } /websocket` ;
53- this . websocket = new WebSocket ( wsUrl ) ;
54- this . websocket . onopen = ( ) => this . onWebsocketConnected ( ) ;
55- this . websocket . onmessage = ( e ) => this . onWebsocketMessage ( e ) ;
56- this . websocket . onclose = ( e ) => this . onWebsocketClosed ( e ) ;
57- this . websocket . onerror = ( e ) =>
58- super . fail (
59- `WebSocket error rs=${ ( e . target as WebSocket ) ?. readyState } (ERR:ws)` ,
60- ) ;
61- }
55+ if ( this . disposed ) return ;
56+ this . websocket ??= this . initWebSocket ( ) ;
6257
6358 if ( this . connected ) {
6459 this . websocket . send ( data ) ;
@@ -67,27 +62,67 @@ export class HttpRpcEngine extends EngineBase {
6762 }
6863 }
6964
65+ private initWebSocket ( ) : WebSocket {
66+ const wsUrl = `ws://${ HttpRpcEngine . getHostAndPort ( this . port ) } /websocket` ;
67+ this . websocket = new WebSocket ( wsUrl ) ;
68+ this . websocket . onopen = ( ) => this . onWebsocketConnected ( ) ;
69+ this . websocket . onmessage = ( e ) => this . onWebsocketMessage ( e ) ;
70+ this . websocket . onclose = ( e ) => this . onWebsocketClosed ( e ) ;
71+ this . websocket . onerror = ( e ) => this . onWebsocketError ( e ) ;
72+ return this . websocket ;
73+ }
74+
75+ private onWebsocketError ( e : Event ) : void {
76+ if ( this . disposed ) return ;
77+ const readyState = ( e . target as WebSocket ) ?. readyState ;
78+ console . warn ( `WebSocket error rs=${ readyState } , will retry with backoff` ) ;
79+ // The close event will fire after this, which will trigger the retry logic
80+ }
81+
82+ private scheduleReconnect ( ) : void {
83+ if ( this . disposed ) return ;
84+
85+ console . debug (
86+ `Scheduling WebSocket reconnection in ${ this . retryDelayMs } ms` ,
87+ ) ;
88+
89+ this . retryTimeoutId = setTimeout ( ( ) => {
90+ if ( this . disposed ) return ;
91+ console . debug ( 'Attempting WebSocket reconnection...' ) ;
92+ this . initWebSocket ( ) ;
93+ } , this . retryDelayMs ) ;
94+
95+ // Exponential backoff with cap
96+ this . retryDelayMs = Math . min (
97+ this . retryDelayMs * BACKOFF_MULTIPLIER ,
98+ MAX_RETRY_DELAY_MS ,
99+ ) ;
100+ }
101+
70102 private onWebsocketConnected ( ) {
103+ // Reset retry delay on successful connection
104+ this . retryDelayMs = INITIAL_RETRY_DELAY_MS ;
105+
71106 for ( ; ; ) {
72107 const queuedMsg = this . requestQueue . shift ( ) ;
73108 if ( queuedMsg === undefined ) break ;
74109 assertExists ( this . websocket ) . send ( queuedMsg ) ;
75110 }
111+ console . debug ( 'WebSocket (re)connected on port' , this . port ) ;
76112 this . connected = true ;
77113 }
78114
79115 private onWebsocketClosed ( e : CloseEvent ) {
80116 if ( this . disposed ) return ;
81- if ( e . code === 1006 && this . connected ) {
82- // On macbooks the act of closing the lid / suspending often causes socket
83- // disconnections. Try to gracefully re-connect.
84- console . log ( 'Websocket closed, reconnecting' ) ;
85- this . websocket = undefined ;
86- this . connected = false ;
87- this . rpcSendRequestBytes ( new Uint8Array ( ) ) ; // Triggers a reconnection.
88- } else {
89- super . fail ( `Websocket closed (${ e . code } : ${ e . reason } ) (ERR:ws)` ) ;
90- }
117+
118+ // Always attempt to reconnect with backoff, regardless of close code
119+ console . debug (
120+ `WebSocket closed (code=${ e . code } , reason=${ e . reason || 'none' } , wasConnected=${ this . connected } ), scheduling reconnect` ,
121+ ) ;
122+
123+ this . websocket = undefined ;
124+ this . connected = false ;
125+ this . scheduleReconnect ( ) ;
91126 }
92127
93128 private onWebsocketMessage ( e : MessageEvent ) {
@@ -147,6 +182,13 @@ export class HttpRpcEngine extends EngineBase {
147182 [ Symbol . dispose ] ( ) {
148183 this . disposed = true ;
149184 this . connected = false ;
185+
186+ // Clear any pending retry timeout
187+ if ( this . retryTimeoutId !== undefined ) {
188+ clearTimeout ( this . retryTimeoutId ) ;
189+ this . retryTimeoutId = undefined ;
190+ }
191+
150192 const websocket = this . websocket ;
151193 this . websocket = undefined ;
152194 websocket ?. close ( ) ;
0 commit comments