Skip to content

Commit 92ffa69

Browse files
committed
process: add getActiveResourcesInfo()
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>
1 parent 2b0087f commit 92ffa69

File tree

4 files changed

+173
-7
lines changed

4 files changed

+173
-7
lines changed

doc/api/process.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,71 @@ a code.
18171817
Specifying a code to [`process.exit(code)`][`process.exit()`] will override any
18181818
previous setting of `process.exitCode`.
18191819
1820+
## `process.getActiveResourcesInfo()`
1821+
1822+
<!-- YAML
1823+
added: REPLACEME
1824+
-->
1825+
1826+
> Stability: 1 - Experimental
1827+
1828+
* Returns: {Array} of objects containing the `type`, `asyncId` and the
1829+
`triggerAsyncId` of the currently active requests, handles and timers.
1830+
1831+
The `process.getActiveResourcesInfo()` method can be called at any moment and it
1832+
would return an array of objects containing some information about all the
1833+
resources that are currently keeping the event loop alive. This can be useful
1834+
while debugging.
1835+
1836+
The same functionality can be implemented using [`async_hooks`][] by setting up
1837+
an [`AsyncHook`][] that keeps a track of the calls made to [`init` callbacks][]
1838+
and [`destroy` callbacks][] but this function should be preferred because it
1839+
doesn't incur the overhead of creating any [`AsyncHook`][].
1840+
1841+
```mjs
1842+
import { getActiveResourcesInfo } from 'process';
1843+
import { setTimeout } from 'timers';
1844+
1845+
console.log('Before:', getActiveResourcesInfo());
1846+
setTimeout(() => {}, 1000);
1847+
console.log('After:', getActiveResourcesInfo());
1848+
// Prints:
1849+
// Before: [
1850+
// { type: 'CloseReq', asyncId: 6, triggerAsyncId: 0 },
1851+
// { type: 'TTYWrap', asyncId: 7, triggerAsyncId: 0 },
1852+
// { type: 'TTYWrap', asyncId: 9, triggerAsyncId: 0 },
1853+
// { type: 'TTYWrap', asyncId: 10, triggerAsyncId: 0 }
1854+
// ]
1855+
// After: [
1856+
// { type: 'CloseReq', asyncId: 6, triggerAsyncId: 0 },
1857+
// { type: 'TTYWrap', asyncId: 7, triggerAsyncId: 0 },
1858+
// { type: 'TTYWrap', asyncId: 9, triggerAsyncId: 0 },
1859+
// { type: 'TTYWrap', asyncId: 10, triggerAsyncId: 0 },
1860+
// { type: 'Timeout', asyncId: 12, triggerAsyncId: 0 }
1861+
// ]
1862+
```
1863+
1864+
```cjs
1865+
const { getActiveResourcesInfo } = require('process');
1866+
const { setTimeout } = require('timers');
1867+
1868+
console.log('Before:', getActiveResourcesInfo());
1869+
setTimeout(() => {}, 1000);
1870+
console.log('After:', getActiveResourcesInfo());
1871+
// Prints:
1872+
// Before: [
1873+
// { type: 'TTYWrap', asyncId: 2, triggerAsyncId: 1 },
1874+
// { type: 'TTYWrap', asyncId: 4, triggerAsyncId: 1 },
1875+
// { type: 'TTYWrap', asyncId: 5, triggerAsyncId: 1 }
1876+
// ]
1877+
// After: [
1878+
// { type: 'TTYWrap', asyncId: 2, triggerAsyncId: 1 },
1879+
// { type: 'TTYWrap', asyncId: 4, triggerAsyncId: 1 },
1880+
// { type: 'TTYWrap', asyncId: 5, triggerAsyncId: 1 },
1881+
// { type: 'Timeout', asyncId: 7, triggerAsyncId: 1 }
1882+
// ]
1883+
```
1884+
18201885
## `process.getegid()`
18211886
18221887
<!-- YAML
@@ -3792,6 +3857,7 @@ cases:
37923857
[`'message'`]: child_process.md#event-message
37933858
[`'uncaughtException'`]: #event-uncaughtexception
37943859
[`--unhandled-rejections`]: cli.md#--unhandled-rejectionsmode
3860+
[`AsyncHook`]: async_hooks.md#class-asynchook
37953861
[`Buffer`]: buffer.md
37963862
[`ChildProcess.disconnect()`]: child_process.md#subprocessdisconnect
37973863
[`ChildProcess.send()`]: child_process.md#subprocesssendmessage-sendhandle-options-callback
@@ -3802,9 +3868,12 @@ cases:
38023868
[`Promise.race()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
38033869
[`Worker`]: worker_threads.md#class-worker
38043870
[`Worker` constructor]: worker_threads.md#new-workerfilename-options
3871+
[`async_hooks`]: async_hooks.md
38053872
[`console.error()`]: console.md#consoleerrordata-args
38063873
[`console.log()`]: console.md#consolelogdata-args
3874+
[`destroy` callbacks]: async_hooks.md#destroyasyncid
38073875
[`domain`]: domain.md
3876+
[`init` callbacks]: async_hooks.md#initasyncid-type-triggerasyncid-resource
38083877
[`net.Server`]: net.md#class-netserver
38093878
[`net.Socket`]: net.md#class-netsocket
38103879
[`os.constants.dlopen`]: os.md#dlopen-constants

lib/internal/bootstrap/node.js

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,24 @@
3939
setupPrepareStackTrace();
4040

4141
const {
42+
ArrayPrototypeConcat,
43+
ArrayPrototypeForEach,
44+
ArrayPrototypePush,
4245
FunctionPrototypeCall,
4346
JSONParse,
4447
ObjectDefineProperty,
4548
ObjectDefineProperties,
4649
ObjectGetPrototypeOf,
4750
ObjectPreventExtensions,
4851
ObjectSetPrototypeOf,
52+
ObjectValues,
4953
ReflectGet,
5054
ReflectSet,
5155
SymbolToStringTag,
5256
globalThis,
5357
} = primordials;
5458
const config = internalBinding('config');
59+
const internalTimers = require('internal/timers');
5560
const { deprecate, lazyDOMExceptionClass } = require('internal/util');
5661

5762
setupProcessObject();
@@ -150,6 +155,37 @@ const rawMethods = internalBinding('process_methods');
150155
process._getActiveRequests = rawMethods._getActiveRequests;
151156
process._getActiveHandles = rawMethods._getActiveHandles;
152157

158+
process.getActiveResourcesInfo = function() {
159+
const resources = ArrayPrototypeConcat(rawMethods._getActiveRequestsInfo(),
160+
rawMethods._getActiveHandlesInfo());
161+
162+
ArrayPrototypeForEach(ObjectValues(internalTimers.timerListMap), (list) => {
163+
let timeout = list._idlePrev === list ? null : list._idlePrev;
164+
while (timeout !== null) {
165+
ArrayPrototypePush(resources, {
166+
type: 'Timeout',
167+
asyncId: timeout[internalTimers.async_id_symbol],
168+
triggerAsyncId: timeout[internalTimers.trigger_async_id_symbol],
169+
});
170+
timeout = timeout._idlePrev === list ? null : list._idlePrev;
171+
}
172+
});
173+
174+
const queue = internalTimers.outstandingQueue.head !== null ?
175+
internalTimers.outstandingQueue : internalTimers.immediateQueue;
176+
let immediate = queue.head;
177+
while (immediate !== null) {
178+
ArrayPrototypePush(resources, {
179+
type: 'Immediate',
180+
asyncId: immediate[internalTimers.async_id_symbol],
181+
triggerAsyncId: immediate[internalTimers.trigger_async_id_symbol],
182+
});
183+
immediate = immediate._idleNext;
184+
}
185+
186+
return resources;
187+
};
188+
153189
// TODO(joyeecheung): remove these
154190
process.reallyExit = rawMethods.reallyExit;
155191
process._kill = rawMethods._kill;
@@ -360,9 +396,11 @@ process.emitWarning = emitWarning;
360396
// TODO(joyeecheung): either remove it or make it public
361397
process._tickCallback = runNextTicks;
362398

363-
const { getTimerCallbacks } = require('internal/timers');
364399
const { setupTimers } = internalBinding('timers');
365-
const { processImmediate, processTimers } = getTimerCallbacks(runNextTicks);
400+
const {
401+
processImmediate,
402+
processTimers,
403+
} = internalTimers.getTimerCallbacks(runNextTicks);
366404
// Sets two per-Environment callbacks that will be run from libuv:
367405
// - processImmediate will be run in the callback of the per-Environment
368406
// check handle.

lib/internal/timers.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ const kRefed = Symbol('refed');
139139
// Create a single linked list instance only once at startup
140140
const immediateQueue = new ImmediateList();
141141

142+
// If an uncaught exception was thrown during execution of immediateQueue,
143+
// this queue will store all remaining Immediates that need to run upon
144+
// resolution of all error handling (if process is still alive).
145+
const outstandingQueue = new ImmediateList();
146+
142147
let nextExpiry = Infinity;
143148
let refCount = 0;
144149

@@ -413,11 +418,6 @@ function setPosition(node, pos) {
413418
}
414419

415420
function getTimerCallbacks(runNextTicks) {
416-
// If an uncaught exception was thrown during execution of immediateQueue,
417-
// this queue will store all remaining Immediates that need to run upon
418-
// resolution of all error handling (if process is still alive).
419-
const outstandingQueue = new ImmediateList();
420-
421421
function processImmediate() {
422422
const queue = outstandingQueue.head !== null ?
423423
outstandingQueue : immediateQueue;
@@ -649,6 +649,7 @@ module.exports = {
649649
setUnrefTimeout,
650650
getTimerDuration,
651651
immediateQueue,
652+
outstandingQueue,
652653
getTimerCallbacks,
653654
immediateInfoFields: {
654655
kCount,

src/node_process_methods.cc

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "async_wrap-inl.h"
12
#include "base_object-inl.h"
23
#include "debug_utils-inl.h"
34
#include "env-inl.h"
@@ -43,8 +44,11 @@ using v8::FunctionCallbackInfo;
4344
using v8::HeapStatistics;
4445
using v8::Integer;
4546
using v8::Isolate;
47+
using v8::JustVoid;
4648
using v8::Local;
49+
using v8::Maybe;
4750
using v8::NewStringType;
51+
using v8::Nothing;
4852
using v8::Number;
4953
using v8::Object;
5054
using v8::String;
@@ -242,6 +246,28 @@ static void Uptime(const FunctionCallbackInfo<Value>& args) {
242246
args.GetReturnValue().Set(result);
243247
}
244248

249+
static Maybe<void> GetResourceInfo(Environment* env, AsyncWrap* w,
250+
std::vector<Local<Value>>* resources) {
251+
Local<Object> resource = Object::New(env->isolate());
252+
253+
if (resource->Set(env->context(),
254+
OneByteString(env->isolate(), "type"),
255+
OneByteString(env->isolate(), w->MemoryInfoName().c_str()))
256+
.IsNothing() ||
257+
resource->Set(env->context(),
258+
OneByteString(env->isolate(), "asyncId"),
259+
Number::New(env->isolate(), w->get_async_id()))
260+
.IsNothing() ||
261+
resource->Set(env->context(),
262+
OneByteString(env->isolate(), "triggerAsyncId"),
263+
Number::New(env->isolate(), w->get_trigger_async_id()))
264+
.IsNothing()) return Nothing<void>();
265+
266+
resources->emplace_back(resource);
267+
268+
return JustVoid();
269+
}
270+
245271
static void GetActiveRequests(const FunctionCallbackInfo<Value>& args) {
246272
Environment* env = Environment::GetCurrent(args);
247273

@@ -250,13 +276,28 @@ static void GetActiveRequests(const FunctionCallbackInfo<Value>& args) {
250276
AsyncWrap* w = req_wrap->GetAsyncWrap();
251277
if (w->persistent().IsEmpty())
252278
continue;
279+
253280
request_v.emplace_back(w->GetOwner());
254281
}
255282

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

287+
static void GetActiveRequestsInfo(const FunctionCallbackInfo<Value>& args) {
288+
Environment* env = Environment::GetCurrent(args);
289+
290+
std::vector<Local<Value>> requests;
291+
for (ReqWrapBase* req_wrap : *env->req_wrap_queue()) {
292+
AsyncWrap* w = req_wrap->GetAsyncWrap();
293+
if (!w->persistent().IsEmpty())
294+
if (GetResourceInfo(env, w, &requests).IsNothing()) return;
295+
}
296+
297+
args.GetReturnValue().Set(
298+
Array::New(env->isolate(), requests.data(), requests.size()));
299+
}
300+
260301
// Non-static, friend of HandleWrap. Could have been a HandleWrap method but
261302
// implemented here for consistency with GetActiveRequests().
262303
void GetActiveHandles(const FunctionCallbackInfo<Value>& args) {
@@ -272,6 +313,19 @@ void GetActiveHandles(const FunctionCallbackInfo<Value>& args) {
272313
Array::New(env->isolate(), handle_v.data(), handle_v.size()));
273314
}
274315

316+
void GetActiveHandlesInfo(const FunctionCallbackInfo<Value>& args) {
317+
Environment* env = Environment::GetCurrent(args);
318+
319+
std::vector<Local<Value>> handles;
320+
for (HandleWrap* w : *env->handle_wrap_queue()) {
321+
if (!w->persistent().IsEmpty() && HandleWrap::HasRef(w))
322+
if (GetResourceInfo(env, w, &handles).IsNothing()) return;
323+
}
324+
325+
args.GetReturnValue().Set(
326+
Array::New(env->isolate(), handles.data(), handles.size()));
327+
}
328+
275329
static void ResourceUsage(const FunctionCallbackInfo<Value>& args) {
276330
Environment* env = Environment::GetCurrent(args);
277331

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

549603
env->SetMethod(target, "_getActiveRequests", GetActiveRequests);
604+
env->SetMethod(target, "_getActiveRequestsInfo", GetActiveRequestsInfo);
550605
env->SetMethod(target, "_getActiveHandles", GetActiveHandles);
606+
env->SetMethod(target, "_getActiveHandlesInfo", GetActiveHandlesInfo);
551607
env->SetMethod(target, "_kill", Kill);
552608

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

576632
registry->Register(GetActiveRequests);
633+
registry->Register(GetActiveRequestsInfo);
577634
registry->Register(GetActiveHandles);
635+
registry->Register(GetActiveHandlesInfo);
578636
registry->Register(Kill);
579637

580638
registry->Register(Cwd);

0 commit comments

Comments
 (0)