Skip to content

Commit 1d531f4

Browse files
committed
async_hooks: execute destroy hooks faster
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: nodejs#34328 refs: nodejs#33896
1 parent dc00a07 commit 1d531f4

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

src/async_wrap.cc

+11
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,17 @@ void AsyncWrap::EmitDestroy(Environment* env, double async_id) {
856856
env->SetImmediate(&DestroyAsyncIdsCallback, CallbackFlags::kUnrefed);
857857
}
858858

859+
// If the list gets very large empty it faster using a Microtask.
860+
// Microtasks can't be added in GC context therefore we use an
861+
// interrupt to get this Microtask scheduled as fast as possible.
862+
if (env->destroy_async_id_list()->size() == 16384) {
863+
env->RequestInterrupt([](Environment* env) {
864+
env->isolate()->EnqueueMicrotask(
865+
reinterpret_cast<v8::MicrotaskCallback>(DestroyAsyncIdsCallback),
866+
env);
867+
});
868+
}
869+
859870
env->destroy_async_id_list()->push_back(async_id);
860871
}
861872

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)