Skip to content

Commit 9af6264

Browse files
Flarnacodebytere
authored andcommitted
async_hooks: execute destroy hooks earlier
Use a microtask to call destroy hooks in case there are a lot queued as immediate may be scheduled late in case of long running promise chains. Queuing a mircrotasks in GC context is not allowed therefore an interrupt is triggered to do this in JS context as fast as possible. fixes: #34328 refs: #33896 PR-URL: #34342 Fixes: #34328 Refs: #33896 Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 0a9389b commit 9af6264

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

src/async_wrap.cc

+12
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,18 @@ void AsyncWrap::EmitDestroy(Environment* env, double async_id) {
836836
env->SetImmediate(&DestroyAsyncIdsCallback, CallbackFlags::kUnrefed);
837837
}
838838

839+
// If the list gets very large empty it faster using a Microtask.
840+
// Microtasks can't be added in GC context therefore we use an
841+
// interrupt to get this Microtask scheduled as fast as possible.
842+
if (env->destroy_async_id_list()->size() == 16384) {
843+
env->RequestInterrupt([](Environment* env) {
844+
env->isolate()->EnqueueMicrotask(
845+
[](void* arg) {
846+
DestroyAsyncIdsCallback(static_cast<Environment*>(arg));
847+
}, env);
848+
});
849+
}
850+
839851
env->destroy_async_id_list()->push_back(async_id);
840852
}
841853

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use strict';
2+
// Flags: --expose_gc
3+
4+
const common = require('../common');
5+
const assert = require('assert');
6+
const tick = require('../common/tick');
7+
8+
const { createHook, AsyncResource } = require('async_hooks');
9+
10+
// Test priority of destroy hook relative to nextTick,... and
11+
// verify a microtask is scheduled in case a lot items are queued
12+
13+
const resType = 'MyResource';
14+
let activeId = -1;
15+
createHook({
16+
init(id, type) {
17+
if (type === resType) {
18+
assert.strictEqual(activeId, -1);
19+
activeId = id;
20+
}
21+
},
22+
destroy(id) {
23+
if (activeId === id) {
24+
activeId = -1;
25+
}
26+
}
27+
}).enable();
28+
29+
function testNextTick() {
30+
assert.strictEqual(activeId, -1);
31+
const res = new AsyncResource(resType);
32+
assert.strictEqual(activeId, res.asyncId());
33+
res.emitDestroy();
34+
// nextTick has higher prio than emit destroy
35+
process.nextTick(common.mustCall(() =>
36+
assert.strictEqual(activeId, res.asyncId()))
37+
);
38+
}
39+
40+
function testQueueMicrotask() {
41+
assert.strictEqual(activeId, -1);
42+
const res = new AsyncResource(resType);
43+
assert.strictEqual(activeId, res.asyncId());
44+
res.emitDestroy();
45+
// queueMicrotask has higher prio than emit destroy
46+
queueMicrotask(common.mustCall(() =>
47+
assert.strictEqual(activeId, res.asyncId()))
48+
);
49+
}
50+
51+
function testImmediate() {
52+
assert.strictEqual(activeId, -1);
53+
const res = new AsyncResource(resType);
54+
assert.strictEqual(activeId, res.asyncId());
55+
res.emitDestroy();
56+
setImmediate(common.mustCall(() =>
57+
assert.strictEqual(activeId, -1))
58+
);
59+
}
60+
61+
function testPromise() {
62+
assert.strictEqual(activeId, -1);
63+
const res = new AsyncResource(resType);
64+
assert.strictEqual(activeId, res.asyncId());
65+
res.emitDestroy();
66+
// Promise has higher prio than emit destroy
67+
Promise.resolve().then(common.mustCall(() =>
68+
assert.strictEqual(activeId, res.asyncId()))
69+
);
70+
}
71+
72+
async function testAwait() {
73+
assert.strictEqual(activeId, -1);
74+
const res = new AsyncResource(resType);
75+
assert.strictEqual(activeId, res.asyncId());
76+
res.emitDestroy();
77+
78+
for (let i = 0; i < 5000; i++) {
79+
await Promise.resolve();
80+
}
81+
global.gc();
82+
await Promise.resolve();
83+
// Limit to trigger a microtask not yet reached
84+
assert.strictEqual(activeId, res.asyncId());
85+
for (let i = 0; i < 5000; i++) {
86+
await Promise.resolve();
87+
}
88+
global.gc();
89+
await Promise.resolve();
90+
assert.strictEqual(activeId, -1);
91+
}
92+
93+
testNextTick();
94+
tick(2, testQueueMicrotask);
95+
tick(4, testImmediate);
96+
tick(6, testPromise);
97+
tick(8, () => testAwait().then(common.mustCall()));

0 commit comments

Comments
 (0)