@@ -60,6 +60,9 @@ inline constexpr size_t kSizeClassGranularity = 256;
6060// Number of size class buckets.
6161inline constexpr size_t kNumSizeClasses =
6262 kMaxCachedFrameSize / kSizeClassGranularity ;
63+ // Maximum number of frames to cache per size class bucket.
64+ // Bounds memory retained after a burst of concurrent coroutines.
65+ inline constexpr size_t kMaxCachedPerBucket = 32 ;
6366
6467inline size_t SizeClassIndex (size_t n) {
6568 return (n + kSizeClassGranularity - 1 ) / kSizeClassGranularity - 1 ;
@@ -69,20 +72,26 @@ inline size_t RoundUpToSizeClass(size_t n) {
6972 return (SizeClassIndex (n) + 1 ) * kSizeClassGranularity ;
7073}
7174
75+ struct FreeBucket {
76+ FreeBlock* head = nullptr ;
77+ size_t count = 0 ;
78+ };
79+
7280// Thread-local free lists, one per size class.
7381// Safe without locks because coroutines run on the event loop thread.
74- inline FreeBlock*& GetFreeList (size_t index) {
75- static thread_local FreeBlock* free_lists [kNumSizeClasses ] = {};
76- return free_lists [index];
82+ inline FreeBucket& GetBucket (size_t index) {
83+ static thread_local FreeBucket buckets [kNumSizeClasses ] = {};
84+ return buckets [index];
7785}
7886
7987inline void * CoroFrameAlloc (size_t n) {
8088 if (n <= kMaxCachedFrameSize ) {
8189 size_t idx = SizeClassIndex (n);
82- FreeBlock*& head = GetFreeList (idx);
83- if (head != nullptr ) {
84- FreeBlock* block = head;
85- head = block->next ;
90+ FreeBucket& bucket = GetBucket (idx);
91+ if (bucket.head != nullptr ) {
92+ FreeBlock* block = bucket.head ;
93+ bucket.head = block->next ;
94+ bucket.count --;
8695 return block;
8796 }
8897 // Nothing on free list; allocate at rounded-up size.
@@ -94,11 +103,16 @@ inline void* CoroFrameAlloc(size_t n) {
94103inline void CoroFrameFree (void * p, size_t n) {
95104 if (n <= kMaxCachedFrameSize ) {
96105 size_t idx = SizeClassIndex (n);
97- FreeBlock*& head = GetFreeList (idx);
106+ FreeBucket& bucket = GetBucket (idx);
107+ if (bucket.count >= kMaxCachedPerBucket ) {
108+ // Bucket is full; return directly to the system allocator.
109+ ::operator delete (p, RoundUpToSizeClass (n));
110+ return ;
111+ }
98112 auto * block = static_cast <FreeBlock*>(p);
99- block->next = head;
100- block-> size = n ;
101- head = block ;
113+ block->next = bucket. head ;
114+ bucket. head = block ;
115+ bucket. count ++ ;
102116 return ;
103117 }
104118 ::operator delete (p, n);
@@ -183,9 +197,7 @@ struct TrackedPromiseBase : PromiseBase {
183197 static void * operator new (size_t n) { return CoroFrameAlloc (n); }
184198 static void operator delete (void * p, size_t n) { CoroFrameFree (p, n); }
185199
186- void init_tracking (Environment* env,
187- const char * type_name,
188- v8::Eternal<v8::String>& cached_type_name) {
200+ void init_tracking (Environment* env, const char * type_name) {
189201 env_ = env;
190202 v8::Isolate* isolate = env->isolate ();
191203 v8::HandleScope handle_scope (isolate);
@@ -217,14 +229,19 @@ struct TrackedPromiseBase : PromiseBase {
217229 }
218230
219231 if (hooks->fields ()[AsyncHooks::kInit ] > 0 ) {
220- // Cache the type name V8 string per template instantiation.
221- // Eternal handles are immortal and free to dereference.
222- if (cached_type_name.IsEmpty ()) {
223- cached_type_name.Set (
232+ // Cache the type name string in the per-isolate static_str_map.
233+ // The key is the const char* from ConstString, which is a unique
234+ // pointer per template instantiation. This gives us per-isolate
235+ // caching that is safe with Worker threads (each Worker has its
236+ // own Isolate and IsolateData).
237+ auto & str_map = env->isolate_data ()->static_str_map ;
238+ v8::Eternal<v8::String>& eternal = str_map[type_name];
239+ if (eternal.IsEmpty ()) {
240+ eternal.Set (
224241 isolate,
225242 v8::String::NewFromUtf8 (isolate, type_name).ToLocalChecked ());
226243 }
227- v8::Local<v8::String> type = cached_type_name .Get (isolate);
244+ v8::Local<v8::String> type = eternal .Get (isolate);
228245 AsyncWrap::EmitAsyncInit (
229246 env, resource_.Get (isolate), type, async_id_, trigger_async_id_);
230247 }
@@ -372,11 +389,7 @@ class UvTrackedTask {
372389 }
373390
374391 void InitTracking (Environment* env) {
375- // Per-template-instantiation cache for the V8 type name string.
376- // Eternal handles are immortal, zero-cost to dereference, and
377- // safe across GC cycles.
378- static v8::Eternal<v8::String> cached_type_name;
379- handle_.promise ().init_tracking (env, Name.c_str (), cached_type_name);
392+ handle_.promise ().init_tracking (env, Name.c_str ());
380393 }
381394
382395 void Start () {
@@ -449,8 +462,7 @@ class UvTrackedTask<void, Name> {
449462 void await_resume () { handle_.promise ().rethrow_if_exception (); }
450463
451464 void InitTracking (Environment* env) {
452- static v8::Eternal<v8::String> cached_type_name;
453- handle_.promise ().init_tracking (env, Name.c_str (), cached_type_name);
465+ handle_.promise ().init_tracking (env, Name.c_str ());
454466 }
455467
456468 void Start () {
0 commit comments