Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async_wrap: correctly pass parent to init callback #3216

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't seem to be used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks. fixed.


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