Skip to content

Commit

Permalink
async_wrap: correctly pass parent to init callback
Browse files Browse the repository at this point in the history
Previous logic didn't allow parent to propagate to the init callback
properly. The fix now allows the init callback to be called and receive
the parent if:

- async wrap callbacks are enabled and parent exists
- the init callback has been called on the parent and an init callback
  exists then it will be called regardless of whether async wrap
  callbacks are disabled.

Change the init/pre/post callback checks to see if it has been properly
set. This allows removal of the Environment "using_asyncwrap" variable.

Pass Isolate to a TryCatch instance.

Fixes: nodejs#2986
PR-URL: nodejs#3216
Reviewed-By: Rod Vagg <[email protected]>
  • Loading branch information
trevnorris committed Oct 7, 2015
1 parent 07a8419 commit aeee956
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 40 deletions.
33 changes: 20 additions & 13 deletions src/async-wrap-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,39 @@ inline AsyncWrap::AsyncWrap(Environment* env,
// Shift provider value over to prevent id collision.
persistent().SetWrapperClassId(NODE_ASYNC_ID_OFFSET + provider);

// Check user controlled flag to see if the init callback should run.
if (!env->using_asyncwrap())
return;
v8::Local<v8::Function> init_fn = env->async_hooks_init_function();

// If callback hooks have not been enabled, and there is no parent, return.
if (!env->async_wrap_callbacks_enabled() && parent == nullptr)
// No init callback exists, no reason to go on.
if (init_fn.IsEmpty())
return;

// If callback hooks have not been enabled and parent has no queue, return.
if (!env->async_wrap_callbacks_enabled() && !parent->has_async_queue())
// 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::TryCatch try_catch;

v8::Local<v8::Value> n = v8::Int32::New(env->isolate(), provider);
env->async_hooks_init_function()->Call(object, 1, &n);
v8::Local<v8::Value> argv[] = {
v8::Int32::New(env->isolate(), provider),
Null(env->isolate())
};

if (parent != nullptr)
argv[1] = parent->object();

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

if (try_catch.HasCaught())
if (ret.IsEmpty())
FatalError("node::AsyncWrap::AsyncWrap", "init hook threw");

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


inline bool AsyncWrap::has_async_queue() const {
inline bool AsyncWrap::ran_init_callback() const {
return static_cast<bool>(bits_ & 1);
}

Expand Down
18 changes: 11 additions & 7 deletions src/async-wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
env->set_async_hooks_init_function(args[0].As<Function>());
env->set_async_hooks_pre_function(args[1].As<Function>());
env->set_async_hooks_post_function(args[2].As<Function>());

env->set_using_asyncwrap(true);
}


Expand All @@ -146,6 +144,10 @@ static void Initialize(Local<Object> target,
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
target->Set(FIXED_ONE_BYTE_STRING(isolate, "Providers"), async_providers);

env->set_async_hooks_init_function(Local<Function>());
env->set_async_hooks_pre_function(Local<Function>());
env->set_async_hooks_post_function(Local<Function>());
}


Expand All @@ -164,6 +166,8 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
Local<Value>* argv) {
CHECK(env()->context() == env()->isolate()->GetCurrentContext());

Local<Function> pre_fn = env()->async_hooks_pre_function();
Local<Function> post_fn = env()->async_hooks_post_function();
Local<Object> context = object();
Local<Object> process = env()->process_object();
Local<Object> domain;
Expand All @@ -179,7 +183,7 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
}
}

TryCatch try_catch;
TryCatch try_catch(env()->isolate());
try_catch.SetVerbose(true);

if (has_domain) {
Expand All @@ -191,9 +195,9 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
}
}

if (has_async_queue()) {
if (ran_init_callback() && !pre_fn.IsEmpty()) {
try_catch.SetVerbose(false);
env()->async_hooks_pre_function()->Call(context, 0, nullptr);
pre_fn->Call(context, 0, nullptr);
if (try_catch.HasCaught())
FatalError("node::AsyncWrap::MakeCallback", "pre hook threw");
try_catch.SetVerbose(true);
Expand All @@ -205,9 +209,9 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
return Undefined(env()->isolate());
}

if (has_async_queue()) {
if (ran_init_callback() && !post_fn.IsEmpty()) {
try_catch.SetVerbose(false);
env()->async_hooks_post_function()->Call(context, 0, nullptr);
post_fn->Call(context, 0, nullptr);
if (try_catch.HasCaught())
FatalError("node::AsyncWrap::MakeCallback", "post hook threw");
try_catch.SetVerbose(true);
Expand Down
2 changes: 1 addition & 1 deletion src/async-wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class AsyncWrap : public BaseObject {

private:
inline AsyncWrap();
inline bool has_async_queue() const;
inline bool ran_init_callback() const;

// When the async hooks init JS function is called from the constructor it is
// expected the context object will receive a _asyncQueue object property
Expand Down
9 changes: 0 additions & 9 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ inline Environment::Environment(v8::Local<v8::Context> context,
isolate_data_(IsolateData::GetOrCreate(context->GetIsolate(), loop)),
timer_base_(uv_now(loop)),
using_domains_(false),
using_asyncwrap_(false),
printed_error_(false),
trace_sync_io_(false),
debugger_agent_(this),
Expand Down Expand Up @@ -348,14 +347,6 @@ inline void Environment::set_using_domains(bool value) {
using_domains_ = value;
}

inline bool Environment::using_asyncwrap() const {
return using_asyncwrap_;
}

inline void Environment::set_using_asyncwrap(bool value) {
using_asyncwrap_ = value;
}

inline bool Environment::printed_error() const {
return printed_error_;
}
Expand Down
4 changes: 0 additions & 4 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,6 @@ class Environment {
inline bool using_domains() const;
inline void set_using_domains(bool value);

inline bool using_asyncwrap() const;
inline void set_using_asyncwrap(bool value);

inline bool printed_error() const;
inline void set_printed_error(bool value);

Expand Down Expand Up @@ -537,7 +534,6 @@ class Environment {
ares_channel cares_channel_;
ares_task_list cares_task_list_;
bool using_domains_;
bool using_asyncwrap_;
bool printed_error_;
bool trace_sync_io_;
debugger::Agent debugger_agent_;
Expand Down
16 changes: 10 additions & 6 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1040,15 +1040,19 @@ Local<Value> MakeCallback(Environment* env,
// If you hit this assertion, you forgot to enter the v8::Context first.
CHECK_EQ(env->context(), env->isolate()->GetCurrentContext());

Local<Function> pre_fn = env->async_hooks_pre_function();
Local<Function> post_fn = env->async_hooks_post_function();
Local<Object> object, domain;
bool has_async_queue = false;
bool ran_init_callback = false;
bool has_domain = false;

// TODO(trevnorris): Adding "_asyncQueue" to the "this" in the init callback
// is a horrible way to detect usage. Rethink how detection should happen.
if (recv->IsObject()) {
object = recv.As<Object>();
Local<Value> async_queue_v = object->Get(env->async_queue_string());
if (async_queue_v->IsObject())
has_async_queue = true;
ran_init_callback = true;
}

if (env->using_domains()) {
Expand All @@ -1074,19 +1078,19 @@ Local<Value> MakeCallback(Environment* env,
}
}

if (has_async_queue) {
if (ran_init_callback && !pre_fn.IsEmpty()) {
try_catch.SetVerbose(false);
env->async_hooks_pre_function()->Call(object, 0, nullptr);
pre_fn->Call(object, 0, nullptr);
if (try_catch.HasCaught())
FatalError("node::MakeCallback", "pre hook threw");
try_catch.SetVerbose(true);
}

Local<Value> ret = callback->Call(recv, argc, argv);

if (has_async_queue) {
if (ran_init_callback && !post_fn.IsEmpty()) {
try_catch.SetVerbose(false);
env->async_hooks_post_function()->Call(object, 0, nullptr);
post_fn->Call(object, 0, nullptr);
if (try_catch.HasCaught())
FatalError("node::MakeCallback", "post hook threw");
try_catch.SetVerbose(true);
Expand Down
46 changes: 46 additions & 0 deletions test/parallel/test-async-wrap-disabled-propagate-parent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const net = require('net');
const async_wrap = process.binding('async_wrap');
const providers = Object.keys(async_wrap.Providers);

let cntr = 0;
let server;
let client;

function init(type, parent) {
if (parent) {
cntr++;
// Cannot assert in init callback or will abort.
process.nextTick(() => {
assert.equal(providers[type], 'TCPWRAP');
assert.equal(parent, server._handle, 'server doesn\'t match parent');
assert.equal(this, client._handle, 'client doesn\'t match context');
});
}
}

function noop() { }

async_wrap.setupHooks(init, noop, noop);
async_wrap.enable();

server = net.createServer(function(c) {
client = c;
// Allow init callback to run before closing.
setImmediate(() => {
c.end();
this.close();
});
}).listen(common.PORT, function() {
net.connect(common.PORT, noop);
});

async_wrap.disable();

process.on('exit', function() {
// init should have only been called once with a parent.
assert.equal(cntr, 1);
});
43 changes: 43 additions & 0 deletions test/parallel/test-async-wrap-propagate-parent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const net = require('net');
const async_wrap = process.binding('async_wrap');

let cntr = 0;
let server;
let client;

function init(type, parent) {
if (parent) {
cntr++;
// Cannot assert in init callback or will abort.
process.nextTick(() => {
assert.equal(parent, server._handle, 'server doesn\'t match parent');
assert.equal(this, client._handle, 'client doesn\'t match context');
});
}
}

function noop() { }

async_wrap.setupHooks(init, noop, noop);
async_wrap.enable();

server = net.createServer(function(c) {
client = c;
// Allow init callback to run before closing.
setImmediate(() => {
c.end();
this.close();
});
}).listen(common.PORT, function() {
net.connect(common.PORT, noop);
});


process.on('exit', function() {
// init should have only been called once with a parent.
assert.equal(cntr, 1);
});

0 comments on commit aeee956

Please sign in to comment.