Skip to content
Open
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
95 changes: 83 additions & 12 deletions src/node_http_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "node.h"
#include "node_buffer.h"
#include "node_debug.h"
#include "util.h"

#include "async_wrap-inl.h"
Expand Down Expand Up @@ -52,6 +53,7 @@ namespace http_parser { // NOLINT(build/namespaces)

using v8::Array;
using v8::Boolean;
using v8::CFunction;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Exception;
Expand Down Expand Up @@ -604,30 +606,49 @@ class Parser : public AsyncWrap, public StreamListener {
new Parser(binding_data, args.This());
}

// TODO(@anonrig): Add V8 Fast API
// NOTE: Close() deletes the parser object, which is not safe in Fast API
// because the calling convention assumes the receiver remains valid.
static void Close(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.This());

delete parser;
}

// TODO(@anonrig): Add V8 Fast API
static void Free(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.This());
FreeImpl(parser);
}

static void FastFree(Local<Object> receiver) {
TRACK_V8_FAST_API_CALL("http_parser.free");
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, receiver);
FreeImpl(parser);
}

static void FreeImpl(Parser* parser) {
// Since the Parser destructor isn't going to run the destroy() callbacks
// it needs to be triggered manually.
parser->EmitTraceEventDestroy();
parser->EmitDestroy();
}
Comment on lines 632 to 636
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FreeImpl() calls EmitTraceEventDestroy()/EmitDestroy(). AsyncWrap::EmitDestroy() can schedule SetImmediate / microtasks and touch V8 handles, which is not safe to run from a V8 Fast API callback. The fast path should either be removed for free, or FastFree should take FastApiCallbackOptions& and force a fallback to the slow callback whenever destroy hooks are enabled (or whenever it would call into V8).

Copilot uses AI. Check for mistakes.

// TODO(@anonrig): Add V8 Fast API
static void Remove(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.This());
RemoveImpl(parser);
}

static void FastRemove(Local<Object> receiver) {
TRACK_V8_FAST_API_CALL("http_parser.remove");
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, receiver);
RemoveImpl(parser);
}

static void RemoveImpl(Parser* parser) {
if (parser->connectionsList_ != nullptr) {
parser->connectionsList_->Pop(parser);
parser->connectionsList_->PopActive(parser);
Expand Down Expand Up @@ -736,23 +757,39 @@ class Parser : public AsyncWrap, public StreamListener {
}
}

// TODO(@anonrig): Add V8 Fast API
template <bool should_pause>
static void Pause(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.This());
// Should always be called from the same context.
CHECK_EQ(env, parser->env());
PauseImpl<should_pause>(parser);
}

template <bool should_pause>
static void FastPause(Local<Object> receiver) {
if constexpr (should_pause) {
TRACK_V8_FAST_API_CALL("http_parser.pause");
} else {
TRACK_V8_FAST_API_CALL("http_parser.resume");
Comment on lines +771 to +775
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FastPause()/FastResume() bypass the context invariant enforced in the slow path (CHECK_EQ(env, parser->env())). That means cross-context usage would no longer be caught on the fast path, potentially masking bugs and diverging behavior. Consider adding a fast-path guard (e.g., obtain the current Environment* and CHECK_EQ, or use FastApiCallbackOptions& to fall back to the slow callback when the current context/Environment does not match).

Copilot uses AI. Check for mistakes.
}
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, receiver);
PauseImpl<should_pause>(parser);
}

template <bool should_pause>
static void PauseImpl(Parser* parser) {
if constexpr (should_pause) {
llhttp_pause(&parser->parser_);
} else {
llhttp_resume(&parser->parser_);
}
}

// TODO(@anonrig): Add V8 Fast API
// NOTE: Consume() requires unwrapping a StreamBase from a JS object argument,
// which involves V8 API calls that are not supported in Fast API.
static void Consume(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.This());
Expand All @@ -762,19 +799,27 @@ class Parser : public AsyncWrap, public StreamListener {
stream->PushStreamListener(parser);
}

// TODO(@anonrig): Add V8 Fast API
static void Unconsume(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.This());
UnconsumeImpl(parser);
}

static void FastUnconsume(Local<Object> receiver) {
TRACK_V8_FAST_API_CALL("http_parser.unconsume");
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, receiver);
UnconsumeImpl(parser);
}

static void UnconsumeImpl(Parser* parser) {
// Already unconsumed
if (parser->stream_ == nullptr)
return;

parser->stream_->RemoveStreamListener(parser);
}


static void GetCurrentBuffer(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.This());
Expand Down Expand Up @@ -1086,9 +1131,22 @@ class Parser : public AsyncWrap, public StreamListener {
typedef int (Parser::*Call)();
typedef int (Parser::*DataCall)(const char* at, size_t length);

public:
static const llhttp_settings_t settings;

static CFunction fast_free_;
static CFunction fast_remove_;
static CFunction fast_pause_;
static CFunction fast_resume_;
static CFunction fast_unconsume_;
};

CFunction Parser::fast_free_(CFunction::Make(Parser::FastFree));
CFunction Parser::fast_remove_(CFunction::Make(Parser::FastRemove));
CFunction Parser::fast_pause_(CFunction::Make(Parser::FastPause<true>));
CFunction Parser::fast_resume_(CFunction::Make(Parser::FastPause<false>));
CFunction Parser::fast_unconsume_(CFunction::Make(Parser::FastUnconsume));

bool ParserComparator::operator()(const Parser* lhs, const Parser* rhs) const {
if (lhs->last_message_start_ == 0 && rhs->last_message_start_ == 0) {
// When both parsers are idle, guarantee strict order by
Expand Down Expand Up @@ -1330,16 +1388,24 @@ void CreatePerIsolateProperties(IsolateData* isolate_data,
Integer::NewFromUnsigned(isolate, kLenientAll));

t->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
Local<ObjectTemplate> instance = t->InstanceTemplate();
SetProtoMethod(isolate, t, "close", Parser::Close);
SetProtoMethod(isolate, t, "free", Parser::Free);
SetProtoMethod(isolate, t, "remove", Parser::Remove);
SetFastMethod(isolate, instance, "free", Parser::Free, &Parser::fast_free_);
SetFastMethod(
isolate, instance, "remove", Parser::Remove, &Parser::fast_remove_);
SetProtoMethod(isolate, t, "execute", Parser::Execute);
SetProtoMethod(isolate, t, "finish", Parser::Finish);
SetProtoMethod(isolate, t, "initialize", Parser::Initialize);
SetProtoMethod(isolate, t, "pause", Parser::Pause<true>);
SetProtoMethod(isolate, t, "resume", Parser::Pause<false>);
SetFastMethod(
isolate, instance, "pause", Parser::Pause<true>, &Parser::fast_pause_);
SetFastMethod(
isolate, instance, "resume", Parser::Pause<false>, &Parser::fast_resume_);
SetProtoMethod(isolate, t, "consume", Parser::Consume);
SetProtoMethod(isolate, t, "unconsume", Parser::Unconsume);
SetFastMethod(isolate,
instance,
"unconsume",
Parser::Unconsume,
&Parser::fast_unconsume_);
SetProtoMethod(isolate, t, "getCurrentBuffer", Parser::GetCurrentBuffer);

SetConstructorFunction(isolate, target, "HTTPParser", t);
Expand Down Expand Up @@ -1401,14 +1467,19 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(Parser::New);
registry->Register(Parser::Close);
registry->Register(Parser::Free);
registry->Register(Parser::fast_free_);
registry->Register(Parser::Remove);
registry->Register(Parser::fast_remove_);
registry->Register(Parser::Execute);
registry->Register(Parser::Finish);
registry->Register(Parser::Initialize);
registry->Register(Parser::Pause<true>);
registry->Register(Parser::fast_pause_);
registry->Register(Parser::Pause<false>);
registry->Register(Parser::fast_resume_);
registry->Register(Parser::Consume);
registry->Register(Parser::Unconsume);
registry->Register(Parser::fast_unconsume_);
registry->Register(Parser::GetCurrentBuffer);
registry->Register(ConnectionsList::New);
registry->Register(ConnectionsList::All);
Expand Down
Loading