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 strings containing the types of the
active resources that are currently keeping the event loop alive.

Signed-off-by: Darshan Sen <darshan.sen@postman.com>

PR-URL: #40813
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Vladimir de Turckheim <vlad2t@hotmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
  • Loading branch information
RaisinTen authored and danielleadams committed Dec 17, 2021
1 parent bbdcd05 commit d8a2125
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 5 deletions.
38 changes: 38 additions & 0 deletions doc/api/process.md
Expand Up @@ -1817,6 +1817,44 @@ 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: {string\[]}
The `process.getActiveResourcesInfo()` method returns an array of strings
containing the types of the active resources that are currently keeping the
event loop alive.
```mjs
import { getActiveResourcesInfo } from 'process';
import { setTimeout } from 'timers';

console.log('Before:', getActiveResourcesInfo());
setTimeout(() => {}, 1000);
console.log('After:', getActiveResourcesInfo());
// Prints:
// Before: [ 'CloseReq', 'TTYWrap', 'TTYWrap', 'TTYWrap' ]
// After: [ 'CloseReq', 'TTYWrap', 'TTYWrap', 'TTYWrap', 'Timeout' ]
```
```cjs
const { getActiveResourcesInfo } = require('process');
const { setTimeout } = require('timers');

console.log('Before:', getActiveResourcesInfo());
setTimeout(() => {}, 1000);
console.log('After:', getActiveResourcesInfo());
// Prints:
// Before: [ 'TTYWrap', 'TTYWrap', 'TTYWrap' ]
// After: [ 'TTYWrap', 'TTYWrap', 'TTYWrap', 'Timeout' ]
```
## `process.getegid()`
<!-- YAML
Expand Down
21 changes: 19 additions & 2 deletions lib/internal/bootstrap/node.js
Expand Up @@ -39,19 +39,24 @@
setupPrepareStackTrace();

const {
ArrayPrototypeConcat,
ArrayPrototypeFilter,
ArrayPrototypeMap,
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,16 @@ const rawMethods = internalBinding('process_methods');
process._getActiveRequests = rawMethods._getActiveRequests;
process._getActiveHandles = rawMethods._getActiveHandles;

process.getActiveResourcesInfo = function() {
return ArrayPrototypeConcat(
rawMethods._getActiveRequestsInfo(),
rawMethods._getActiveHandlesInfo(),
ArrayPrototypeMap(
ArrayPrototypeFilter(ObjectValues(internalTimers.activeTimersMap),
({ resource }) => resource.hasRef()),
({ type }) => type));
};

// TODO(joyeecheung): remove these
process.reallyExit = rawMethods.reallyExit;
process._kill = rawMethods._kill;
Expand Down Expand Up @@ -360,9 +375,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
13 changes: 13 additions & 0 deletions lib/internal/timers.js
Expand Up @@ -139,6 +139,12 @@ const kRefed = Symbol('refed');
// Create a single linked list instance only once at startup
const immediateQueue = new ImmediateList();

// Object map containing timers
//
// - key = asyncId
// - value = { type, resource }
const activeTimersMap = ObjectCreate(null);

let nextExpiry = Infinity;
let refCount = 0;

Expand All @@ -160,6 +166,7 @@ function initAsyncResource(resource, type) {
resource[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
if (initHooksExist())
emitInit(asyncId, type, triggerAsyncId, resource);
activeTimersMap[asyncId] = { type, resource };
}

// Timer constructor function.
Expand Down Expand Up @@ -446,6 +453,8 @@ function getTimerCallbacks(runNextTicks) {
continue;
}

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

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

if (destroyHooksExist())
emitDestroy(asyncId);
delete activeTimersMap[asyncId];

outstandingQueue.head = immediate = immediate._idleNext;
}
Expand Down Expand Up @@ -541,6 +551,7 @@ function getTimerCallbacks(runNextTicks) {

if (destroyHooksExist())
emitDestroy(asyncId);
delete activeTimersMap[asyncId];
}
continue;
}
Expand Down Expand Up @@ -569,6 +580,7 @@ function getTimerCallbacks(runNextTicks) {

if (destroyHooksExist())
emitDestroy(asyncId);
delete activeTimersMap[asyncId];
}
}

Expand Down Expand Up @@ -658,6 +670,7 @@ module.exports = {
active,
unrefActive,
insert,
activeTimersMap,
timerListMap,
timerListQueue,
decRefCount,
Expand Down
3 changes: 3 additions & 0 deletions lib/timers.js
Expand Up @@ -45,6 +45,7 @@ const {
kRefed,
kHasPrimitive,
getTimerDuration,
activeTimersMap,
timerListMap,
timerListQueue,
immediateQueue,
Expand Down Expand Up @@ -87,6 +88,7 @@ function unenroll(item) {
// Fewer checks may be possible, but these cover everything.
if (destroyHooksExist() && item[async_id_symbol] !== undefined)
emitDestroy(item[async_id_symbol]);
delete activeTimersMap[item[async_id_symbol]];

L.remove(item);

Expand Down Expand Up @@ -329,6 +331,7 @@ function clearImmediate(immediate) {
if (destroyHooksExist() && immediate[async_id_symbol] !== undefined) {
emitDestroy(immediate[async_id_symbol]);
}
delete activeTimersMap[immediate[async_id_symbol]];

immediate._onImmediate = null;

Expand Down
34 changes: 34 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 @@ -257,6 +258,21 @@ static void GetActiveRequests(const FunctionCallbackInfo<Value>& args) {
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_info;
for (ReqWrapBase* req_wrap : *env->req_wrap_queue()) {
AsyncWrap* w = req_wrap->GetAsyncWrap();
if (w->persistent().IsEmpty()) continue;
requests_info.emplace_back(OneByteString(env->isolate(),
w->MemoryInfoName().c_str()));
}

args.GetReturnValue().Set(
Array::New(env->isolate(), requests_info.data(), requests_info.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 +288,20 @@ 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_info;
for (HandleWrap* w : *env->handle_wrap_queue()) {
if (w->persistent().IsEmpty() || !HandleWrap::HasRef(w)) continue;
handles_info.emplace_back(OneByteString(env->isolate(),
w->MemoryInfoName().c_str()));
}

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

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

Expand Down Expand Up @@ -547,7 +577,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 +606,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
26 changes: 26 additions & 0 deletions test/parallel/test-handle-wrap-isrefed.js
Expand Up @@ -106,5 +106,31 @@ const { kStateSymbol } = require('internal/dgram');
false, 'tcp_wrap: not unrefed on close')));
}

// timers
{
strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Timeout').length, 0);
const timeout = setTimeout(() => {}, 500);
strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Timeout').length, 1);
timeout.unref();
strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Timeout').length, 0);
timeout.ref();
strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Timeout').length, 1);

strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Immediate').length, 0);
const immediate = setImmediate(() => {});
strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Immediate').length, 1);
immediate.unref();
strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Immediate').length, 0);
immediate.ref();
strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Immediate').length, 1);
}

// See also test/pseudo-tty/test-handle-wrap-isrefed-tty.js
@@ -0,0 +1,44 @@
'use strict';

require('../common');
const assert = require('assert');
const net = require('net');
const NUM = 8;
const connections = [];
const clients = [];
let clients_counter = 0;

const server = net.createServer(function listener(c) {
connections.push(c);
}).listen(0, makeConnection);


function makeConnection() {
if (clients_counter >= NUM) return;
net.connect(server.address().port, function connected() {
clientConnected(this);
makeConnection();
});
}


function clientConnected(client) {
clients.push(client);
if (++clients_counter >= NUM)
checkAll();
}


function checkAll() {
assert.strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'TCPSocketWrap').length,
clients.length + connections.length);

clients.forEach((item) => item.destroy());
connections.forEach((item) => item.end());

assert.strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'TCPServerWrap').length, 1);

server.close();
}
@@ -0,0 +1,11 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const fs = require('fs');

for (let i = 0; i < 12; i++) {
fs.open(__filename, 'r', common.mustCall());
}

assert.strictEqual(process.getActiveResourcesInfo().length, 12);
@@ -0,0 +1,21 @@
'use strict';

const common = require('../common');

const assert = require('assert');

assert.strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Timeout').length, 0);

let count = 0;
const interval = setInterval(common.mustCall(() => {
assert.strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Timeout').length, 1);
++count;
if (count === 3) {
clearInterval(interval);
}
}, 3), 0);

assert.strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Timeout').length, 1);
@@ -0,0 +1,20 @@
'use strict';

const common = require('../common');
const assert = require('assert');

for (let i = 0; i < 10; ++i) {
for (let j = 0; j < 10; ++j) {
setTimeout(common.mustCall(), i);
}
}

assert.strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Timeout').length, 100);

for (let i = 0; i < 10; ++i) {
setImmediate(common.mustCall());
}

assert.strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'Immediate').length, 10);

0 comments on commit d8a2125

Please sign in to comment.