Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
47df432
build,deps: replace cjs-module-lexer with merve
anonrig Jan 28, 2026
17122e5
test_runner: fix test enqueue when test file has syntax error
geeksilva97 Feb 2, 2026
d147c08
module: do not invoke resolve hooks twice for imported cjs
joyeecheung Feb 2, 2026
b064ddc
test: unify assertSnapshot common patterns
legendecas Feb 2, 2026
300de2b
deps: update amaro to 1.1.6
nodejs-github-bot Feb 3, 2026
e559ef6
deps: update googletest to 56efe3983185e3f37e43415d1afa97e3860f187f
nodejs-github-bot Feb 3, 2026
4998f53
doc: update `server.dropMaxConnection` link
samuel871211 Feb 3, 2026
f0574fd
test: use the skipIfNoWatch() utility function
lpinca Feb 3, 2026
086a5a5
test: update WPT for url to e3c46fdf55
nodejs-github-bot Feb 3, 2026
a8b32b8
test: fix race condition in watch mode tests
mcollina Feb 3, 2026
99012a8
test: remove duplicate wpt tests
panva Feb 3, 2026
0df72f0
meta: bump actions/checkout from 6.0.1 to 6.0.2
dependabot[bot] Feb 3, 2026
020a836
meta: bump actions/stale from 10.1.0 to 10.1.1
dependabot[bot] Feb 3, 2026
e3ef6cb
meta: bump peter-evans/create-pull-request from 8.0.0 to 8.1.0
dependabot[bot] Feb 3, 2026
72a4136
meta: bump actions/cache from 5.0.1 to 5.0.3
dependabot[bot] Feb 3, 2026
820c1d0
meta: bump actions/setup-node from 6.1.0 to 6.2.0
dependabot[bot] Feb 3, 2026
fd98187
meta: bump cachix/cachix-action
dependabot[bot] Feb 3, 2026
eaa9a96
meta: bump actions/setup-python from 6.1.0 to 6.2.0
dependabot[bot] Feb 3, 2026
2fedc03
tools: update OpenSSL to 3.5.5 in `test-shared`
aduh95 Feb 4, 2026
26638bd
meta: bump step-security/harden-runner from 2.14.0 to 2.14.1
dependabot[bot] Feb 4, 2026
ade36ac
meta: bump github/codeql-action from 4.31.9 to 4.32.0
dependabot[bot] Feb 4, 2026
9383ac4
http: implement slab allocation for HTTP header parsing
mertcanaltin Feb 4, 2026
821e59e
doc: correct tools README Boxstarter link
MikeMcC399 Feb 4, 2026
e90eb1d
meta: persist sccache daemon until end of build workflows
Renegade334 Feb 4, 2026
e71e950
dns: fix Windows SRV ECONNREFUSED by adjusting c-ares fallback detection
NotVivek12 Feb 3, 2026
23d1ecf
test: fix flaky test-performance-eventloopdelay
mcollina Feb 5, 2026
c843e44
tools: test `--shared-merve` in `test-shared` workflow
aduh95 Feb 5, 2026
75a7a67
doc: fix grammatical error in README.md
junnyontop-pixel Feb 5, 2026
aa1b366
sqlite: use DictionaryTemplate for run() result
mertcanaltin Feb 5, 2026
1c1db94
tools,win: upgrade install additional tools to Visual Studio 2026
MikeMcC399 Feb 5, 2026
9409d30
doc: deprecation add more codemod
AugustinMauroy Feb 5, 2026
7fb7030
repl: fix flaky test-repl-programmatic-history
mcollina Feb 5, 2026
513df82
doc: update to Visual Studio 2026 manual install
MikeMcC399 Feb 5, 2026
bb90ef2
doc: document `url.format(urlString)` as deprecated under DEP0169
Renegade334 Feb 5, 2026
a8f33fd
tools: use ubuntu-slim runner in meta GitHub Actions
bnb Feb 6, 2026
bad3f02
tools: enforce removal of `lts-watch-*` labels on release proposals
aduh95 Feb 6, 2026
a2843f8
net: defer synchronous destroy calls in internalConnect
RajeshKumar11 Feb 6, 2026
7643bc8
test: fix case-insensitive path matching on Windows
mcollina Feb 6, 2026
8c71740
deps: update undici to 7.21.0
nodejs-github-bot Feb 6, 2026
41ec451
doc: fix --inspect security warning section
pimterry Feb 6, 2026
28905b9
src: consolidate C++ ReadFileSync/WriteFileSync utilities
joyeecheung Feb 3, 2026
d4c9b5c
sqlite: avoid extra copy for large text binds
thisalihassan Feb 7, 2026
c4ecfef
src: use simdutf for one-byte string UTF-8 write in stringBytes
mertcanaltin Feb 6, 2026
c1c6641
doc: mention default option for test runner env
styfle Feb 7, 2026
adce20c
test: print stack immediately avoiding GC interleaving
legendecas Feb 7, 2026
439b816
doc: clarify EventEmitter error handling in threat model
mcollina Feb 7, 2026
9c8ad7e
src: elide heap allocation in structured clone implementation
addaleax Feb 8, 2026
4790816
deps: update minimatch to 10.1.2
nodejs-github-bot Feb 8, 2026
c78c49e
deps: update amaro to 1.1.7
nodejs-github-bot Feb 8, 2026
a727054
deps: upgrade npm to 11.9.0
npm-cli-bot Feb 9, 2026
e90cec2
test: restraint version replacement pattern in snapshots
legendecas Feb 9, 2026
43133f2
2026-02-10, Version 25.6.1 (Current)
aduh95 Feb 9, 2026
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
Prev Previous commit
Next Next commit
http: implement slab allocation for HTTP header parsing
PR-URL: #61375
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
  • Loading branch information
mertcanaltin authored and aduh95 committed Feb 8, 2026
commit 9383ac4ab7c9f981d2bcdcdfbc8016de9888b42b
67 changes: 67 additions & 0 deletions benchmark/http/bench-parser-fragmented.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use strict';

const common = require('../common');

const bench = common.createBenchmark(main, {
len: [8, 16],
frags: [2, 4, 8],
n: [1e5],
}, {
flags: ['--expose-internals', '--no-warnings'],
});

function main({ len, frags, n }) {
const { HTTPParser } = common.binding('http_parser');
const REQUEST = HTTPParser.REQUEST;
const kOnHeaders = HTTPParser.kOnHeaders | 0;
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
const kOnBody = HTTPParser.kOnBody | 0;
const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;

function processHeaderFragmented(fragments, n) {
const parser = newParser(REQUEST);

bench.start();
for (let i = 0; i < n; i++) {
// Send header in fragments
for (const frag of fragments) {
parser.execute(frag, 0, frag.length);
}
parser.initialize(REQUEST, {});
}
bench.end(n);
}

function newParser(type) {
const parser = new HTTPParser();
parser.initialize(type, {});

parser.headers = [];

parser[kOnHeaders] = function() { };
parser[kOnHeadersComplete] = function() { };
parser[kOnBody] = function() { };
parser[kOnMessageComplete] = function() { };

return parser;
}

// Build the header
let header = `GET /hello HTTP/1.1\r\nContent-Type: text/plain\r\n`;

for (let i = 0; i < len; i++) {
header += `X-Filler${i}: ${Math.random().toString(36).substring(2)}\r\n`;
}
header += '\r\n';

// Split header into fragments
const headerBuf = Buffer.from(header);
const fragSize = Math.ceil(headerBuf.length / frags);
const fragments = [];

for (let i = 0; i < headerBuf.length; i += fragSize) {
fragments.push(headerBuf.slice(i, Math.min(i + fragSize, headerBuf.length)));
}

processHeaderFragmented(fragments, n);
}
140 changes: 91 additions & 49 deletions src/node_http_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -122,72 +122,116 @@ class BindingData : public BaseObject {
SET_MEMORY_INFO_NAME(BindingData)
};

// helper class for the Parser
struct StringPtr {
StringPtr() {
on_heap_ = false;
Reset();
}
class Parser;

class StringPtrAllocator {
public:
// Memory impact: ~8KB per parser (66 StringPtr × 128 bytes).
static constexpr size_t kSlabSize = 8192;

StringPtrAllocator() = default;

~StringPtr() {
Reset();
// Allocate memory from the slab. Returns nullptr if full.
char* TryAllocate(size_t size) {
if (length_ + size > kSlabSize) {
return nullptr;
}
char* ptr = buffer_ + length_;
length_ += size;
return ptr;
}

// Check if pointer is within this allocator's buffer.
bool Contains(const char* ptr) const {
return ptr >= buffer_ && ptr < buffer_ + kSlabSize;
}

// Reset allocator for new message.
void Reset() { length_ = 0; }

// If str_ does not point to a heap string yet, this function makes it do
private:
char buffer_[kSlabSize];
size_t length_ = 0;
};

struct StringPtr {
StringPtr() = default;
~StringPtr() { Reset(); }

StringPtr(const StringPtr&) = delete;
StringPtr& operator=(const StringPtr&) = delete;

// If str_ does not point to owned storage yet, this function makes it do
// so. This is called at the end of each http_parser_execute() so as not
// to leak references. See issue #2438 and test-http-parser-bad-ref.js.
void Save() {
if (!on_heap_ && size_ > 0) {
char* s = new char[size_];
memcpy(s, str_, size_);
str_ = s;
on_heap_ = true;
void Save(StringPtrAllocator* allocator) {
if (str_ == nullptr || on_heap_ ||
(allocator != nullptr && allocator->Contains(str_))) {
return;
}
// Try allocator first, fall back to heap
if (allocator != nullptr) {
char* ptr = allocator->TryAllocate(size_);
if (ptr != nullptr) {
memcpy(ptr, str_, size_);
str_ = ptr;
return;
}
}
char* s = new char[size_];
memcpy(s, str_, size_);
str_ = s;
on_heap_ = true;
}


void Reset() {
if (on_heap_) {
delete[] str_;
on_heap_ = false;
}

str_ = nullptr;
size_ = 0;
}


void Update(const char* str, size_t size) {
void Update(const char* str, size_t size, StringPtrAllocator* allocator) {
if (str_ == nullptr) {
str_ = str;
} else if (on_heap_ || str_ + size_ != str) {
// Non-consecutive input, make a copy on the heap.
// TODO(bnoordhuis) Use slab allocation, O(n) allocs is bad.
char* s = new char[size_ + size];
memcpy(s, str_, size_);
memcpy(s + size_, str, size);

if (on_heap_)
delete[] str_;
else
on_heap_ = true;
} else if (on_heap_ ||
(allocator != nullptr && allocator->Contains(str_)) ||
str_ + size_ != str) {
// Non-consecutive input, make a copy
const size_t new_size = size_ + size;
char* new_str = nullptr;

// Try allocator first (if not already on heap)
if (!on_heap_ && allocator != nullptr) {
new_str = allocator->TryAllocate(new_size);
}

str_ = s;
if (new_str != nullptr) {
memcpy(new_str, str_, size_);
memcpy(new_str + size_, str, size);
str_ = new_str;
} else {
// Fall back to heap
char* s = new char[new_size];
memcpy(s, str_, size_);
memcpy(s + size_, str, size);
if (on_heap_) delete[] str_;
str_ = s;
on_heap_ = true;
}
}
size_ += size;
}


Local<String> ToString(Environment* env) const {
if (size_ != 0)
return OneByteString(env->isolate(), str_, size_);
else
return String::Empty(env->isolate());
}


// Strip trailing OWS (SPC or HTAB) from string.
Local<String> ToTrimmedString(Environment* env) {
while (size_ > 0 && IsOWS(str_[size_ - 1])) {
Expand All @@ -196,14 +240,11 @@ struct StringPtr {
return ToString(env);
}


const char* str_;
bool on_heap_;
size_t size_;
const char* str_ = nullptr;
bool on_heap_ = false;
size_t size_ = 0;
};

class Parser;

struct ParserComparator {
bool operator()(const Parser* lhs, const Parser* rhs) const;
};
Expand Down Expand Up @@ -259,8 +300,7 @@ class Parser : public AsyncWrap, public StreamListener {
: AsyncWrap(binding_data->env(), wrap),
current_buffer_len_(0),
current_buffer_data_(nullptr),
binding_data_(binding_data) {
}
binding_data_(binding_data) {}

SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(Parser)
Expand All @@ -278,6 +318,7 @@ class Parser : public AsyncWrap, public StreamListener {
headers_completed_ = false;
chunk_extensions_nread_ = 0;
last_message_start_ = uv_hrtime();
allocator_.Reset();
url_.Reset();
status_message_.Reset();

Expand Down Expand Up @@ -308,7 +349,7 @@ class Parser : public AsyncWrap, public StreamListener {
return rv;
}

url_.Update(at, length);
url_.Update(at, length, &allocator_);
return 0;
}

Expand All @@ -319,7 +360,7 @@ class Parser : public AsyncWrap, public StreamListener {
return rv;
}

status_message_.Update(at, length);
status_message_.Update(at, length, &allocator_);
return 0;
}

Expand All @@ -345,7 +386,7 @@ class Parser : public AsyncWrap, public StreamListener {
CHECK_LT(num_fields_, kMaxHeaderFieldsCount);
CHECK_EQ(num_fields_, num_values_ + 1);

fields_[num_fields_ - 1].Update(at, length);
fields_[num_fields_ - 1].Update(at, length, &allocator_);

return 0;
}
Expand All @@ -366,7 +407,7 @@ class Parser : public AsyncWrap, public StreamListener {
CHECK_LT(num_values_, arraysize(values_));
CHECK_EQ(num_values_, num_fields_);

values_[num_values_ - 1].Update(at, length);
values_[num_values_ - 1].Update(at, length, &allocator_);

return 0;
}
Expand Down Expand Up @@ -594,15 +635,15 @@ class Parser : public AsyncWrap, public StreamListener {
}

void Save() {
url_.Save();
status_message_.Save();
url_.Save(&allocator_);
status_message_.Save(&allocator_);

for (size_t i = 0; i < num_fields_; i++) {
fields_[i].Save();
fields_[i].Save(&allocator_);
}

for (size_t i = 0; i < num_values_; i++) {
values_[i].Save();
values_[i].Save(&allocator_);
}
}

Expand Down Expand Up @@ -1006,6 +1047,7 @@ class Parser : public AsyncWrap, public StreamListener {


llhttp_t parser_;
StringPtrAllocator allocator_; // shared slab for all StringPtrs
StringPtr fields_[kMaxHeaderFieldsCount]; // header fields
StringPtr values_[kMaxHeaderFieldsCount]; // header values
StringPtr url_;
Expand Down