Skip to content

Commit 56ae6d7

Browse files
Fix promise transfer settlement stalling on node 26.4+ (#565)
TransferablePromiseHolder::ResolveTask settles the receiving side's promise without running a microtask checkpoint, so under the nodejs isolate's explicit microtasks policy the awaiting continuations stay queued until unrelated JS activity runs one. Node used to mask this with an accidental per-iteration checkpoint; nodejs/node#62969 (26.4.0) removed it.
1 parent 38e244b commit 56ae6d7

2 files changed

Lines changed: 23 additions & 0 deletions

File tree

src/module/transferable.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ class TransferablePromiseHolder final : public ClassHandle {
124124
} else {
125125
Unmaybe(resolver->Resolve(context, value->TransferIn()));
126126
}
127+
// Under the nodejs isolate's explicit microtasks policy the continuations
128+
// queued above would wait for an unrelated callback to run a checkpoint.
129+
Isolate::GetCurrent()->PerformMicrotaskCheckpoint();
127130
}
128131

129132
RemoteTuple<Promise::Resolver, v8::Context> resolver;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const ivm = require('isolated-vm');
2+
3+
(async () => {
4+
const isolate = new ivm.Isolate;
5+
const context = await isolate.createContext();
6+
await context.evalClosure(
7+
'sleep = ms => $0.applySync(undefined, [ ms ], { result: { promise: true } })',
8+
[ ms => new Promise(resolve => setTimeout(resolve, ms)) ],
9+
{ arguments: { reference: true } },
10+
);
11+
// The transferred promise must settle on its own, without waiting for
12+
// another callback to wake the microtask queue.
13+
setTimeout(() => {
14+
console.log('fail');
15+
process.exit();
16+
}, 10000);
17+
await context.eval('sleep(5)', { promise: true });
18+
console.log('pass');
19+
process.exit();
20+
})();

0 commit comments

Comments
 (0)