Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Gaurd against infinite memory leak in dgram #8705

Closed
wants to merge 6 commits into from
Closed

Gaurd against infinite memory leak in dgram #8705

wants to merge 6 commits into from

Conversation

Raynos
Copy link

@Raynos Raynos commented Nov 10, 2014

There is a unbounded array in the dgram library. We've seen this array have over 250 thousands items in production heap dumps.

We put about a huge amount of traffic through our statsd UDP socket. When we get DNS failures this unbounded queue buffers
all statsd data which causes a huge memory leak.

cc @tjfontaine @indutny

self.send.apply(self, self._sendQueue[i]);
self._sendQueue = undefined;
});
self.on('listening', onListening);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be once, since onListening blows away the ._sendQueue property after it executes (making subsequent calls to onListening throw an exception)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside onListening I remove the listener manually. I should probably remove the listener as the first line to avoid a sync race condition.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed a flaw here. I need to not add an 'error' listener as thats a major breaking semantic change.

I'll refactor this to once and deal with the error case differently.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added another commit to address this.

@@ -110,6 +110,7 @@ function Socket(type, listener) {
this._bindState = BIND_STATE_UNBOUND;
this.type = type;
this.fd = null; // compatibility hack
this.highWaterMark = 100;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious -- why 100?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty arbitrary. I really don't know what a good default is.

@jasnell
Copy link
Member

jasnell commented Aug 15, 2015

@Raynos ... still an issue for you? This would likely need to be updated and retargeted at master https://github.com/nodejs/node if you'd like to pursue.

@Fishrock123
Copy link

@mcollina could you check to see if this might still be an issue?

@cjihrig
Copy link

cjihrig commented Jan 16, 2017

@Fishrock123 I believe I can artificially reproduce the problem on master:

'use strict';
const dns = require('dns');

dns.lookup = function(address, family, callback) {
  callback(new Error('foo'));
};

const dgram = require('dgram');
const socket = dgram.createSocket('udp4');

socket.on('error', (err) => {});

setInterval(() => {
  for (let i = 0; i < 100000; ++i) {
    socket.send('foobar', 80, 'google.com');
  }

  console.log(`queue length: ${socket._queue ? socket._queue.length : 0}`);
}, 1000);

The queue grows on every send() until DNS lookup succeeds.

@Raynos
Copy link
Author

Raynos commented Jan 18, 2017

Sounds like it's still an issue.

At uber we only have one UDP use case and it has a manual gaurd ( https://github.com/uber/node-statsd-client/blob/master/lib/ephemeral-socket.js#L136-L150 ) in it work around this bug.

So, funny story. Because of the "nodejs dns bug" we've literally stopped using DNS in our uber data centers and only use IP addresses.

cjihrig added a commit to cjihrig/node that referenced this pull request Jan 30, 2017
When send() triggers an implicit bind, the send operation is
added to an internal queue. If a DNS error occurs during the bind,
there is currently no mechanism for clearing the queue other than
sending more data. If DNS errors keep occurring, the queue will
continue to grow with no upper bound. This commit reports errors
with implicit binds, and clears the queue. This should be fine,
given the nature of UDP.

Refs: nodejs/node-v0.x-archive#8705
Refs: nodejs#10902
PR-URL: nodejs#11036
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: James M Snell <[email protected]>
@cjihrig
Copy link

cjihrig commented Jan 30, 2017

Closed in nodejs/node#11036.

@cjihrig cjihrig closed this Jan 30, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants