From 7f2d1b3cbd88a98cbe011b09a2368c0a9697ddee Mon Sep 17 00:00:00 2001 From: Mike Kaufman Date: Wed, 6 Apr 2016 10:57:38 -0700 Subject: [PATCH 1/2] async_wrap,lib: Firing async-wrap callbacks for next-tick callbacks This change updates nextTick() callback processing to call back into the node runtime to notify the runtime of async state transitions. Currently, the runtime will track the active async ID and fire any registered async-wrap callbacks. This gives async-wrap consistent user semantics for nextTick() callbacks. I anticipate some concerns around impact to perf. Rough measurements show an approximate 2x increase in time to process 1,000,000 nextTick() callbacks. These went from an average of 1094 ms per million nextTick() callbacks to 2133 ms per million with the changes in this PR. I'm open to alternative suggestions around implementation here. :) It's not lost on me that the implementation of next_tick.js is making an effort to minimize transitions from JS to the runtime, and this change increases those transitions by a factor of three. One of the goals here was to have basic entry-points for javascript code to notify the runtime of async transitions (namely, "enqueue", "start", and "end"). This allows a future where async call graph information can be tracked by the runtime and eliminates a number of cases where users will require the async-wrap callbacks. For example, continuation-local storage can be implemented on such an API, without callbacks, assuming each node in the graph contains a tuple of (unique ID, parent ID, pending child callback count, state), and where state is one of enqueued, running, or completed. --- lib/internal/process/next_tick.js | 31 ++- src/async-wrap-inl.h | 57 +---- src/async-wrap.cc | 246 +++++++++++++++++++--- src/async-wrap.h | 12 +- src/env-inl.h | 15 +- src/env.h | 7 +- test/parallel/test-async-wrap-nextTick.js | 114 ++++++++++ test/parallel/test-async-wrap-uid.js | 9 + 8 files changed, 397 insertions(+), 94 deletions(-) create mode 100644 test/parallel/test-async-wrap-nextTick.js diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js index 529645aa8d65c4..8e5502d9726ee8 100644 --- a/lib/internal/process/next_tick.js +++ b/lib/internal/process/next_tick.js @@ -3,6 +3,8 @@ exports.setup = setupNextTick; function setupNextTick() { + + var asyncWrap = process.binding('async_wrap'); const promises = require('internal/process/promises'); const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks); var nextTickQueue = []; @@ -85,17 +87,32 @@ function setupNextTick() { // Run callbacks that have no domain. // Using domains will cause this to be overridden. function _tickCallback() { + var callback, args, tock; do { while (tickInfo[kIndex] < tickInfo[kLength]) { tock = nextTickQueue[tickInfo[kIndex]++]; callback = tock.callback; + + asyncWrap.notifyAsyncStart( + tock.ranInitCallback, tock.asyncId, tock.asyncState); + args = tock.args; - // Using separate callback execution functions allows direct - // callback invocation with small numbers of arguments to avoid the - // performance hit associated with using `fn.apply()` - _combinedTickCallback(args, callback); + var callbackThrew = true; + try { + // Using separate callback execution functions allows direct + // callback invocation with small numbers of arguments to avoid the + // performance hit associated with using `fn.apply()` + _combinedTickCallback(args, callback); + callbackThrew = false; + } + finally { + asyncWrap.notifyAsyncEnd( + tock.ranInitCallback, tock.asyncId, + tock.asyncState, callbackThrew); + } + if (1e4 < tickInfo[kIndex]) tickDone(); } @@ -135,6 +152,12 @@ function setupNextTick() { this.callback = c; this.domain = process.domain || null; this.args = args; + this.parentAsyncId = asyncWrap.getCurrentAsyncId(); + this.asyncId = asyncWrap.getNextAsyncId(); + this.asyncState = {}; + this.ranInitCallback = asyncWrap.notifyAsyncEnqueue( + this.asyncId, this.asyncState, undefined, undefined, + asyncWrap.Providers.NEXTTICK); } function nextTick(callback) { diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h index cf7024e7e31461..7a7bfb66b14141 100644 --- a/src/async-wrap-inl.h +++ b/src/async-wrap-inl.h @@ -18,69 +18,22 @@ inline AsyncWrap::AsyncWrap(Environment* env, ProviderType provider, AsyncWrap* parent) : BaseObject(env, object), bits_(static_cast(provider) << 1), - uid_(env->get_async_wrap_uid()) { + uid_(env->get_next_async_wrap_uid()) { CHECK_NE(provider, PROVIDER_NONE); CHECK_GE(object->InternalFieldCount(), 1); // Shift provider value over to prevent id collision. persistent().SetWrapperClassId(NODE_ASYNC_ID_OFFSET + provider); - v8::Local init_fn = env->async_hooks_init_function(); - - // No init callback exists, no reason to go on. - if (init_fn.IsEmpty()) - return; - - // If async wrap callbacks are disabled and no parent was passed that has - // run the init callback then return. - if (!env->async_wrap_callbacks_enabled() && - (parent == nullptr || !parent->ran_init_callback())) - return; - - v8::HandleScope scope(env->isolate()); - - v8::Local argv[] = { - v8::Integer::New(env->isolate(), get_uid()), - v8::Int32::New(env->isolate(), provider), - Null(env->isolate()), - Null(env->isolate()) - }; - - if (parent != nullptr) { - argv[2] = v8::Integer::New(env->isolate(), parent->get_uid()); - argv[3] = parent->object(); + if (AsyncWrap::FireAsyncInitCallbacks(env, get_uid(), object, provider, parent)) { + bits_ |= 1; // ran_init_callback() is true now. } - - v8::TryCatch try_catch(env->isolate()); - - v8::MaybeLocal ret = - init_fn->Call(env->context(), object, arraysize(argv), argv); - - if (ret.IsEmpty()) { - ClearFatalExceptionHandlers(env); - FatalException(env->isolate(), try_catch); - } - - bits_ |= 1; // ran_init_callback() is true now. } inline AsyncWrap::~AsyncWrap() { - if (!ran_init_callback()) - return; - - v8::Local fn = env()->async_hooks_destroy_function(); - if (!fn.IsEmpty()) { - v8::HandleScope scope(env()->isolate()); - v8::Local uid = v8::Integer::New(env()->isolate(), get_uid()); - v8::TryCatch try_catch(env()->isolate()); - v8::MaybeLocal ret = - fn->Call(env()->context(), v8::Null(env()->isolate()), 1, &uid); - if (ret.IsEmpty()) { - ClearFatalExceptionHandlers(env()); - FatalException(env()->isolate(), try_catch); - } - } + v8::HandleScope scope(env()->isolate()); + FireAsyncDestroyCallbacks(env(), ran_init_callback(), v8::Integer::New(env()->isolate(), get_uid())); } diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 8129500a922d97..ad9ba1894f2e09 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -19,6 +19,7 @@ using v8::Integer; using v8::Isolate; using v8::Local; using v8::MaybeLocal; +using v8::Number; using v8::Object; using v8::RetainedObjectInfo; using v8::TryCatch; @@ -117,6 +118,202 @@ static void DisableHooksJS(const FunctionCallbackInfo& args) { env->async_hooks()->set_enable_callbacks(0); } +static void GetCurrentAsyncId(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + v8::Local uid = v8::Integer::New(env->isolate(), env->get_current_async_wrap_uid()); + args.GetReturnValue().Set(uid); +} + +static void GetNextAsyncId(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + v8::Local uid = v8::Integer::New(env->isolate(), env->get_next_async_wrap_uid()); + args.GetReturnValue().Set(uid); +} + +static bool FireAsyncInitCallbacksInternal( + Environment* env, + int64_t uid, + v8::Local object, + int64_t parentUid, + v8::Local parentObject, + AsyncWrap::ProviderType provider, + AsyncWrap* parent) +{ + v8::Local init_fn = env->async_hooks_init_function(); + bool didRun = false; + + // No init callback exists, no reason to go on. + if (!init_fn.IsEmpty()) { + + // If async wrap callbacks are disabled and no parent was passed that has + // run the init callback then return. + if (!env->async_wrap_callbacks_enabled() && + (parent == nullptr || !parent->ran_init_callback())) { + return false; + } + + v8::HandleScope scope(env->isolate()); + + v8::Local argv[] = { + v8::Integer::New(env->isolate(), uid), + v8::Int32::New(env->isolate(), provider), + Null(env->isolate()), + Null(env->isolate()) + }; + + if (!parentObject.IsEmpty() && !parentObject->IsUndefined()) { + argv[2] = v8::Integer::New(env->isolate(), parentUid); + argv[3] = parentObject; + } + + v8::TryCatch try_catch(env->isolate()); + + v8::MaybeLocal ret = + init_fn->Call(env->context(), object, arraysize(argv), argv); + didRun = true; + + if (ret.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); + } + } + + return didRun; +} + +static void NotifyAsyncEnqueueFromJS(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + v8::Local uid = args[0].As(); + v8::Local obj = args[1].As(); + v8::Local parentUid = args[2].As(); + v8::Local parentObj = args[3].As(); + v8::Local providerId = args[4].As(); + AsyncWrap::ProviderType provider = static_cast(providerId->Int32Value()); + + int64_t parentUidVal = 0; + if (!parentUid.IsEmpty() && !parentUid->IsUndefined() && parentUid->IsNumber()) { + parentUidVal = parentUid->IntegerValue(); + } + + bool didRunInit = FireAsyncInitCallbacksInternal( + env, + uid->IntegerValue(), + obj, + parentUidVal, + parentObj, + provider, + (AsyncWrap*)nullptr); + + args.GetReturnValue().Set(didRunInit); +} + +bool AsyncWrap::FireAsyncInitCallbacks( + Environment* env, + int64_t uid, + v8::Local object, + AsyncWrap::ProviderType provider, + AsyncWrap* parent) +{ + v8::Local init_fn = env->async_hooks_init_function(); + if (!init_fn.IsEmpty()) { + + int64_t parentUid = 0; + v8::Local parentObject = v8::Local(); + + if (parent != nullptr) { + parentUid = parent->get_uid(); + parentObject = parent->object(); + } + + return FireAsyncInitCallbacksInternal( + env, + uid, + object, + parentUid, + parentObject, + provider, + parent); + } + return false; +} + +void AsyncWrap::FireAsyncPreCallbacks( + Environment* env, + bool ranInitCallbacks, + v8::Local uid, + v8::Local obj) +{ + env->set_current_async_wrap_uid(uid->IntegerValue()); + + if (ranInitCallbacks) { + Local pre_fn = env->async_hooks_pre_function(); + if (!pre_fn.IsEmpty()) { + TryCatch try_catch(env->isolate()); + v8::Local argv[] = { uid }; + MaybeLocal result = pre_fn->Call(env->context(), obj, 1, argv); + if (result.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); + } + } + } +} + +static void NotifyAsyncStartFromJS(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + v8::Local ranInitCallbacks = args[0].As(); + v8::Local uid = args[1].As(); + v8::Local obj = args[2].As(); + AsyncWrap::FireAsyncPreCallbacks(env, ranInitCallbacks->Value(), uid, obj); +} + +void AsyncWrap::FireAsyncPostCallbacks(Environment* env, bool ranInitCallback, v8::Local uid, v8::Local obj, v8::Local didUserCodeThrow) { + + if (ranInitCallback) { + Local post_fn = env->async_hooks_post_function(); + if (!post_fn.IsEmpty()) { + Local vals[] = { uid, didUserCodeThrow }; + TryCatch try_catch(env->isolate()); + MaybeLocal ar = + post_fn->Call(env->context(), obj, arraysize(vals), vals); + if (ar.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); + } + } + } + + env->set_current_async_wrap_uid(0); +} + +static void NotifyAsyncEndFromJS(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + v8::Local ranInitCallbacks = args[0].As(); + v8::Local uid = args[1].As(); + v8::Local obj = args[2].As(); + v8::Local didUserCodeThrow = args[3].As(); + AsyncWrap::FireAsyncPostCallbacks(env, ranInitCallbacks->Value(), uid, obj, didUserCodeThrow); + AsyncWrap::FireAsyncDestroyCallbacks(env, ranInitCallbacks->BooleanValue(), uid); +} + +void AsyncWrap::FireAsyncDestroyCallbacks(Environment* env, bool ranInitCallbacks, v8::Local uid) { + + if (ranInitCallbacks) { + v8::Local fn = env->async_hooks_destroy_function(); + if (!fn.IsEmpty()) { + v8::HandleScope scope(env->isolate()); + v8::TryCatch try_catch(env->isolate()); + Local argv[] = { uid }; + v8::MaybeLocal ret = + fn->Call(env->context(), v8::Null(env->isolate()), arraysize(argv), argv); + if (ret.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); + } + } + } + +} static void SetupHooks(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -129,17 +326,17 @@ static void SetupHooks(const FunctionCallbackInfo& args) { Local fn_obj = args[0].As(); Local init_v = fn_obj->Get( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "init")).ToLocalChecked(); + env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "init")).ToLocalChecked(); Local pre_v = fn_obj->Get( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "pre")).ToLocalChecked(); + env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "pre")).ToLocalChecked(); Local post_v = fn_obj->Get( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "post")).ToLocalChecked(); + env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "post")).ToLocalChecked(); Local destroy_v = fn_obj->Get( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "destroy")).ToLocalChecked(); + env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "destroy")).ToLocalChecked(); if (!init_v->IsFunction()) return env->ThrowTypeError("init callback must be a function"); @@ -165,6 +362,11 @@ static void Initialize(Local target, env->SetMethod(target, "setupHooks", SetupHooks); env->SetMethod(target, "disable", DisableHooksJS); env->SetMethod(target, "enable", EnableHooksJS); + env->SetMethod(target, "getCurrentAsyncId", GetCurrentAsyncId); + env->SetMethod(target, "getNextAsyncId", GetNextAsyncId); + env->SetMethod(target, "notifyAsyncEnqueue", NotifyAsyncEnqueueFromJS); + env->SetMethod(target, "notifyAsyncStart", NotifyAsyncStartFromJS); + env->SetMethod(target, "notifyAsyncEnd", NotifyAsyncEndFromJS); Local async_providers = Object::New(isolate); #define V(PROVIDER) \ @@ -198,7 +400,7 @@ Local AsyncWrap::MakeCallback(const Local cb, Local pre_fn = env()->async_hooks_pre_function(); Local post_fn = env()->async_hooks_post_function(); - Local uid = Integer::New(env()->isolate(), get_uid()); + Local uid = Integer::New(env()->isolate(), get_uid()); Local context = object(); Local domain; bool has_domain = false; @@ -225,31 +427,13 @@ Local AsyncWrap::MakeCallback(const Local cb, } } - if (ran_init_callback() && !pre_fn.IsEmpty()) { - TryCatch try_catch(env()->isolate()); - MaybeLocal ar = pre_fn->Call(env()->context(), context, 1, &uid); - if (ar.IsEmpty()) { - ClearFatalExceptionHandlers(env()); - FatalException(env()->isolate(), try_catch); - return Local(); - } - } + AsyncWrap::FireAsyncPreCallbacks(env(), ran_init_callback(), uid, context); Local ret = cb->Call(context, argc, argv); - if (ran_init_callback() && !post_fn.IsEmpty()) { - Local did_throw = Boolean::New(env()->isolate(), ret.IsEmpty()); - Local vals[] = { uid, did_throw }; - TryCatch try_catch(env()->isolate()); - MaybeLocal ar = - post_fn->Call(env()->context(), context, arraysize(vals), vals); - if (ar.IsEmpty()) { - ClearFatalExceptionHandlers(env()); - FatalException(env()->isolate(), try_catch); - return Local(); - } - } - + Local did_throw = Boolean::New(env()->isolate(), ret.IsEmpty()); + AsyncWrap::FireAsyncPostCallbacks(env(), ran_init_callback(), uid, context, did_throw); + if (ret.IsEmpty()) { return ret; } diff --git a/src/async-wrap.h b/src/async-wrap.h index cb0c9e211a8923..7a67fd21a60071 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -34,7 +34,8 @@ namespace node { V(UDPWRAP) \ V(UDPSENDWRAP) \ V(WRITEWRAP) \ - V(ZLIB) + V(ZLIB) \ + V(NEXTTICK) class Environment; @@ -47,6 +48,11 @@ class AsyncWrap : public BaseObject { #undef V }; + static bool FireAsyncInitCallbacks(Environment* env,int64_t uid,v8::Local object,AsyncWrap::ProviderType provider,AsyncWrap* parent); + static void FireAsyncPreCallbacks(Environment* env, bool ranInitCallback, v8::Local uid, v8::Local obj); + static void FireAsyncPostCallbacks(Environment* env, bool ranInitCallback, v8::Local uid, v8::Local obj, v8::Local didUserCodeThrow); + static void FireAsyncDestroyCallbacks(Environment* env, bool ranInitCallbacks, v8::Local uid); + inline AsyncWrap(Environment* env, v8::Local object, ProviderType provider, @@ -71,9 +77,11 @@ class AsyncWrap : public BaseObject { virtual size_t self_size() const = 0; + inline bool ran_init_callback() const; + private: inline AsyncWrap(); - inline bool ran_init_callback() const; + // When the async hooks init JS function is called from the constructor it is // expected the context object will receive a _asyncQueue object property diff --git a/src/env-inl.h b/src/env-inl.h index 475e8e83f594e6..bf21d881579c18 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -221,7 +221,8 @@ inline Environment::Environment(v8::Local context, printed_error_(false), trace_sync_io_(false), makecallback_cntr_(0), - async_wrap_uid_(0), + async_wrap_counter_uid_(0), + async_wrap_current_uid_(0), debugger_agent_(this), http_parser_buffer_(nullptr), context_(context->GetIsolate(), context) { @@ -372,8 +373,16 @@ inline void Environment::set_trace_sync_io(bool value) { trace_sync_io_ = value; } -inline int64_t Environment::get_async_wrap_uid() { - return ++async_wrap_uid_; +inline int64_t Environment::get_next_async_wrap_uid() { + return ++async_wrap_counter_uid_; +} + +inline int64_t Environment::get_current_async_wrap_uid() { + return this->async_wrap_current_uid_; +} + +inline void Environment::set_current_async_wrap_uid(int64_t value) { + this->async_wrap_current_uid_ = value; } inline uint32_t* Environment::heap_statistics_buffer() const { diff --git a/src/env.h b/src/env.h index 0590a8a434b104..12efca9f7c7237 100644 --- a/src/env.h +++ b/src/env.h @@ -473,7 +473,9 @@ class Environment { void PrintSyncTrace() const; inline void set_trace_sync_io(bool value); - inline int64_t get_async_wrap_uid(); + inline int64_t get_next_async_wrap_uid(); + inline int64_t get_current_async_wrap_uid(); + inline void set_current_async_wrap_uid(int64_t value); inline uint32_t* heap_statistics_buffer() const; inline void set_heap_statistics_buffer(uint32_t* pointer); @@ -577,7 +579,8 @@ class Environment { bool printed_error_; bool trace_sync_io_; size_t makecallback_cntr_; - int64_t async_wrap_uid_; + int64_t async_wrap_counter_uid_; + int64_t async_wrap_current_uid_; debugger::Agent debugger_agent_; HandleWrapQueue handle_wrap_queue_; diff --git a/test/parallel/test-async-wrap-nextTick.js b/test/parallel/test-async-wrap-nextTick.js new file mode 100644 index 00000000000000..d22fd86b246834 --- /dev/null +++ b/test/parallel/test-async-wrap-nextTick.js @@ -0,0 +1,114 @@ +'use strict'; + +require('../common'); +require('console'); +const assert = require('assert'); +const async_wrap = process.binding('async_wrap'); + +const storage = new Map(); +async_wrap.setupHooks({ init, pre, post, destroy }); +async_wrap.enable(); + +function init(uid) { + assert.notStrictEqual(async_wrap.getCurrentAsyncId(), uid); + storage.set(uid, { + init: true, + pre: false, + post: false, + destroy: false + }); + // track uid on the this pointer to confirm the same object is being pased + // to subsequent callbacks + this.uid = uid; +} + +function pre(uid) { + assert.strictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.strictEqual(this.uid, uid); + storage.get(uid).pre = true; +} + +function post(uid) { + assert.strictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.strictEqual(this.uid, uid); + storage.get(uid).post = true; +} + +function destroy(uid) { + assert.notStrictEqual(async_wrap.getCurrentAsyncId(), uid); + storage.get(uid).destroy = true; +} + +function validateAsyncCallbacksDuringNextTickCallback() { + const currentAsyncId = async_wrap.getCurrentAsyncId(); + assert.strictEqual(storage.get(currentAsyncId).init, true); + assert.strictEqual(storage.get(currentAsyncId).pre, true); + assert.strictEqual(storage.get(currentAsyncId).post, false); + assert.strictEqual(storage.get(currentAsyncId).destroy, false); +} + +let id1 = 0; +let id2 = 0; +let id3 = 0; +let id4 = 0; + +process.nextTick(function tick1() { + + id1 = async_wrap.getCurrentAsyncId(); + assert.notStrictEqual(id1, 0); + assert.strictEqual(storage.size, 1); + validateAsyncCallbacksDuringNextTickCallback(); + + process.nextTick(function tick2() { + id2 = async_wrap.getCurrentAsyncId(); + assert.notStrictEqual(id2, 0); + assert.notStrictEqual(id1, id2); + validateAsyncCallbacksDuringNextTickCallback(); + + process.nextTick(function tick3() { + id3 = async_wrap.getCurrentAsyncId(); + assert.notStrictEqual(id3, 0); + assert.notStrictEqual(id1, id3); + assert.notStrictEqual(id2, id3); + validateAsyncCallbacksDuringNextTickCallback(); + + // async-wrap callbacks should be disabled when this is enqueued + // so init shouldn't fire + process.nextTick(function tick4() { + // but currentAsyncId should still be set up + id4 = async_wrap.getCurrentAsyncId(); + assert.notStrictEqual(id4, 0); + assert.notStrictEqual(id1, id4); + assert.notStrictEqual(id2, id4); + assert.notStrictEqual(id3, id4); + assert.strictEqual(!storage.get(id4), true); + assert.strictEqual(storage.size, 3); + }); + }); + + async_wrap.disable(); + + assert.strictEqual(storage.size, 3); + assert.strictEqual(id2, async_wrap.getCurrentAsyncId()); + }); + + assert.strictEqual(storage.size, 2); + assert.strictEqual(id1, async_wrap.getCurrentAsyncId()); +}); + +process.once('exit', function() { + + assert.strictEqual(storage.size, 3); + + for (const item of storage) { + const uid = item[0]; + const value = item[1]; + assert.strictEqual(typeof uid, 'number'); + assert.deepStrictEqual(value, { + init: true, + pre: true, + post: true, + destroy: true + }); + } +}); diff --git a/test/parallel/test-async-wrap-uid.js b/test/parallel/test-async-wrap-uid.js index 5bf3a8856e0e3f..bc1e475ad53216 100644 --- a/test/parallel/test-async-wrap-uid.js +++ b/test/parallel/test-async-wrap-uid.js @@ -10,6 +10,7 @@ async_wrap.setupHooks({ init, pre, post, destroy }); async_wrap.enable(); function init(uid) { + assert.notStrictEqual(async_wrap.getCurrentAsyncId(), uid); storage.set(uid, { init: true, pre: false, @@ -19,14 +20,17 @@ function init(uid) { } function pre(uid) { + assert.strictEqual(async_wrap.getCurrentAsyncId(), uid); storage.get(uid).pre = true; } function post(uid) { + assert.strictEqual(async_wrap.getCurrentAsyncId(), uid); storage.get(uid).post = true; } function destroy(uid) { + assert.notStrictEqual(async_wrap.getCurrentAsyncId(), uid); storage.get(uid).destroy = true; } @@ -55,3 +59,8 @@ process.once('exit', function() { }); } }); + +// verify each call to next ID produces an increasing uid. +var nextId = async_wrap.getNextAsyncId(); +var nextId2 = async_wrap.getNextAsyncId(); +assert.strictEqual(nextId + 1, nextId2); From 7fc44a936bbe227a3445b133428660d2ddfdb00b Mon Sep 17 00:00:00 2001 From: Mike Kaufman Date: Fri, 8 Apr 2016 10:53:16 -0600 Subject: [PATCH 2/2] Reduced the number of times the nextTick logic needs to transition from JS to runtime. Did this by doing the following: - Added a JS file at ./lib/async_wrap.js to expose certain async-wrap functionality to JS code. - exposed array of async-wrap callbacks from runtime to JS via an array. This is in anticpation of eventually supporting multiple async-wrap callbacks. - exposed current-async-ID & next-async-ID counters from runtime through JS via mapping an int64 to int32[2]. This allows reading/ writing of these values without transition to runtime & re-allocaiton of v8 types on each read/write. --- lib/async_wrap.js | 170 ++++++++++++++++++++++ lib/internal/process/next_tick.js | 72 ++++++--- node.gyp | 1 + src/async-wrap-inl.h | 2 +- src/async-wrap.cc | 83 +++-------- src/env-inl.h | 85 +++++++++-- src/env.h | 23 ++- test/parallel/test-async-wrap-nextTick.js | 23 +-- test/parallel/test-async-wrap-uid.js | 43 +++++- 9 files changed, 378 insertions(+), 124 deletions(-) create mode 100644 lib/async_wrap.js diff --git a/lib/async_wrap.js b/lib/async_wrap.js new file mode 100644 index 00000000000000..3f980b8ad72a73 --- /dev/null +++ b/lib/async_wrap.js @@ -0,0 +1,170 @@ +'use strict'; + +const async_wrap = process.binding('async_wrap'); + +const nextIdArray = async_wrap.getNextAsyncIdArray(); +const currentIdArray = async_wrap.getCurrentAsyncIdArray(); +const fieldsArray = async_wrap.getAsyncHookFields(); +const asyncCallbacks = async_wrap.getAsyncCallbacks(); + +function getLittleEndian(a) { + return a[0] + a[1] * 0x100000000; +} + +function getBigEndian(a) { + return a[1] + a[0] * 0x100000000; +} + +function setLittleEndian(a, val) { + + if (val < 0) { + throw new Error('Negative value not supported'); + } + + var lword = val & 0xffffffff; + var hword = 0; + if (val > 0xffffffff) { + // effectively we're doing shift-right by 32 bits. Javascript bit + // operators convert operands to 32 bits, so we lose the + // high-order bits if we try to use >>> or >>. + hword = Math.floor((val / 0x100000000)); + } + a[0] = lword; + a[1] = hword; +} + +function setBigEndian(a, val) { + + if (val < 0) { + throw new Error('Negative value not supported'); + } + + var lword = val & 0xffffffff; + var hword = 0; + if (val > 0xffffffff) { + // effectively we're doing shift-right by 32 bits. Javascript bit + // operators convert operands to 32 bits, so we lose the + // high-order bits if we try to use >>> or >>. + hword = Math.floor((val / 0x100000000)); + } + a[1] = lword; + a[0] = hword; +} + +function incrementLittleEndian(a) { + // carry-over if lsb is maxed out + if (a[0] === 0xffffffff) { + a[0] = 0; + a[1]++; + } + a[0]++; + return a[0] + a[1] * 0x100000000; +} + +function incrementBigEndian(a) { + // carry-over if lsb is maxed out + if (a[1] === 0xffffffff) { + a[1] = 0; + a[0]++; + } + a[1]++; + return a[1] + a[0] * 0x100000000; +} + +function getCurrentIdLittleEndian() { + return getLittleEndian(currentIdArray); +} + +function getCurrentIdBigEndian() { + return getBigEndian(currentIdArray); +} + +function setCurrentIdLittleEndian(val) { + return setLittleEndian(currentIdArray, val); +} + +function setCurrentIdBigEndian(val) { + return setBigEndian(currentIdArray, val); +} + +function incrementNextIdLittleEndian() { + return incrementLittleEndian(nextIdArray); +} + +function incrementNextIdBigEndian() { + return incrementBigEndian(nextIdArray); +} + +// must match enum definitions in AsyncHook class in env.h +const kEnableCallbacks = 0; + +function callbacksEnabled() { + return (fieldsArray[kEnableCallbacks] !== 0 ? true : false); +} + +const getCurrentAsyncId = + process.binding('os').isBigEndian ? + getCurrentIdBigEndian : getCurrentIdLittleEndian; + +const setCurrentAsyncId = + process.binding('os').isBigEndian ? + setCurrentIdBigEndian : setCurrentIdLittleEndian; + +const incrementNextAsyncId = + process.binding('os').isBigEndian ? + incrementNextIdBigEndian : incrementNextIdLittleEndian; + +function notifyAsyncEnqueue(asyncId) { + if (callbacksEnabled()) { + const asyncState = {}; + for (let i = 0; i < asyncCallbacks.length; i++) { + if (asyncCallbacks[i].init) { + /* init(asyncId, provider, parentId, parentObject) */ + asyncCallbacks[i].init.call(asyncState, asyncId, + async_wrap.Providers.NEXTTICK, undefined, undefined); + } + } + return asyncState; + } + return undefined; +} + +function notifyAsyncStart(asyncId, asyncState) { + setCurrentAsyncId(asyncId); + if (asyncState) { + for (let i = 0; i < asyncCallbacks.length; i++) { + if (asyncCallbacks[i].pre) { + /* pre(asyncId); */ + asyncCallbacks[i].pre.call(asyncState, asyncId); + } + } + } +} + +function notifyAsyncEnd(asyncId, asyncState, callbackThrew) { + if (asyncState) { + for (let i = 0; i < asyncCallbacks.length; i++) { + if (asyncCallbacks[i].post) { + /* post(asyncId, didUserCodeThrow); */ + asyncCallbacks[i].post.call(asyncState, asyncId, callbackThrew); + } + } + + setCurrentAsyncId(0); + + for (let i = 0; i < asyncCallbacks.length; i++) { + if (asyncCallbacks[i].destroy) { + /* destroy(asyncId); */ + asyncCallbacks[i].destroy.call(undefined, asyncId); + } + } + } +} + +module.exports.incrementNextAsyncId = incrementNextAsyncId; +module.exports.getCurrentAsyncId = getCurrentAsyncId; +module.exports.setCurrentAsyncId = setCurrentAsyncId; +module.exports.callbacksEnabled = callbacksEnabled; +module.exports.notifyAsyncEnqueue = notifyAsyncEnqueue; +module.exports.notifyAsyncStart = notifyAsyncStart; +module.exports.notifyAsyncEnd = notifyAsyncEnd; diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js index 8e5502d9726ee8..50e5ec1a8bb0a6 100644 --- a/lib/internal/process/next_tick.js +++ b/lib/internal/process/next_tick.js @@ -3,8 +3,8 @@ exports.setup = setupNextTick; function setupNextTick() { + const async_wrap = require('async_wrap'); - var asyncWrap = process.binding('async_wrap'); const promises = require('internal/process/promises'); const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks); var nextTickQueue = []; @@ -95,26 +95,32 @@ function setupNextTick() { tock = nextTickQueue[tickInfo[kIndex]++]; callback = tock.callback; - asyncWrap.notifyAsyncStart( - tock.ranInitCallback, tock.asyncId, tock.asyncState); - args = tock.args; - var callbackThrew = true; - try { - // Using separate callback execution functions allows direct - // callback invocation with small numbers of arguments to avoid the - // performance hit associated with using `fn.apply()` + if (!tock.asyncState) { + async_wrap.setCurrentAsyncId(tock.asyncId); _combinedTickCallback(args, callback); - callbackThrew = false; + async_wrap.setCurrentAsyncId(0); } - finally { - asyncWrap.notifyAsyncEnd( - tock.ranInitCallback, tock.asyncId, - tock.asyncState, callbackThrew); + else { + var callbackThrew = true; + try { + async_wrap.notifyAsyncStart(tock.asyncId, tock.asyncState); + + // Using separate callback execution functions allows direct + // callback invocation with small numbers of arguments to avoid the + // performance hit associated with using `fn.apply()` + _combinedTickCallback(args, callback); + callbackThrew = false; + } + finally { + async_wrap.notifyAsyncEnd( + tock.asyncId, tock.asyncState, callbackThrew); + } } if (1e4 < tickInfo[kIndex]) tickDone(); + } tickDone(); _runMicrotasks(); @@ -133,10 +139,29 @@ function setupNextTick() { args = tock.args; if (domain) domain.enter(); - // Using separate callback execution functions allows direct - // callback invocation with small numbers of arguments to avoid the - // performance hit associated with using `fn.apply()` - _combinedTickCallback(args, callback); + + if (!tock.asyncState) { + async_wrap.setCurrentAsyncId(tock.asyncId); + _combinedTickCallback(args, callback); + async_wrap.setCurrentAsyncId(0); + } + else { + var callbackThrew = true; + try { + async_wrap.notifyAsyncStart(tock.asyncId, tock.asyncState); + + // Using separate callback execution functions allows direct + // callback invocation with small numbers of arguments to avoid the + // performance hit associated with using `fn.apply()` + _combinedTickCallback(args, callback); + callbackThrew = false; + } + finally { + async_wrap.notifyAsyncEnd( + tock.asyncId, tock.asyncState, callbackThrew); + } + } + if (1e4 < tickInfo[kIndex]) tickDone(); if (domain) @@ -152,12 +177,10 @@ function setupNextTick() { this.callback = c; this.domain = process.domain || null; this.args = args; - this.parentAsyncId = asyncWrap.getCurrentAsyncId(); - this.asyncId = asyncWrap.getNextAsyncId(); - this.asyncState = {}; - this.ranInitCallback = asyncWrap.notifyAsyncEnqueue( - this.asyncId, this.asyncState, undefined, undefined, - asyncWrap.Providers.NEXTTICK); + this.asyncId = async_wrap.incrementNextAsyncId(); + if (async_wrap.callbacksEnabled()) { + this.asyncState = async_wrap.notifyAsyncEnqueue(this.asyncId); + } } function nextTick(callback) { @@ -177,4 +200,5 @@ function setupNextTick() { nextTickQueue.push(new TickObject(callback, args)); tickInfo[kLength]++; } + } diff --git a/node.gyp b/node.gyp index 0e9fe40c419af6..4be2ac8359bfaf 100644 --- a/node.gyp +++ b/node.gyp @@ -21,6 +21,7 @@ 'lib/_debug_agent.js', 'lib/_debugger.js', 'lib/assert.js', + 'lib/async_wrap.js', 'lib/buffer.js', 'lib/child_process.js', 'lib/console.js', diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h index 7a7bfb66b14141..8f7e1403d5e9f4 100644 --- a/src/async-wrap-inl.h +++ b/src/async-wrap-inl.h @@ -18,7 +18,7 @@ inline AsyncWrap::AsyncWrap(Environment* env, ProviderType provider, AsyncWrap* parent) : BaseObject(env, object), bits_(static_cast(provider) << 1), - uid_(env->get_next_async_wrap_uid()) { + uid_(env->async_hooks()->get_next_async_wrap_uid()) { CHECK_NE(provider, PROVIDER_NONE); CHECK_GE(object->InternalFieldCount(), 1); diff --git a/src/async-wrap.cc b/src/async-wrap.cc index ad9ba1894f2e09..930ae27979e479 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -118,16 +118,19 @@ static void DisableHooksJS(const FunctionCallbackInfo& args) { env->async_hooks()->set_enable_callbacks(0); } -static void GetCurrentAsyncId(const FunctionCallbackInfo& args) { +static void GetCurrentAsyncIdArrayFromJS(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - v8::Local uid = v8::Integer::New(env->isolate(), env->get_current_async_wrap_uid()); - args.GetReturnValue().Set(uid); + args.GetReturnValue().Set(env->async_hooks()->get_current_async_id_array()); } -static void GetNextAsyncId(const FunctionCallbackInfo& args) { +static void GetNextAsyncIdArrayFromJS(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - v8::Local uid = v8::Integer::New(env->isolate(), env->get_next_async_wrap_uid()); - args.GetReturnValue().Set(uid); + args.GetReturnValue().Set(env->async_hooks()->get_next_async_id_array()); +} + +static void GetAsyncHookFieldsFromJS(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + args.GetReturnValue().Set(env->async_hooks()->get_fields_array()); } static bool FireAsyncInitCallbacksInternal( @@ -181,32 +184,6 @@ static bool FireAsyncInitCallbacksInternal( return didRun; } -static void NotifyAsyncEnqueueFromJS(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - v8::Local uid = args[0].As(); - v8::Local obj = args[1].As(); - v8::Local parentUid = args[2].As(); - v8::Local parentObj = args[3].As(); - v8::Local providerId = args[4].As(); - AsyncWrap::ProviderType provider = static_cast(providerId->Int32Value()); - - int64_t parentUidVal = 0; - if (!parentUid.IsEmpty() && !parentUid->IsUndefined() && parentUid->IsNumber()) { - parentUidVal = parentUid->IntegerValue(); - } - - bool didRunInit = FireAsyncInitCallbacksInternal( - env, - uid->IntegerValue(), - obj, - parentUidVal, - parentObj, - provider, - (AsyncWrap*)nullptr); - - args.GetReturnValue().Set(didRunInit); -} - bool AsyncWrap::FireAsyncInitCallbacks( Environment* env, int64_t uid, @@ -243,7 +220,7 @@ void AsyncWrap::FireAsyncPreCallbacks( v8::Local uid, v8::Local obj) { - env->set_current_async_wrap_uid(uid->IntegerValue()); + env->async_hooks()->set_current_async_wrap_uid(uid->IntegerValue()); if (ranInitCallbacks) { Local pre_fn = env->async_hooks_pre_function(); @@ -259,14 +236,6 @@ void AsyncWrap::FireAsyncPreCallbacks( } } -static void NotifyAsyncStartFromJS(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - v8::Local ranInitCallbacks = args[0].As(); - v8::Local uid = args[1].As(); - v8::Local obj = args[2].As(); - AsyncWrap::FireAsyncPreCallbacks(env, ranInitCallbacks->Value(), uid, obj); -} - void AsyncWrap::FireAsyncPostCallbacks(Environment* env, bool ranInitCallback, v8::Local uid, v8::Local obj, v8::Local didUserCodeThrow) { if (ranInitCallback) { @@ -283,17 +252,7 @@ void AsyncWrap::FireAsyncPostCallbacks(Environment* env, bool ranInitCallback, v } } - env->set_current_async_wrap_uid(0); -} - -static void NotifyAsyncEndFromJS(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - v8::Local ranInitCallbacks = args[0].As(); - v8::Local uid = args[1].As(); - v8::Local obj = args[2].As(); - v8::Local didUserCodeThrow = args[3].As(); - AsyncWrap::FireAsyncPostCallbacks(env, ranInitCallbacks->Value(), uid, obj, didUserCodeThrow); - AsyncWrap::FireAsyncDestroyCallbacks(env, ranInitCallbacks->BooleanValue(), uid); + env->async_hooks()->set_current_async_wrap_uid(0); } void AsyncWrap::FireAsyncDestroyCallbacks(Environment* env, bool ranInitCallbacks, v8::Local uid) { @@ -349,12 +308,18 @@ static void SetupHooks(const FunctionCallbackInfo& args) { env->set_async_hooks_post_function(post_v.As()); if (destroy_v->IsFunction()) env->set_async_hooks_destroy_function(destroy_v.As()); + + env->async_hooks_callbacks_objects()->Set(0, fn_obj); } +static void GetAsyncCallbacksFromJS(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + args.GetReturnValue().Set(env->async_hooks_callbacks_objects()); +} static void Initialize(Local target, - Local unused, - Local context) { + Local unused, + Local context) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); HandleScope scope(isolate); @@ -362,11 +327,10 @@ static void Initialize(Local target, env->SetMethod(target, "setupHooks", SetupHooks); env->SetMethod(target, "disable", DisableHooksJS); env->SetMethod(target, "enable", EnableHooksJS); - env->SetMethod(target, "getCurrentAsyncId", GetCurrentAsyncId); - env->SetMethod(target, "getNextAsyncId", GetNextAsyncId); - env->SetMethod(target, "notifyAsyncEnqueue", NotifyAsyncEnqueueFromJS); - env->SetMethod(target, "notifyAsyncStart", NotifyAsyncStartFromJS); - env->SetMethod(target, "notifyAsyncEnd", NotifyAsyncEndFromJS); + env->SetMethod(target, "getCurrentAsyncIdArray", GetCurrentAsyncIdArrayFromJS); + env->SetMethod(target, "getNextAsyncIdArray", GetNextAsyncIdArrayFromJS); + env->SetMethod(target, "getAsyncHookFields", GetAsyncHookFieldsFromJS); + env->SetMethod(target, "getAsyncCallbacks", GetAsyncCallbacksFromJS); Local async_providers = Object::New(isolate); #define V(PROVIDER) \ @@ -380,6 +344,7 @@ static void Initialize(Local target, env->set_async_hooks_pre_function(Local()); env->set_async_hooks_post_function(Local()); env->set_async_hooks_destroy_function(Local()); + env->set_async_hooks_callbacks_objects(v8::Array::New(env->isolate(), 0)); } diff --git a/src/env-inl.h b/src/env-inl.h index bf21d881579c18..3714f177d61e1d 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -81,8 +81,49 @@ inline v8::Isolate* Environment::IsolateData::isolate() const { return isolate_; } -inline Environment::AsyncHooks::AsyncHooks() { - for (int i = 0; i < kFieldsCount; i++) fields_[i] = 0; +inline Environment::AsyncHooks::AsyncHooks(Environment* env) + : async_wrap_current_uid_(0), + async_wrap_counter_uid_(0), + env_(env) { + + for (int i = 0; i < kFieldsCount; i++) { + fields_[i] = 0; + } + + v8::HandleScope handle_scope(env_->isolate()); + + // set up int array for returing async_wrap_counter_uid_ value + { + const size_t array_length1 = sizeof(async_wrap_counter_uid_) / sizeof(int32_t); + static_assert(array_length1 == 2, "async_wrap_counter_uid_ unexpected size"); + v8::Local ab1 = + v8::ArrayBuffer::New(this->env_->isolate(), &async_wrap_counter_uid_, sizeof(async_wrap_counter_uid_)); + v8::Local ua1 = + v8::Uint32Array::New(ab1, 0, array_length1); + this->async_wrap_next_id_array_.Reset(this->env_->isolate(), ua1); + } + + // set up int array for returing async_wrap_current_uid_ value + { + const size_t array_length2 = sizeof(async_wrap_current_uid_) / sizeof(int32_t); + static_assert(array_length2 == 2, "async_wrap_current_uid_ unexpected size"); + v8::Local ab2 = + v8::ArrayBuffer::New(this->env_->isolate(), &async_wrap_current_uid_, sizeof(async_wrap_current_uid_)); + v8::Local ua2 = + v8::Uint32Array::New(ab2, 0, array_length2); + this->async_wrap_current_id_array_.Reset(this->env_->isolate(), ua2); + } + + // set up int array for returing async_hooks "fields" array + { + const size_t array_length3 = kFieldsCount; + v8::Local ab3 = + v8::ArrayBuffer::New(this->env_->isolate(), fields_, sizeof(*fields()) * kFieldsCount); + v8::Local ua3 = + v8::Uint32Array::New(ab3, 0, array_length3); + this->async_wrap_fields_array_.Reset(this->env_->isolate(), ua3); + } + } inline uint32_t* Environment::AsyncHooks::fields() { @@ -101,6 +142,30 @@ inline void Environment::AsyncHooks::set_enable_callbacks(uint32_t flag) { fields_[kEnableCallbacks] = flag; } +inline int64_t Environment::AsyncHooks::get_next_async_wrap_uid() { + return ++async_wrap_counter_uid_; +} + +inline int64_t Environment::AsyncHooks::get_current_async_wrap_uid() { + return this->async_wrap_current_uid_; +} + +inline v8::Local Environment::AsyncHooks::get_current_async_id_array() { + return async_wrap_current_id_array_.Get(this->env_->isolate()); +} + +inline v8::Local Environment::AsyncHooks::get_next_async_id_array() { + return async_wrap_next_id_array_.Get(this->env_->isolate()); +} + +inline v8::Local Environment::AsyncHooks::get_fields_array() { + return async_wrap_fields_array_.Get(this->env_->isolate()); +} + +inline void Environment::AsyncHooks::set_current_async_wrap_uid(int64_t value) { + this->async_wrap_current_uid_ = value; +} + inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment* env) : env_(env) { env_->makecallback_cntr_++; @@ -155,6 +220,7 @@ inline void Environment::TickInfo::set_index(uint32_t value) { fields_[kIndex] = value; } + inline Environment::ArrayBufferAllocatorInfo::ArrayBufferAllocatorInfo() { for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0; @@ -216,13 +282,12 @@ inline Environment::Environment(v8::Local context, uv_loop_t* loop) : isolate_(context->GetIsolate()), isolate_data_(IsolateData::GetOrCreate(context->GetIsolate(), loop)), + async_hooks_(this), timer_base_(uv_now(loop)), using_domains_(false), printed_error_(false), trace_sync_io_(false), makecallback_cntr_(0), - async_wrap_counter_uid_(0), - async_wrap_current_uid_(0), debugger_agent_(this), http_parser_buffer_(nullptr), context_(context->GetIsolate(), context) { @@ -373,18 +438,6 @@ inline void Environment::set_trace_sync_io(bool value) { trace_sync_io_ = value; } -inline int64_t Environment::get_next_async_wrap_uid() { - return ++async_wrap_counter_uid_; -} - -inline int64_t Environment::get_current_async_wrap_uid() { - return this->async_wrap_current_uid_; -} - -inline void Environment::set_current_async_wrap_uid(int64_t value) { - this->async_wrap_current_uid_ = value; -} - inline uint32_t* Environment::heap_statistics_buffer() const { CHECK_NE(heap_statistics_buffer_, nullptr); return heap_statistics_buffer_; diff --git a/src/env.h b/src/env.h index 12efca9f7c7237..ea26b056837199 100644 --- a/src/env.h +++ b/src/env.h @@ -254,6 +254,7 @@ namespace node { V(async_hooks_init_function, v8::Function) \ V(async_hooks_post_function, v8::Function) \ V(async_hooks_pre_function, v8::Function) \ + V(async_hooks_callbacks_objects, v8::Array) \ V(binding_cache_object, v8::Object) \ V(buffer_constructor_function, v8::Function) \ V(buffer_prototype_object, v8::Object) \ @@ -300,10 +301,22 @@ class Environment { inline int fields_count() const; inline bool callbacks_enabled(); inline void set_enable_callbacks(uint32_t flag); + inline int64_t get_next_async_wrap_uid(); + inline int64_t get_current_async_wrap_uid(); + inline void set_current_async_wrap_uid(int64_t value); + inline v8::Local get_current_async_id_array(); + inline v8::Local get_next_async_id_array(); + inline v8::Local get_fields_array(); private: friend class Environment; // So we can call the constructor. - inline AsyncHooks(); + inline AsyncHooks(Environment* env); + + int64_t async_wrap_counter_uid_; + int64_t async_wrap_current_uid_; + v8::Persistent async_wrap_current_id_array_; + v8::Persistent async_wrap_next_id_array_; + v8::Persistent async_wrap_fields_array_; enum Fields { // Set this to not zero if the init hook should be called. @@ -312,6 +325,7 @@ class Environment { }; uint32_t fields_[kFieldsCount]; + Environment* env_; DISALLOW_COPY_AND_ASSIGN(AsyncHooks); }; @@ -473,10 +487,6 @@ class Environment { void PrintSyncTrace() const; inline void set_trace_sync_io(bool value); - inline int64_t get_next_async_wrap_uid(); - inline int64_t get_current_async_wrap_uid(); - inline void set_current_async_wrap_uid(int64_t value); - inline uint32_t* heap_statistics_buffer() const; inline void set_heap_statistics_buffer(uint32_t* pointer); @@ -579,8 +589,7 @@ class Environment { bool printed_error_; bool trace_sync_io_; size_t makecallback_cntr_; - int64_t async_wrap_counter_uid_; - int64_t async_wrap_current_uid_; + debugger::Agent debugger_agent_; HandleWrapQueue handle_wrap_queue_; diff --git a/test/parallel/test-async-wrap-nextTick.js b/test/parallel/test-async-wrap-nextTick.js index d22fd86b246834..1c495c79db7941 100644 --- a/test/parallel/test-async-wrap-nextTick.js +++ b/test/parallel/test-async-wrap-nextTick.js @@ -4,13 +4,14 @@ require('../common'); require('console'); const assert = require('assert'); const async_wrap = process.binding('async_wrap'); +const async_wrap_module = require('async_wrap'); const storage = new Map(); async_wrap.setupHooks({ init, pre, post, destroy }); async_wrap.enable(); function init(uid) { - assert.notStrictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.notStrictEqual(async_wrap_module.getCurrentAsyncId(), uid); storage.set(uid, { init: true, pre: false, @@ -23,24 +24,24 @@ function init(uid) { } function pre(uid) { - assert.strictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.strictEqual(async_wrap_module.getCurrentAsyncId(), uid); assert.strictEqual(this.uid, uid); storage.get(uid).pre = true; } function post(uid) { - assert.strictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.strictEqual(async_wrap_module.getCurrentAsyncId(), uid); assert.strictEqual(this.uid, uid); storage.get(uid).post = true; } function destroy(uid) { - assert.notStrictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.notStrictEqual(async_wrap_module.getCurrentAsyncId(), uid); storage.get(uid).destroy = true; } function validateAsyncCallbacksDuringNextTickCallback() { - const currentAsyncId = async_wrap.getCurrentAsyncId(); + const currentAsyncId = async_wrap_module.getCurrentAsyncId(); assert.strictEqual(storage.get(currentAsyncId).init, true); assert.strictEqual(storage.get(currentAsyncId).pre, true); assert.strictEqual(storage.get(currentAsyncId).post, false); @@ -54,19 +55,19 @@ let id4 = 0; process.nextTick(function tick1() { - id1 = async_wrap.getCurrentAsyncId(); + id1 = async_wrap_module.getCurrentAsyncId(); assert.notStrictEqual(id1, 0); assert.strictEqual(storage.size, 1); validateAsyncCallbacksDuringNextTickCallback(); process.nextTick(function tick2() { - id2 = async_wrap.getCurrentAsyncId(); + id2 = async_wrap_module.getCurrentAsyncId(); assert.notStrictEqual(id2, 0); assert.notStrictEqual(id1, id2); validateAsyncCallbacksDuringNextTickCallback(); process.nextTick(function tick3() { - id3 = async_wrap.getCurrentAsyncId(); + id3 = async_wrap_module.getCurrentAsyncId(); assert.notStrictEqual(id3, 0); assert.notStrictEqual(id1, id3); assert.notStrictEqual(id2, id3); @@ -76,7 +77,7 @@ process.nextTick(function tick1() { // so init shouldn't fire process.nextTick(function tick4() { // but currentAsyncId should still be set up - id4 = async_wrap.getCurrentAsyncId(); + id4 = async_wrap_module.getCurrentAsyncId(); assert.notStrictEqual(id4, 0); assert.notStrictEqual(id1, id4); assert.notStrictEqual(id2, id4); @@ -89,11 +90,11 @@ process.nextTick(function tick1() { async_wrap.disable(); assert.strictEqual(storage.size, 3); - assert.strictEqual(id2, async_wrap.getCurrentAsyncId()); + assert.strictEqual(id2, async_wrap_module.getCurrentAsyncId()); }); assert.strictEqual(storage.size, 2); - assert.strictEqual(id1, async_wrap.getCurrentAsyncId()); + assert.strictEqual(id1, async_wrap_module.getCurrentAsyncId()); }); process.once('exit', function() { diff --git a/test/parallel/test-async-wrap-uid.js b/test/parallel/test-async-wrap-uid.js index bc1e475ad53216..8d5d4da7db9375 100644 --- a/test/parallel/test-async-wrap-uid.js +++ b/test/parallel/test-async-wrap-uid.js @@ -4,13 +4,14 @@ require('../common'); const fs = require('fs'); const assert = require('assert'); const async_wrap = process.binding('async_wrap'); +const async_wrap_module = require('async_wrap'); const storage = new Map(); async_wrap.setupHooks({ init, pre, post, destroy }); async_wrap.enable(); function init(uid) { - assert.notStrictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.notStrictEqual(async_wrap_module.getCurrentAsyncId(), uid); storage.set(uid, { init: true, pre: false, @@ -20,17 +21,17 @@ function init(uid) { } function pre(uid) { - assert.strictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.strictEqual(async_wrap_module.getCurrentAsyncId(), uid); storage.get(uid).pre = true; } function post(uid) { - assert.strictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.strictEqual(async_wrap_module.getCurrentAsyncId(), uid); storage.get(uid).post = true; } function destroy(uid) { - assert.notStrictEqual(async_wrap.getCurrentAsyncId(), uid); + assert.notStrictEqual(async_wrap_module.getCurrentAsyncId(), uid); storage.get(uid).destroy = true; } @@ -61,6 +62,36 @@ process.once('exit', function() { }); // verify each call to next ID produces an increasing uid. -var nextId = async_wrap.getNextAsyncId(); -var nextId2 = async_wrap.getNextAsyncId(); +var nextId = async_wrap_module.incrementNextAsyncId(); +var nextId2 = async_wrap_module.incrementNextAsyncId(); assert.strictEqual(nextId + 1, nextId2); + +// verify getCurrentAsyncId() & setCurrentAsyncId() work as expected +var expectedId = 0; +async_wrap_module.setCurrentAsyncId(expectedId); +assert.strictEqual(async_wrap_module.getCurrentAsyncId(), expectedId); + +expectedId = 1; +async_wrap_module.setCurrentAsyncId(expectedId); +assert.strictEqual(async_wrap_module.getCurrentAsyncId(), expectedId); + +// low-order 32 bits set +expectedId = 0xFFFFFFFF; +async_wrap_module.setCurrentAsyncId(expectedId); +assert.strictEqual(async_wrap_module.getCurrentAsyncId(), expectedId); + +// low-order 36 bits set +expectedId = 0xFFFFFFFFF; +async_wrap_module.setCurrentAsyncId(expectedId); +assert.strictEqual(async_wrap_module.getCurrentAsyncId(), expectedId); + +// negative numbers should throw +var didThrow = false; +try { + async_wrap_module.setCurrentAsyncId(-1); +} +catch (err) { + didThrow = true; +} +assert.strictEqual(async_wrap_module.getCurrentAsyncId(), expectedId); +assert.strictEqual(didThrow, true);