Skip to content

Commit 60883de

Browse files
trevnorrisMylesBorins
authored andcommitted
async_wrap: call destroy() callback in uv_idle_t
Calling JS during GC is a no-no. So intead create a queue of all ids that need to have their destroy() callback called and call them later. Removed checking destroy() in test-async-wrap-uid because destroy() can be called after the 'exit' callback. Missing a reliable test to reproduce the issue that caused the FATAL_ERROR. Fixes: #8216Fixes: #9465 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]>
1 parent 28dbc46 commit 60883de

File tree

7 files changed

+69
-25
lines changed

7 files changed

+69
-25
lines changed

‎src/async-wrap.cc‎

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include"util.h"
66
#include"util-inl.h"
77

8+
#include"uv.h"
89
#include"v8.h"
910
#include"v8-profiler.h"
1011

@@ -183,6 +184,38 @@ void AsyncWrap::Initialize(Local<Object> target,
183184
}
184185

185186

187+
voidAsyncWrap::DestroyIdsCb(uv_idle_t* handle){
188+
uv_idle_stop(handle);
189+
190+
Environment* env = Environment::from_destroy_ids_idle_handle(handle);
191+
// None of the V8 calls done outside the HandleScope leak a handle. If this
192+
// changes in the future then the SealHandleScope wrapping the uv_run()
193+
// will catch this can cause the process to abort.
194+
HandleScope handle_scope(env->isolate());
195+
Context::Scope context_scope(env->context());
196+
Local<Function> fn = env->async_hooks_destroy_function();
197+
198+
if (fn.IsEmpty())
199+
return env->destroy_ids_list()->clear();
200+
201+
TryCatch try_catch(env->isolate());
202+
203+
for (auto current_id : *env->destroy_ids_list()){
204+
// Want each callback to be cleaned up after itself, instead of cleaning
205+
// them all up after the while() loop completes.
206+
HandleScope scope(env->isolate());
207+
Local<Value> argv = Number::New(env->isolate(), current_id);
208+
MaybeLocal<Value> ret = fn->Call(
209+
env->context(), Undefined(env->isolate()), 1, &argv);
210+
211+
if (ret.IsEmpty()){
212+
ClearFatalExceptionHandlers(env);
213+
FatalException(env->isolate(), try_catch);
214+
}
215+
}
216+
}
217+
218+
186219
voidLoadAsyncWrapperInfo(Environment* env){
187220
HeapProfiler* heap_profiler = env->isolate()->GetHeapProfiler();
188221
#defineV(PROVIDER) \
@@ -249,18 +282,10 @@ AsyncWrap::~AsyncWrap(){
249282
if (!ran_init_callback())
250283
return;
251284

252-
Local<Function> fn = env()->async_hooks_destroy_function();
253-
if (!fn.IsEmpty()){
254-
HandleScope scope(env()->isolate());
255-
Local<Value> uid = Number::New(env()->isolate(), get_uid());
256-
TryCatch try_catch(env()->isolate());
257-
MaybeLocal<Value> ret =
258-
fn->Call(env()->context(), Null(env()->isolate()), 1, &uid);
259-
if (ret.IsEmpty()){
260-
ClearFatalExceptionHandlers(env());
261-
FatalException(env()->isolate(), try_catch);
262-
}
263-
}
285+
if (env()->destroy_ids_list()->empty())
286+
uv_idle_start(env()->destroy_ids_idle_handle(), DestroyIdsCb);
287+
288+
env()->destroy_ids_list()->push_back(get_uid());
264289
}
265290

266291

‎src/async-wrap.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#defineSRC_ASYNC_WRAP_H_
33

44
#include"base-object.h"
5+
#include"uv.h"
56
#include"v8.h"
67

78
#include<stdint.h>
@@ -58,6 +59,8 @@ class AsyncWrap : public BaseObject{
5859
v8::Local<v8::Value> unused,
5960
v8::Local<v8::Context> context);
6061

62+
staticvoidDestroyIdsCb(uv_idle_t* handle);
63+
6164
inline ProviderType provider_type() const;
6265

6366
inlineint64_tget_uid() const;

‎src/env-inl.h‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ inline Environment::Environment(v8::Local<v8::Context> context,
227227

228228
RB_INIT(&cares_task_list_);
229229
handle_cleanup_waiting_ = 0;
230+
231+
destroy_ids_list_.reserve(512);
230232
}
231233

232234
inlineEnvironment::~Environment(){
@@ -286,6 +288,15 @@ inline uv_idle_t* Environment::immediate_idle_handle(){
286288
return &immediate_idle_handle_;
287289
}
288290

291+
inline Environment* Environment::from_destroy_ids_idle_handle(
292+
uv_idle_t* handle){
293+
returnContainerOf(&Environment::destroy_ids_idle_handle_, handle);
294+
}
295+
296+
inlineuv_idle_t* Environment::destroy_ids_idle_handle(){
297+
return &destroy_ids_idle_handle_;
298+
}
299+
289300
inline Environment* Environment::from_idle_prepare_handle(
290301
uv_prepare_t* handle){
291302
returnContainerOf(&Environment::idle_prepare_handle_, handle);
@@ -362,6 +373,10 @@ inline int64_t Environment::get_async_wrap_uid(){
362373
return ++async_wrap_uid_;
363374
}
364375

376+
inline std::vector<int64_t>* Environment::destroy_ids_list(){
377+
return &destroy_ids_list_;
378+
}
379+
365380
inlineuint32_t* Environment::heap_statistics_buffer() const{
366381
CHECK_NE(heap_statistics_buffer_, nullptr);
367382
return heap_statistics_buffer_;

‎src/env.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include"v8.h"
1212

1313
#include<stdint.h>
14+
#include<vector>
1415

1516
// Caveat emptor: we're going slightly crazy with macros here but the end
1617
// hopefully justifies the means. We have a lot of per-context properties
@@ -413,8 +414,10 @@ class Environment{
413414
inlineuint32_twatched_providers() const;
414415

415416
staticinline Environment* from_immediate_check_handle(uv_check_t* handle);
417+
staticinline Environment* from_destroy_ids_idle_handle(uv_idle_t* handle);
416418
inlineuv_check_t* immediate_check_handle();
417419
inlineuv_idle_t* immediate_idle_handle();
420+
inlineuv_idle_t* destroy_ids_idle_handle();
418421

419422
staticinline Environment* from_idle_prepare_handle(uv_prepare_t* handle);
420423
inlineuv_prepare_t* idle_prepare_handle();
@@ -451,6 +454,9 @@ class Environment{
451454

452455
inlineint64_tget_async_wrap_uid();
453456

457+
// List of id's that have been destroyed and need the destroy() cb called.
458+
inline std::vector<int64_t>* destroy_ids_list();
459+
454460
inlineuint32_t* heap_statistics_buffer() const;
455461
inlinevoidset_heap_statistics_buffer(uint32_t* pointer);
456462

@@ -531,6 +537,7 @@ class Environment{
531537
IsolateData* const isolate_data_;
532538
uv_check_t immediate_check_handle_;
533539
uv_idle_t immediate_idle_handle_;
540+
uv_idle_t destroy_ids_idle_handle_;
534541
uv_prepare_t idle_prepare_handle_;
535542
uv_check_t idle_check_handle_;
536543
AsyncHooks async_hooks_;
@@ -546,6 +553,7 @@ class Environment{
546553
bool trace_sync_io_;
547554
size_t makecallback_cntr_;
548555
int64_t async_wrap_uid_;
556+
std::vector<int64_t> destroy_ids_list_;
549557
debugger::Agent debugger_agent_;
550558

551559
HandleWrapQueue handle_wrap_queue_;

‎src/node.cc‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4202,6 +4202,9 @@ Environment* CreateEnvironment(Isolate* isolate,
42024202
uv_unref(reinterpret_cast<uv_handle_t*>(env->idle_prepare_handle()));
42034203
uv_unref(reinterpret_cast<uv_handle_t*>(env->idle_check_handle()));
42044204

4205+
uv_idle_init(env->event_loop(), env->destroy_ids_idle_handle());
4206+
uv_unref(reinterpret_cast<uv_handle_t*>(env->destroy_ids_idle_handle()));
4207+
42054208
// Register handle cleanups
42064209
env->RegisterHandleCleanup(
42074210
reinterpret_cast<uv_handle_t*>(env->immediate_check_handle()),

‎test/parallel/test-async-wrap-throw-from-callback.js‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const assert = require('assert');
66
constcrypto=require('crypto');
77
constdomain=require('domain');
88
constspawn=require('child_process').spawn;
9-
constcallbacks=['init','pre','post','destroy'];
9+
constcallbacks=['init','pre','post'];
1010
consttoCall=process.argv[2];
1111
varmsgCalled=0;
1212
varmsgReceived=0;
@@ -23,13 +23,9 @@ function post(){
2323
if(toCall==='post')
2424
thrownewError('post');
2525
}
26-
functiondestroy(){
27-
if(toCall==='destroy')
28-
thrownewError('destroy');
29-
}
3026

3127
if(typeofprocess.argv[2]==='string'){
32-
async_wrap.setupHooks({ init, pre, post, destroy});
28+
async_wrap.setupHooks({ init, pre, post });
3329
async_wrap.enable();
3430

3531
process.on('uncaughtException',()=>assert.ok(0,'UNREACHABLE'));

‎test/parallel/test-async-wrap-uid.js‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ const assert = require('assert');
66
constasync_wrap=process.binding('async_wrap');
77

88
conststorage=newMap();
9-
async_wrap.setupHooks({ init, pre, post, destroy});
9+
async_wrap.setupHooks({ init, pre, post });
1010
async_wrap.enable();
1111

1212
functioninit(uid){
1313
storage.set(uid,{
1414
init: true,
1515
pre: false,
1616
post: false,
17-
destroy: false
1817
});
1918
}
2019

@@ -26,10 +25,6 @@ function post(uid){
2625
storage.get(uid).post=true;
2726
}
2827

29-
functiondestroy(uid){
30-
storage.get(uid).destroy=true;
31-
}
32-
3328
fs.access(__filename,function(err){
3429
assert.ifError(err);
3530
});
@@ -51,7 +46,6 @@ process.once('exit', function(){
5146
init: true,
5247
pre: true,
5348
post: true,
54-
destroy: true
5549
});
5650
}
5751
});

0 commit comments

Comments
(0)