@@ -486,6 +486,92 @@ Http2Session::Callbacks::~Callbacks() {
486486 nghttp2_session_callbacks_del (callbacks);
487487}
488488
489+ // Track memory allocated by nghttp2 using a custom allocator.
490+ class Http2Session ::MemoryAllocatorInfo {
491+ public:
492+ explicit MemoryAllocatorInfo (Http2Session* session)
493+ : info({ session, H2Malloc, H2Free, H2Calloc, H2Realloc }) {}
494+
495+ static void * H2Malloc (size_t size, void * user_data) {
496+ return H2Realloc (nullptr , size, user_data);
497+ }
498+
499+ static void * H2Calloc (size_t nmemb, size_t size, void * user_data) {
500+ size_t real_size = MultiplyWithOverflowCheck (nmemb, size);
501+ void * mem = H2Malloc (real_size, user_data);
502+ if (mem != nullptr )
503+ memset (mem, 0 , real_size);
504+ return mem;
505+ }
506+
507+ static void H2Free (void * ptr, void * user_data) {
508+ if (ptr == nullptr ) return ; // free(null); happens quite often.
509+ void * result = H2Realloc (ptr, 0 , user_data);
510+ CHECK_EQ (result, nullptr );
511+ }
512+
513+ static void * H2Realloc (void * ptr, size_t size, void * user_data) {
514+ Http2Session* session = static_cast <Http2Session*>(user_data);
515+ size_t previous_size = 0 ;
516+ char * original_ptr = nullptr ;
517+
518+ // We prepend each allocated buffer with a size_t containing the full
519+ // size of the allocation.
520+ if (size > 0 ) size += sizeof (size_t );
521+
522+ if (ptr != nullptr ) {
523+ // We are free()ing or re-allocating.
524+ original_ptr = static_cast <char *>(ptr) - sizeof (size_t );
525+ previous_size = *reinterpret_cast <size_t *>(original_ptr);
526+ // This means we called StopTracking() on this pointer before.
527+ if (previous_size == 0 ) {
528+ // Fall back to the standard Realloc() function.
529+ char * ret = UncheckedRealloc (original_ptr, size);
530+ if (ret != nullptr )
531+ ret += sizeof (size_t );
532+ return ret;
533+ }
534+ }
535+ CHECK_GE (session->current_nghttp2_memory_ , previous_size);
536+
537+ // TODO(addaleax): Add the following, and handle NGHTTP2_ERR_NOMEM properly
538+ // everywhere:
539+ //
540+ // if (size > previous_size &&
541+ // !session->IsAvailableSessionMemory(size - previous_size)) {
542+ // return nullptr;
543+ // }
544+
545+ char * mem = UncheckedRealloc (original_ptr, size);
546+
547+ if (mem != nullptr ) {
548+ // Adjust the memory info counter.
549+ session->current_nghttp2_memory_ += size - previous_size;
550+ *reinterpret_cast <size_t *>(mem) = size;
551+ mem += sizeof (size_t );
552+ } else if (size == 0 ) {
553+ session->current_nghttp2_memory_ -= previous_size;
554+ }
555+
556+ return mem;
557+ }
558+
559+ static void StopTracking (Http2Session* session, void * ptr) {
560+ size_t * original_ptr = reinterpret_cast <size_t *>(
561+ static_cast <char *>(ptr) - sizeof (size_t ));
562+ session->current_nghttp2_memory_ -= *original_ptr;
563+ *original_ptr = 0 ;
564+ }
565+
566+ inline nghttp2_mem* operator *() { return &info; }
567+
568+ nghttp2_mem info;
569+ };
570+
571+ void Http2Session::StopTrackingRcbuf (nghttp2_rcbuf* buf) {
572+ MemoryAllocatorInfo::StopTracking (this , buf);
573+ }
574+
489575Http2Session::Http2Session (Environment* env,
490576 Local<Object> wrap,
491577 nghttp2_session_type type)
@@ -517,15 +603,17 @@ Http2Session::Http2Session(Environment* env,
517603 = callback_struct_saved[hasGetPaddingCallback ? 1 : 0 ].callbacks ;
518604
519605 auto fn = type == NGHTTP2_SESSION_SERVER ?
520- nghttp2_session_server_new2 :
521- nghttp2_session_client_new2;
606+ nghttp2_session_server_new3 :
607+ nghttp2_session_client_new3;
608+
609+ MemoryAllocatorInfo allocator_info (this );
522610
523611 // This should fail only if the system is out of memory, which
524612 // is going to cause lots of other problems anyway, or if any
525613 // of the options are out of acceptable range, which we should
526614 // be catching before it gets this far. Either way, crash if this
527615 // fails.
528- CHECK_EQ (fn (&session_, callbacks, this , *opts), 0 );
616+ CHECK_EQ (fn (&session_, callbacks, this , *opts, *allocator_info ), 0 );
529617
530618 outgoing_storage_.reserve (4096 );
531619 outgoing_buffers_.reserve (32 );
@@ -553,6 +641,7 @@ Http2Session::~Http2Session() {
553641 Unconsume ();
554642 DEBUG_HTTP2SESSION (this , " freeing nghttp2 session" );
555643 nghttp2_session_del (session_);
644+ CHECK_EQ (current_nghttp2_memory_, 0 );
556645}
557646
558647inline bool HasHttp2Observer (Environment* env) {
@@ -1160,9 +1249,9 @@ inline void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
11601249 nghttp2_header item = headers[n++];
11611250 // The header name and value are passed as external one-byte strings
11621251 name_str =
1163- ExternalHeader::New<true >(env () , item.name ).ToLocalChecked ();
1252+ ExternalHeader::New<true >(this , item.name ).ToLocalChecked ();
11641253 value_str =
1165- ExternalHeader::New<false >(env () , item.value ).ToLocalChecked ();
1254+ ExternalHeader::New<false >(this , item.value ).ToLocalChecked ();
11661255 argv[j * 2 ] = name_str;
11671256 argv[j * 2 + 1 ] = value_str;
11681257 j++;
0 commit comments