Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const { registerDestroyHook } = internal_async_hooks;
const {
executionAsyncId,
triggerAsyncId,
getActiveResources,
// Private API
hasAsyncIdStack,
getHookArrays,
Expand Down Expand Up @@ -347,6 +348,7 @@ module.exports = {
createHook,
executionAsyncId,
triggerAsyncId,
getActiveResources,
executionAsyncResource,
// Embedder API
AsyncResource,
Expand Down
116 changes: 116 additions & 0 deletions lib/internal/async_hooks.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
'use strict';

const {
ArrayIsArray,
ArrayPrototypeForEach,
ArrayPrototypeSlice,
ErrorCaptureStackTrace,
ObjectPrototypeHasOwnProperty,
ObjectDefineProperty,
ObjectValues,
Promise,
ReflectOwnKeys,
Symbol,
} = primordials;

Expand Down Expand Up @@ -544,9 +548,121 @@ function triggerAsyncId() {
}


const {
_getActiveHandles,
_getActiveRequests,
} = internalBinding('process_methods');

function getSummaryOf(source) {
const summary = {};
const allowedTypes = [
'bigint',
'boolean',
'number',
'string',
];

for (const asyncId in source) {
const object = source[asyncId];
const eachSummary = {
type: object.constructor.name
};

for (const key of ReflectOwnKeys(object)) {
const value = object[key];

if (allowedTypes.includes(typeof value)) {
eachSummary[key] = value;
}
}

summary[asyncId] = eachSummary;
}

return summary;
}

let internalTimers;

function getActiveResources(filters) {
if (internalTimers == null) {
internalTimers = require('internal/timers');
}

if (!ArrayIsArray(filters) && typeof filters !== 'string') {
filters = [
'handles',
'requests',
'timeouts',
'immediates',
];
}

let activeResources = {};

if (filters.includes('handles')) {
const handles = _getActiveHandles();

activeResources = {
...activeResources,
...getSummaryOf(handles),
};
}

if (filters.includes('requests')) {
const reqs = _getActiveRequests();

activeResources = {
...activeResources,
...getSummaryOf(reqs),
};
}

if (filters.includes('timeouts')) {
const timeouts = {};

ArrayPrototypeForEach(ObjectValues(internalTimers.timerListMap), (list) => {
let timeout = list._idlePrev === list ? null : list._idlePrev;

while (timeout !== null) {
timeouts[timeout[internalTimers.async_id_symbol]] = timeout;

timeout = timeout._idlePrev === list ? null : list._idlePrev;
}
});

activeResources = {
...activeResources,
...getSummaryOf(timeouts),
};
}

if (filters.includes('immediates')) {
const immediates = {};
const queue = internalTimers.outstandingQueue.head !== null ?
internalTimers.outstandingQueue : internalTimers.immediateQueue;
let immediate = queue.head;

while (immediate !== null) {
immediates[immediate[internalTimers.async_id_symbol]] = immediate;

immediate = immediate._idleNext;
}

activeResources = {
...activeResources,
...getSummaryOf(immediates),
};
}

return activeResources;
}


module.exports = {
executionAsyncId,
triggerAsyncId,
getActiveResources,
// Private API
getHookArrays,
symbols: {
Expand Down
11 changes: 6 additions & 5 deletions lib/internal/timers.js
Original file line number Diff line number Diff line change
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
32 changes: 22 additions & 10 deletions src/node_process_methods.cc
Original file line number Diff line number Diff line change
@@ -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 @@ -35,7 +36,6 @@ typedef int mode_t;
namespace node {

using v8::ApiObject;
using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::CFunction;
Expand Down Expand Up @@ -253,31 +253,43 @@ static void Uptime(const FunctionCallbackInfo<Value>& args) {
static void GetActiveRequests(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

std::vector<Local<Value>> request_v;
Local<Context> ctx = env->context();
Local<Object> return_obj = Object::New(args.GetIsolate());

for (ReqWrapBase* req_wrap : *env->req_wrap_queue()) {
AsyncWrap* w = req_wrap->GetAsyncWrap();
if (w->persistent().IsEmpty())
continue;
request_v.emplace_back(w->GetOwner());
double async_id = w->get_async_id();
Local<Object> req_obj = w->object();

USE(return_obj->Set(ctx,
Number::New(args.GetIsolate(), async_id),
req_obj));
}

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

// Non-static, friend of HandleWrap. Could have been a HandleWrap method but
// implemented here for consistency with GetActiveRequests().
void GetActiveHandles(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

std::vector<Local<Value>> handle_v;
Local<Context> ctx = env->context();
Local<Object> return_obj = Object::New(args.GetIsolate());

for (auto w : *env->handle_wrap_queue()) {
if (!HandleWrap::HasRef(w))
if (w->persistent().IsEmpty() || !HandleWrap::HasRef(w))
continue;
handle_v.emplace_back(w->GetOwner());
double async_id = w->get_async_id();
Local<Object> handle_object = w->object();
USE(return_obj->Set(ctx, Number::New(args.GetIsolate(),
async_id),
handle_object));
}
args.GetReturnValue().Set(
Array::New(env->isolate(), handle_v.data(), handle_v.size()));

args.GetReturnValue().Set(return_obj);
}

static void ResourceUsage(const FunctionCallbackInfo<Value>& args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
require('../common');
const assert = require('assert');
const net = require('net');
const { getActiveResources } = require('async_hooks');

const NUM = 8;
const connections = [];
const clients = [];
Expand Down Expand Up @@ -30,18 +32,20 @@ function clientConnected(client) {


function checkAll() {
const handles = process._getActiveHandles();
const handles = Object.values(getActiveResources('handles'));

assert.ok(
handles.length >=
clients.length + connections.length + [server].length
);

clients.forEach(function(item) {
assert.ok(handles.includes(item));
item.destroy();
});

connections.forEach(function(item) {
assert.ok(handles.includes(item));
item.end();
});

assert.ok(handles.includes(server));
server.close();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const { getActiveResources } = require('async_hooks');

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

assert.strictEqual(process._getActiveRequests().length, 12);
assert.strictEqual(Object.values(getActiveResources(['requests'])).length, 12);
24 changes: 24 additions & 0 deletions test/parallel/test-handle-wrap-isrefed.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const common = require('../common');
const strictEqual = require('assert').strictEqual;
const { internalBinding } = require('internal/test/binding');
const { getActiveResources } = require('async_hooks');

// child_process
{
Expand Down Expand Up @@ -107,4 +108,27 @@ const { kStateSymbol } = require('internal/dgram');
}


// timers
{
strictEqual(
Object.values(getActiveResources('timeouts'))
.filter((timeout) => timeout.type === 'Timer'),
0);
setTimeout(() => {}, 500);
strictEqual(
Object.values(getActiveResources('timeouts'))
.filter((timeout) => timeout.type === 'Timer'),
1);

strictEqual(
Object.values(getActiveResources('immediates'))
.filter((immediate) => immediate.type === 'Immediate'),
0);
setImmediate(() => {});
strictEqual(
Object.values(getActiveResources('immediates'))
.filter((immediate) => immediate.type === 'Immediate'),
1);
}

// See also test/pseudo-tty/test-handle-wrap-isrefed-tty.js
29 changes: 22 additions & 7 deletions test/pseudo-tty/ref_keeps_node_running.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,40 @@ require('../common');
const { internalBinding } = require('internal/test/binding');
const { TTY, isTTY } = internalBinding('tty_wrap');
const strictEqual = require('assert').strictEqual;
const { getActiveResources } = require('async_hooks');

strictEqual(isTTY(0), true, 'fd 0 is not a TTY');

const handle = new TTY(0);
handle.readStart();
handle.onread = () => {};

function isHandleActive(handle) {
return process._getActiveHandles().some((active) => active === handle);
}

strictEqual(isHandleActive(handle), true, 'TTY handle not initially active');
strictEqual(
Object.values(getActiveResources('handles')).length,
1,
'TTY handle not initially active'
);

handle.unref();

strictEqual(isHandleActive(handle), false, 'TTY handle active after unref()');
strictEqual(
Object.values(getActiveResources('handles')).length,
0,
'TTY handle not initially active'
);

handle.ref();

strictEqual(isHandleActive(handle), true, 'TTY handle inactive after ref()');
strictEqual(
Object.values(getActiveResources('handles')).length,
1,
'TTY handle not initially active'
);

handle.unref();

strictEqual(
Object.values(getActiveResources('handles')).length,
0,
'TTY handle not initially active'
);
Loading