Skip to content

Commit

Permalink
async_wrap,lib: Firing async-wrap callbacks for next-tick callbacks
Browse files Browse the repository at this point in the history
This change updates nextTick() callback processing to call back into
the node runtime to notify the runtime of async state transitions.
Currently, the runtime will track the active async ID and fire any
registered async-wrap callbacks. This gives async-wrap consistent user
semantics for nextTick() callbacks.

I anticipate some concerns around impact to perf. Rough measurements
show an approximate 2x increase in time to process 1,000,000 nextTick()
callbacks.  These went from an average of 1094 ms per million
nextTick() callbacks to 2133 ms per million with the changes in this PR.

I'm open to alternative suggestions around implementation here. :) It's
not lost on me that the implementation of next_tick.js is making an
effort to minimize transitions from JS to the runtime, and this change
increases those transitions by a factor of three. One of the goals
here was to have basic entry-points for javascript code to notify the
runtime of async transitions (namely, "enqueue", "start", and "end").
This allows a future where async call graph information can be tracked
by the runtime and eliminates a number of cases where users will
require the async-wrap callbacks.  For example, continuation-local
storage can be implemented on such an API, without callbacks, assuming
each node in the graph contains a tuple of (unique ID, parent ID,
pending child callback count, state), and where state is one of
enqueued, running, or completed.
  • Loading branch information
Mike Kaufman committed Apr 6, 2016
1 parent 0f3c3b4 commit 7f2d1b3
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 94 deletions.
31 changes: 27 additions & 4 deletions lib/internal/process/next_tick.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
exports.setup = setupNextTick;

function setupNextTick() {

var asyncWrap = process.binding('async_wrap');
const promises = require('internal/process/promises');
const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks);
var nextTickQueue = [];
Expand Down Expand Up @@ -85,17 +87,32 @@ function setupNextTick() {
// Run callbacks that have no domain.
// Using domains will cause this to be overridden.
function _tickCallback() {

var callback, args, tock;

do {
while (tickInfo[kIndex] < tickInfo[kLength]) {
tock = nextTickQueue[tickInfo[kIndex]++];
callback = tock.callback;

asyncWrap.notifyAsyncStart(
tock.ranInitCallback, tock.asyncId, tock.asyncState);

args = tock.args;
// Using separate callback execution functions allows direct
// callback invocation with small numbers of arguments to avoid the
// performance hit associated with using `fn.apply()`
_combinedTickCallback(args, callback);
var callbackThrew = true;
try {
// Using separate callback execution functions allows direct
// callback invocation with small numbers of arguments to avoid the
// performance hit associated with using `fn.apply()`
_combinedTickCallback(args, callback);
callbackThrew = false;
}
finally {
asyncWrap.notifyAsyncEnd(
tock.ranInitCallback, tock.asyncId,
tock.asyncState, callbackThrew);
}

if (1e4 < tickInfo[kIndex])
tickDone();
}
Expand Down Expand Up @@ -135,6 +152,12 @@ function setupNextTick() {
this.callback = c;
this.domain = process.domain || null;
this.args = args;
this.parentAsyncId = asyncWrap.getCurrentAsyncId();
this.asyncId = asyncWrap.getNextAsyncId();
this.asyncState = {};
this.ranInitCallback = asyncWrap.notifyAsyncEnqueue(
this.asyncId, this.asyncState, undefined, undefined,
asyncWrap.Providers.NEXTTICK);
}

function nextTick(callback) {
Expand Down
57 changes: 5 additions & 52 deletions src/async-wrap-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,69 +18,22 @@ inline AsyncWrap::AsyncWrap(Environment* env,
ProviderType provider,
AsyncWrap* parent)
: BaseObject(env, object), bits_(static_cast<uint32_t>(provider) << 1),
uid_(env->get_async_wrap_uid()) {
uid_(env->get_next_async_wrap_uid()) {
CHECK_NE(provider, PROVIDER_NONE);
CHECK_GE(object->InternalFieldCount(), 1);

// Shift provider value over to prevent id collision.
persistent().SetWrapperClassId(NODE_ASYNC_ID_OFFSET + provider);

v8::Local<v8::Function> init_fn = env->async_hooks_init_function();

// No init callback exists, no reason to go on.
if (init_fn.IsEmpty())
return;

// If async wrap callbacks are disabled and no parent was passed that has
// run the init callback then return.
if (!env->async_wrap_callbacks_enabled() &&
(parent == nullptr || !parent->ran_init_callback()))
return;

v8::HandleScope scope(env->isolate());

v8::Local<v8::Value> argv[] = {
v8::Integer::New(env->isolate(), get_uid()),
v8::Int32::New(env->isolate(), provider),
Null(env->isolate()),
Null(env->isolate())
};

if (parent != nullptr) {
argv[2] = v8::Integer::New(env->isolate(), parent->get_uid());
argv[3] = parent->object();
if (AsyncWrap::FireAsyncInitCallbacks(env, get_uid(), object, provider, parent)) {
bits_ |= 1; // ran_init_callback() is true now.
}

v8::TryCatch try_catch(env->isolate());

v8::MaybeLocal<v8::Value> ret =
init_fn->Call(env->context(), object, arraysize(argv), argv);

if (ret.IsEmpty()) {
ClearFatalExceptionHandlers(env);
FatalException(env->isolate(), try_catch);
}

bits_ |= 1; // ran_init_callback() is true now.
}


inline AsyncWrap::~AsyncWrap() {
if (!ran_init_callback())
return;

v8::Local<v8::Function> fn = env()->async_hooks_destroy_function();
if (!fn.IsEmpty()) {
v8::HandleScope scope(env()->isolate());
v8::Local<v8::Value> uid = v8::Integer::New(env()->isolate(), get_uid());
v8::TryCatch try_catch(env()->isolate());
v8::MaybeLocal<v8::Value> ret =
fn->Call(env()->context(), v8::Null(env()->isolate()), 1, &uid);
if (ret.IsEmpty()) {
ClearFatalExceptionHandlers(env());
FatalException(env()->isolate(), try_catch);
}
}
v8::HandleScope scope(env()->isolate());
FireAsyncDestroyCallbacks(env(), ran_init_callback(), v8::Integer::New(env()->isolate(), get_uid()));
}


Expand Down
Loading

0 comments on commit 7f2d1b3

Please sign in to comment.