question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

WebSocketProvider handle ws close and reconnect

See original GitHub issue

Hi @ricmoo,

I’m using WebSocketProvider server-side to listen to blockchain events and performing calls to smart contracts. Sometimes the websocket pipe got broken and I need to reconnect it.

I use this code to detect ws close and reconnect but it would be nice to not have to rely on _websocket to do it:

let wsProvider;

init = async () => {
  wsProvider = new ethers.providers.WebSocketProvider(wsHost);
  wsProvider._websocket.on('close', async (code) => {
    console.log('ws closed', code);
    wsProvider._websocket.terminate();
    await sleep(3000); // wait before reconnect
    init();
  });
  wsProvider.on('block', doStuff);
};

I also noticed when the websocket is broken Promise call don’t reject wich is not super intuitive.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:24
  • Comments:52 (4 by maintainers)

github_iconTop GitHub Comments

63reactions
mikevercoelencommented, Mar 27, 2021

I think this is probably the best solution:

const EXPECTED_PONG_BACK = 15000
const KEEP_ALIVE_CHECK_INTERVAL = 7500

export const startConnection = () => {
  provider = new ethers.providers.WebSocketProvider(config.ETH_NODE_WSS)

  let pingTimeout = null
  let keepAliveInterval = null

  provider._websocket.on('open', () => {
    keepAliveInterval = setInterval(() => {
      logger.debug('Checking if the connection is alive, sending a ping')

      provider._websocket.ping()

      // Use `WebSocket#terminate()`, which immediately destroys the connection,
      // instead of `WebSocket#close()`, which waits for the close timer.
      // Delay should be equal to the interval at which your server
      // sends out pings plus a conservative assumption of the latency.
      pingTimeout = setTimeout(() => {
        provider._websocket.terminate()
      }, EXPECTED_PONG_BACK)
    }, KEEP_ALIVE_CHECK_INTERVAL)

    // TODO: handle contract listeners setup + indexing
  })

  provider._websocket.on('close', () => {
    logger.error('The websocket connection was closed')
    clearInterval(keepAliveInterval)
    clearTimeout(pingTimeout)
    startConnection()
  })

  provider._websocket.on('pong', () => {
    logger.debug('Received pong, so connection is alive, clearing the timeout')
    clearInterval(pingTimeout)
  })
}

This send a ping every 15 seconds, when it sends a ping, it expects a pong back within 7.5 seconds otherwise it closes the connection and calls the main startConnection function to start everything over.

Where it says // TODO: handle contract listeners setup + indexing that’s where you should do any indexing or listening for contract events etc.

Fine tune these timing vars to taste, depending on who your Node provider is, this are the settings I use for QuikNode

const EXPECTED_PONG_BACK = 15000
const KEEP_ALIVE_CHECK_INTERVAL = 7500
16reactions
gwendallcommented, May 17, 2021

To elaborate on @mikevercoelen’s answer, I extracted the logic to a function

type KeepAliveParams = {
  provider: ethers.providers.WebSocketProvider;
  onDisconnect: (err: any) => void;
  expectedPongBack?: number;
  checkInterval?: number;
};

const keepAlive = ({
  provider,
  onDisconnect,
  expectedPongBack = 15000,
  checkInterval = 7500,
}: KeepAliveParams) => {
  let pingTimeout: NodeJS.Timeout | null = null;
  let keepAliveInterval: NodeJS.Timeout | null = null;

  provider._websocket.on('open', () => {
    keepAliveInterval = setInterval(() => {
      provider._websocket.ping();

      // Use `WebSocket#terminate()`, which immediately destroys the connection,
      // instead of `WebSocket#close()`, which waits for the close timer.
      // Delay should be equal to the interval at which your server
      // sends out pings plus a conservative assumption of the latency.
      pingTimeout = setTimeout(() => {
        provider._websocket.terminate();
      }, expectedPongBack);
    }, checkInterval);
  });

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  provider._websocket.on('close', (err: any) => {
    if (keepAliveInterval) clearInterval(keepAliveInterval);
    if (pingTimeout) clearTimeout(pingTimeout);
    onDisconnect(err);
  });

  provider._websocket.on('pong', () => {
    if (pingTimeout) clearInterval(pingTimeout);
  });
};

Then in my code, i get:

const startBot = () => {
  const provider = new ethers.providers.WebSocketProvider(wsUrl);
  keepAlive({
      provider,
      onDisconnect: (err) => {
        startBot();
        console.error('The ws connection was closed', JSON.stringify(err, null, 2));
      },
    });
};
Read more comments on GitHub >

github_iconTop Results From Across the Web

web3js - Websocket provider reconnects every 1 minute
As a workaround, I added reconnect options introduced in web3.js v1.2.7. That's how I noticed the regular dropping each minute. Here's the code:...
Read more >
Using the reconnect option with web3.js WSS connection
Introduction When your WSS connection closes abnormally, you may get the following errors: code: 1006,reason: 'Socket Error: read...
Read more >
What is use-case of error: "Provider started to reconnect ...
In web3-provider-ws node_module's code I find this reconnect function: WebsocketProvider.prototype.reconnect = function () { var _this ...
Read more >
Providers — Web3.py 5.31.3 documentation
If you must connect to a node on a different computer, use Websockets. ... This provider handles interactions with an WS or WSS...
Read more >
[SOLVED] CONNECTION ERROR with web3 with websockets ...
I´m using moralis socket url to connect web3 and HDWalletProvider. ... Error: CONNECTION ERROR: The connection got closed with the close code 4040...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found