From 82f0b234c45ceb9f20cd7ac0448daa82ab0d9899 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 1 Oct 2021 17:27:19 +0100 Subject: [PATCH] Timeout based on last msg received and not ping timeout (#418) * Timeout based on last msg received and not ping timeout * Use object method shorthand Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com> * Types.Milliseconds instead of number Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com> --- frontend/src/Connection.ts | 63 +++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/frontend/src/Connection.ts b/frontend/src/Connection.ts index fe7ffe6..39d296a 100644 --- a/frontend/src/Connection.ts +++ b/frontend/src/Connection.ts @@ -33,8 +33,9 @@ import { StateCacheColumn, } from './components/List'; -const TIMEOUT_BASE = (1000 * 5) as Types.Milliseconds; // 5 seconds -const TIMEOUT_MAX = (1000 * 60 * 5) as Types.Milliseconds; // 5 minutes +const CONNECTION_TIMEOUT_BASE = (1000 * 5) as Types.Milliseconds; // 5 seconds +const CONNECTION_TIMEOUT_MAX = (1000 * 60 * 5) as Types.Milliseconds; // 5 minutes +const MESSAGE_TIMEOUT = (1000 * 60) as Types.Milliseconds; // 60 seconds declare global { interface Window { @@ -74,12 +75,15 @@ export class Connection { private static async socket(): Promise { let socket = await Connection.trySocket(); - let timeout = TIMEOUT_BASE; + let timeout = CONNECTION_TIMEOUT_BASE; while (!socket) { await sleep(timeout); - timeout = Math.min(timeout * 2, TIMEOUT_MAX) as Types.Milliseconds; + timeout = Math.min( + timeout * 2, + CONNECTION_TIMEOUT_MAX + ) as Types.Milliseconds; socket = await Connection.trySocket(); } @@ -112,6 +116,8 @@ export class Connection { }); } + // timer which will force a reconnection if no message is seen for a while + private messageTimeout: Maybe = null; // id sent to the backend used to pair responses private pingId = 0; // timeout handler for ping messages @@ -170,7 +176,8 @@ export class Connection { this.socket.send(`no-more-finality:${chain}`); } - public handleMessages = (messages: FeedMessage.Message[]) => { + private handleMessages = (messages: FeedMessage.Message[]) => { + this.messageTimeout?.reset(); const { nodes, chains, sortBy, selectedColumns } = this.appState; const nodesStateRef = nodes.ref; @@ -429,6 +436,13 @@ export class Connection { }; private bindSocket() { + console.log('Connected'); + // Disconnect if no messages are received in 60s: + this.messageTimeout = resettableTimeout( + this.handleDisconnect, + MESSAGE_TIMEOUT + ); + // Ping periodically to keep the above happy even if no other data is coming in: this.ping(); if (this.appState) { @@ -460,8 +474,6 @@ export class Connection { this.pingId += 1; this.pingSent = timestamp(); this.socket.send(`ping:${this.pingId}`); - - this.pingTimeout = setTimeout(this.ping, 30000); }; private pong(id: number) { @@ -479,7 +491,15 @@ export class Connection { } const latency = timestamp() - this.pingSent; + console.log(`Ping latency: ${latency}ms`); + this.pingSent = null; + + // Schedule a new ping to be sent at least 30s after the last one: + this.pingTimeout = setTimeout( + this.ping, + Math.max(0, MESSAGE_TIMEOUT / 2 - latency) + ); } private newVersion() { @@ -493,6 +513,8 @@ export class Connection { private clean() { clearTimeout(this.pingTimeout); this.pingSent = null; + this.messageTimeout?.cancel(); + this.messageTimeout = null; this.socket.removeEventListener('message', this.handleFeedData); this.socket.removeEventListener('close', this.handleDisconnect); @@ -557,6 +579,7 @@ export class Connection { } private handleDisconnect = async () => { + console.warn('Disconnecting; will attempt reconnect'); this.appUpdate({ status: 'offline' }); this.resetConsensus(); this.clean(); @@ -565,3 +588,29 @@ export class Connection { this.bindSocket(); }; } + +/** + * Fire a function if the timer runs out. You can reset it, or + * cancel it to prevent the function from being fired. + * + * @param onExpired + * @param timeoutMs + * @returns + */ +function resettableTimeout( + onExpired: () => void, + timeoutMs: Types.Milliseconds +) { + let timer = setTimeout(onExpired, timeoutMs); + + return { + reset() { + clearTimeout(timer); + timer = setTimeout(onExpired, timeoutMs); + }, + cancel() { + clearTimeout(timer); + }, + }; +} +type ResettableTimeout = ReturnType;