Skip to content

Commit 2122e2f

Browse files
trevnorrisaddaleax
authored andcommitted
async_wrap: use kTotals to enable PromiseHook
Keep a total of enabled hook callbacks in kTotals. This value is used to track whether node::PromiseHook (src/async-wrap.cc) should be enabled or disabled. Don't enable node::PromiseHook, using enablePromiseHook(), until a hook has been added. Then, using disablePromiseHook(), disable node::PromiseHook when all hooks have been disabled. Need to use a native test in order to check the internal field of the Promise and check for a PromiseWrap. PR-URL: #13509 Reviewed-By: Andreas Madsen <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 96279e8 commit 2122e2f

File tree

7 files changed

+177
-12
lines changed

7 files changed

+177
-12
lines changed

‎lib/async_hooks.js‎

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ var tmp_async_hook_fields = null;
3838
// Each constant tracks how many callbacks there are for any given step of
3939
// async execution. These are tracked so if the user didn't include callbacks
4040
// for a given step, that step can bail out early.
41-
const{ kInit, kBefore, kAfter, kDestroy,kCurrentAsyncId, kCurrentTriggerId,
42-
kAsyncUidCntr, kInitTriggerId }=async_wrap.constants;
41+
const{ kInit, kBefore, kAfter, kDestroy,kTotals, kCurrentAsyncId,
42+
kCurrentTriggerId, kAsyncUidCntr, kInitTriggerId }=async_wrap.constants;
4343

4444
const{ async_id_symbol, trigger_id_symbol }=async_wrap;
4545

@@ -50,7 +50,9 @@ const after_symbol = Symbol('after');
5050
constdestroy_symbol=Symbol('destroy');
5151

5252
// Setup the callbacks that node::AsyncWrap will call when there are hooks to
53-
// process. They use the same functions as the JS embedder API.
53+
// process. They use the same functions as the JS embedder API. These callbacks
54+
// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
55+
// and the cost of doing so is negligible.
5456
async_wrap.setupHooks({ init,
5557
before: emitBeforeN,
5658
after: emitAfterN,
@@ -103,14 +105,21 @@ class AsyncHook{
103105
if(hooks_array.includes(this))
104106
returnthis;
105107

108+
constprev_kTotals=hook_fields[kTotals];
109+
hook_fields[kTotals]=0;
110+
106111
// createHook() has already enforced that the callbacks are all functions,
107112
// so here simply increment the count of whether each callbacks exists or
108113
// not.
109-
hook_fields[kInit]+=+!!this[init_symbol];
110-
hook_fields[kBefore]+=+!!this[before_symbol];
111-
hook_fields[kAfter]+=+!!this[after_symbol];
112-
hook_fields[kDestroy]+=+!!this[destroy_symbol];
114+
hook_fields[kTotals]+=hook_fields[kInit]+=+!!this[init_symbol];
115+
hook_fields[kTotals]+=hook_fields[kBefore]+=+!!this[before_symbol];
116+
hook_fields[kTotals]+=hook_fields[kAfter]+=+!!this[after_symbol];
117+
hook_fields[kTotals]+=hook_fields[kDestroy]+=+!!this[destroy_symbol];
113118
hooks_array.push(this);
119+
120+
if(prev_kTotals===0&&hook_fields[kTotals]>0)
121+
async_wrap.enablePromiseHook();
122+
114123
returnthis;
115124
}
116125

@@ -121,11 +130,18 @@ class AsyncHook{
121130
if(index===-1)
122131
returnthis;
123132

124-
hook_fields[kInit]-=+!!this[init_symbol];
125-
hook_fields[kBefore]-=+!!this[before_symbol];
126-
hook_fields[kAfter]-=+!!this[after_symbol];
127-
hook_fields[kDestroy]-=+!!this[destroy_symbol];
133+
constprev_kTotals=hook_fields[kTotals];
134+
hook_fields[kTotals]=0;
135+
136+
hook_fields[kTotals]+=hook_fields[kInit]-=+!!this[init_symbol];
137+
hook_fields[kTotals]+=hook_fields[kBefore]-=+!!this[before_symbol];
138+
hook_fields[kTotals]+=hook_fields[kAfter]-=+!!this[after_symbol];
139+
hook_fields[kTotals]+=hook_fields[kDestroy]-=+!!this[destroy_symbol];
128140
hooks_array.splice(index,1);
141+
142+
if(prev_kTotals>0&&hook_fields[kTotals]===0)
143+
async_wrap.disablePromiseHook();
144+
129145
returnthis;
130146
}
131147
}

‎src/async-wrap.cc‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,17 @@ static void PromiseHook(PromiseHookType type, Local<Promise> promise,
335335
Local<Value> parent, void* arg){
336336
Local<Context> context = promise->CreationContext();
337337
Environment* env = Environment::GetCurrent(context);
338+
339+
// PromiseHook() should never be called if no hooks have been enabled.
340+
CHECK_GT(env->async_hooks()->fields()[AsyncHooks::kTotals], 0);
341+
338342
Local<Value> resource_object_value = promise->GetInternalField(0);
339343
PromiseWrap* wrap = nullptr;
340344
if (resource_object_value->IsObject()){
341345
Local<Object> resource_object = resource_object_value.As<Object>();
342346
wrap = Unwrap<PromiseWrap>(resource_object);
343347
}
348+
344349
if (type == PromiseHookType::kInit || wrap == nullptr){
345350
bool silent = type != PromiseHookType::kInit;
346351
PromiseWrap* parent_wrap = nullptr;
@@ -368,6 +373,7 @@ static void PromiseHook(PromiseHookType type, Local<Promise> promise,
368373
} elseif (type == PromiseHookType::kResolve){
369374
// TODO(matthewloring): need to expose this through the async hooks api.
370375
}
376+
371377
CHECK_NE(wrap, nullptr);
372378
if (type == PromiseHookType::kBefore){
373379
PreCallbackExecution(wrap, false);
@@ -401,7 +407,6 @@ static void SetupHooks(const FunctionCallbackInfo<Value>& args){
401407
SET_HOOK_FN(before);
402408
SET_HOOK_FN(after);
403409
SET_HOOK_FN(destroy);
404-
env->AddPromiseHook(PromiseHook, nullptr);
405410
#undef SET_HOOK_FN
406411

407412
{
@@ -542,6 +547,7 @@ void AsyncWrap::Initialize(Local<Object> target,
542547
SET_HOOKS_CONSTANT(kBefore);
543548
SET_HOOKS_CONSTANT(kAfter);
544549
SET_HOOKS_CONSTANT(kDestroy);
550+
SET_HOOKS_CONSTANT(kTotals);
545551
SET_HOOKS_CONSTANT(kCurrentAsyncId);
546552
SET_HOOKS_CONSTANT(kCurrentTriggerId);
547553
SET_HOOKS_CONSTANT(kAsyncUidCntr);

‎src/env.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ class Environment{
344344
kBefore,
345345
kAfter,
346346
kDestroy,
347+
kTotals,
347348
kFieldsCount,
348349
};
349350

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include<node.h>
2+
#include<v8.h>
3+
4+
namespace{
5+
6+
using v8::FunctionCallbackInfo;
7+
using v8::Isolate;
8+
using v8::Local;
9+
using v8::NewStringType;
10+
using v8::Object;
11+
using v8::Promise;
12+
using v8::String;
13+
using v8::Value;
14+
15+
staticvoidThrowError(Isolate* isolate, constchar* err_msg){
16+
Local<String> str = String::NewFromOneByte(
17+
isolate,
18+
reinterpret_cast<constuint8_t*>(err_msg),
19+
NewStringType::kNormal).ToLocalChecked();
20+
isolate->ThrowException(str);
21+
}
22+
23+
staticvoidGetPromiseField(const FunctionCallbackInfo<Value>& args){
24+
auto isolate = args.GetIsolate();
25+
26+
if (!args[0]->IsPromise())
27+
returnThrowError(isolate, "arg is not an Promise");
28+
29+
auto p = args[0].As<Promise>();
30+
if (p->InternalFieldCount() < 1)
31+
returnThrowError(isolate, "Promise has no internal field");
32+
33+
auto l = p->GetInternalField(0);
34+
args.GetReturnValue().Set(l);
35+
}
36+
37+
inlinevoidInitialize(v8::Local<v8::Object> binding){
38+
NODE_SET_METHOD(binding, "getPromiseField", GetPromiseField);
39+
}
40+
41+
NODE_MODULE(binding, Initialize)
42+
43+
} // anonymous namespace
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
6+
'sources': [ 'binding.cc' ]
7+
}
8+
]
9+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
3+
constcommon=require('../../common');
4+
constassert=require('assert');
5+
constasync_hooks=require('async_hooks');
6+
constbinding=require(`./build/${common.buildType}/binding`);
7+
8+
// Baseline to make sure the internal field isn't being set.
9+
assert.strictEqual(
10+
binding.getPromiseField(Promise.resolve(1)),
11+
0,
12+
'Promise internal field used despite missing enabled AsyncHook');
13+
14+
consthook0=async_hooks.createHook({}).enable();
15+
16+
// Check that no PromiseWrap is created when there are no hook callbacks.
17+
assert.strictEqual(
18+
binding.getPromiseField(Promise.resolve(1)),
19+
0,
20+
'Promise internal field used despite missing enabled AsyncHook');
21+
22+
hook0.disable();
23+
24+
letpwrap=null;
25+
consthook1=async_hooks.createHook({
26+
init(id,type,tid,resource){
27+
pwrap=resource;
28+
}
29+
}).enable();
30+
31+
// Check that the internal field returns the same PromiseWrap passed to init().
32+
assert.strictEqual(
33+
binding.getPromiseField(Promise.resolve(1)),
34+
pwrap,
35+
'Unexpected PromiseWrap');
36+
37+
hook1.disable();
38+
39+
// Check that internal fields are no longer being set.
40+
assert.strictEqual(
41+
binding.getPromiseField(Promise.resolve(1)),
42+
0,
43+
'Promise internal field used despite missing enabled AsyncHook');
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
constassert=require('assert');
5+
constasync_hooks=require('async_hooks');
6+
constEXPECTED_INITS=2;
7+
letp_resource=null;
8+
letp_er=null;
9+
letp_inits=0;
10+
11+
// Not useful to place common.mustCall() around 'exit' event b/c it won't be
12+
// able to check it anway.
13+
process.on('exit',(code)=>{
14+
if(code!==0)
15+
return;
16+
if(p_er!==null)
17+
throwp_er;
18+
// Expecint exactly 2 PROMISE types to reach init.
19+
assert.strictEqual(p_inits,EXPECTED_INITS);
20+
});
21+
22+
constmustCallInit=common.mustCall(functioninit(id,type,tid,resource){
23+
if(type!=='PROMISE')
24+
return;
25+
p_inits++;
26+
p_resource=resource.promise;
27+
},EXPECTED_INITS);
28+
29+
consthook=async_hooks.createHook({
30+
init: mustCallInit
31+
// Enable then disable to test whether disable() actually works.
32+
}).enable().disable().disable();
33+
34+
newPromise(common.mustCall((res)=>{
35+
res(42);
36+
})).then(common.mustCall((val)=>{
37+
hook.enable().enable();
38+
constp=newPromise((res)=>res(val));
39+
assert.strictEqual(p,p_resource);
40+
hook.disable();
41+
returnp;
42+
})).then(common.mustCall((val2)=>{
43+
hook.enable();
44+
constp=newPromise((res)=>res(val2));
45+
hook.disable();
46+
returnp;
47+
})).catch((er)=>p_er=er);

0 commit comments

Comments
(0)