Skip to content

Commit

Permalink
process: add getActiveResourcesInfo()
Browse files Browse the repository at this point in the history
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 <darshan.sen@postman.com>
  • Loading branch information
RaisinTen committed Nov 23, 2021
1 parent 2b0087f commit 92ffa69
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 7 deletions.
69 changes: 69 additions & 0 deletions doc/api/process.md
Expand Up @@ -1817,6 +1817,71 @@ a code.
Specifying a code to [`process.exit(code)`][`process.exit()`] will override any
previous setting of `process.exitCode`.
## `process.getActiveResourcesInfo()`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental
* Returns: {Array} of objects containing the `type`, `asyncId` and the
`triggerAsyncId` of the currently active requests, handles and timers.
The `process.getActiveResourcesInfo()` method can be called at any moment and it
would return an array of objects containing some information about all the
resources that are currently keeping the event loop alive. This can be useful
while debugging.
The same functionality can be implemented using [`async_hooks`][] by setting up
an [`AsyncHook`][] that keeps a track of the calls made to [`init` callbacks][]
and [`destroy` callbacks][] but this function should be preferred because it
doesn't incur the overhead of creating any [`AsyncHook`][].
```mjs
import { getActiveResourcesInfo } from 'process';
import { setTimeout } from 'timers';

console.log('Before:', getActiveResourcesInfo());
setTimeout(() => {}, 1000);
console.log('After:', getActiveResourcesInfo());
// Prints:
// Before: [
// { type: 'CloseReq', asyncId: 6, triggerAsyncId: 0 },
// { type: 'TTYWrap', asyncId: 7, triggerAsyncId: 0 },
// { type: 'TTYWrap', asyncId: 9, triggerAsyncId: 0 },
// { type: 'TTYWrap', asyncId: 10, triggerAsyncId: 0 }
// ]
// After: [
// { type: 'CloseReq', asyncId: 6, triggerAsyncId: 0 },
// { type: 'TTYWrap', asyncId: 7, triggerAsyncId: 0 },
// { type: 'TTYWrap', asyncId: 9, triggerAsyncId: 0 },
// { type: 'TTYWrap', asyncId: 10, triggerAsyncId: 0 },
// { type: 'Timeout', asyncId: 12, triggerAsyncId: 0 }
// ]
```
```cjs
const { getActiveResourcesInfo } = require('process');
const { setTimeout } = require('timers');

console.log('Before:', getActiveResourcesInfo());
setTimeout(() => {}, 1000);
console.log('After:', getActiveResourcesInfo());
// Prints:
// Before: [
// { type: 'TTYWrap', asyncId: 2, triggerAsyncId: 1 },
// { type: 'TTYWrap', asyncId: 4, triggerAsyncId: 1 },
// { type: 'TTYWrap', asyncId: 5, triggerAsyncId: 1 }
// ]
// After: [
// { type: 'TTYWrap', asyncId: 2, triggerAsyncId: 1 },
// { type: 'TTYWrap', asyncId: 4, triggerAsyncId: 1 },
// { type: 'TTYWrap', asyncId: 5, triggerAsyncId: 1 },
// { type: 'Timeout', asyncId: 7, triggerAsyncId: 1 }
// ]
```
## `process.getegid()`
<!-- YAML
Expand Down Expand Up @@ -3792,6 +3857,7 @@ cases:
[`'message'`]: child_process.md#event-message
[`'uncaughtException'`]: #event-uncaughtexception
[`--unhandled-rejections`]: cli.md#--unhandled-rejectionsmode
[`AsyncHook`]: async_hooks.md#class-asynchook
[`Buffer`]: buffer.md
[`ChildProcess.disconnect()`]: child_process.md#subprocessdisconnect
[`ChildProcess.send()`]: child_process.md#subprocesssendmessage-sendhandle-options-callback
Expand All @@ -3802,9 +3868,12 @@ cases:
[`Promise.race()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
[`Worker`]: worker_threads.md#class-worker
[`Worker` constructor]: worker_threads.md#new-workerfilename-options
[`async_hooks`]: async_hooks.md
[`console.error()`]: console.md#consoleerrordata-args
[`console.log()`]: console.md#consolelogdata-args
[`destroy` callbacks]: async_hooks.md#destroyasyncid
[`domain`]: domain.md
[`init` callbacks]: async_hooks.md#initasyncid-type-triggerasyncid-resource
[`net.Server`]: net.md#class-netserver
[`net.Socket`]: net.md#class-netsocket
[`os.constants.dlopen`]: os.md#dlopen-constants
Expand Down
42 changes: 40 additions & 2 deletions lib/internal/bootstrap/node.js
Expand Up @@ -39,19 +39,24 @@
setupPrepareStackTrace();

const {
ArrayPrototypeConcat,
ArrayPrototypeForEach,
ArrayPrototypePush,
FunctionPrototypeCall,
JSONParse,
ObjectDefineProperty,
ObjectDefineProperties,
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();
Expand Down Expand Up @@ -150,6 +155,37 @@ const rawMethods = internalBinding('process_methods');
process._getActiveRequests = rawMethods._getActiveRequests;
process._getActiveHandles = rawMethods._getActiveHandles;

process.getActiveResourcesInfo = function() {
const resources = ArrayPrototypeConcat(rawMethods._getActiveRequestsInfo(),
rawMethods._getActiveHandlesInfo());

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;
Expand Down Expand Up @@ -360,9 +396,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.
Expand Down
11 changes: 6 additions & 5 deletions lib/internal/timers.js
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -649,6 +649,7 @@ module.exports = {
setUnrefTimeout,
getTimerDuration,
immediateQueue,
outstandingQueue,
getTimerCallbacks,
immediateInfoFields: {
kCount,
Expand Down
58 changes: 58 additions & 0 deletions 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"
Expand Down Expand Up @@ -43,8 +44,11 @@ using v8::FunctionCallbackInfo;
using v8::HeapStatistics;
using v8::Integer;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::NewStringType;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::String;
Expand Down Expand Up @@ -242,6 +246,28 @@ static void Uptime(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(result);
}

static Maybe<void> GetResourceInfo(Environment* env, AsyncWrap* w,
std::vector<Local<Value>>* resources) {
Local<Object> resource = Object::New(env->isolate());

if (resource->Set(env->context(),
OneByteString(env->isolate(), "type"),
OneByteString(env->isolate(), w->MemoryInfoName().c_str()))
.IsNothing() ||
resource->Set(env->context(),
OneByteString(env->isolate(), "asyncId"),
Number::New(env->isolate(), w->get_async_id()))
.IsNothing() ||
resource->Set(env->context(),
OneByteString(env->isolate(), "triggerAsyncId"),
Number::New(env->isolate(), w->get_trigger_async_id()))
.IsNothing()) return Nothing<void>();

resources->emplace_back(resource);

return JustVoid();
}

static void GetActiveRequests(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Expand All @@ -250,13 +276,28 @@ static void GetActiveRequests(const FunctionCallbackInfo<Value>& args) {
AsyncWrap* w = req_wrap->GetAsyncWrap();
if (w->persistent().IsEmpty())
continue;

request_v.emplace_back(w->GetOwner());
}

args.GetReturnValue().Set(
Array::New(env->isolate(), request_v.data(), request_v.size()));
}

static void GetActiveRequestsInfo(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

std::vector<Local<Value>> requests;
for (ReqWrapBase* req_wrap : *env->req_wrap_queue()) {
AsyncWrap* w = req_wrap->GetAsyncWrap();
if (!w->persistent().IsEmpty())
if (GetResourceInfo(env, w, &requests).IsNothing()) return;
}

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<Value>& args) {
Expand All @@ -272,6 +313,19 @@ void GetActiveHandles(const FunctionCallbackInfo<Value>& args) {
Array::New(env->isolate(), handle_v.data(), handle_v.size()));
}

void GetActiveHandlesInfo(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

std::vector<Local<Value>> handles;
for (HandleWrap* w : *env->handle_wrap_queue()) {
if (!w->persistent().IsEmpty() && HandleWrap::HasRef(w))
if (GetResourceInfo(env, w, &handles).IsNothing()) return;
}

args.GetReturnValue().Set(
Array::New(env->isolate(), handles.data(), handles.size()));
}

static void ResourceUsage(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Expand Down Expand Up @@ -547,7 +601,9 @@ static void Initialize(Local<Object> target,
env->SetMethod(target, "resourceUsage", ResourceUsage);

env->SetMethod(target, "_getActiveRequests", GetActiveRequests);
env->SetMethod(target, "_getActiveRequestsInfo", GetActiveRequestsInfo);
env->SetMethod(target, "_getActiveHandles", GetActiveHandles);
env->SetMethod(target, "_getActiveHandlesInfo", GetActiveHandlesInfo);
env->SetMethod(target, "_kill", Kill);

env->SetMethodNoSideEffect(target, "cwd", Cwd);
Expand All @@ -574,7 +630,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(ResourceUsage);

registry->Register(GetActiveRequests);
registry->Register(GetActiveRequestsInfo);
registry->Register(GetActiveHandles);
registry->Register(GetActiveHandlesInfo);
registry->Register(Kill);

registry->Register(Cwd);
Expand Down

0 comments on commit 92ffa69

Please sign in to comment.