Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test-shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ jobs:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
core.exportVariable('SCCACHE_GHA_VERSION', 'on');
core.exportVariable('SCCACHE_GHA_ENABLED', 'on');
core.exportVariable('ACTIONS_CACHE_SERVICE_V2', 'on');
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
Expand All @@ -195,7 +195,7 @@ jobs:
nix-shell \
-I nixpkgs=./tools/nix/pkgs.nix \
--pure --keep TAR_DIR --keep FLAKY_TESTS \
--keep SCCACHE_GHA_VERSION --keep ACTIONS_CACHE_SERVICE_V2 --keep ACTIONS_RESULTS_URL --keep ACTIONS_RUNTIME_TOKEN \
--keep SCCACHE_GHA_ENABLED --keep ACTIONS_CACHE_SERVICE_V2 --keep ACTIONS_RESULTS_URL --keep ACTIONS_RUNTIME_TOKEN \
--arg loadJSBuiltinsDynamically false \
--arg ccache '(import <nixpkgs> {}).sccache' \
--arg devTools '[]' \
Expand Down
6 changes: 5 additions & 1 deletion lib/_http_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,11 @@ function responseOnTimeout() {
function requestOnFinish() {
const req = this;

if (req.shouldKeepAlive && req._ended)
// If the response ends before this request finishes writing, `responseOnEnd()`
// already released the socket. When `finish` fires later, that socket may
// belong to a different request, so only call `responseKeepAlive()` when the
// original request is still alive (`!req.destroyed`).
if (req.shouldKeepAlive && req._ended && !req.destroyed)
responseKeepAlive(req);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/internal/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ if (isMainThread) {
cwdCounter = new Uint32Array(constructSharedArrayBuffer(4));
const originalChdir = process.chdir;
process.chdir = function(path) {
AtomicsAdd(cwdCounter, 0, 1);
originalChdir(path);
AtomicsAdd(cwdCounter, 0, 1);
};
}

Expand Down
20 changes: 19 additions & 1 deletion lib/zlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -830,11 +830,29 @@ function Brotli(opts, mode) {
});
}

let dictionary = opts?.dictionary;
if (dictionary !== undefined && !isArrayBufferView(dictionary)) {
if (isAnyArrayBuffer(dictionary)) {
dictionary = Buffer.from(dictionary);
} else {
throw new ERR_INVALID_ARG_TYPE(
'options.dictionary',
['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],
dictionary,
);
}
}

const handle = mode === BROTLI_DECODE ?
new binding.BrotliDecoder(mode) : new binding.BrotliEncoder(mode);

this._writeState = new Uint32Array(2);
handle.init(brotliInitParamsArray, this._writeState, processCallback);
handle.init(
brotliInitParamsArray,
this._writeState,
processCallback,
dictionary,
);

ZlibBase.call(this, opts, mode, handle, brotliDefaultOpts);
}
Expand Down
94 changes: 84 additions & 10 deletions src/node_zlib.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

#include "brotli/decode.h"
#include "brotli/encode.h"
#include "brotli/shared_dictionary.h"
#include "zlib.h"
#include "zstd.h"
#include "zstd_errors.h"
Expand Down Expand Up @@ -256,7 +257,7 @@ class BrotliEncoderContext final : public BrotliContext {
public:
void Close();
void DoThreadPoolWork();
CompressionError Init();
CompressionError Init(std::vector<uint8_t>&& dictionary = {});
CompressionError ResetStream();
CompressionError SetParams(int key, uint32_t value);
CompressionError GetErrorInfo() const;
Expand All @@ -268,13 +269,18 @@ class BrotliEncoderContext final : public BrotliContext {
private:
bool last_result_ = false;
DeleteFnPtr<BrotliEncoderState, BrotliEncoderDestroyInstance> state_;
DeleteFnPtr<BrotliEncoderPreparedDictionary,
BrotliEncoderDestroyPreparedDictionary>
prepared_dictionary_;
// Dictionary data must remain valid while the prepared dictionary is alive.
std::vector<uint8_t> dictionary_;
};

class BrotliDecoderContext final : public BrotliContext {
public:
void Close();
void DoThreadPoolWork();
CompressionError Init();
CompressionError Init(std::vector<uint8_t>&& dictionary = {});
CompressionError ResetStream();
CompressionError SetParams(int key, uint32_t value);
CompressionError GetErrorInfo() const;
Expand All @@ -288,6 +294,8 @@ class BrotliDecoderContext final : public BrotliContext {
BrotliDecoderErrorCode error_ = BROTLI_DECODER_NO_ERROR;
std::string error_string_;
DeleteFnPtr<BrotliDecoderState, BrotliDecoderDestroyInstance> state_;
// Dictionary data must remain valid for the lifetime of the decoder.
std::vector<uint8_t> dictionary_;
};

class ZstdContext : public MemoryRetainer {
Expand Down Expand Up @@ -830,7 +838,8 @@ class BrotliCompressionStream final :
static void Init(const FunctionCallbackInfo<Value>& args) {
BrotliCompressionStream* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
CHECK(args.Length() == 3 && "init(params, writeResult, writeCallback)");
CHECK((args.Length() == 3 || args.Length() == 4) &&
"init(params, writeResult, writeCallback[, dictionary])");

CHECK(args[1]->IsUint32Array());
CHECK_GE(args[1].As<Uint32Array>()->Length(), 2);
Expand All @@ -841,7 +850,18 @@ class BrotliCompressionStream final :
wrap->InitStream(write_result, write_js_callback);

AllocScope alloc_scope(wrap);
CompressionError err = wrap->context()->Init();
std::vector<uint8_t> dictionary;
if (args.Length() == 4 && !args[3]->IsUndefined()) {
if (!args[3]->IsArrayBufferView()) {
THROW_ERR_INVALID_ARG_TYPE(
wrap->env(), "dictionary must be an ArrayBufferView if provided");
return;
}
ArrayBufferViewContents<uint8_t> contents(args[3]);
dictionary.assign(contents.data(), contents.data() + contents.length());
}

CompressionError err = wrap->context()->Init(std::move(dictionary));
if (err.IsError()) {
wrap->EmitError(err);
// TODO(addaleax): Sometimes we generate better error codes in C++ land,
Expand Down Expand Up @@ -1387,23 +1407,57 @@ void BrotliEncoderContext::DoThreadPoolWork() {

void BrotliEncoderContext::Close() {
state_.reset();
prepared_dictionary_.reset();
dictionary_.clear();
mode_ = NONE;
}

CompressionError BrotliEncoderContext::Init() {
CompressionError BrotliEncoderContext::Init(std::vector<uint8_t>&& dictionary) {
brotli_alloc_func alloc = CompressionStreamMemoryOwner::AllocForBrotli;
brotli_free_func free = CompressionStreamMemoryOwner::FreeForZlib;
void* opaque =
CompressionStream<BrotliEncoderContext>::AllocatorOpaquePointerForContext(
this);

// Clean up any previous dictionary state before re-initializing.
prepared_dictionary_.reset();
dictionary_.clear();

state_.reset(BrotliEncoderCreateInstance(alloc, free, opaque));
if (!state_) {
return CompressionError("Could not initialize Brotli instance",
"ERR_ZLIB_INITIALIZATION_FAILED",
-1);
} else {
return CompressionError {};
}

if (!dictionary.empty()) {
// The dictionary data must remain valid for the lifetime of the prepared
// dictionary, so take ownership via move.
dictionary_ = std::move(dictionary);

prepared_dictionary_.reset(
BrotliEncoderPrepareDictionary(BROTLI_SHARED_DICTIONARY_RAW,
dictionary_.size(),
dictionary_.data(),
BROTLI_MAX_QUALITY,
alloc,
free,
opaque));
if (!prepared_dictionary_) {
return CompressionError("Failed to prepare brotli dictionary",
"ERR_ZLIB_DICTIONARY_LOAD_FAILED",
-1);
}

if (!BrotliEncoderAttachPreparedDictionary(state_.get(),
prepared_dictionary_.get())) {
return CompressionError("Failed to attach brotli dictionary",
"ERR_ZLIB_DICTIONARY_LOAD_FAILED",
-1);
}
}

return CompressionError{};
}

CompressionError BrotliEncoderContext::ResetStream() {
Expand Down Expand Up @@ -1435,6 +1489,7 @@ CompressionError BrotliEncoderContext::GetErrorInfo() const {

void BrotliDecoderContext::Close() {
state_.reset();
dictionary_.clear();
mode_ = NONE;
}

Expand All @@ -1455,20 +1510,39 @@ void BrotliDecoderContext::DoThreadPoolWork() {
}
}

CompressionError BrotliDecoderContext::Init() {
CompressionError BrotliDecoderContext::Init(std::vector<uint8_t>&& dictionary) {
brotli_alloc_func alloc = CompressionStreamMemoryOwner::AllocForBrotli;
brotli_free_func free = CompressionStreamMemoryOwner::FreeForZlib;
void* opaque =
CompressionStream<BrotliDecoderContext>::AllocatorOpaquePointerForContext(
this);

// Clean up any previous dictionary state before re-initializing.
dictionary_.clear();

state_.reset(BrotliDecoderCreateInstance(alloc, free, opaque));
if (!state_) {
return CompressionError("Could not initialize Brotli instance",
"ERR_ZLIB_INITIALIZATION_FAILED",
-1);
} else {
return CompressionError {};
}

if (!dictionary.empty()) {
// The dictionary data must remain valid for the lifetime of the decoder,
// so take ownership via move.
dictionary_ = std::move(dictionary);

if (!BrotliDecoderAttachDictionary(state_.get(),
BROTLI_SHARED_DICTIONARY_RAW,
dictionary_.size(),
dictionary_.data())) {
return CompressionError("Failed to attach brotli dictionary",
"ERR_ZLIB_DICTIONARY_LOAD_FAILED",
-1);
}
}

return CompressionError{};
}

CompressionError BrotliDecoderContext::ResetStream() {
Expand Down
Loading
Loading