Skip to content

Commit

Permalink
async_wrap: run destroy in uv_timer_t
Browse files Browse the repository at this point in the history
Calling the destroy callbacks in a uv_idle_t causes a timing issue where
if a handle or request is closed then the class isn't deleted until
uv_close() callbacks are called (which happens after the poll phase).
This results in some destroy callbacks not being called just before the
application exits. So instead switch the destroy callbacks to be called
in a uv_timer_t with the timeout set to zero.

When uv_run() is called with UV_RUN_ONCE the final operation of the
event loop is to process all remaining timers. By setting the timeout to
zero it results in the destroy callbacks being processed after
uv_close() but before uv_run() returned. Processing the destroyed ids
that were previously missed.

Also, process the destroy_ids_list() in a do {} while() loop that makes
sure the vector is empty before returning. Which also makes running
clear() unnecessary.

Fixes: nodejs#13262
PR-URL: nodejs#13369
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Andreas Madsen <[email protected]>
  • Loading branch information
trevnorris committed Jun 2, 2017
1 parent 98aa25c commit 1e44fd9
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 42 deletions.
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 {
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

0 comments on commit 1e44fd9

Please sign in to comment.