async-wrap: add event hooks
authorTrevor Norris <trev.norris@gmail.com>
Tue, 9 Dec 2014 04:10:44 +0000 (05:10 +0100)
committerBert Belder <bertbelder@gmail.com>
Tue, 9 Dec 2014 16:57:18 +0000 (17:57 +0100)
Call a user-defined callback at specific points in the lifetime of an
asynchronous event. Which are on instantiation, just before/after the
callback has been run.

**If any of these callbacks throws an exception, there is no forgiveness
or recovery. A message will be displayed and a core file dumped.**

Currently these only tie into AsyncWrap, meaning no call to a hook
callback will be made for timers or process.nextTick() events. Though
those will be added in a future commit.

Here are a few notes on how to make the hooks work:

- The "this" of all event hook callbacks is the request object.

- The zero field (kCallInitHook) of the flags object passed to
  setupHooks() must be set != 0 before the init callback will be called.

- kCallInitHook only affects the calling of the init callback. If the
  request object has been run through the create callback it will always
  run the before/after callbacks. Regardless of kCallInitHook.

- In the init callback the property "_asyncQueue" must be attached to
  the request object. e.g.

  function initHook() {
    this._asyncQueue = {};
  }

- DO NOT inspect the properties of the object in the init callback.
  Since the object is in the middle of being instantiated there are some
  cases when a getter is not complete, and doing so will cause Node to
  crash.

PR-URL: https://github.com/joyent/node/pull/8110
Signed-off-by: Trevor Norris <trev.norris@gmail.com>
Reviewed-by: Fedor Indutny <fedor@indutny.com>
Reviewed-by: Alexis Campailla <alexis@janeasystems.com>
Reviewed-by: Julien Gilli <julien.gilli@joyent.com>
src/async-wrap-inl.h
src/async-wrap.cc
src/async-wrap.h
src/env-inl.h
src/env.h
src/node.cc

index 73a177872e93d2b5a6faa395db4eb1226c3be2dd..4dbb3a6cfc42bd80008115063e31942b3631c15a 100644 (file)
@@ -27,6 +27,7 @@
 #include "base-object-inl.h"
 #include "env.h"
 #include "env-inl.h"
+#include "node_internals.h"
 #include "util.h"
 #include "util-inl.h"
 #include "v8.h"
@@ -38,7 +39,42 @@ inline AsyncWrap::AsyncWrap(Environment* env,
                             ProviderType provider,
                             AsyncWrap* parent)
     : BaseObject(env, object),
+      has_async_queue_(false),
       provider_type_(provider) {
+  // Check user controlled flag to see if the init callback should run.
+  if (!env->call_async_init_hook())
+    return;
+
+  // TODO(trevnorris): Until it's verified all passed object's are not weak,
+  // add a HandleScope to make sure there's no leak.
+  v8::HandleScope scope(env->isolate());
+
+  v8::Local<v8::Object> parent_obj;
+
+  v8::TryCatch try_catch;
+
+  // If a parent value was sent then call its pre/post functions to let it know
+  // a conceptual "child" is being instantiated (e.g. that a server has
+  // received a connection).
+  if (parent != nullptr) {
+    parent_obj = parent->object();
+    env->async_hooks_pre_function()->Call(parent_obj, 0, nullptr);
+    if (try_catch.HasCaught())
+      FatalError("node::AsyncWrap::AsyncWrap", "parent pre hook threw");
+  }
+
+  env->async_hooks_init_function()->Call(object, 0, nullptr);
+
+  if (try_catch.HasCaught())
+    FatalError("node::AsyncWrap::AsyncWrap", "init hook threw");
+
+  has_async_queue_ = true;
+
+  if (parent != nullptr) {
+    env->async_hooks_post_function()->Call(parent_obj, 0, nullptr);
+    if (try_catch.HasCaught())
+      FatalError("node::AsyncWrap::AsyncWrap", "parent post hook threw");
+  }
 }
 
 
index 2990f923c4eb1d3206435100cc059c7a40504c5b..3526512c6e008933d1fc9bba757e0e3a608bbd60 100644 (file)
@@ -30,6 +30,7 @@
 
 using v8::Context;
 using v8::Function;
+using v8::FunctionCallbackInfo;
 using v8::Handle;
 using v8::HandleScope;
 using v8::Integer;
@@ -38,9 +39,39 @@ using v8::Local;
 using v8::Object;
 using v8::TryCatch;
 using v8::Value;
+using v8::kExternalUint32Array;
 
 namespace node {
 
+static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
+  Environment* env = Environment::GetCurrent(args.GetIsolate());
+
+  CHECK(args[0]->IsObject());
+  CHECK(args[1]->IsFunction());
+  CHECK(args[2]->IsFunction());
+  CHECK(args[3]->IsFunction());
+
+  // Attach Fields enum from Environment::AsyncHooks.
+  // Flags attached to this object are:
+  // - kCallInitHook (0): Tells the AsyncWrap constructor whether it should
+  //   make a call to the init JS callback. This is disabled by default, so
+  //   even after setting the callbacks the flag will have to be set to
+  //   non-zero to have those callbacks called. This only affects the init
+  //   callback. If the init callback was called, then the pre/post callbacks
+  //   will automatically be called.
+  Local<Object> async_hooks_obj = args[0].As<Object>();
+  Environment::AsyncHooks* async_hooks = env->async_hooks();
+  async_hooks_obj->SetIndexedPropertiesToExternalArrayData(
+      async_hooks->fields(),
+      kExternalUint32Array,
+      async_hooks->fields_count());
+
+  env->set_async_hooks_init_function(args[1].As<Function>());
+  env->set_async_hooks_pre_function(args[2].As<Function>());
+  env->set_async_hooks_post_function(args[3].As<Function>());
+}
+
+
 static void Initialize(Handle<Object> target,
                 Handle<Value> unused,
                 Handle<Context> context) {
@@ -48,6 +79,8 @@ static void Initialize(Handle<Object> target,
   Isolate* isolate = env->isolate();
   HandleScope scope(isolate);
 
+  NODE_SET_METHOD(target, "setupHooks", SetupHooks);
+
   Local<Object> async_providers = Object::New(isolate);
 #define V(PROVIDER)                                                           \
   async_providers->Set(FIXED_ONE_BYTE_STRING(isolate, #PROVIDER),             \
@@ -90,12 +123,28 @@ Handle<Value> AsyncWrap::MakeCallback(const Handle<Function> cb,
     }
   }
 
+  if (has_async_queue_) {
+    try_catch.SetVerbose(false);
+    env()->async_hooks_pre_function()->Call(context, 0, nullptr);
+    if (try_catch.HasCaught())
+      FatalError("node::AsyncWrap::MakeCallback", "pre hook threw");
+    try_catch.SetVerbose(true);
+  }
+
   Local<Value> ret = cb->Call(context, argc, argv);
 
   if (try_catch.HasCaught()) {
     return Undefined(env()->isolate());
   }
 
+  if (has_async_queue_) {
+    try_catch.SetVerbose(false);
+    env()->async_hooks_post_function()->Call(context, 0, nullptr);
+    if (try_catch.HasCaught())
+      FatalError("node::AsyncWrap::MakeCallback", "post hook threw");
+    try_catch.SetVerbose(true);
+  }
+
   if (has_domain) {
     Local<Value> exit_v = domain->Get(env()->exit_string());
     if (exit_v->IsFunction()) {
index ea8dfd3f0122d04835856d1090d76d1270509392..0e42a00855c9a38ce9a2acffc25c754c65c114e3 100644 (file)
@@ -84,7 +84,11 @@ class AsyncWrap : public BaseObject {
  private:
   inline AsyncWrap();
 
-  uint32_t provider_type_;
+  // When the async hooks init JS function is called from the constructor it is
+  // expected the context object will receive a _asyncQueue object property
+  // that will be used to call pre/post in MakeCallback.
+  bool has_async_queue_;
+  ProviderType provider_type_;
 };
 
 }  // namespace node
index b40272cbdf0691f36dd4eec3e6911060798e5a11..4ccf899a46e141d66a903ff0cadcb07ecd2c0db6 100644 (file)
@@ -111,6 +111,22 @@ inline v8::Isolate* Environment::IsolateData::isolate() const {
   return isolate_;
 }
 
+inline Environment::AsyncHooks::AsyncHooks() {
+  for (int i = 0; i < kFieldsCount; i++) fields_[i] = 0;
+}
+
+inline uint32_t* Environment::AsyncHooks::fields() {
+  return fields_;
+}
+
+inline int Environment::AsyncHooks::fields_count() const {
+  return kFieldsCount;
+}
+
+inline bool Environment::AsyncHooks::call_init_hook() {
+  return fields_[kCallInitHook] != 0;
+}
+
 inline Environment::DomainFlag::DomainFlag() {
   for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0;
 }
@@ -256,6 +272,11 @@ inline v8::Isolate* Environment::isolate() const {
   return isolate_;
 }
 
+inline bool Environment::call_async_init_hook() const {
+  // The const_cast is okay, it doesn't violate conceptual const-ness.
+  return const_cast<Environment*>(this)->async_hooks()->call_init_hook();
+}
+
 inline bool Environment::in_domain() const {
   // The const_cast is okay, it doesn't violate conceptual const-ness.
   return using_domains() &&
@@ -307,6 +328,10 @@ inline uv_loop_t* Environment::event_loop() const {
   return isolate_data()->event_loop();
 }
 
+inline Environment::AsyncHooks* Environment::async_hooks() {
+  return &async_hooks_;
+}
+
 inline Environment::DomainFlag* Environment::domain_flag() {
   return &domain_flag_;
 }
index 68ec7224d24875f901a2dd2b7562341df13ec617..c167041e25cec6dd61cb7cfb1241badf6a265563 100644 (file)
--- a/src/env.h
+++ b/src/env.h
@@ -65,6 +65,7 @@ namespace node {
   V(args_string, "args")                                                      \
   V(argv_string, "argv")                                                      \
   V(async, "async")                                                           \
+  V(async_queue_string, "_asyncQueue")                                        \
   V(atime_string, "atime")                                                    \
   V(birthtime_string, "birthtime")                                            \
   V(blksize_string, "blksize")                                                \
@@ -249,6 +250,9 @@ namespace node {
   V(zero_return_string, "ZERO_RETURN")                                        \
 
 #define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)                           \
+  V(async_hooks_init_function, v8::Function)                                  \
+  V(async_hooks_pre_function, v8::Function)                                   \
+  V(async_hooks_post_function, v8::Function)                                  \
   V(binding_cache_object, v8::Object)                                         \
   V(buffer_constructor_function, v8::Function)                                \
   V(context, v8::Context)                                                     \
@@ -282,6 +286,27 @@ RB_HEAD(ares_task_list, ares_task_t);
 
 class Environment {
  public:
+  class AsyncHooks {
+   public:
+    inline uint32_t* fields();
+    inline int fields_count() const;
+    inline bool call_init_hook();
+
+   private:
+    friend class Environment;  // So we can call the constructor.
+    inline AsyncHooks();
+
+    enum Fields {
+      // Set this to not zero if the init hook should be called.
+      kCallInitHook,
+      kFieldsCount
+    };
+
+    uint32_t fields_[kFieldsCount];
+
+    DISALLOW_COPY_AND_ASSIGN(AsyncHooks);
+  };
+
   class DomainFlag {
    public:
     inline uint32_t* fields();
@@ -373,6 +398,7 @@ class Environment {
 
   inline v8::Isolate* isolate() const;
   inline uv_loop_t* event_loop() const;
+  inline bool call_async_init_hook() const;
   inline bool in_domain() const;
   inline uint32_t watched_providers() const;
 
@@ -392,6 +418,7 @@ class Environment {
                                     void *arg);
   inline void FinishHandleCleanup(uv_handle_t* handle);
 
+  inline AsyncHooks* async_hooks();
   inline DomainFlag* domain_flag();
   inline TickInfo* tick_info();
 
@@ -485,6 +512,7 @@ class Environment {
   uv_idle_t immediate_idle_handle_;
   uv_prepare_t idle_prepare_handle_;
   uv_check_t idle_check_handle_;
+  AsyncHooks async_hooks_;
   DomainFlag domain_flag_;
   TickInfo tick_info_;
   uv_timer_t cares_timer_handle_;
index 130d5ab9ea36aba604e94a3201b175b2eb3a0e00..cbc1f06c40db8d57738bb9cc69a6e48156415b53 100644 (file)
@@ -996,11 +996,18 @@ Handle<Value> MakeCallback(Environment* env,
 
   Local<Object> process = env->process_object();
   Local<Object> object, domain;
+  bool has_async_queue = false;
   bool has_domain = false;
 
+  if (recv->IsObject()) {
+    object = recv.As<Object>();
+    Local<Value> async_queue_v = object->Get(env->async_queue_string());
+    if (async_queue_v->IsObject())
+      has_async_queue = true;
+  }
+
   if (env->using_domains()) {
     CHECK(recv->IsObject());
-    object = recv.As<Object>();
     Local<Value> domain_v = object->Get(env->domain_string());
     has_domain = domain_v->IsObject();
     if (has_domain) {
@@ -1022,8 +1029,24 @@ Handle<Value> MakeCallback(Environment* env,
     }
   }
 
+  if (has_async_queue) {
+    try_catch.SetVerbose(false);
+    env->async_hooks_pre_function()->Call(object, 0, nullptr);
+    if (try_catch.HasCaught())
+      FatalError("node:;MakeCallback", "pre hook threw");
+    try_catch.SetVerbose(true);
+  }
+
   Local<Value> ret = callback->Call(recv, argc, argv);
 
+  if (has_async_queue) {
+    try_catch.SetVerbose(false);
+    env->async_hooks_post_function()->Call(object, 0, nullptr);
+    if (try_catch.HasCaught())
+      FatalError("node::MakeCallback", "post hook threw");
+    try_catch.SetVerbose(true);
+  }
+
   if (has_domain) {
     Local<Value> exit_v = domain->Get(env->exit_string());
     if (exit_v->IsFunction()) {