Skip to content

Commit b9e6032

Browse files
trevnorrispiscisaureus
authored andcommitted
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: nodejs/node-v0.x-archive#8110 Signed-off-by: Trevor Norris <[email protected]> Reviewed-by: Fedor Indutny <[email protected]> Reviewed-by: Alexis Campailla <[email protected]> Reviewed-by: Julien Gilli <[email protected]>
1 parent bd83c39 commit b9e6032

File tree

6 files changed

+167
-2
lines changed

6 files changed

+167
-2
lines changed

src/async-wrap-inl.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "base-object-inl.h"
2828
#include "env.h"
2929
#include "env-inl.h"
30+
#include "node_internals.h"
3031
#include "util.h"
3132
#include "util-inl.h"
3233
#include "v8.h"
@@ -38,7 +39,42 @@ inline AsyncWrap::AsyncWrap(Environment* env,
3839
ProviderType provider,
3940
AsyncWrap* parent)
4041
: BaseObject(env, object),
42+
has_async_queue_(false),
4143
provider_type_(provider) {
44+
// Check user controlled flag to see if the init callback should run.
45+
if (!env->call_async_init_hook())
46+
return;
47+
48+
// TODO(trevnorris): Until it's verified all passed object's are not weak,
49+
// add a HandleScope to make sure there's no leak.
50+
v8::HandleScope scope(env->isolate());
51+
52+
v8::Local<v8::Object> parent_obj;
53+
54+
v8::TryCatch try_catch;
55+
56+
// If a parent value was sent then call its pre/post functions to let it know
57+
// a conceptual "child" is being instantiated (e.g. that a server has
58+
// received a connection).
59+
if (parent != nullptr) {
60+
parent_obj = parent->object();
61+
env->async_hooks_pre_function()->Call(parent_obj, 0, nullptr);
62+
if (try_catch.HasCaught())
63+
FatalError("node::AsyncWrap::AsyncWrap", "parent pre hook threw");
64+
}
65+
66+
env->async_hooks_init_function()->Call(object, 0, nullptr);
67+
68+
if (try_catch.HasCaught())
69+
FatalError("node::AsyncWrap::AsyncWrap", "init hook threw");
70+
71+
has_async_queue_ = true;
72+
73+
if (parent != nullptr) {
74+
env->async_hooks_post_function()->Call(parent_obj, 0, nullptr);
75+
if (try_catch.HasCaught())
76+
FatalError("node::AsyncWrap::AsyncWrap", "parent post hook threw");
77+
}
4278
}
4379

4480

src/async-wrap.cc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
using v8::Context;
3232
using v8::Function;
33+
using v8::FunctionCallbackInfo;
3334
using v8::Handle;
3435
using v8::HandleScope;
3536
using v8::Integer;
@@ -38,16 +39,48 @@ using v8::Local;
3839
using v8::Object;
3940
using v8::TryCatch;
4041
using v8::Value;
42+
using v8::kExternalUint32Array;
4143

4244
namespace node {
4345

46+
static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
47+
Environment* env = Environment::GetCurrent(args.GetIsolate());
48+
49+
CHECK(args[0]->IsObject());
50+
CHECK(args[1]->IsFunction());
51+
CHECK(args[2]->IsFunction());
52+
CHECK(args[3]->IsFunction());
53+
54+
// Attach Fields enum from Environment::AsyncHooks.
55+
// Flags attached to this object are:
56+
// - kCallInitHook (0): Tells the AsyncWrap constructor whether it should
57+
// make a call to the init JS callback. This is disabled by default, so
58+
// even after setting the callbacks the flag will have to be set to
59+
// non-zero to have those callbacks called. This only affects the init
60+
// callback. If the init callback was called, then the pre/post callbacks
61+
// will automatically be called.
62+
Local<Object> async_hooks_obj = args[0].As<Object>();
63+
Environment::AsyncHooks* async_hooks = env->async_hooks();
64+
async_hooks_obj->SetIndexedPropertiesToExternalArrayData(
65+
async_hooks->fields(),
66+
kExternalUint32Array,
67+
async_hooks->fields_count());
68+
69+
env->set_async_hooks_init_function(args[1].As<Function>());
70+
env->set_async_hooks_pre_function(args[2].As<Function>());
71+
env->set_async_hooks_post_function(args[3].As<Function>());
72+
}
73+
74+
4475
static void Initialize(Handle<Object> target,
4576
Handle<Value> unused,
4677
Handle<Context> context) {
4778
Environment* env = Environment::GetCurrent(context);
4879
Isolate* isolate = env->isolate();
4980
HandleScope scope(isolate);
5081

82+
NODE_SET_METHOD(target, "setupHooks", SetupHooks);
83+
5184
Local<Object> async_providers = Object::New(isolate);
5285
#define V(PROVIDER) \
5386
async_providers->Set(FIXED_ONE_BYTE_STRING(isolate, #PROVIDER), \
@@ -90,12 +123,28 @@ Handle<Value> AsyncWrap::MakeCallback(const Handle<Function> cb,
90123
}
91124
}
92125

126+
if (has_async_queue_) {
127+
try_catch.SetVerbose(false);
128+
env()->async_hooks_pre_function()->Call(context, 0, nullptr);
129+
if (try_catch.HasCaught())
130+
FatalError("node::AsyncWrap::MakeCallback", "pre hook threw");
131+
try_catch.SetVerbose(true);
132+
}
133+
93134
Local<Value> ret = cb->Call(context, argc, argv);
94135

95136
if (try_catch.HasCaught()) {
96137
return Undefined(env()->isolate());
97138
}
98139

140+
if (has_async_queue_) {
141+
try_catch.SetVerbose(false);
142+
env()->async_hooks_post_function()->Call(context, 0, nullptr);
143+
if (try_catch.HasCaught())
144+
FatalError("node::AsyncWrap::MakeCallback", "post hook threw");
145+
try_catch.SetVerbose(true);
146+
}
147+
99148
if (has_domain) {
100149
Local<Value> exit_v = domain->Get(env()->exit_string());
101150
if (exit_v->IsFunction()) {

src/async-wrap.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ class AsyncWrap : public BaseObject {
8484
private:
8585
inline AsyncWrap();
8686

87-
uint32_t provider_type_;
87+
// When the async hooks init JS function is called from the constructor it is
88+
// expected the context object will receive a _asyncQueue object property
89+
// that will be used to call pre/post in MakeCallback.
90+
bool has_async_queue_;
91+
ProviderType provider_type_;
8892
};
8993

9094
} // namespace node

src/env-inl.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,22 @@ inline v8::Isolate* Environment::IsolateData::isolate() const {
111111
return isolate_;
112112
}
113113

114+
inline Environment::AsyncHooks::AsyncHooks() {
115+
for (int i = 0; i < kFieldsCount; i++) fields_[i] = 0;
116+
}
117+
118+
inline uint32_t* Environment::AsyncHooks::fields() {
119+
return fields_;
120+
}
121+
122+
inline int Environment::AsyncHooks::fields_count() const {
123+
return kFieldsCount;
124+
}
125+
126+
inline bool Environment::AsyncHooks::call_init_hook() {
127+
return fields_[kCallInitHook] != 0;
128+
}
129+
114130
inline Environment::DomainFlag::DomainFlag() {
115131
for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0;
116132
}
@@ -256,6 +272,11 @@ inline v8::Isolate* Environment::isolate() const {
256272
return isolate_;
257273
}
258274

275+
inline bool Environment::call_async_init_hook() const {
276+
// The const_cast is okay, it doesn't violate conceptual const-ness.
277+
return const_cast<Environment*>(this)->async_hooks()->call_init_hook();
278+
}
279+
259280
inline bool Environment::in_domain() const {
260281
// The const_cast is okay, it doesn't violate conceptual const-ness.
261282
return using_domains() &&
@@ -307,6 +328,10 @@ inline uv_loop_t* Environment::event_loop() const {
307328
return isolate_data()->event_loop();
308329
}
309330

331+
inline Environment::AsyncHooks* Environment::async_hooks() {
332+
return &async_hooks_;
333+
}
334+
310335
inline Environment::DomainFlag* Environment::domain_flag() {
311336
return &domain_flag_;
312337
}

src/env.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ namespace node {
6565
V(args_string, "args") \
6666
V(argv_string, "argv") \
6767
V(async, "async") \
68+
V(async_queue_string, "_asyncQueue") \
6869
V(atime_string, "atime") \
6970
V(birthtime_string, "birthtime") \
7071
V(blksize_string, "blksize") \
@@ -249,6 +250,9 @@ namespace node {
249250
V(zero_return_string, "ZERO_RETURN") \
250251

251252
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
253+
V(async_hooks_init_function, v8::Function) \
254+
V(async_hooks_pre_function, v8::Function) \
255+
V(async_hooks_post_function, v8::Function) \
252256
V(binding_cache_object, v8::Object) \
253257
V(buffer_constructor_function, v8::Function) \
254258
V(context, v8::Context) \
@@ -282,6 +286,27 @@ RB_HEAD(ares_task_list, ares_task_t);
282286

283287
class Environment {
284288
public:
289+
class AsyncHooks {
290+
public:
291+
inline uint32_t* fields();
292+
inline int fields_count() const;
293+
inline bool call_init_hook();
294+
295+
private:
296+
friend class Environment; // So we can call the constructor.
297+
inline AsyncHooks();
298+
299+
enum Fields {
300+
// Set this to not zero if the init hook should be called.
301+
kCallInitHook,
302+
kFieldsCount
303+
};
304+
305+
uint32_t fields_[kFieldsCount];
306+
307+
DISALLOW_COPY_AND_ASSIGN(AsyncHooks);
308+
};
309+
285310
class DomainFlag {
286311
public:
287312
inline uint32_t* fields();
@@ -373,6 +398,7 @@ class Environment {
373398

374399
inline v8::Isolate* isolate() const;
375400
inline uv_loop_t* event_loop() const;
401+
inline bool call_async_init_hook() const;
376402
inline bool in_domain() const;
377403
inline uint32_t watched_providers() const;
378404

@@ -392,6 +418,7 @@ class Environment {
392418
void *arg);
393419
inline void FinishHandleCleanup(uv_handle_t* handle);
394420

421+
inline AsyncHooks* async_hooks();
395422
inline DomainFlag* domain_flag();
396423
inline TickInfo* tick_info();
397424

@@ -485,6 +512,7 @@ class Environment {
485512
uv_idle_t immediate_idle_handle_;
486513
uv_prepare_t idle_prepare_handle_;
487514
uv_check_t idle_check_handle_;
515+
AsyncHooks async_hooks_;
488516
DomainFlag domain_flag_;
489517
TickInfo tick_info_;
490518
uv_timer_t cares_timer_handle_;

src/node.cc

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,11 +996,18 @@ Handle<Value> MakeCallback(Environment* env,
996996

997997
Local<Object> process = env->process_object();
998998
Local<Object> object, domain;
999+
bool has_async_queue = false;
9991000
bool has_domain = false;
10001001

1002+
if (recv->IsObject()) {
1003+
object = recv.As<Object>();
1004+
Local<Value> async_queue_v = object->Get(env->async_queue_string());
1005+
if (async_queue_v->IsObject())
1006+
has_async_queue = true;
1007+
}
1008+
10011009
if (env->using_domains()) {
10021010
CHECK(recv->IsObject());
1003-
object = recv.As<Object>();
10041011
Local<Value> domain_v = object->Get(env->domain_string());
10051012
has_domain = domain_v->IsObject();
10061013
if (has_domain) {
@@ -1022,8 +1029,24 @@ Handle<Value> MakeCallback(Environment* env,
10221029
}
10231030
}
10241031

1032+
if (has_async_queue) {
1033+
try_catch.SetVerbose(false);
1034+
env->async_hooks_pre_function()->Call(object, 0, nullptr);
1035+
if (try_catch.HasCaught())
1036+
FatalError("node:;MakeCallback", "pre hook threw");
1037+
try_catch.SetVerbose(true);
1038+
}
1039+
10251040
Local<Value> ret = callback->Call(recv, argc, argv);
10261041

1042+
if (has_async_queue) {
1043+
try_catch.SetVerbose(false);
1044+
env->async_hooks_post_function()->Call(object, 0, nullptr);
1045+
if (try_catch.HasCaught())
1046+
FatalError("node::MakeCallback", "post hook threw");
1047+
try_catch.SetVerbose(true);
1048+
}
1049+
10271050
if (has_domain) {
10281051
Local<Value> exit_v = domain->Get(env->exit_string());
10291052
if (exit_v->IsFunction()) {

0 commit comments

Comments
 (0)