async_hooks: use typed array stack as fast path
- Communicate the current async stack length through a typed array field rather than a native binding method - Add a new fixed-size `async_ids_fast_stack` typed array that contains the async ID stack up to a fixed limit. This increases performance noticeably, since most of the time the async ID stack will not be more than a handful of levels deep. - Make the JS `pushAsyncIds()` and `popAsyncIds()` functions do the same thing as the native ones if the fast path is applicable. Benchmarks: $ ./node benchmark/compare.js --new ./node --old ./node-master --runs 10 --filter next-tick process | Rscript benchmark/compare.R [00:03:25|% 100| 6/6 files | 20/20 runs | 1/1 configs]: Done improvement confidence p.value process/next-tick-breadth-args.js millions=4 19.72 % *** 3.013913e-06 process/next-tick-breadth.js millions=4 27.33 % *** 5.847983e-11 process/next-tick-depth-args.js millions=12 40.08 % *** 1.237127e-13 process/next-tick-depth.js millions=12 77.27 % *** 1.413290e-11 process/next-tick-exec-args.js millions=5 13.58 % *** 1.245180e-07 process/next-tick-exec.js millions=5 16.80 % *** 2.961386e-07 PR-URL: https://github.com/nodejs/node/pull/17780 Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
df30fd586d
commit
83e5215a4e
@ -19,6 +19,12 @@ const async_wrap = process.binding('async_wrap');
|
|||||||
* retrieving the triggerAsyncId value is passing directly to the
|
* retrieving the triggerAsyncId value is passing directly to the
|
||||||
* constructor -> value set in kDefaultTriggerAsyncId -> executionAsyncId of
|
* constructor -> value set in kDefaultTriggerAsyncId -> executionAsyncId of
|
||||||
* the current resource.
|
* the current resource.
|
||||||
|
*
|
||||||
|
* async_ids_fast_stack is a Float64Array that contains part of the async ID
|
||||||
|
* stack. Each pushAsyncIds() call adds two doubles to it, and each
|
||||||
|
* popAsyncIds() call removes two doubles from it.
|
||||||
|
* It has a fixed size, so if that is exceeded, calls to the native
|
||||||
|
* side are used instead in pushAsyncIds() and popAsyncIds().
|
||||||
*/
|
*/
|
||||||
const { async_hook_fields, async_id_fields } = async_wrap;
|
const { async_hook_fields, async_id_fields } = async_wrap;
|
||||||
// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
|
// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
|
||||||
@ -26,7 +32,7 @@ const { async_hook_fields, async_id_fields } = async_wrap;
|
|||||||
// current execution stack. This is unwound as each resource exits. In the case
|
// current execution stack. This is unwound as each resource exits. In the case
|
||||||
// of a fatal exception this stack is emptied after calling each hook's after()
|
// of a fatal exception this stack is emptied after calling each hook's after()
|
||||||
// callback.
|
// callback.
|
||||||
const { pushAsyncIds, popAsyncIds } = async_wrap;
|
const { pushAsyncIds: pushAsyncIds_, popAsyncIds: popAsyncIds_ } = async_wrap;
|
||||||
// For performance reasons, only track Proimses when a hook is enabled.
|
// For performance reasons, only track Proimses when a hook is enabled.
|
||||||
const { enablePromiseHook, disablePromiseHook } = async_wrap;
|
const { enablePromiseHook, disablePromiseHook } = async_wrap;
|
||||||
// Properties in active_hooks are used to keep track of the set of hooks being
|
// Properties in active_hooks are used to keep track of the set of hooks being
|
||||||
@ -60,8 +66,8 @@ const active_hooks = {
|
|||||||
// async execution. These are tracked so if the user didn't include callbacks
|
// async execution. These are tracked so if the user didn't include callbacks
|
||||||
// for a given step, that step can bail out early.
|
// for a given step, that step can bail out early.
|
||||||
const { kInit, kBefore, kAfter, kDestroy, kPromiseResolve,
|
const { kInit, kBefore, kAfter, kDestroy, kPromiseResolve,
|
||||||
kCheck, kExecutionAsyncId, kAsyncIdCounter,
|
kCheck, kExecutionAsyncId, kAsyncIdCounter, kTriggerAsyncId,
|
||||||
kDefaultTriggerAsyncId } = async_wrap.constants;
|
kDefaultTriggerAsyncId, kStackLength } = async_wrap.constants;
|
||||||
|
|
||||||
// Used in AsyncHook and AsyncResource.
|
// Used in AsyncHook and AsyncResource.
|
||||||
const init_symbol = Symbol('init');
|
const init_symbol = Symbol('init');
|
||||||
@ -329,6 +335,38 @@ function emitDestroyScript(asyncId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This is the equivalent of the native push_async_ids() call.
|
||||||
|
function pushAsyncIds(asyncId, triggerAsyncId) {
|
||||||
|
const offset = async_hook_fields[kStackLength];
|
||||||
|
if (offset * 2 >= async_wrap.async_ids_stack.length)
|
||||||
|
return pushAsyncIds_(asyncId, triggerAsyncId);
|
||||||
|
async_wrap.async_ids_stack[offset * 2] = async_id_fields[kExecutionAsyncId];
|
||||||
|
async_wrap.async_ids_stack[offset * 2 + 1] = async_id_fields[kTriggerAsyncId];
|
||||||
|
async_hook_fields[kStackLength]++;
|
||||||
|
async_id_fields[kExecutionAsyncId] = asyncId;
|
||||||
|
async_id_fields[kTriggerAsyncId] = triggerAsyncId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This is the equivalent of the native pop_async_ids() call.
|
||||||
|
function popAsyncIds(asyncId) {
|
||||||
|
if (async_hook_fields[kStackLength] === 0) return false;
|
||||||
|
const stackLength = async_hook_fields[kStackLength];
|
||||||
|
|
||||||
|
if (async_hook_fields[kCheck] > 0 &&
|
||||||
|
async_id_fields[kExecutionAsyncId] !== asyncId) {
|
||||||
|
// Do the same thing as the native code (i.e. crash hard).
|
||||||
|
return popAsyncIds_(asyncId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = stackLength - 1;
|
||||||
|
async_id_fields[kExecutionAsyncId] = async_wrap.async_ids_stack[2 * offset];
|
||||||
|
async_id_fields[kTriggerAsyncId] = async_wrap.async_ids_stack[2 * offset + 1];
|
||||||
|
async_hook_fields[kStackLength] = offset;
|
||||||
|
return offset > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// Private API
|
// Private API
|
||||||
getHookArrays,
|
getHookArrays,
|
||||||
|
6
lib/internal/bootstrap_node.js
vendored
6
lib/internal/bootstrap_node.js
vendored
@ -367,9 +367,9 @@
|
|||||||
// Arrays containing hook flags and ids for async_hook calls.
|
// Arrays containing hook flags and ids for async_hook calls.
|
||||||
const { async_hook_fields, async_id_fields } = async_wrap;
|
const { async_hook_fields, async_id_fields } = async_wrap;
|
||||||
// Internal functions needed to manipulate the stack.
|
// Internal functions needed to manipulate the stack.
|
||||||
const { clearAsyncIdStack, asyncIdStackSize } = async_wrap;
|
const { clearAsyncIdStack } = async_wrap;
|
||||||
const { kAfter, kExecutionAsyncId,
|
const { kAfter, kExecutionAsyncId,
|
||||||
kDefaultTriggerAsyncId } = async_wrap.constants;
|
kDefaultTriggerAsyncId, kStackLength } = async_wrap.constants;
|
||||||
|
|
||||||
process._fatalException = function(er) {
|
process._fatalException = function(er) {
|
||||||
var caught;
|
var caught;
|
||||||
@ -407,7 +407,7 @@
|
|||||||
do {
|
do {
|
||||||
NativeModule.require('internal/async_hooks').emitAfter(
|
NativeModule.require('internal/async_hooks').emitAfter(
|
||||||
async_id_fields[kExecutionAsyncId]);
|
async_id_fields[kExecutionAsyncId]);
|
||||||
} while (asyncIdStackSize() > 0);
|
} while (async_hook_fields[kStackLength] > 0);
|
||||||
// Or completely empty the id stack.
|
// Or completely empty the id stack.
|
||||||
} else {
|
} else {
|
||||||
clearAsyncIdStack();
|
clearAsyncIdStack();
|
||||||
|
@ -95,6 +95,21 @@ class AliasedBuffer {
|
|||||||
js_array_.Reset();
|
js_array_.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AliasedBuffer& operator=(AliasedBuffer&& that) {
|
||||||
|
this->~AliasedBuffer();
|
||||||
|
isolate_ = that.isolate_;
|
||||||
|
count_ = that.count_;
|
||||||
|
byte_offset_ = that.byte_offset_;
|
||||||
|
buffer_ = that.buffer_;
|
||||||
|
free_buffer_ = that.free_buffer_;
|
||||||
|
|
||||||
|
js_array_.Reset(isolate_, that.js_array_.Get(isolate_));
|
||||||
|
|
||||||
|
that.buffer_ = nullptr;
|
||||||
|
that.js_array_.Reset();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class that is returned from operator[] to support assignment into
|
* Helper class that is returned from operator[] to support assignment into
|
||||||
* a specified location.
|
* a specified location.
|
||||||
@ -111,11 +126,17 @@ class AliasedBuffer {
|
|||||||
index_(that.index_) {
|
index_(that.index_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Reference& operator=(const NativeT &val) {
|
template <typename T>
|
||||||
|
inline Reference& operator=(const T& val) {
|
||||||
aliased_buffer_->SetValue(index_, val);
|
aliased_buffer_->SetValue(index_, val);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is not caught by the template operator= above.
|
||||||
|
inline Reference& operator=(const Reference& val) {
|
||||||
|
return *this = static_cast<NativeT>(val);
|
||||||
|
}
|
||||||
|
|
||||||
operator NativeT() const {
|
operator NativeT() const {
|
||||||
return aliased_buffer_->GetValue(index_);
|
return aliased_buffer_->GetValue(index_);
|
||||||
}
|
}
|
||||||
@ -186,8 +207,12 @@ class AliasedBuffer {
|
|||||||
return GetValue(index);
|
return GetValue(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t Length() const {
|
||||||
|
return count_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
v8::Isolate* const isolate_;
|
v8::Isolate* isolate_;
|
||||||
size_t count_;
|
size_t count_;
|
||||||
size_t byte_offset_;
|
size_t byte_offset_;
|
||||||
NativeT* buffer_;
|
NativeT* buffer_;
|
||||||
|
@ -467,13 +467,6 @@ void AsyncWrap::PopAsyncIds(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AsyncWrap::AsyncIdStackSize(const FunctionCallbackInfo<Value>& args) {
|
|
||||||
Environment* env = Environment::GetCurrent(args);
|
|
||||||
args.GetReturnValue().Set(
|
|
||||||
static_cast<double>(env->async_hooks()->stack_size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AsyncWrap::ClearAsyncIdStack(const FunctionCallbackInfo<Value>& args) {
|
void AsyncWrap::ClearAsyncIdStack(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
env->async_hooks()->clear_async_id_stack();
|
env->async_hooks()->clear_async_id_stack();
|
||||||
@ -512,7 +505,6 @@ void AsyncWrap::Initialize(Local<Object> target,
|
|||||||
env->SetMethod(target, "setupHooks", SetupHooks);
|
env->SetMethod(target, "setupHooks", SetupHooks);
|
||||||
env->SetMethod(target, "pushAsyncIds", PushAsyncIds);
|
env->SetMethod(target, "pushAsyncIds", PushAsyncIds);
|
||||||
env->SetMethod(target, "popAsyncIds", PopAsyncIds);
|
env->SetMethod(target, "popAsyncIds", PopAsyncIds);
|
||||||
env->SetMethod(target, "asyncIdStackSize", AsyncIdStackSize);
|
|
||||||
env->SetMethod(target, "clearAsyncIdStack", ClearAsyncIdStack);
|
env->SetMethod(target, "clearAsyncIdStack", ClearAsyncIdStack);
|
||||||
env->SetMethod(target, "queueDestroyAsyncId", QueueDestroyAsyncId);
|
env->SetMethod(target, "queueDestroyAsyncId", QueueDestroyAsyncId);
|
||||||
env->SetMethod(target, "enablePromiseHook", EnablePromiseHook);
|
env->SetMethod(target, "enablePromiseHook", EnablePromiseHook);
|
||||||
@ -550,6 +542,10 @@ void AsyncWrap::Initialize(Local<Object> target,
|
|||||||
"async_id_fields",
|
"async_id_fields",
|
||||||
env->async_hooks()->async_id_fields().GetJSArray());
|
env->async_hooks()->async_id_fields().GetJSArray());
|
||||||
|
|
||||||
|
target->Set(context,
|
||||||
|
env->async_ids_stack_string(),
|
||||||
|
env->async_hooks()->async_ids_stack().GetJSArray()).FromJust();
|
||||||
|
|
||||||
Local<Object> constants = Object::New(isolate);
|
Local<Object> constants = Object::New(isolate);
|
||||||
#define SET_HOOKS_CONSTANT(name) \
|
#define SET_HOOKS_CONSTANT(name) \
|
||||||
FORCE_SET_TARGET_FIELD( \
|
FORCE_SET_TARGET_FIELD( \
|
||||||
@ -566,6 +562,7 @@ void AsyncWrap::Initialize(Local<Object> target,
|
|||||||
SET_HOOKS_CONSTANT(kTriggerAsyncId);
|
SET_HOOKS_CONSTANT(kTriggerAsyncId);
|
||||||
SET_HOOKS_CONSTANT(kAsyncIdCounter);
|
SET_HOOKS_CONSTANT(kAsyncIdCounter);
|
||||||
SET_HOOKS_CONSTANT(kDefaultTriggerAsyncId);
|
SET_HOOKS_CONSTANT(kDefaultTriggerAsyncId);
|
||||||
|
SET_HOOKS_CONSTANT(kStackLength);
|
||||||
#undef SET_HOOKS_CONSTANT
|
#undef SET_HOOKS_CONSTANT
|
||||||
FORCE_SET_TARGET_FIELD(target, "constants", constants);
|
FORCE_SET_TARGET_FIELD(target, "constants", constants);
|
||||||
|
|
||||||
@ -595,6 +592,7 @@ void AsyncWrap::Initialize(Local<Object> target,
|
|||||||
env->set_async_hooks_after_function(Local<Function>());
|
env->set_async_hooks_after_function(Local<Function>());
|
||||||
env->set_async_hooks_destroy_function(Local<Function>());
|
env->set_async_hooks_destroy_function(Local<Function>());
|
||||||
env->set_async_hooks_promise_resolve_function(Local<Function>());
|
env->set_async_hooks_promise_resolve_function(Local<Function>());
|
||||||
|
env->set_async_hooks_binding(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,7 +123,6 @@ class AsyncWrap : public BaseObject {
|
|||||||
static void GetAsyncId(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void GetAsyncId(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void PushAsyncIds(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void PushAsyncIds(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void PopAsyncIds(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void PopAsyncIds(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void AsyncIdStackSize(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
||||||
static void ClearAsyncIdStack(
|
static void ClearAsyncIdStack(
|
||||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void AsyncReset(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void AsyncReset(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
@ -53,11 +53,11 @@ inline MultiIsolatePlatform* IsolateData::platform() const {
|
|||||||
return platform_;
|
return platform_;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Environment::AsyncHooks::AsyncHooks(v8::Isolate* isolate)
|
inline Environment::AsyncHooks::AsyncHooks()
|
||||||
: isolate_(isolate),
|
: async_ids_stack_(env()->isolate(), 16 * 2),
|
||||||
fields_(isolate, kFieldsCount),
|
fields_(env()->isolate(), kFieldsCount),
|
||||||
async_id_fields_(isolate, kUidFieldsCount) {
|
async_id_fields_(env()->isolate(), kUidFieldsCount) {
|
||||||
v8::HandleScope handle_scope(isolate_);
|
v8::HandleScope handle_scope(env()->isolate());
|
||||||
|
|
||||||
// Always perform async_hooks checks, not just when async_hooks is enabled.
|
// Always perform async_hooks checks, not just when async_hooks is enabled.
|
||||||
// TODO(AndreasMadsen): Consider removing this for LTS releases.
|
// TODO(AndreasMadsen): Consider removing this for LTS releases.
|
||||||
@ -81,9 +81,9 @@ inline Environment::AsyncHooks::AsyncHooks(v8::Isolate* isolate)
|
|||||||
// strings can be retrieved quickly.
|
// strings can be retrieved quickly.
|
||||||
#define V(Provider) \
|
#define V(Provider) \
|
||||||
providers_[AsyncWrap::PROVIDER_ ## Provider].Set( \
|
providers_[AsyncWrap::PROVIDER_ ## Provider].Set( \
|
||||||
isolate_, \
|
env()->isolate(), \
|
||||||
v8::String::NewFromOneByte( \
|
v8::String::NewFromOneByte( \
|
||||||
isolate_, \
|
env()->isolate(), \
|
||||||
reinterpret_cast<const uint8_t*>(#Provider), \
|
reinterpret_cast<const uint8_t*>(#Provider), \
|
||||||
v8::NewStringType::kInternalized, \
|
v8::NewStringType::kInternalized, \
|
||||||
sizeof(#Provider) - 1).ToLocalChecked());
|
sizeof(#Provider) - 1).ToLocalChecked());
|
||||||
@ -101,8 +101,13 @@ Environment::AsyncHooks::async_id_fields() {
|
|||||||
return async_id_fields_;
|
return async_id_fields_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline AliasedBuffer<double, v8::Float64Array>&
|
||||||
|
Environment::AsyncHooks::async_ids_stack() {
|
||||||
|
return async_ids_stack_;
|
||||||
|
}
|
||||||
|
|
||||||
inline v8::Local<v8::String> Environment::AsyncHooks::provider_string(int idx) {
|
inline v8::Local<v8::String> Environment::AsyncHooks::provider_string(int idx) {
|
||||||
return providers_[idx].Get(isolate_);
|
return providers_[idx].Get(env()->isolate());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Environment::AsyncHooks::no_force_checks() {
|
inline void Environment::AsyncHooks::no_force_checks() {
|
||||||
@ -110,6 +115,11 @@ inline void Environment::AsyncHooks::no_force_checks() {
|
|||||||
fields_[kCheck] = fields_[kCheck] - 1;
|
fields_[kCheck] = fields_[kCheck] - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Environment* Environment::AsyncHooks::env() {
|
||||||
|
return Environment::ForAsyncHooks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember to keep this code aligned with pushAsyncIds() in JS.
|
||||||
inline void Environment::AsyncHooks::push_async_ids(double async_id,
|
inline void Environment::AsyncHooks::push_async_ids(double async_id,
|
||||||
double trigger_async_id) {
|
double trigger_async_id) {
|
||||||
// Since async_hooks is experimental, do only perform the check
|
// Since async_hooks is experimental, do only perform the check
|
||||||
@ -119,16 +129,21 @@ inline void Environment::AsyncHooks::push_async_ids(double async_id,
|
|||||||
CHECK_GE(trigger_async_id, -1);
|
CHECK_GE(trigger_async_id, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async_ids_stack_.push({ async_id_fields_[kExecutionAsyncId],
|
uint32_t offset = fields_[kStackLength];
|
||||||
async_id_fields_[kTriggerAsyncId] });
|
if (offset * 2 >= async_ids_stack_.Length())
|
||||||
|
grow_async_ids_stack();
|
||||||
|
async_ids_stack_[2 * offset] = async_id_fields_[kExecutionAsyncId];
|
||||||
|
async_ids_stack_[2 * offset + 1] = async_id_fields_[kTriggerAsyncId];
|
||||||
|
fields_[kStackLength] = fields_[kStackLength] + 1;
|
||||||
async_id_fields_[kExecutionAsyncId] = async_id;
|
async_id_fields_[kExecutionAsyncId] = async_id;
|
||||||
async_id_fields_[kTriggerAsyncId] = trigger_async_id;
|
async_id_fields_[kTriggerAsyncId] = trigger_async_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remember to keep this code aligned with popAsyncIds() in JS.
|
||||||
inline bool Environment::AsyncHooks::pop_async_id(double async_id) {
|
inline bool Environment::AsyncHooks::pop_async_id(double async_id) {
|
||||||
// In case of an exception then this may have already been reset, if the
|
// In case of an exception then this may have already been reset, if the
|
||||||
// stack was multiple MakeCallback()'s deep.
|
// stack was multiple MakeCallback()'s deep.
|
||||||
if (async_ids_stack_.empty()) return false;
|
if (fields_[kStackLength] == 0) return false;
|
||||||
|
|
||||||
// Ask for the async_id to be restored as a check that the stack
|
// Ask for the async_id to be restored as a check that the stack
|
||||||
// hasn't been corrupted.
|
// hasn't been corrupted.
|
||||||
@ -140,32 +155,27 @@ inline bool Environment::AsyncHooks::pop_async_id(double async_id) {
|
|||||||
"actual: %.f, expected: %.f)\n",
|
"actual: %.f, expected: %.f)\n",
|
||||||
async_id_fields_.GetValue(kExecutionAsyncId),
|
async_id_fields_.GetValue(kExecutionAsyncId),
|
||||||
async_id);
|
async_id);
|
||||||
Environment* env = Environment::GetCurrent(isolate_);
|
|
||||||
DumpBacktrace(stderr);
|
DumpBacktrace(stderr);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
if (!env->abort_on_uncaught_exception())
|
if (!env()->abort_on_uncaught_exception())
|
||||||
exit(1);
|
exit(1);
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
ABORT_NO_BACKTRACE();
|
ABORT_NO_BACKTRACE();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto async_ids = async_ids_stack_.top();
|
uint32_t offset = fields_[kStackLength] - 1;
|
||||||
async_ids_stack_.pop();
|
async_id_fields_[kExecutionAsyncId] = async_ids_stack_[2 * offset];
|
||||||
async_id_fields_[kExecutionAsyncId] = async_ids.async_id;
|
async_id_fields_[kTriggerAsyncId] = async_ids_stack_[2 * offset + 1];
|
||||||
async_id_fields_[kTriggerAsyncId] = async_ids.trigger_async_id;
|
fields_[kStackLength] = offset;
|
||||||
return !async_ids_stack_.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline size_t Environment::AsyncHooks::stack_size() {
|
return fields_[kStackLength] > 0;
|
||||||
return async_ids_stack_.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Environment::AsyncHooks::clear_async_id_stack() {
|
inline void Environment::AsyncHooks::clear_async_id_stack() {
|
||||||
while (!async_ids_stack_.empty())
|
|
||||||
async_ids_stack_.pop();
|
|
||||||
async_id_fields_[kExecutionAsyncId] = 0;
|
async_id_fields_[kExecutionAsyncId] = 0;
|
||||||
async_id_fields_[kTriggerAsyncId] = 0;
|
async_id_fields_[kTriggerAsyncId] = 0;
|
||||||
|
fields_[kStackLength] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Environment::AsyncHooks::DefaultTriggerAsyncIdScope
|
inline Environment::AsyncHooks::DefaultTriggerAsyncIdScope
|
||||||
@ -189,6 +199,11 @@ inline Environment::AsyncHooks::DefaultTriggerAsyncIdScope
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Environment* Environment::ForAsyncHooks(AsyncHooks* hooks) {
|
||||||
|
return ContainerOf(&Environment::async_hooks_, hooks);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment* env)
|
inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment* env)
|
||||||
: env_(env) {
|
: env_(env) {
|
||||||
env_->makecallback_cntr_++;
|
env_->makecallback_cntr_++;
|
||||||
@ -254,7 +269,6 @@ inline Environment::Environment(IsolateData* isolate_data,
|
|||||||
v8::Local<v8::Context> context)
|
v8::Local<v8::Context> context)
|
||||||
: isolate_(context->GetIsolate()),
|
: isolate_(context->GetIsolate()),
|
||||||
isolate_data_(isolate_data),
|
isolate_data_(isolate_data),
|
||||||
async_hooks_(context->GetIsolate()),
|
|
||||||
timer_base_(uv_now(isolate_data->event_loop())),
|
timer_base_(uv_now(isolate_data->event_loop())),
|
||||||
using_domains_(false),
|
using_domains_(false),
|
||||||
printed_error_(false),
|
printed_error_(false),
|
||||||
|
17
src/env.cc
17
src/env.cc
@ -403,4 +403,21 @@ void Environment::CollectUVExceptionInfo(v8::Local<v8::Value> object,
|
|||||||
syscall, message, path, dest);
|
syscall, message, path, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Environment::AsyncHooks::grow_async_ids_stack() {
|
||||||
|
const uint32_t old_capacity = async_ids_stack_.Length() / 2;
|
||||||
|
const uint32_t new_capacity = old_capacity * 1.5;
|
||||||
|
AliasedBuffer<double, v8::Float64Array> new_buffer(
|
||||||
|
env()->isolate(), new_capacity * 2);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < old_capacity * 2; ++i)
|
||||||
|
new_buffer[i] = async_ids_stack_[i];
|
||||||
|
async_ids_stack_ = std::move(new_buffer);
|
||||||
|
|
||||||
|
env()->async_hooks_binding()->Set(
|
||||||
|
env()->context(),
|
||||||
|
env()->async_ids_stack_string(),
|
||||||
|
async_ids_stack_.GetJSArray()).FromJust();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
26
src/env.h
26
src/env.h
@ -41,7 +41,6 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <stack>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
struct nghttp2_rcbuf;
|
struct nghttp2_rcbuf;
|
||||||
@ -100,6 +99,7 @@ class ModuleWrap;
|
|||||||
V(address_string, "address") \
|
V(address_string, "address") \
|
||||||
V(args_string, "args") \
|
V(args_string, "args") \
|
||||||
V(async, "async") \
|
V(async, "async") \
|
||||||
|
V(async_ids_stack_string, "async_ids_stack") \
|
||||||
V(buffer_string, "buffer") \
|
V(buffer_string, "buffer") \
|
||||||
V(bytes_string, "bytes") \
|
V(bytes_string, "bytes") \
|
||||||
V(bytes_parsed_string, "bytesParsed") \
|
V(bytes_parsed_string, "bytesParsed") \
|
||||||
@ -280,6 +280,7 @@ class ModuleWrap;
|
|||||||
V(async_hooks_before_function, v8::Function) \
|
V(async_hooks_before_function, v8::Function) \
|
||||||
V(async_hooks_after_function, v8::Function) \
|
V(async_hooks_after_function, v8::Function) \
|
||||||
V(async_hooks_promise_resolve_function, v8::Function) \
|
V(async_hooks_promise_resolve_function, v8::Function) \
|
||||||
|
V(async_hooks_binding, v8::Object) \
|
||||||
V(binding_cache_object, v8::Object) \
|
V(binding_cache_object, v8::Object) \
|
||||||
V(internal_binding_cache_object, v8::Object) \
|
V(internal_binding_cache_object, v8::Object) \
|
||||||
V(buffer_prototype_object, v8::Object) \
|
V(buffer_prototype_object, v8::Object) \
|
||||||
@ -314,11 +315,6 @@ class ModuleWrap;
|
|||||||
|
|
||||||
class Environment;
|
class Environment;
|
||||||
|
|
||||||
struct node_async_ids {
|
|
||||||
double async_id;
|
|
||||||
double trigger_async_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IsolateData {
|
class IsolateData {
|
||||||
public:
|
public:
|
||||||
IsolateData(v8::Isolate* isolate, uv_loop_t* event_loop,
|
IsolateData(v8::Isolate* isolate, uv_loop_t* event_loop,
|
||||||
@ -382,6 +378,7 @@ class Environment {
|
|||||||
kPromiseResolve,
|
kPromiseResolve,
|
||||||
kTotals,
|
kTotals,
|
||||||
kCheck,
|
kCheck,
|
||||||
|
kStackLength,
|
||||||
kFieldsCount,
|
kFieldsCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -393,18 +390,17 @@ class Environment {
|
|||||||
kUidFieldsCount,
|
kUidFieldsCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
AsyncHooks() = delete;
|
|
||||||
|
|
||||||
inline AliasedBuffer<uint32_t, v8::Uint32Array>& fields();
|
inline AliasedBuffer<uint32_t, v8::Uint32Array>& fields();
|
||||||
inline AliasedBuffer<double, v8::Float64Array>& async_id_fields();
|
inline AliasedBuffer<double, v8::Float64Array>& async_id_fields();
|
||||||
|
inline AliasedBuffer<double, v8::Float64Array>& async_ids_stack();
|
||||||
|
|
||||||
inline v8::Local<v8::String> provider_string(int idx);
|
inline v8::Local<v8::String> provider_string(int idx);
|
||||||
|
|
||||||
inline void no_force_checks();
|
inline void no_force_checks();
|
||||||
|
inline Environment* env();
|
||||||
|
|
||||||
inline void push_async_ids(double async_id, double trigger_async_id);
|
inline void push_async_ids(double async_id, double trigger_async_id);
|
||||||
inline bool pop_async_id(double async_id);
|
inline bool pop_async_id(double async_id);
|
||||||
inline size_t stack_size();
|
|
||||||
inline void clear_async_id_stack(); // Used in fatal exceptions.
|
inline void clear_async_id_stack(); // Used in fatal exceptions.
|
||||||
|
|
||||||
// Used to set the kDefaultTriggerAsyncId in a scope. This is instead of
|
// Used to set the kDefaultTriggerAsyncId in a scope. This is instead of
|
||||||
@ -426,19 +422,21 @@ class Environment {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Environment; // So we can call the constructor.
|
friend class Environment; // So we can call the constructor.
|
||||||
inline explicit AsyncHooks(v8::Isolate* isolate);
|
inline AsyncHooks();
|
||||||
// Keep a list of all Persistent strings used for Provider types.
|
// Keep a list of all Persistent strings used for Provider types.
|
||||||
v8::Eternal<v8::String> providers_[AsyncWrap::PROVIDERS_LENGTH];
|
v8::Eternal<v8::String> providers_[AsyncWrap::PROVIDERS_LENGTH];
|
||||||
// Used by provider_string().
|
// Keep track of the environment copy itself.
|
||||||
v8::Isolate* isolate_;
|
Environment* env_;
|
||||||
// Stores the ids of the current execution context stack.
|
// Stores the ids of the current execution context stack.
|
||||||
std::stack<struct node_async_ids> async_ids_stack_;
|
AliasedBuffer<double, v8::Float64Array> async_ids_stack_;
|
||||||
// Attached to a Uint32Array that tracks the number of active hooks for
|
// Attached to a Uint32Array that tracks the number of active hooks for
|
||||||
// each type.
|
// each type.
|
||||||
AliasedBuffer<uint32_t, v8::Uint32Array> fields_;
|
AliasedBuffer<uint32_t, v8::Uint32Array> fields_;
|
||||||
// Attached to a Float64Array that tracks the state of async resources.
|
// Attached to a Float64Array that tracks the state of async resources.
|
||||||
AliasedBuffer<double, v8::Float64Array> async_id_fields_;
|
AliasedBuffer<double, v8::Float64Array> async_id_fields_;
|
||||||
|
|
||||||
|
void grow_async_ids_stack();
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(AsyncHooks);
|
DISALLOW_COPY_AND_ASSIGN(AsyncHooks);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -693,6 +691,8 @@ class Environment {
|
|||||||
|
|
||||||
inline bool inside_should_not_abort_on_uncaught_scope() const;
|
inline bool inside_should_not_abort_on_uncaught_scope() const;
|
||||||
|
|
||||||
|
static inline Environment* ForAsyncHooks(AsyncHooks* hooks);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
|
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
|
||||||
const char* errmsg);
|
const char* errmsg);
|
||||||
|
20
test/parallel/test-async-hooks-recursive-stack.js
Normal file
20
test/parallel/test-async-hooks-recursive-stack.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
'use strict';
|
||||||
|
require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const async_hooks = require('async_hooks');
|
||||||
|
|
||||||
|
// This test verifies that the async ID stack can grow indefinitely.
|
||||||
|
|
||||||
|
function recurse(n) {
|
||||||
|
const a = new async_hooks.AsyncResource('foobar');
|
||||||
|
a.emitBefore();
|
||||||
|
assert.strictEqual(a.asyncId(), async_hooks.executionAsyncId());
|
||||||
|
assert.strictEqual(a.triggerAsyncId(), async_hooks.triggerAsyncId());
|
||||||
|
if (n >= 0)
|
||||||
|
recurse(n - 1);
|
||||||
|
assert.strictEqual(a.asyncId(), async_hooks.executionAsyncId());
|
||||||
|
assert.strictEqual(a.triggerAsyncId(), async_hooks.triggerAsyncId());
|
||||||
|
a.emitAfter();
|
||||||
|
}
|
||||||
|
|
||||||
|
recurse(1000);
|
Loading…
x
Reference in New Issue
Block a user