Skip to content

Commit d8a2125

Browse files
RaisinTendanielleadams
authored andcommitted
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 strings containing the types of the active resources that are currently keeping the event loop alive. Signed-off-by: Darshan Sen <[email protected]> PR-URL: #40813 Reviewed-By: Stephen Belanger <[email protected]> Reviewed-By: Vladimir de Turckheim <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent bbdcd05 commit d8a2125

13 files changed

+281
-5
lines changed

doc/api/process.md

+38
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,44 @@ 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: {string\[]}
1829+
1830+
The `process.getActiveResourcesInfo()` method returns an array of strings
1831+
containing the types of the active resources that are currently keeping the
1832+
event loop alive.
1833+
1834+
```mjs
1835+
import { getActiveResourcesInfo } from 'process';
1836+
import { setTimeout } from 'timers';
1837+
1838+
console.log('Before:', getActiveResourcesInfo());
1839+
setTimeout(() => {}, 1000);
1840+
console.log('After:', getActiveResourcesInfo());
1841+
// Prints:
1842+
// Before: [ 'CloseReq', 'TTYWrap', 'TTYWrap', 'TTYWrap' ]
1843+
// After: [ 'CloseReq', 'TTYWrap', 'TTYWrap', 'TTYWrap', 'Timeout' ]
1844+
```
1845+
1846+
```cjs
1847+
const { getActiveResourcesInfo } = require('process');
1848+
const { setTimeout } = require('timers');
1849+
1850+
console.log('Before:', getActiveResourcesInfo());
1851+
setTimeout(() => {}, 1000);
1852+
console.log('After:', getActiveResourcesInfo());
1853+
// Prints:
1854+
// Before: [ 'TTYWrap', 'TTYWrap', 'TTYWrap' ]
1855+
// After: [ 'TTYWrap', 'TTYWrap', 'TTYWrap', 'Timeout' ]
1856+
```
1857+
18201858
## `process.getegid()`
18211859
18221860
<!-- YAML

lib/internal/bootstrap/node.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,24 @@
3939
setupPrepareStackTrace();
4040

4141
const {
42+
ArrayPrototypeConcat,
43+
ArrayPrototypeFilter,
44+
ArrayPrototypeMap,
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,16 @@ const rawMethods = internalBinding('process_methods');
150155
process._getActiveRequests = rawMethods._getActiveRequests;
151156
process._getActiveHandles = rawMethods._getActiveHandles;
152157

158+
process.getActiveResourcesInfo = function() {
159+
return ArrayPrototypeConcat(
160+
rawMethods._getActiveRequestsInfo(),
161+
rawMethods._getActiveHandlesInfo(),
162+
ArrayPrototypeMap(
163+
ArrayPrototypeFilter(ObjectValues(internalTimers.activeTimersMap),
164+
({ resource }) => resource.hasRef()),
165+
({ type }) => type));
166+
};
167+
153168
// TODO(joyeecheung): remove these
154169
process.reallyExit = rawMethods.reallyExit;
155170
process._kill = rawMethods._kill;
@@ -360,9 +375,11 @@ process.emitWarning = emitWarning;
360375
// TODO(joyeecheung): either remove it or make it public
361376
process._tickCallback = runNextTicks;
362377

363-
const { getTimerCallbacks } = require('internal/timers');
364378
const { setupTimers } = internalBinding('timers');
365-
const { processImmediate, processTimers } = getTimerCallbacks(runNextTicks);
379+
const {
380+
processImmediate,
381+
processTimers,
382+
} = internalTimers.getTimerCallbacks(runNextTicks);
366383
// Sets two per-Environment callbacks that will be run from libuv:
367384
// - processImmediate will be run in the callback of the per-Environment
368385
// check handle.

lib/internal/timers.js

+13
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ const kRefed = Symbol('refed');
139139
// Create a single linked list instance only once at startup
140140
const immediateQueue = new ImmediateList();
141141

142+
// Object map containing timers
143+
//
144+
// - key = asyncId
145+
// - value = { type, resource }
146+
const activeTimersMap = ObjectCreate(null);
147+
142148
let nextExpiry = Infinity;
143149
let refCount = 0;
144150

@@ -160,6 +166,7 @@ function initAsyncResource(resource, type) {
160166
resource[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
161167
if (initHooksExist())
162168
emitInit(asyncId, type, triggerAsyncId, resource);
169+
activeTimersMap[asyncId] = { type, resource };
163170
}
164171

165172
// Timer constructor function.
@@ -446,6 +453,8 @@ function getTimerCallbacks(runNextTicks) {
446453
continue;
447454
}
448455

456+
// TODO(RaisinTen): Destroy and unref the Immediate after _onImmediate()
457+
// gets executed, just like how Timeouts work.
449458
immediate._destroyed = true;
450459

451460
immediateInfo[kCount]--;
@@ -469,6 +478,7 @@ function getTimerCallbacks(runNextTicks) {
469478

470479
if (destroyHooksExist())
471480
emitDestroy(asyncId);
481+
delete activeTimersMap[asyncId];
472482

473483
outstandingQueue.head = immediate = immediate._idleNext;
474484
}
@@ -541,6 +551,7 @@ function getTimerCallbacks(runNextTicks) {
541551

542552
if (destroyHooksExist())
543553
emitDestroy(asyncId);
554+
delete activeTimersMap[asyncId];
544555
}
545556
continue;
546557
}
@@ -569,6 +580,7 @@ function getTimerCallbacks(runNextTicks) {
569580

570581
if (destroyHooksExist())
571582
emitDestroy(asyncId);
583+
delete activeTimersMap[asyncId];
572584
}
573585
}
574586

@@ -658,6 +670,7 @@ module.exports = {
658670
active,
659671
unrefActive,
660672
insert,
673+
activeTimersMap,
661674
timerListMap,
662675
timerListQueue,
663676
decRefCount,

lib/timers.js

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const {
4545
kRefed,
4646
kHasPrimitive,
4747
getTimerDuration,
48+
activeTimersMap,
4849
timerListMap,
4950
timerListQueue,
5051
immediateQueue,
@@ -87,6 +88,7 @@ function unenroll(item) {
8788
// Fewer checks may be possible, but these cover everything.
8889
if (destroyHooksExist() && item[async_id_symbol] !== undefined)
8990
emitDestroy(item[async_id_symbol]);
91+
delete activeTimersMap[item[async_id_symbol]];
9092

9193
L.remove(item);
9294

@@ -329,6 +331,7 @@ function clearImmediate(immediate) {
329331
if (destroyHooksExist() && immediate[async_id_symbol] !== undefined) {
330332
emitDestroy(immediate[async_id_symbol]);
331333
}
334+
delete activeTimersMap[immediate[async_id_symbol]];
332335

333336
immediate._onImmediate = null;
334337

src/node_process_methods.cc

+34
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"
@@ -257,6 +258,21 @@ static void GetActiveRequests(const FunctionCallbackInfo<Value>& args) {
257258
Array::New(env->isolate(), request_v.data(), request_v.size()));
258259
}
259260

261+
static void GetActiveRequestsInfo(const FunctionCallbackInfo<Value>& args) {
262+
Environment* env = Environment::GetCurrent(args);
263+
264+
std::vector<Local<Value>> requests_info;
265+
for (ReqWrapBase* req_wrap : *env->req_wrap_queue()) {
266+
AsyncWrap* w = req_wrap->GetAsyncWrap();
267+
if (w->persistent().IsEmpty()) continue;
268+
requests_info.emplace_back(OneByteString(env->isolate(),
269+
w->MemoryInfoName().c_str()));
270+
}
271+
272+
args.GetReturnValue().Set(
273+
Array::New(env->isolate(), requests_info.data(), requests_info.size()));
274+
}
275+
260276
// Non-static, friend of HandleWrap. Could have been a HandleWrap method but
261277
// implemented here for consistency with GetActiveRequests().
262278
void GetActiveHandles(const FunctionCallbackInfo<Value>& args) {
@@ -272,6 +288,20 @@ void GetActiveHandles(const FunctionCallbackInfo<Value>& args) {
272288
Array::New(env->isolate(), handle_v.data(), handle_v.size()));
273289
}
274290

291+
void GetActiveHandlesInfo(const FunctionCallbackInfo<Value>& args) {
292+
Environment* env = Environment::GetCurrent(args);
293+
294+
std::vector<Local<Value>> handles_info;
295+
for (HandleWrap* w : *env->handle_wrap_queue()) {
296+
if (w->persistent().IsEmpty() || !HandleWrap::HasRef(w)) continue;
297+
handles_info.emplace_back(OneByteString(env->isolate(),
298+
w->MemoryInfoName().c_str()));
299+
}
300+
301+
args.GetReturnValue().Set(
302+
Array::New(env->isolate(), handles_info.data(), handles_info.size()));
303+
}
304+
275305
static void ResourceUsage(const FunctionCallbackInfo<Value>& args) {
276306
Environment* env = Environment::GetCurrent(args);
277307

@@ -547,7 +577,9 @@ static void Initialize(Local<Object> target,
547577
env->SetMethod(target, "resourceUsage", ResourceUsage);
548578

549579
env->SetMethod(target, "_getActiveRequests", GetActiveRequests);
580+
env->SetMethod(target, "_getActiveRequestsInfo", GetActiveRequestsInfo);
550581
env->SetMethod(target, "_getActiveHandles", GetActiveHandles);
582+
env->SetMethod(target, "_getActiveHandlesInfo", GetActiveHandlesInfo);
551583
env->SetMethod(target, "_kill", Kill);
552584

553585
env->SetMethodNoSideEffect(target, "cwd", Cwd);
@@ -574,7 +606,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
574606
registry->Register(ResourceUsage);
575607

576608
registry->Register(GetActiveRequests);
609+
registry->Register(GetActiveRequestsInfo);
577610
registry->Register(GetActiveHandles);
611+
registry->Register(GetActiveHandlesInfo);
578612
registry->Register(Kill);
579613

580614
registry->Register(Cwd);

test/parallel/test-handle-wrap-isrefed.js

+26
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,31 @@ const { kStateSymbol } = require('internal/dgram');
106106
false, 'tcp_wrap: not unrefed on close')));
107107
}
108108

109+
// timers
110+
{
111+
strictEqual(process.getActiveResourcesInfo().filter(
112+
(type) => type === 'Timeout').length, 0);
113+
const timeout = setTimeout(() => {}, 500);
114+
strictEqual(process.getActiveResourcesInfo().filter(
115+
(type) => type === 'Timeout').length, 1);
116+
timeout.unref();
117+
strictEqual(process.getActiveResourcesInfo().filter(
118+
(type) => type === 'Timeout').length, 0);
119+
timeout.ref();
120+
strictEqual(process.getActiveResourcesInfo().filter(
121+
(type) => type === 'Timeout').length, 1);
122+
123+
strictEqual(process.getActiveResourcesInfo().filter(
124+
(type) => type === 'Immediate').length, 0);
125+
const immediate = setImmediate(() => {});
126+
strictEqual(process.getActiveResourcesInfo().filter(
127+
(type) => type === 'Immediate').length, 1);
128+
immediate.unref();
129+
strictEqual(process.getActiveResourcesInfo().filter(
130+
(type) => type === 'Immediate').length, 0);
131+
immediate.ref();
132+
strictEqual(process.getActiveResourcesInfo().filter(
133+
(type) => type === 'Immediate').length, 1);
134+
}
109135

110136
// See also test/pseudo-tty/test-handle-wrap-isrefed-tty.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const net = require('net');
6+
const NUM = 8;
7+
const connections = [];
8+
const clients = [];
9+
let clients_counter = 0;
10+
11+
const server = net.createServer(function listener(c) {
12+
connections.push(c);
13+
}).listen(0, makeConnection);
14+
15+
16+
function makeConnection() {
17+
if (clients_counter >= NUM) return;
18+
net.connect(server.address().port, function connected() {
19+
clientConnected(this);
20+
makeConnection();
21+
});
22+
}
23+
24+
25+
function clientConnected(client) {
26+
clients.push(client);
27+
if (++clients_counter >= NUM)
28+
checkAll();
29+
}
30+
31+
32+
function checkAll() {
33+
assert.strictEqual(process.getActiveResourcesInfo().filter(
34+
(type) => type === 'TCPSocketWrap').length,
35+
clients.length + connections.length);
36+
37+
clients.forEach((item) => item.destroy());
38+
connections.forEach((item) => item.end());
39+
40+
assert.strictEqual(process.getActiveResourcesInfo().filter(
41+
(type) => type === 'TCPServerWrap').length, 1);
42+
43+
server.close();
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const fs = require('fs');
6+
7+
for (let i = 0; i < 12; i++) {
8+
fs.open(__filename, 'r', common.mustCall());
9+
}
10+
11+
assert.strictEqual(process.getActiveResourcesInfo().length, 12);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
const assert = require('assert');
6+
7+
assert.strictEqual(process.getActiveResourcesInfo().filter(
8+
(type) => type === 'Timeout').length, 0);
9+
10+
let count = 0;
11+
const interval = setInterval(common.mustCall(() => {
12+
assert.strictEqual(process.getActiveResourcesInfo().filter(
13+
(type) => type === 'Timeout').length, 1);
14+
++count;
15+
if (count === 3) {
16+
clearInterval(interval);
17+
}
18+
}, 3), 0);
19+
20+
assert.strictEqual(process.getActiveResourcesInfo().filter(
21+
(type) => type === 'Timeout').length, 1);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
for (let i = 0; i < 10; ++i) {
7+
for (let j = 0; j < 10; ++j) {
8+
setTimeout(common.mustCall(), i);
9+
}
10+
}
11+
12+
assert.strictEqual(process.getActiveResourcesInfo().filter(
13+
(type) => type === 'Timeout').length, 100);
14+
15+
for (let i = 0; i < 10; ++i) {
16+
setImmediate(common.mustCall());
17+
}
18+
19+
assert.strictEqual(process.getActiveResourcesInfo().filter(
20+
(type) => type === 'Immediate').length, 10);

0 commit comments

Comments
 (0)