34
34
#include < stdlib.h>
35
35
#include < string.h>
36
36
#include < sys/types.h>
37
+ #include < atomic>
37
38
38
39
namespace node {
39
40
@@ -97,6 +98,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
97
98
~ZCtx () override {
98
99
CHECK_EQ (false , write_in_progress_ && " write in progress" );
99
100
Close ();
101
+ CHECK_EQ (zlib_memory_, 0 );
102
+ CHECK_EQ (unreported_allocations_, 0 );
100
103
}
101
104
102
105
void Close () {
@@ -109,17 +112,15 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
109
112
CHECK (init_done_ && " close before init" );
110
113
CHECK_LE (mode_, UNZIP);
111
114
115
+ AllocScope alloc_scope (this );
112
116
int status = Z_OK;
113
117
if (mode_ == DEFLATE || mode_ == GZIP || mode_ == DEFLATERAW) {
114
118
status = deflateEnd (&strm_);
115
- int64_t change_in_bytes = -static_cast <int64_t >(kDeflateContextSize );
116
- env ()->isolate ()->AdjustAmountOfExternalAllocatedMemory (change_in_bytes);
117
119
} else if (mode_ == INFLATE || mode_ == GUNZIP || mode_ == INFLATERAW ||
118
120
mode_ == UNZIP) {
119
121
status = inflateEnd (&strm_);
120
- int64_t change_in_bytes = -static_cast <int64_t >(kInflateContextSize );
121
- env ()->isolate ()->AdjustAmountOfExternalAllocatedMemory (change_in_bytes);
122
122
}
123
+
123
124
CHECK (status == Z_OK || status == Z_DATA_ERROR);
124
125
mode_ = NONE;
125
126
@@ -165,6 +166,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
165
166
CHECK (0 && " Invalid flush value" );
166
167
}
167
168
169
+ AllocScope alloc_scope (ctx);
170
+
168
171
Bytef* in;
169
172
Bytef* out;
170
173
size_t in_off, in_len, out_off, out_len;
@@ -355,6 +358,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
355
358
356
359
// v8 land!
357
360
void AfterThreadPoolWork (int status) override {
361
+ AllocScope alloc_scope (this );
362
+
358
363
write_in_progress_ = false ;
359
364
360
365
if (status == UV_ECANCELED) {
@@ -505,14 +510,15 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
505
510
int strategy, uint32_t * write_result,
506
511
Local<Function> write_js_callback, char * dictionary,
507
512
size_t dictionary_len) {
513
+ AllocScope alloc_scope (ctx);
508
514
ctx->level_ = level;
509
515
ctx->windowBits_ = windowBits;
510
516
ctx->memLevel_ = memLevel;
511
517
ctx->strategy_ = strategy;
512
518
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) ;
516
522
517
523
ctx->flush_ = Z_NO_FLUSH;
518
524
@@ -540,16 +546,12 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
540
546
ctx->windowBits_ ,
541
547
ctx->memLevel_ ,
542
548
ctx->strategy_ );
543
- ctx->env ()->isolate ()
544
- ->AdjustAmountOfExternalAllocatedMemory (kDeflateContextSize );
545
549
break ;
546
550
case INFLATE:
547
551
case GUNZIP:
548
552
case INFLATERAW:
549
553
case UNZIP:
550
554
ctx->err_ = inflateInit2 (&ctx->strm_ , ctx->windowBits_ );
551
- ctx->env ()->isolate ()
552
- ->AdjustAmountOfExternalAllocatedMemory (kInflateContextSize );
553
555
break ;
554
556
default :
555
557
UNREACHABLE ();
@@ -605,6 +607,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
605
607
}
606
608
607
609
static void Params (ZCtx* ctx, int level, int strategy) {
610
+ AllocScope alloc_scope (ctx);
611
+
608
612
ctx->err_ = Z_OK;
609
613
610
614
switch (ctx->mode_ ) {
@@ -622,6 +626,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
622
626
}
623
627
624
628
void Reset () {
629
+ AllocScope alloc_scope (this );
630
+
625
631
err_ = Z_OK;
626
632
627
633
switch (mode_) {
@@ -660,8 +666,51 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
660
666
}
661
667
}
662
668
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
+ };
665
714
666
715
Bytef* dictionary_;
667
716
size_t dictionary_len_;
@@ -680,6 +729,8 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork {
680
729
unsigned int gzip_id_bytes_read_;
681
730
uint32_t * write_result_;
682
731
Persistent<Function> write_js_callback_;
732
+ std::atomic<ssize_t > unreported_allocations_{0 };
733
+ size_t zlib_memory_ = 0 ;
683
734
};
684
735
685
736
0 commit comments