mirror of
https://github.com/nodejs/node.git
synced 2025-05-01 08:42:45 +00:00
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();
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
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)`
|
||||
@ -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
|
||||
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()`
|
||||
|
||||
* 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
|
||||
// async execution. These are tracked so if the user didn't include callbacks
|
||||
// for a given step, that step can bail out early.
|
||||
const { kInit, kBefore, kAfter, kDestroy, kTotals, kCurrentAsyncId,
|
||||
kCurrentTriggerId, kAsyncUidCntr,
|
||||
const { kInit, kBefore, kAfter, kDestroy, kPromiseResolve, kTotals,
|
||||
kCurrentAsyncId, kCurrentTriggerId, kAsyncUidCntr,
|
||||
kInitTriggerId } = async_wrap.constants;
|
||||
|
||||
// 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 after_symbol = Symbol('after');
|
||||
const destroy_symbol = Symbol('destroy');
|
||||
const promise_resolve_symbol = Symbol('promiseResolve');
|
||||
const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
|
||||
const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative');
|
||||
const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
|
||||
const emitPromiseResolveNative =
|
||||
emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative');
|
||||
|
||||
// TODO(refack): move to node-config.cc
|
||||
const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
|
||||
@ -86,7 +89,8 @@ const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
|
||||
async_wrap.setupHooks({ init: emitInitNative,
|
||||
before: emitBeforeNative,
|
||||
after: emitAfterNative,
|
||||
destroy: emitDestroyNative });
|
||||
destroy: emitDestroyNative,
|
||||
promise_resolve: emitPromiseResolveNative });
|
||||
|
||||
// Used to fatally abort the process if a callback throws.
|
||||
function fatalError(e) {
|
||||
@ -107,7 +111,7 @@ function fatalError(e) {
|
||||
// Public API //
|
||||
|
||||
class AsyncHook {
|
||||
constructor({ init, before, after, destroy }) {
|
||||
constructor({ init, before, after, destroy, promiseResolve }) {
|
||||
if (init !== undefined && typeof init !== 'function')
|
||||
throw new errors.TypeError('ERR_ASYNC_CALLBACK', 'init');
|
||||
if (before !== undefined && typeof before !== 'function')
|
||||
@ -116,11 +120,14 @@ class AsyncHook {
|
||||
throw new errors.TypeError('ERR_ASYNC_CALLBACK', 'before');
|
||||
if (destroy !== undefined && typeof destroy !== 'function')
|
||||
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[before_symbol] = before;
|
||||
this[after_symbol] = after;
|
||||
this[destroy_symbol] = destroy;
|
||||
this[promise_resolve_symbol] = promiseResolve;
|
||||
}
|
||||
|
||||
enable() {
|
||||
@ -144,6 +151,8 @@ class AsyncHook {
|
||||
hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol];
|
||||
hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol];
|
||||
hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol];
|
||||
hook_fields[kTotals] +=
|
||||
hook_fields[kPromiseResolve] += +!!this[promise_resolve_symbol];
|
||||
hooks_array.push(this);
|
||||
|
||||
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[kAfter] -= +!!this[after_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);
|
||||
|
||||
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[kAfter] = async_hook_fields[kAfter];
|
||||
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[kAfter] = active_hooks.tmp_fields[kAfter];
|
||||
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_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) {
|
||||
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);
|
||||
} else if (type == PromiseHookType::kResolve) {
|
||||
// TODO(matthewloring): need to expose this through the async hooks api.
|
||||
}
|
||||
|
||||
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.
|
||||
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(after);
|
||||
SET_HOOK_FN(destroy);
|
||||
SET_HOOK_FN(promise_resolve);
|
||||
#undef SET_HOOK_FN
|
||||
|
||||
{
|
||||
@ -500,6 +520,7 @@ void AsyncWrap::Initialize(Local<Object> target,
|
||||
SET_HOOKS_CONSTANT(kBefore);
|
||||
SET_HOOKS_CONSTANT(kAfter);
|
||||
SET_HOOKS_CONSTANT(kDestroy);
|
||||
SET_HOOKS_CONSTANT(kPromiseResolve);
|
||||
SET_HOOKS_CONSTANT(kTotals);
|
||||
SET_HOOKS_CONSTANT(kCurrentAsyncId);
|
||||
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_after_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 EmitAfter(Environment* env, double id);
|
||||
static void EmitPromiseResolve(Environment* env, double id);
|
||||
|
||||
inline ProviderType provider_type() const;
|
||||
|
||||
|
@ -295,6 +295,7 @@ struct performance_state;
|
||||
V(async_hooks_init_function, v8::Function) \
|
||||
V(async_hooks_before_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(buffer_prototype_object, v8::Object) \
|
||||
V(context, v8::Context) \
|
||||
@ -377,6 +378,7 @@ class Environment {
|
||||
kBefore,
|
||||
kAfter,
|
||||
kDestroy,
|
||||
kPromiseResolve,
|
||||
kTotals,
|
||||
kFieldsCount,
|
||||
};
|
||||
|
@ -4,11 +4,16 @@ const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
|
||||
const initCalls = [];
|
||||
const resolveCalls = [];
|
||||
|
||||
async_hooks.createHook({
|
||||
init: common.mustCall((id, type, triggerId, resource) => {
|
||||
assert.strictEqual(type, 'PROMISE');
|
||||
initCalls.push({ id, triggerId, resource });
|
||||
}, 2),
|
||||
promiseResolve: common.mustCall((id) => {
|
||||
assert.strictEqual(initCalls[resolveCalls.length].id, id);
|
||||
resolveCalls.push(id);
|
||||
}, 2)
|
||||
}).enable();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user