@@ -451,6 +451,92 @@ Http2Session::Callbacks::~Callbacks() {
451451 nghttp2_session_callbacks_del (callbacks);
452452}
453453
454+ // Track memory allocated by nghttp2 using a custom allocator.
455+ class Http2Session ::MemoryAllocatorInfo {
456+ public:
457+ explicit MemoryAllocatorInfo (Http2Session* session)
458+ : info({ session, H2Malloc, H2Free, H2Calloc, H2Realloc }) {}
459+
460+ static void * H2Malloc (size_t size, void * user_data) {
461+ return H2Realloc (nullptr , size, user_data);
462+ }
463+
464+ static void * H2Calloc (size_t nmemb, size_t size, void * user_data) {
465+ size_t real_size = MultiplyWithOverflowCheck (nmemb, size);
466+ void * mem = H2Malloc (real_size, user_data);
467+ if (mem != nullptr )
468+ memset (mem, 0 , real_size);
469+ return mem;
470+ }
471+
472+ static void H2Free (void * ptr, void * user_data) {
473+ if (ptr == nullptr ) return ; // free(null); happens quite often.
474+ void * result = H2Realloc (ptr, 0 , user_data);
475+ CHECK_EQ (result, nullptr );
476+ }
477+
478+ static void * H2Realloc (void * ptr, size_t size, void * user_data) {
479+ Http2Session* session = static_cast <Http2Session*>(user_data);
480+ size_t previous_size = 0 ;
481+ char * original_ptr = nullptr ;
482+
483+ // We prepend each allocated buffer with a size_t containing the full
484+ // size of the allocation.
485+ if (size > 0 ) size += sizeof (size_t );
486+
487+ if (ptr != nullptr ) {
488+ // We are free()ing or re-allocating.
489+ original_ptr = static_cast <char *>(ptr) - sizeof (size_t );
490+ previous_size = *reinterpret_cast <size_t *>(original_ptr);
491+ // This means we called StopTracking() on this pointer before.
492+ if (previous_size == 0 ) {
493+ // Fall back to the standard Realloc() function.
494+ char * ret = UncheckedRealloc (original_ptr, size);
495+ if (ret != nullptr )
496+ ret += sizeof (size_t );
497+ return ret;
498+ }
499+ }
500+ CHECK_GE (session->current_nghttp2_memory_ , previous_size);
501+
502+ // TODO(addaleax): Add the following, and handle NGHTTP2_ERR_NOMEM properly
503+ // everywhere:
504+ //
505+ // if (size > previous_size &&
506+ // !session->IsAvailableSessionMemory(size - previous_size)) {
507+ // return nullptr;
508+ // }
509+
510+ char * mem = UncheckedRealloc (original_ptr, size);
511+
512+ if (mem != nullptr ) {
513+ // Adjust the memory info counter.
514+ session->current_nghttp2_memory_ += size - previous_size;
515+ *reinterpret_cast <size_t *>(mem) = size;
516+ mem += sizeof (size_t );
517+ } else if (size == 0 ) {
518+ session->current_nghttp2_memory_ -= previous_size;
519+ }
520+
521+ return mem;
522+ }
523+
524+ static void StopTracking (Http2Session* session, void * ptr) {
525+ size_t * original_ptr = reinterpret_cast <size_t *>(
526+ static_cast <char *>(ptr) - sizeof (size_t ));
527+ session->current_nghttp2_memory_ -= *original_ptr;
528+ *original_ptr = 0 ;
529+ }
530+
531+ inline nghttp2_mem* operator *() { return &info; }
532+
533+ nghttp2_mem info;
534+ };
535+
536+ void Http2Session::StopTrackingRcbuf (nghttp2_rcbuf* buf) {
537+ MemoryAllocatorInfo::StopTracking (this , buf);
538+ }
539+
454540Http2Session::Http2Session (Environment* env,
455541 Local<Object> wrap,
456542 nghttp2_session_type type)
@@ -482,15 +568,17 @@ Http2Session::Http2Session(Environment* env,
482568 = callback_struct_saved[hasGetPaddingCallback ? 1 : 0 ].callbacks ;
483569
484570 auto fn = type == NGHTTP2_SESSION_SERVER ?
485- nghttp2_session_server_new2 :
486- nghttp2_session_client_new2;
571+ nghttp2_session_server_new3 :
572+ nghttp2_session_client_new3;
573+
574+ MemoryAllocatorInfo allocator_info (this );
487575
488576 // This should fail only if the system is out of memory, which
489577 // is going to cause lots of other problems anyway, or if any
490578 // of the options are out of acceptable range, which we should
491579 // be catching before it gets this far. Either way, crash if this
492580 // fails.
493- CHECK_EQ (fn (&session_, callbacks, this , *opts), 0 );
581+ CHECK_EQ (fn (&session_, callbacks, this , *opts, *allocator_info ), 0 );
494582
495583 outgoing_storage_.reserve (4096 );
496584 outgoing_buffers_.reserve (32 );
@@ -502,6 +590,7 @@ Http2Session::~Http2Session() {
502590 for (const auto & iter : streams_)
503591 iter.second ->session_ = nullptr ;
504592 nghttp2_session_del (session_);
593+ CHECK_EQ (current_nghttp2_memory_, 0 );
505594}
506595
507596std::string Http2Session::diagnostic_name () const {
@@ -1150,9 +1239,9 @@ void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
11501239 nghttp2_header item = headers[n++];
11511240 // The header name and value are passed as external one-byte strings
11521241 name_str =
1153- ExternalHeader::New<true >(env () , item.name ).ToLocalChecked ();
1242+ ExternalHeader::New<true >(this , item.name ).ToLocalChecked ();
11541243 value_str =
1155- ExternalHeader::New<false >(env () , item.value ).ToLocalChecked ();
1244+ ExternalHeader::New<false >(this , item.value ).ToLocalChecked ();
11561245 argv[j * 2 ] = name_str;
11571246 argv[j * 2 + 1 ] = value_str;
11581247 j++;
0 commit comments