Skip to content

Commit 5e592d2

Browse files
committed
zlib: do not coalesce multiple .flush() calls
This is an approach to address the issue linked below. Previously, when `.write()` and `.flush()` calls to a zlib stream were interleaved synchronously (i.e. without waiting for these operations to finish), multiple flush calls would have been coalesced into a single flushing operation. This patch changes behaviour so that each `.flush()` all corresponds to one flushing operation on the underlying zlib resource, and the order of operations is as if the `.flush()` call were a `.write()` call. One test had to be removed because it specifically tested the previous behaviour. As a drive-by fix, this also makes sure that all flush callbacks are called. Previously, that was not the case. Fixes: #28478
1 parent ca0884a commit 5e592d2

4 files changed

Lines changed: 47 additions & 50 deletions

File tree

lib/zlib.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ const {
4949
} = require('buffer');
5050
const { owner_symbol } = require('internal/async_hooks').symbols;
5151

52+
const kFlushFlag = Symbol('kFlushFlag');
53+
5254
const constants = internalBinding('constants').zlib;
5355
const {
5456
// Zlib flush levels
@@ -261,7 +263,6 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
261263
this._chunkSize = chunkSize;
262264
this._defaultFlushFlag = flush;
263265
this._finishFlushFlag = finishFlush;
264-
this._nextFlush = -1;
265266
this._defaultFullFlushFlag = fullFlush;
266267
this.once('end', this.close);
267268
this._info = opts && opts.info;
@@ -308,6 +309,8 @@ ZlibBase.prototype._flush = function(callback) {
308309

309310
// If a flush is scheduled while another flush is still pending, a way to figure
310311
// out which one is the "stronger" flush is needed.
312+
// This is currently only used to figure out which flush flag to use for the
313+
// last chunk.
311314
// Roughly, the following holds:
312315
// Z_NO_FLUSH (< Z_TREES) < Z_BLOCK < Z_PARTIAL_FLUSH <
313316
// Z_SYNC_FLUSH < Z_FULL_FLUSH < Z_FINISH
@@ -322,7 +325,6 @@ function maxFlush(a, b) {
322325
return flushiness[a] > flushiness[b] ? a : b;
323326
}
324327

325-
const flushBuffer = Buffer.alloc(0);
326328
ZlibBase.prototype.flush = function(kind, callback) {
327329
const ws = this._writableState;
328330

@@ -337,12 +339,9 @@ ZlibBase.prototype.flush = function(kind, callback) {
337339
} else if (ws.ending) {
338340
if (callback)
339341
this.once('end', callback);
340-
} else if (this._nextFlush !== -1) {
341-
// This means that there is a flush currently in the write queue.
342-
// We currently coalesce this flush into the pending one.
343-
this._nextFlush = maxFlush(this._nextFlush, kind);
344342
} else {
345-
this._nextFlush = kind;
343+
const flushBuffer = Buffer.alloc(0);
344+
flushBuffer[kFlushFlag] = kind;
346345
this.write(flushBuffer, '', callback);
347346
}
348347
};
@@ -361,9 +360,8 @@ ZlibBase.prototype._transform = function(chunk, encoding, cb) {
361360
var flushFlag = this._defaultFlushFlag;
362361
// We use a 'fake' zero-length chunk to carry information about flushes from
363362
// the public API to the actual stream implementation.
364-
if (chunk === flushBuffer) {
365-
flushFlag = this._nextFlush;
366-
this._nextFlush = -1;
363+
if (typeof chunk[kFlushFlag] === 'number') {
364+
flushFlag = chunk[kFlushFlag];
367365
}
368366

369367
// For the last chunk, also apply `_finishFlushFlag`.

test/parallel/test-zlib-flush-multiple-scheduled.js

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const { createGzip, createGunzip, Z_PARTIAL_FLUSH } = require('zlib');
5+
6+
// Verify that .flush() behaves like .write() in terms of ordering, e.g. in
7+
// a sequence like .write() + .flush() + .write() + .flush() each .flush() call
8+
// only affects the data written before it.
9+
// Refs: https://github.com/nodejs/node/issues/28478
10+
11+
const compress = createGzip();
12+
const decompress = createGunzip();
13+
decompress.setEncoding('utf8');
14+
15+
const events = [];
16+
17+
for (const chunk of ['abc', 'def', 'ghi']) {
18+
compress.write(chunk, common.mustCall(() => events.push({ written: chunk })));
19+
compress.flush(Z_PARTIAL_FLUSH, common.mustCall(() => {
20+
events.push('flushed');
21+
decompress.write(compress.read(), common.mustCall(() => {
22+
events.push({ read: decompress.read() });
23+
}));
24+
}));
25+
}
26+
27+
process.on('exit', () => {
28+
assert.deepStrictEqual(events, [
29+
{ written: 'abc' },
30+
'flushed',
31+
{ written: 'def' },
32+
{ read: 'abc' },
33+
'flushed',
34+
{ written: 'ghi' },
35+
{ read: 'def' },
36+
'flushed',
37+
{ read: 'ghi' }
38+
]);
39+
});

test/parallel/test-zlib-write-after-flush.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ for (const [ createCompress, createDecompress ] of [
3939
gunz.on('data', (c) => output += c);
4040
gunz.on('end', common.mustCall(() => {
4141
assert.strictEqual(output, input);
42-
assert.strictEqual(gzip._nextFlush, -1);
4342
}));
4443

4544
// Make sure that flush/write doesn't trigger an assert failure

0 commit comments

Comments
 (0)