From 235f79afb0adf02d8e750697c8871bbf9e6d0c02 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Fri, 22 Aug 2014 16:51:53 -0400 Subject: [PATCH 1/8] net: allow cluster workers to listen on exclusive ports Currently, all listen() calls go through the cluster master. This commit allows workers to listen on ports exclusively. Closes #3856 --- doc/api/net.markdown | 26 ++++++++ lib/net.js | 42 ++++++++----- test/simple/test-net-listen-shared-ports.js | 66 +++++++++++++++++++++ 3 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 test/simple/test-net-listen-shared-ports.js diff --git a/doc/api/net.markdown b/doc/api/net.markdown index 08ffe023f32..99c6f38392d 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -208,6 +208,32 @@ This function is asynchronous. When the server has been bound, the last parameter `callback` will be added as an listener for the ['listening'][] event. +### server.listen(options, [callback]) + +* `options` {Object} - Required. Supports the following properties: + * `port` {Number} - Required. + * `host` {String} - Optional. + * `backlog` {Number} - Optional. + * `shared` {Boolean} - Optional. +* `callback` {Function} + +The `port`, `host`, and `backlog` properties of `options`, as well as the +optional callback function, behave as they do on a call to +[server.listen(port, \[host\], \[backlog\], \[callback\]) +](#net_server_listen_port_host_backlog_callback). + +If `shared` is `true` (default), then cluster workers will use the same +underlying handle, allowing connection handling duties to be shared. When +`shared` is `false`, the handle is not shared, and attempted port sharing +results in an error. An example which listens on an exclusive port is +shown below. + + server.listen({ + host: 'localhost', + port: 80, + shared: true + }); + ### server.close([callback]) Stops the server from accepting new connections and keeps existing diff --git a/lib/net.js b/lib/net.js index c653c814b75..cc5494a0bf2 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1140,10 +1140,13 @@ Server.prototype._listen2 = function(address, port, addressType, backlog, fd) { }; -function listen(self, address, port, addressType, backlog, fd) { +function listen(self, address, port, addressType, backlog, fd, shared) { + // listening still goes through master by default + shared = shared === undefined ? true : !!shared; + if (!cluster) cluster = require('cluster'); - if (cluster.isMaster) { + if (cluster.isMaster || !shared) { self._listen2(address, port, addressType, backlog, fd); return; } @@ -1191,22 +1194,27 @@ Server.prototype.listen = function() { var TCP = process.binding('tcp_wrap').TCP; - if (arguments.length == 0 || util.isFunction(arguments[0])) { + if (arguments.length === 0 || util.isFunction(arguments[0])) { // Bind to a random port. listen(self, null, 0, null, backlog); - - } else if (arguments[0] && util.isObject(arguments[0])) { + } else if (util.isObject(arguments[0])) { var h = arguments[0]; - if (h._handle) { - h = h._handle; - } else if (h.handle) { - h = h.handle; - } + h = h._handle || h.handle || h; + if (h instanceof TCP) { self._handle = h; listen(self, null, -1, -1, backlog); } else if (util.isNumber(h.fd) && h.fd >= 0) { listen(self, null, null, null, backlog, h.fd); + } else if (util.isNumber(h.port)) { + // The first argument is a configuration object + if (h.backlog) + backlog = h.backlog; + + if (h.host) + listenAfterLookup(h.port, h.host, backlog, h.shared); + else + listen(self, null, h.port, 4, backlog, undefined, h.shared); } else { throw new Error('Invalid listen argument: ' + h); } @@ -1214,23 +1222,27 @@ Server.prototype.listen = function() { // UNIX socket or Windows pipe. var pipeName = self._pipeName = arguments[0]; listen(self, pipeName, -1, -1, backlog); - } else if (util.isUndefined(arguments[1]) || util.isFunction(arguments[1]) || util.isNumber(arguments[1])) { // The first argument is the port, no IP given. listen(self, null, port, 4, backlog); - } else { // The first argument is the port, the second an IP. - require('dns').lookup(arguments[1], function(err, ip, addressType) { + listenAfterLookup(port, arguments[1], backlog); + } + + function listenAfterLookup(port, address, backlog, shared) { + require('dns').lookup(address, function(err, ip, addressType) { if (err) { self.emit('error', err); } else { - listen(self, ip, port, ip ? addressType : 4, backlog); + addressType = ip ? addressType : 4; + listen(self, ip, port, addressType, backlog, undefined, shared); } }); - } + }; + return self; }; diff --git a/test/simple/test-net-listen-shared-ports.js b/test/simple/test-net-listen-shared-ports.js new file mode 100644 index 00000000000..dfb7eff0db7 --- /dev/null +++ b/test/simple/test-net-listen-shared-ports.js @@ -0,0 +1,66 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var cluster = require('cluster'); +var net = require('net'); + +function noop() {} + +if (cluster.isMaster) { + var worker1 = cluster.fork(); + + worker1.on('message', function(msg) { + assert.equal(msg, 'success'); + var worker2 = cluster.fork(); + + worker2.on('message', function(msg) { + assert.equal(msg, 'server2:EADDRINUSE'); + worker1.kill(); + worker2.kill(); + }); + }); +} else { + var server1 = net.createServer(noop); + var server2 = net.createServer(noop); + + server1.on('error', function(err) { + // no errors expected + process.send('server1:' + err.code); + }); + + server2.on('error', function(err) { + // an error is expected on the second worker + process.send('server2:' + err.code); + }); + + server1.listen({ + host: 'localhost', + port: common.PORT, + shared: true + }, function() { + server2.listen({port: common.PORT + 1, shared: false}, function() { + // the first worker should succeed + process.send('success'); + }); + }); +} From 2a21811631940e746b5d1ac45e452b75bb40bafa Mon Sep 17 00:00:00 2001 From: cjihrig Date: Wed, 27 Aug 2014 10:11:09 -0400 Subject: [PATCH 2/8] address comments on #8238 --- lib/net.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/net.js b/lib/net.js index cc5494a0bf2..d68d37a288d 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1142,7 +1142,7 @@ Server.prototype._listen2 = function(address, port, addressType, backlog, fd) { function listen(self, address, port, addressType, backlog, fd, shared) { // listening still goes through master by default - shared = shared === undefined ? true : !!shared; + shared = shared !== false; if (!cluster) cluster = require('cluster'); @@ -1222,11 +1222,13 @@ Server.prototype.listen = function() { // UNIX socket or Windows pipe. var pipeName = self._pipeName = arguments[0]; listen(self, pipeName, -1, -1, backlog); + } else if (util.isUndefined(arguments[1]) || util.isFunction(arguments[1]) || util.isNumber(arguments[1])) { // The first argument is the port, no IP given. listen(self, null, port, 4, backlog); + } else { // The first argument is the port, the second an IP. listenAfterLookup(port, arguments[1], backlog); From 9d11d17382baa177b89b4b9727feffff453a3aa1 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Thu, 28 Aug 2014 20:49:02 -0400 Subject: [PATCH 3/8] replace `shared` with `exclusive` to simplify logic. also, doc fix --- doc/api/net.markdown | 8 ++++---- lib/net.js | 15 +++++++-------- test/simple/test-net-listen-shared-ports.js | 4 ++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/doc/api/net.markdown b/doc/api/net.markdown index 99c6f38392d..c49b3d2d974 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -214,7 +214,7 @@ the last parameter `callback` will be added as an listener for the * `port` {Number} - Required. * `host` {String} - Optional. * `backlog` {Number} - Optional. - * `shared` {Boolean} - Optional. + * `exclusive` {Boolean} - Optional. * `callback` {Function} The `port`, `host`, and `backlog` properties of `options`, as well as the @@ -222,16 +222,16 @@ optional callback function, behave as they do on a call to [server.listen(port, \[host\], \[backlog\], \[callback\]) ](#net_server_listen_port_host_backlog_callback). -If `shared` is `true` (default), then cluster workers will use the same +If `exclusive` is `false` (default), then cluster workers will use the same underlying handle, allowing connection handling duties to be shared. When -`shared` is `false`, the handle is not shared, and attempted port sharing +`exclusive` is `true`, the handle is not shared, and attempted port sharing results in an error. An example which listens on an exclusive port is shown below. server.listen({ host: 'localhost', port: 80, - shared: true + exclusive: true }); ### server.close([callback]) diff --git a/lib/net.js b/lib/net.js index d68d37a288d..77520ba9e2c 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1140,13 +1140,12 @@ Server.prototype._listen2 = function(address, port, addressType, backlog, fd) { }; -function listen(self, address, port, addressType, backlog, fd, shared) { - // listening still goes through master by default - shared = shared !== false; +function listen(self, address, port, addressType, backlog, fd, exclusive) { + exclusive = !!exclusive; if (!cluster) cluster = require('cluster'); - if (cluster.isMaster || !shared) { + if (cluster.isMaster || exclusive) { self._listen2(address, port, addressType, backlog, fd); return; } @@ -1212,9 +1211,9 @@ Server.prototype.listen = function() { backlog = h.backlog; if (h.host) - listenAfterLookup(h.port, h.host, backlog, h.shared); + listenAfterLookup(h.port, h.host, backlog, h.exclusive); else - listen(self, null, h.port, 4, backlog, undefined, h.shared); + listen(self, null, h.port, 4, backlog, undefined, h.exclusive); } else { throw new Error('Invalid listen argument: ' + h); } @@ -1234,13 +1233,13 @@ Server.prototype.listen = function() { listenAfterLookup(port, arguments[1], backlog); } - function listenAfterLookup(port, address, backlog, shared) { + function listenAfterLookup(port, address, backlog, exclusive) { require('dns').lookup(address, function(err, ip, addressType) { if (err) { self.emit('error', err); } else { addressType = ip ? addressType : 4; - listen(self, ip, port, addressType, backlog, undefined, shared); + listen(self, ip, port, addressType, backlog, undefined, exclusive); } }); }; diff --git a/test/simple/test-net-listen-shared-ports.js b/test/simple/test-net-listen-shared-ports.js index dfb7eff0db7..7422e491fc6 100644 --- a/test/simple/test-net-listen-shared-ports.js +++ b/test/simple/test-net-listen-shared-ports.js @@ -56,9 +56,9 @@ if (cluster.isMaster) { server1.listen({ host: 'localhost', port: common.PORT, - shared: true + exclusive: false }, function() { - server2.listen({port: common.PORT + 1, shared: false}, function() { + server2.listen({port: common.PORT + 1, exclusive: true}, function() { // the first worker should succeed process.send('success'); }); From 6915f2ab5263d5a1a86b38dd3be2c23a9b02da77 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Thu, 28 Aug 2014 22:00:31 -0400 Subject: [PATCH 4/8] dgram: allow cluster workers to bind to exclusive ports Currently, all dgram bind() calls go through the cluster master. This commit allows workers to bind to a port exclusively. --- doc/api/dgram.markdown | 26 ++++++++ doc/api/net.markdown | 2 +- lib/dgram.js | 24 ++++++-- test/simple/test-dgram-bind-shared-ports.js | 66 +++++++++++++++++++++ 4 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 test/simple/test-dgram-bind-shared-ports.js diff --git a/doc/api/dgram.markdown b/doc/api/dgram.markdown index b659e6b1a97..891c0c70300 100644 --- a/doc/api/dgram.markdown +++ b/doc/api/dgram.markdown @@ -188,6 +188,32 @@ Example of a UDP server listening on port 41234: // server listening 0.0.0.0:41234 +### socket.bind(options, [callback]) + +* `options` {Object} - Required. Supports the following properties: + * `port` {Number} - Required. + * `address` {String} - Optional. + * `exclusive` {Boolean} - Optional. +* `callback` {Function} - Optional. + +The `port` and `address` properties of `options`, as well as the optional +callback function, behave as they do on a call to +[socket.bind(port, \[address\], \[callback\]) +](#dgram_socket_bind_port_address_callback). + +If `exclusive` is `false` (default), then cluster workers will use the same +underlying handle, allowing connection handling duties to be shared. When +`exclusive` is `true`, the handle is not shared, and attempted port sharing +results in an error. An example which listens on an exclusive port is +shown below. + + socket.bind({ + address: 'localhost', + port: 8000, + exclusive: true + }); + + ### socket.close() Close the underlying socket and stop listening for data on it. diff --git a/doc/api/net.markdown b/doc/api/net.markdown index c49b3d2d974..bad46d4b494 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -215,7 +215,7 @@ the last parameter `callback` will be added as an listener for the * `host` {String} - Optional. * `backlog` {Number} - Optional. * `exclusive` {Boolean} - Optional. -* `callback` {Function} +* `callback` {Function} - Optional. The `port`, `host`, and `backlog` properties of `options`, as well as the optional callback function, behave as they do on a call to diff --git a/lib/dgram.js b/lib/dgram.js index 761b4288293..e8f1f54efa5 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -152,6 +152,7 @@ function replaceHandle(self, newHandle) { Socket.prototype.bind = function(/*port, address, callback*/) { var self = this; + var firstArg = arguments[0]; self._healthCheck(); @@ -164,15 +165,26 @@ Socket.prototype.bind = function(/*port, address, callback*/) { self.once('listening', arguments[arguments.length - 1]); var UDP = process.binding('udp_wrap').UDP; - if (arguments[0] instanceof UDP) { - replaceHandle(self, arguments[0]); + if (firstArg instanceof UDP) { + replaceHandle(self, firstArg); startListening(self); return; } - var port = arguments[0]; - var address = arguments[1]; - if (util.isFunction(address)) address = ''; // a.k.a. "any address" + var port; + var address; + var exclusive; + + if (util.isObject(firstArg)) { + port = firstArg.port; + address = firstArg.address || ''; + exclusive = !!firstArg.exclusive; + } else { + port = firstArg; + address = arguments[1]; + exclusive = false; + if (util.isFunction(address)) address = ''; // a.k.a. "any address" + } // resolve address first self._handle.lookup(address, function(err, ip) { @@ -185,7 +197,7 @@ Socket.prototype.bind = function(/*port, address, callback*/) { if (!cluster) cluster = require('cluster'); - if (cluster.isWorker) { + if (cluster.isWorker && !exclusive) { cluster._getServer(self, ip, port, self.type, -1, function(err, handle) { if (err) { self.emit('error', errnoException(err, 'bind')); diff --git a/test/simple/test-dgram-bind-shared-ports.js b/test/simple/test-dgram-bind-shared-ports.js new file mode 100644 index 00000000000..709d3ad5518 --- /dev/null +++ b/test/simple/test-dgram-bind-shared-ports.js @@ -0,0 +1,66 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var cluster = require('cluster'); +var dgram = require('dgram'); + +function noop() {} + +if (cluster.isMaster) { + var worker1 = cluster.fork(); + + worker1.on('message', function(msg) { + assert.equal(msg, 'success'); + var worker2 = cluster.fork(); + + worker2.on('message', function(msg) { + assert.equal(msg, 'socket2:EADDRINUSE'); + worker1.kill(); + worker2.kill(); + }); + }); +} else { + var socket1 = dgram.createSocket('udp4', noop); + var socket2 = dgram.createSocket('udp4', noop); + + socket1.on('error', function(err) { + // no errors expected + process.send('socket1:' + err.code); + }); + + socket2.on('error', function(err) { + // an error is expected on the second worker + process.send('socket2:' + err.code); + }); + + socket1.bind({ + address: 'localhost', + port: common.PORT, + exclusive: false + }, function() { + socket2.bind({port: common.PORT + 1, exclusive: true}, function() { + // the first worker should succeed + process.send('success'); + }); + }); +} From e0c721c0e72ac1a738279714c0bbf139ec7ac59e Mon Sep 17 00:00:00 2001 From: cjihrig Date: Thu, 28 Aug 2014 23:24:35 -0400 Subject: [PATCH 5/8] net: support exclusive listen() for UNIX sockets --- doc/api/net.markdown | 6 ++++-- lib/net.js | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/doc/api/net.markdown b/doc/api/net.markdown index bad46d4b494..a1f80d1cbf5 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -211,16 +211,18 @@ the last parameter `callback` will be added as an listener for the ### server.listen(options, [callback]) * `options` {Object} - Required. Supports the following properties: - * `port` {Number} - Required. + * `port` {Number} - Optional. * `host` {String} - Optional. * `backlog` {Number} - Optional. + * `path` {String} - Optional. * `exclusive` {Boolean} - Optional. * `callback` {Function} - Optional. The `port`, `host`, and `backlog` properties of `options`, as well as the optional callback function, behave as they do on a call to [server.listen(port, \[host\], \[backlog\], \[callback\]) -](#net_server_listen_port_host_backlog_callback). +](#net_server_listen_port_host_backlog_callback). Alternatively, the `path` +option can be used to specify a UNIX socket. If `exclusive` is `false` (default), then cluster workers will use the same underlying handle, allowing connection handling duties to be shared. When diff --git a/lib/net.js b/lib/net.js index 77520ba9e2c..a410547cfe7 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1205,17 +1205,22 @@ Server.prototype.listen = function() { listen(self, null, -1, -1, backlog); } else if (util.isNumber(h.fd) && h.fd >= 0) { listen(self, null, null, null, backlog, h.fd); - } else if (util.isNumber(h.port)) { + } else { // The first argument is a configuration object if (h.backlog) backlog = h.backlog; - if (h.host) - listenAfterLookup(h.port, h.host, backlog, h.exclusive); - else - listen(self, null, h.port, 4, backlog, undefined, h.exclusive); - } else { - throw new Error('Invalid listen argument: ' + h); + if (util.isNumber(h.port)) { + if (h.host) + listenAfterLookup(h.port, h.host, backlog, h.exclusive); + else + listen(self, null, h.port, 4, backlog, undefined, h.exclusive); + } else if (h.path && isPipeName(h.path)) { + var pipeName = self._pipeName = h.path; + listen(self, pipeName, -1, -1, backlog, undefined, h.exclusive); + } else { + throw new Error('Invalid listen argument: ' + h); + } } } else if (isPipeName(arguments[0])) { // UNIX socket or Windows pipe. From ac42564523500f697e2327b3490ccc0f15a72426 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Wed, 3 Sep 2014 09:17:01 -0400 Subject: [PATCH 6/8] dgram: address comments on #8238 --- lib/dgram.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/dgram.js b/lib/dgram.js index e8f1f54efa5..ac167eeab25 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -150,9 +150,8 @@ function replaceHandle(self, newHandle) { self._handle = newHandle; } -Socket.prototype.bind = function(/*port, address, callback*/) { +Socket.prototype.bind = function(port /*, address, callback*/) { var self = this; - var firstArg = arguments[0]; self._healthCheck(); @@ -165,25 +164,22 @@ Socket.prototype.bind = function(/*port, address, callback*/) { self.once('listening', arguments[arguments.length - 1]); var UDP = process.binding('udp_wrap').UDP; - if (firstArg instanceof UDP) { - replaceHandle(self, firstArg); + if (port instanceof UDP) { + replaceHandle(self, port); startListening(self); return; } - var port; var address; var exclusive; - if (util.isObject(firstArg)) { - port = firstArg.port; - address = firstArg.address || ''; - exclusive = !!firstArg.exclusive; + if (util.isObject(port)) { + address = port.address || ''; + exclusive = !!port.exclusive; + port = port.port; } else { - port = firstArg; - address = arguments[1]; + address = util.isFunction(arguments[1]) ? '' : arguments[1]; exclusive = false; - if (util.isFunction(address)) address = ''; // a.k.a. "any address" } // resolve address first From 46799552f2a9005209ba2fb5bb26ff1527569aaa Mon Sep 17 00:00:00 2001 From: cjihrig Date: Wed, 3 Sep 2014 18:03:27 -0400 Subject: [PATCH 7/8] doc: remove trailing whitespace --- doc/api/net.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/net.markdown b/doc/api/net.markdown index a1f80d1cbf5..eb4988a7069 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -219,7 +219,7 @@ the last parameter `callback` will be added as an listener for the * `callback` {Function} - Optional. The `port`, `host`, and `backlog` properties of `options`, as well as the -optional callback function, behave as they do on a call to +optional callback function, behave as they do on a call to [server.listen(port, \[host\], \[backlog\], \[callback\]) ](#net_server_listen_port_host_backlog_callback). Alternatively, the `path` option can be used to specify a UNIX socket. From ab26f7e14096e4f3aaabef8a16bcdaa3d8274cf5 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Wed, 3 Sep 2014 18:04:32 -0400 Subject: [PATCH 8/8] net: remove extraneous semicolon --- lib/net.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net.js b/lib/net.js index a410547cfe7..4902bb3bf5f 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1247,7 +1247,7 @@ Server.prototype.listen = function() { listen(self, ip, port, addressType, backlog, undefined, exclusive); } }); - }; + } return self; };