mirror of https://github.com/nodejs/node.git
async_hooks: support promise resolve hook
Add a `promiseResolve()` hook. PR-URL: https://github.com/nodejs/node/pull/15296 Reviewed-By: Trevor Norris <trev.norris@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
7a953929fe
commit
b605b15346
|
@ -36,7 +36,8 @@ const eid = async_hooks.executionAsyncId();
|
||||||
const tid = async_hooks.triggerAsyncId();
|
const tid = async_hooks.triggerAsyncId();
|
||||||
|
|
||||||
// Create a new AsyncHook instance. All of these callbacks are optional.
|
// Create a new AsyncHook instance. All of these callbacks are optional.
|
||||||
const asyncHook = async_hooks.createHook({ init, before, after, destroy });
|
const asyncHook =
|
||||||
|
async_hooks.createHook({ init, before, after, destroy, promiseResolve });
|
||||||
|
|
||||||
// Allow callbacks of this AsyncHook instance to call. This is not an implicit
|
// Allow callbacks of this AsyncHook instance to call. This is not an implicit
|
||||||
// action after running the constructor, and must be explicitly run to begin
|
// action after running the constructor, and must be explicitly run to begin
|
||||||
|
@ -65,6 +66,11 @@ function after(asyncId) { }
|
||||||
|
|
||||||
// destroy is called when an AsyncWrap instance is destroyed.
|
// destroy is called when an AsyncWrap instance is destroyed.
|
||||||
function destroy(asyncId) { }
|
function destroy(asyncId) { }
|
||||||
|
|
||||||
|
// promiseResolve is called only for promise resources, when the
|
||||||
|
// `resolve` function passed to the `Promise` constructor is invoked
|
||||||
|
// (either directly or through other means of resolving a promise).
|
||||||
|
function promiseResolve(asyncId) { }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `async_hooks.createHook(callbacks)`
|
#### `async_hooks.createHook(callbacks)`
|
||||||
|
@ -430,6 +436,36 @@ reference is made to the `resource` object passed to `init` it is possible that
|
||||||
the resource does not depend on garbage collection, then this will not be an
|
the resource does not depend on garbage collection, then this will not be an
|
||||||
issue.
|
issue.
|
||||||
|
|
||||||
|
##### `promiseResolve(asyncId)`
|
||||||
|
|
||||||
|
* `asyncId` {number}
|
||||||
|
|
||||||
|
Called when the `resolve` function passed to the `Promise` constructor is
|
||||||
|
invoked (either directly or through other means of resolving a promise).
|
||||||
|
|
||||||
|
Note that `resolve()` does not do any observable synchronous work.
|
||||||
|
|
||||||
|
*Note:* This does not necessarily mean that the `Promise` is fulfilled or
|
||||||
|
rejected at this point, if the `Promise` was resolved by assuming the state
|
||||||
|
of another `Promise`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
new Promise((resolve) => resolve(true)).then((a) => {});
|
||||||
|
```
|
||||||
|
|
||||||
|
calls the following callbacks:
|
||||||
|
|
||||||
|
```
|
||||||
|
init for PROMISE with id 5, trigger id: 1
|
||||||
|
promise resolve 5 # corresponds to resolve(true)
|
||||||
|
init for PROMISE with id 6, trigger id: 5 # the Promise returned by then()
|
||||||
|
before 6 # the then() callback is entered
|
||||||
|
promise resolve 6 # the then() callback resolves the promise by returning
|
||||||
|
after 6
|
||||||
|
```
|
||||||
|
|
||||||
#### `async_hooks.executionAsyncId()`
|
#### `async_hooks.executionAsyncId()`
|
||||||
|
|
||||||
* Returns {number} the `asyncId` of the current execution context. Useful to
|
* Returns {number} the `asyncId` of the current execution context. Useful to
|
||||||
|
|
|
@ -58,8 +58,8 @@ const active_hooks = {
|
||||||
// Each constant tracks how many callbacks there are for any given step of
|
// Each constant tracks how many callbacks there are for any given step of
|
||||||
// async execution. These are tracked so if the user didn't include callbacks
|
// async execution. These are tracked so if the user didn't include callbacks
|
||||||
// for a given step, that step can bail out early.
|
// for a given step, that step can bail out early.
|
||||||
const { kInit, kBefore, kAfter, kDestroy, kTotals, kCurrentAsyncId,
|
const { kInit, kBefore, kAfter, kDestroy, kPromiseResolve, kTotals,
|
||||||
kCurrentTriggerId, kAsyncUidCntr,
|
kCurrentAsyncId, kCurrentTriggerId, kAsyncUidCntr,
|
||||||
kInitTriggerId } = async_wrap.constants;
|
kInitTriggerId } = async_wrap.constants;
|
||||||
|
|
||||||
// Symbols used to store the respective ids on both AsyncResource instances and
|
// Symbols used to store the respective ids on both AsyncResource instances and
|
||||||
|
@ -72,9 +72,12 @@ const init_symbol = Symbol('init');
|
||||||
const before_symbol = Symbol('before');
|
const before_symbol = Symbol('before');
|
||||||
const after_symbol = Symbol('after');
|
const after_symbol = Symbol('after');
|
||||||
const destroy_symbol = Symbol('destroy');
|
const destroy_symbol = Symbol('destroy');
|
||||||
|
const promise_resolve_symbol = Symbol('promiseResolve');
|
||||||
const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
|
const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
|
||||||
const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative');
|
const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative');
|
||||||
const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
|
const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
|
||||||
|
const emitPromiseResolveNative =
|
||||||
|
emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative');
|
||||||
|
|
||||||
// TODO(refack): move to node-config.cc
|
// TODO(refack): move to node-config.cc
|
||||||
const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
|
const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
|
||||||
|
@ -86,7 +89,8 @@ const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
|
||||||
async_wrap.setupHooks({ init: emitInitNative,
|
async_wrap.setupHooks({ init: emitInitNative,
|
||||||
before: emitBeforeNative,
|
before: emitBeforeNative,
|
||||||
after: emitAfterNative,
|
after: emitAfterNative,
|
||||||
destroy: emitDestroyNative });
|
destroy: emitDestroyNative,
|
||||||
|
promise_resolve: emitPromiseResolveNative });
|
||||||
|
|
||||||
// Used to fatally abort the process if a callback throws.
|
// Used to fatally abort the process if a callback throws.
|
||||||
function fatalError(e) {
|
function fatalError(e) {
|
||||||
|
@ -107,7 +111,7 @@ function fatalError(e) {
|
||||||
// Public API //
|
// Public API //
|
||||||
|
|
||||||
class AsyncHook {
|
class AsyncHook {
|
||||||
constructor({ init, before, after, destroy }) {
|
constructor({ init, before, after, destroy, promiseResolve }) {
|
||||||
if (init !== undefined && typeof init !== 'function')
|
if (init !== undefined && typeof init !== 'function')
|
||||||
throw new errors.TypeError('ERR_ASYNC_CALLBACK', 'init');
|
throw new errors.TypeError('ERR_ASYNC_CALLBACK', 'init');
|
||||||
if (before !== undefined && typeof before !== 'function')
|
if (before !== undefined && typeof before !== 'function')
|
||||||
|
@ -116,11 +120,14 @@ class AsyncHook {
|
||||||
throw new errors.TypeError('ERR_ASYNC_CALLBACK', 'before');
|
throw new errors.TypeError('ERR_ASYNC_CALLBACK', 'before');
|
||||||
if (destroy !== undefined && typeof destroy !== 'function')
|
if (destroy !== undefined && typeof destroy !== 'function')
|
||||||
throw new errors.TypeError('ERR_ASYNC_CALLBACK', 'before');
|
throw new errors.TypeError('ERR_ASYNC_CALLBACK', 'before');
|
||||||
|
if (promiseResolve !== undefined && typeof promiseResolve !== 'function')
|
||||||
|
throw new errors.TypeError('ERR_ASYNC_CALLBACK', 'promiseResolve');
|
||||||
|
|
||||||
this[init_symbol] = init;
|
this[init_symbol] = init;
|
||||||
this[before_symbol] = before;
|
this[before_symbol] = before;
|
||||||
this[after_symbol] = after;
|
this[after_symbol] = after;
|
||||||
this[destroy_symbol] = destroy;
|
this[destroy_symbol] = destroy;
|
||||||
|
this[promise_resolve_symbol] = promiseResolve;
|
||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
|
@ -144,6 +151,8 @@ class AsyncHook {
|
||||||
hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol];
|
hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol];
|
||||||
hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol];
|
hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol];
|
||||||
hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol];
|
hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol];
|
||||||
|
hook_fields[kTotals] +=
|
||||||
|
hook_fields[kPromiseResolve] += +!!this[promise_resolve_symbol];
|
||||||
hooks_array.push(this);
|
hooks_array.push(this);
|
||||||
|
|
||||||
if (prev_kTotals === 0 && hook_fields[kTotals] > 0)
|
if (prev_kTotals === 0 && hook_fields[kTotals] > 0)
|
||||||
|
@ -166,6 +175,8 @@ class AsyncHook {
|
||||||
hook_fields[kTotals] += hook_fields[kBefore] -= +!!this[before_symbol];
|
hook_fields[kTotals] += hook_fields[kBefore] -= +!!this[before_symbol];
|
||||||
hook_fields[kTotals] += hook_fields[kAfter] -= +!!this[after_symbol];
|
hook_fields[kTotals] += hook_fields[kAfter] -= +!!this[after_symbol];
|
||||||
hook_fields[kTotals] += hook_fields[kDestroy] -= +!!this[destroy_symbol];
|
hook_fields[kTotals] += hook_fields[kDestroy] -= +!!this[destroy_symbol];
|
||||||
|
hook_fields[kTotals] +=
|
||||||
|
hook_fields[kPromiseResolve] -= +!!this[promise_resolve_symbol];
|
||||||
hooks_array.splice(index, 1);
|
hooks_array.splice(index, 1);
|
||||||
|
|
||||||
if (prev_kTotals > 0 && hook_fields[kTotals] === 0)
|
if (prev_kTotals > 0 && hook_fields[kTotals] === 0)
|
||||||
|
@ -198,6 +209,7 @@ function storeActiveHooks() {
|
||||||
active_hooks.tmp_fields[kBefore] = async_hook_fields[kBefore];
|
active_hooks.tmp_fields[kBefore] = async_hook_fields[kBefore];
|
||||||
active_hooks.tmp_fields[kAfter] = async_hook_fields[kAfter];
|
active_hooks.tmp_fields[kAfter] = async_hook_fields[kAfter];
|
||||||
active_hooks.tmp_fields[kDestroy] = async_hook_fields[kDestroy];
|
active_hooks.tmp_fields[kDestroy] = async_hook_fields[kDestroy];
|
||||||
|
active_hooks.tmp_fields[kPromiseResolve] = async_hook_fields[kPromiseResolve];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -209,6 +221,7 @@ function restoreActiveHooks() {
|
||||||
async_hook_fields[kBefore] = active_hooks.tmp_fields[kBefore];
|
async_hook_fields[kBefore] = active_hooks.tmp_fields[kBefore];
|
||||||
async_hook_fields[kAfter] = active_hooks.tmp_fields[kAfter];
|
async_hook_fields[kAfter] = active_hooks.tmp_fields[kAfter];
|
||||||
async_hook_fields[kDestroy] = active_hooks.tmp_fields[kDestroy];
|
async_hook_fields[kDestroy] = active_hooks.tmp_fields[kDestroy];
|
||||||
|
async_hook_fields[kPromiseResolve] = active_hooks.tmp_fields[kPromiseResolve];
|
||||||
|
|
||||||
active_hooks.tmp_array = null;
|
active_hooks.tmp_array = null;
|
||||||
active_hooks.tmp_fields = null;
|
active_hooks.tmp_fields = null;
|
||||||
|
|
|
@ -181,6 +181,25 @@ static void PushBackDestroyId(Environment* env, double id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AsyncWrap::EmitPromiseResolve(Environment* env, double async_id) {
|
||||||
|
AsyncHooks* async_hooks = env->async_hooks();
|
||||||
|
|
||||||
|
if (async_hooks->fields()[AsyncHooks::kPromiseResolve] == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Local<Value> uid = Number::New(env->isolate(), async_id);
|
||||||
|
Local<Function> fn = env->async_hooks_promise_resolve_function();
|
||||||
|
TryCatch try_catch(env->isolate());
|
||||||
|
MaybeLocal<Value> ar = fn->Call(
|
||||||
|
env->context(), Undefined(env->isolate()), 1, &uid);
|
||||||
|
if (ar.IsEmpty()) {
|
||||||
|
ClearFatalExceptionHandlers(env);
|
||||||
|
FatalException(env->isolate(), try_catch);
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void AsyncWrap::EmitBefore(Environment* env, double async_id) {
|
void AsyncWrap::EmitBefore(Environment* env, double async_id) {
|
||||||
AsyncHooks* async_hooks = env->async_hooks();
|
AsyncHooks* async_hooks = env->async_hooks();
|
||||||
|
|
||||||
|
@ -303,8 +322,6 @@ static void PromiseHook(PromiseHookType type, Local<Promise> promise,
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap = PromiseWrap::New(env, promise, parent_wrap, silent);
|
wrap = PromiseWrap::New(env, promise, parent_wrap, silent);
|
||||||
} else if (type == PromiseHookType::kResolve) {
|
|
||||||
// TODO(matthewloring): need to expose this through the async hooks api.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_NE(wrap, nullptr);
|
CHECK_NE(wrap, nullptr);
|
||||||
|
@ -321,6 +338,8 @@ static void PromiseHook(PromiseHookType type, Local<Promise> promise,
|
||||||
// PromiseHookType::kBefore that was not witnessed by the PromiseHook.
|
// PromiseHookType::kBefore that was not witnessed by the PromiseHook.
|
||||||
env->async_hooks()->pop_ids(wrap->get_id());
|
env->async_hooks()->pop_ids(wrap->get_id());
|
||||||
}
|
}
|
||||||
|
} else if (type == PromiseHookType::kResolve) {
|
||||||
|
AsyncWrap::EmitPromiseResolve(wrap->env(), wrap->get_id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,6 +368,7 @@ static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
|
||||||
SET_HOOK_FN(before);
|
SET_HOOK_FN(before);
|
||||||
SET_HOOK_FN(after);
|
SET_HOOK_FN(after);
|
||||||
SET_HOOK_FN(destroy);
|
SET_HOOK_FN(destroy);
|
||||||
|
SET_HOOK_FN(promise_resolve);
|
||||||
#undef SET_HOOK_FN
|
#undef SET_HOOK_FN
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -500,6 +520,7 @@ void AsyncWrap::Initialize(Local<Object> target,
|
||||||
SET_HOOKS_CONSTANT(kBefore);
|
SET_HOOKS_CONSTANT(kBefore);
|
||||||
SET_HOOKS_CONSTANT(kAfter);
|
SET_HOOKS_CONSTANT(kAfter);
|
||||||
SET_HOOKS_CONSTANT(kDestroy);
|
SET_HOOKS_CONSTANT(kDestroy);
|
||||||
|
SET_HOOKS_CONSTANT(kPromiseResolve);
|
||||||
SET_HOOKS_CONSTANT(kTotals);
|
SET_HOOKS_CONSTANT(kTotals);
|
||||||
SET_HOOKS_CONSTANT(kCurrentAsyncId);
|
SET_HOOKS_CONSTANT(kCurrentAsyncId);
|
||||||
SET_HOOKS_CONSTANT(kCurrentTriggerId);
|
SET_HOOKS_CONSTANT(kCurrentTriggerId);
|
||||||
|
@ -533,6 +554,7 @@ void AsyncWrap::Initialize(Local<Object> target,
|
||||||
env->set_async_hooks_before_function(Local<Function>());
|
env->set_async_hooks_before_function(Local<Function>());
|
||||||
env->set_async_hooks_after_function(Local<Function>());
|
env->set_async_hooks_after_function(Local<Function>());
|
||||||
env->set_async_hooks_destroy_function(Local<Function>());
|
env->set_async_hooks_destroy_function(Local<Function>());
|
||||||
|
env->set_async_hooks_promise_resolve_function(Local<Function>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,7 @@ class AsyncWrap : public BaseObject {
|
||||||
|
|
||||||
static void EmitBefore(Environment* env, double id);
|
static void EmitBefore(Environment* env, double id);
|
||||||
static void EmitAfter(Environment* env, double id);
|
static void EmitAfter(Environment* env, double id);
|
||||||
|
static void EmitPromiseResolve(Environment* env, double id);
|
||||||
|
|
||||||
inline ProviderType provider_type() const;
|
inline ProviderType provider_type() const;
|
||||||
|
|
||||||
|
|
|
@ -295,6 +295,7 @@ struct performance_state;
|
||||||
V(async_hooks_init_function, v8::Function) \
|
V(async_hooks_init_function, v8::Function) \
|
||||||
V(async_hooks_before_function, v8::Function) \
|
V(async_hooks_before_function, v8::Function) \
|
||||||
V(async_hooks_after_function, v8::Function) \
|
V(async_hooks_after_function, v8::Function) \
|
||||||
|
V(async_hooks_promise_resolve_function, v8::Function) \
|
||||||
V(binding_cache_object, v8::Object) \
|
V(binding_cache_object, v8::Object) \
|
||||||
V(buffer_prototype_object, v8::Object) \
|
V(buffer_prototype_object, v8::Object) \
|
||||||
V(context, v8::Context) \
|
V(context, v8::Context) \
|
||||||
|
@ -377,6 +378,7 @@ class Environment {
|
||||||
kBefore,
|
kBefore,
|
||||||
kAfter,
|
kAfter,
|
||||||
kDestroy,
|
kDestroy,
|
||||||
|
kPromiseResolve,
|
||||||
kTotals,
|
kTotals,
|
||||||
kFieldsCount,
|
kFieldsCount,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,11 +4,16 @@ const assert = require('assert');
|
||||||
const async_hooks = require('async_hooks');
|
const async_hooks = require('async_hooks');
|
||||||
|
|
||||||
const initCalls = [];
|
const initCalls = [];
|
||||||
|
const resolveCalls = [];
|
||||||
|
|
||||||
async_hooks.createHook({
|
async_hooks.createHook({
|
||||||
init: common.mustCall((id, type, triggerId, resource) => {
|
init: common.mustCall((id, type, triggerId, resource) => {
|
||||||
assert.strictEqual(type, 'PROMISE');
|
assert.strictEqual(type, 'PROMISE');
|
||||||
initCalls.push({ id, triggerId, resource });
|
initCalls.push({ id, triggerId, resource });
|
||||||
|
}, 2),
|
||||||
|
promiseResolve: common.mustCall((id) => {
|
||||||
|
assert.strictEqual(initCalls[resolveCalls.length].id, id);
|
||||||
|
resolveCalls.push(id);
|
||||||
}, 2)
|
}, 2)
|
||||||
}).enable();
|
}).enable();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue