Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stream: don't emit finish on error #28979

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/api/stream.md
Original file line number Diff line number Diff line change
Expand Up @@ -2413,7 +2413,8 @@ and `stream.Readable` classes, respectively. The `'finish'` event is emitted
after [`stream.end()`][stream-end] is called and all chunks have been processed
by [`stream._transform()`][stream-_transform]. The `'end'` event is emitted
after all data has been output, which occurs after the callback in
[`transform._flush()`][stream-_flush] has been called.
[`transform._flush()`][stream-_flush] has been called. In the case of an error
ronag marked this conversation as resolved.
Show resolved Hide resolved
neither `'finish'` nor `'end'` *should* be emitted.
ronag marked this conversation as resolved.
Show resolved Hide resolved

#### transform.\_flush(callback)

Expand Down
10 changes: 2 additions & 8 deletions lib/_stream_writable.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,19 +437,12 @@ function onwriteError(stream, state, sync, er, cb) {
// Defer the callback if we are being called synchronously
// to avoid piling up things on the stack
process.nextTick(cb, er);
// This can emit finish, and it will always happen
// after error
process.nextTick(finishMaybe, stream, state);
errorOrDestroy(stream, er);
} else {
// The caller expect this to happen before if
// it is async
cb(er);
errorOrDestroy(stream, er);
// This can emit finish, but finish must
// always follow error
finishMaybe(stream, state);
}
errorOrDestroy(stream, er);
}

function onwrite(stream, er) {
Expand Down Expand Up @@ -618,6 +611,7 @@ Object.defineProperty(Writable.prototype, 'writableLength', {
function needFinish(state) {
return (state.ending &&
state.length === 0 &&
!state.errorEmitted &&
state.bufferedRequest === null &&
!state.finished &&
!state.writing);
Expand Down
28 changes: 0 additions & 28 deletions test/parallel/test-net-connect-buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,34 +51,6 @@ tcp.listen(0, common.mustCall(function() {
assert.strictEqual(socket.connecting, true);
assert.strictEqual(socket.readyState, 'opening');

// Make sure that anything besides a buffer or a string throws.
common.expectsError(() => socket.write(null),
{
code: 'ERR_STREAM_NULL_VALUES',
type: TypeError,
message: 'May not write null values to stream'
});
[
true,
false,
undefined,
1,
1.0,
+Infinity,
-Infinity,
[],
{}
].forEach((value) => {
// We need to check the callback since 'error' will only
// be emitted once per instance.
socket.write(value, common.expectsError({
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "chunk" argument must be one of type string or Buffer. ' +
`Received type ${typeof value}`
}));
});

// Write a string that contains a multi-byte character sequence to test that
// `bytesWritten` is incremented with the # of bytes, not # of characters.
const a = "L'État, c'est ";
Expand Down
4 changes: 2 additions & 2 deletions test/parallel/test-net-socket-write-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function connectToServer() {
type: TypeError
});

client.end();
client.destroy();
})
.on('end', () => server.close());
.on('close', () => server.close());
Copy link
Member Author

@ronag ronag Aug 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note here. Since the client is errored the end() doesn't cause 'finish' which changes so that the socket is not destroyed (since net.socket doesn't have autoDestroy).

}
33 changes: 33 additions & 0 deletions test/parallel/test-net-write-arguments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';
const common = require('../common');
const net = require('net');

const socket = net.Stream({ highWaterMark: 0 });

// Make sure that anything besides a buffer or a string throws.
common.expectsError(() => socket.write(null),
{
code: 'ERR_STREAM_NULL_VALUES',
type: TypeError,
message: 'May not write null values to stream'
});
[
true,
false,
undefined,
1,
1.0,
+Infinity,
-Infinity,
[],
{}
].forEach((value) => {
// We need to check the callback since 'error' will only
// be emitted once per instance.
socket.write(value, common.expectsError({
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "chunk" argument must be one of type string or Buffer. ' +
`Received type ${typeof value}`
}));
});
49 changes: 10 additions & 39 deletions test/parallel/test-stream-writable-write-writev-finish.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,10 @@ const stream = require('stream');
cb(new Error('write test error'));
};

let firstError = false;
writable.on('finish', common.mustCall(function() {
assert.strictEqual(firstError, true);
}));

writable.on('prefinish', common.mustCall());

writable.on('finish', common.mustNotCall());
writable.on('prefinish', common.mustNotCall());
writable.on('error', common.mustCall((er) => {
assert.strictEqual(er.message, 'write test error');
firstError = true;
}));

writable.end('test');
Expand All @@ -36,16 +30,10 @@ const stream = require('stream');
setImmediate(cb, new Error('write test error'));
};

let firstError = false;
writable.on('finish', common.mustCall(function() {
assert.strictEqual(firstError, true);
}));

writable.on('prefinish', common.mustCall());

writable.on('finish', common.mustNotCall());
writable.on('prefinish', common.mustNotCall());
writable.on('error', common.mustCall((er) => {
assert.strictEqual(er.message, 'write test error');
firstError = true;
}));

writable.end('test');
Expand All @@ -62,16 +50,10 @@ const stream = require('stream');
cb(new Error('writev test error'));
};

let firstError = false;
writable.on('finish', common.mustCall(function() {
assert.strictEqual(firstError, true);
}));

writable.on('prefinish', common.mustCall());

writable.on('finish', common.mustNotCall());
writable.on('prefinish', common.mustNotCall());
writable.on('error', common.mustCall((er) => {
assert.strictEqual(er.message, 'writev test error');
firstError = true;
}));

writable.cork();
Expand All @@ -93,16 +75,10 @@ const stream = require('stream');
setImmediate(cb, new Error('writev test error'));
};

let firstError = false;
writable.on('finish', common.mustCall(function() {
assert.strictEqual(firstError, true);
}));

writable.on('prefinish', common.mustCall());

writable.on('finish', common.mustNotCall());
writable.on('prefinish', common.mustNotCall());
writable.on('error', common.mustCall((er) => {
assert.strictEqual(er.message, 'writev test error');
firstError = true;
}));

writable.cork();
Expand All @@ -123,14 +99,9 @@ const stream = require('stream');
rs._read = () => {};

const ws = new stream.Writable();
let firstError = false;

ws.on('finish', common.mustCall(function() {
assert.strictEqual(firstError, true);
}));
ws.on('error', common.mustCall(function() {
firstError = true;
}));
ws.on('finish', common.mustNotCall());
ws.on('error', common.mustCall());

ws._write = (chunk, encoding, done) => {
setImmediate(done, new Error());
Expand Down