3434#include < stdlib.h>
3535#include < string.h>
3636#include < sys/types.h>
37+ #include < atomic>
3738
3839namespace node {
3940
@@ -97,6 +98,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
9798 ~ZCtx () override {
9899 CHECK_EQ (false , write_in_progress_ && " write in progress" );
99100 Close ();
101+ CHECK_EQ (zlib_memory_, 0 );
102+ CHECK_EQ (unreported_allocations_, 0 );
100103 }
101104
102105 void Close () {
@@ -109,17 +112,15 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
109112 CHECK (init_done_ && " close before init" );
110113 CHECK_LE (mode_, UNZIP);
111114
115+ AllocScope alloc_scope (this );
112116 int status = Z_OK;
113117 if (mode_ == DEFLATE || mode_ == GZIP || mode_ == DEFLATERAW) {
114118 status = deflateEnd (&strm_);
115- int64_t change_in_bytes = -static_cast <int64_t >(kDeflateContextSize );
116- env ()->isolate ()->AdjustAmountOfExternalAllocatedMemory (change_in_bytes);
117119 } else if (mode_ == INFLATE || mode_ == GUNZIP || mode_ == INFLATERAW ||
118120 mode_ == UNZIP) {
119121 status = inflateEnd (&strm_);
120- int64_t change_in_bytes = -static_cast <int64_t >(kInflateContextSize );
121- env ()->isolate ()->AdjustAmountOfExternalAllocatedMemory (change_in_bytes);
122122 }
123+
123124 CHECK (status == Z_OK || status == Z_DATA_ERROR);
124125 mode_ = NONE;
125126
@@ -165,6 +166,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
165166 CHECK (0 && " Invalid flush value" );
166167 }
167168
169+ AllocScope alloc_scope (ctx);
170+
168171 Bytef* in;
169172 Bytef* out;
170173 size_t in_off, in_len, out_off, out_len;
@@ -355,6 +358,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
355358
356359 // v8 land!
357360 void AfterThreadPoolWork (int status) override {
361+ AllocScope alloc_scope (this );
362+
358363 write_in_progress_ = false ;
359364
360365 if (status == UV_ECANCELED) {
@@ -505,14 +510,15 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
505510 int strategy, uint32_t * write_result,
506511 Local<Function> write_js_callback, char * dictionary,
507512 size_t dictionary_len) {
513+ AllocScope alloc_scope (ctx);
508514 ctx->level_ = level;
509515 ctx->windowBits_ = windowBits;
510516 ctx->memLevel_ = memLevel;
511517 ctx->strategy_ = strategy;
512518
513- ctx->strm_ .zalloc = Z_NULL ;
514- ctx->strm_ .zfree = Z_NULL ;
515- ctx->strm_ .opaque = Z_NULL ;
519+ ctx->strm_ .zalloc = AllocForZlib ;
520+ ctx->strm_ .zfree = FreeForZlib ;
521+ ctx->strm_ .opaque = static_cast < void *>(ctx) ;
516522
517523 ctx->flush_ = Z_NO_FLUSH;
518524
@@ -540,16 +546,12 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
540546 ctx->windowBits_ ,
541547 ctx->memLevel_ ,
542548 ctx->strategy_ );
543- ctx->env ()->isolate ()
544- ->AdjustAmountOfExternalAllocatedMemory (kDeflateContextSize );
545549 break ;
546550 case INFLATE:
547551 case GUNZIP:
548552 case INFLATERAW:
549553 case UNZIP:
550554 ctx->err_ = inflateInit2 (&ctx->strm_ , ctx->windowBits_ );
551- ctx->env ()->isolate ()
552- ->AdjustAmountOfExternalAllocatedMemory (kInflateContextSize );
553555 break ;
554556 default :
555557 UNREACHABLE ();
@@ -605,6 +607,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
605607 }
606608
607609 static void Params (ZCtx* ctx, int level, int strategy) {
610+ AllocScope alloc_scope (ctx);
611+
608612 ctx->err_ = Z_OK;
609613
610614 switch (ctx->mode_ ) {
@@ -622,6 +626,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
622626 }
623627
624628 void Reset () {
629+ AllocScope alloc_scope (this );
630+
625631 err_ = Z_OK;
626632
627633 switch (mode_) {
@@ -660,8 +666,51 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
660666 }
661667 }
662668
663- static const int kDeflateContextSize = 16384 ; // approximate
664- static const int kInflateContextSize = 10240 ; // approximate
669+ // Allocation functions provided to zlib itself. We store the real size of
670+ // the allocated memory chunk just before the "payload" memory we return
671+ // to zlib.
672+ // Because we use zlib off the thread pool, we can not report memory directly
673+ // to V8; rather, we first store it as "unreported" memory in a separate
674+ // field and later report it back from the main thread.
675+ static void * AllocForZlib (void * data, uInt items, uInt size) {
676+ ZCtx* ctx = static_cast <ZCtx*>(data);
677+ size_t real_size =
678+ MultiplyWithOverflowCheck (static_cast <size_t >(items),
679+ static_cast <size_t >(size)) + sizeof (size_t );
680+ char * memory = UncheckedMalloc (real_size);
681+ if (UNLIKELY (memory == nullptr )) return nullptr ;
682+ *reinterpret_cast <size_t *>(memory) = real_size;
683+ ctx->unreported_allocations_ .fetch_add (real_size,
684+ std::memory_order_relaxed);
685+ return memory + sizeof (size_t );
686+ }
687+
688+ static void FreeForZlib (void * data, void * pointer) {
689+ if (UNLIKELY (pointer == nullptr )) return ;
690+ ZCtx* ctx = static_cast <ZCtx*>(data);
691+ char * real_pointer = static_cast <char *>(pointer) - sizeof (size_t );
692+ size_t real_size = *reinterpret_cast <size_t *>(real_pointer);
693+ ctx->unreported_allocations_ .fetch_sub (real_size,
694+ std::memory_order_relaxed);
695+ free (real_pointer);
696+ }
697+
698+ // This is called on the main thread after zlib may have allocated something
699+ // in order to report it back to V8.
700+ void AdjustAmountOfExternalAllocatedMemory () {
701+ ssize_t report =
702+ unreported_allocations_.exchange (0 , std::memory_order_relaxed);
703+ if (report == 0 ) return ;
704+ CHECK_IMPLIES (report < 0 , zlib_memory_ >= static_cast <size_t >(-report));
705+ zlib_memory_ += report;
706+ env ()->isolate ()->AdjustAmountOfExternalAllocatedMemory (report);
707+ }
708+
709+ struct AllocScope {
710+ explicit AllocScope (ZCtx* ctx) : ctx(ctx) {}
711+ ~AllocScope () { ctx->AdjustAmountOfExternalAllocatedMemory (); }
712+ ZCtx* ctx;
713+ };
665714
666715 Bytef* dictionary_;
667716 size_t dictionary_len_;
@@ -680,6 +729,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
680729 unsigned int gzip_id_bytes_read_;
681730 uint32_t * write_result_;
682731 Persistent<Function> write_js_callback_;
732+ std::atomic<ssize_t > unreported_allocations_{0 };
733+ size_t zlib_memory_ = 0 ;
683734};
684735
685736
0 commit comments