Browse Source

Merge pull request #15219 from vvlladd28/fix/ws-reconnect-backoff-rate-limit

Fixed WS reconnect loop and notification spam when session limit is reached
pull/15262/head
Viacheslav Klimov 7 months ago
committed by GitHub
parent
commit
2a3ac2972b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 49
      ui-ngx/src/app/core/ws/websocket.service.ts

49
ui-ngx/src/app/core/ws/websocket.service.ts

@ -29,9 +29,11 @@ import {
WebsocketDataMsg
} from '@shared/models/telemetry/telemetry.models';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { NotificationType } from '@core/notification/notification.models';
import Timeout = NodeJS.Timeout;
const RECONNECT_INTERVAL = 2000;
const MAX_RECONNECT_INTERVAL = 60000;
const WS_IDLE_TIMEOUT = 90000;
const MAX_PUBLISH_COMMANDS = 10;
@ -57,6 +59,16 @@ export abstract class WebsocketService<T extends WsSubscriber> implements WsServ
errorName = 'WebSocket Error';
// Exponential backoff: tracks the number of consecutive failed reconnect attempts.
// Reset only after a productive connection (i.e. at least one message received).
// This prevents the open→immediately-closed cycle from resetting the counter.
private reconnectAttempts = 0;
// Stores the last close-event error code shown to the user during a reconnect cycle.
// Only suppresses duplicate notifications for the same error code; a new error code is still shown.
// Cleared after receiving a successful message.
private lastShownCloseCode: number | null = null;
protected constructor(protected store: Store<AppState>,
protected authService: AuthService,
protected ngZone: NgZone,
@ -126,6 +138,8 @@ export abstract class WebsocketService<T extends WsSubscriber> implements WsServ
this.subscribersCount = 0;
this.cmdWrapper.clear();
if (close) {
this.reconnectAttempts = 0;
this.lastShownCloseCode = null;
this.closeSocket();
}
}
@ -221,6 +235,10 @@ export abstract class WebsocketService<T extends WsSubscriber> implements WsServ
this.processOnMessage(message as WebsocketDataMsg);
}
this.checkToClose();
if (this.reconnectAttempts) {
this.reconnectAttempts = 0;
this.lastShownCloseCode = null;
}
}
private onError(errorEvent) {
@ -231,8 +249,13 @@ export abstract class WebsocketService<T extends WsSubscriber> implements WsServ
}
private onClose(closeEvent: CloseEvent) {
// Show error notification only when the error code changes to prevent notification spam,
// while still surfacing new, potentially actionable errors during a reconnect cycle.
// lastShownCloseCode is cleared only after a productive connection (onMessage).
if (closeEvent && closeEvent.code > 1001 && closeEvent.code !== 1006
&& closeEvent.code !== 1011 && closeEvent.code !== 1012 && closeEvent.code !== 4500) {
&& closeEvent.code !== 1011 && closeEvent.code !== 1012 && closeEvent.code !== 4500
&& this.lastShownCloseCode !== closeEvent.code) {
this.lastShownCloseCode = closeEvent.code;
this.showWsError(closeEvent.code, closeEvent.reason);
}
this.isOpening = false;
@ -251,18 +274,28 @@ export abstract class WebsocketService<T extends WsSubscriber> implements WsServ
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
this.reconnectTimer = setTimeout(() => this.tryOpenSocket(), RECONNECT_INTERVAL);
const delay = Math.min(RECONNECT_INTERVAL * Math.pow(2, this.reconnectAttempts), MAX_RECONNECT_INTERVAL);
this.reconnectAttempts = Math.min(this.reconnectAttempts + 1, 10);
this.reconnectTimer = setTimeout(() => this.tryOpenSocket(), delay);
}
}
private showWsError(errorCode: number, errorMsg: string) {
let message = errorMsg;
if (!message) {
message += `${this.errorName}: error code - ${errorCode}.`;
let notificationType: NotificationType = 'error';
if (errorCode === 1008 || (errorMsg && errorMsg.includes('limit reached'))) {
message = 'Too many active sessions. Please close unused browser tabs or sign out from other devices';
notificationType = 'warn';
} else if (errorCode === 1009) {
message = 'Too much data to display. Please refresh the page or narrow your request.';
notificationType = 'warn';
} else if (!message) {
message = `${this.errorName}: error code - ${errorCode}.`;
}
this.store.dispatch(new ActionNotificationShow(
{
message, type: 'error'
}));
this.store.dispatch(new ActionNotificationShow({
message, type: notificationType
}));
}
}

Loading…
Cancel
Save