[MM-64244] Add websocket disconnect reason metric (#31032)
We've recently spent some effort improving websocket reconnection logic. With this commit, I've augmented the websocket reconnect metric to include a disconnect reason. This will help us measure the impact of these changes in production.
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
611b2a8e79
Коммит
761584c040
@@ -49,6 +49,22 @@ if (typeof Event === 'undefined') {
|
||||
};
|
||||
}
|
||||
|
||||
// Mock CloseEvent class if it's not defined
|
||||
if (typeof CloseEvent === 'undefined') {
|
||||
(global as any).CloseEvent = class MockCloseEvent extends (global as any).Event {
|
||||
code: number;
|
||||
reason: string;
|
||||
wasClean: boolean;
|
||||
|
||||
constructor(type: string, options?: {code?: number; reason?: string; wasClean?: boolean}) {
|
||||
super(type);
|
||||
this.code = options?.code || 0;
|
||||
this.reason = options?.reason || '';
|
||||
this.wasClean = options?.wasClean || false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class MockWebSocket {
|
||||
readonly binaryType: BinaryType = 'blob';
|
||||
readonly bufferedAmount: number = 0;
|
||||
|
||||
@@ -19,6 +19,10 @@ export type WebSocketClientConfig = {
|
||||
clientPingInterval: number;
|
||||
}
|
||||
|
||||
// Custom close error codes must be in the range of 4000-4999
|
||||
const clientPingTimeoutErrCode = 4000;
|
||||
const clientSequenceMismatchErrCode = 4001;
|
||||
|
||||
const defaultWebSocketClientConfig: WebSocketClientConfig = {
|
||||
maxWebSocketFails: 7,
|
||||
minWebSocketRetryTime: 3000, // 3 seconds
|
||||
@@ -45,6 +49,7 @@ export default class WebSocketClient {
|
||||
private serverSequence: number;
|
||||
private connectFailCount: number;
|
||||
private responseCallbacks: {[x: number]: ((msg: any) => void)};
|
||||
private lastErrCode: string | null;
|
||||
|
||||
/**
|
||||
* @deprecated Use messageListeners instead
|
||||
@@ -110,6 +115,7 @@ export default class WebSocketClient {
|
||||
this.config = {...defaultWebSocketClientConfig, ...config};
|
||||
this.pingInterval = null;
|
||||
this.waitingForPong = false;
|
||||
this.lastErrCode = null;
|
||||
}
|
||||
|
||||
// on connect, only send auth cookie and blank state.
|
||||
@@ -198,19 +204,32 @@ export default class WebSocketClient {
|
||||
// Add connection id, and last_sequence_number to the query param.
|
||||
// We cannot use a cookie because it will bleed across tabs.
|
||||
// We cannot also send it as part of the auth_challenge, because the session cookie is already sent with the request.
|
||||
const websocketUrl = `${connectionUrl}?connection_id=${this.connectionId}&sequence_number=${this.serverSequence}${this.postedAck ? '&posted_ack=true' : ''}`;
|
||||
let websocketUrl = `${connectionUrl}?connection_id=${this.connectionId}&sequence_number=${this.serverSequence}`;
|
||||
|
||||
if (this.postedAck) {
|
||||
websocketUrl += '&posted_ack=true';
|
||||
}
|
||||
|
||||
if (this.lastErrCode) {
|
||||
websocketUrl += `&disconnect_err_code=${encodeURIComponent(this.lastErrCode)}`;
|
||||
}
|
||||
|
||||
if (this.config.newWebSocketFn) {
|
||||
this.conn = this.config.newWebSocketFn(websocketUrl);
|
||||
} else {
|
||||
this.conn = new WebSocket(websocketUrl);
|
||||
}
|
||||
|
||||
const onclose = () => {
|
||||
const onclose = (event: CloseEvent) => {
|
||||
this.conn = null;
|
||||
this.responseSequence = 1;
|
||||
|
||||
if (!this.lastErrCode && event && event.code) {
|
||||
this.lastErrCode = `${event.code}`;
|
||||
}
|
||||
|
||||
if (this.connectFailCount === 0) {
|
||||
console.log('websocket closed'); //eslint-disable-line no-console
|
||||
console.log(`websocket closed: ${this.lastErrCode}`); //eslint-disable-line no-console
|
||||
}
|
||||
|
||||
this.connectFailCount++;
|
||||
@@ -254,6 +273,8 @@ export default class WebSocketClient {
|
||||
this.sendMessage('authentication_challenge', {token});
|
||||
}
|
||||
|
||||
this.lastErrCode = null;
|
||||
|
||||
if (this.connectFailCount > 0) {
|
||||
console.log('websocket re-established connection'); //eslint-disable-line no-console
|
||||
|
||||
@@ -294,6 +315,11 @@ export default class WebSocketClient {
|
||||
|
||||
console.log('ping received no response within time limit: re-establishing websocket'); //eslint-disable-line no-console
|
||||
|
||||
const closeEvent = new CloseEvent('close', {
|
||||
code: clientPingTimeoutErrCode,
|
||||
wasClean: false,
|
||||
});
|
||||
|
||||
// Calling conn.close() will trigger the onclose callback,
|
||||
// but sometimes with a significant delay. So instead, we
|
||||
// call the onclose callback ourselves immediately. We also
|
||||
@@ -303,7 +329,7 @@ export default class WebSocketClient {
|
||||
this.responseSequence = 1;
|
||||
this.conn.onclose = () => {};
|
||||
this.conn.close();
|
||||
onclose();
|
||||
onclose(closeEvent);
|
||||
},
|
||||
this.config.clientPingInterval);
|
||||
|
||||
@@ -369,10 +395,24 @@ export default class WebSocketClient {
|
||||
// we just disconnect and reconnect.
|
||||
if (msg.seq !== this.serverSequence) {
|
||||
console.log('missed websocket event, act_seq=' + msg.seq + ' exp_seq=' + this.serverSequence); //eslint-disable-line no-console
|
||||
// We are not calling this.close() because we need to auto-restart.
|
||||
|
||||
const closeEvent = new CloseEvent('close', {
|
||||
code: clientSequenceMismatchErrCode,
|
||||
wasClean: false,
|
||||
});
|
||||
|
||||
// Calling conn.close() will trigger the onclose callback,
|
||||
// but sometimes with a significant delay. So instead, we
|
||||
// call the onclose callback ourselves immediately. We also
|
||||
// unset the callback on the old connection to ensure it
|
||||
// is only called once.
|
||||
this.connectFailCount = 0;
|
||||
this.responseSequence = 1;
|
||||
this.conn?.close(); // Will auto-reconnect after MIN_WEBSOCKET_RETRY_TIME.
|
||||
if (this.conn) {
|
||||
this.conn.onclose = () => {};
|
||||
this.conn.close();
|
||||
onclose(closeEvent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.serverSequence = msg.seq + 1;
|
||||
@@ -507,13 +547,14 @@ export default class WebSocketClient {
|
||||
this.connectFailCount = 0;
|
||||
this.responseSequence = 1;
|
||||
this.clearReconnectTimeout();
|
||||
this.lastErrCode = null;
|
||||
this.stopPingInterval();
|
||||
|
||||
if (this.conn && this.conn.readyState === WebSocket.OPEN) {
|
||||
this.conn.onclose = () => {};
|
||||
this.conn.close();
|
||||
this.conn = null;
|
||||
console.log('websocket closed'); //eslint-disable-line no-console
|
||||
console.log('websocket closed manually'); //eslint-disable-line no-console
|
||||
}
|
||||
|
||||
if (this.onlineHandler) {
|
||||
|
||||
Ссылка в новой задаче
Block a user