From 489e4ce0b7b3c910886105f4dc95d49a538aba91 Mon Sep 17 00:00:00 2001 From: Darshan Sen Date: Sun, 14 Nov 2021 17:09:49 +0530 Subject: [PATCH] process: add `getActiveResources()` This is supposed to be a public alternative of the private APIs, `process._getActiveResources()` and `process._getActiveHandles()`. When called, it returns an array of objects containing the `type`, `asyncId` and the `triggerAsyncId` of the currently active `requests`, `handles` and `timers`. Signed-off-by: Darshan Sen --- lib/internal/bootstrap/node.js | 42 ++++++++++++++++++++-- lib/internal/timers.js | 11 +++--- src/node_process_methods.cc | 65 ++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 7 deletions(-) diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index ae4eac502803e7..8e8f4d77a2d873 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -39,6 +39,9 @@ setupPrepareStackTrace(); const { + ArrayPrototypeConcat, + ArrayPrototypeForEach, + ArrayPrototypePush, FunctionPrototypeCall, JSONParse, ObjectDefineProperty, @@ -46,12 +49,14 @@ const { ObjectGetPrototypeOf, ObjectPreventExtensions, ObjectSetPrototypeOf, + ObjectValues, ReflectGet, ReflectSet, SymbolToStringTag, globalThis, } = primordials; const config = internalBinding('config'); +const internalTimers = require('internal/timers'); const { deprecate, lazyDOMExceptionClass } = require('internal/util'); setupProcessObject(); @@ -150,6 +155,37 @@ const rawMethods = internalBinding('process_methods'); process._getActiveRequests = rawMethods._getActiveRequests; process._getActiveHandles = rawMethods._getActiveHandles; + process.getActiveResources = function() { + const resources = ArrayPrototypeConcat(rawMethods._getActiveRequests2(), + rawMethods._getActiveHandles2()); + + ArrayPrototypeForEach(ObjectValues(internalTimers.timerListMap), (list) => { + let timeout = list._idlePrev === list ? null : list._idlePrev; + while (timeout !== null) { + ArrayPrototypePush(resources, { + type: 'Timeout', + asyncId: timeout[internalTimers.async_id_symbol], + triggerAsyncId: timeout[internalTimers.trigger_async_id_symbol], + }); + timeout = timeout._idlePrev === list ? null : list._idlePrev; + } + }); + + const queue = internalTimers.outstandingQueue.head !== null ? + internalTimers.outstandingQueue : internalTimers.immediateQueue; + let immediate = queue.head; + while (immediate !== null) { + ArrayPrototypePush(resources, { + type: 'Immediate', + asyncId: immediate[internalTimers.async_id_symbol], + triggerAsyncId: immediate[internalTimers.trigger_async_id_symbol], + }); + immediate = immediate._idleNext; + } + + return resources; + }; + // TODO(joyeecheung): remove these process.reallyExit = rawMethods.reallyExit; process._kill = rawMethods._kill; @@ -357,9 +393,11 @@ process.emitWarning = emitWarning; // TODO(joyeecheung): either remove it or make it public process._tickCallback = runNextTicks; - const { getTimerCallbacks } = require('internal/timers'); const { setupTimers } = internalBinding('timers'); - const { processImmediate, processTimers } = getTimerCallbacks(runNextTicks); + const { + processImmediate, + processTimers, + } = internalTimers.getTimerCallbacks(runNextTicks); // Sets two per-Environment callbacks that will be run from libuv: // - processImmediate will be run in the callback of the per-Environment // check handle. diff --git a/lib/internal/timers.js b/lib/internal/timers.js index 9a07fe3348f65e..9aa572989c8cd1 100644 --- a/lib/internal/timers.js +++ b/lib/internal/timers.js @@ -139,6 +139,11 @@ const kRefed = Symbol('refed'); // Create a single linked list instance only once at startup const immediateQueue = new ImmediateList(); +// If an uncaught exception was thrown during execution of immediateQueue, +// this queue will store all remaining Immediates that need to run upon +// resolution of all error handling (if process is still alive). +const outstandingQueue = new ImmediateList(); + let nextExpiry = Infinity; let refCount = 0; @@ -413,11 +418,6 @@ function setPosition(node, pos) { } function getTimerCallbacks(runNextTicks) { - // If an uncaught exception was thrown during execution of immediateQueue, - // this queue will store all remaining Immediates that need to run upon - // resolution of all error handling (if process is still alive). - const outstandingQueue = new ImmediateList(); - function processImmediate() { const queue = outstandingQueue.head !== null ? outstandingQueue : immediateQueue; @@ -649,6 +649,7 @@ module.exports = { setUnrefTimeout, getTimerDuration, immediateQueue, + outstandingQueue, getTimerCallbacks, immediateInfoFields: { kCount, diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index e1ff446ac106d3..22234e36feb99f 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -1,3 +1,4 @@ +#include "async_wrap-inl.h" #include "base_object-inl.h" #include "debug_utils-inl.h" #include "env-inl.h" @@ -257,6 +258,7 @@ static void GetActiveRequests(const FunctionCallbackInfo& args) { AsyncWrap* w = req_wrap->GetAsyncWrap(); if (w->persistent().IsEmpty()) continue; + request_v.emplace_back(w->GetOwner()); } @@ -264,6 +266,36 @@ static void GetActiveRequests(const FunctionCallbackInfo& args) { Array::New(env->isolate(), request_v.data(), request_v.size())); } +static void GetActiveRequests2(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + std::vector> requests; + for (ReqWrapBase* req_wrap : *env->req_wrap_queue()) { + AsyncWrap* w = req_wrap->GetAsyncWrap(); + if (w->persistent().IsEmpty()) + continue; + + Local request = Object::New(env->isolate()); + if (request->Set(env->context(), + OneByteString(env->isolate(), "type"), + OneByteString(env->isolate(), w->MemoryInfoName().c_str())) + .IsNothing() || + request->Set(env->context(), + OneByteString(env->isolate(), "asyncId"), + Number::New(env->isolate(), w->get_async_id())) + .IsNothing() || + request->Set(env->context(), + OneByteString(env->isolate(), "triggerAsyncId"), + Number::New(env->isolate(), w->get_trigger_async_id())) + .IsNothing()) return; + + requests.emplace_back(request); + } + + args.GetReturnValue().Set( + Array::New(env->isolate(), requests.data(), requests.size())); +} + // Non-static, friend of HandleWrap. Could have been a HandleWrap method but // implemented here for consistency with GetActiveRequests(). void GetActiveHandles(const FunctionCallbackInfo& args) { @@ -279,6 +311,35 @@ void GetActiveHandles(const FunctionCallbackInfo& args) { Array::New(env->isolate(), handle_v.data(), handle_v.size())); } +void GetActiveHandles2(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + std::vector> handles; + for (HandleWrap* w : *env->handle_wrap_queue()) { + if (w->persistent().IsEmpty() || !HandleWrap::HasRef(w)) + continue; + + Local handle = Object::New(env->isolate()); + if (handle->Set(env->context(), + OneByteString(env->isolate(), "type"), + OneByteString(env->isolate(), w->MemoryInfoName().c_str())) + .IsNothing() || + handle->Set(env->context(), + OneByteString(env->isolate(), "asyncId"), + Number::New(env->isolate(), w->get_async_id())) + .IsNothing() || + handle->Set(env->context(), + OneByteString(env->isolate(), "triggerAsyncId"), + Number::New(env->isolate(), w->get_trigger_async_id())) + .IsNothing()) return; + + handles.emplace_back(handle); + } + + args.GetReturnValue().Set( + Array::New(env->isolate(), handles.data(), handles.size())); +} + static void ResourceUsage(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -559,7 +620,9 @@ static void InitializeProcessMethods(Local target, env->SetMethod(target, "resourceUsage", ResourceUsage); env->SetMethod(target, "_getActiveRequests", GetActiveRequests); + env->SetMethod(target, "_getActiveRequests2", GetActiveRequests2); env->SetMethod(target, "_getActiveHandles", GetActiveHandles); + env->SetMethod(target, "_getActiveHandles2", GetActiveHandles2); env->SetMethod(target, "_kill", Kill); env->SetMethodNoSideEffect(target, "cwd", Cwd); @@ -586,7 +649,9 @@ void RegisterProcessMethodsExternalReferences( registry->Register(ResourceUsage); registry->Register(GetActiveRequests); + registry->Register(GetActiveRequests2); registry->Register(GetActiveHandles); + registry->Register(GetActiveHandles2); registry->Register(Kill); registry->Register(Cwd);