Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

fix: implement backoff retry policy for websocket handler #3600

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 45 additions & 25 deletions src/chains/ethereum/ethereum/src/forking/handlers/ws-handler.ts
Original file line number Diff line number Diff line change
@@ -20,6 +20,12 @@ export class WsHandler extends BaseHandler implements Handler {
}>
>();

// retry configuration
private retryIntervalBase: number = 2;
private retryCounter: number = 3;
private initialRetryCounter = this.retryCounter;
private retryTimeoutId: NodeJS.Timeout;

constructor(options: EthereumInternalOptions, abortSignal: AbortSignal) {
super(options, abortSignal);

@@ -28,28 +34,17 @@ export class WsHandler extends BaseHandler implements Handler {
logging
} = options;

this.connection = new WebSocket(url.toString(), {
origin,
headers: this.headers
});
// `nodebuffer` is already the default, but I just wanted to be explicit
// here because when `nodebuffer` is the binaryType the `message` event's
// data type is guaranteed to be a `Buffer`. We don't need to check for
// different types of data.
// I mention all this because if `arraybuffer` or `fragment` is used for the
// binaryType the `"message"` event's `data` may end up being
// `ArrayBuffer | Buffer`, or `Buffer[] | Buffer`, respectively.
// If you need to change this, you probably need to change our `onMessage`
// handler too.
this.connection.binaryType = "nodebuffer";

this.open = this.connect(this.connection, logging);
this.open = this.connect(url.toString(), origin, logging);
this.connection.onclose = () => {
// try to connect again...
// Issue: https://github.com/trufflesuite/ganache/issues/3476
// TODO: backoff and eventually fail
// Issue: https://github.com/trufflesuite/ganache/issues/3477
this.open = this.connect(this.connection, logging);
// backoff and eventually fail
if( this.retryCounter > 0 ) {
clearTimeout( this.retryTimeoutId );
this.retryTimeoutId = setTimeout( () => {
this.reconnect(url.toString(), origin, logging);
}, Math.pow( this.retryIntervalBase, this.initialRetryCounter - this.retryCounter ) * 1000 );
this.retryCounter--;
}
};
this.abortSignal.addEventListener("abort", () => {
this.connection.onclose = null;
@@ -106,17 +101,34 @@ export class WsHandler extends BaseHandler implements Handler {
}

private connect(
connection: WebSocket,
url: string,
origin: string,
logging: EthereumInternalOptions["logging"]
) {
this.connection = new WebSocket(url, {
origin,
headers: this.headers
});
// `nodebuffer` is already the default, but I just wanted to be explicit
// here because when `nodebuffer` is the binaryType the `message` event's
// data type is guaranteed to be a `Buffer`. We don't need to check for
// different types of data.
// I mention all this because if `arraybuffer` or `fragment` is used for the
// binaryType the `"message"` event's `data` may end up being
// `ArrayBuffer | Buffer`, or `Buffer[] | Buffer`, respectively.
// If you need to change this, you probably need to change our `onMessage`
// handler too.
this.connection.binaryType = "nodebuffer";
let open = new Promise((resolve, reject) => {
connection.onopen = resolve;
connection.onerror = reject;
this.connection.onopen = resolve;
this.connection.onerror = reject;
});
open.then(
() => {
connection.onopen = null;
connection.onerror = null;
this.connection.onopen = null;
this.connection.onerror = null;
// reset the retry counter
this.retryCounter = this.initialRetryCounter;
},
err => {
logging.logger.log(err);
@@ -125,6 +137,14 @@ export class WsHandler extends BaseHandler implements Handler {
return open;
}

private reconnect (url: string,
origin: string,
logging: EthereumInternalOptions["logging"]) {
const onCloseEvent = this.connection.onclose;
this.open = this.connect(url, origin, logging);
this.connection.onclose = onCloseEvent;
}

public async close() {
await super.close();
this.connection.close();