diff --git a/lib/net.js b/lib/net.js index 7f6f306b846..620fd29a321 100644 --- a/lib/net.js +++ b/lib/net.js @@ -249,9 +249,11 @@ function onSocketEnd() { this._readableState.ended = true; if (this._readableState.endEmitted) { this.readable = false; + maybeDestroy(this); } else { this.once('end', function() { this.readable = false; + maybeDestroy(this); }); this.read(0); } @@ -399,10 +401,22 @@ Socket.prototype.end = function(data, encoding) { // just in case we're waiting for an EOF. if (this.readable && !this._readableState.endEmitted) this.read(0); - return; + else + maybeDestroy(this); }; +// Call whenever we set writable=false or readable=false +function maybeDestroy(socket) { + if (!socket.readable && + !socket.writable && + !socket.destroyed && + !socket._connecting) { + socket.destroy(); + } +} + + Socket.prototype.destroySoon = function() { if (this.writable) this.end(); @@ -521,8 +535,10 @@ function onread(buffer, offset, length) { } else if (process._errno == 'EOF') { debug('EOF'); - if (self._readableState.length === 0) + if (self._readableState.length === 0) { self.readable = false; + maybeDestroy(self); + } if (self.onend) self.once('end', self.onend); diff --git a/test/simple/test-net-GH-5504.js b/test/simple/test-net-GH-5504.js new file mode 100644 index 00000000000..d5059f02864 --- /dev/null +++ b/test/simple/test-net-GH-5504.js @@ -0,0 +1,123 @@ +// 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'); + +// this test only fails with CentOS 6.3 using kernel version 2.6.32 +// On other linuxes and darwin, the `read` call gets an ECONNRESET in +// that case. On sunos, the `write` call fails with EPIPE. +// +// However, old CentOS will occasionally send an EOF instead of a +// ECONNRESET or EPIPE when the client has been destroyed abruptly. +// +// Make sure we don't keep trying to write or read more in that case. + +switch (process.argv[2]) { + case 'server': return server(); + case 'client': return client(); + case undefined: return parent(); + default: throw new Error('wtf'); +} + +function server() { + var net = require('net'); + var content = new Buffer(64 * 1024 * 1024); + content.fill('#'); + net.createServer(function(socket) { + this.close(); + socket.on('end', function() { + console.error('end'); + }); + socket.on('_socketEnd', function() { + console.error('_socketEnd'); + }); + socket.write(content); + }).listen(3000, function() { + console.log('listening'); + }); +} + +function client() { + var net = require('net'); + var client = net.connect({ + host: 'localhost', + port: 3000 + }, function() { + client.destroy(); + }); +} + +function parent() { + var spawn = require('child_process').spawn; + var node = process.execPath; + var assert = require('assert'); + var serverExited = false; + var clientExited = false; + var serverListened = false; + var opt = { env: { NODE_DEBUG: 'net' } }; + + process.on('exit', function() { + assert(serverExited); + assert(clientExited); + assert(serverListened); + console.log('ok'); + }); + + setTimeout(function() { + if (s) s.kill(); + if (c) c.kill(); + setTimeout(function() { + throw new Error('hang'); + }); + }, 1000).unref(); + + var s = spawn(node, [__filename, 'server'], opt); + var c; + + wrap(s.stderr, process.stderr, 'SERVER 2>'); + wrap(s.stdout, process.stdout, 'SERVER 1>'); + s.on('exit', function(c) { + console.error('server exited', c); + serverExited = true; + }); + + s.stdout.once('data', function() { + serverListened = true; + c = spawn(node, [__filename, 'client']); + wrap(c.stderr, process.stderr, 'CLIENT 2>'); + wrap(c.stdout, process.stdout, 'CLIENT 1>'); + c.on('exit', function(c) { + console.error('client exited', c); + clientExited = true; + }); + }); + + function wrap(inp, out, w) { + inp.setEncoding('utf8'); + inp.on('data', function(c) { + c = c.trim(); + if (!c) return; + out.write(w + c.split('\n').join('\n' + w) + '\n'); + }); + } +} +