Skip to content

Commit 1d6325f

Browse files
Fishrock123RaisinTen
authored andcommitted
async_hooks: add getActiveResources (prototype)
Prototype of a new getActiveResources() API for async_hooks. Returns an object map of { <id>: <resource> }, and differs slightly from getActiveHandles(). Currently, it works for all async_hooks resources except nextTick-s, which are probably not useful to expose in this way.
1 parent 29fbd7b commit 1d6325f

File tree

8 files changed

+142
-18
lines changed

8 files changed

+142
-18
lines changed

lib/async_hooks.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const {
2323
validateString,
2424
} = require('internal/validators');
2525
const internal_async_hooks = require('internal/async_hooks');
26+
const internalTimers = require('internal/timers');
2627

2728
// Get functions
2829
// For userland AsyncResources, make sure to emit a destroy event when the
@@ -148,6 +149,35 @@ function createHook(fns) {
148149
}
149150

150151

152+
function getActiveResources() {
153+
const handles = process._getActiveHandles();
154+
const reqs = process._getActiveRequests();
155+
156+
const timers = {};
157+
for (const list of Object.values(internalTimers.timerLists)) {
158+
var timer = list._idlePrev === list ? null : list._idlePrev;
159+
160+
while (timer !== null) {
161+
timers[timer[internalTimers.async_id_symbol]] = timer;
162+
163+
timer = timer._idlePrev === list ? null : list._idlePrev;
164+
}
165+
}
166+
167+
const immediates = {};
168+
const queue = internalTimers.outstandingQueue.head !== null ?
169+
internalTimers.outstandingQueue : internalTimers.immediateQueue;
170+
var immediate = queue.head;
171+
while (immediate !== null) {
172+
immediates[immediate[internalTimers.async_id_symbol]] = immediate;
173+
174+
immediate = immediate._idleNext;
175+
}
176+
177+
return Object.assign({}, handles, reqs, timers, immediates);
178+
}
179+
180+
151181
// Embedder API //
152182

153183
const destroyedSymbol = Symbol('destroyed');
@@ -348,6 +378,7 @@ module.exports = {
348378
executionAsyncId,
349379
triggerAsyncId,
350380
executionAsyncResource,
381+
getActiveResources,
351382
// Embedder API
352383
AsyncResource,
353384
};

lib/internal/timers.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,59 @@ let timerListId = NumberMIN_SAFE_INTEGER;
136136

137137
const kRefed = Symbol('refed');
138138

139+
// Object map containing linked lists of timers, keyed and sorted by their
140+
// duration in milliseconds.
141+
//
142+
// - key = time in milliseconds
143+
// - value = linked list
144+
const timerLists = Object.create(null);
145+
146+
// A linked list for storing `setImmediate()` requests
147+
function ImmediateList() {
148+
this.head = null;
149+
this.tail = null;
150+
}
151+
152+
// Appends an item to the end of the linked list, adjusting the current tail's
153+
// previous and next pointers where applicable
154+
ImmediateList.prototype.append = function(item) {
155+
if (this.tail !== null) {
156+
this.tail._idleNext = item;
157+
item._idlePrev = this.tail;
158+
} else {
159+
this.head = item;
160+
}
161+
this.tail = item;
162+
};
163+
164+
// Removes an item from the linked list, adjusting the pointers of adjacent
165+
// items and the linked list's head or tail pointers as necessary
166+
ImmediateList.prototype.remove = function(item) {
167+
if (item._idleNext !== null) {
168+
item._idleNext._idlePrev = item._idlePrev;
169+
}
170+
171+
if (item._idlePrev !== null) {
172+
item._idlePrev._idleNext = item._idleNext;
173+
}
174+
175+
if (item === this.head)
176+
this.head = item._idleNext;
177+
if (item === this.tail)
178+
this.tail = item._idlePrev;
179+
180+
item._idleNext = null;
181+
item._idlePrev = null;
182+
};
183+
139184
// Create a single linked list instance only once at startup
140185
const immediateQueue = new ImmediateList();
141186

187+
// If an uncaught exception was thrown during execution of immediateQueue,
188+
// this queue will store all remaining Immediates that need to run upon
189+
// resolution of all error handling (if process is still alive).
190+
const outstandingQueue = new ImmediateList();
191+
142192
let nextExpiry = Infinity;
143193
let refCount = 0;
144194

@@ -649,6 +699,7 @@ module.exports = {
649699
setUnrefTimeout,
650700
getTimerDuration,
651701
immediateQueue,
702+
outstandingQueue,
652703
getTimerCallbacks,
653704
immediateInfoFields: {
654705
kCount,
@@ -658,6 +709,7 @@ module.exports = {
658709
active,
659710
unrefActive,
660711
insert,
712+
timerLists,
661713
timerListMap,
662714
timerListQueue,
663715
decRefCount,

lib/timers.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ const {
5050
immediateQueue,
5151
active,
5252
unrefActive,
53-
insert
53+
insert,
54+
timerLists: lists,
55+
immediateQueue,
56+
outstandingQueue,
5457
} = require('internal/timers');
5558
const {
5659
promisify: { custom: customPromisify },

src/node_process_methods.cc

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -251,31 +251,42 @@ static void Uptime(const FunctionCallbackInfo<Value>& args) {
251251
static void GetActiveRequests(const FunctionCallbackInfo<Value>& args) {
252252
Environment* env = Environment::GetCurrent(args);
253253

254-
std::vector<Local<Value>> request_v;
255-
for (ReqWrapBase* req_wrap : *env->req_wrap_queue()) {
256-
AsyncWrap* w = req_wrap->GetAsyncWrap();
254+
Local<Context> ctx = env->context();
255+
Local<Object> return_obj = Object::New(args.GetIsolate());
256+
257+
for (auto w : *env->req_wrap_queue()) {
257258
if (w->persistent().IsEmpty())
258259
continue;
259-
request_v.emplace_back(w->GetOwner());
260+
double async_id = w->get_async_id();
261+
Local<Object> req_obj = w->object();
262+
263+
return_obj->Set(ctx, Number::New(args.GetIsolate(), async_id), req_obj);
260264
}
261265

262-
args.GetReturnValue().Set(
263-
Array::New(env->isolate(), request_v.data(), request_v.size()));
266+
args.GetReturnValue().Set(return_obj);
264267
}
265268

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

271-
std::vector<Local<Value>> handle_v;
274+
Local<Context> ctx = env->context();
275+
Local<Object> return_obj = Object::New(args.GetIsolate());
276+
277+
Local<String> owner_sym = env->owner_string();
278+
272279
for (auto w : *env->handle_wrap_queue()) {
273-
if (!HandleWrap::HasRef(w))
280+
if (w->persistent().IsEmpty() || !HandleWrap::HasRef(w))
274281
continue;
275-
handle_v.emplace_back(w->GetOwner());
282+
double async_id = w->get_async_id();
283+
Local<Object> handle_object = w->object();
284+
return_obj->Set(ctx, Number::New(args.GetIsolate(),
285+
async_id),
286+
handle_object);
276287
}
277-
args.GetReturnValue().Set(
278-
Array::New(env->isolate(), handle_v.data(), handle_v.size()));
288+
289+
args.GetReturnValue().Set(return_obj);
279290
}
280291

281292
static void ResourceUsage(const FunctionCallbackInfo<Value>& args) {

test/parallel/test-process-getactiverequests.js renamed to test/parallel/test-async-hooks-getactiveresources-requests.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
const common = require('../common');
44
const assert = require('assert');
55
const fs = require('fs');
6+
const { getActiveResources } = require('async_hooks');
67

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

10-
assert.strictEqual(process._getActiveRequests().length, 12);
11+
assert.strictEqual(12, Object.values(getActiveResources()).length);

test/parallel/test-process-getactivehandles.js renamed to test/parallel/test-async-hooks-getactiveresources.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
require('../common');
44
const assert = require('assert');
55
const net = require('net');
6+
const { getActiveResources } = require('async_hooks');
7+
68
const NUM = 8;
79
const connections = [];
810
const clients = [];
@@ -30,18 +32,18 @@ function clientConnected(client) {
3032

3133

3234
function checkAll() {
33-
const handles = process._getActiveHandles();
35+
const handles = Object.values(getActiveResources());
3436

3537
clients.forEach(function(item) {
36-
assert.ok(handles.includes(item));
38+
assert.ok(handles.includes(item._handle));
3739
item.destroy();
3840
});
3941

4042
connections.forEach(function(item) {
41-
assert.ok(handles.includes(item));
43+
assert.ok(handles.includes(item._handle));
4244
item.end();
4345
});
4446

45-
assert.ok(handles.includes(server));
47+
assert.ok(handles.includes(server._handle));
4648
server.close();
4749
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
const common = require('../common');
55
const strictEqual = require('assert').strictEqual;
66
const { internalBinding } = require('internal/test/binding');
7+
const { getActiveResources } = require('async_hooks');
78

89
// child_process
910
{
@@ -107,4 +108,25 @@ const { kStateSymbol } = require('internal/dgram');
107108
}
108109

109110

111+
// timers
112+
{
113+
const { Timer } = process.binding('timer_wrap');
114+
strictEqual(Object.values(getActiveResources()).filter(
115+
(handle) => (handle instanceof Timer)).length, 0);
116+
const timer = setTimeout(() => {}, 500);
117+
const handles = Object.values(getActiveResources()).filter(
118+
(handle) => (handle instanceof Timer));
119+
strictEqual(handles.length, 1);
120+
const handle = handles[0];
121+
strictEqual(Object.getPrototypeOf(handle).hasOwnProperty('hasRef'),
122+
true, 'timer_wrap: hasRef() missing');
123+
strictEqual(handle.hasRef(), true);
124+
timer.unref();
125+
strictEqual(handle.hasRef(),
126+
false, 'timer_wrap: unref() ineffective');
127+
timer.ref();
128+
strictEqual(handle.hasRef(),
129+
true, 'timer_wrap: ref() ineffective');
130+
}
131+
110132
// See also test/pseudo-tty/test-handle-wrap-isrefed-tty.js

test/pseudo-tty/ref_keeps_node_running.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require('../common');
66
const { internalBinding } = require('internal/test/binding');
77
const { TTY, isTTY } = internalBinding('tty_wrap');
88
const strictEqual = require('assert').strictEqual;
9+
const { getActiveResources } = require('async_hooks');
910

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

@@ -14,7 +15,8 @@ handle.readStart();
1415
handle.onread = () => {};
1516

1617
function isHandleActive(handle) {
17-
return process._getActiveHandles().some((active) => active === handle);
18+
return Object.values(getActiveResources())
19+
.some((active) => active === handle);
1820
}
1921

2022
strictEqual(isHandleActive(handle), true, 'TTY handle not initially active');

0 commit comments

Comments
 (0)