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] call destroy hooks on uv_timer_t #13369

Merged
merged 2 commits into from
Jun 2, 2017
Merged
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
40 changes: 19 additions & 21 deletions src/async-wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,34 +138,32 @@ RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local<Value> wrapper) {
// end RetainedAsyncInfo


static void DestroyIdsCb(uv_idle_t* handle) {
uv_idle_stop(handle);

Environment* env = Environment::from_destroy_ids_idle_handle(handle);
static void DestroyIdsCb(uv_timer_t* handle) {
Environment* env = Environment::from_destroy_ids_timer_handle(handle);

HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
Local<Function> fn = env->async_hooks_destroy_function();

TryCatch try_catch(env->isolate());

std::vector<double> destroy_ids_list;
destroy_ids_list.swap(*env->destroy_ids_list());
for (auto current_id : destroy_ids_list) {
// Want each callback to be cleaned up after itself, instead of cleaning
// them all up after the while() loop completes.
HandleScope scope(env->isolate());
Local<Value> argv = Number::New(env->isolate(), current_id);
MaybeLocal<Value> ret = fn->Call(
env->context(), Undefined(env->isolate()), 1, &argv);

if (ret.IsEmpty()) {
ClearFatalExceptionHandlers(env);
FatalException(env->isolate(), try_catch);
do {
Copy link
Member

Choose a reason for hiding this comment

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

will this not call the destroy hook synchronously if emitDestroy was called from within the destroy hook? We don't want that because destroy hooks should never be called synchronously.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@AndreasMadsen It would act like a nextTick(). The current execution scope would complete before the destroy hook was called.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, that came to me a few hours after I wrote that. Thanks!

std::vector<double> destroy_ids_list;
destroy_ids_list.swap(*env->destroy_ids_list());
for (auto current_id : destroy_ids_list) {
// Want each callback to be cleaned up after itself, instead of cleaning
// them all up after the while() loop completes.
HandleScope scope(env->isolate());
Local<Value> argv = Number::New(env->isolate(), current_id);
MaybeLocal<Value> ret = fn->Call(
env->context(), Undefined(env->isolate()), 1, &argv);

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

env->destroy_ids_list()->clear();
} while (!env->destroy_ids_list()->empty());
}


Expand All @@ -174,7 +172,7 @@ static void PushBackDestroyId(Environment* env, double id) {
return;

if (env->destroy_ids_list()->empty())
uv_idle_start(env->destroy_ids_idle_handle(), DestroyIdsCb);
uv_timer_start(env->destroy_ids_timer_handle(), DestroyIdsCb, 0, 0);

env->destroy_ids_list()->push_back(id);
}
Expand Down
10 changes: 5 additions & 5 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,13 +356,13 @@ inline uv_idle_t* Environment::immediate_idle_handle() {
return &immediate_idle_handle_;
}

inline Environment* Environment::from_destroy_ids_idle_handle(
uv_idle_t* handle) {
return ContainerOf(&Environment::destroy_ids_idle_handle_, handle);
inline Environment* Environment::from_destroy_ids_timer_handle(
uv_timer_t* handle) {
return ContainerOf(&Environment::destroy_ids_timer_handle_, handle);
}

inline uv_idle_t* Environment::destroy_ids_idle_handle() {
return &destroy_ids_idle_handle_;
inline uv_timer_t* Environment::destroy_ids_timer_handle() {
return &destroy_ids_timer_handle_;
}

inline void Environment::RegisterHandleCleanup(uv_handle_t* handle,
Expand Down
13 changes: 1 addition & 12 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ void Environment::Start(int argc,
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));

uv_idle_init(event_loop(), destroy_ids_idle_handle());
uv_unref(reinterpret_cast<uv_handle_t*>(destroy_ids_idle_handle()));
uv_timer_init(event_loop(), destroy_ids_timer_handle());

auto close_and_finish = [](Environment* env, uv_handle_t* handle, void* arg) {
handle->data = env;
Expand Down Expand Up @@ -102,16 +101,6 @@ void Environment::CleanupHandles() {
while (handle_cleanup_waiting_ != 0)
uv_run(event_loop(), UV_RUN_ONCE);

// Closing the destroy_ids_idle_handle_ within the handle cleanup queue
// prevents the async wrap destroy hook from being called.
uv_handle_t* handle =
reinterpret_cast<uv_handle_t*>(&destroy_ids_idle_handle_);
handle->data = this;
handle_cleanup_waiting_ = 1;
uv_close(handle, [](uv_handle_t* handle) {
static_cast<Environment*>(handle->data)->FinishHandleCleanup(handle);
});

while (handle_cleanup_waiting_ != 0)
uv_run(event_loop(), UV_RUN_ONCE);
}
Expand Down
6 changes: 3 additions & 3 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -526,10 +526,10 @@ class Environment {
inline uint32_t watched_providers() const;

static inline Environment* from_immediate_check_handle(uv_check_t* handle);
static inline Environment* from_destroy_ids_idle_handle(uv_idle_t* handle);
static inline Environment* from_destroy_ids_timer_handle(uv_timer_t* handle);
inline uv_check_t* immediate_check_handle();
inline uv_idle_t* immediate_idle_handle();
inline uv_idle_t* destroy_ids_idle_handle();
inline uv_timer_t* destroy_ids_timer_handle();

// Register clean-up cb to be called on environment destruction.
inline void RegisterHandleCleanup(uv_handle_t* handle,
Expand Down Expand Up @@ -662,7 +662,7 @@ class Environment {
IsolateData* const isolate_data_;
uv_check_t immediate_check_handle_;
uv_idle_t immediate_idle_handle_;
uv_idle_t destroy_ids_idle_handle_;
uv_timer_t destroy_ids_timer_handle_;
uv_prepare_t idle_prepare_handle_;
uv_check_t idle_check_handle_;
AsyncHooks async_hooks_;
Expand Down
2 changes: 1 addition & 1 deletion test/async-hooks/test-writewrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function checkDestroyedWriteWraps(n, stage) {
assert.strictEqual(typeof w.uid, 'number', 'uid is a number');
assert.strictEqual(typeof w.triggerId, 'number', 'triggerId is a number');

checkInvocations(w, { init: 1, destroy: 1 }, 'when ' + stage);
checkInvocations(w, { init: 1 }, 'when ' + stage);
}
as.forEach(checkValidWriteWrap);
}
Expand Down
37 changes: 37 additions & 0 deletions test/parallel/test-async-hooks-close-during-destroy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';
// Test that async ids that are added to the destroy queue while running a
// `destroy` callback are handled correctly.

const common = require('../common');
const assert = require('assert');
const async_hooks = require('async_hooks');

const initCalls = new Set();
let destroyResCallCount = 0;
let res2;

async_hooks.createHook({
init: common.mustCallAtLeast((id, provider, triggerId) => {
if (provider === 'foobar')
initCalls.add(id);
}, 2),
destroy: common.mustCallAtLeast((id) => {
if (!initCalls.has(id)) return;

switch (destroyResCallCount++) {
case 0:
// Trigger the second `destroy` call.
res2.emitDestroy();
break;
case 2:
assert.fail('More than 2 destroy() invocations');
break;
}
}, 2)
}).enable();

const res1 = new async_hooks.AsyncResource('foobar');
res2 = new async_hooks.AsyncResource('foobar');
res1.emitDestroy();

process.on('exit', () => assert.strictEqual(destroyResCallCount, 2));
27 changes: 27 additions & 0 deletions test/parallel/test-async-hooks-top-level-clearimmediate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

// Regression test for https://github.com/nodejs/node/issues/13262

const common = require('../common');
const assert = require('assert');
const async_hooks = require('async_hooks');

let seenId, seenResource;

async_hooks.createHook({
init: common.mustCall((id, provider, triggerId, resource) => {
seenId = id;
seenResource = resource;
assert.strictEqual(provider, 'Immediate');
assert.strictEqual(triggerId, 1);
}),
before: common.mustNotCall(),
after: common.mustNotCall(),
destroy: common.mustCall((id) => {
assert.strictEqual(seenId, id);
})
}).enable();

const immediate = setImmediate(common.mustNotCall());
assert.strictEqual(immediate, seenResource);
clearImmediate(immediate);