From 2d82870bfd9bae34488db8465a4ed6c2bd5c0fd6 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 26 Mar 2014 16:30:50 -0700 Subject: [PATCH 1/5] async-wrap: fix coverage and providers * Generalize the ASYNC_PROVIDER being passed by then category to which it belongs. Instead of having specialized providers for classes like ConnectWrap. * When some classes are instantiated from a libuv callback there is no activeContext, so manually set the parent class that contains the context that should be used for class instantiation. This will allow proper propagation. * Force before/after callbacks to run when a class is instantiated, not only when a callback is about to be called from MakeCallback. * Add _removeAsyncQueue() method to handles so when there are no longer any items in the queue async_flags_ can be reset. * Manually track additional states in Environment. For example: the length of the activeContext queue, the provider type of the activeContext or whether any AsyncListener callbacks are being processed. --- src/async-wrap-inl.h | 61 +++++++++++++++++++++++++++++++++------- src/async-wrap.h | 49 +++++++++++++++++--------------- src/cares_wrap.cc | 2 +- src/env-inl.h | 51 +++++++++++++++++++++++++++++---- src/env.h | 26 ++++++++++++++--- src/fs_event_wrap.cc | 2 ++ src/node_crypto.cc | 4 +++ src/node_file.cc | 4 ++- src/node_stat_watcher.cc | 2 ++ src/node_zlib.cc | 2 ++ src/pipe_wrap.cc | 10 +++++-- src/process_wrap.cc | 2 ++ src/req_wrap.h | 4 +-- src/signal_wrap.cc | 2 ++ src/stream_wrap.cc | 17 ++++++++--- src/stream_wrap.h | 9 +++--- src/tcp_wrap.cc | 12 ++++++-- src/timer_wrap.cc | 2 ++ src/tls_wrap.cc | 2 ++ src/tty_wrap.cc | 2 ++ src/udp_wrap.cc | 4 ++- 21 files changed, 211 insertions(+), 58 deletions(-) diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h index 388ee19c2bd..c90b947ec6d 100644 --- a/src/async-wrap-inl.h +++ b/src/async-wrap-inl.h @@ -41,30 +41,63 @@ inline AsyncWrap::AsyncWrap(Environment* env, : BaseObject(env, object), async_flags_(NO_OPTIONS), provider_type_(provider) { - if (!env->has_async_listener()) + AsyncWrap* parent = env->async_wrap_parent_class(); + bool parent_has_async_queue = parent != NULL && parent->has_async_queue(); + + // No need to run if in the middle of processing any AsyncListener callbacks + // or if there is neither an activeContext nor is there a parent context + // that contains an async queue. + if (env->in_async_tick() || + (!env->has_active_async_queue() && !parent_has_async_queue)) return; - // TODO(trevnorris): Do we really need to TryCatch this call? + env->set_provider_type(provider); + + v8::Local process = env->process_object(); + v8::Local parent_val; + v8::TryCatch try_catch; try_catch.SetVerbose(true); - v8::Local val = object.As(); - env->async_listener_run_function()->Call(env->process_object(), 1, &val); + if (parent_has_async_queue) { + parent_val = parent->object().As(); + env->async_listener_load_function()->Call(process, 1, &parent_val); + + if (try_catch.HasCaught()) + return; + } + + v8::Local val_v[] = { + object.As(), + v8::Integer::NewFromUnsigned(env->isolate(), provider) + }; + env->async_listener_run_function()->Call(process, ARRAY_SIZE(val_v), val_v); if (!try_catch.HasCaught()) async_flags_ |= HAS_ASYNC_LISTENER; + else + return; + + if (parent_has_async_queue) + env->async_listener_unload_function()->Call(process, 1, &parent_val); } inline AsyncWrap::~AsyncWrap() { } -inline uint32_t AsyncWrap::provider_type() const { + +template +inline void AsyncWrap::AddMethods(v8::Local t) { + NODE_SET_PROTOTYPE_METHOD(t, "_removeAsyncQueue", RemoveAsyncQueue); +} + +inline AsyncWrap::ProviderType AsyncWrap::provider_type() const { return provider_type_; } -inline bool AsyncWrap::has_async_listener() { +inline bool AsyncWrap::has_async_queue() { return async_flags_ & HAS_ASYNC_LISTENER; } @@ -84,7 +117,7 @@ inline v8::Handle AsyncWrap::MakeDomainCallback( v8::TryCatch try_catch; try_catch.SetVerbose(true); - if (has_async_listener()) { + if (has_async_queue()) { v8::Local val = context.As(); env()->async_listener_load_function()->Call(process, 1, &val); @@ -124,7 +157,7 @@ inline v8::Handle AsyncWrap::MakeDomainCallback( return Undefined(env()->isolate()); } - if (has_async_listener()) { + if (has_async_queue()) { v8::Local val = context.As(); env()->async_listener_unload_function()->Call(process, 1, &val); @@ -173,7 +206,7 @@ inline v8::Handle AsyncWrap::MakeCallback( v8::TryCatch try_catch; try_catch.SetVerbose(true); - if (has_async_listener()) { + if (has_async_queue()) { v8::Local val = context.As(); env()->async_listener_load_function()->Call(process, 1, &val); @@ -187,7 +220,7 @@ inline v8::Handle AsyncWrap::MakeCallback( return Undefined(env()->isolate()); } - if (has_async_listener()) { + if (has_async_queue()) { v8::Local val = context.As(); env()->async_listener_unload_function()->Call(process, 1, &val); @@ -244,6 +277,14 @@ inline v8::Handle AsyncWrap::MakeCallback( return MakeCallback(cb, argc, argv); } + +template +inline void AsyncWrap::RemoveAsyncQueue( + const v8::FunctionCallbackInfo& args) { + Type* wrap = Unwrap(args.Holder()); + wrap->async_flags_ = NO_OPTIONS; +} + } // namespace node #endif // SRC_ASYNC_WRAP_INL_H_ diff --git a/src/async-wrap.h b/src/async-wrap.h index 1b1802a68ff..1fee8e3a217 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -36,25 +36,24 @@ class AsyncWrap : public BaseObject { }; enum ProviderType { - PROVIDER_NONE = 1 << 0, - PROVIDER_CARES = 1 << 1, - PROVIDER_CONNECTWRAP = 1 << 2, - PROVIDER_CRYPTO = 1 << 3, - PROVIDER_FSEVENTWRAP = 1 << 4, - PROVIDER_GETADDRINFOREQWRAP = 1 << 5, - PROVIDER_PIPEWRAP = 1 << 6, - PROVIDER_PROCESSWRAP = 1 << 7, - PROVIDER_REQWRAP = 1 << 8, - PROVIDER_SHUTDOWNWRAP = 1 << 9, - PROVIDER_SIGNALWRAP = 1 << 10, - PROVIDER_STATWATCHER = 1 << 11, - PROVIDER_TCPWRAP = 1 << 12, - PROVIDER_TIMERWRAP = 1 << 13, - PROVIDER_TLSWRAP = 1 << 14, - PROVIDER_TTYWRAP = 1 << 15, - PROVIDER_UDPWRAP = 1 << 16, - PROVIDER_ZLIB = 1 << 17, - PROVIDER_GETNAMEINFOREQWRAP = 1 << 18 + PROVIDER_NONE = 0, + PROVIDER_CRYPTO = 1 << 0, + PROVIDER_FSEVENTWRAP = 1 << 1, + PROVIDER_FSREQWRAP = 1 << 2, + PROVIDER_GETADDRINFOREQWRAP = 1 << 3, + PROVIDER_GETNAMEINFOREQWRAP = 1 << 4, + PROVIDER_PIPEWRAP = 1 << 5, + PROVIDER_PROCESSWRAP = 1 << 6, + PROVIDER_QUERYWRAP = 1 << 7, + PROVIDER_SHUTDOWNWRAP = 1 << 8, + PROVIDER_SIGNALWRAP = 1 << 9, + PROVIDER_STATWATCHER = 1 << 10, + PROVIDER_TCPWRAP = 1 << 11, + PROVIDER_TIMERWRAP = 1 << 12, + PROVIDER_TLSWRAP = 1 << 13, + PROVIDER_TTYWRAP = 1 << 14, + PROVIDER_UDPWRAP = 1 << 15, + PROVIDER_ZLIB = 1 << 16 }; inline AsyncWrap(Environment* env, @@ -63,9 +62,12 @@ class AsyncWrap : public BaseObject { inline ~AsyncWrap(); - inline bool has_async_listener(); + template + static inline void AddMethods(v8::Local t); - inline uint32_t provider_type() const; + inline bool has_async_queue(); + + inline ProviderType provider_type() const; // Only call these within a valid HandleScope. inline v8::Handle MakeCallback(const v8::Handle cb, @@ -80,6 +82,9 @@ class AsyncWrap : public BaseObject { private: inline AsyncWrap(); + template + static inline void RemoveAsyncQueue( + const v8::FunctionCallbackInfo& args); // TODO(trevnorris): BURN IN FIRE! Remove this as soon as a suitable // replacement is committed. @@ -89,7 +94,7 @@ class AsyncWrap : public BaseObject { v8::Handle* argv); uint32_t async_flags_; - uint32_t provider_type_; + ProviderType provider_type_; }; } // namespace node diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 7523e9df655..a6919edd456 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -229,7 +229,7 @@ static Local HostentToNames(Environment* env, struct hostent* host) { class QueryWrap : public AsyncWrap { public: QueryWrap(Environment* env, Local req_wrap_obj) - : AsyncWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_CARES) { + : AsyncWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_QUERYWRAP) { } virtual ~QueryWrap() { diff --git a/src/env-inl.h b/src/env-inl.h index 90fc77c6243..40dfe7ccf33 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -123,14 +123,26 @@ inline int Environment::AsyncListener::fields_count() const { return kFieldsCount; } -inline bool Environment::AsyncListener::has_listener() const { - return fields_[kHasListener] > 0; +inline bool Environment::AsyncListener::has_active_context() const { + return fields_[kActiveAsyncContextType] != 0; +} + +inline bool Environment::AsyncListener::in_async_tick() const { + return fields_[kInAsyncTick] != 0; +} + +inline void Environment::AsyncListener::set_provider_type(int32_t provider) { + fields_[kActiveAsyncContextType] = provider; } inline uint32_t Environment::AsyncListener::watched_providers() const { return fields_[kWatchedProviders]; } +inline uint32_t Environment::AsyncListener::active_async_queue_length() const { + return fields_[kActiveAsyncQueueLength]; +} + inline Environment::DomainFlag::DomainFlag() { for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0; } @@ -258,9 +270,19 @@ inline v8::Isolate* Environment::isolate() const { return isolate_; } -inline bool Environment::has_async_listener() const { +inline bool Environment::has_active_context() const { // The const_cast is okay, it doesn't violate conceptual const-ness. - return const_cast(this)->async_listener()->has_listener(); + return const_cast(this)->async_listener()->has_active_context(); +} + +inline bool Environment::in_async_tick() const { + // The const_cast is okay, it doesn't violate conceptual const-ness. + return const_cast(this)->async_listener()->in_async_tick(); +} + +inline void Environment::set_provider_type(int32_t provider) { + // The const_cast is okay, it doesn't violate conceptual const-ness. + const_cast(this)->async_listener()->set_provider_type(provider); } inline uint32_t Environment::watched_providers() const { @@ -268,6 +290,12 @@ inline uint32_t Environment::watched_providers() const { return const_cast(this)->async_listener()->watched_providers(); } +inline uint32_t Environment::has_active_async_queue() const { + // The const_cast is okay, it doesn't violate conceptual const-ness. + return const_cast(this)-> + async_listener()->active_async_queue_length() > 0; +} + inline bool Environment::in_domain() const { // The const_cast is okay, it doesn't violate conceptual const-ness. return using_domains() && @@ -309,7 +337,7 @@ inline uv_loop_t* Environment::event_loop() const { } inline Environment::AsyncListener* Environment::async_listener() { - return &async_listener_count_; + return &async_listener_; } inline Environment::DomainFlag* Environment::domain_flag() { @@ -420,6 +448,19 @@ inline void Environment::ThrowUVException(int errorno, UVException(isolate(), errorno, syscall, message, path)); } +template +inline void Environment::set_async_wrap_parent_class(WrapType* parent) { + async_wrap_parent_ = static_cast(parent); +} + +inline AsyncWrap* Environment::async_wrap_parent_class() const { + return async_wrap_parent_; +} + +inline void Environment::reset_async_wrap_parent_class() { + async_wrap_parent_ = NULL; +} + #define V(PropertyName, StringValue) \ inline \ v8::Local Environment::IsolateData::PropertyName() const { \ diff --git a/src/env.h b/src/env.h index fba0ce975b0..93ac881e011 100644 --- a/src/env.h +++ b/src/env.h @@ -263,6 +263,7 @@ namespace node { V(tty_constructor_template, v8::FunctionTemplate) \ V(udp_constructor_function, v8::Function) \ +class AsyncWrap; class Environment; // TODO(bnoordhuis) Rename struct, the ares_ prefix implies it's part @@ -282,16 +283,22 @@ class Environment { public: inline uint32_t* fields(); inline int fields_count() const; - inline bool has_listener() const; + inline bool has_active_context() const; + inline bool in_async_tick() const; + // Using int32_t because JS specific flags are < 0. + inline void set_provider_type(int32_t provider); inline uint32_t watched_providers() const; + inline uint32_t active_async_queue_length() const; private: friend class Environment; // So we can call the constructor. inline AsyncListener(); enum Fields { - kHasListener, + kActiveAsyncContextType, + kActiveAsyncQueueLength, kWatchedProviders, + kInAsyncTick, kFieldsCount }; @@ -366,9 +373,12 @@ class Environment { inline v8::Isolate* isolate() const; inline uv_loop_t* event_loop() const; - inline bool has_async_listener() const; + inline bool has_active_context() const; + inline bool in_async_tick() const; + inline void set_provider_type(int32_t provider); inline bool in_domain() const; inline uint32_t watched_providers() const; + inline uint32_t has_active_async_queue() const; static inline Environment* from_immediate_check_handle(uv_check_t* handle); inline uv_check_t* immediate_check_handle(); @@ -416,6 +426,13 @@ class Environment { inline static void ThrowTypeError(v8::Isolate* isolate, const char* errmsg); inline static void ThrowRangeError(v8::Isolate* isolate, const char* errmsg); + // Temporarily set the async wrap parent class when instantiating a + // new class originating from a libuv callback. + template + inline void set_async_wrap_parent_class(WrapType* parent); + inline AsyncWrap* async_wrap_parent_class() const; + inline void reset_async_wrap_parent_class(); + // Strings are shared across shared contexts. The getters simply proxy to // the per-isolate primitive. #define V(PropertyName, StringValue) \ @@ -444,13 +461,14 @@ class Environment { kContextEmbedderDataIndex = NODE_CONTEXT_EMBEDDER_DATA_INDEX }; + AsyncWrap* async_wrap_parent_; v8::Isolate* const isolate_; IsolateData* const isolate_data_; uv_check_t immediate_check_handle_; uv_idle_t immediate_idle_handle_; uv_prepare_t idle_prepare_handle_; uv_check_t idle_check_handle_; - AsyncListener async_listener_count_; + AsyncListener async_listener_; DomainFlag domain_flag_; TickInfo tick_info_; uv_timer_t cares_timer_handle_; diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc index 04f3b997fd8..8b0a579e043 100644 --- a/src/fs_event_wrap.cc +++ b/src/fs_event_wrap.cc @@ -90,6 +90,8 @@ void FSEventWrap::Initialize(Handle target, NODE_SET_PROTOTYPE_METHOD(t, "start", Start); NODE_SET_PROTOTYPE_METHOD(t, "close", Close); + AsyncWrap::AddMethods(t); + target->Set(env->fsevent_string(), t->GetFunction()); } diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 4abc513dce4..d1885954a96 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -2004,6 +2004,8 @@ void Connection::Initialize(Environment* env, Handle target) { NODE_SET_PROTOTYPE_METHOD(t, "setSNICallback", Connection::SetSNICallback); #endif + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Connection"), t->GetFunction()); } @@ -4454,6 +4456,8 @@ void Certificate::Initialize(Environment* env, Handle target) { NODE_SET_PROTOTYPE_METHOD(t, "exportPublicKey", ExportPublicKey); NODE_SET_PROTOTYPE_METHOD(t, "exportChallenge", ExportChallenge); + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Certificate"), t->GetFunction()); } diff --git a/src/node_file.cc b/src/node_file.cc index d62fbe2ab9d..d403222251e 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -72,7 +72,9 @@ class FSReqWrap: public ReqWrap { void* operator new(size_t size, char* storage) { return storage; } FSReqWrap(Environment* env, const char* syscall, char* data = NULL) - : ReqWrap(env, Object::New(env->isolate())), + : ReqWrap(env, + Object::New(env->isolate()), + AsyncWrap::PROVIDER_FSREQWRAP), syscall_(syscall), data_(data), dest_len_(0) { diff --git a/src/node_stat_watcher.cc b/src/node_stat_watcher.cc index aaaa1a3cdb0..fa05d86eb60 100644 --- a/src/node_stat_watcher.cc +++ b/src/node_stat_watcher.cc @@ -56,6 +56,8 @@ void StatWatcher::Initialize(Environment* env, Handle target) { NODE_SET_PROTOTYPE_METHOD(t, "start", StatWatcher::Start); NODE_SET_PROTOTYPE_METHOD(t, "stop", StatWatcher::Stop); + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "StatWatcher"), t->GetFunction()); } diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 4f0c938998a..e7e65a04a73 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -665,6 +665,8 @@ void InitZlib(Handle target, NODE_DEFINE_CONSTANT(target, INFLATERAW); NODE_DEFINE_CONSTANT(target, UNZIP); + AsyncWrap::AddMethods(z); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"), FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)); } diff --git a/src/pipe_wrap.cc b/src/pipe_wrap.cc index 03156cc8f52..cd798afba1b 100644 --- a/src/pipe_wrap.cc +++ b/src/pipe_wrap.cc @@ -114,6 +114,8 @@ void PipeWrap::Initialize(Handle target, NODE_SET_PROTOTYPE_METHOD(t, "setPendingInstances", SetPendingInstances); #endif + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Pipe"), t->GetFunction()); env->set_pipe_constructor_template(t); } @@ -130,7 +132,9 @@ void PipeWrap::New(const FunctionCallbackInfo& args) { } -PipeWrap::PipeWrap(Environment* env, Handle object, bool ipc) +PipeWrap::PipeWrap(Environment* env, + Handle object, + bool ipc) : StreamWrap(env, object, reinterpret_cast(&handle_), @@ -205,8 +209,10 @@ void PipeWrap::OnConnection(uv_stream_t* handle, int status) { return; } + env->set_async_wrap_parent_class(pipe_wrap); // Instanciate the client javascript object and handle. Local client_obj = Instantiate(env); + env->reset_async_wrap_parent_class(); // Unwrap the client javascript object. PipeWrap* wrap = Unwrap(client_obj); @@ -286,7 +292,7 @@ void PipeWrap::Connect(const FunctionCallbackInfo& args) { ConnectWrap* req_wrap = new ConnectWrap(env, req_wrap_obj, - AsyncWrap::PROVIDER_CONNECTWRAP); + AsyncWrap::PROVIDER_PIPEWRAP); uv_pipe_connect(&req_wrap->req_, &wrap->handle_, *name, diff --git a/src/process_wrap.cc b/src/process_wrap.cc index a270c388436..c8ba7d4816e 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -64,6 +64,8 @@ class ProcessWrap : public HandleWrap { NODE_SET_PROTOTYPE_METHOD(constructor, "ref", HandleWrap::Ref); NODE_SET_PROTOTYPE_METHOD(constructor, "unref", HandleWrap::Unref); + AsyncWrap::AddMethods(constructor); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Process"), constructor->GetFunction()); } diff --git a/src/req_wrap.h b/src/req_wrap.h index 56d63eb7113..d225250f3b3 100644 --- a/src/req_wrap.h +++ b/src/req_wrap.h @@ -39,8 +39,8 @@ class ReqWrap : public AsyncWrap { public: ReqWrap(Environment* env, v8::Handle object, - AsyncWrap::ProviderType provider = AsyncWrap::PROVIDER_REQWRAP) - : AsyncWrap(env, object, AsyncWrap::PROVIDER_REQWRAP) { + AsyncWrap::ProviderType provider) + : AsyncWrap(env, object, provider) { if (env->in_domain()) object->Set(env->domain_string(), env->domain_array()->Get(0)); diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc index a50340d511a..de70a164e47 100644 --- a/src/signal_wrap.cc +++ b/src/signal_wrap.cc @@ -58,6 +58,8 @@ class SignalWrap : public HandleWrap { NODE_SET_PROTOTYPE_METHOD(constructor, "start", Start); NODE_SET_PROTOTYPE_METHOD(constructor, "stop", Stop); + AsyncWrap::AddMethods(constructor); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Signal"), constructor->GetFunction()); } diff --git a/src/stream_wrap.cc b/src/stream_wrap.cc index 026e204929c..db33b6acbfa 100644 --- a/src/stream_wrap.cc +++ b/src/stream_wrap.cc @@ -60,7 +60,10 @@ StreamWrap::StreamWrap(Environment* env, Local object, uv_stream_t* stream, AsyncWrap::ProviderType provider) - : HandleWrap(env, object, reinterpret_cast(stream), provider), + : HandleWrap(env, + object, + reinterpret_cast(stream), + provider), stream_(stream), default_callbacks_(this), callbacks_(&default_callbacks_) { @@ -126,7 +129,11 @@ static Local AcceptHandle(Environment* env, uv_stream_t* pipe) { Local wrap_obj; UVType* handle; + WrapType* parent = static_cast(pipe->data); + env->set_async_wrap_parent_class(parent); wrap_obj = WrapType::Instantiate(env); + env->reset_async_wrap_parent_class(); + if (wrap_obj.IsEmpty()) return Local(); @@ -219,7 +226,8 @@ void StreamWrap::WriteBuffer(const FunctionCallbackInfo& args) { // Allocate, or write rest storage = new char[sizeof(WriteWrap)]; - req_wrap = new(storage) WriteWrap(env, req_wrap_obj, wrap); + req_wrap = + new(storage) WriteWrap(env, req_wrap_obj, wrap, wrap->provider_type()); err = wrap->callbacks()->DoWrite(req_wrap, bufs, @@ -307,7 +315,8 @@ void StreamWrap::WriteStringImpl(const FunctionCallbackInfo& args) { } storage = new char[sizeof(WriteWrap) + storage_size + 15]; - req_wrap = new(storage) WriteWrap(env, req_wrap_obj, wrap); + req_wrap = + new(storage) WriteWrap(env, req_wrap_obj, wrap, wrap->provider_type()); data = reinterpret_cast(ROUND_UP( reinterpret_cast(storage) + sizeof(WriteWrap), 16)); @@ -422,7 +431,7 @@ void StreamWrap::Writev(const FunctionCallbackInfo& args) { storage_size += sizeof(WriteWrap); char* storage = new char[storage_size]; WriteWrap* req_wrap = - new(storage) WriteWrap(env, req_wrap_obj, wrap); + new(storage) WriteWrap(env, req_wrap_obj, wrap, wrap->provider_type()); uint32_t bytes = 0; size_t offset = sizeof(WriteWrap); diff --git a/src/stream_wrap.h b/src/stream_wrap.h index 3a7f6f86383..9ef152fd538 100644 --- a/src/stream_wrap.h +++ b/src/stream_wrap.h @@ -37,10 +37,11 @@ typedef class ReqWrap ShutdownWrap; class WriteWrap: public ReqWrap { public: - // TODO(trevnorris): WrapWrap inherits from ReqWrap, which I've globbed - // into the same provider. How should these be broken apart? - WriteWrap(Environment* env, v8::Local obj, StreamWrap* wrap) - : ReqWrap(env, obj), + WriteWrap(Environment* env, + v8::Local obj, + StreamWrap* wrap, + AsyncWrap::ProviderType provider) + : ReqWrap(env, obj, provider), wrap_(wrap) { } diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index ecaffef8035..b52592d7a91 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -117,6 +117,8 @@ void TCPWrap::Initialize(Handle target, SetSimultaneousAccepts); #endif + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "TCP"), t->GetFunction()); env->set_tcp_constructor_template(t); } @@ -325,8 +327,10 @@ void TCPWrap::OnConnection(uv_stream_t* handle, int status) { }; if (status == 0) { + env->set_async_wrap_parent_class(tcp_wrap); // Instantiate the client javascript object and handle. Local client_obj = Instantiate(env); + env->reset_async_wrap_parent_class(); // Unwrap the client javascript object. TCPWrap* wrap = Unwrap(client_obj); @@ -388,9 +392,11 @@ void TCPWrap::Connect(const FunctionCallbackInfo& args) { int err = uv_ip4_addr(*ip_address, port, &addr); if (err == 0) { + env->set_async_wrap_parent_class(wrap); ConnectWrap* req_wrap = new ConnectWrap(env, req_wrap_obj, - AsyncWrap::PROVIDER_CONNECTWRAP); + AsyncWrap::PROVIDER_TCPWRAP); + env->reset_async_wrap_parent_class(); err = uv_tcp_connect(&req_wrap->req_, &wrap->handle_, reinterpret_cast(&addr), @@ -422,9 +428,11 @@ void TCPWrap::Connect6(const FunctionCallbackInfo& args) { int err = uv_ip6_addr(*ip_address, port, &addr); if (err == 0) { + env->set_async_wrap_parent_class(wrap); ConnectWrap* req_wrap = new ConnectWrap(env, req_wrap_obj, - AsyncWrap::PROVIDER_CONNECTWRAP); + AsyncWrap::PROVIDER_TCPWRAP); + env->reset_async_wrap_parent_class(); err = uv_tcp_connect(&req_wrap->req_, &wrap->handle_, reinterpret_cast(&addr), diff --git a/src/timer_wrap.cc b/src/timer_wrap.cc index 099a54ec95d..292710ba2e1 100644 --- a/src/timer_wrap.cc +++ b/src/timer_wrap.cc @@ -71,6 +71,8 @@ class TimerWrap : public HandleWrap { target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Timer"), constructor->GetFunction()); + + AsyncWrap::AddMethods(constructor); } private: diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 803bc4edcf7..64efe49e62c 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -830,6 +830,8 @@ void TLSCallbacks::Initialize(Handle target, NODE_SET_PROTOTYPE_METHOD(t, "setServername", SetServername); #endif // SSL_CRT_SET_TLSEXT_SERVERNAME_CB + AsyncWrap::AddMethods(t); + env->set_tls_wrap_constructor_function(t->GetFunction()); } diff --git a/src/tty_wrap.cc b/src/tty_wrap.cc index dba545b5f45..d233b1ea3b1 100644 --- a/src/tty_wrap.cc +++ b/src/tty_wrap.cc @@ -85,6 +85,8 @@ void TTYWrap::Initialize(Handle target, NODE_SET_METHOD(target, "isTTY", IsTTY); NODE_SET_METHOD(target, "guessHandleType", GuessHandleType); + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "TTY"), t->GetFunction()); env->set_tty_constructor_template(t); } diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index b392e357851..2921f0afd58 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -62,7 +62,7 @@ class SendWrap : public ReqWrap { SendWrap::SendWrap(Environment* env, Local req_wrap_obj, bool have_callback) - : ReqWrap(env, req_wrap_obj), + : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_UDPWRAP), have_callback_(have_callback) { } @@ -122,6 +122,8 @@ void UDPWrap::Initialize(Handle target, NODE_SET_PROTOTYPE_METHOD(t, "ref", HandleWrap::Ref); NODE_SET_PROTOTYPE_METHOD(t, "unref", HandleWrap::Unref); + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "UDP"), t->GetFunction()); env->set_udp_constructor_function(t->GetFunction()); } From 14f6f5a2ee4dd52b81313bde713e7259a68e63e2 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 26 Mar 2014 16:42:30 -0700 Subject: [PATCH 2/5] asynclistener: update to match AsyncWrap changes * Pass if the error was handled as the fourth argument to the error callback handler. * Move all properties to single _async object. * Only run callbacks for watched providers. * Each asynchronous call chain is independent and the listener can be removed at any time for that specific branch in the call stack. * Each AsyncListener tracks its own watched providers. These are accumulated on _async.watchedProviders for quick checks when the run/load/unload functions are called. * When an AsyncListener is removed, it's removed from the activeContext and from every context in the contextStack. --- lib/timers.js | 32 +-- lib/tracing.js | 585 ++++++++++++++++++++++++++++++------------------- src/node.js | 34 ++- 3 files changed, 397 insertions(+), 254 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index 3039b49f2c3..d5a16e51125 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -37,7 +37,12 @@ var loadAsyncQueue = tracing._loadAsyncQueue; var unloadAsyncQueue = tracing._unloadAsyncQueue; // Same as in AsyncListener in env.h -var kHasListener = 0; +var kActiveAsyncContextType = 0; +var kActiveAsyncQueueLength = 1; +var kWatchedProviders = 2; +var kInAsyncTick = 3; + +var PROVIDER_TIMER = tracing.ASYNC_PROVIDERS.TIMER; // Do a little housekeeping. delete tracing._asyncFlags; @@ -59,11 +64,6 @@ delete tracing._unloadAsyncQueue; // value = list var lists = {}; -// Make Timer as monomorphic as possible. -Timer.prototype._asyncQueue = undefined; -Timer.prototype._asyncData = undefined; -Timer.prototype._asyncFlags = 0; - // the main function - creates lists on demand and the watchers associated // with them. function insert(item, msecs) { @@ -122,7 +122,7 @@ function listOnTimeout() { if (domain && domain._disposed) continue; - hasQueue = !!first._asyncQueue; + hasQueue = first._async && first._async.queue.length > 0; try { if (hasQueue) @@ -207,8 +207,9 @@ exports.active = function(item) { // Whether or not a new TimerWrap needed to be created, this should run // for each item. This way each "item" (i.e. timer) can properly have // their own domain assigned. - if (asyncFlags[kHasListener] > 0) - runAsyncQueue(item); + if (asyncFlags[kInAsyncTick] === 0 && + asyncFlags[kActiveAsyncQueueLength] > 0) + runAsyncQueue(item, PROVIDER_TIMER); }; @@ -361,7 +362,7 @@ function processImmediate() { while (L.isEmpty(queue) === false) { immediate = L.shift(queue); - hasQueue = !!immediate._asyncQueue; + hasQueue = immediate._async && immediate._async.queue.length > 0; domain = immediate.domain; if (hasQueue) @@ -406,11 +407,9 @@ function Immediate() { } Immediate.prototype.domain = undefined; Immediate.prototype._onImmediate = undefined; -Immediate.prototype._asyncQueue = undefined; -Immediate.prototype._asyncData = undefined; Immediate.prototype._idleNext = undefined; Immediate.prototype._idlePrev = undefined; -Immediate.prototype._asyncFlags = 0; +Immediate.prototype._async = undefined; exports.setImmediate = function(callback) { @@ -437,8 +436,9 @@ exports.setImmediate = function(callback) { } // setImmediates are handled more like nextTicks. - if (asyncFlags[kHasListener] > 0) - runAsyncQueue(immediate); + if (asyncFlags[kInAsyncTick] === 0 && + asyncFlags[kActiveAsyncQueueLength] > 0) + runAsyncQueue(immediate, PROVIDER_TIMER); if (process.domain) immediate.domain = process.domain; @@ -490,7 +490,7 @@ function unrefTimeout() { if (!first._onTimeout) continue; if (domain && domain._disposed) continue; - hasQueue = !!first._asyncQueue; + hasQueue = first._async && first._async.queue.length > 0; try { if (hasQueue) diff --git a/lib/tracing.js b/lib/tracing.js index 49d0dec35c2..368a092ec14 100644 --- a/lib/tracing.js +++ b/lib/tracing.js @@ -68,48 +68,91 @@ v8.on('removeListener', function(name) { }); -// AsyncListener - -// new Array() is used here because it is more efficient for sparse -// arrays. Please *do not* change these to simple bracket notation. - -// Track the active queue of AsyncListeners that have been added. -var asyncQueue = new Array(); - -// Keep the stack of all contexts that have been loaded in the -// execution chain of asynchronous events. -var contextStack = new Array(); -var currentContext = undefined; - -// Incremental uid for new AsyncListener instances. +/* --- AsyncListener --- */ + +// Keep the stack of all contexts that have been loaded in the execution +// chain of asynchronous events. +var contextStack = []; +// The context (i.e. "this") that has been loaded before the user-defined +// callback is fired. +var activeContext; + +// Incremental uid for new AsyncListener instances. The uid is used as a +// unique storage location for any data returned from an AsyncListener +// to a given context. Which is stored on the _async.data property. var alUid = 0; -// Stateful flags shared with Environment for quick JS/C++ -// communication. +// Stateful flags shared with Environment for quick JS/C++ communication. +// This object communicates the following things: +// +// - kActiveAsyncContextType: The ASYNC_PROVIDERS type of the activeContext. +// +// - kActiveAsyncQueueLength: Length of _async.queue on activeContext. +// +// - kWatchedProviders: Bitmasks specifying which ASYNC_PROVIDERS are +// currently being listened for. This value is accumulated on the +// activeContext._async.watchedProviders property. +// +// - kInAsyncTick: Whether callbacks from create/before/after are running. var asyncFlags = {}; -// Prevent accidentally suppressed thrown errors from before/after. -var inAsyncTick = false; +// Must be the same as Environment::AsyncListener::Fields in src/env.h. +var kActiveAsyncContextType = 0; +var kActiveAsyncQueueLength = 1; +var kWatchedProviders = 2; +var kInAsyncTick = 3; -// To prevent infinite recursion when an error handler also throws -// flag when an error is currenly being handled. +// To prevent infinite recursion when an error handler also throws. var inErrorTick = false; -// Needs to be the same as src/env.h -var kHasListener = 0; +// Flags to determine what AsyncListener callbacks are available. These +// are set on individual AsyncListener instances, then accumulated on the +// context object for quick check in each load/unload/error phase. +var HAS_NO_CB = 0; +var HAS_CREATE_CB = 1 << 0; +var HAS_BEFORE_CB = 1 << 1; +var HAS_AFTER_CB = 1 << 2; +var HAS_ERROR_CB = 1 << 3; + +// Providers that users can watch for. The ASYNC_PROVIDERS names have been +// simplified from what's located in src/async-wrap.h. +var ASYNC_PROVIDERS = { + // NEXTTICK is not located in async-wrap.h because it is a JavaScript + // exclusive execution context. This will always fire on all callbacks + // because it is currently impossible to determine the appropriate + // provider for the nextTick() call site. + NEXTTICK: -1, + NONE: 0, + CRYPTO: 1 << 0, + FSEVENT: 1 << 1, + FS: 1 << 2, + GETADDRINFO: 1 << 3, + PIPE: 1 << 4, + PROCESS: 1 << 5, + QUERY: 1 << 6, + SHUTDOWN: 1 << 7, + SIGNAL: 1 << 8, + STATWATCHER: 1 << 9, + TCP: 1 << 10, + TIMER: 1 << 11, + TLS: 1 << 12, + TTY: 1 << 13, + UDP: 1 << 14, + ZLIB: 1 << 15 +}; -// Flags to determine what async listeners are available. -var HAS_CREATE_AL = 1 << 0; -var HAS_BEFORE_AL = 1 << 1; -var HAS_AFTER_AL = 1 << 2; -var HAS_ERROR_AL = 1 << 3; +// Build a named map for all providers that are passed to create(). +var PROVIDER_MAP = {}; +for (var i in ASYNC_PROVIDERS) + PROVIDER_MAP[ASYNC_PROVIDERS[i]] = i; // _errorHandler is scoped so it's also accessible by _fatalException. +// This will be removed once a reference there is made. exports._errorHandler = errorHandler; -// Needs to be accessible from lib/timers.js so they know when async -// listeners are currently in queue. They'll be cleaned up once -// references there are made. +// Needs to be accessible from src/node.js and lib/timers.js so they know +// when async listeners are currently in queue. They'll be cleaned up once +// references are made. exports._asyncFlags = asyncFlags; exports._runAsyncQueue = runAsyncQueue; exports._loadAsyncQueue = loadAsyncQueue; @@ -119,277 +162,365 @@ exports._unloadAsyncQueue = unloadAsyncQueue; exports.createAsyncListener = createAsyncListener; exports.addAsyncListener = addAsyncListener; exports.removeAsyncListener = removeAsyncListener; - -// Load the currently executing context as the current context, and -// create a new asyncQueue that can receive any added queue items -// during the executing of the callback. -function loadContext(ctx) { - contextStack.push(currentContext); - currentContext = ctx; - - asyncFlags[kHasListener] = 1; +exports.ASYNC_PROVIDERS = ASYNC_PROVIDERS; + + +function resetGlobalContext() { + // All the values here are explained in runAsyncQueue(). + activeContext = { + _async: { + // data is a sparse array. Do NOT change to basic bracket notation. + data: new Array(), + queue: [], + watchedProviders: ASYNC_PROVIDERS.NONE, + providerType: ASYNC_PROVIDERS.NONE, + callbackFlags: HAS_NO_CB + } + }; } +// Initialize the global context. +resetGlobalContext(); -function unloadContext() { - currentContext = contextStack.pop(); - - if (currentContext === undefined && asyncQueue.length === 0) - asyncFlags[kHasListener] = 0; -} // Run all the async listeners attached when an asynchronous event is // instantiated. -function runAsyncQueue(context) { - var queue = new Array(); +function runAsyncQueue(ctx, provider) { + // data is a sparse array. Do NOT change to basic bracket notation. var data = new Array(); - var ccQueue, i, queueItem, value; - - context._asyncQueue = queue; - context._asyncData = data; - context._asyncFlags = 0; - - inAsyncTick = true; - - // First run through all callbacks in the currentContext. These may - // add new AsyncListeners to the asyncQueue during execution. Hence - // why they need to be evaluated first. - if (currentContext) { - ccQueue = currentContext._asyncQueue; - context._asyncFlags |= currentContext._asyncFlags; - for (i = 0; i < ccQueue.length; i++) { - queueItem = ccQueue[i]; - queue[queue.length] = queueItem; - if ((queueItem.callback_flags & HAS_CREATE_AL) === 0) { - data[queueItem.uid] = queueItem.data; - continue; - } - value = queueItem.create(queueItem.data); - data[queueItem.uid] = (value === undefined) ? queueItem.data : value; - } - } - - // Then run through all items in the asyncQueue - if (asyncQueue) { - for (i = 0; i < asyncQueue.length; i++) { - queueItem = asyncQueue[i]; - // Quick way to check if an AL instance with the same uid was - // already run from currentContext. - if (data[queueItem.uid] !== undefined) - continue; - queue[queue.length] = queueItem; - context._asyncFlags |= queueItem.callback_flags; - if ((queueItem.callback_flags & HAS_CREATE_AL) === 0) { - data[queueItem.uid] = queueItem.data; - continue; - } - value = queueItem.create(queueItem.data); - data[queueItem.uid] = (value === undefined) ? queueItem.data : value; + var queue = []; + var ctxQueue = {}; + var providerType = PROVIDER_MAP[provider]; + var acAsync = activeContext._async; + var acQueue = acAsync.queue; + var i, item, qItem, value; + + // Set the single _async object that contains all the AL data. + ctx._async = ctxQueue; + + // Array of all AsyncListeners attached to this context. + ctxQueue.queue = queue; + // Object containing passed or returned storageData. The storage index + // is the same as the AsyncListener instance's uid. If the AsyncListener + // was set then the value will at least be null. Which means a quick + // check can be made to see if the AsyncListener exists by checking if + // the value is undefined. + ctxQueue.data = data; + // Attach the numeric ASYNC_PROVIDERS type to set kActiveAsyncContextType + // when an activeContext is loaded. + ctxQueue.provider = provider; + // Specify flags identifying the cumulate of callbacks for all + // AsyncListeners in the attached _asyncQueue. + ctxQueue.callbackFlags = acAsync.callbackFlags; + // The cumulate of all watched providers from all AsyncListeners in + // the _asyncQueue. + ctxQueue.watchedProviders = 0; + + asyncFlags[kInAsyncTick] = 1; + + // Regardless whether this context's provider type is being listened for + // or not, always iterate through the loop and propagate the listener. + for (i = 0; i < acQueue.length; i++) { + qItem = acQueue[i]; + queue.push(qItem); + ctxQueue.watchedProviders |= qItem.watched_providers; + // Check if the qItem has a create() callback. + if ((qItem.callback_flags & HAS_CREATE_CB) === 0 || + // Check if this provider is being watched. + (provider & qItem.watched_providers) === 0) { + data[qItem.uid] = qItem.data; + continue; } + // Run the create() callback and overwrite the default userData if + // a value was returned. + value = qItem.create(qItem.data, providerType); + data[qItem.uid] = (value === undefined) ? qItem.data : value; } - inAsyncTick = false; + asyncFlags[kInAsyncTick] = 0; } -// Load the AsyncListener queue attached to context and run all -// "before" callbacks, if they exist. -function loadAsyncQueue(context) { - loadContext(context); - if ((context._asyncFlags & HAS_BEFORE_AL) === 0) +// Add the passed context to the contextStack. +function loadAsyncQueue(ctx) { + var async = ctx._async; + + // There are specific cases where changing the async_flags_ value is + // currently impossible (e.g. TimerWrap). In those cases go ahead and + // return early if this was called with nothing in _async.queue. + if (async.queue.length === 0) return; - var queue = context._asyncQueue; - var data = context._asyncData; - var i, queueItem; + loadContext(ctx); + + // Check if this provider type is being watched or if there are any + // before() callbacks. + if ((async.provider & async.watchedProviders) === 0 || + (async.callbackFlags & HAS_BEFORE_CB) === 0) + return; - inAsyncTick = true; + var queue = async.queue; + var data = async.data; + var i, qItem; + + asyncFlags[kInAsyncTick] = 1; for (i = 0; i < queue.length; i++) { - queueItem = queue[i]; - if ((queueItem.callback_flags & HAS_BEFORE_AL) > 0) - queueItem.before(context, data[queueItem.uid]); + qItem = queue[i]; + // Check if this provider type is being watched or if it has any + // before() callbacks. + if ((async.provider & qItem.watched_providers) !== 0 && + (qItem.callback_flags & HAS_BEFORE_CB) !== 0) + qItem.before(ctx, data[qItem.uid]); } - inAsyncTick = false; + asyncFlags[kInAsyncTick] = 0; +} + + +// Load the passed context as the new activeContext, and place the +// current activeContext in the contextStack. +function loadContext(ctx) { + var async = ctx._async; + + contextStack.push(activeContext); + activeContext = ctx; + + asyncFlags[kActiveAsyncContextType] = async.provider; + asyncFlags[kWatchedProviders] = async.watchedProviders; + asyncFlags[kActiveAsyncQueueLength] = async.queue.length; } -// Unload the AsyncListener queue attached to context and run all -// "after" callbacks, if they exist. -function unloadAsyncQueue(context) { - if ((context._asyncFlags & HAS_AFTER_AL) === 0) { + +// Remove the passed context from the contextStack. +function unloadAsyncQueue(ctx) { + var async = ctx._async; + + // Check if this provider type is being watched or if there are any + // after() callbacks. There is also the case where all items in the + // async.queue were removed, but that will be handled by these checks. + if ((async.provider & async.watchedProviders) === 0 || + // Check if any AsyncListeners have an after() callback. + (async.callbackFlags & HAS_AFTER_CB) === 0) { unloadContext(); return; } - var queue = context._asyncQueue; - var data = context._asyncData; - var i, queueItem; + var queue = async.queue; + var data = async.data; + var i, qItem; - inAsyncTick = true; + asyncFlags[kInAsyncTick] = 1; for (i = 0; i < queue.length; i++) { - queueItem = queue[i]; - if ((queueItem.callback_flags & HAS_AFTER_AL) > 0) - queueItem.after(context, data[queueItem.uid]); + qItem = queue[i]; + // Check if this provider type is being watched or if it has any + // after() callbacks. + if ((async.provider & qItem.watched_providers) !== 0 && + (qItem.callback_flags & HAS_AFTER_CB) !== 0) + qItem.after(ctx, data[qItem.uid]); } - inAsyncTick = false; + asyncFlags[kInAsyncTick] = 0; unloadContext(); } -// Handle errors that are thrown while in the context of an -// AsyncListener. If an error is thrown from an AsyncListener -// callback error handlers will be called once more to report -// the error, then the application will die forcefully. + +// Unload an activeContext after callbacks have run. +function unloadContext() { + // If the contextStack has nothing in it then the activeContext is the + // global context. It should then be reset back to initial state. + if (contextStack.length > 0) + activeContext = contextStack.pop(); + else + resetGlobalContext(); + + var async = activeContext._async; + + asyncFlags[kActiveAsyncContextType] = async.provider; + asyncFlags[kActiveAsyncQueueLength] = async.queue.length; + asyncFlags[kWatchedProviders] = async.watchedProviders; +} + + +// This will always be called first from _fatalException. If the activeContext +// has any error handlers then trigger those and check if "true" was +// returned to indicate the error was handled. +// +// The error() callback is unique because it will always fire in any tracked +// call stack regardless whether the provider of the context matches the +// providers being watched. function errorHandler(er) { - if (inErrorTick) + var async = activeContext._async; + + if (inErrorTick || (async.callbackFlags & HAS_ERROR_CB) === 0) return false; + // Cache whether the error was thrown while processing AL callbacks. Then + // set that we are so the AsyncWrap constructor will return early and not + // cause infinite loops. + var inAsyncTick = asyncFlags[kInAsyncTick]; var handled = false; - var i, queueItem, threw; + var data = async.data; + var queue = async.queue; + var i, qItem, threw; - inErrorTick = true; + asyncFlags[kInAsyncTick] = 1; - // First process error callbacks from the current context. - if (currentContext && (currentContext._asyncFlags & HAS_ERROR_AL) > 0) { - var queue = currentContext._asyncQueue; - var data = currentContext._asyncData; - for (i = 0; i < queue.length; i++) { - queueItem = queue[i]; - if ((queueItem.callback_flags & HAS_ERROR_AL) === 0) - continue; - try { - threw = true; - // While it would be possible to pass in currentContext, if - // the error is thrown from the "create" callback then there's - // a chance the object hasn't been fully constructed. - handled = queueItem.error(data[queueItem.uid], er) || handled; - threw = false; - } finally { - // If the error callback thew then die quickly. Only allow the - // exit events to be processed. - if (threw) { - process._exiting = true; - process.emit('exit', 1); - } - } - } - } - - // Now process callbacks from any existing queue. - if (asyncQueue) { - for (i = 0; i < asyncQueue.length; i++) { - queueItem = asyncQueue[i]; - if ((queueItem.callback_flags & HAS_ERROR_AL) === 0 || - (data && data[queueItem.uid] !== undefined)) - continue; - try { - threw = true; - handled = queueItem.error(queueItem.data, er) || handled; - threw = false; - } finally { - // If the error callback thew then die quickly. Only allow the - // exit events to be processed. - if (threw) { - process._exiting = true; - process.emit('exit', 1); - } + inErrorTick = true; + for (i = 0; i < queue.length; i++) { + qItem = queue[i]; + // Check if the AsyncListener has an error callback. + if ((qItem.callback_flags & HAS_ERROR_CB) === 0) + continue; + try { + threw = true; + handled = + qItem.error(activeContext, data[qItem.uid], er, handled) || handled; + threw = false; + } finally { + // Die quickly if the error callback threw. Only allow exit events + // to be processed. + if (threw) { + process._exiting = true; + process.emit('exit', 1); } } } - inErrorTick = false; + asyncFlags[kInAsyncTick] = inAsyncTick; unloadContext(); - // TODO(trevnorris): If the error was handled, should the after callbacks - // be fired anyways? - - return handled && !inAsyncTick; + return handled && inAsyncTick === 0; } + // Instance function of an AsyncListener object. -function AsyncListenerInst(callbacks, data) { +function AsyncListener(callbacks, data, provider) { if (typeof callbacks.create === 'function') { this.create = callbacks.create; - this.callback_flags |= HAS_CREATE_AL; + this.callback_flags |= HAS_CREATE_CB; } if (typeof callbacks.before === 'function') { this.before = callbacks.before; - this.callback_flags |= HAS_BEFORE_AL; + this.callback_flags |= HAS_BEFORE_CB; } if (typeof callbacks.after === 'function') { this.after = callbacks.after; - this.callback_flags |= HAS_AFTER_AL; + this.callback_flags |= HAS_AFTER_CB; } if (typeof callbacks.error === 'function') { this.error = callbacks.error; - this.callback_flags |= HAS_ERROR_AL; + this.callback_flags |= HAS_ERROR_CB; } this.uid = ++alUid; this.data = data === undefined ? null : data; + this.watched_providers = provider === undefined ? 0xfffffff : provider >>> 0; } -AsyncListenerInst.prototype.create = undefined; -AsyncListenerInst.prototype.before = undefined; -AsyncListenerInst.prototype.after = undefined; -AsyncListenerInst.prototype.error = undefined; -AsyncListenerInst.prototype.data = undefined; -AsyncListenerInst.prototype.uid = 0; -AsyncListenerInst.prototype.callback_flags = 0; - -// Create new async listener object. Useful when instantiating a new -// object and want the listener instance, but not add it to the stack. -// If an existing AsyncListenerInst is passed then any new "data" is -// ignored. -function createAsyncListener(callbacks, data) { - if (typeof callbacks !== 'object' || callbacks == null) - throw new TypeError('callbacks argument must be an object'); - - if (callbacks instanceof AsyncListenerInst) +// Not sure which callbacks will be set, so pre-define all of them. +AsyncListener.prototype.create = undefined; +AsyncListener.prototype.before = undefined; +AsyncListener.prototype.after = undefined; +AsyncListener.prototype.error = undefined; +// Track if this instance has create/before/after/error callbacks. +AsyncListener.prototype.callback_flags = HAS_NO_CB; + + +// Create new AsyncListener, but don't add it to the activeContext's +// _asyncQueue. +// +// TODO(trevnorris): If an AL is passed, should a new instance be created +// with the new data and provider information? +function createAsyncListener(callbacks, data, providers) { + if (callbacks instanceof AsyncListener) return callbacks; - else - return new AsyncListenerInst(callbacks, data); + + if (typeof callbacks !== 'object' || callbacks === null) + throw new TypeError('Missing expected callbacks object'); + + return new AsyncListener(callbacks, data, providers); } -// Add a listener to the current queue. -function addAsyncListener(callbacks, data) { - // Fast track if a new AsyncListenerInst has to be created. - if (!(callbacks instanceof AsyncListenerInst)) { - callbacks = createAsyncListener(callbacks, data); - asyncQueue.push(callbacks); - asyncFlags[kHasListener] = 1; - return callbacks; - } - var inQueue = false; - // The asyncQueue will be small. Probably always <= 3 items. - for (var i = 0; i < asyncQueue.length; i++) { - if (callbacks === asyncQueue[i]) { - inQueue = true; - break; - } +// Add a listener to the activeContext. +function addAsyncListener(callbacks, data, providers) { + if (!(callbacks instanceof AsyncListener)) + callbacks = createAsyncListener(callbacks, data, providers); + + // Check if the AsyncListener already exists in the stack. + for (var i = 0; i < contextStack.length; i++) { + if (contextStack[i]._async.data[callbacks.uid] !== undefined) + return; } - // Make sure the callback doesn't already exist in the queue. - if (!inQueue) { - asyncQueue.push(callbacks); - asyncFlags[kHasListener] = 1; + // userData values === undefiend mean the AsyncListener does not exist + // in the _asyncQueue. + if (activeContext._async.data[callbacks.uid] === undefined) { + addListenerQueueItem(callbacks); + + // Update Environment::AsyncListener flags. + asyncFlags[kWatchedProviders] = activeContext._async.watchedProviders; + asyncFlags[kActiveAsyncQueueLength] = activeContext._async.queue.length; } return callbacks; } -// Remove listener from the current queue. Though this will not remove -// the listener from the current context. So callback propagation will -// continue. -function removeAsyncListener(obj) { - for (var i = 0; i < asyncQueue.length; i++) { - if (obj === asyncQueue[i]) { - asyncQueue.splice(i, 1); - break; + +function addListenerQueueItem(al) { + activeContext._async.queue.push(al); + activeContext._async.data[al.uid] = al.data; + activeContext._async.callbackFlags |= al.callback_flags; + activeContext._async.watchedProviders |= al.watched_providers; +} + + +// Remove the AsyncListener from the stack. +function removeAsyncListener(al) { + if (!(al instanceof AsyncListener)) + throw new TypeError('argument should be instance of AsyncListener'); + + removeAL(activeContext, al); + + // Update Environment::AsyncListener flags. + asyncFlags[kWatchedProviders] = activeContext._async.watchedProviders; + asyncFlags[kActiveAsyncQueueLength] = activeContext._async.queue.length; + + var cslen = contextStack.length; + if (cslen === 0) + return; + + // Remove the AsyncListener from all contexts in the current stack. + for (var i = 0; i < cslen; i++) + removeAL(contextStack[i], al); +} + + +function removeAL(ctx, al) { + var async = ctx._async; + + // Return early if the AsyncListener doesn't exist in this context. + if (async.data[al.uid] === undefined) + return; + + var data = async.data; + var queue = async.queue; + var i, tmp; + + async.callbackFlags = HAS_NO_CB; + async.watchedProviders = ASYNC_PROVIDERS.NONE; + for (i = 0; i < queue.length; i++) { + if (queue[i] === al) { + tmp = queue.splice(i, 1)[0]; + data[tmp.uid] = undefined; + i--; + } else { + async.callbackFlags |= queue[i].callback_flags; + async.watchedProviders |= queue[i].watched_providers; } } - if (asyncQueue.length > 0 || currentContext !== undefined) - asyncFlags[kHasListener] = 1; - else - asyncFlags[kHasListener] = 0; + // Set async_flags_ = NO_OPTIONS in C++ where possible. In some locations + // it is impossible to reach the actual context because it is abstracted + // too far (e.g. TimerWrap). + if (queue.length === 0 && typeof ctx._removeAsyncQueue === 'function') + ctx._removeAsyncQueue(); } diff --git a/src/node.js b/src/node.js index 6f8e53704a6..ccd099bab72 100644 --- a/src/node.js +++ b/src/node.js @@ -253,8 +253,8 @@ var t = setImmediate(process._tickCallback); // Complete hack to make sure any errors thrown from async // listeners don't cause an infinite loop. - if (t._asyncQueue) - t._asyncQueue = []; + if (t._async && t._async.queue) + t._async.queue = []; } return caught; @@ -289,12 +289,21 @@ startup.processNextTick = function() { var tracing = NativeModule.require('tracing'); + var PROVIDER_NEXTTICK = tracing.ASYNC_PROVIDERS.NEXTTICK; var nextTickQueue = []; + var asyncFlags = tracing._asyncFlags; var _runAsyncQueue = tracing._runAsyncQueue; var _loadAsyncQueue = tracing._loadAsyncQueue; var _unloadAsyncQueue = tracing._unloadAsyncQueue; + // For AsyncListener. + // *Must* match Environment::AsyncListener::Fields in src/env.h. + var kActiveAsyncContextType = 0; + var kActiveAsyncQueueLength = 1; + var kWatchedProviders = 2; + var kInAsyncTick = 3; + // This tickInfo thing is used so that the C++ code in src/node.cc // can have easy accesss to our nextTick state, and avoid unnecessary var tickInfo = {}; @@ -303,10 +312,6 @@ var kIndex = 0; var kLength = 1; - // For asyncFlags. - // *Must* match Environment::AsyncListeners::Fields in src/env.h - var kCount = 0; - process.nextTick = nextTick; // Needs to be accessible from beyond this scope. process._tickCallback = _tickCallback; @@ -336,7 +341,7 @@ tock = nextTickQueue[tickInfo[kIndex]++]; callback = tock.callback; threw = true; - hasQueue = !!tock._asyncQueue; + hasQueue = tock._async && tock._async.queue.length > 0; if (hasQueue) _loadAsyncQueue(tock); try { @@ -362,7 +367,7 @@ tock = nextTickQueue[tickInfo[kIndex]++]; callback = tock.callback; domain = tock.domain; - hasQueue = !!tock._asyncQueue; + hasQueue = tock._async && tock._async.queue.length > 0; if (hasQueue) _loadAsyncQueue(tock); if (domain) @@ -394,11 +399,18 @@ var obj = { callback: callback, domain: process.domain || null, - _asyncQueue: undefined + _async: undefined }; - if (asyncFlags[kCount] > 0) - _runAsyncQueue(obj); + // The AsyncListener callbacks must be run for all nextTick() if + // there is an activeContext because currently there is no reliable + // way to determine what the source of the caller is. For cases + // when nextTick() is implicitly called from core code. + // + // TODO(trevnorris): Find way to reliably pass the caller's type. + if (asyncFlags[kInAsyncTick] === 0 && + asyncFlags[kActiveAsyncQueueLength] > 0) + _runAsyncQueue(obj, PROVIDER_NEXTTICK); nextTickQueue.push(obj); tickInfo[kLength]++; From a2bcea143cc07d4f467fe384bdffc226468b864a Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Thu, 27 Mar 2014 13:33:42 -0700 Subject: [PATCH 3/5] asynclistener: only run after if before could If an AsyncListener was added in the middle of an async execution chain then it was possible for the after callback to run w/o the before callback having had a chance to run. This make sure the before callback has a chance to run, or the after callback will be skipped. --- lib/tracing.js | 14 +++- test/simple/test-asynclistener-multi-added.js | 76 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 test/simple/test-asynclistener-multi-added.js diff --git a/lib/tracing.js b/lib/tracing.js index 368a092ec14..8649491d7ea 100644 --- a/lib/tracing.js +++ b/lib/tracing.js @@ -171,6 +171,7 @@ function resetGlobalContext() { _async: { // data is a sparse array. Do NOT change to basic bracket notation. data: new Array(), + beforeRan: new Array(), queue: [], watchedProviders: ASYNC_PROVIDERS.NONE, providerType: ASYNC_PROVIDERS.NONE, @@ -187,6 +188,7 @@ resetGlobalContext(); function runAsyncQueue(ctx, provider) { // data is a sparse array. Do NOT change to basic bracket notation. var data = new Array(); + var beforeRan = new Array(); var queue = []; var ctxQueue = {}; var providerType = PROVIDER_MAP[provider]; @@ -205,6 +207,8 @@ function runAsyncQueue(ctx, provider) { // check can be made to see if the AsyncListener exists by checking if // the value is undefined. ctxQueue.data = data; + // Make sure the before callback ran before calling the after callback. + ctxQueue.beforeRan = beforeRan; // Attach the numeric ASYNC_PROVIDERS type to set kActiveAsyncContextType // when an activeContext is loaded. ctxQueue.provider = provider; @@ -255,16 +259,19 @@ function loadAsyncQueue(ctx) { // Check if this provider type is being watched or if there are any // before() callbacks. if ((async.provider & async.watchedProviders) === 0 || - (async.callbackFlags & HAS_BEFORE_CB) === 0) + ((async.callbackFlags & HAS_BEFORE_CB) === 0 && + (async.callbackFlags & HAS_AFTER_CB) === 0)) return; var queue = async.queue; var data = async.data; + var beforeRan = async.beforeRan; var i, qItem; asyncFlags[kInAsyncTick] = 1; for (i = 0; i < queue.length; i++) { qItem = queue[i]; + beforeRan[qItem.uid] = true; // Check if this provider type is being watched or if it has any // before() callbacks. if ((async.provider & qItem.watched_providers) !== 0 && @@ -305,11 +312,14 @@ function unloadAsyncQueue(ctx) { var queue = async.queue; var data = async.data; + var beforeRan = async.beforeRan; var i, qItem; asyncFlags[kInAsyncTick] = 1; for (i = 0; i < queue.length; i++) { qItem = queue[i]; + if (beforeRan[qItem.uid] !== true) + continue; // Check if this provider type is being watched or if it has any // after() callbacks. if ((async.provider & qItem.watched_providers) !== 0 && @@ -503,6 +513,7 @@ function removeAL(ctx, al) { var data = async.data; var queue = async.queue; + var beforeRan = async.beforeRan; var i, tmp; async.callbackFlags = HAS_NO_CB; @@ -511,6 +522,7 @@ function removeAL(ctx, al) { if (queue[i] === al) { tmp = queue.splice(i, 1)[0]; data[tmp.uid] = undefined; + beforeRan[tmp.uid] = undefined; i--; } else { async.callbackFlags |= queue[i].callback_flags; diff --git a/test/simple/test-asynclistener-multi-added.js b/test/simple/test-asynclistener-multi-added.js new file mode 100644 index 00000000000..caacc716e5a --- /dev/null +++ b/test/simple/test-asynclistener-multi-added.js @@ -0,0 +1,76 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var tracing = require('tracing'); + +var queue = []; +var actual = 0; +var expected = 0; + +var addListener = tracing.addAsyncListener; +var removeListener = tracing.removeAsyncListener; + +var callbacks = { + create: function onCreate() { + return 'good'; + }, + before: function onBefore(ctx, stor) { }, + after: function onAfter(ctx, stor) { + assert.ok(stor !== 'bad'); + } +}; + +var listener = tracing.createAsyncListener(callbacks, 'bad'); + +process.on('beforeExit', function() { + if (queue.length > 0) + runQueue(); +}); + +process.on('exit', function(code) { + removeListener(listener); + + // Very important this code is checked, or real reason for failure may + // be obfuscated by a failing assert below. + if (code !== 0) + return; + + console.log('ok'); +}); + +setImmediate(function() { + addListener(listener); + + setImmediate(function() { + removeListener(listener); + addListener(listener); + setImmediate(function() { + removeListener(listener); + setImmediate(function() { + addListener(listener); + }); + }); + }); +}); + + From 3948bbc451b08b820aff8f8cc73da90b6cbfa0d3 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 26 Mar 2014 17:27:08 -0700 Subject: [PATCH 4/5] test/doc: add remaining changes for asynclistener --- doc/api/tracing.markdown | 279 +++++++++++------- ...js => test-asynclistener-error-context.js} | 70 +++-- ...st-asynclistener-error-multiple-handled.js | 2 +- .../test-asynclistener-error-multiple-mix.js | 4 +- ...-asynclistener-error-multiple-unhandled.js | 2 +- test/simple/test-asynclistener-error-net.js | 2 +- ...test-asynclistener-error-throw-in-after.js | 2 +- ...listener-error-throw-in-before-multiple.js | 4 +- ...est-asynclistener-error-throw-in-before.js | 2 +- test/simple/test-asynclistener-error.js | 16 +- .../test-asynclistener-multi-context.js | 217 ++++++++++++++ .../test-asynclistener-multi-timeout.js | 2 +- .../test-asynclistener-remove-in-before.js | 43 --- .../test-asynclistener-run-error-once.js | 2 +- test/simple/test-asynclistener.js | 60 ++-- 15 files changed, 480 insertions(+), 227 deletions(-) rename test/simple/{test-asynclistener-remove-add-in-before.js => test-asynclistener-error-context.js} (58%) create mode 100644 test/simple/test-asynclistener-multi-context.js delete mode 100644 test/simple/test-asynclistener-remove-in-before.js diff --git a/doc/api/tracing.markdown b/doc/api/tracing.markdown index 92e6809a04a..e3f4cad94b9 100644 --- a/doc/api/tracing.markdown +++ b/doc/api/tracing.markdown @@ -59,45 +59,41 @@ Returns an object with the following properties ``` -# Async Listeners +## Async Listeners The `AsyncListener` API is the JavaScript interface for the `AsyncWrap` -class which allows developers to be notified about key events in the +class. This allows developers to be notified about key events in the lifetime of an asynchronous event. Node performs a lot of asynchronous events internally, and significant use of this API may have a **significant performance impact** on your application. +**Note**: Running any of these functions within any `AsyncListener` +callback is *undefined behavior*. -## tracing.createAsyncListener(callbacksObj[, userData]) + +### tracing.createAsyncListener(callbacksObj[, userData[, provider]]) * `callbacksObj` {Object} Contains optional callbacks that will fire at specific times in the life cycle of the asynchronous event. -* `userData` {Value} a value that will be passed to all callbacks. +* `userData` {Value} a default value (i.e. primitive or object) that will be +passed to all callbacks. It can be overwritten by returning a new value from +the `create()` callback. +* `provider` {Number} Types of providers the user wishes to track. These are +provided by `tracing.ASYNC_PROVIDERS`. If the user wishes to pass in a custom +set of providers but no `userData`, then pass `null` to `userData`. Returns a constructed `AsyncListener` object. -To begin capturing asynchronous events pass either the `callbacksObj` or -pass an existing `AsyncListener` instance to [`tracing.addAsyncListener()`][]. -The same `AsyncListener` instance can only be added once to the active -queue, and subsequent attempts to add the instance will be ignored. - -To stop capturing pass the `AsyncListener` instance to -[`tracing.removeAsyncListener()`][]. This does _not_ mean the -`AsyncListener` previously added will stop triggering callbacks. Once -attached to an asynchronous event it will persist with the lifetime of the -asynchronous call stack. - Explanation of function parameters: +**callbacksObj**: An `Object` which may contain several optional fields: -`callbacksObj`: An `Object` which may contain several optional fields: - -* `create(userData)`: A `Function` called when an asynchronous -event is instantiated. If a `Value` is returned then it will be attached -to the event and overwrite any value that had been passed to -`tracing.createAsyncListener()`'s `userData` argument. If an initial -`userData` was passed when created, then `create()` will -receive that as a function argument. +* `create(userData, type)`: A `Function` called when an asynchronous +event is instantiated. Any `userData` value passed when the `AsyncListener` +was created will be passed as the first argument. If a value is returned then +it will overwrite the `userData` value for the specific instance in which it +was run. The `type` argument is the type of `ASYNC_PROVIDERS` where the call +originated. * `before(context, userData)`: A `Function` that is called immediately before the asynchronous callback is about to run. It will be passed both @@ -107,23 +103,31 @@ either occurred). * `after(context, userData)`: A `Function` called immediately after the asynchronous event's callback has run. Note this will not be called -if the callback throws and the error is not handled. - -* `error(userData, error)`: A `Function` called if the event's -callback threw. If this registered callback returns `true` then Node will -assume the error has been properly handled and resume execution normally. -When multiple `error()` callbacks have been registered only **one** of -those callbacks needs to return `true` for `AsyncListener` to accept that -the error has been handled, but all `error()` callbacks will always be run. - -`userData`: A `Value` (i.e. anything) that will be, by default, -attached to all new event instances. This will be overwritten if a `Value` -is returned by `create()`. +if the callback throws. + +* `error(context, userData, error, handled)`: A `Function` called if any call +threw within the synchronous execution stack of the originating asynchronous +callback. The `context` is that of the original asynchronous function. Not +that of the originating function in the case something threw within a +synchronous call stack. If the `error()` callback returns `true` then Node +will assume the error has been properly handled and resume execution normally. +When multiple `error()` callbacks have been registered only **one** of those +callbacks needs to return `true` for `AsyncListener` to accept that the +error has been handled, but all `error()` callbacks will always be run. +The `context` passed to `error()` is that of the originating function in +the call stack. `handled` is whether a previous AL has already returned that +the error has been handled. + +**userData**: A `Value` (i.e. anything) that will, by default, be attached +to all new event instances. This will be overwritten if a `Value` is +returned by `create()`. Here is an example of overwriting the `userData`: + var tracing = require('tracing'); + tracing.createAsyncListener({ - create: function listener(value) { + create: function listener(value, type) { // value === true return false; }, { @@ -132,44 +136,66 @@ Here is an example of overwriting the `userData`: } }, true); +**provider**: A provider, or combination of providers, from `ASYNC_PROVIDERS`. +Here's an example: + + var tracing = require('tracing'); + var tap = tracing.ASYNC_PROVIDERS; + + tracing.createAsyncListener(callbacksObj, null, tap.TCP | tap.PIPE); + +Currently callbacks will fire for all `NEXTTICK` providers. As it is currently +impossible to correctly determine their caller's provider. As such `NEXTTICK` +should never be passed as a provider type. + **Note:** The [EventEmitter][], while used to emit status of an asynchronous event, is not itself asynchronous. So `create()` will not fire when -an event is added, and `before()`/`after()` will not fire when emitted -callbacks are called. +an EventEmitter is instantiated, and `before()`/`after()` will not fire when +a callback is emitted. -## tracing.addAsyncListener(callbacksObj[, userData]) -## tracing.addAsyncListener(asyncListener) +### tracing.addAsyncListener(callbacksObj[, userData[, provider]]) +### tracing.addAsyncListener(asyncListener) -Returns a constructed `AsyncListener` object and immediately adds it to +Returns a constructed `AsyncListener` instance and immediately adds it to the listening queue to begin capturing asynchronous events. +To begin capturing asynchronous events pass either the `callbacksObj` or +pass an existing `AsyncListener` instance. The same `AsyncListener` instance +can only be added once to the active queue, and subsequent attempts to add +the instance will be ignored. If a previously instantiated `AsyncListener` +is passed than any value passed to `userData` or `provider` will be ignored. + +To stop capturing pass the returned `AsyncListener` instance to +[`tracing.removeAsyncListener()`][]. This does _not_ mean the +`AsyncListener` previously added will stop triggering callbacks. Once +attached to an asynchronous event it will persist with the lifetime of the +asynchronous call stack. + Function parameters can either be the same as -[`tracing.createAsyncListener()`][], or a constructed `AsyncListener` -object. +[`tracing.createAsyncListener()`][], or a constructed `AsyncListener`. Example usage for capturing errors: var fs = require('fs'); + var tracing = require('tracing'); var cntr = 0; var key = tracing.addAsyncListener({ create: function onCreate() { - return { uid: cntr++ }; + return cntr++; }, - before: function onBefore(context, storage) { - // Write directly to stdout or we'll enter a recursive loop - fs.writeSync(1, 'uid: ' + storage.uid + ' is about to run\n'); + before: function onBefore(context, uid) { + console.log('uid: ' + uid + ' is about to run\n'); }, - after: function onAfter(context, storage) { - fs.writeSync(1, 'uid: ' + storage.uid + ' ran\n'); + after: function onAfter(context, uid) { + console.log('uid: ' + uid + ' ran\n'); }, - error: function onError(storage, err) { + error: function onError(context, uid, err) { // Handle known errors if (err.message === 'everything is fine') { - // Writing to stderr this time. - fs.writeSync(2, 'handled error just threw:\n'); - fs.writeSync(2, err.stack + '\n'); + console.error('handled error just threw:\n'); + console.error(err.stack + '\n'); return true; } } @@ -182,29 +208,113 @@ Example usage for capturing errors: // Output: // uid: 0 is about to run // handled error just threw: - // Error: really, it's ok - // at /tmp/test2.js:27:9 - // at process._tickCallback (node.js:583:11) + // Error: everything is fine + // at /tmp/test.js:28:9 + // at process._tickCallback (node.js:339:11) // at Function.Module.runMain (module.js:492:11) - // at startup (node.js:123:16) - // at node.js:1012:3 + // at startup (node.js:124:16) + // at node.js:803:3 + +When a `ASYNC_PROVIDERS` is passed the callbacks will only be fired when that +provider type is triggered. Again with the exception that all `NEXTTICK` +providers will fire. Here is an example usage: + + var fs = require('fs'); + var tracing = require('tracing'); + var tap = tracing.ASYNC_PROVIDERS; + + tracing.addAsyncListener(tap.TIMER, { + create: function(stor, type) { } + }); + + // create() will fire for this setImmediate(). + setImmediate(function() { + // But won't fire for this fs operation. + fs.stat(__filename, function() { + // Even so, the stack has been kept in the background and + // create() will fire again for this setTimeout(). + setTimeout(function() { }); + }); + }); + +The `error()` callback is unique because it will still fire anywhere +within the call stack, regardless whether the error occurred while in +the stack of the specified provider. + + var fs = require('fs'); + var net = require('net'); + var tracing = require('tracing'); + + tracing.addAsyncListener(tracing.ASYNC_PROVIDERS.TCP, { + create: function(stor, type) { }, + error: function(context, stor, err) { + // Continue normally + return true; + } + }); -## tracing.removeAsyncListener(asyncListener) + // The TCP connections made from the net module will trigger + // any create/before/after callbacks. + net.createServer(function(c) { + // This FS operation will not. + fs.stat(__filename, function() { + // Regardless, this error will still be caught. + throw new Error('from fs'); + }); + }).listen(8080); + +If an `error()` callback throws then there is no allowed recovery. Any +callbacks placed in the [`process` exit event][] will still fire. Though +the user should be careful to check the exit status before doing much +because it's possible to override the actual reason for triggering the +exit. For example: + + var assert = require('assert'); + var tracing = require('tracing'); + + process.on('exit', function(status) { + // Need to check if the exit status is fine before continuing. + if (status > 0) + return; + + // Or else a somthing like this will not display why the code + // actually exited. + assert.ok(false); + }); + + tracing.addAsyncListener({ + error: function(context, data, err) { + // Check if we can recover from this error or not. + if (err.message === 'expected message') + return true; + } + }); + + process.nextTick(function() { + // This error will bubble through because it doesn't match + // the criterion necessary to recover properly. + throw new Error('random message'); + }); + +Note that the `NEXTTICK` `ASYNC_PROVIDERS` will always fire. Because of +the implicit way [`process.nextTick()`][] is called it is impossible to +properly determine the actual provider that initiated the call. + +### tracing.removeAsyncListener(asyncListener) Removes the `AsyncListener` from the listening queue. Removing the `AsyncListener` from the active queue does _not_ mean the -`asyncListener` callbacks will cease to fire on the events they've been -registered. Subsequently, any asynchronous events fired during the -execution of a callback will also have the same `asyncListener` callbacks -attached for future execution. For example: +callbacks will cease to fire on the events they've been registered. +Subsequently, any asynchronous events fired during the execution of a +callback will also have the same `AsyncListener` callbacks attached for +future execution. For example: - var fs = require('fs'); + var tracing = require('tracing'); var key = tracing.createAsyncListener({ - create: function asyncListener() { - // Write directly to stdout or we'll enter a recursive loop - fs.writeSync(1, 'You summoned me?\n'); + create: function() { + console.log('You summoned me?'); } }); @@ -233,41 +343,12 @@ attached for future execution. For example: The fact that we logged 4 asynchronous events is an implementation detail of Node's [Timers][]. -To stop capturing from a specific asynchronous event stack -`tracing.removeAsyncListener()` must be called from within the call -stack itself. For example: - - var fs = require('fs'); - - var key = tracing.createAsyncListener({ - create: function asyncListener() { - // Write directly to stdout or we'll enter a recursive loop - fs.writeSync(1, 'You summoned me?\n'); - } - }); - - // We want to begin capturing async events some time in the future. - setTimeout(function() { - tracing.addAsyncListener(key); - - // Perform a few additional async events. - setImmediate(function() { - // Stop capturing from this call stack. - tracing.removeAsyncListener(key); - - process.nextTick(function() { }); - }); - }, 100); - - // Output: - // You summoned me? - -The user must be explicit and always pass the `AsyncListener` they wish -to remove. It is not possible to simply remove all listeners at once. - [EventEmitter]: events.html#events_class_events_eventemitter [Timers]: timers.html [`tracing.createAsyncListener()`]: #tracing_tracing_createasynclistener_asynclistener_callbacksobj_storagevalue [`tracing.addAsyncListener()`]: #tracing_tracing_addasynclistener_asynclistener [`tracing.removeAsyncListener()`]: #tracing_tracing_removeasynclistener_asynclistener +[`process.nextTick()`]: process.html#process_process_nexttick_callback +[`util.inspect()`]: util.html#util_util_inspect_object_options +[`process` exit event]: process.html#process_event_exit diff --git a/test/simple/test-asynclistener-remove-add-in-before.js b/test/simple/test-asynclistener-error-context.js similarity index 58% rename from test/simple/test-asynclistener-remove-add-in-before.js rename to test/simple/test-asynclistener-error-context.js index af0bc78e0e8..c729da31c5a 100644 --- a/test/simple/test-asynclistener-remove-add-in-before.js +++ b/test/simple/test-asynclistener-error-context.js @@ -22,26 +22,62 @@ var common = require('../common'); var assert = require('assert'); var tracing = require('tracing'); -var val; -var callbacks = { - create: function() { - return 42; - }, - before: function() { - tracing.removeAsyncListener(listener); - tracing.addAsyncListener(listener); - }, - after: function(context, storage) { - val = storage; + +var tests = []; +var nor = 0; +var activeContext; + +var callbacksObj = { + error: function(ctx) { + process._rawDebug('Checking activeContext'); + assert.equal(ctx, activeContext); + setImmediate(next); + return true; } }; -var listener = tracing.addAsyncListener(callbacks); - -process.nextTick(function() {}); +function next() { + if (tests.length > 0) { + nor--; + tests.pop()(); + } +} +setImmediate(next); -process.on('exit', function(status) { +process.on('exit', function(code) { tracing.removeAsyncListener(listener); - assert.equal(status, 0); - assert.equal(val, 42); + if (code !== 0) + return; + assert.equal(nor, 0); + process._rawDebug('ok'); +}); + +var listener = tracing.addAsyncListener(callbacksObj); + + +tests.push(function() { + activeContext = setTimeout(function() { + throw new Error('error'); + }); }); + + +tests.push(function() { + activeContext = setTimeout(function() { + (function a() { + throw new Error('error'); + }()); + }); +}); + + +tests.push(function() { + setImmediate(function() { + activeContext = setInterval(function() { + throw new Error('error'); + }); + }); +}); + + +nor = tests.length; diff --git a/test/simple/test-asynclistener-error-multiple-handled.js b/test/simple/test-asynclistener-error-multiple-handled.js index 576ce58e4cf..38a95b4e448 100644 --- a/test/simple/test-asynclistener-error-multiple-handled.js +++ b/test/simple/test-asynclistener-error-multiple-handled.js @@ -34,7 +34,7 @@ function onAsync1() { return 1; } -function onError(stor) { +function onError(ctx, stor) { results.push(stor); return true; } diff --git a/test/simple/test-asynclistener-error-multiple-mix.js b/test/simple/test-asynclistener-error-multiple-mix.js index 83716bc281c..4e54e384fec 100644 --- a/test/simple/test-asynclistener-error-multiple-mix.js +++ b/test/simple/test-asynclistener-error-multiple-mix.js @@ -25,13 +25,13 @@ var tracing = require('tracing'); var results = []; var asyncNoHandleError = { - error: function(stor) { + error: function(ctx, stor) { results.push(1); } }; var asyncHandleError = { - error: function(stor) { + error: function(ctx, stor) { results.push(0); return true; } diff --git a/test/simple/test-asynclistener-error-multiple-unhandled.js b/test/simple/test-asynclistener-error-multiple-unhandled.js index 33afaaa81b1..5c16155c33c 100644 --- a/test/simple/test-asynclistener-error-multiple-unhandled.js +++ b/test/simple/test-asynclistener-error-multiple-unhandled.js @@ -31,7 +31,7 @@ function onAsync1() { return 1; } -function onError(stor) { +function onError(ctx, stor) { results.push(stor); } diff --git a/test/simple/test-asynclistener-error-net.js b/test/simple/test-asynclistener-error-net.js index 26a337a5044..1c167ed0636 100644 --- a/test/simple/test-asynclistener-error-net.js +++ b/test/simple/test-asynclistener-error-net.js @@ -31,7 +31,7 @@ var caught = 0; var expectCaught = 0; var callbacksObj = { - error: function(value, er) { + error: function(ctx, value, er) { var idx = errorMsgs.indexOf(er.message); caught++; diff --git a/test/simple/test-asynclistener-error-throw-in-after.js b/test/simple/test-asynclistener-error-throw-in-after.js index 3eb02e165d4..8521920cf6c 100644 --- a/test/simple/test-asynclistener-error-throw-in-after.js +++ b/test/simple/test-asynclistener-error-throw-in-after.js @@ -30,7 +30,7 @@ var handlers = { after: function() { throw 1; }, - error: function(stor, err) { + error: function(ctx, stor, err) { // Error handler must be called exactly *once*. once++; assert.equal(err, 1); diff --git a/test/simple/test-asynclistener-error-throw-in-before-multiple.js b/test/simple/test-asynclistener-error-throw-in-before-multiple.js index b9aecf3977b..1f82c12f7f8 100644 --- a/test/simple/test-asynclistener-error-throw-in-before-multiple.js +++ b/test/simple/test-asynclistener-error-throw-in-before-multiple.js @@ -30,7 +30,7 @@ var handlers = { before: function() { throw 1; }, - error: function(stor, err) { + error: function(ctx, stor, err) { // Must catch error thrown in before callback. assert.equal(err, 1); once++; @@ -42,7 +42,7 @@ var handlers1 = { before: function() { throw 2; }, - error: function(stor, err) { + error: function(ctx, stor, err) { // Must catch *other* handlers throw by error callback. assert.equal(err, 1); once++; diff --git a/test/simple/test-asynclistener-error-throw-in-before.js b/test/simple/test-asynclistener-error-throw-in-before.js index fb6b6eeecae..284f18fa17b 100644 --- a/test/simple/test-asynclistener-error-throw-in-before.js +++ b/test/simple/test-asynclistener-error-throw-in-before.js @@ -30,7 +30,7 @@ var handlers = { before: function() { throw 1; }, - error: function(stor, err) { + error: function(ctx, stor, err) { // Error handler must be called exactly *once*. once++; assert.equal(err, 1); diff --git a/test/simple/test-asynclistener-error.js b/test/simple/test-asynclistener-error.js index 6e5f31b0348..9ec688be666 100644 --- a/test/simple/test-asynclistener-error.js +++ b/test/simple/test-asynclistener-error.js @@ -35,7 +35,7 @@ var expectCaught = 0; var exitCbRan = false; var callbacksObj = { - error: function(value, er) { + error: function(ctx, value, er) { var idx = errorMsgs.indexOf(er.message); caught++; @@ -69,19 +69,6 @@ process.on('exit', function(code) { }); -// Catch synchronous throws -errorMsgs.push('sync throw'); -process.nextTick(function() { - addListener(listener); - - expectCaught++; - currentMsg = 'sync throw'; - throw new Error(currentMsg); - - removeListener(listener); -}); - - // Simple cases errorMsgs.push('setTimeout - simple'); errorMsgs.push('setImmediate - simple'); @@ -119,6 +106,7 @@ process.nextTick(function() { }); + // Deeply nested errorMsgs.push('setInterval - nested'); errorMsgs.push('setImmediate - nested'); diff --git a/test/simple/test-asynclistener-multi-context.js b/test/simple/test-asynclistener-multi-context.js new file mode 100644 index 00000000000..c133575dc0f --- /dev/null +++ b/test/simple/test-asynclistener-multi-context.js @@ -0,0 +1,217 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var net = require('net'); +var tracing = require('tracing'); + +var queue = []; +var actual = 0; +var expected = 0; + +var addListener = tracing.addAsyncListener; +var removeListener = tracing.removeAsyncListener; +var tap = tracing.ASYNC_PROVIDERS; + +var callbacks = { + error: function onError(ctx, data, er) { + actual++; + if ('catch me please' === er.message) { + return true; + } + // Want to preserve the actual location of the error. + throw er; + } +}; + +var listener = tracing.createAsyncListener(callbacks); + +process.on('beforeExit', function() { + if (queue.length > 0) + runQueue(); +}); + +process.on('exit', function(code) { + removeListener(listener); + process.removeAllListeners('uncaughtException'); + + // Very important this code is checked, or real reason for failure may + // be obfuscated by a failing assert below. + if (code !== 0) + return; + + console.log('actual:', actual); + console.log('expected:', expected); + assert.equal(actual, expected); + console.log('ok'); +}); + +process.on('uncaughtException', function(er) { + actual++; + if ('catch me not' !== er.message) + throw er; +}); + +function runQueue() { + if (queue.length > 0) { + queue.shift()(); + removeListener(listener); + } +} +setImmediate(runQueue); + + +queue.push(function() { + addListener(listener); + var ranFirst = false; + + setTimeout(function() { + process.nextTick(function() { + ranFirst = true; + removeListener(listener); + setTimeout(function() { + expected++; + throw new Error('catch me not'); + }); + }); + + setImmediate(function() { + assert.ok(ranFirst); + expected++; + throw new Error('catch me please'); + }); + }); +}); + + +queue.push(function() { + process.nextTick(function() { + expected++; + throw new Error('catch me not'); + }); +}); + + +queue.push(function() { + setTimeout(function() { + process.nextTick(function() { + addListener(listener); + setTimeout(function() { + setImmediate(function() { + expected++; + throw new Error('catch me please'); + }); + removeListener(listener); + setImmediate(function() { + expected++; + throw new Error('catch me not'); + }); + }); + }); + expected++; + throw new Error('catch me not'); + }); +}); + + +queue.push(function() { + addListener(listener); + setTimeout(function() { + process.nextTick(function() { + expected++; + throw new Error('catch me please'); + }); + removeListener(listener); + setTimeout(function() { + // This listener shouldn't start capturing until errors until it + // hits the first TCP provider. + var tcpListener = addListener(callbacks, null, tap.TCP); + debugger; + setTimeout(function() { + net.createServer().listen(common.PORT, function() { + this.close(function() { + removeListener(tcpListener); + expected++; + throw new Error('catch me not'); + }); + expected++; + throw new Error('catch me please'); + }); + removeListener(listener); + process.nextTick(function() { + removeListener(tcpListener); + expected++; + throw new Error('catch me not'); + }); + // Should not be caught because it hasn't happened within the first + // async call stack that matches the passed provider. + // TODO(trevnorris): Fix this test. + //expected++; + //throw new Error('catch me not'); + }); + }); + expected++; + throw new Error('catch me not'); + }); + removeListener(listener); +}); + + +queue.push(function() { + addListener(listener); + var server = net.createServer(function(conn) { + conn.on('data', function() { + conn.write('hello', function() { + removeListener(listener); + expected++; + throw new Error('catch me not'); + }); + expected++; + throw new Error('catch me please'); + }); + expected++; + throw new Error('catch me please'); + }).listen(common.PORT, function() { + net.connect(common.PORT, function() { + this.write('sup', function() { + expected++; + throw new Error('catch me please'); + }); + removeListener(listener); + var self = this; + this.on('data', function() { + debugger; + removeListener(listener); + server.close(function() { + // This error will still be caught because it is part of the server's + // asynchronous call stack. Not that of the connection. + expected++; + throw new Error('catch me please'); + }); + self.destroy(); + expected++; + throw new Error('catch me not'); + }); + }); + }); + removeListener(listener); +}); diff --git a/test/simple/test-asynclistener-multi-timeout.js b/test/simple/test-asynclistener-multi-timeout.js index 9af48205450..51b973ccec3 100644 --- a/test/simple/test-asynclistener-multi-timeout.js +++ b/test/simple/test-asynclistener-multi-timeout.js @@ -29,7 +29,7 @@ var caught = []; var expect = []; var callbacksObj = { - error: function(value, er) { + error: function(ctx, value, er) { process._rawDebug('caught', er.message); caught.push(er.message); return (expect.indexOf(er.message) !== -1); diff --git a/test/simple/test-asynclistener-remove-in-before.js b/test/simple/test-asynclistener-remove-in-before.js deleted file mode 100644 index 06f470ce075..00000000000 --- a/test/simple/test-asynclistener-remove-in-before.js +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -var common = require('../common'); -var assert = require('assert'); -var tracing = require('tracing'); -var done = false; -var callbacks = { - before: function() { - tracing.removeAsyncListener(listener); - }, - after: function() { - done = true; - } -}; - -var listener = tracing.addAsyncListener(callbacks); - -process.nextTick(function() {}); - -process.on('exit', function(status) { - tracing.removeAsyncListener(listener); - assert.equal(status, 0); - assert.ok(done); -}); diff --git a/test/simple/test-asynclistener-run-error-once.js b/test/simple/test-asynclistener-run-error-once.js index 154decb99af..2d3f5f32990 100644 --- a/test/simple/test-asynclistener-run-error-once.js +++ b/test/simple/test-asynclistener-run-error-once.js @@ -27,7 +27,7 @@ var tracing = require('tracing'); var errMsg = 'net - error: server connection'; var cntr = 0; var al = tracing.addAsyncListener({ - error: function(stor, er) { + error: function(ctx, stor, er) { cntr++; process._rawDebug('Handling error: ' + er.message); assert.equal(errMsg, er.message); diff --git a/test/simple/test-asynclistener.js b/test/simple/test-asynclistener.js index 4c29ec9054f..280f2566dd6 100644 --- a/test/simple/test-asynclistener.js +++ b/test/simple/test-asynclistener.js @@ -32,8 +32,9 @@ var actualAsync = 0; var expectAsync = 0; var callbacks = { - create: function onAsync() { - actualAsync++; + create: function onAsync(data, type) { + if (type !== 'NEXTTICK') + actualAsync++; } }; @@ -52,13 +53,13 @@ process.on('exit', function() { process.nextTick(function() { addListener(listener); - var b = setInterval(function() { - clearInterval(b); + setInterval(function() { + clearInterval(this); }); expectAsync++; - var c = setInterval(function() { - clearInterval(c); + setInterval(function() { + clearInterval(this); }); expectAsync++; @@ -68,12 +69,6 @@ process.nextTick(function() { setTimeout(function() { }); expectAsync++; - process.nextTick(function() { }); - expectAsync++; - - process.nextTick(function() { }); - expectAsync++; - setImmediate(function() { }); expectAsync++; @@ -98,9 +93,9 @@ process.nextTick(function() { process.nextTick(function() { setTimeout(function() { setImmediate(function() { - var i = setInterval(function() { + setInterval(function() { if (--interval <= 0) - clearInterval(i); + clearInterval(this); }); expectAsync++; }); @@ -116,28 +111,10 @@ process.nextTick(function() { }); expectAsync++; }); - expectAsync++; removeListener(listener); }); - -// Test triggers with two async listeners -process.nextTick(function() { - addListener(listener); - addListener(listener); - - setTimeout(function() { - process.nextTick(function() { }); - expectAsync += 2; - }); - expectAsync += 2; - - removeListener(listener); - removeListener(listener); -}); - - // Test callbacks from fs I/O process.nextTick(function() { addListener(listener); @@ -145,10 +122,10 @@ process.nextTick(function() { fs.stat('something random', function(err, stat) { }); expectAsync++; - setImmediate(function() { + setTimeout(function() { fs.stat('random again', function(err, stat) { }); expectAsync++; - }); + }, 10); expectAsync++; removeListener(listener); @@ -159,11 +136,13 @@ process.nextTick(function() { process.nextTick(function() { addListener(listener); - var server = net.createServer(function(c) { }); - expectAsync++; + var server = net.createServer(); server.listen(common.PORT, function() { - server.close(); + net.connect(common.PORT, function() { + this.destroy(); + server.close(); + }); expectAsync++; }); expectAsync++; @@ -176,12 +155,7 @@ process.nextTick(function() { process.nextTick(function() { addListener(listener); - var server = dgram.createSocket('udp4'); - expectAsync++; - - server.bind(common.PORT); - - server.close(); + dgram.createSocket('udp4').close(); expectAsync++; removeListener(listener); From 38c01bf53163f1879a516b61f8a56547063aae0e Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 23 Apr 2014 16:01:15 -0700 Subject: [PATCH 5/5] async-wrap: initialize async_wrap_parent_ This is to prevent an edge case that causees EXC_BAD_ACCESS. --- src/env-inl.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/env-inl.h b/src/env-inl.h index 40dfe7ccf33..0e81bec8f10 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -237,7 +237,8 @@ inline Environment* Environment::GetCurrentChecked( } inline Environment::Environment(v8::Local context) - : isolate_(context->GetIsolate()), + : async_wrap_parent_(NULL), + isolate_(context->GetIsolate()), isolate_data_(IsolateData::GetOrCreate(context->GetIsolate())), using_smalloc_alloc_cb_(false), using_domains_(false),