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

Conversation

Projects
None yet
7 participants
@trevnorris
Contributor

trevnorris commented Nov 23, 2016

Checklist
  • make -j8 test (UNIX), or vcbuild test nosign (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

async_wrap

Description of change

Calling JS during GC is a no-no. So intead create a queue of all ids
that need to have their destroy() callback called and call them later.

Removed checking destroy() in test-async-wrap-uid because destroy() can
be called after the 'exit' callback.

Missing a reliable test to reproduce the issue that caused the
FATAL_ERROR.

Fixes: #8216
Fixes: #9465

R=@bnoordhuis

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

This comment has been minimized.

@Fishrock123

Fishrock123 Nov 23, 2016

Member

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

@Fishrock123

Fishrock123 Nov 23, 2016

Member

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

This comment has been minimized.

@trevnorris

trevnorris Nov 23, 2016

Contributor

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

@trevnorris

trevnorris Nov 23, 2016

Contributor

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

src/env.h
+ // List of id's that have been destroyed and need the destroy() cb called.
+ inline void add_destroy_id(int64_t id);
+ inline int64_t get_destroy_id();
+ inline int64_t destroy_ids_length();

This comment has been minimized.

@addaleax

addaleax Nov 23, 2016

Member

nit: size_t

@addaleax

addaleax Nov 23, 2016

Member

nit: size_t

This comment has been minimized.

@trevnorris

trevnorris Nov 23, 2016

Contributor

whoops. thanks.

@trevnorris

trevnorris Nov 23, 2016

Contributor

whoops. thanks.

src/env-inl.h
+ if (destroy_ids_current_->length < arraysize(destroy_ids_current_->ids))
+ return;
+ node_destroy_ids_list* old_list = destroy_ids_current_;
+ destroy_ids_current_ = new node_destroy_ids_list{ .prev = old_list };

This comment has been minimized.

@addaleax

addaleax Nov 23, 2016

Member

nit: Could you explicitly initialize .length = 0 here, too? (edit: gcc doesn’t seem to like this anyway?)

@addaleax

addaleax Nov 23, 2016

Member

nit: Could you explicitly initialize .length = 0 here, too? (edit: gcc doesn’t seem to like this anyway?)

This comment has been minimized.

@trevnorris

trevnorris Nov 23, 2016

Contributor

cool. i'll do it the pre c++11 way.

@trevnorris

trevnorris Nov 23, 2016

Contributor

cool. i'll do it the pre c++11 way.

src/env-inl.h
+ return;
+ node_destroy_ids_list* old_list = destroy_ids_current_;
+ destroy_ids_current_ = new node_destroy_ids_list{ .prev = old_list };
+ destroy_ids_current_->ids[0] = id;

This comment has been minimized.

@addaleax

addaleax Nov 23, 2016

Member

Doesn’t this set id twice, once in the new instance and once at the end of the prev one?

@addaleax

addaleax Nov 23, 2016

Member

Doesn’t this set id twice, once in the new instance and once at the end of the prev one?

This comment has been minimized.

@trevnorris

trevnorris Nov 23, 2016

Contributor

you're right. fixed.

@trevnorris

trevnorris Nov 23, 2016

Contributor

you're right. fixed.

src/env-inl.h
+ destroy_ids_current_ = prev_list->prev;
+ delete prev_list;
+ }
+ destroy_ids_length_ = 0;

This comment has been minimized.

@addaleax

addaleax Nov 23, 2016

Member

This should probably also set destroy_ids_current_->length = 0;?

@addaleax

addaleax Nov 23, 2016

Member

This should probably also set destroy_ids_current_->length = 0;?

This comment has been minimized.

@trevnorris

trevnorris Nov 23, 2016

Contributor

ah yup. fixed.

@trevnorris

trevnorris Nov 23, 2016

Contributor

ah yup. fixed.

src/env.h
@@ -266,6 +266,13 @@ struct node_ares_task {
RB_ENTRY(node_ares_task) node;
};
+// 4096 bytes on x64 and 4088 on ia32.
+struct node_destroy_ids_list {
+ int64_t ids[510];

This comment has been minimized.

@addaleax

addaleax Nov 23, 2016

Member

teeny-tiny nit: Could you make this the last member of the struct so that length, prev and the first few entries all fit into a single cache line?

@addaleax

addaleax Nov 23, 2016

Member

teeny-tiny nit: Could you make this the last member of the struct so that length, prev and the first few entries all fit into a single cache line?

This comment has been minimized.

@trevnorris

trevnorris Nov 23, 2016

Contributor

ooh. nice. done.

@trevnorris

trevnorris Nov 23, 2016

Contributor

ooh. nice. done.

src/env.h
@@ -463,6 +472,12 @@ class Environment {
inline int64_t get_async_wrap_uid();
+ // List of id's that have been destroyed and need the destroy() cb called.
+ inline void add_destroy_id(int64_t id);
+ inline int64_t get_destroy_id();

This comment has been minimized.

@addaleax

addaleax Nov 23, 2016

Member

Naming this get_… makes this sound a bit like a getter without side effects. How about pop_destroy_id() or something like that? (And if you like, correspondingly push_destroy_id?)

@addaleax

addaleax Nov 23, 2016

Member

Naming this get_… makes this sound a bit like a getter without side effects. How about pop_destroy_id() or something like that? (And if you like, correspondingly push_destroy_id?)

This comment has been minimized.

@trevnorris

trevnorris Nov 23, 2016

Contributor

good idea. done.

@trevnorris

trevnorris Nov 23, 2016

Contributor

good idea. done.

@trevnorris

This comment has been minimized.

Show comment
Hide comment
@trevnorris

trevnorris Nov 23, 2016

Contributor

Fixed things addressed in first review.

CI: https://ci.nodejs.org/job/node-test-pull-request/4976/

Contributor

trevnorris commented Nov 23, 2016

Fixed things addressed in first review.

CI: https://ci.nodejs.org/job/node-test-pull-request/4976/

@addaleax

LGTM

src/async-wrap.cc
@@ -181,6 +183,33 @@ static void Initialize(Local<Object> target,
}
+void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
+ Environment* env = Environment::from_destroy_ids_idle_handle(handle);

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

There should be a HandleScope and Context::Scope here, the env->async_hooks_destroy_function() call below leaks a handle now.

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

There should be a HandleScope and Context::Scope here, the env->async_hooks_destroy_function() call below leaks a handle now.

This comment has been minimized.

@trevnorris

trevnorris Nov 25, 2016

Contributor

For some reason I thought that was a strong persistent conversion, which I thought wouldn't leak a handle. Since that doesn't seem to be the case I'll add it. Though I'll still leave in the HandleScope in the loop so each function call can be cleaned up.

About the first note, should we be using a SealHandleScope in all these locations so we can detect any leaked handles? Currently it's only used around uv_run, but that doesn't seem to catch leaked handles from an asynchronous call.

@trevnorris

trevnorris Nov 25, 2016

Contributor

For some reason I thought that was a strong persistent conversion, which I thought wouldn't leak a handle. Since that doesn't seem to be the case I'll add it. Though I'll still leave in the HandleScope in the loop so each function call can be cleaned up.

About the first note, should we be using a SealHandleScope in all these locations so we can detect any leaked handles? Currently it's only used around uv_run, but that doesn't seem to catch leaked handles from an asynchronous call.

This comment has been minimized.

@trevnorris

trevnorris Nov 25, 2016

Contributor

Here's my diff to double check the scenario:

diff --git a/src/async-wrap.cc b/src/async-wrap.cc
index a06e163..fa91e1a 100644
--- a/src/async-wrap.cc
+++ b/src/async-wrap.cc
@@ -185,6 +185,8 @@ void AsyncWrap::Initialize(Local<Object> target,
 
 void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
   Environment* env = Environment::from_destroy_ids_idle_handle(handle);
+  v8::SealHandleScope seal(env->isolate());
+  v8::Context::Scope context_scope(env->context());
   // This doesn't leak a handle.
   Local<Function> fn = env->async_hooks_destroy_function();
   int64_t current_id;

This never aborts. To double check I added an Object::New(env->isolate()) right after the Context::Scope() which did cause it to abort. So I don't believe this is leaking a handle. But if I am to hoist the TryCatch out of the loop it would be necessary to do anyway.

On the note of always using a SealHandleScope on the return of an async call. Guess that would only be useful if there was a centralized way to add the wrapper around uv_run every time it's about to call the callback. You know if there's a way to wrap wrap all callbacks from uv_run in a SealHandleScope? (update: see comment below about this being incorrect)

@trevnorris

trevnorris Nov 25, 2016

Contributor

Here's my diff to double check the scenario:

diff --git a/src/async-wrap.cc b/src/async-wrap.cc
index a06e163..fa91e1a 100644
--- a/src/async-wrap.cc
+++ b/src/async-wrap.cc
@@ -185,6 +185,8 @@ void AsyncWrap::Initialize(Local<Object> target,
 
 void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
   Environment* env = Environment::from_destroy_ids_idle_handle(handle);
+  v8::SealHandleScope seal(env->isolate());
+  v8::Context::Scope context_scope(env->context());
   // This doesn't leak a handle.
   Local<Function> fn = env->async_hooks_destroy_function();
   int64_t current_id;

This never aborts. To double check I added an Object::New(env->isolate()) right after the Context::Scope() which did cause it to abort. So I don't believe this is leaking a handle. But if I am to hoist the TryCatch out of the loop it would be necessary to do anyway.

On the note of always using a SealHandleScope on the return of an async call. Guess that would only be useful if there was a centralized way to add the wrapper around uv_run every time it's about to call the callback. You know if there's a way to wrap wrap all callbacks from uv_run in a SealHandleScope? (update: see comment below about this being incorrect)

This comment has been minimized.

@trevnorris

trevnorris Nov 25, 2016

Contributor

So, I tried this and it surprisingly didn't abort:

diff --git a/src/async-wrap.cc b/src/async-wrap.cc
index a06e163..3e57ee8 100644
--- a/src/async-wrap.cc
+++ b/src/async-wrap.cc
@@ -185,6 +185,8 @@ void AsyncWrap::Initialize(Local<Object> target,
 
 void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
   Environment* env = Environment::from_destroy_ids_idle_handle(handle);
+  v8::SealHandleScope seal(env->isolate());
+  v8::Context::Scope context_scope(env->context());
   // This doesn't leak a handle.
   Local<Function> fn = env->async_hooks_destroy_function();
   int64_t current_id;
@@ -194,11 +196,12 @@ void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
   if (fn.IsEmpty())
     return env->erase_destroy_ids();
 
+  TryCatch try_catch(env->isolate());
+
   while (env->destroy_ids_length() > 0) {
     current_id = env->pop_destroy_id();
     HandleScope scope(env->isolate());
     Local<Value> argv = Number::New(env->isolate(), current_id);
-    TryCatch try_catch(env->isolate());
     MaybeLocal<Value> ret = fn->Call(
         env->context(), Undefined(env->isolate()), 1, &argv);
 

Which surprises me a bit, but leads me to believe it isn't leaking a handle not wrapping the TryCatch in a HandleScope. Guess since it's using RAII it doesn't need the HandleScope to clean up?

@trevnorris

trevnorris Nov 25, 2016

Contributor

So, I tried this and it surprisingly didn't abort:

diff --git a/src/async-wrap.cc b/src/async-wrap.cc
index a06e163..3e57ee8 100644
--- a/src/async-wrap.cc
+++ b/src/async-wrap.cc
@@ -185,6 +185,8 @@ void AsyncWrap::Initialize(Local<Object> target,
 
 void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
   Environment* env = Environment::from_destroy_ids_idle_handle(handle);
+  v8::SealHandleScope seal(env->isolate());
+  v8::Context::Scope context_scope(env->context());
   // This doesn't leak a handle.
   Local<Function> fn = env->async_hooks_destroy_function();
   int64_t current_id;
@@ -194,11 +196,12 @@ void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
   if (fn.IsEmpty())
     return env->erase_destroy_ids();
 
+  TryCatch try_catch(env->isolate());
+
   while (env->destroy_ids_length() > 0) {
     current_id = env->pop_destroy_id();
     HandleScope scope(env->isolate());
     Local<Value> argv = Number::New(env->isolate(), current_id);
-    TryCatch try_catch(env->isolate());
     MaybeLocal<Value> ret = fn->Call(
         env->context(), Undefined(env->isolate()), 1, &argv);
 

Which surprises me a bit, but leads me to believe it isn't leaking a handle not wrapping the TryCatch in a HandleScope. Guess since it's using RAII it doesn't need the HandleScope to clean up?

This comment has been minimized.

@trevnorris

trevnorris Nov 25, 2016

Contributor

Forget everything I said about SealHandleScope not catching handles from an async call. My test case was incorrect. It does catch all leaked handles.

@trevnorris

trevnorris Nov 25, 2016

Contributor

Forget everything I said about SealHandleScope not catching handles from an async call. My test case was incorrect. It does catch all leaked handles.

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

For some reason I thought that was a strong persistent conversion, which I thought wouldn't leak a handle.

Oh, you're right. That's an awfully subtle implementation detail though, I'd just create the HandleScope. You'll need the Context::Scope anyway.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

For some reason I thought that was a strong persistent conversion, which I thought wouldn't leak a handle.

Oh, you're right. That's an awfully subtle implementation detail though, I'd just create the HandleScope. You'll need the Context::Scope anyway.

This comment has been minimized.

@trevnorris

trevnorris Nov 25, 2016

Contributor

You're right. Something that subtle could easily lead to future pain. Will add.

On the side, I'm surprised that both Context::Scope and TryCatch don't leak a handle. Think the Context::Scope would still work properly without the HandleScope?

@trevnorris

trevnorris Nov 25, 2016

Contributor

You're right. Something that subtle could easily lead to future pain. Will add.

On the side, I'm surprised that both Context::Scope and TryCatch don't leak a handle. Think the Context::Scope would still work properly without the HandleScope?

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

Context::Scope doesn't strictly need a HandleScope because it doesn't really do anything with handles, it just changes the active context. (The context itself is a handle, of course.)

Something similar applies to TryCatch, it merely changes the active landing pad for JS exceptions.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

Context::Scope doesn't strictly need a HandleScope because it doesn't really do anything with handles, it just changes the active context. (The context itself is a handle, of course.)

Something similar applies to TryCatch, it merely changes the active landing pad for JS exceptions.

src/async-wrap.cc
+ current_id = env->pop_destroy_id();
+ HandleScope scope(env->isolate());
+ Local<Value> argv = Number::New(env->isolate(), current_id);
+ TryCatch try_catch(env->isolate());

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

You could lift the TryCatch out of the loop, that would be (marginally) more efficient.

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

You could lift the TryCatch out of the loop, that would be (marginally) more efficient.

This comment has been minimized.

@trevnorris

trevnorris Nov 25, 2016

Contributor

Ah yup. Will do.

@trevnorris

trevnorris Nov 25, 2016

Contributor

Ah yup. Will do.

src/async-wrap.cc
+ return env->erase_destroy_ids();
+
+ while (env->destroy_ids_length() > 0) {
+ current_id = env->pop_destroy_id();

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

Is there a reason the declaration is done outside the loop?

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

Is there a reason the declaration is done outside the loop?

This comment has been minimized.

@trevnorris

trevnorris Nov 25, 2016

Contributor

Nope.

@trevnorris

trevnorris Nov 25, 2016

Contributor

Nope.

src/async-wrap.cc
+ Local<Value> argv = Number::New(env->isolate(), current_id);
+ TryCatch try_catch(env->isolate());
+ MaybeLocal<Value> ret = fn->Call(
+ env->context(), Undefined(env->isolate()), 1, &argv);

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

Cache env->context() in a handle outside the loop, it creates a new handle.

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

Cache env->context() in a handle outside the loop, it creates a new handle.

This comment has been minimized.

@trevnorris

trevnorris Nov 25, 2016

Contributor

Ah thanks. Thought it was a strong persistent conversion, which I believe doesn't create a new handle.

@trevnorris

trevnorris Nov 25, 2016

Contributor

Ah thanks. Thought it was a strong persistent conversion, which I believe doesn't create a new handle.

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

You're right, never mind.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

You're right, never mind.

src/env.h
+ size_t length;
+ node_destroy_ids_list* prev;
+ int64_t ids[510];
+};

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

Is there a reason you don't use a std::vector<int64_t>?

@bnoordhuis

bnoordhuis Nov 24, 2016

Member

Is there a reason you don't use a std::vector<int64_t>?

This comment has been minimized.

@trevnorris

trevnorris Nov 25, 2016

Contributor

If I judge the implementation complexity between using and not using the STL to be comparable then I usually choose to not use the STL. Though the thought of using std::vector doesn't bother me. Can switch it if that's your preference.

@trevnorris

trevnorris Nov 25, 2016

Contributor

If I judge the implementation complexity between using and not using the STL to be comparable then I usually choose to not use the STL. Though the thought of using std::vector doesn't bother me. Can switch it if that's your preference.

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

It's simpler than hand-rolling a vector-like class. If you're worried about excessive copying, you could simply vec.reserve(512) at startup.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

It's simpler than hand-rolling a vector-like class. If you're worried about excessive copying, you could simply vec.reserve(512) at startup.

src/async-wrap.cc
@@ -181,6 +183,33 @@ static void Initialize(Local<Object> target,
}
+void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
+ Environment* env = Environment::from_destroy_ids_idle_handle(handle);

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

For some reason I thought that was a strong persistent conversion, which I thought wouldn't leak a handle.

Oh, you're right. That's an awfully subtle implementation detail though, I'd just create the HandleScope. You'll need the Context::Scope anyway.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

For some reason I thought that was a strong persistent conversion, which I thought wouldn't leak a handle.

Oh, you're right. That's an awfully subtle implementation detail though, I'd just create the HandleScope. You'll need the Context::Scope anyway.

src/async-wrap.cc
+ Local<Value> argv = Number::New(env->isolate(), current_id);
+ TryCatch try_catch(env->isolate());
+ MaybeLocal<Value> ret = fn->Call(
+ env->context(), Undefined(env->isolate()), 1, &argv);

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

You're right, never mind.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

You're right, never mind.

src/env.h
+ size_t length;
+ node_destroy_ids_list* prev;
+ int64_t ids[510];
+};

This comment has been minimized.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

It's simpler than hand-rolling a vector-like class. If you're worried about excessive copying, you could simply vec.reserve(512) at startup.

@bnoordhuis

bnoordhuis Nov 25, 2016

Member

It's simpler than hand-rolling a vector-like class. If you're worried about excessive copying, you could simply vec.reserve(512) at startup.

@danscales

This comment has been minimized.

Show comment
Hide comment
@danscales

danscales Nov 29, 2016

@trevnorris We have now seen this bug (same assert in execution.cc) on node v4.5.0, possibly because of our use of weak pointers via the 'weak' module (https://github.com/TooTallNate/node-weak ). Do you think your fix will backport well to node v4.x? Also, just to confirm, your change is not removing weak functionality, I think, so weak pointer functionality from the 'weak' module would still work, at least in node v6 -- is that correct?

@trevnorris We have now seen this bug (same assert in execution.cc) on node v4.5.0, possibly because of our use of weak pointers via the 'weak' module (https://github.com/TooTallNate/node-weak ). Do you think your fix will backport well to node v4.x? Also, just to confirm, your change is not removing weak functionality, I think, so weak pointer functionality from the 'weak' module would still work, at least in node v6 -- is that correct?

@trevnorris

This comment has been minimized.

Show comment
Hide comment
@trevnorris

trevnorris Nov 29, 2016

Contributor

@danscales That issue will be with the module node-weak itself. It'll need a PR to fix when it runs the callback. This fix will be backported in some form to all LTS versions that are affected by this issue.

your change is not removing weak functionality

No. It's simply delaying when the callback runs.

Contributor

trevnorris commented Nov 29, 2016

@danscales That issue will be with the module node-weak itself. It'll need a PR to fix when it runs the callback. This fix will be backported in some form to all LTS versions that are affected by this issue.

your change is not removing weak functionality

No. It's simply delaying when the callback runs.

@trevnorris

This comment has been minimized.

Show comment
Hide comment
@trevnorris

trevnorris Nov 29, 2016

Contributor

@bnoordhuis comments should be addressed in commit REVIEW finish fixes for second review round

Contributor

trevnorris commented Nov 29, 2016

@bnoordhuis comments should be addressed in commit REVIEW finish fixes for second review round

@trevnorris

This comment has been minimized.

Show comment
Hide comment
@bnoordhuis

LGTM with some final comments.

src/async-wrap.cc
+ // 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.
+ v8::Context::Scope context_scope(env->context());

This comment has been minimized.

@bnoordhuis

bnoordhuis Dec 1, 2016

Member

Can you at least add a SealHandleScope? That said, a HandleScop isn't that expensive to create and it's more robust. Debugging a crash or a leak six months from now because something changed would be highly annoying.

@bnoordhuis

bnoordhuis Dec 1, 2016

Member

Can you at least add a SealHandleScope? That said, a HandleScop isn't that expensive to create and it's more robust. Debugging a crash or a leak six months from now because something changed would be highly annoying.

This comment has been minimized.

@trevnorris

trevnorris Dec 1, 2016

Contributor

I misread the following stack:

FATAL ERROR: v8::HandleScope::CreateHandle() Cannot create a handle without a HandleScope
 1: node::Abort() [./node_g]
 2: 0x21986e3 [./node_g]
 3: v8::Utils::ReportApiFailure(char const*, char const*) [./node_g]
 4: v8::Utils::ApiCheck(bool, char const*, char const*) [./node_g]
 5: v8::internal::HandleScope::Extend(v8::internal::Isolate*) [./node_g]
 6: v8::internal::HandleScope::CreateHandle(v8::internal::Isolate*, v8::internal::Object*) [./node_g]
 7: v8::internal::HandleScope::GetHandle(v8::internal::Isolate*, v8::internal::Object*) [./node_g]
 8: v8::internal::HandleBase::HandleBase(v8::internal::Object*, v8::internal::Isolate*) [./node_g]
 9: v8::internal::Handle<v8::internal::JSFunction>::Handle(v8::internal::JSFunction*, v8::internal::Isolate*) [./node_g]
10: v8::internal::Isolate::object_function() [./node_g]
11: v8::Object::New(v8::Isolate*) [./node_g]
12: node::AsyncWrap::DestroyIdsCb(uv_idle_s*) [./node_g]

Which is generated by the following diff when running test-async-wrap-check-providers.js:

diff --git a/src/async-wrap.cc b/src/async-wrap.cc
index 3887179..0bdde8d 100644
--- a/src/async-wrap.cc
+++ b/src/async-wrap.cc
@@ -190,6 +190,7 @@ void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
   // will catch this can cause the process to abort.
   v8::Context::Scope context_scope(env->context());
   Local<Function> fn = env->async_hooks_destroy_function();
+  Local<Object> test = Object::New(env->isolate());
 
   uv_idle_stop(handle);
 

Now I'm confused as to why the SealHandleScope would be needed since I get the same stack even if I add the SealHandleScope before it. Thoughts?

Anyway, I'll add the HandleScope. Hadn't yet b/c I haven't been able to write a test that would cause the script to crash w/o it. If you have an example of one that would be excellent.

@trevnorris

trevnorris Dec 1, 2016

Contributor

I misread the following stack:

FATAL ERROR: v8::HandleScope::CreateHandle() Cannot create a handle without a HandleScope
 1: node::Abort() [./node_g]
 2: 0x21986e3 [./node_g]
 3: v8::Utils::ReportApiFailure(char const*, char const*) [./node_g]
 4: v8::Utils::ApiCheck(bool, char const*, char const*) [./node_g]
 5: v8::internal::HandleScope::Extend(v8::internal::Isolate*) [./node_g]
 6: v8::internal::HandleScope::CreateHandle(v8::internal::Isolate*, v8::internal::Object*) [./node_g]
 7: v8::internal::HandleScope::GetHandle(v8::internal::Isolate*, v8::internal::Object*) [./node_g]
 8: v8::internal::HandleBase::HandleBase(v8::internal::Object*, v8::internal::Isolate*) [./node_g]
 9: v8::internal::Handle<v8::internal::JSFunction>::Handle(v8::internal::JSFunction*, v8::internal::Isolate*) [./node_g]
10: v8::internal::Isolate::object_function() [./node_g]
11: v8::Object::New(v8::Isolate*) [./node_g]
12: node::AsyncWrap::DestroyIdsCb(uv_idle_s*) [./node_g]

Which is generated by the following diff when running test-async-wrap-check-providers.js:

diff --git a/src/async-wrap.cc b/src/async-wrap.cc
index 3887179..0bdde8d 100644
--- a/src/async-wrap.cc
+++ b/src/async-wrap.cc
@@ -190,6 +190,7 @@ void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) {
   // will catch this can cause the process to abort.
   v8::Context::Scope context_scope(env->context());
   Local<Function> fn = env->async_hooks_destroy_function();
+  Local<Object> test = Object::New(env->isolate());
 
   uv_idle_stop(handle);
 

Now I'm confused as to why the SealHandleScope would be needed since I get the same stack even if I add the SealHandleScope before it. Thoughts?

Anyway, I'll add the HandleScope. Hadn't yet b/c I haven't been able to write a test that would cause the script to crash w/o it. If you have an example of one that would be excellent.

This comment has been minimized.

@bnoordhuis

bnoordhuis Dec 1, 2016

Member

There's a SealHandleScope at the bottom of the stack (node.cc creates one before calling uv_run()) but I figure it's better to create one here explicitly than rely on a detail from elsewhere.

@bnoordhuis

bnoordhuis Dec 1, 2016

Member

There's a SealHandleScope at the bottom of the stack (node.cc creates one before calling uv_run()) but I figure it's better to create one here explicitly than rely on a detail from elsewhere.

src/async-wrap.cc
+ v8::Context::Scope context_scope(env->context());
+ Local<Function> fn = env->async_hooks_destroy_function();
+
+ uv_idle_stop(handle);

This comment has been minimized.

@bnoordhuis

bnoordhuis Dec 1, 2016

Member

Move move this to the top of the function? It looks a little incongruous here.

@bnoordhuis

bnoordhuis Dec 1, 2016

Member

Move move this to the top of the function? It looks a little incongruous here.

This comment has been minimized.

@trevnorris

trevnorris Dec 1, 2016

Contributor

sure. just habit of placing it after the variable declarations.

@trevnorris

trevnorris Dec 1, 2016

Contributor

sure. just habit of placing it after the variable declarations.

src/async-wrap.cc
+ if (!ran_init_callback())
+ return;
+
+ if (env()->destroy_ids_list()->size() == 0)

This comment has been minimized.

@bnoordhuis

bnoordhuis Dec 1, 2016

Member

empty()?

@bnoordhuis

bnoordhuis Dec 1, 2016

Member

empty()?

This comment has been minimized.

@trevnorris

trevnorris Dec 1, 2016

Contributor

heh. yup.

@trevnorris

trevnorris Dec 1, 2016

Contributor

heh. yup.

@trevnorris

This comment has been minimized.

Show comment
Hide comment
Contributor

trevnorris commented Dec 1, 2016

trevnorris added some commits Nov 23, 2016

async_wrap: mode constructor/destructor to .cc
The constructor and destructor shouldn't have been placed in the -inl.h
file from the beginning.

PR-URL: #9753
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
async_wrap: make Initialize a static class member
This is how it's done everywhere else in core. Make it follow suit.

PR-URL: #9753
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
async_wrap: call destroy() callback in uv_idle_t
Calling JS during GC is a no-no. So intead create a queue of all ids
that need to have their destroy() callback called and call them later.

Removed checking destroy() in test-async-wrap-uid because destroy() can
be called after the 'exit' callback.

Missing a reliable test to reproduce the issue that caused the
FATAL_ERROR.

PR-URL: #9753
Fixes: #8216
Fixes: #9465
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
@trevnorris

This comment has been minimized.

Show comment
Hide comment
@trevnorris

trevnorris Dec 1, 2016

Contributor

Thanks. Landed in 517e3a6, cf5f4b8 and b49b496.

Contributor

trevnorris commented Dec 1, 2016

Thanks. Landed in 517e3a6, cf5f4b8 and b49b496.

@trevnorris trevnorris merged commit b49b496 into nodejs:master Dec 1, 2016

@trevnorris

This comment has been minimized.

Show comment
Hide comment
@trevnorris

trevnorris Dec 1, 2016

Contributor

This'll need to be backported to v7, and possibly v6? @nodejs/lts how far back should land? since it's related to other asyncwrap changes coming though perhaps i'll just backport to v4 to make life easier.

Contributor

trevnorris commented Dec 1, 2016

This'll need to be backported to v7, and possibly v6? @nodejs/lts how far back should land? since it's related to other asyncwrap changes coming though perhaps i'll just backport to v4 to make life easier.

@trevnorris trevnorris deleted the trevnorris:fix-destroy-gc-abort branch Dec 2, 2016

MylesBorins added a commit that referenced this pull request Dec 2, 2016

async_wrap: mode constructor/destructor to .cc
The constructor and destructor shouldn't have been placed in the -inl.h
file from the beginning.

PR-URL: #9753
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>

MylesBorins added a commit that referenced this pull request Dec 2, 2016

async_wrap: make Initialize a static class member
This is how it's done everywhere else in core. Make it follow suit.

PR-URL: #9753
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>

MylesBorins added a commit that referenced this pull request Dec 2, 2016

async_wrap: call destroy() callback in uv_idle_t
Calling JS during GC is a no-no. So intead create a queue of all ids
that need to have their destroy() callback called and call them later.

Removed checking destroy() in test-async-wrap-uid because destroy() can
be called after the 'exit' callback.

Missing a reliable test to reproduce the issue that caused the
FATAL_ERROR.

PR-URL: #9753
Fixes: #8216
Fixes: #9465
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>

@Fishrock123 Fishrock123 referenced this pull request Dec 5, 2016

Merged

v7.2.1 proposal #10127

2 of 2 tasks complete

jmdarling added a commit to jmdarling/node that referenced this pull request Dec 8, 2016

async_wrap: mode constructor/destructor to .cc
The constructor and destructor shouldn't have been placed in the -inl.h
file from the beginning.

PR-URL: nodejs#9753
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>

jmdarling added a commit to jmdarling/node that referenced this pull request Dec 8, 2016

async_wrap: make Initialize a static class member
This is how it's done everywhere else in core. Make it follow suit.

PR-URL: nodejs#9753
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>

jmdarling added a commit to jmdarling/node that referenced this pull request Dec 8, 2016

async_wrap: call destroy() callback in uv_idle_t
Calling JS during GC is a no-no. So intead create a queue of all ids
that need to have their destroy() callback called and call them later.

Removed checking destroy() in test-async-wrap-uid because destroy() can
be called after the 'exit' callback.

Missing a reliable test to reproduce the issue that caused the
FATAL_ERROR.

PR-URL: nodejs#9753
Fixes: nodejs#8216
Fixes: nodejs#9465
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
@danscales

This comment has been minimized.

Show comment
Hide comment
@danscales

danscales Dec 9, 2016

@trevnorris Do you have a suggestion on how to fix node-weak to deal with the same problem? As I mentioned, we get a similar problem to what you fixed in node v6 (and less often in node v4) when we use node-weak extensively. Our application needs weak pointers, but we can get away without actually invoking a javascript callback when the weak object is garbage collected, if needed. So, I tried just disabling the code in weakref.cc/TargetCallback that invokes the javascript callback for weak pointers, but that did not help. So, I'm not sure if there is a much deeper problem in using SetWeak() and so there is no simple fix for weakref.cc. Thanks for any help or suggestions. Seems like it would be good to fix node-weak, since it is actually in the node test suite (test/gc/node_modules/weak)

@trevnorris Do you have a suggestion on how to fix node-weak to deal with the same problem? As I mentioned, we get a similar problem to what you fixed in node v6 (and less often in node v4) when we use node-weak extensively. Our application needs weak pointers, but we can get away without actually invoking a javascript callback when the weak object is garbage collected, if needed. So, I tried just disabling the code in weakref.cc/TargetCallback that invokes the javascript callback for weak pointers, but that did not help. So, I'm not sure if there is a much deeper problem in using SetWeak() and so there is no simple fix for weakref.cc. Thanks for any help or suggestions. Seems like it would be good to fix node-weak, since it is actually in the node test suite (test/gc/node_modules/weak)

@trevnorris

This comment has been minimized.

Show comment
Hide comment
@trevnorris

trevnorris Dec 9, 2016

Contributor

@danscales I'd suggest that node-weak also implement a similar implementation to d479826 using a uv_idle_t.

Contributor

trevnorris commented Dec 9, 2016

@danscales I'd suggest that node-weak also implement a similar implementation to d479826 using a uv_idle_t.

@MylesBorins

This comment has been minimized.

Show comment
Hide comment
@MylesBorins

MylesBorins Dec 21, 2016

Member

@trevnorris if you could manually backport that would be rad!

Member

MylesBorins commented Dec 21, 2016

@trevnorris if you could manually backport that would be rad!

@trevnorris

This comment has been minimized.

Show comment
Hide comment
@trevnorris

trevnorris Dec 21, 2016

Contributor

@thealphanerd Will do, and hot damn! found a bug with this that's fixed by #10400 :P So will wait for that to land before backporting this.

Contributor

trevnorris commented Dec 21, 2016

@thealphanerd Will do, and hot damn! found a bug with this that's fixed by #10400 :P So will wait for that to land before backporting this.

@MylesBorins

This comment has been minimized.

Show comment
Hide comment
@MylesBorins

MylesBorins Jan 23, 2017

Member

@trevnorris #10400 has landed on v6.x

Member

MylesBorins commented Jan 23, 2017

@trevnorris #10400 has landed on v6.x

@MylesBorins

This comment has been minimized.

Show comment
Hide comment
@MylesBorins

MylesBorins Mar 7, 2017

Member

@trevnorris can you backport this?

Member

MylesBorins commented Mar 7, 2017

@trevnorris can you backport this?

@MylesBorins

This comment has been minimized.

Show comment
Hide comment
Member

MylesBorins commented May 8, 2017

@addaleax

This comment has been minimized.

Show comment
Hide comment
@addaleax

addaleax May 9, 2017

Member

@MylesBorins I’m confused, it looks like this has already landed on v6.x-staging as 59d8255...f3b0cf5?

Member

addaleax commented May 9, 2017

@MylesBorins I’m confused, it looks like this has already landed on v6.x-staging as 59d8255...f3b0cf5?

@MylesBorins

This comment has been minimized.

Show comment
Hide comment
@MylesBorins

MylesBorins May 9, 2017

Member

Perhaps this was mislabelled. My audit brought up commits, but you are correct that they are there.
¯\_(ツ)_/¯

Member

MylesBorins commented May 9, 2017

Perhaps this was mislabelled. My audit brought up commits, but you are correct that they are there.
¯\_(ツ)_/¯

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment