Showing with 68 additions and 23 deletions.
  1. +49 −20 lib/_stream_writable.js
  2. +1 −0 lib/net.js
  3. +1 −0 test/fixtures/GH-892-request.js
  4. +1 −0 test/pummel/test-http-upload-timeout.js
  5. +16 −3 test/pummel/test-net-write-callbacks.js
@@ -81,6 +81,11 @@ function WritableState(options, stream) {
// or on a later tick.
this.sync = false;

// a flag to know if we're processing previously buffered items, which
// may call the _write() callback in the same tick, so that we don't
// end up in an overlapped onwrite situation.
this.bufferProcessing = false;

// the callback that's passed to _write(chunk,cb)
this.onwrite = function(er) {
onwrite(stream, er);
@@ -188,8 +193,7 @@ function onwrite(stream, er) {

if (cb) {
// Don't call the cb until the next tick if we're in sync mode.
// Also defer if we're about to write some more right now.
if (sync || state.buffer.length)
if (sync)
process.nextTick(cb);
else
cb();
@@ -204,24 +208,6 @@ function onwrite(stream, er) {
return;
}

// if there's something in the buffer waiting, then do that, too.
if (state.buffer.length) {
var chunkCb = state.buffer.shift();
var chunk = chunkCb[0];
cb = chunkCb[1];

if (false === state.decodeStrings)
l = chunk[0].length;
else
l = chunk.length;

state.writelen = l;
state.writecb = cb;
state.writechunk = chunk;
state.writing = true;
stream._write(chunk, state.onwrite);
}

if (state.length <= state.lowWaterMark && state.needDrain) {
// Must force callback to be called on nextTick, so that we don't
// emit 'drain' before the write() consumer gets the 'false' return
@@ -233,6 +219,49 @@ function onwrite(stream, er) {
stream.emit('drain');
});
}

// if there's something in the buffer waiting, then process it
// It would be nice if there were TCO in JS, and we could just
// shift the top off the buffer and _write that, but that approach
// causes RangeErrors when you have a very large number of very
// small writes, and is not very efficient otherwise.
if (!state.bufferProcessing && state.buffer.length) {
state.bufferProcessing = true;

for (var c = 0; c < state.buffer.length; c++) {
var chunkCb = state.buffer[c];
var chunk = chunkCb[0];
cb = chunkCb[1];

if (false === state.decodeStrings)
l = chunk[0].length;
else
l = chunk.length;

state.writelen = l;
state.writecb = cb;
state.writechunk = chunk;
state.writing = true;
state.sync = true;
stream._write(chunk, state.onwrite);
state.sync = false;

// if we didn't call the onwrite immediately, then
// it means that we need to wait until it does.
// also, that means that the chunk and cb are currently
// being processed, so move the buffer counter past them.
if (state.writing) {
c++;
break;
}
}

state.bufferProcessing = false;
if (c < state.buffer.length)
state.buffer = state.buffer.slice(c);
else
state.buffer.length = 0;
}
}

Writable.prototype._write = function(chunk, cb) {
@@ -612,6 +612,7 @@ Socket.prototype.__defineGetter__('bytesWritten', function() {
pending = this._pendingWrite;

state.buffer.forEach(function(el) {
el = el[0];
bytes += Buffer.byteLength(el[0], el[1]);
});

@@ -40,6 +40,7 @@ var req = https.request(options, function(res) {
assert.equal(200, res.statusCode);
gotResponse = true;
console.error('DONE');
res.resume();
});

req.end(new Buffer(bytesExpected));
@@ -41,6 +41,7 @@ server.on('request', function(req, res) {
server.close();
}
});
req.resume();
});

server.listen(common.PORT, '127.0.0.1', function() {
@@ -38,14 +38,27 @@ var server = net.Server(function(socket) {
});
});

var lastCalled = -1;
function makeCallback(c) {
var called = false;
return function() {
if (called)
throw new Error('called callback #' + c + ' more than once');
called = true;
if (c < lastCalled)
throw new Error('callbacks out of order. last=' + lastCalled +
' current=' + c);
lastCalled = c;
cbcount++;
};
}

server.listen(common.PORT, function() {
var client = net.createConnection(common.PORT);

client.on('connect', function() {
for (var i = 0; i < N; i++) {
client.write('hello world', function() {
cbcount++;
});
client.write('hello world', makeCallback(i));
}
client.end();
});