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
Expand Up @@ -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;
satyajeetkolhapure marked this conversation as resolved.
Show resolved Hide resolved
private retryTimeoutId: NodeJS.Timeout;

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

Expand All @@ -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 = () => {
satyajeetkolhapure marked this conversation as resolved.
Show resolved Hide resolved
// 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;
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand Down