Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible EventTarget Memory Leak Detected #900

Closed
TJKoury opened this issue Mar 11, 2021 · 17 comments · Fixed by #913 or #1050
Closed

Possible EventTarget Memory Leak Detected #900

TJKoury opened this issue Mar 11, 2021 · 17 comments · Fixed by #913 or #1050
Assignees
Labels
kind/bug A bug in existing code (including security flaws) status/in-progress In progress

Comments

@TJKoury
Copy link
Contributor

TJKoury commented Mar 11, 2021

Version: 0.30.10

Platform: Linux pop-os 5.8.0-7642-generic # 47~ 1612288990 ~20.10 ~b8113e7-Ubuntu x86_64 GNU/Linux, Node v15.8.0.

Subsystem: Dialer (src/dialer/dial-request.js)

Type: Possible EventTarget memory leak detected.

Severity: Medium

Description:

(node:52434) MaxListenersExceededWarning: Possible EventTarget memory leak detected. 11 abort listeners added to [AbortSignal]. Use events.setMaxListeners() to increase limit
at EventTarget.[kNewListener] (node:internal/event_target:256:17)
at EventTarget.addEventListener (node:internal/event_target:335:23)
at anySignal (node_modules/any-signal/index.js:27:12)
at node_modules/libp2p/src/dialer/dial-request.js:70:85
at runMicrotasks ()
at processTicksAndRejections (node:internal/process/task_queues:94:5)

Steps to reproduce the error:

Import ipfs-core into a Node project, initialize, let it run for ~30 minutes.

@vasco-santos
Copy link
Member

Thanks for reporting this @TJKoury

Can you share a code snippet and dependencies for this or a replication test directly with libp2p? From https://github.com/libp2p/js-libp2p/blob/master/src/dialer/dial-request.js#L70 + https://github.com/jacobheun/any-signal/blob/master/index.js#L27 I cannot see at this point how 11 listeners would be added.

@vasco-santos vasco-santos added kind/bug A bug in existing code (including security flaws) status/ready Ready to be worked labels Mar 11, 2021
@TJKoury
Copy link
Contributor Author

TJKoury commented Mar 11, 2021

See snippet below.

Can you recommend a test harness setup for libp2p? I was trying to follow the dialog in issue 886. Tried using libp2p-daemon but could not get it to run after global install.

import IPFS from "ipfs-core";
import { existsSync  } from "fs";
import { join } from "path";
import { homedir } from "os";
import PeerId from "peer-id";
import { execSync } from "child_process";

async function main() {

  let peerID = await PeerId.create({ bits: 256, keyType: "secp256k1" });

  let jsdir = join(homedir(), ".jsipfs");

  if (existsSync(jsdir)) {
    console.log("removing old repo");
    execSync(`rm -rf ${jsdir}`);
  }

  const TCP_HOST = process.env.TCP_HOST || "0.0.0.0";
  const IPFS_SWARM_TCP_PORT = 6601;
  const IPFS_SWARM_WS_PORT = 6602;
  let options = {
    privateKey: peerID,
    EXPERIMENTAL: { ipnsPubsub: true },
    repo: jsdir,
    config: {
      Addresses: {
        Swarm: [
          `/ip4/${TCP_HOST}/tcp/${IPFS_SWARM_TCP_PORT}`,
          `/ip4/${TCP_HOST}/tcp/${IPFS_SWARM_WS_PORT}/ws`,
        ],
      },
    },
  };
  const node = await IPFS.create(options);
  let peers = await node.swarm.peers(); 
  console.log(peers);

  const version = await node.version();

  const fileAdded = await node.add({
    path: "hello.txt",
    content: `TEST FILE ${new Date().toISOString()}`,
  });

  console.log("Added file:", fileAdded, fileAdded.path, fileAdded.cid);
  const addr = `/ipfs/${fileAdded.cid}`;

  let res = await node.name.publish(addr, {
    allowOffline: true,
    resolve: false,
    lifetime: "2h",
  });

  console.log(`https://gateway.ipfs.io${addr}`);
  console.log(`https://gateway.ipfs.io/ipns/${res.name}`);
  for await (const name of node.name.resolve(`/ipns/${res.name}`)) {
    console.log(name);
  }
}

main();

@vasco-santos
Copy link
Member

vasco-santos commented Mar 15, 2021

There are some inconsistencies in the snippet, peerId1 is not set and the crypto lib not used. For now, I just removed the privatekey set.
Do you get the above warning by running this? Doing the change above did not lead me to any issue so far

Tried using libp2p-daemon but could not get it to run after global install.

Perhaps it is easier to just import libp2p directly and use its API? libp2p-daemon should just need to be imported, a daemon created and started, but you also need libp2p-daemon-client to interact with it.

@TJKoury
Copy link
Contributor Author

TJKoury commented Mar 15, 2021 via email

@vasco-santos
Copy link
Member

Thanks @TJKoury
I will keep it running longer to see if I can replicate it

@vasco-santos
Copy link
Member

I have been running it for 2 hours without any issue. Perhaps linux related as I am running on Mac OS.
Probably also related to libp2p/js-libp2p-tcp#141

@TJKoury
Copy link
Contributor Author

TJKoury commented Mar 15, 2021 via email

@TJKoury
Copy link
Contributor Author

TJKoury commented Mar 15, 2021

OSX Mojave 10.14.6, Node v15.11.0, no issues. Will try on Windows next. Test repo.

@vasco-santos
Copy link
Member

Interesting, can you also try your linux machine using node14?

@TJKoury
Copy link
Contributor Author

TJKoury commented Mar 15, 2021

Both are using Node 15, so it seems it might be platform related. If there is another reason you think I should try let me know.

@vasco-santos
Copy link
Member

I wanted to understand if this might be related to an update on Node15 in the platform you are having issues, as this problem should have appeared early on if it is a problem in other Node versions. If you can, it would be great to test your machine using Node14.

Meanwhile, I will try to book some time to try to replicate this on a VM

@achingbrain
Copy link
Member

I'm seeing this in js-IPFS master too. To repro just start the daemon and wait a few seconds.

@vasco-santos
Copy link
Member

@achingbrain are you using Node15 + MacOS?

I have been running it for over two hours and I only got:

➜  js-ipfs git:(master) node packages/ipfs/src/cli.js daemon
Initializing IPFS daemon...
js-ipfs version: 0.4.4
System version: x64/darwin
Node.js version: 15.13.0
Swarm listening on /ip4/127.0.0.1/tcp/6601/p2p/16Uiu2HAmATU25wevp93GNwzew7LnpKneHbfejiPS1tx4SPGnuARP
Swarm listening on /ip4/192.168.1.120/tcp/6601/p2p/16Uiu2HAmATU25wevp93GNwzew7LnpKneHbfejiPS1tx4SPGnuARP
Swarm listening on /ip4/127.0.0.1/tcp/6602/ws/p2p/16Uiu2HAmATU25wevp93GNwzew7LnpKneHbfejiPS1tx4SPGnuARP
Swarm listening on /ip4/192.168.1.120/tcp/6602/ws/p2p/16Uiu2HAmATU25wevp93GNwzew7LnpKneHbfejiPS1tx4SPGnuARP
HTTP API listening on /ip4/127.0.0.1/tcp/5002/http
gRPC listening on /ip4/127.0.0.1/tcp/5003/ws
Gateway (read only) listening on /ip4/127.0.0.1/tcp/9090/http
Web UI available at http://127.0.0.1:5002/webui
Daemon is ready

@achingbrain
Copy link
Member

Yes, node 15 on a Mac:

$ time jsipfs daemon
Initializing IPFS daemon...
js-ipfs version: 0.4.4
System version: x64/darwin
Node.js version: 15.12.0
Swarm listening on /ip4/127.0.0.1/tcp/4002/p2p/QmQ73f8hbM4hKwRYBqeUsPtiwfE2x6WPv9WnzaYt4nYcXf
Swarm listening on /ip4/192.168.2.50/tcp/4002/p2p/QmQ73f8hbM4hKwRYBqeUsPtiwfE2x6WPv9WnzaYt4nYcXf
Swarm listening on /ip4/127.0.0.1/tcp/4003/ws/p2p/QmQ73f8hbM4hKwRYBqeUsPtiwfE2x6WPv9WnzaYt4nYcXf
Swarm listening on /ip4/81.135.100.179/tcp/35278/p2p/QmQ73f8hbM4hKwRYBqeUsPtiwfE2x6WPv9WnzaYt4nYcXf
HTTP API listening on /ip4/127.0.0.1/tcp/5002/http
gRPC listening on /ip4/127.0.0.1/tcp/5003/ws
Gateway (read only) listening on /ip4/127.0.0.1/tcp/9090/http
Web UI available at http://127.0.0.1:5002/webui
Daemon is ready
(node:2561) MaxListenersExceededWarning: Possible EventTarget memory leak detected. 11 abort listeners added to [AbortSignal]. Use events.setMaxListeners() to increase limit
    at EventTarget.[kNewListener] (node:internal/event_target:288:17)
    at EventTarget.addEventListener (node:internal/event_target:369:23)
    at anySignal (/Users/alex/Documents/Workspaces/ipfs/js-ipfs/node_modules/any-signal/index.js:27:12)
    at /Users/alex/Documents/Workspaces/ipfs/js-ipfs/node_modules/libp2p/src/dialer/dial-request.js:70:85
    at processTicksAndRejections (node:internal/process/task_queues:94:5)
    at async /Users/alex/Documents/Workspaces/ipfs/js-ipfs/node_modules/p-some/index.js:53:19
^CReceived interrupt signal, shutting down...

real	0m17.050s
user	0m3.690s
sys	0m0.507s

@achingbrain
Copy link
Member

It's trying to dial more than 10 addresses at once and an abort event listener gets added to options.signal on every dial:

// added to src/dialer/dial-request.js
const dialAbortControllers = this.addrs.map(() => new AbortController())
let completedDials = 0

console.info(dialAbortControllers.length, 'controllers', this.addrs)
12 controllers [
  <Multiaddr 29200300e79f106f000042acfffe10640591020fa1cc03a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip6/2003:e7:9f10:6f00:42:acff:fe10:6405/udp/4001/quic/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 045b16b77991020fa1cc03a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip4/91.22.183.121/udp/4001/quic/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 29200300e79f106f000042acfffe106405060fa1a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip6/2003:e7:9f10:6f00:42:acff:fe10:6405/tcp/4001/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 29200300e79f1004000042acfffe106405060fa1a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip6/2003:e7:9f10:400:42:acff:fe10:6405/tcp/4001/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 045b16b72491020fa1cc03a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip4/91.22.183.36/udp/4001/quic/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 29200300e79f1004000042acfffe10640591020fa1cc03a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip6/2003:e7:9f10:400:42:acff:fe10:6405/udp/4001/quic/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 04ac106405060fa1a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip4/172.16.100.5/tcp/4001/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 04ac10640591020fa1cc03a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip4/172.16.100.5/udp/4001/quic/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 047f00000191020fa1cc03a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip4/127.0.0.1/udp/4001/quic/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 047f000001060fa1a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip4/127.0.0.1/tcp/4001/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 290000000000000000000000000000000191020fa1cc03a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip6/::1/udp/4001/quic/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>,
  <Multiaddr 2900000000000000000000000000000001060fa1a503260024080112208f17ad76a74bde266dfcea2d155c7d88b64e9a297756ea81faccd81ab14a4b8e - /ip6/::1/tcp/4001/p2p/12D3KooWKSwQk5DH6v34bGZgF7XTqUuQGU2aK8RpHUH1CwPqgD9w>
]
(node:2830) MaxListenersExceededWarning: Possible EventTarget memory leak detected. 11 abort listeners added to [AbortSignal]. Use events.setMaxListeners() to increase limit
    at EventTarget.[kNewListener] (node:internal/event_target:288:17)
    at EventTarget.addEventListener (node:internal/event_target:369:23)
    at anySignal (/Users/alex/Documents/Workspaces/ipfs/js-ipfs/node_modules/any-signal/index.js:27:12)
    at /Users/alex/Documents/Workspaces/ipfs/js-ipfs/node_modules/libp2p/src/dialer/dial-request.js:72:85
    at processTicksAndRejections (node:internal/process/task_queues:94:5)
    at async /Users/alex/Documents/Workspaces/ipfs/js-ipfs/node_modules/p-some/index.js:53:19

@vasco-santos
Copy link
Member

vasco-santos commented Apr 1, 2021

It's trying to dial more than 10 addresses at once

We allow by default doing 4 parallel dials per dialRequest. So after 4 being in progress, the next ones will wait for a token to start dialling.

Anyway, I already understood what is happening. In this case, it seems that before an actual dial is successful a bunch are tested. As we we do not support quic we will add abort controllers to it, even though we will not be able to dial it. So, we will rapidly go over all the quic addresses, while the TCP will actually do the dials. This means the options.signal will have a lot of dials attempted.

There is definitely room for improvements here! Probably the cleanest solution will be to call transportManager. transportForMultiaddr() while creating a DialTarget. This way we can immediately filter out the multiaddrs that will be useless before we use dial tokens with them and create AbortControllers. The downside of this approach is that we already call transportManager. transportForMultiaddr() later on when we actually perform the dail (to get the transport to use). This will be called twice per valid address (if we do not achieve a dial before going through all the addresses). However, this seems reasonable as we also get rid of Promise resolutions and Abort Controllers from multiaddrs without any value. If there is a node with a lot valid addresses this will continue to be a problem.

I am going to add this issue to the maintenance board to get it fixed faster.

Thanks for your inputs

@achingbrain
Copy link
Member

achingbrain commented Sep 17, 2021

I'm still seeing this happen with libp2p@0.32.4:

(node:88952) MaxListenersExceededWarning: Possible EventTarget memory leak detected. 11 abort listeners added to [AbortSignal]. Use events.setMaxListeners() to increase limit
    at EventTarget.[kNewListener] (node:internal/event_target:286:17)
    at EventTarget.addEventListener (node:internal/event_target:367:23)
    at anySignal (/Users/alex/Documents/Workspaces/ipfs/js-ipfs/node_modules/any-signal/index.js:27:12)
    at /Users/alex/Documents/Workspaces/ipfs/js-ipfs/node_modules/libp2p/src/dialer/dial-request.js:71:85
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async /Users/alex/Documents/Workspaces/ipfs/js-ipfs/node_modules/p-some/index.js:53:19

It's trying to dial a peer with more than 10x addresses:

[
  '/ip4/109.158.113.107/tcp/25342/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip4/192.168.2.50/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/::1/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fe80::1/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fe80::aede:48ff:fe00:1122/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fe80::10ae:5211:e735:64b3/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fd1e:dece:3175:1:1426:3c09:4b7d:c6a9/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fe80::4fd:93ff:fe31:ac9b/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fe80::1802:2d7f:7bb6:ae8d/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fe80::11bc:ab62:fcee:81b1/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fe80::1ea1:90b:b2f7:37cc/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fe80::644b:3d0d:555f:44/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip6/fe80::f1c4:f079:810a:9697/tcp/4001/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz',
  '/ip4/127.0.0.1/tcp/4003/ws/p2p/12D3KooWPb6UGtpxdcdTYxedhoM7JTJU9N5AYyu9H6dKDnStexKz'
]

We just need to handle peers like this. Put the dials in a queue or increase the number of listeners or something.

The warning is harmless, but it doesn't look harmless.

@achingbrain achingbrain reopened this Sep 17, 2021
achingbrain added a commit that referenced this issue Dec 6, 2021
Sometimes you encounter peers with lots of addresses. When this happens
you can attach more than 10x event listeners to the abort signal we
use to abort all the dials - this causes node to print a warning
which is misleading.

This PR increases the default number of listeners on the signal.

Fixes #900
vasco-santos pushed a commit that referenced this issue Dec 6, 2021
Sometimes you encounter peers with lots of addresses. When this happens
you can attach more than 10x event listeners to the abort signal we
use to abort all the dials - this causes node to print a warning
which is misleading.

This PR increases the default number of listeners on the signal.

Fixes #900
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug A bug in existing code (including security flaws) status/in-progress In progress
Projects
Archived in project
3 participants