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] fix fatal error during destroy() calls #9753

Merged
merged 3 commits into from
Dec 1, 2016
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
71 changes: 0 additions & 71 deletions src/async-wrap-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,77 +15,6 @@

namespace node {

inline AsyncWrap::AsyncWrap(Environment* env,
v8::Local<v8::Object> object,
ProviderType provider,
AsyncWrap* parent)
: BaseObject(env, object), bits_(static_cast<uint32_t>(provider) << 1),
uid_(env->get_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::Number::New(env->isolate(), get_uid()),
v8::Int32::New(env->isolate(), provider),
Null(env->isolate()),
Null(env->isolate())
};

if (parent != nullptr) {
argv[2] = v8::Number::New(env->isolate(), parent->get_uid());
argv[3] = parent->object();
}

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


inline bool AsyncWrap::ran_init_callback() const {
return static_cast<bool>(bits_ & 1);
}
Expand Down
105 changes: 101 additions & 4 deletions src/async-wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "util.h"
#include "util-inl.h"

#include "uv.h"
#include "v8.h"
#include "v8-profiler.h"

Expand All @@ -14,6 +15,7 @@ using v8::Function;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::HeapProfiler;
using v8::Int32;
using v8::Integer;
using v8::Isolate;
using v8::Local;
Expand Down Expand Up @@ -155,9 +157,9 @@ static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
}


static void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
void AsyncWrap::Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
Environment* env = Environment::GetCurrent(context);
Isolate* isolate = env->isolate();
HandleScope scope(isolate);
Expand All @@ -181,6 +183,38 @@ static void Initialize(Local<Object> target,
}


void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
uv_idle_stop(handle);

Environment* env = Environment::from_destroy_ids_idle_handle(handle);
// None of the V8 calls done outside the HandleScope leak a handle. If this
// changes in the future then the SealHandleScope wrapping the uv_run()
// will catch this can cause the process to abort.
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
Local<Function> fn = env->async_hooks_destroy_function();

if (fn.IsEmpty())
return env->destroy_ids_list()->clear();

TryCatch try_catch(env->isolate());

for (auto current_id : *env->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);
}
}
}


void LoadAsyncWrapperInfo(Environment* env) {
HeapProfiler* heap_profiler = env->isolate()->GetHeapProfiler();
#define V(PROVIDER) \
Expand All @@ -191,6 +225,69 @@ void LoadAsyncWrapperInfo(Environment* env) {
}


AsyncWrap::AsyncWrap(Environment* env,
Local<Object> object,
ProviderType provider,
AsyncWrap* parent)
: BaseObject(env, object), bits_(static_cast<uint32_t>(provider) << 1),
uid_(env->get_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);

Local<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;

HandleScope scope(env->isolate());

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

if (parent != nullptr) {
argv[2] = Number::New(env->isolate(), parent->get_uid());
argv[3] = parent->object();
}

TryCatch try_catch(env->isolate());

MaybeLocal<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.
}


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

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

env()->destroy_ids_list()->push_back(get_uid());
}


Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
int argc,
Local<Value>* argv) {
Expand Down Expand Up @@ -290,4 +387,4 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,

} // namespace node

NODE_MODULE_CONTEXT_AWARE_BUILTIN(async_wrap, node::Initialize)
NODE_MODULE_CONTEXT_AWARE_BUILTIN(async_wrap, node::AsyncWrap::Initialize)
17 changes: 12 additions & 5 deletions src/async-wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "base-object.h"
#include "uv.h"
#include "v8.h"

#include <stdint.h>
Expand Down Expand Up @@ -49,12 +50,18 @@ class AsyncWrap : public BaseObject {
#undef V
};

inline AsyncWrap(Environment* env,
v8::Local<v8::Object> object,
ProviderType provider,
AsyncWrap* parent = nullptr);
AsyncWrap(Environment* env,
v8::Local<v8::Object> object,
ProviderType provider,
AsyncWrap* parent = nullptr);

inline virtual ~AsyncWrap();
virtual ~AsyncWrap();

static void Initialize(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context);

static void DestroyIdsCb(uv_idle_t* handle);

inline ProviderType provider_type() const;

Expand Down
15 changes: 15 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ inline Environment::Environment(IsolateData* isolate_data,

RB_INIT(&cares_task_list_);
AssignToContext(context);

destroy_ids_list_.reserve(512);
}

inline Environment::~Environment() {
Expand Down Expand Up @@ -247,6 +249,15 @@ 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 uv_idle_t* Environment::destroy_ids_idle_handle() {
return &destroy_ids_idle_handle_;
}

inline void Environment::RegisterHandleCleanup(uv_handle_t* handle,
HandleCleanupCb cb,
void *arg) {
Expand Down Expand Up @@ -301,6 +312,10 @@ inline int64_t Environment::get_async_wrap_uid() {
return ++async_wrap_uid_;
}

inline std::vector<int64_t>* Environment::destroy_ids_list() {
return &destroy_ids_list_;
}

inline uint32_t* Environment::heap_statistics_buffer() const {
CHECK_NE(heap_statistics_buffer_, nullptr);
return heap_statistics_buffer_;
Expand Down
3 changes: 3 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ 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()));

auto close_and_finish = [](Environment* env, uv_handle_t* handle, void* arg) {
handle->data = env;

Expand Down
8 changes: 8 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "v8.h"

#include <stdint.h>
#include <vector>

// Caveat emptor: we're going slightly crazy with macros here but the end
// hopefully justifies the means. We have a lot of per-context properties
Expand Down Expand Up @@ -431,8 +432,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);
inline uv_check_t* immediate_check_handle();
inline uv_idle_t* immediate_idle_handle();
inline uv_idle_t* destroy_ids_idle_handle();

// Register clean-up cb to be called on environment destruction.
inline void RegisterHandleCleanup(uv_handle_t* handle,
Expand Down Expand Up @@ -463,6 +466,9 @@ class Environment {

inline int64_t get_async_wrap_uid();

// List of id's that have been destroyed and need the destroy() cb called.
inline std::vector<int64_t>* destroy_ids_list();

inline uint32_t* heap_statistics_buffer() const;
inline void set_heap_statistics_buffer(uint32_t* pointer);

Expand Down Expand Up @@ -548,6 +554,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_prepare_t idle_prepare_handle_;
uv_check_t idle_check_handle_;
AsyncHooks async_hooks_;
Expand All @@ -562,6 +569,7 @@ class Environment {
bool trace_sync_io_;
size_t makecallback_cntr_;
int64_t async_wrap_uid_;
std::vector<int64_t> destroy_ids_list_;
debugger::Agent debugger_agent_;
#if HAVE_INSPECTOR
inspector::Agent inspector_agent_;
Expand Down
8 changes: 1 addition & 7 deletions test/parallel/test-async-wrap-uid.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ const assert = require('assert');
const async_wrap = process.binding('async_wrap');

const storage = new Map();
async_wrap.setupHooks({ init, pre, post, destroy });
async_wrap.setupHooks({ init, pre, post });
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this PR remove destroy() then or...?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no. it just makes triggering destroy() before exit a pain. and haven't figured out a good way to test it yet.

async_wrap.enable();

function init(uid) {
storage.set(uid, {
init: true,
pre: false,
post: false,
destroy: false
});
}

Expand All @@ -26,10 +25,6 @@ function post(uid) {
storage.get(uid).post = true;
}

function destroy(uid) {
storage.get(uid).destroy = true;
}

fs.access(__filename, function(err) {
assert.ifError(err);
});
Expand All @@ -51,7 +46,6 @@ process.once('exit', function() {
init: true,
pre: true,
post: true,
destroy: true
});
}
});