async_hooks: add executionAsyncResource

Remove the need for the destroy hook in the basic APM case.

Co-authored-by: Stephen Belanger <admin@stephenbelanger.com>
PR-URL: https://github.com/nodejs/node/pull/30959
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Vladimir de Turckheim <vlad2t@hotmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Matteo Collina 2018-05-04 08:00:07 +02:00 committed by Anna Henningsen
parent 1c11ea4388
commit 9fdb6e6aaf
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
19 changed files with 458 additions and 57 deletions

View File

@ -0,0 +1,151 @@
'use strict';
const { promisify } = require('util');
const { readFile } = require('fs');
const sleep = promisify(setTimeout);
const read = promisify(readFile);
const common = require('../common.js');
const {
createHook,
executionAsyncResource,
executionAsyncId
} = require('async_hooks');
const { createServer } = require('http');
// Configuration for the http server
// there is no need for parameters in this test
const connections = 500;
const path = '/';
const bench = common.createBenchmark(main, {
type: ['async-resource', 'destroy'],
asyncMethod: ['callbacks', 'async'],
n: [1e6]
});
function buildCurrentResource(getServe) {
const server = createServer(getServe(getCLS, setCLS));
const hook = createHook({ init });
const cls = Symbol('cls');
hook.enable();
return {
server,
close
};
function getCLS() {
const resource = executionAsyncResource();
if (resource === null || !resource[cls]) {
return null;
}
return resource[cls].state;
}
function setCLS(state) {
const resource = executionAsyncResource();
if (resource === null) {
return;
}
if (!resource[cls]) {
resource[cls] = { state };
} else {
resource[cls].state = state;
}
}
function init(asyncId, type, triggerAsyncId, resource) {
var cr = executionAsyncResource();
if (cr !== null) {
resource[cls] = cr[cls];
}
}
function close() {
hook.disable();
server.close();
}
}
function buildDestroy(getServe) {
const transactions = new Map();
const server = createServer(getServe(getCLS, setCLS));
const hook = createHook({ init, destroy });
hook.enable();
return {
server,
close
};
function getCLS() {
const asyncId = executionAsyncId();
return transactions.has(asyncId) ? transactions.get(asyncId) : null;
}
function setCLS(value) {
const asyncId = executionAsyncId();
transactions.set(asyncId, value);
}
function init(asyncId, type, triggerAsyncId, resource) {
transactions.set(asyncId, getCLS());
}
function destroy(asyncId) {
transactions.delete(asyncId);
}
function close() {
hook.disable();
server.close();
}
}
function getServeAwait(getCLS, setCLS) {
return async function serve(req, res) {
setCLS(Math.random());
await sleep(10);
await read(__filename);
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ cls: getCLS() }));
};
}
function getServeCallbacks(getCLS, setCLS) {
return function serve(req, res) {
setCLS(Math.random());
setTimeout(() => {
readFile(__filename, () => {
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ cls: getCLS() }));
});
}, 10);
};
}
const types = {
'async-resource': buildCurrentResource,
'destroy': buildDestroy
};
const asyncMethods = {
'callbacks': getServeCallbacks,
'async': getServeAwait
};
function main({ type, asyncMethod }) {
const { server, close } = types[type](asyncMethods[asyncMethod]);
server
.listen(common.PORT)
.on('listening', () => {
bench.http({
path,
connections
}, () => {
close();
});
});
}

View File

@ -459,6 +459,62 @@ init for PROMISE with id 6, trigger id: 5 # the Promise returned by then()
after 6
```
#### `async_hooks.executionAsyncResource()`
<!-- YAML
added: REPLACEME
-->
* Returns: {Object} The resource representing the current execution.
Useful to store data within the resource.
Resource objects returned by `executionAsyncResource()` are most often internal
Node.js handle objects with undocumented APIs. Using any functions or properties
on the object is likely to crash your application and should be avoided.
Using `executionAsyncResource()` in the top-level execution context will
return an empty object as there is no handle or request object to use,
but having an object representing the top-level can be helpful.
```js
const { open } = require('fs');
const { executionAsyncId, executionAsyncResource } = require('async_hooks');
console.log(executionAsyncId(), executionAsyncResource()); // 1 {}
open(__filename, 'r', (err, fd) => {
console.log(executionAsyncId(), executionAsyncResource()); // 7 FSReqWrap
});
```
This can be used to implement continuation local storage without the
use of a tracking `Map` to store the metadata:
```js
const { createServer } = require('http');
const {
executionAsyncId,
executionAsyncResource,
createHook
} = require('async_hooks');
const sym = Symbol('state'); // Private symbol to avoid pollution
createHook({
init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource();
if (cr) {
resource[sym] = cr[sym];
}
}
}).enable();
const server = createServer(function(req, res) {
executionAsyncResource()[sym] = { state: req.url };
setTimeout(function() {
res.end(JSON.stringify(executionAsyncResource()[sym]));
}, 100);
}).listen(3000);
```
#### `async_hooks.executionAsyncId()`
<!-- YAML

View File

@ -25,6 +25,7 @@ const {
getHookArrays,
enableHooks,
disableHooks,
executionAsyncResource,
// Internal Embedder API
newAsyncId,
getDefaultTriggerAsyncId,
@ -176,7 +177,7 @@ class AsyncResource {
runInAsyncScope(fn, thisArg, ...args) {
const asyncId = this[async_id_symbol];
emitBefore(asyncId, this[trigger_async_id_symbol]);
emitBefore(asyncId, this[trigger_async_id_symbol], this);
const ret = thisArg === undefined ?
fn(...args) :
@ -211,6 +212,7 @@ module.exports = {
createHook,
executionAsyncId,
triggerAsyncId,
executionAsyncResource,
// Embedder API
AsyncResource,
};

View File

@ -28,18 +28,26 @@ const async_wrap = internalBinding('async_wrap');
* 3. executionAsyncId of the current resource.
*
* async_ids_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.
* stack. Each pushAsyncContext() call adds two doubles to it, and each
* popAsyncContext() 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().
* side are used instead in pushAsyncContext() and popAsyncContext().
*/
const { async_hook_fields, async_id_fields, owner_symbol } = async_wrap;
const {
async_hook_fields,
async_id_fields,
execution_async_resources,
owner_symbol
} = async_wrap;
// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
// Environment::AsyncHooks::async_ids_stack_ tracks the resource responsible for
// the 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() callback.
const { pushAsyncIds: pushAsyncIds_, popAsyncIds: popAsyncIds_ } = async_wrap;
const {
pushAsyncContext: pushAsyncContext_,
popAsyncContext: popAsyncContext_
} = async_wrap;
// For performance reasons, only track Promises when a hook is enabled.
const { enablePromiseHook, disablePromiseHook } = async_wrap;
// Properties in active_hooks are used to keep track of the set of hooks being
@ -92,6 +100,15 @@ const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
const emitPromiseResolveNative =
emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative');
const topLevelResource = {};
function executionAsyncResource() {
const index = async_hook_fields[kStackLength] - 1;
if (index === -1) return topLevelResource;
const resource = execution_async_resources[index];
return resource;
}
// Used to fatally abort the process if a callback throws.
function fatalError(e) {
if (typeof e.stack === 'string') {
@ -330,8 +347,8 @@ function emitInitScript(asyncId, type, triggerAsyncId, resource) {
}
function emitBeforeScript(asyncId, triggerAsyncId) {
pushAsyncIds(asyncId, triggerAsyncId);
function emitBeforeScript(asyncId, triggerAsyncId, resource) {
pushAsyncContext(asyncId, triggerAsyncId, resource);
if (async_hook_fields[kBefore] > 0)
emitBeforeNative(asyncId);
@ -342,7 +359,7 @@ function emitAfterScript(asyncId) {
if (async_hook_fields[kAfter] > 0)
emitAfterNative(asyncId);
popAsyncIds(asyncId);
popAsyncContext(asyncId);
}
@ -360,6 +377,7 @@ function clearAsyncIdStack() {
async_id_fields[kExecutionAsyncId] = 0;
async_id_fields[kTriggerAsyncId] = 0;
async_hook_fields[kStackLength] = 0;
execution_async_resources.splice(0, execution_async_resources.length);
}
@ -369,12 +387,13 @@ function hasAsyncIdStack() {
// This is the equivalent of the native push_async_ids() call.
function pushAsyncIds(asyncId, triggerAsyncId) {
function pushAsyncContext(asyncId, triggerAsyncId, resource) {
const offset = async_hook_fields[kStackLength];
if (offset * 2 >= async_wrap.async_ids_stack.length)
return pushAsyncIds_(asyncId, triggerAsyncId);
return pushAsyncContext_(asyncId, triggerAsyncId, resource);
async_wrap.async_ids_stack[offset * 2] = async_id_fields[kExecutionAsyncId];
async_wrap.async_ids_stack[offset * 2 + 1] = async_id_fields[kTriggerAsyncId];
execution_async_resources[offset] = resource;
async_hook_fields[kStackLength]++;
async_id_fields[kExecutionAsyncId] = asyncId;
async_id_fields[kTriggerAsyncId] = triggerAsyncId;
@ -382,18 +401,19 @@ function pushAsyncIds(asyncId, triggerAsyncId) {
// This is the equivalent of the native pop_async_ids() call.
function popAsyncIds(asyncId) {
function popAsyncContext(asyncId) {
const stackLength = async_hook_fields[kStackLength];
if (stackLength === 0) return false;
if (enabledHooksExist() && async_id_fields[kExecutionAsyncId] !== asyncId) {
// Do the same thing as the native code (i.e. crash hard).
return popAsyncIds_(asyncId);
return popAsyncContext_(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];
execution_async_resources.pop();
async_hook_fields[kStackLength] = offset;
return offset > 0;
}
@ -426,6 +446,7 @@ module.exports = {
clearDefaultTriggerAsyncId,
clearAsyncIdStack,
hasAsyncIdStack,
executionAsyncResource,
// Internal Embedder API
newAsyncId,
getOrSetAsyncId,

View File

@ -71,7 +71,7 @@ function processTicksAndRejections() {
do {
while (tock = queue.shift()) {
const asyncId = tock[async_id_symbol];
emitBefore(asyncId, tock[trigger_async_id_symbol]);
emitBefore(asyncId, tock[trigger_async_id_symbol], tock);
try {
const callback = tock.callback;

View File

@ -96,7 +96,7 @@ const {
emitInit,
emitBefore,
emitAfter,
emitDestroy
emitDestroy,
} = require('internal/async_hooks');
// Symbols for storing async id state.
@ -448,7 +448,7 @@ function getTimerCallbacks(runNextTicks) {
prevImmediate = immediate;
const asyncId = immediate[async_id_symbol];
emitBefore(asyncId, immediate[trigger_async_id_symbol]);
emitBefore(asyncId, immediate[trigger_async_id_symbol], immediate);
try {
const argv = immediate._argv;
@ -537,7 +537,7 @@ function getTimerCallbacks(runNextTicks) {
continue;
}
emitBefore(asyncId, timer[trigger_async_id_symbol]);
emitBefore(asyncId, timer[trigger_async_id_symbol], timer);
let start;
if (timer._repeat)

View File

@ -36,7 +36,7 @@ CallbackScope::~CallbackScope() {
InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags)
: InternalCallbackScope(async_wrap->env(),
async_wrap->object(),
async_wrap->GetResource(),
{ async_wrap->get_async_id(),
async_wrap->get_trigger_async_id() },
flags) {}
@ -50,7 +50,6 @@ InternalCallbackScope::InternalCallbackScope(Environment* env,
object_(object),
skip_hooks_(flags & kSkipAsyncHooks),
skip_task_queues_(flags & kSkipTaskQueues) {
CHECK_IMPLIES(!(flags & kAllowEmptyResource), !object.IsEmpty());
CHECK_NOT_NULL(env);
env->PushAsyncCallbackScope();
@ -69,8 +68,9 @@ InternalCallbackScope::InternalCallbackScope(Environment* env,
AsyncWrap::EmitBefore(env, asyncContext.async_id);
}
env->async_hooks()->push_async_ids(async_context_.async_id,
async_context_.trigger_async_id);
env->async_hooks()->push_async_context(async_context_.async_id,
async_context_.trigger_async_id, object);
pushed_ids_ = true;
}
@ -89,7 +89,7 @@ void InternalCallbackScope::Close() {
}
if (pushed_ids_)
env_->async_hooks()->pop_async_id(async_context_.async_id);
env_->async_hooks()->pop_async_context(async_context_.async_id);
if (failed_) return;

View File

@ -260,8 +260,8 @@ static void PromiseHook(PromiseHookType type, Local<Promise> promise,
if (wrap == nullptr) return;
if (type == PromiseHookType::kBefore) {
env->async_hooks()->push_async_ids(
wrap->get_async_id(), wrap->get_trigger_async_id());
env->async_hooks()->push_async_context(wrap->get_async_id(),
wrap->get_trigger_async_id(), wrap->object());
wrap->EmitTraceEventBefore();
AsyncWrap::EmitBefore(wrap->env(), wrap->get_async_id());
} else if (type == PromiseHookType::kAfter) {
@ -273,7 +273,7 @@ static void PromiseHook(PromiseHookType type, Local<Promise> promise,
// Popping it off the stack can be skipped in that case, because it is
// known that it would correspond to exactly one call with
// PromiseHookType::kBefore that was not witnessed by the PromiseHook.
env->async_hooks()->pop_async_id(wrap->get_async_id());
env->async_hooks()->pop_async_context(wrap->get_async_id());
}
} else if (type == PromiseHookType::kResolve) {
AsyncWrap::EmitPromiseResolve(wrap->env(), wrap->get_async_id());
@ -382,7 +382,6 @@ static void RegisterDestroyHook(const FunctionCallbackInfo<Value>& args) {
p->target.SetWeak(p, AsyncWrap::WeakCallback, WeakCallbackType::kParameter);
}
void AsyncWrap::GetAsyncId(const FunctionCallbackInfo<Value>& args) {
AsyncWrap* wrap;
args.GetReturnValue().Set(kInvalidAsyncId);
@ -391,20 +390,20 @@ void AsyncWrap::GetAsyncId(const FunctionCallbackInfo<Value>& args) {
}
void AsyncWrap::PushAsyncIds(const FunctionCallbackInfo<Value>& args) {
void AsyncWrap::PushAsyncContext(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
// No need for CHECK(IsNumber()) on args because if FromJust() doesn't fail
// then the checks in push_async_ids() and pop_async_id() will.
double async_id = args[0]->NumberValue(env->context()).FromJust();
double trigger_async_id = args[1]->NumberValue(env->context()).FromJust();
env->async_hooks()->push_async_ids(async_id, trigger_async_id);
env->async_hooks()->push_async_context(async_id, trigger_async_id, args[2]);
}
void AsyncWrap::PopAsyncIds(const FunctionCallbackInfo<Value>& args) {
void AsyncWrap::PopAsyncContext(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
double async_id = args[0]->NumberValue(env->context()).FromJust();
args.GetReturnValue().Set(env->async_hooks()->pop_async_id(async_id));
args.GetReturnValue().Set(env->async_hooks()->pop_async_context(async_id));
}
@ -433,6 +432,7 @@ void AsyncWrap::EmitDestroy() {
AsyncWrap::EmitDestroy(env(), async_id_);
// Ensure no double destroy is emitted via AsyncReset().
async_id_ = kInvalidAsyncId;
resource_.Reset();
}
void AsyncWrap::QueueDestroyAsyncId(const FunctionCallbackInfo<Value>& args) {
@ -464,8 +464,8 @@ void AsyncWrap::Initialize(Local<Object> target,
HandleScope scope(isolate);
env->SetMethod(target, "setupHooks", SetupHooks);
env->SetMethod(target, "pushAsyncIds", PushAsyncIds);
env->SetMethod(target, "popAsyncIds", PopAsyncIds);
env->SetMethod(target, "pushAsyncContext", PushAsyncContext);
env->SetMethod(target, "popAsyncContext", PopAsyncContext);
env->SetMethod(target, "queueDestroyAsyncId", QueueDestroyAsyncId);
env->SetMethod(target, "enablePromiseHook", EnablePromiseHook);
env->SetMethod(target, "disablePromiseHook", DisablePromiseHook);
@ -502,6 +502,10 @@ void AsyncWrap::Initialize(Local<Object> target,
"async_id_fields",
env->async_hooks()->async_id_fields().GetJSArray());
FORCE_SET_TARGET_FIELD(target,
"execution_async_resources",
env->async_hooks()->execution_async_resources());
target->Set(context,
env->async_ids_stack_string(),
env->async_hooks()->async_ids_stack().GetJSArray()).Check();
@ -670,6 +674,12 @@ void AsyncWrap::AsyncReset(Local<Object> resource, double execution_async_id,
: execution_async_id;
trigger_async_id_ = env()->get_default_trigger_async_id();
if (resource != object()) {
// TODO(addaleax): Using a strong reference here makes it very easy to
// introduce memory leaks. Move away from using a strong reference.
resource_.Reset(env()->isolate(), resource);
}
switch (provider_type()) {
#define V(PROVIDER) \
case PROVIDER_ ## PROVIDER: \
@ -776,6 +786,14 @@ Local<Object> AsyncWrap::GetOwner(Environment* env, Local<Object> obj) {
}
}
Local<Object> AsyncWrap::GetResource() {
if (resource_.IsEmpty()) {
return object();
}
return resource_.Get(env()->isolate());
}
} // namespace node
NODE_MODULE_CONTEXT_AWARE_INTERNAL(async_wrap, node::AsyncWrap::Initialize)

View File

@ -134,8 +134,8 @@ class AsyncWrap : public BaseObject {
void* priv);
static void GetAsyncId(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 PushAsyncContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PopAsyncContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AsyncReset(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetProviderType(const v8::FunctionCallbackInfo<v8::Value>& args);
static void QueueDestroyAsyncId(
@ -202,6 +202,7 @@ class AsyncWrap : public BaseObject {
v8::Local<v8::Object> obj);
bool IsDoneInitializing() const override;
v8::Local<v8::Object> GetResource();
private:
friend class PromiseWrap;
@ -216,6 +217,7 @@ class AsyncWrap : public BaseObject {
// Because the values may be Reset(), cannot be made const.
double async_id_ = kInvalidAsyncId;
double trigger_async_id_;
v8::Global<v8::Object> resource_;
};
} // namespace node

View File

@ -68,7 +68,7 @@ inline AsyncHooks::AsyncHooks()
: async_ids_stack_(env()->isolate(), 16 * 2),
fields_(env()->isolate(), kFieldsCount),
async_id_fields_(env()->isolate(), kUidFieldsCount) {
v8::HandleScope handle_scope(env()->isolate());
clear_async_id_stack();
// Always perform async_hooks checks, not just when async_hooks is enabled.
// TODO(AndreasMadsen): Consider removing this for LTS releases.
@ -113,6 +113,10 @@ inline AliasedFloat64Array& AsyncHooks::async_ids_stack() {
return async_ids_stack_;
}
inline v8::Local<v8::Array> AsyncHooks::execution_async_resources() {
return PersistentToLocal::Strong(execution_async_resources_);
}
inline v8::Local<v8::String> AsyncHooks::provider_string(int idx) {
return providers_[idx].Get(env()->isolate());
}
@ -125,9 +129,12 @@ inline Environment* AsyncHooks::env() {
return Environment::ForAsyncHooks(this);
}
// Remember to keep this code aligned with pushAsyncIds() in JS.
inline void AsyncHooks::push_async_ids(double async_id,
double trigger_async_id) {
// Remember to keep this code aligned with pushAsyncContext() in JS.
inline void AsyncHooks::push_async_context(double async_id,
double trigger_async_id,
v8::Local<v8::Value> resource) {
v8::HandleScope handle_scope(env()->isolate());
// Since async_hooks is experimental, do only perform the check
// when async_hooks is enabled.
if (fields_[kCheck] > 0) {
@ -143,10 +150,13 @@ inline void AsyncHooks::push_async_ids(double async_id,
fields_[kStackLength] += 1;
async_id_fields_[kExecutionAsyncId] = async_id;
async_id_fields_[kTriggerAsyncId] = trigger_async_id;
auto resources = execution_async_resources();
USE(resources->Set(env()->context(), offset, resource));
}
// Remember to keep this code aligned with popAsyncIds() in JS.
inline bool AsyncHooks::pop_async_id(double async_id) {
// Remember to keep this code aligned with popAsyncContext() in JS.
inline bool AsyncHooks::pop_async_context(double async_id) {
// In case of an exception then this may have already been reset, if the
// stack was multiple MakeCallback()'s deep.
if (fields_[kStackLength] == 0) return false;
@ -175,11 +185,18 @@ inline bool AsyncHooks::pop_async_id(double async_id) {
async_id_fields_[kTriggerAsyncId] = async_ids_stack_[2 * offset + 1];
fields_[kStackLength] = offset;
auto resources = execution_async_resources();
USE(resources->Delete(env()->context(), offset));
return fields_[kStackLength] > 0;
}
// Keep in sync with clearAsyncIdStack in lib/internal/async_hooks.js.
inline void AsyncHooks::clear_async_id_stack() {
auto isolate = env()->isolate();
v8::HandleScope handle_scope(isolate);
execution_async_resources_.Reset(isolate, v8::Array::New(isolate));
async_id_fields_[kExecutionAsyncId] = 0;
async_id_fields_[kTriggerAsyncId] = 0;
fields_[kStackLength] = 0;
@ -206,7 +223,6 @@ inline AsyncHooks::DefaultTriggerAsyncIdScope ::~DefaultTriggerAsyncIdScope() {
old_default_trigger_async_id_;
}
Environment* Environment::ForAsyncHooks(AsyncHooks* hooks) {
return ContainerOf(&Environment::async_hooks_, hooks);
}

View File

@ -655,14 +655,16 @@ class AsyncHooks : public MemoryRetainer {
inline AliasedUint32Array& fields();
inline AliasedFloat64Array& async_id_fields();
inline AliasedFloat64Array& async_ids_stack();
inline v8::Local<v8::Array> execution_async_resources();
inline v8::Local<v8::String> provider_string(int idx);
inline void no_force_checks();
inline Environment* env();
inline void push_async_ids(double async_id, double trigger_async_id);
inline bool pop_async_id(double async_id);
inline void push_async_context(double async_id, double trigger_async_id,
v8::Local<v8::Value> execution_async_resource_);
inline bool pop_async_context(double async_id);
inline void clear_async_id_stack(); // Used in fatal exceptions.
AsyncHooks(const AsyncHooks&) = delete;
@ -707,6 +709,8 @@ class AsyncHooks : public MemoryRetainer {
AliasedFloat64Array async_id_fields_;
void grow_async_ids_stack();
v8::Global<v8::Array> execution_async_resources_;
};
class ImmediateInfo : public MemoryRetainer {

View File

@ -208,15 +208,13 @@ class InternalCallbackScope {
public:
enum Flags {
kNoFlags = 0,
// Tell the constructor whether its `object` parameter may be empty or not.
kAllowEmptyResource = 1,
// Indicates whether 'before' and 'after' hooks should be skipped.
kSkipAsyncHooks = 2,
kSkipAsyncHooks = 1,
// Indicates whether nextTick and microtask queues should be skipped.
// This should only be used when there is no call into JS in this scope.
// (The HTTP parser also uses it for some weird backwards
// compatibility issues, but it shouldn't.)
kSkipTaskQueues = 4
kSkipTaskQueues = 2
};
InternalCallbackScope(Environment* env,
v8::Local<v8::Object> object,

View File

@ -121,10 +121,9 @@ int NodeMainInstance::Run() {
{
InternalCallbackScope callback_scope(
env.get(),
Local<Object>(),
Object::New(isolate_),
{ 1, 0 },
InternalCallbackScope::kAllowEmptyResource |
InternalCallbackScope::kSkipAsyncHooks);
InternalCallbackScope::kSkipAsyncHooks);
LoadEnvironment(env.get());
}

View File

@ -10,7 +10,6 @@
namespace node {
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::Platform;
using v8::Task;
@ -388,8 +387,9 @@ void PerIsolatePlatformData::RunForegroundTask(std::unique_ptr<Task> task) {
DebugSealHandleScope scope(isolate);
Environment* env = Environment::GetCurrent(isolate);
if (env != nullptr) {
InternalCallbackScope cb_scope(env, Local<Object>(), { 0, 0 },
InternalCallbackScope::kAllowEmptyResource);
v8::HandleScope scope(isolate);
InternalCallbackScope cb_scope(env, Object::New(isolate), { 0, 0 },
InternalCallbackScope::kNoFlags);
task->Run();
} else {
task->Run();

View File

@ -326,10 +326,9 @@ void Worker::Run() {
HandleScope handle_scope(isolate_);
InternalCallbackScope callback_scope(
env_.get(),
Local<Object>(),
Object::New(isolate_),
{ 1, 0 },
InternalCallbackScope::kAllowEmptyResource |
InternalCallbackScope::kSkipAsyncHooks);
InternalCallbackScope::kSkipAsyncHooks);
if (!env_->RunBootstrapping().IsEmpty()) {
CreateEnvMessagePort(env_.get());

View File

@ -0,0 +1,30 @@
'use strict';
require('../common');
const assert = require('assert');
const {
executionAsyncResource,
executionAsyncId,
createHook,
} = require('async_hooks');
const http = require('http');
const hooked = {};
createHook({
init(asyncId, type, triggerAsyncId, resource) {
hooked[asyncId] = resource;
}
}).enable();
const server = http.createServer((req, res) => {
res.end('ok');
});
server.listen(0, () => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
http.get({ port: server.address().port }, () => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
server.close();
});
});

View File

@ -15,6 +15,8 @@ runBenchmark('async_hooks',
'asyncHooks=all',
'connections=50',
'method=trackingDisabled',
'n=10'
'n=10',
'type=async-resource',
'asyncMethod=async'
],
{});

View File

@ -0,0 +1,54 @@
'use strict';
const common = require('../common');
const sleep = require('util').promisify(setTimeout);
const assert = require('assert');
const { executionAsyncResource, createHook } = require('async_hooks');
const { createServer, get } = require('http');
const sym = Symbol('cls');
// Tests continuation local storage with the currentResource API
// through an async function
assert.ok(executionAsyncResource());
createHook({
init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource();
resource[sym] = cr[sym];
}
}).enable();
async function handler(req, res) {
executionAsyncResource()[sym] = { state: req.url };
await sleep(10);
const { state } = executionAsyncResource()[sym];
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ state }));
}
const server = createServer(function(req, res) {
handler(req, res);
});
function test(n) {
get(`http://localhost:${server.address().port}/${n}`, common.mustCall(function(res) {
res.setEncoding('utf8');
let body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', common.mustCall(function() {
assert.deepStrictEqual(JSON.parse(body), { state: `/${n}` });
}));
}));
}
server.listen(0, common.mustCall(function() {
server.unref();
for (let i = 0; i < 10; i++) {
test(i);
}
}));

View File

@ -0,0 +1,49 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { executionAsyncResource, createHook } = require('async_hooks');
const { createServer, get } = require('http');
const sym = Symbol('cls');
// Tests continuation local storage with the executionAsyncResource API
assert.ok(executionAsyncResource());
createHook({
init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource();
resource[sym] = cr[sym];
}
}).enable();
const server = createServer(function(req, res) {
executionAsyncResource()[sym] = { state: req.url };
setTimeout(function() {
const { state } = executionAsyncResource()[sym];
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ state }));
}, 10);
});
function test(n) {
get(`http://localhost:${server.address().port}/${n}`, common.mustCall(function(res) {
res.setEncoding('utf8');
let body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', common.mustCall(function() {
assert.deepStrictEqual(JSON.parse(body), { state: `/${n}` });
}));
}));
}
server.listen(0, common.mustCall(function() {
server.unref();
for (let i = 0; i < 10; i++) {
test(i);
}
}));