Skip to content

Commit

Permalink
dgram: don't hide implicit bind errors
Browse files Browse the repository at this point in the history
When dgram socket implicit binding fails, an attempt is made to
clean up the send queue. This was originally implemented using
an 'error' handler that performed cleanup and then emitted a
fake error, which concealed the original error. This was done
to prevent cases where the same error was emitted twice. Now
that the errorMonitor event is available, use that to perform
the cleanup without impacting the actual error handling.

PR-URL: #31958
Refs: nodejs/help#2484
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
cjihrig committed Feb 28, 2020
1 parent fb26b13 commit 1b2e294
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 36 deletions.
6 changes: 2 additions & 4 deletions lib/dgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const {
ERR_SOCKET_BAD_BUFFER_SIZE,
ERR_SOCKET_BAD_PORT,
ERR_SOCKET_BUFFER_SIZE,
ERR_SOCKET_CANNOT_SEND,
ERR_SOCKET_DGRAM_IS_CONNECTED,
ERR_SOCKET_DGRAM_NOT_CONNECTED,
ERR_SOCKET_DGRAM_NOT_RUNNING,
Expand Down Expand Up @@ -506,23 +505,22 @@ function enqueue(self, toEnqueue) {
// event handler that flushes the send queue after binding is done.
if (state.queue === undefined) {
state.queue = [];
self.once('error', onListenError);
self.once(EventEmitter.errorMonitor, onListenError);
self.once('listening', onListenSuccess);
}
state.queue.push(toEnqueue);
}


function onListenSuccess() {
this.removeListener('error', onListenError);
this.removeListener(EventEmitter.errorMonitor, onListenError);
clearQueue.call(this);
}


function onListenError(err) {
this.removeListener('listening', onListenSuccess);
this[kStateSymbol].queue = undefined;
this.emit('error', new ERR_SOCKET_CANNOT_SEND());
}


Expand Down
47 changes: 15 additions & 32 deletions test/sequential/test-dgram-implicit-bind-failure.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,31 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const EventEmitter = require('events');
const dgram = require('dgram');
const dns = require('dns');
const { kStateSymbol } = require('internal/dgram');
const mockError = new Error('fake DNS');

// Monkey patch dns.lookup() so that it always fails.
dns.lookup = function(address, family, callback) {
process.nextTick(() => { callback(new Error('fake DNS')); });
process.nextTick(() => { callback(mockError); });
};

const socket = dgram.createSocket('udp4');
let dnsFailures = 0;
let sendFailures = 0;

process.on('exit', () => {
assert.strictEqual(dnsFailures, 3);
assert.strictEqual(sendFailures, 3);
});

socket.on('error', (err) => {
if (/^Error: fake DNS$/.test(err)) {
// The DNS lookup should fail since it is monkey patched. At that point in
// time, the send queue should be populated with the send() operation. There
// should also be two listeners - this function and the dgram internal one
// time error handler.
dnsFailures++;
assert(Array.isArray(socket[kStateSymbol].queue));
assert.strictEqual(socket[kStateSymbol].queue.length, 1);
assert.strictEqual(socket.listenerCount('error'), 2);
return;
}

if (err.code === 'ERR_SOCKET_CANNOT_SEND') {
// On error, the queue should be destroyed and this function should be
// the only listener.
sendFailures++;
assert.strictEqual(socket[kStateSymbol].queue, undefined);
assert.strictEqual(socket.listenerCount('error'), 1);
return;
}

assert.fail(`Unexpected error: ${err}`);
});
socket.on(EventEmitter.errorMonitor, common.mustCall((err) => {
// The DNS lookup should fail since it is monkey patched. At that point in
// time, the send queue should be populated with the send() operation.
assert.strictEqual(err, mockError);
assert(Array.isArray(socket[kStateSymbol].queue));
assert.strictEqual(socket[kStateSymbol].queue.length, 1);
}, 3));

socket.on('error', common.mustCall((err) => {
assert.strictEqual(err, mockError);
assert.strictEqual(socket[kStateSymbol].queue, undefined);
}, 3));

// Initiate a few send() operations, which will fail.
socket.send('foobar', common.PORT, 'localhost');
Expand Down

0 comments on commit 1b2e294

Please sign in to comment.