web3 / web3.js

Collection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.
https://web3js.org/
Other
19.26k stars 4.94k forks source link

`WebSocketProvider.disconnect()` should have an option to wait until `_pendingRequestsQueue`/`_sentRequestsQueue` are empty #6451

Closed outSH closed 11 months ago

outSH commented 1 year ago

Expected behavior

WebSocketProvider.disconnect() should have an option to wait until _pendingRequestsQueue/_sentRequestsQueue are empty. If this is not possible, WebSocketProvider could export public field (requestQueueSize?) to check current queue size. This would allow us to wait until all requests are completed on user side.

Actual behavior

ConnectionNotOpenError is thrown, some request may not be sent to the ledger node. Current behavior is problematic when there are many web3js requests sent asynchronously from different parts of an app (for instance from async callbacks, which can be hard to track). This happened in one of https://github.com/hyperledger/cacti CI tests.

Steps to reproduce the behavior

  1. Start dev geth ./geth --dev --ws --ws.addr 0.0.0.0 --ws.port 8546 --ws.origins "*" --dev.period 1
  2. Run the script. It starts newBlockHeaders monitor, unsubscribes from it and immediately disconnects WS provider.
const { Web3, WebSocketProvider } = require("web3");

const gethUrl = "ws://localhost:8546";

async function main() {
  try {
    const web3 = new Web3(new WebSocketProvider(gethUrl));

    // Subscribe to new block headers
    const subscription = await web3.eth.subscribe("newBlockHeaders");

    subscription.on("data", async (blockhead) => {
      console.log("New block header: ", blockhead.hash);
    });
    subscription.on("error", (error) =>
      console.log("Error when subscribing to New block header: ", error),
    );

    subscription.unsubscribe();
    console.log(
      "_pendingRequestsQueue",
      web3.currentProvider._pendingRequestsQueue.size,
    );
    console.log(
      "_sentRequestsQueue",
      web3.currentProvider._sentRequestsQueue.size,
    );
    web3.currentProvider.disconnect();
    console.log("All done");
  } catch (error) {
    console.error(error);
  }
}

main();

Logs

Exception
node_modules/web3-providers-ws/node_modules/web3-utils/lib/commonjs/socket_provider.js:299
                request.deferredPromise.reject(new web3_errors_1.ConnectionNotOpenError(event));
                                               ^

ConnectionNotOpenError: Connection not open
    at /home/vagrant/cactus/packages/cactus-plugin-ledger-connector-ethereum/node_modules/web3-providers-ws/node_modules/web3-utils/lib/commonjs/socket_provider.js:299:48
    at Map.forEach (<anonymous>)
    at WebSocketProvider._clearQueues (/home/vagrant/cactus/packages/cactus-plugin-ledger-connector-ethereum/node_modules/web3-providers-ws/node_modules/web3-utils/lib/commonjs/socket_provider.js:297:37)
    at WebSocketProvider._onCloseEvent (/home/vagrant/cactus/packages/cactus-plugin-ledger-connector-ethereum/node_modules/web3-providers-ws/lib/commonjs/index.js:133:14)
    at WebSocket.<anonymous> (/home/vagrant/cactus/packages/cactus-plugin-ledger-connector-ethereum/node_modules/web3-providers-ws/lib/commonjs/index.js:116:115)
    at callListener (/home/vagrant/cactus/packages/cactus-plugin-ledger-connector-ethereum/node_modules/ws/lib/event-target.js:290:14)
    at WebSocket.onClose (/home/vagrant/cactus/packages/cactus-plugin-ledger-connector-ethereum/node_modules/ws/lib/event-target.js:220:9)
    at WebSocket.emit (node:events:513:28)
    at WebSocket.emitClose (/home/vagrant/cactus/packages/cactus-plugin-ledger-connector-ethereum/node_modules/ws/lib/websocket.js:258:10)
    at Socket.socketOnClose (/home/vagrant/cactus/packages/cactus-plugin-ledger-connector-ethereum/node_modules/ws/lib/websocket.js:1264:15)
Emitted 'error' event at:
    at /home/vagrant/cactus/packages/cactus-plugin-ledger-connector-ethereum/node_modules/web3-providers-ws/node_modules/web3-utils/lib/commonjs/socket_provider.js:223:36
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  innerError: undefined,
  code: 503,
  errorCode: 1000,
  errorReason: ''
}
Request on queue
request {
  payload: {
    jsonrpc: '2.0',
    id: '020aba80-a519-4892-8581-14ffe500655c',
    method: 'eth_unsubscribe',
    params: [ '0xc101d184e3cbf307e3e958f9f9845ca6' ]
  },
  deferredPromise: Web3DeferredPromise {
    _state: 'pending',
    _resolve: [Function (anonymous)],
    _reject: [Function (anonymous)],
    _promise: Promise { <pending> },
    _timeoutMessage: 'DeferredPromise timed out',
    _timeoutInterval: 0,
    [Symbol(Symbol.toStringTag)]: 'Promise'
  }
}

Environment

luu-alex commented 1 year ago

Hey there, thank you for submitting this issue, having a public field requestQueueSize sounds feasible

luu-alex commented 1 year ago

6479