Skip to content

Commit 632253f

Browse files
indutnyindutny-signal
authored andcommitted
lib: use Writable#_final for 'finish' tracking
Instead overriding `emit` method and using `this._realFinish` to detect unexpected end of data - implement `_final` method and track the logic there. The only behavior change from the use of `_final` is that we would no longer emit `finish` when the input data is terminated early. Instead we would emit `error` as before and stop the processing. Note that this is the behavior provided by `stream.Writable`, and it is thus conformant with the specification. Additionally, replace uses of private `_events` property with either straight `emit()` with return value check, or `listenerCount()` in the situations where there might be an overhead from the constructed event arguments. Fix: #26
1 parent c64ada8 commit 632253f

3 files changed

Lines changed: 72 additions & 57 deletions

File tree

lib/Dicer.js

Lines changed: 35 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -55,40 +55,6 @@ class Dicer extends Writable {
5555
});
5656
}
5757

58-
emit(ev) {
59-
if (ev !== 'finish' || this._realFinish) {
60-
Writable.prototype.emit.apply(this, arguments);
61-
return;
62-
}
63-
64-
if (this._finished)
65-
return;
66-
67-
process.nextTick(() => {
68-
this.emit('error', new Error('Unexpected end of multipart data'));
69-
70-
if (this._part && !this._ignoreData) {
71-
const type = (this._isPreamble ? 'Preamble' : 'Part');
72-
this._part.emit(
73-
'error',
74-
new Error(`${type} terminated early due to `
75-
+ 'unexpected end of multipart data')
76-
);
77-
this._part.push(null);
78-
process.nextTick(() => {
79-
this._realFinish = true;
80-
this.emit('finish');
81-
this._realFinish = false;
82-
});
83-
return;
84-
}
85-
86-
this._realFinish = true;
87-
this.emit('finish');
88-
this._realFinish = false;
89-
});
90-
}
91-
9258
_write(data, encoding, cb) {
9359
// Ignore unexpected data (e.g. extra trailer data after finished)
9460
if (!this._hparser && !this._bparser)
@@ -97,9 +63,7 @@ class Dicer extends Writable {
9763
if (this._headerFirst && this._isPreamble) {
9864
if (!this._part) {
9965
this._part = new PartStream(this._partOpts);
100-
if (this._events.preamble)
101-
this.emit('preamble', this._part);
102-
else
66+
if (!this.emit('preamble', this._part))
10367
ignore(this);
10468
}
10569
const r = this._hparser.push(data);
@@ -123,6 +87,32 @@ class Dicer extends Writable {
12387
cb();
12488
}
12589

90+
_final(cb) {
91+
if (this._finished) {
92+
if (this._parts !== 0) {
93+
this._pause = true;
94+
this._cb = cb;
95+
return;
96+
}
97+
98+
cb();
99+
return;
100+
}
101+
102+
if (this._part && !this._ignoreData) {
103+
const type = (this._isPreamble ? 'Preamble' : 'Part');
104+
this._part.emit(
105+
'error',
106+
new Error(`${type} terminated early due to `
107+
+ 'unexpected end of multipart data')
108+
);
109+
this._part.push(null);
110+
ignore(this);
111+
}
112+
113+
cb(new Error('Unexpected end of multipart data'));
114+
}
115+
126116
reset() {
127117
this._part = undefined;
128118
this._bparser = undefined;
@@ -154,16 +144,14 @@ function onInfo(isMatch, data, start, end) {
154144
}
155145
}
156146
if (this._dashes === 2) {
157-
if ((start + i) < end && this._events.trailer)
147+
if ((start + i) < end && this.listenerCount('trailer'))
158148
this.emit('trailer', data.slice(start + i, end));
159149
this.reset();
160150
this._finished = true;
161151
// No more parts will be added
162-
if (this._parts === 0) {
163-
this._realFinish = true;
164-
this.emit('finish');
165-
this._realFinish = false;
166-
}
152+
if (this._parts === 0)
153+
unpause(this);
154+
167155
}
168156
if (this._dashes)
169157
return;
@@ -176,9 +164,7 @@ function onInfo(isMatch, data, start, end) {
176164
unpause(this);
177165
};
178166
ev = this._isPreamble ? 'preamble' : 'part';
179-
if (this._events[ev])
180-
this.emit(ev, this._part);
181-
else
167+
if (!this.emit(ev, this._part))
182168
ignore(this);
183169
if (!this._isPreamble)
184170
this._inHeader = true;
@@ -205,15 +191,9 @@ function onInfo(isMatch, data, start, end) {
205191
} else {
206192
++this._parts;
207193
this._part.on('end', () => {
208-
if (--this._parts === 0) {
209-
if (this._finished) {
210-
this._realFinish = true;
211-
this.emit('finish');
212-
this._realFinish = false;
213-
} else {
214-
unpause(this);
215-
}
216-
}
194+
if (--this._parts === 0)
195+
unpause(this);
196+
217197
});
218198
}
219199
this._part.push(null);

test/test-multipart.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,10 @@ function next() {
133133
});
134134
}).on('error', (err) => {
135135
error = err;
136-
}).on('finish', () => {
136+
onFinish();
137+
}).on('finish', onFinish);
138+
139+
function onFinish() {
137140
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'));
138141

139142
if (v.dicerError) {
@@ -242,7 +245,7 @@ function next() {
242245
}
243246
++t;
244247
next();
245-
});
248+
}
246249

247250
fs.createReadStream(fixtureBase + '/original').pipe(dicer);
248251
}

test/test-pipeline.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
3+
const { Readable } = require('stream');
4+
const { pipeline } = require('stream/promises');
5+
const Dicer = require('..');
6+
7+
async function main() {
8+
const r = new Readable({ read() {} });
9+
const d = new Dicer({ boundary: 'a' });
10+
11+
d.on('part', async (part) => {
12+
part.resume();
13+
});
14+
15+
r.push('--a\r\nA: 1\r\nB: 1\r\n\r\n123\r\n--a\r\n\r\n456\r\n--a--\r\n');
16+
setImmediate(() => {
17+
r.push(null);
18+
});
19+
20+
const timer = setTimeout(() => {
21+
throw new Error('Should be canceled');
22+
}, 2000);
23+
24+
await pipeline(r, d);
25+
26+
clearTimeout(timer);
27+
}
28+
29+
main().catch((err) => {
30+
console.error(err);
31+
process.exit(1);
32+
});

0 commit comments

Comments
 (0)