Skip to content

Commit cca0ba2

Browse files
committed
src: give Http2Session JS fields their own backing store
It looks like it’s virtually impossible at this point to create “fake” backing stores for objects that don’t fully own their memory allocations, like the sub-field `js_fields_` of `Http2Session`. In particular, it turns out that an `ArrayBuffer` cannot always be easily separated from its backing store in that situation through by detaching it. This commit gives the JS-exposed parts of the class its own memory allocation and its own backing store, simplifying the code a bit and fixing flakiness coming from it, at the cost of one additional layer of indirection when accessing the data. Refs: nodejs#30782 Fixes: nodejs#31107
1 parent 7e911d8 commit cca0ba2

File tree

2 files changed

+21
-40
lines changed

2 files changed

+21
-40
lines changed

src/node_http2.cc

+19-38
Original file line numberDiff line numberDiff line change
@@ -568,42 +568,23 @@ Http2Session::Http2Session(Environment* env,
568568
outgoing_storage_.reserve(1024);
569569
outgoing_buffers_.reserve(32);
570570

571-
{
572-
// Make the js_fields_ property accessible to JS land.
573-
std::unique_ptr<BackingStore> backing =
574-
ArrayBuffer::NewBackingStore(
575-
reinterpret_cast<uint8_t*>(&js_fields_),
576-
kSessionUint8FieldCount,
577-
[](void*, size_t, void*){},
578-
nullptr);
579-
Local<ArrayBuffer> ab =
580-
ArrayBuffer::New(env->isolate(), std::move(backing));
581-
// TODO(thangktran): drop this check when V8 is pumped to 8.0 .
582-
if (!ab->IsExternal())
583-
ab->Externalize(ab->GetBackingStore());
584-
ab->SetPrivate(env->context(),
585-
env->arraybuffer_untransferable_private_symbol(),
586-
True(env->isolate())).Check();
587-
js_fields_ab_.Reset(env->isolate(), ab);
588-
Local<Uint8Array> uint8_arr =
589-
Uint8Array::New(ab, 0, kSessionUint8FieldCount);
590-
USE(wrap->Set(env->context(), env->fields_string(), uint8_arr));
591-
}
571+
// Make the js_fields_ property accessible to JS land.
572+
js_fields_store_ =
573+
ArrayBuffer::NewBackingStore(env->isolate(), sizeof(SessionJSFields));
574+
js_fields_ = new(js_fields_store_->Data()) SessionJSFields;
575+
576+
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), js_fields_store_);
577+
Local<Uint8Array> uint8_arr =
578+
Uint8Array::New(ab, 0, kSessionUint8FieldCount);
579+
USE(wrap->Set(env->context(), env->fields_string(), uint8_arr));
592580
}
593581

594582
Http2Session::~Http2Session() {
595583
CHECK_EQ(flags_ & SESSION_STATE_HAS_SCOPE, 0);
596584
Debug(this, "freeing nghttp2 session");
597585
nghttp2_session_del(session_);
598586
CHECK_EQ(current_nghttp2_memory_, 0);
599-
HandleScope handle_scope(env()->isolate());
600-
// Detach js_fields_ab_ to avoid having problem when new Http2Session
601-
// instances are created on the same location of previous
602-
// instances. This in turn will call ArrayBuffer::NewBackingStore()
603-
// multiple times with the same buffer address and causing error.
604-
// Ref: https://github.com/nodejs/node/pull/30782
605-
Local<ArrayBuffer> ab = js_fields_ab_.Get(env()->isolate());
606-
ab->Detach();
587+
js_fields_->~SessionJSFields();
607588
}
608589

609590
std::string Http2Session::diagnostic_name() const {
@@ -871,7 +852,7 @@ int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
871852
Http2Stream::New(session, id, frame->headers.cat) ==
872853
nullptr)) {
873854
if (session->rejected_stream_count_++ >
874-
session->js_fields_.max_rejected_streams)
855+
session->js_fields_->max_rejected_streams)
875856
return NGHTTP2_ERR_CALLBACK_FAILURE;
876857
// Too many concurrent streams being opened
877858
nghttp2_submit_rst_stream(**session, NGHTTP2_FLAG_NONE, id,
@@ -965,9 +946,9 @@ int Http2Session::OnInvalidFrame(nghttp2_session* handle,
965946
Debug(session,
966947
"invalid frame received (%u/%u), code: %d",
967948
session->invalid_frame_count_,
968-
session->js_fields_.max_invalid_frames,
949+
session->js_fields_->max_invalid_frames,
969950
lib_error_code);
970-
if (session->invalid_frame_count_++ > session->js_fields_.max_invalid_frames)
951+
if (session->invalid_frame_count_++ > session->js_fields_->max_invalid_frames)
971952
return 1;
972953

973954
// If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error
@@ -1003,7 +984,7 @@ int Http2Session::OnFrameNotSent(nghttp2_session* handle,
1003984
if (error_code == NGHTTP2_ERR_SESSION_CLOSING ||
1004985
error_code == NGHTTP2_ERR_STREAM_CLOSED ||
1005986
error_code == NGHTTP2_ERR_STREAM_CLOSING ||
1006-
session->js_fields_.frame_error_listener_count == 0) {
987+
session->js_fields_->frame_error_listener_count == 0) {
1007988
return 0;
1008989
}
1009990

@@ -1306,7 +1287,7 @@ void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
13061287
// are considered advisory only, so this has no real effect other than to
13071288
// simply let user code know that the priority has changed.
13081289
void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1309-
if (js_fields_.priority_listener_count == 0) return;
1290+
if (js_fields_->priority_listener_count == 0) return;
13101291
Isolate* isolate = env()->isolate();
13111292
HandleScope scope(isolate);
13121293
Local<Context> context = env()->context();
@@ -1375,7 +1356,7 @@ void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
13751356

13761357
// Called by OnFrameReceived when a complete ALTSVC frame has been received.
13771358
void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) {
1378-
if (!(js_fields_.bitfield & (1 << kSessionHasAltsvcListeners))) return;
1359+
if (!(js_fields_->bitfield & (1 << kSessionHasAltsvcListeners))) return;
13791360
Isolate* isolate = env()->isolate();
13801361
HandleScope scope(isolate);
13811362
Local<Context> context = env()->context();
@@ -1454,7 +1435,7 @@ void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
14541435
return;
14551436
}
14561437

1457-
if (!(js_fields_.bitfield & (1 << kSessionHasPingListeners))) return;
1438+
if (!(js_fields_->bitfield & (1 << kSessionHasPingListeners))) return;
14581439
// Notify the session that a ping occurred
14591440
arg = Buffer::Copy(env(),
14601441
reinterpret_cast<const char*>(frame->ping.opaque_data),
@@ -1466,8 +1447,8 @@ void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
14661447
void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
14671448
bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
14681449
if (!ack) {
1469-
js_fields_.bitfield &= ~(1 << kSessionRemoteSettingsIsUpToDate);
1470-
if (!(js_fields_.bitfield & (1 << kSessionHasRemoteSettingsListeners)))
1450+
js_fields_->bitfield &= ~(1 << kSessionRemoteSettingsIsUpToDate);
1451+
if (!(js_fields_->bitfield & (1 << kSessionHasRemoteSettingsListeners)))
14711452
return;
14721453
// This is not a SETTINGS acknowledgement, notify and return
14731454
MakeCallback(env()->http2session_on_settings_function(), 0, nullptr);

src/node_http2.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -990,8 +990,8 @@ class Http2Session : public AsyncWrap,
990990
nghttp2_session* session_;
991991

992992
// JS-accessible numeric fields, as indexed by SessionUint8Fields.
993-
SessionJSFields js_fields_ = {};
994-
v8::Global<v8::ArrayBuffer> js_fields_ab_;
993+
SessionJSFields* js_fields_ = nullptr;
994+
std::shared_ptr<v8::BackingStore> js_fields_store_;
995995

996996
// The session type: client or server
997997
nghttp2_session_type session_type_;

0 commit comments

Comments
 (0)