Skip to content

Commit e529914

Browse files
committed
zlib: fix interaction of flushing and needDrain
Backport-PR-URL: #14571 Backport-Reviewed-By: James M Snell <[email protected]> Fixes: #14523 PR-URL: #14527 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent b1fef05 commit e529914

File tree

3 files changed

+96
-2
lines changed

3 files changed

+96
-2
lines changed

lib/zlib.js

+28-2
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ function Zlib(opts, mode) {
188188
this._flushFlag = opts.flush || constants.Z_NO_FLUSH;
189189
this._finishFlushFlag = opts.finishFlush !== undefined ?
190190
opts.finishFlush : constants.Z_FINISH;
191+
this._scheduledFlushFlag = constants.Z_NO_FLUSH;
191192

192193
if (opts.chunkSize !== undefined) {
193194
if (opts.chunkSize < constants.Z_MIN_CHUNK) {
@@ -300,6 +301,23 @@ Zlib.prototype._flush = function _flush(callback) {
300301
this._transform(Buffer.alloc(0), '', callback);
301302
};
302303

304+
// If a flush is scheduled while another flush is still pending, a way to figure
305+
// out which one is the "stronger" flush is needed.
306+
// Roughly, the following holds:
307+
// Z_NO_FLUSH (< Z_TREES) < Z_BLOCK < Z_PARTIAL_FLUSH <
308+
// Z_SYNC_FLUSH < Z_FULL_FLUSH < Z_FINISH
309+
const flushiness = [];
310+
let i = 0;
311+
for (const flushFlag of [constants.Z_NO_FLUSH, constants.Z_BLOCK,
312+
constants.Z_PARTIAL_FLUSH, constants.Z_SYNC_FLUSH,
313+
constants.Z_FULL_FLUSH, constants.Z_FINISH]) {
314+
flushiness[flushFlag] = i++;
315+
}
316+
317+
function maxFlush(a, b) {
318+
return flushiness[a] > flushiness[b] ? a : b;
319+
}
320+
303321
Zlib.prototype.flush = function flush(kind, callback) {
304322
var ws = this._writableState;
305323

@@ -315,13 +333,21 @@ Zlib.prototype.flush = function flush(kind, callback) {
315333
if (callback)
316334
this.once('end', callback);
317335
} else if (ws.needDrain) {
318-
if (callback) {
319-
const drainHandler = () => this.flush(kind, callback);
336+
const alreadyHadFlushScheduled =
337+
this._scheduledFlushFlag !== constants.Z_NO_FLUSH;
338+
this._scheduledFlushFlag = maxFlush(kind, this._scheduledFlushFlag);
339+
340+
// If a callback was passed, always register a new `drain` + flush handler,
341+
// mostly because that’s simpler and flush callbacks piling up is a rare
342+
// thing anyway.
343+
if (!alreadyHadFlushScheduled || callback) {
344+
const drainHandler = () => this.flush(this._scheduledFlushFlag, callback);
320345
this.once('drain', drainHandler);
321346
}
322347
} else {
323348
this._flushFlag = kind;
324349
this.write(Buffer.alloc(0), '', callback);
350+
this._scheduledFlushFlag = constants.Z_NO_FLUSH;
325351
}
326352
};
327353

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
3+
// Regression test for https://github.com/nodejs/node/issues/14523.
4+
// Checks that flushes interact properly with writableState.needDrain,
5+
// even if no flush callback was passed.
6+
7+
const common = require('../common');
8+
const assert = require('assert');
9+
const zlib = require('zlib');
10+
11+
const zipper = zlib.createGzip({ highWaterMark: 16384 });
12+
const unzipper = zlib.createGunzip();
13+
zipper.pipe(unzipper);
14+
15+
zipper.write('A'.repeat(17000));
16+
zipper.flush();
17+
18+
let received = 0;
19+
unzipper.on('data', common.mustCall((d) => {
20+
received += d.length;
21+
}, 2));
22+
23+
// Properly `.end()`ing the streams would interfere with checking that
24+
// `.flush()` works.
25+
process.on('exit', () => {
26+
assert.strictEqual(received, 17000);
27+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const zlib = require('zlib');
6+
7+
const {
8+
Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH
9+
} = zlib.constants;
10+
11+
common.crashOnUnhandledRejection();
12+
13+
async function getOutput(...sequenceOfFlushes) {
14+
const zipper = zlib.createGzip({ highWaterMark: 16384 });
15+
16+
zipper.write('A'.repeat(17000));
17+
for (const flush of sequenceOfFlushes) {
18+
zipper.flush(flush);
19+
}
20+
21+
const data = [];
22+
23+
return new Promise((resolve) => {
24+
zipper.on('data', common.mustCall((d) => {
25+
data.push(d);
26+
if (data.length === 2) resolve(Buffer.concat(data));
27+
}, 2));
28+
});
29+
}
30+
31+
(async function() {
32+
assert.deepStrictEqual(await getOutput(Z_SYNC_FLUSH),
33+
await getOutput(Z_SYNC_FLUSH, Z_PARTIAL_FLUSH));
34+
assert.deepStrictEqual(await getOutput(Z_SYNC_FLUSH),
35+
await getOutput(Z_PARTIAL_FLUSH, Z_SYNC_FLUSH));
36+
37+
assert.deepStrictEqual(await getOutput(Z_FINISH),
38+
await getOutput(Z_FULL_FLUSH, Z_FINISH));
39+
assert.deepStrictEqual(await getOutput(Z_FINISH),
40+
await getOutput(Z_SYNC_FLUSH, Z_FINISH));
41+
})();

0 commit comments

Comments
 (0)