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 529645aa8d65c4..50e5ec1a8bb0a6 100644 --- a/lib/internal/process/next_tick.js +++ b/lib/internal/process/next_tick.js @@ -3,6 +3,8 @@ exports.setup = setupNextTick; function setupNextTick() { + const async_wrap = require('async_wrap'); + const promises = require('internal/process/promises'); const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks); var nextTickQueue = []; @@ -85,19 +87,40 @@ 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; + 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); + 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(); + } tickDone(); _runMicrotasks(); @@ -116,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) @@ -135,6 +177,10 @@ function setupNextTick() { this.callback = c; this.domain = process.domain || null; this.args = args; + this.asyncId = async_wrap.incrementNextAsyncId(); + if (async_wrap.callbacksEnabled()) { + this.asyncState = async_wrap.notifyAsyncEnqueue(this.asyncId); + } } function nextTick(callback) { @@ -154,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 cf7024e7e31461..8f7e1403d5e9f4 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->async_hooks()->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..930ae27979e479 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,161 @@ static void DisableHooksJS(const FunctionCallbackInfo& args) { env->async_hooks()->set_enable_callbacks(0); } +static void GetCurrentAsyncIdArrayFromJS(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + args.GetReturnValue().Set(env->async_hooks()->get_current_async_id_array()); +} + +static void GetNextAsyncIdArrayFromJS(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + 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( + 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; +} + +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->async_hooks()->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); + } + } + } +} + +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->async_hooks()->set_current_async_wrap_uid(0); +} + +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 +285,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"); @@ -152,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); @@ -165,6 +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, "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) \ @@ -178,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)); } @@ -198,7 +365,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 +392,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..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,12 +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_uid_(0), debugger_agent_(this), http_parser_buffer_(nullptr), context_(context->GetIsolate(), context) { @@ -372,10 +438,6 @@ 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 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 0590a8a434b104..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,8 +487,6 @@ class Environment { void PrintSyncTrace() const; inline void set_trace_sync_io(bool value); - inline int64_t get_async_wrap_uid(); - inline uint32_t* heap_statistics_buffer() const; inline void set_heap_statistics_buffer(uint32_t* pointer); @@ -577,7 +589,7 @@ class Environment { bool printed_error_; bool trace_sync_io_; size_t makecallback_cntr_; - int64_t async_wrap_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..1c495c79db7941 --- /dev/null +++ b/test/parallel/test-async-wrap-nextTick.js @@ -0,0 +1,115 @@ +'use strict'; + +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_module.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_module.getCurrentAsyncId(), uid); + assert.strictEqual(this.uid, uid); + storage.get(uid).pre = true; +} + +function post(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_module.getCurrentAsyncId(), uid); + storage.get(uid).destroy = true; +} + +function validateAsyncCallbacksDuringNextTickCallback() { + 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); + 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_module.getCurrentAsyncId(); + assert.notStrictEqual(id1, 0); + assert.strictEqual(storage.size, 1); + validateAsyncCallbacksDuringNextTickCallback(); + + process.nextTick(function tick2() { + id2 = async_wrap_module.getCurrentAsyncId(); + assert.notStrictEqual(id2, 0); + assert.notStrictEqual(id1, id2); + validateAsyncCallbacksDuringNextTickCallback(); + + process.nextTick(function tick3() { + id3 = async_wrap_module.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_module.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_module.getCurrentAsyncId()); + }); + + assert.strictEqual(storage.size, 2); + assert.strictEqual(id1, async_wrap_module.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..8d5d4da7db9375 100644 --- a/test/parallel/test-async-wrap-uid.js +++ b/test/parallel/test-async-wrap-uid.js @@ -4,12 +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_module.getCurrentAsyncId(), uid); storage.set(uid, { init: true, pre: false, @@ -19,14 +21,17 @@ function init(uid) { } function pre(uid) { + assert.strictEqual(async_wrap_module.getCurrentAsyncId(), uid); storage.get(uid).pre = true; } function post(uid) { + assert.strictEqual(async_wrap_module.getCurrentAsyncId(), uid); storage.get(uid).post = true; } function destroy(uid) { + assert.notStrictEqual(async_wrap_module.getCurrentAsyncId(), uid); storage.get(uid).destroy = true; } @@ -55,3 +60,38 @@ process.once('exit', function() { }); } }); + +// verify each call to next ID produces an increasing uid. +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);