Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

process: add getActiveResourcesInfo() #40813

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);