Browse Source

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>
canary-base
Anna Henningsen 8 years ago
parent
commit
b605b15346
No known key found for this signature in database GPG Key ID: 9C63F3A6CD2AD8F9
  1. 38
      doc/api/async_hooks.md
  2. 21
      lib/async_hooks.js
  3. 26
      src/async-wrap.cc
  4. 1
      src/async-wrap.h
  5. 2
      src/env.h
  6. 5
      test/parallel/test-async-hooks-promise.js

38
doc/api/async_hooks.md

@ -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

21
lib/async_hooks.js

@ -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;

26
src/async-wrap.cc

@ -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>());
}

1
src/async-wrap.h

@ -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;

2
src/env.h

@ -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,
};

5
test/parallel/test-async-hooks-promise.js

@ -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…
Cancel
Save