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