diff --git a/lib/async_hooks.js b/lib/async_hooks.js index 345b003e54997b..aef5a8b67d2cc6 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -6,6 +6,7 @@ const { ERR_INVALID_ASYNC_ID } = require('internal/errors').codes; const internal_async_hooks = require('internal/async_hooks'); +const internalTimers = require('internal/timers'); // Get functions // For userland AsyncResources, make sure to emit a destroy event when the @@ -123,6 +124,35 @@ function createHook(fns) { } +function getActiveResources() { + const handles = process._getActiveHandles(); + const reqs = process._getActiveRequests(); + + const timers = {}; + for (const list of Object.values(internalTimers.timerLists)) { + var timer = list._idlePrev === list ? null : list._idlePrev; + + while (timer !== null) { + timers[timer[internalTimers.async_id_symbol]] = timer; + + timer = timer._idlePrev === list ? null : list._idlePrev; + } + } + + const immediates = {}; + const queue = internalTimers.outstandingQueue.head !== null ? + internalTimers.outstandingQueue : internalTimers.immediateQueue; + var immediate = queue.head; + while (immediate !== null) { + immediates[immediate[internalTimers.async_id_symbol]] = immediate; + + immediate = immediate._idleNext; + } + + return Object.assign({}, handles, reqs, timers, immediates); +} + + // Embedder API // const destroyedSymbol = Symbol('destroyed'); @@ -216,6 +246,7 @@ module.exports = { createHook, executionAsyncId, triggerAsyncId, + getActiveResources, // Embedder API AsyncResource, }; diff --git a/lib/internal/timers.js b/lib/internal/timers.js index 44ebc65dc1d4aa..0ce0ec6cb34248 100644 --- a/lib/internal/timers.js +++ b/lib/internal/timers.js @@ -21,6 +21,59 @@ const TIMEOUT_MAX = 2 ** 31 - 1; const kRefed = Symbol('refed'); +// Object map containing linked lists of timers, keyed and sorted by their +// duration in milliseconds. +// +// - key = time in milliseconds +// - value = linked list +const timerLists = Object.create(null); + +// A linked list for storing `setImmediate()` requests +function ImmediateList() { + this.head = null; + this.tail = null; +} + +// Appends an item to the end of the linked list, adjusting the current tail's +// previous and next pointers where applicable +ImmediateList.prototype.append = function(item) { + if (this.tail !== null) { + this.tail._idleNext = item; + item._idlePrev = this.tail; + } else { + this.head = item; + } + this.tail = item; +}; + +// Removes an item from the linked list, adjusting the pointers of adjacent +// items and the linked list's head or tail pointers as necessary +ImmediateList.prototype.remove = function(item) { + if (item._idleNext !== null) { + item._idleNext._idlePrev = item._idlePrev; + } + + if (item._idlePrev !== null) { + item._idlePrev._idleNext = item._idleNext; + } + + if (item === this.head) + this.head = item._idleNext; + if (item === this.tail) + this.tail = item._idlePrev; + + item._idleNext = null; + item._idlePrev = null; +}; + +// 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(); + module.exports = { TIMEOUT_MAX, kTimeout: Symbol('timeout'), // For hiding Timeouts on other internals. @@ -30,7 +83,10 @@ module.exports = { kRefed, initAsyncResource, setUnrefTimeout, - validateTimerDuration + validateTimerDuration, + timerLists, + immediateQueue, + outstandingQueue }; var timers; diff --git a/lib/timers.js b/lib/timers.js index 5ae0e6a5ad4fd4..81ff13eb95e330 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -33,7 +33,10 @@ const { Timeout, kRefed, initAsyncResource, - validateTimerDuration + validateTimerDuration, + timerLists: lists, + immediateQueue, + outstandingQueue } = require('internal/timers'); const internalUtil = require('internal/util'); const { createPromise, promiseResolve } = process.binding('util'); @@ -128,14 +131,6 @@ const [immediateInfo, toggleImmediateRef] = // timers within (or creation of a new list). However, these operations combined // have shown to be trivial in comparison to other timers architectures. - -// Object map containing linked lists of timers, keyed and sorted by their -// duration in milliseconds. -// -// - key = time in milliseconds -// - value = linked list -const lists = Object.create(null); - // This is a priority queue with a custom sorting function that first compares // the expiry times of two lists and if they're the same then compares their // individual IDs to determine which list was created first. @@ -557,53 +552,6 @@ Timeout.prototype.close = function() { }; -// A linked list for storing `setImmediate()` requests -function ImmediateList() { - this.head = null; - this.tail = null; -} - -// Appends an item to the end of the linked list, adjusting the current tail's -// previous and next pointers where applicable -ImmediateList.prototype.append = function(item) { - if (this.tail !== null) { - this.tail._idleNext = item; - item._idlePrev = this.tail; - } else { - this.head = item; - } - this.tail = item; -}; - -// Removes an item from the linked list, adjusting the pointers of adjacent -// items and the linked list's head or tail pointers as necessary -ImmediateList.prototype.remove = function(item) { - if (item._idleNext !== null) { - item._idleNext._idlePrev = item._idlePrev; - } - - if (item._idlePrev !== null) { - item._idlePrev._idleNext = item._idleNext; - } - - if (item === this.head) - this.head = item._idleNext; - if (item === this.tail) - this.tail = item._idlePrev; - - item._idleNext = null; - item._idlePrev = null; -}; - -// 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(); - - function processImmediate() { const queue = outstandingQueue.head !== null ? outstandingQueue : immediateQueue; diff --git a/src/node.cc b/src/node.cc index 292307e9dae788..ac37b92a2b27b1 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1016,27 +1016,19 @@ static MaybeLocal ExecuteString(Environment* env, static void GetActiveRequests(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local ary = Array::New(args.GetIsolate()); Local ctx = env->context(); - Local fn = env->push_values_to_array_function(); - Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; - size_t idx = 0; + Local return_obj = Object::New(args.GetIsolate()); for (auto w : *env->req_wrap_queue()) { if (w->persistent().IsEmpty()) continue; - argv[idx] = w->object(); - if (++idx >= arraysize(argv)) { - fn->Call(ctx, ary, idx, argv).ToLocalChecked(); - idx = 0; - } - } + double async_id = w->get_async_id(); + Local req_obj = w->object(); - if (idx > 0) { - fn->Call(ctx, ary, idx, argv).ToLocalChecked(); + return_obj->Set(ctx, Number::New(args.GetIsolate(), async_id), req_obj); } - args.GetReturnValue().Set(ary); + args.GetReturnValue().Set(return_obj); } @@ -1045,32 +1037,22 @@ static void GetActiveRequests(const FunctionCallbackInfo& args) { void GetActiveHandles(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local ary = Array::New(env->isolate()); Local ctx = env->context(); - Local fn = env->push_values_to_array_function(); - Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; - size_t idx = 0; + Local return_obj = Object::New(args.GetIsolate()); Local owner_sym = env->owner_string(); for (auto w : *env->handle_wrap_queue()) { if (w->persistent().IsEmpty() || !HandleWrap::HasRef(w)) continue; - Local object = w->object(); - Local owner = object->Get(owner_sym); - if (owner->IsUndefined()) - owner = object; - argv[idx] = owner; - if (++idx >= arraysize(argv)) { - fn->Call(ctx, ary, idx, argv).ToLocalChecked(); - idx = 0; - } - } - if (idx > 0) { - fn->Call(ctx, ary, idx, argv).ToLocalChecked(); + double async_id = w->get_async_id(); + Local handle_object = w->object(); + return_obj->Set(ctx, Number::New(args.GetIsolate(), + async_id), + handle_object); } - args.GetReturnValue().Set(ary); + args.GetReturnValue().Set(return_obj); } diff --git a/test/parallel/test-process-getactiverequests.js b/test/parallel/test-async-hooks-getactiveresources-requests.js similarity index 57% rename from test/parallel/test-process-getactiverequests.js rename to test/parallel/test-async-hooks-getactiveresources-requests.js index f55f298298d446..d799e6eff002d1 100644 --- a/test/parallel/test-process-getactiverequests.js +++ b/test/parallel/test-async-hooks-getactiveresources-requests.js @@ -3,8 +3,9 @@ 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', () => {}); -assert.strictEqual(12, process._getActiveRequests().length); +assert.strictEqual(12, Object.values(getActiveResources()).length); diff --git a/test/parallel/test-process-getactivehandles.js b/test/parallel/test-async-hooks-getactiveresources.js similarity index 75% rename from test/parallel/test-process-getactivehandles.js rename to test/parallel/test-async-hooks-getactiveresources.js index 2db3da3c563e6e..1787b1c3a7e4d5 100644 --- a/test/parallel/test-process-getactivehandles.js +++ b/test/parallel/test-async-hooks-getactiveresources.js @@ -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 = []; @@ -30,18 +32,18 @@ function clientConnected(client) { function checkAll() { - const handles = process._getActiveHandles(); + const handles = Object.values(getActiveResources()); clients.forEach(function(item) { - assert.ok(handles.includes(item)); + assert.ok(handles.includes(item._handle)); item.destroy(); }); connections.forEach(function(item) { - assert.ok(handles.includes(item)); + assert.ok(handles.includes(item._handle)); item.end(); }); - assert.ok(handles.includes(server)); + assert.ok(handles.includes(server._handle)); server.close(); } diff --git a/test/parallel/test-handle-wrap-isrefed.js b/test/parallel/test-handle-wrap-isrefed.js index 0b6afe83df2115..62fefe2bdc6420 100644 --- a/test/parallel/test-handle-wrap-isrefed.js +++ b/test/parallel/test-handle-wrap-isrefed.js @@ -2,6 +2,7 @@ const common = require('../common'); const strictEqual = require('assert').strictEqual; +const { getActiveResources } = require('async_hooks'); // child_process { @@ -113,10 +114,10 @@ const dgram = require('dgram'); // timers { const { Timer } = process.binding('timer_wrap'); - strictEqual(process._getActiveHandles().filter( + strictEqual(Object.values(getActiveResources()).filter( (handle) => (handle instanceof Timer)).length, 0); const timer = setTimeout(() => {}, 500); - const handles = process._getActiveHandles().filter( + const handles = Object.values(getActiveResources()).filter( (handle) => (handle instanceof Timer)); strictEqual(handles.length, 1); const handle = handles[0]; diff --git a/test/pseudo-tty/ref_keeps_node_running.js b/test/pseudo-tty/ref_keeps_node_running.js index 7832e9ee21ba26..f78dbcb7f3f309 100644 --- a/test/pseudo-tty/ref_keeps_node_running.js +++ b/test/pseudo-tty/ref_keeps_node_running.js @@ -4,6 +4,7 @@ require('../common'); const { TTY, isTTY } = process.binding('tty_wrap'); const strictEqual = require('assert').strictEqual; +const { getActiveResources } = require('async_hooks'); strictEqual(isTTY(0), true, 'fd 0 is not a TTY'); @@ -12,7 +13,8 @@ handle.readStart(); handle.onread = () => {}; function isHandleActive(handle) { - return process._getActiveHandles().some((active) => active === handle); + return Object.values(getActiveResources()) + .some((active) => active === handle); } strictEqual(isHandleActive(handle), true, 'TTY handle not initially active');