Skip to content

Commit d60b13f

Browse files
puzpuzpuzaddaleax
authored andcommitted
zlib: switch to lazy init for zlib streams
PR-URL: #34048 Reviewed-By: Anna Henningsen <[email protected]>
1 parent 8cb306e commit d60b13f

File tree

3 files changed

+92
-26
lines changed

3 files changed

+92
-26
lines changed

lib/zlib.js

+10-12
Original file line numberDiff line numberDiff line change
@@ -652,18 +652,13 @@ function Zlib(opts, mode) {
652652
// to come up with a good solution that doesn't break our internal API,
653653
// and with it all supported npm versions at the time of writing.
654654
this._writeState = new Uint32Array(2);
655-
if (!handle.init(windowBits,
656-
level,
657-
memLevel,
658-
strategy,
659-
this._writeState,
660-
processCallback,
661-
dictionary)) {
662-
// TODO(addaleax): Sometimes we generate better error codes in C++ land,
663-
// e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with
664-
// the current bindings setup, though.
665-
throw new ERR_ZLIB_INITIALIZATION_FAILED();
666-
}
655+
handle.init(windowBits,
656+
level,
657+
memLevel,
658+
strategy,
659+
this._writeState,
660+
processCallback,
661+
dictionary);
667662

668663
ZlibBase.call(this, opts, mode, handle, zlibDefaultOpts);
669664

@@ -809,6 +804,9 @@ function Brotli(opts, mode) {
809804
new binding.BrotliDecoder(mode) : new binding.BrotliEncoder(mode);
810805

811806
this._writeState = new Uint32Array(2);
807+
// TODO(addaleax): Sometimes we generate better error codes in C++ land,
808+
// e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with
809+
// the current bindings setup, though.
812810
if (!handle.init(brotliInitParamsArray,
813811
this._writeState,
814812
processCallback)) {

src/node_zlib.cc

+45-14
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ class ZlibContext : public MemoryRetainer {
139139
CompressionError ResetStream();
140140

141141
// Zlib-specific:
142-
CompressionError Init(int level, int window_bits, int mem_level, int strategy,
143-
std::vector<unsigned char>&& dictionary);
142+
void Init(int level, int window_bits, int mem_level, int strategy,
143+
std::vector<unsigned char>&& dictionary);
144144
void SetAllocationFunctions(alloc_func alloc, free_func free, void* opaque);
145145
CompressionError SetParams(int level, int strategy);
146146

@@ -157,7 +157,10 @@ class ZlibContext : public MemoryRetainer {
157157
private:
158158
CompressionError ErrorForMessage(const char* message) const;
159159
CompressionError SetDictionary();
160+
bool InitZlib();
160161

162+
Mutex mutex_; // Protects zlib_init_done_.
163+
bool zlib_init_done_ = false;
161164
int err_ = 0;
162165
int flush_ = 0;
163166
int level_ = 0;
@@ -614,13 +617,8 @@ class ZlibStream : public CompressionStream<ZlibContext> {
614617
AllocScope alloc_scope(wrap);
615618
wrap->context()->SetAllocationFunctions(
616619
AllocForZlib, FreeForZlib, static_cast<CompressionStream*>(wrap));
617-
const CompressionError err =
618-
wrap->context()->Init(level, window_bits, mem_level, strategy,
619-
std::move(dictionary));
620-
if (err.IsError())
621-
wrap->EmitError(err);
622-
623-
return args.GetReturnValue().Set(!err.IsError());
620+
wrap->context()->Init(level, window_bits, mem_level, strategy,
621+
std::move(dictionary));
624622
}
625623

626624
static void Params(const FunctionCallbackInfo<Value>& args) {
@@ -723,6 +721,15 @@ using BrotliEncoderStream = BrotliCompressionStream<BrotliEncoderContext>;
723721
using BrotliDecoderStream = BrotliCompressionStream<BrotliDecoderContext>;
724722

725723
void ZlibContext::Close() {
724+
{
725+
Mutex::ScopedLock lock(mutex_);
726+
if (!zlib_init_done_) {
727+
dictionary_.clear();
728+
mode_ = NONE;
729+
return;
730+
}
731+
}
732+
726733
CHECK_LE(mode_, UNZIP);
727734

728735
int status = Z_OK;
@@ -741,6 +748,11 @@ void ZlibContext::Close() {
741748

742749

743750
void ZlibContext::DoThreadPoolWork() {
751+
bool first_init_call = InitZlib();
752+
if (first_init_call && err_ != Z_OK) {
753+
return;
754+
}
755+
744756
const Bytef* next_expected_header_byte = nullptr;
745757

746758
// If the avail_out is left at 0, then it means that it ran out
@@ -896,6 +908,11 @@ CompressionError ZlibContext::GetErrorInfo() const {
896908

897909

898910
CompressionError ZlibContext::ResetStream() {
911+
bool first_init_call = InitZlib();
912+
if (first_init_call && err_ != Z_OK) {
913+
return ErrorForMessage("Failed to init stream before reset");
914+
}
915+
899916
err_ = Z_OK;
900917

901918
switch (mode_) {
@@ -929,7 +946,7 @@ void ZlibContext::SetAllocationFunctions(alloc_func alloc,
929946
}
930947

931948

932-
CompressionError ZlibContext::Init(
949+
void ZlibContext::Init(
933950
int level, int window_bits, int mem_level, int strategy,
934951
std::vector<unsigned char>&& dictionary) {
935952
if (!((window_bits == 0) &&
@@ -973,6 +990,15 @@ CompressionError ZlibContext::Init(
973990
window_bits_ *= -1;
974991
}
975992

993+
dictionary_ = std::move(dictionary);
994+
}
995+
996+
bool ZlibContext::InitZlib() {
997+
Mutex::ScopedLock lock(mutex_);
998+
if (zlib_init_done_) {
999+
return false;
1000+
}
1001+
9761002
switch (mode_) {
9771003
case DEFLATE:
9781004
case GZIP:
@@ -994,15 +1020,15 @@ CompressionError ZlibContext::Init(
9941020
UNREACHABLE();
9951021
}
9961022

997-
dictionary_ = std::move(dictionary);
998-
9991023
if (err_ != Z_OK) {
10001024
dictionary_.clear();
10011025
mode_ = NONE;
1002-
return ErrorForMessage("zlib error");
1026+
return true;
10031027
}
10041028

1005-
return SetDictionary();
1029+
SetDictionary();
1030+
zlib_init_done_ = true;
1031+
return true;
10061032
}
10071033

10081034

@@ -1039,6 +1065,11 @@ CompressionError ZlibContext::SetDictionary() {
10391065

10401066

10411067
CompressionError ZlibContext::SetParams(int level, int strategy) {
1068+
bool first_init_call = InitZlib();
1069+
if (first_init_call && err_ != Z_OK) {
1070+
return ErrorForMessage("Failed to init stream before set parameters");
1071+
}
1072+
10421073
err_ = Z_OK;
10431074

10441075
switch (mode_) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const zlib = require('zlib');
5+
6+
// Tests that zlib streams support .reset() and .params()
7+
// before the first write. That is important to ensure that
8+
// lazy init of zlib native library handles these cases.
9+
10+
for (const fn of [
11+
(z, cb) => {
12+
z.reset();
13+
cb();
14+
},
15+
(z, cb) => z.params(0, zlib.constants.Z_DEFAULT_STRATEGY, cb)
16+
]) {
17+
const deflate = zlib.createDeflate();
18+
const inflate = zlib.createInflate();
19+
20+
deflate.pipe(inflate);
21+
22+
const output = [];
23+
inflate
24+
.on('error', (err) => {
25+
assert.ifError(err);
26+
})
27+
.on('data', (chunk) => output.push(chunk))
28+
.on('end', common.mustCall(
29+
() => assert.deepStrictEqual(Buffer.concat(output).toString(), 'abc')));
30+
31+
fn(deflate, () => {
32+
fn(inflate, () => {
33+
deflate.write('abc');
34+
deflate.end();
35+
});
36+
});
37+
}

0 commit comments

Comments
 (0)