Skip to content

Commit 252f376

Browse files
puzpuzpuzMylesBorins
authored andcommitted
zlib: switch to lazy init for zlib streams
PR-URL: #34048 Reviewed-By: Anna Henningsen <[email protected]>
1 parent eecb92c commit 252f376

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
@@ -659,18 +659,13 @@ function Zlib(opts, mode) {
659659
// to come up with a good solution that doesn't break our internal API,
660660
// and with it all supported npm versions at the time of writing.
661661
this._writeState = new Uint32Array(2);
662-
if (!handle.init(windowBits,
663-
level,
664-
memLevel,
665-
strategy,
666-
this._writeState,
667-
processCallback,
668-
dictionary)) {
669-
// TODO(addaleax): Sometimes we generate better error codes in C++ land,
670-
// e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with
671-
// the current bindings setup, though.
672-
throw new ERR_ZLIB_INITIALIZATION_FAILED();
673-
}
662+
handle.init(windowBits,
663+
level,
664+
memLevel,
665+
strategy,
666+
this._writeState,
667+
processCallback,
668+
dictionary);
674669

675670
ZlibBase.call(this, opts, mode, handle, zlibDefaultOpts);
676671

@@ -816,6 +811,9 @@ function Brotli(opts, mode) {
816811
new binding.BrotliDecoder(mode) : new binding.BrotliEncoder(mode);
817812

818813
this._writeState = new Uint32Array(2);
814+
// TODO(addaleax): Sometimes we generate better error codes in C++ land,
815+
// e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with
816+
// the current bindings setup, though.
819817
if (!handle.init(brotliInitParamsArray,
820818
this._writeState,
821819
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;
@@ -615,13 +618,8 @@ class ZlibStream : public CompressionStream<ZlibContext> {
615618
AllocScope alloc_scope(wrap);
616619
wrap->context()->SetAllocationFunctions(
617620
AllocForZlib, FreeForZlib, static_cast<CompressionStream*>(wrap));
618-
const CompressionError err =
619-
wrap->context()->Init(level, window_bits, mem_level, strategy,
620-
std::move(dictionary));
621-
if (err.IsError())
622-
wrap->EmitError(err);
623-
624-
return args.GetReturnValue().Set(!err.IsError());
621+
wrap->context()->Init(level, window_bits, mem_level, strategy,
622+
std::move(dictionary));
625623
}
626624

627625
static void Params(const FunctionCallbackInfo<Value>& args) {
@@ -724,6 +722,15 @@ using BrotliEncoderStream = BrotliCompressionStream<BrotliEncoderContext>;
724722
using BrotliDecoderStream = BrotliCompressionStream<BrotliDecoderContext>;
725723

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

729736
int status = Z_OK;
@@ -742,6 +749,11 @@ void ZlibContext::Close() {
742749

743750

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

747759
// If the avail_out is left at 0, then it means that it ran out
@@ -897,6 +909,11 @@ CompressionError ZlibContext::GetErrorInfo() const {
897909

898910

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

902919
switch (mode_) {
@@ -930,7 +947,7 @@ void ZlibContext::SetAllocationFunctions(alloc_func alloc,
930947
}
931948

932949

933-
CompressionError ZlibContext::Init(
950+
void ZlibContext::Init(
934951
int level, int window_bits, int mem_level, int strategy,
935952
std::vector<unsigned char>&& dictionary) {
936953
if (!((window_bits == 0) &&
@@ -974,6 +991,15 @@ CompressionError ZlibContext::Init(
974991
window_bits_ *= -1;
975992
}
976993

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

998-
dictionary_ = std::move(dictionary);
999-
10001024
if (err_ != Z_OK) {
10011025
dictionary_.clear();
10021026
mode_ = NONE;
1003-
return ErrorForMessage("zlib error");
1027+
return true;
10041028
}
10051029

1006-
return SetDictionary();
1030+
SetDictionary();
1031+
zlib_init_done_ = true;
1032+
return true;
10071033
}
10081034

10091035

@@ -1040,6 +1066,11 @@ CompressionError ZlibContext::SetDictionary() {
10401066

10411067

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

10451076
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)