mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
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:
parent
1c11ea4388
commit
9fdb6e6aaf
151
benchmark/async_hooks/async-resource-vs-destroy.js
Normal file
151
benchmark/async_hooks/async-resource-vs-destroy.js
Normal 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
30
test/async-hooks/test-async-exec-resource-http.js
Normal file
30
test/async-hooks/test-async-exec-resource-http.js
Normal 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();
|
||||
});
|
||||
});
|
||||
|
|
@ -15,6 +15,8 @@ runBenchmark('async_hooks',
|
|||
'asyncHooks=all',
|
||||
'connections=50',
|
||||
'method=trackingDisabled',
|
||||
'n=10'
|
||||
'n=10',
|
||||
'type=async-resource',
|
||||
'asyncMethod=async'
|
||||
],
|
||||
{});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}));
|
||||
49
test/parallel/test-async-hooks-execution-async-resource.js
Normal file
49
test/parallel/test-async-hooks-execution-async-resource.js
Normal 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);
|
||||
}
|
||||
}));
|
||||
Loading…
Reference in New Issue
Block a user