From 7f2d1b3cbd88a98cbe011b09a2368c0a9697ddee Mon Sep 17 00:00:00 2001 From: Mike Kaufman Date: Wed, 6 Apr 2016 10:57:38 -0700 Subject: [PATCH] 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);