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

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

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

1
src/async-wrap.h

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

2
src/env.h

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

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

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