From f8a4e164388a3be9bf882a2249400f3e356dea04 Mon Sep 17 00:00:00 2001 From: laino Date: Fri, 24 Jul 2015 20:22:34 +0200 Subject: [PATCH] Fix https://github.com/nodejs/io.js/issues/1047 --- lib/_http_outgoing.js | 48 +++++++++++++++---- .../test-http-destroyed-socket-write2.js | 6 ++- .../test-http-many-ended-pipelines.js | 8 +++- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index c9b0a87b3dbd0c..8fa4c20508a67d 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -71,6 +71,24 @@ function OutgoingMessage() { this._header = null; this._headers = null; this._headerNames = {}; + + // Call remaining callbacks if stream closes prematurely + this.once('close', function() { + if (this.output.length === 0) return; + + const err = new Error('stream closed prematurely'); + const outputCallbacks = this.outputCallbacks; + + this.output = []; + this.outputEncodings = []; + this.outputCallbacks = []; + + for (var i = 0; i < outputCallbacks.length; i++) { + let callback = outputCallbacks[i]; + if (callback) + callback(err); + } + }); } util.inherits(OutgoingMessage, Stream); @@ -162,12 +180,8 @@ OutgoingMessage.prototype._writeRaw = function(data, encoding, callback) { // Directly write to socket. return connection.write(data, encoding, callback); - } else if (connection && connection.destroyed) { - // The socket was destroyed. If we're still trying to write to it, - // then we haven't gotten the 'close' event yet. - return false; } else { - // buffer, as long as we're not destroyed. + // buffer, as long as we didn't get the "close" event return this._buffer(data, encoding, callback); } }; @@ -430,10 +444,13 @@ OutgoingMessage.prototype.write = function(chunk, encoding, callback) { throw new TypeError('first argument must be a string or Buffer'); } - // If we get an empty string or buffer, then just do nothing, and // signal the user to keep writing. - if (chunk.length === 0) return true; + if (chunk.length === 0) { + if (typeof callback === 'function') + process.nextTick(callback); + return true; + } var len, ret; if (this.chunkedEncoding) { @@ -517,11 +534,26 @@ OutgoingMessage.prototype.end = function(data, encoding, callback) { throw new TypeError('first argument must be a string or Buffer'); } + var self = this; + if (this.finished) { + if (data && data.length > 0) { + // Report that writing the data has failed + // because the stream was already 'ended'. + var err = new Error('write after end'); + process.nextTick(function() { + self.emit('error', err); + if (callback) callback(err); + }); + } else { + // The user wanted to end the stream anyway, + // so we don't need to report a failure. + if (callback) + process.nextTick(callback); + } return false; } - var self = this; function finish() { self.emit('finish'); } diff --git a/test/parallel/test-http-destroyed-socket-write2.js b/test/parallel/test-http-destroyed-socket-write2.js index 5bbb3bbc946f7d..a55991e0dc4fab 100644 --- a/test/parallel/test-http-destroyed-socket-write2.js +++ b/test/parallel/test-http-destroyed-socket-write2.js @@ -40,7 +40,11 @@ server.listen(common.PORT, function() { var sawEnd = false; req.on('error', function(er) { - assert(!gotError); + + // Each failed write will cause an error, but + // we are only interested in one + if (gotError) return; + gotError = true; switch (er.code) { // This is the expected case diff --git a/test/parallel/test-http-many-ended-pipelines.js b/test/parallel/test-http-many-ended-pipelines.js index 4e0bc7e8ff736f..13414193a45ed9 100644 --- a/test/parallel/test-http-many-ended-pipelines.js +++ b/test/parallel/test-http-many-ended-pipelines.js @@ -16,7 +16,13 @@ var numRequests = 20; var done = 0; var server = http.createServer(function(req, res) { - res.end('ok'); + + res.end('ok', common.mustCall(function() {})); + + // We *might* get a socket already closed error here, which + // occurs when the socket was destroyed before we finished + // writing our data. + res.on('error', function() {}); // Oh no! The connection died! req.socket.destroy();