From 8739a82bb36556561aff979c6385cf4f02fa60b9 Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Wed, 23 Jun 2021 09:56:14 -0700 Subject: [PATCH] Reconnect within MessageReceiver --- ts/background.ts | 11 ++--------- ts/textsecure/Errors.ts | 2 ++ ts/textsecure/MessageReceiver.ts | 26 ++++++++++++++++++++------ ts/textsecure/WebAPI.ts | 11 ++--------- ts/util/BackOff.ts | 14 ++++++++++++++ 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/ts/background.ts b/ts/background.ts index e4613a95dd..8af25fd9cd 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -43,7 +43,7 @@ import { connectToServerWithStoredCredentials } from './util/connectToServerWith import * as universalExpireTimer from './util/universalExpireTimer'; import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation'; import { getSendOptions } from './util/getSendOptions'; -import { BackOff } from './util/BackOff'; +import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff'; import { AppViewType } from './state/ducks/app'; import { isIncoming } from './state/selectors/message'; import { actionCreators } from './state/actions'; @@ -113,14 +113,7 @@ export async function startApp(): Promise { resolveOnAppView = resolve; }); - // Fibonacci timeouts - const reconnectBackOff = new BackOff([ - 5 * 1000, - 10 * 1000, - 15 * 1000, - 25 * 1000, - 40 * 1000, - ]); + const reconnectBackOff = new BackOff(FIBONACCI_TIMEOUTS); window.textsecure.protobuf.onLoad(() => { window.storage.onready(() => { diff --git a/ts/textsecure/Errors.ts b/ts/textsecure/Errors.ts index 11da58f2fc..cbacc215f2 100644 --- a/ts/textsecure/Errors.ts +++ b/ts/textsecure/Errors.ts @@ -186,3 +186,5 @@ export class UnregisteredUserError extends Error { appendStack(this, httpError); } } + +export class ConnectTimeoutError extends Error {} diff --git a/ts/textsecure/MessageReceiver.ts b/ts/textsecure/MessageReceiver.ts index 7aa44666d0..3a7aa9bf33 100644 --- a/ts/textsecure/MessageReceiver.ts +++ b/ts/textsecure/MessageReceiver.ts @@ -41,6 +41,7 @@ import { Sessions, SignedPreKeys, } from '../LibSignalStores'; +import { BackOff, FIBONACCI_TIMEOUTS } from '../util/BackOff'; import { BatcherType, createBatcher } from '../util/batcher'; import { sleep } from '../util/sleep'; import { parseIntOrThrow } from '../util/parseIntOrThrow'; @@ -51,6 +52,7 @@ import utils from './Helpers'; import WebSocketResource, { IncomingWebSocketRequest, } from './WebsocketResources'; +import { ConnectTimeoutError } from './Errors'; import Crypto from './Crypto'; import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto'; import { ContactBuffer, GroupBuffer } from './ContactsParser'; @@ -76,7 +78,6 @@ import { deriveGroupFields, MASTER_KEY_LENGTH } from '../groups'; const GROUPV1_ID_LENGTH = 16; const GROUPV2_ID_LENGTH = 32; const RETRY_TIMEOUT = 2 * 60 * 1000; -const RECONNECT_DELAY = 1 * 1000; const decryptionErrorTypeSchema = z .object({ @@ -225,6 +226,8 @@ class MessageReceiverInner extends EventTarget { wsr?: WebSocketResource; + private readonly reconnectBackOff = new BackOff(FIBONACCI_TIMEOUTS); + constructor( oldUsername: string, username: string, @@ -344,6 +347,11 @@ class MessageReceiverInner extends EventTarget { } catch (error) { this.socketStatus = SocketStatus.CLOSED; + if (error instanceof ConnectTimeoutError) { + await this.onclose(-1, 'Connection timed out'); + return; + } + const event = new Event('error'); event.error = error; await this.dispatchAndWait(event); @@ -439,7 +447,7 @@ class MessageReceiverInner extends EventTarget { async onclose(code: number, reason: string): Promise { window.log.info( - 'websocket closed', + 'MessageReceiver: websocket closed', code, reason || '', 'calledClose:', @@ -460,12 +468,18 @@ class MessageReceiverInner extends EventTarget { this.onEmpty(); } - await sleep(RECONNECT_DELAY); + const timeout = this.reconnectBackOff.getAndIncrement(); - // Try to reconnect (if there is an error - we'll get an - // `error` event from `connect()` and hit the retry backoff logic in - // `ts/background.ts`) + window.log.info(`MessageReceiver: reconnecting after ${timeout}ms`); + await sleep(timeout); + + // Try to reconnect (if there is an HTTP error - we'll get an + // `error` event from `connect()` and hit the secondary retry backoff + // logic in `ts/background.ts`) await this.connect(); + + // Successfull reconnect, reset the backoff timeouts + this.reconnectBackOff.reset(); } checkSocket(): void { diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 94c3890791..9b6ea80acf 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -62,6 +62,7 @@ import { StorageServiceCredentials, } from '../textsecure.d'; +import { ConnectTimeoutError } from './Errors'; import MessageSender from './SendMessage'; // Note: this will break some code that expects to be able to use err.response when a @@ -305,15 +306,7 @@ async function _connectSocket( return new Promise((resolve, reject) => { const timer = setTimeout(() => { - reject( - makeHTTPError( - '_connectSocket: Connection timed out', - -1, - {}, - 'Connection timed out', - stack - ) - ); + reject(new ConnectTimeoutError('Connection timed out')); client.abort(); }, timeout); diff --git a/ts/util/BackOff.ts b/ts/util/BackOff.ts index 8e4d83bff2..e021f60fb9 100644 --- a/ts/util/BackOff.ts +++ b/ts/util/BackOff.ts @@ -1,6 +1,20 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +const SECOND = 1000; + +export const FIBONACCI_TIMEOUTS: ReadonlyArray = [ + 1 * SECOND, + 2 * SECOND, + 3 * SECOND, + 5 * SECOND, + 8 * SECOND, + 13 * SECOND, + 21 * SECOND, + 34 * SECOND, + 55 * SECOND, +]; + export class BackOff { private count = 0;