async-wrap: add event hooks
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>
This commit is contained in:
parent
419f18d2e2
commit
709fc160e5
@ -27,6 +27,7 @@
|
|||||||
#include "base-object-inl.h"
|
#include "base-object-inl.h"
|
||||||
#include "env.h"
|
#include "env.h"
|
||||||
#include "env-inl.h"
|
#include "env-inl.h"
|
||||||
|
#include "node_internals.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "util-inl.h"
|
#include "util-inl.h"
|
||||||
|
|
||||||
@ -39,7 +40,42 @@ inline AsyncWrap::AsyncWrap(Environment* env,
|
|||||||
ProviderType provider,
|
ProviderType provider,
|
||||||
AsyncWrap* parent)
|
AsyncWrap* parent)
|
||||||
: BaseObject(env, object),
|
: BaseObject(env, object),
|
||||||
|
has_async_queue_(false),
|
||||||
provider_type_(provider) {
|
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 != NULL) {
|
||||||
|
parent_obj = parent->object();
|
||||||
|
env->async_hooks_pre_function()->Call(parent_obj, 0, NULL);
|
||||||
|
if (try_catch.HasCaught())
|
||||||
|
FatalError("node::AsyncWrap::AsyncWrap", "parent pre hook threw");
|
||||||
|
}
|
||||||
|
|
||||||
|
env->async_hooks_init_function()->Call(object, 0, NULL);
|
||||||
|
|
||||||
|
if (try_catch.HasCaught())
|
||||||
|
FatalError("node::AsyncWrap::AsyncWrap", "init hook threw");
|
||||||
|
|
||||||
|
has_async_queue_ = true;
|
||||||
|
|
||||||
|
if (parent != NULL) {
|
||||||
|
env->async_hooks_post_function()->Call(parent_obj, 0, NULL);
|
||||||
|
if (try_catch.HasCaught())
|
||||||
|
FatalError("node::AsyncWrap::AsyncWrap", "parent post hook threw");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
using v8::Context;
|
using v8::Context;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
|
using v8::FunctionCallbackInfo;
|
||||||
using v8::Handle;
|
using v8::Handle;
|
||||||
using v8::HandleScope;
|
using v8::HandleScope;
|
||||||
using v8::Integer;
|
using v8::Integer;
|
||||||
@ -38,9 +39,39 @@ using v8::Local;
|
|||||||
using v8::Object;
|
using v8::Object;
|
||||||
using v8::TryCatch;
|
using v8::TryCatch;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
using v8::kExternalUint32Array;
|
||||||
|
|
||||||
namespace node {
|
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,
|
static void Initialize(Handle<Object> target,
|
||||||
Handle<Value> unused,
|
Handle<Value> unused,
|
||||||
Handle<Context> context) {
|
Handle<Context> context) {
|
||||||
@ -48,6 +79,8 @@ static void Initialize(Handle<Object> target,
|
|||||||
Isolate* isolate = env->isolate();
|
Isolate* isolate = env->isolate();
|
||||||
HandleScope scope(isolate);
|
HandleScope scope(isolate);
|
||||||
|
|
||||||
|
NODE_SET_METHOD(target, "setupHooks", SetupHooks);
|
||||||
|
|
||||||
Local<Object> async_providers = Object::New(isolate);
|
Local<Object> async_providers = Object::New(isolate);
|
||||||
#define V(PROVIDER) \
|
#define V(PROVIDER) \
|
||||||
async_providers->Set(FIXED_ONE_BYTE_STRING(isolate, #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, NULL);
|
||||||
|
if (try_catch.HasCaught())
|
||||||
|
FatalError("node::AsyncWrap::MakeCallback", "pre hook threw");
|
||||||
|
try_catch.SetVerbose(true);
|
||||||
|
}
|
||||||
|
|
||||||
Local<Value> ret = cb->Call(context, argc, argv);
|
Local<Value> ret = cb->Call(context, argc, argv);
|
||||||
|
|
||||||
if (try_catch.HasCaught()) {
|
if (try_catch.HasCaught()) {
|
||||||
return Undefined(env()->isolate());
|
return Undefined(env()->isolate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (has_async_queue_) {
|
||||||
|
try_catch.SetVerbose(false);
|
||||||
|
env()->async_hooks_post_function()->Call(context, 0, NULL);
|
||||||
|
if (try_catch.HasCaught())
|
||||||
|
FatalError("node::AsyncWrap::MakeCallback", "post hook threw");
|
||||||
|
try_catch.SetVerbose(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (has_domain) {
|
if (has_domain) {
|
||||||
Local<Value> exit_v = domain->Get(env()->exit_string());
|
Local<Value> exit_v = domain->Get(env()->exit_string());
|
||||||
if (exit_v->IsFunction()) {
|
if (exit_v->IsFunction()) {
|
||||||
|
@ -84,7 +84,11 @@ class AsyncWrap : public BaseObject {
|
|||||||
private:
|
private:
|
||||||
inline AsyncWrap();
|
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
|
} // namespace node
|
||||||
|
@ -111,6 +111,22 @@ inline v8::Isolate* Environment::IsolateData::isolate() const {
|
|||||||
return isolate_;
|
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() {
|
inline Environment::DomainFlag::DomainFlag() {
|
||||||
for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0;
|
for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0;
|
||||||
}
|
}
|
||||||
@ -243,6 +259,11 @@ inline v8::Isolate* Environment::isolate() const {
|
|||||||
return isolate_;
|
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 {
|
inline bool Environment::in_domain() const {
|
||||||
// The const_cast is okay, it doesn't violate conceptual const-ness.
|
// The const_cast is okay, it doesn't violate conceptual const-ness.
|
||||||
return using_domains() &&
|
return using_domains() &&
|
||||||
@ -294,6 +315,10 @@ inline uv_loop_t* Environment::event_loop() const {
|
|||||||
return isolate_data()->event_loop();
|
return isolate_data()->event_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Environment::AsyncHooks* Environment::async_hooks() {
|
||||||
|
return &async_hooks_;
|
||||||
|
}
|
||||||
|
|
||||||
inline Environment::DomainFlag* Environment::domain_flag() {
|
inline Environment::DomainFlag* Environment::domain_flag() {
|
||||||
return &domain_flag_;
|
return &domain_flag_;
|
||||||
}
|
}
|
||||||
|
28
src/env.h
28
src/env.h
@ -65,6 +65,7 @@ namespace node {
|
|||||||
V(args_string, "args") \
|
V(args_string, "args") \
|
||||||
V(argv_string, "argv") \
|
V(argv_string, "argv") \
|
||||||
V(async, "async") \
|
V(async, "async") \
|
||||||
|
V(async_queue_string, "_asyncQueue") \
|
||||||
V(atime_string, "atime") \
|
V(atime_string, "atime") \
|
||||||
V(birthtime_string, "birthtime") \
|
V(birthtime_string, "birthtime") \
|
||||||
V(blksize_string, "blksize") \
|
V(blksize_string, "blksize") \
|
||||||
@ -249,6 +250,9 @@ namespace node {
|
|||||||
V(zero_return_string, "ZERO_RETURN") \
|
V(zero_return_string, "ZERO_RETURN") \
|
||||||
|
|
||||||
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
|
#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(binding_cache_object, v8::Object) \
|
||||||
V(buffer_constructor_function, v8::Function) \
|
V(buffer_constructor_function, v8::Function) \
|
||||||
V(context, v8::Context) \
|
V(context, v8::Context) \
|
||||||
@ -282,6 +286,27 @@ RB_HEAD(ares_task_list, ares_task_t);
|
|||||||
|
|
||||||
class Environment {
|
class Environment {
|
||||||
public:
|
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 {
|
class DomainFlag {
|
||||||
public:
|
public:
|
||||||
inline uint32_t* fields();
|
inline uint32_t* fields();
|
||||||
@ -369,6 +394,7 @@ class Environment {
|
|||||||
|
|
||||||
inline v8::Isolate* isolate() const;
|
inline v8::Isolate* isolate() const;
|
||||||
inline uv_loop_t* event_loop() const;
|
inline uv_loop_t* event_loop() const;
|
||||||
|
inline bool call_async_init_hook() const;
|
||||||
inline bool in_domain() const;
|
inline bool in_domain() const;
|
||||||
inline uint32_t watched_providers() const;
|
inline uint32_t watched_providers() const;
|
||||||
|
|
||||||
@ -388,6 +414,7 @@ class Environment {
|
|||||||
void *arg);
|
void *arg);
|
||||||
inline void FinishHandleCleanup(uv_handle_t* handle);
|
inline void FinishHandleCleanup(uv_handle_t* handle);
|
||||||
|
|
||||||
|
inline AsyncHooks* async_hooks();
|
||||||
inline DomainFlag* domain_flag();
|
inline DomainFlag* domain_flag();
|
||||||
inline TickInfo* tick_info();
|
inline TickInfo* tick_info();
|
||||||
|
|
||||||
@ -464,6 +491,7 @@ class Environment {
|
|||||||
uv_idle_t immediate_idle_handle_;
|
uv_idle_t immediate_idle_handle_;
|
||||||
uv_prepare_t idle_prepare_handle_;
|
uv_prepare_t idle_prepare_handle_;
|
||||||
uv_check_t idle_check_handle_;
|
uv_check_t idle_check_handle_;
|
||||||
|
AsyncHooks async_hooks_;
|
||||||
DomainFlag domain_flag_;
|
DomainFlag domain_flag_;
|
||||||
TickInfo tick_info_;
|
TickInfo tick_info_;
|
||||||
uv_timer_t cares_timer_handle_;
|
uv_timer_t cares_timer_handle_;
|
||||||
|
25
src/node.cc
25
src/node.cc
@ -988,11 +988,18 @@ Handle<Value> MakeCallback(Environment* env,
|
|||||||
|
|
||||||
Local<Object> process = env->process_object();
|
Local<Object> process = env->process_object();
|
||||||
Local<Object> object, domain;
|
Local<Object> object, domain;
|
||||||
|
bool has_async_queue = false;
|
||||||
bool has_domain = 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()) {
|
if (env->using_domains()) {
|
||||||
CHECK(recv->IsObject());
|
CHECK(recv->IsObject());
|
||||||
object = recv.As<Object>();
|
|
||||||
Local<Value> domain_v = object->Get(env->domain_string());
|
Local<Value> domain_v = object->Get(env->domain_string());
|
||||||
has_domain = domain_v->IsObject();
|
has_domain = domain_v->IsObject();
|
||||||
if (has_domain) {
|
if (has_domain) {
|
||||||
@ -1014,8 +1021,24 @@ Handle<Value> MakeCallback(Environment* env,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (has_async_queue) {
|
||||||
|
try_catch.SetVerbose(false);
|
||||||
|
env->async_hooks_pre_function()->Call(object, 0, NULL);
|
||||||
|
if (try_catch.HasCaught())
|
||||||
|
FatalError("node:;MakeCallback", "pre hook threw");
|
||||||
|
try_catch.SetVerbose(true);
|
||||||
|
}
|
||||||
|
|
||||||
Local<Value> ret = callback->Call(recv, argc, argv);
|
Local<Value> ret = callback->Call(recv, argc, argv);
|
||||||
|
|
||||||
|
if (has_async_queue) {
|
||||||
|
try_catch.SetVerbose(false);
|
||||||
|
env->async_hooks_post_function()->Call(object, 0, NULL);
|
||||||
|
if (try_catch.HasCaught())
|
||||||
|
FatalError("node::MakeCallback", "post hook threw");
|
||||||
|
try_catch.SetVerbose(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (has_domain) {
|
if (has_domain) {
|
||||||
Local<Value> exit_v = domain->Get(env->exit_string());
|
Local<Value> exit_v = domain->Get(env->exit_string());
|
||||||
if (exit_v->IsFunction()) {
|
if (exit_v->IsFunction()) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user