mirror of
https://github.com/nodejs/node.git
synced 2025-05-21 10:42:18 +00:00

Previously destroy could be called multiple times causing inconsistent and hard to predict behavior. Furthermore, since the stream _destroy implementation can only be called once, the behavior of applying destroy multiple times becomes unclear. This changes so that only the first destroy() call is executed and any subsequent calls are noops. PR-URL: https://github.com/nodejs/node/pull/29197 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
404 lines
9.2 KiB
JavaScript
404 lines
9.2 KiB
JavaScript
'use strict';
|
|
|
|
const common = require('../common');
|
|
const { Writable } = require('stream');
|
|
const assert = require('assert');
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
write.on('finish', common.mustNotCall());
|
|
write.on('close', common.mustCall());
|
|
|
|
write.destroy();
|
|
assert.strictEqual(write.destroyed, true);
|
|
}
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) {
|
|
this.destroy(new Error('asd'));
|
|
cb();
|
|
}
|
|
});
|
|
|
|
write.on('error', common.mustCall());
|
|
write.on('finish', common.mustNotCall());
|
|
write.end('asd');
|
|
assert.strictEqual(write.destroyed, true);
|
|
}
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
const expected = new Error('kaboom');
|
|
|
|
write.on('finish', common.mustNotCall());
|
|
write.on('close', common.mustCall());
|
|
write.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, expected);
|
|
}));
|
|
|
|
write.destroy(expected);
|
|
assert.strictEqual(write.destroyed, true);
|
|
}
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
write._destroy = function(err, cb) {
|
|
assert.strictEqual(err, expected);
|
|
cb(err);
|
|
};
|
|
|
|
const expected = new Error('kaboom');
|
|
|
|
write.on('finish', common.mustNotCall('no finish event'));
|
|
write.on('close', common.mustCall());
|
|
write.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, expected);
|
|
}));
|
|
|
|
write.destroy(expected);
|
|
assert.strictEqual(write.destroyed, true);
|
|
}
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); },
|
|
destroy: common.mustCall(function(err, cb) {
|
|
assert.strictEqual(err, expected);
|
|
cb();
|
|
})
|
|
});
|
|
|
|
const expected = new Error('kaboom');
|
|
|
|
write.on('finish', common.mustNotCall('no finish event'));
|
|
write.on('close', common.mustCall());
|
|
|
|
// Error is swallowed by the custom _destroy
|
|
write.on('error', common.mustNotCall('no error event'));
|
|
|
|
write.destroy(expected);
|
|
assert.strictEqual(write.destroyed, true);
|
|
}
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
write._destroy = common.mustCall(function(err, cb) {
|
|
assert.strictEqual(err, null);
|
|
cb();
|
|
});
|
|
|
|
write.destroy();
|
|
assert.strictEqual(write.destroyed, true);
|
|
}
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
write._destroy = common.mustCall(function(err, cb) {
|
|
assert.strictEqual(err, null);
|
|
process.nextTick(() => {
|
|
this.end();
|
|
cb();
|
|
});
|
|
});
|
|
|
|
const fail = common.mustNotCall('no finish event');
|
|
|
|
write.on('finish', fail);
|
|
write.on('close', common.mustCall());
|
|
|
|
write.destroy();
|
|
|
|
write.removeListener('finish', fail);
|
|
write.on('finish', common.mustCall());
|
|
assert.strictEqual(write.destroyed, true);
|
|
}
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
const expected = new Error('kaboom');
|
|
|
|
write._destroy = common.mustCall(function(err, cb) {
|
|
assert.strictEqual(err, null);
|
|
cb(expected);
|
|
});
|
|
|
|
write.on('close', common.mustCall());
|
|
write.on('finish', common.mustNotCall('no finish event'));
|
|
write.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, expected);
|
|
}));
|
|
|
|
write.destroy();
|
|
assert.strictEqual(write.destroyed, true);
|
|
}
|
|
|
|
{
|
|
// double error case
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
let ticked = false;
|
|
write.on('close', common.mustCall(() => {
|
|
assert.strictEqual(ticked, true);
|
|
}));
|
|
write.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(ticked, true);
|
|
assert.strictEqual(err.message, 'kaboom 1');
|
|
assert.strictEqual(write._writableState.errorEmitted, true);
|
|
}));
|
|
|
|
write.destroy(new Error('kaboom 1'));
|
|
write.destroy(new Error('kaboom 2'));
|
|
assert.strictEqual(write._writableState.errored, true);
|
|
assert.strictEqual(write._writableState.errorEmitted, false);
|
|
assert.strictEqual(write.destroyed, true);
|
|
ticked = true;
|
|
}
|
|
|
|
{
|
|
const writable = new Writable({
|
|
destroy: common.mustCall(function(err, cb) {
|
|
process.nextTick(cb, new Error('kaboom 1'));
|
|
}),
|
|
write(chunk, enc, cb) {
|
|
cb();
|
|
}
|
|
});
|
|
|
|
let ticked = false;
|
|
writable.on('close', common.mustCall(() => {
|
|
writable.on('error', common.mustNotCall());
|
|
writable.destroy(new Error('hello'));
|
|
assert.strictEqual(ticked, true);
|
|
assert.strictEqual(writable._writableState.errorEmitted, true);
|
|
}));
|
|
writable.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(ticked, true);
|
|
assert.strictEqual(err.message, 'kaboom 1');
|
|
assert.strictEqual(writable._writableState.errorEmitted, true);
|
|
}));
|
|
|
|
writable.destroy();
|
|
assert.strictEqual(writable.destroyed, true);
|
|
assert.strictEqual(writable._writableState.errored, false);
|
|
assert.strictEqual(writable._writableState.errorEmitted, false);
|
|
|
|
// Test case where `writable.destroy()` is called again with an error before
|
|
// the `_destroy()` callback is called.
|
|
writable.destroy(new Error('kaboom 2'));
|
|
assert.strictEqual(writable._writableState.errorEmitted, false);
|
|
assert.strictEqual(writable._writableState.errored, false);
|
|
|
|
ticked = true;
|
|
}
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
write.destroyed = true;
|
|
assert.strictEqual(write.destroyed, true);
|
|
|
|
// The internal destroy() mechanism should not be triggered
|
|
write.on('close', common.mustNotCall());
|
|
write.destroy();
|
|
}
|
|
|
|
{
|
|
function MyWritable() {
|
|
assert.strictEqual(this.destroyed, false);
|
|
this.destroyed = false;
|
|
Writable.call(this);
|
|
}
|
|
|
|
Object.setPrototypeOf(MyWritable.prototype, Writable.prototype);
|
|
Object.setPrototypeOf(MyWritable, Writable);
|
|
|
|
new MyWritable();
|
|
}
|
|
|
|
{
|
|
// Destroy and destroy callback
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
write.destroy();
|
|
|
|
const expected = new Error('kaboom');
|
|
|
|
write.destroy(expected, common.mustCall((err) => {
|
|
assert.strictEqual(err, undefined);
|
|
}));
|
|
}
|
|
|
|
{
|
|
// Checks that `._undestroy()` restores the state so that `final` will be
|
|
// called again.
|
|
const write = new Writable({
|
|
write: common.mustNotCall(),
|
|
final: common.mustCall((cb) => cb(), 2),
|
|
autoDestroy: true
|
|
});
|
|
|
|
write.end();
|
|
write.once('close', common.mustCall(() => {
|
|
write._undestroy();
|
|
write.end();
|
|
}));
|
|
}
|
|
|
|
{
|
|
const write = new Writable();
|
|
|
|
write.destroy();
|
|
write.on('error', common.mustNotCall());
|
|
write.write('asd', common.expectsError({
|
|
name: 'Error',
|
|
code: 'ERR_STREAM_DESTROYED',
|
|
message: 'Cannot call write after a stream was destroyed'
|
|
}));
|
|
}
|
|
|
|
{
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
|
|
write.on('error', common.mustNotCall());
|
|
|
|
write.cork();
|
|
write.write('asd', common.mustCall());
|
|
write.uncork();
|
|
|
|
write.cork();
|
|
write.write('asd', common.expectsError({
|
|
name: 'Error',
|
|
code: 'ERR_STREAM_DESTROYED',
|
|
message: 'Cannot call write after a stream was destroyed'
|
|
}));
|
|
write.destroy();
|
|
write.write('asd', common.expectsError({
|
|
name: 'Error',
|
|
code: 'ERR_STREAM_DESTROYED',
|
|
message: 'Cannot call write after a stream was destroyed'
|
|
}));
|
|
write.uncork();
|
|
}
|
|
|
|
{
|
|
// Call end(cb) after error & destroy
|
|
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(new Error('asd')); }
|
|
});
|
|
write.on('error', common.mustCall(() => {
|
|
write.destroy();
|
|
let ticked = false;
|
|
write.end(common.mustCall((err) => {
|
|
assert.strictEqual(ticked, true);
|
|
assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED');
|
|
}));
|
|
ticked = true;
|
|
}));
|
|
write.write('asd');
|
|
}
|
|
|
|
{
|
|
// Call end(cb) after finish & destroy
|
|
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { cb(); }
|
|
});
|
|
write.on('finish', common.mustCall(() => {
|
|
write.destroy();
|
|
let ticked = false;
|
|
write.end(common.mustCall((err) => {
|
|
assert.strictEqual(ticked, true);
|
|
assert.strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED');
|
|
}));
|
|
ticked = true;
|
|
}));
|
|
write.end();
|
|
}
|
|
|
|
{
|
|
// Call end(cb) after error & destroy and don't trigger
|
|
// unhandled exception.
|
|
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) { process.nextTick(cb); }
|
|
});
|
|
write.once('error', common.mustCall((err) => {
|
|
assert.strictEqual(err.message, 'asd');
|
|
}));
|
|
write.end('asd', common.mustCall((err) => {
|
|
assert.strictEqual(err.message, 'asd');
|
|
}));
|
|
write.destroy(new Error('asd'));
|
|
}
|
|
|
|
{
|
|
// Call buffered write callback with error
|
|
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) {
|
|
process.nextTick(cb, new Error('asd'));
|
|
},
|
|
autoDestroy: false
|
|
});
|
|
write.cork();
|
|
write.write('asd', common.mustCall((err) => {
|
|
assert.strictEqual(err.message, 'asd');
|
|
}));
|
|
write.write('asd', common.mustCall((err) => {
|
|
assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED');
|
|
}));
|
|
write.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err.message, 'asd');
|
|
}));
|
|
write.uncork();
|
|
}
|
|
|
|
{
|
|
// Ensure callback order.
|
|
|
|
let state = 0;
|
|
const write = new Writable({
|
|
write(chunk, enc, cb) {
|
|
// `setImmediate()` is used on purpose to ensure the callback is called
|
|
// after `process.nextTick()` callbacks.
|
|
setImmediate(cb);
|
|
}
|
|
});
|
|
write.write('asd', common.mustCall(() => {
|
|
assert.strictEqual(state++, 0);
|
|
}));
|
|
write.write('asd', common.mustCall((err) => {
|
|
assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED');
|
|
assert.strictEqual(state++, 1);
|
|
}));
|
|
write.destroy();
|
|
}
|