Skip to content

Fix promise transfer settlement stalling on node 26.4+ - #565

Merged
laverdet merged 1 commit into
laverdet:mainfrom
maksimyaromin:fix/transferable-promise-microtask-checkpoint
Aug 1, 2026
Merged

Fix promise transfer settlement stalling on node 26.4+#565
laverdet merged 1 commit into
laverdet:mainfrom
maksimyaromin:fix/transferable-promise-microtask-checkpoint

Conversation

@maksimyaromin

Copy link
Copy Markdown
Contributor

Problem

When a promise crosses isolates with { promise: true }, the other side receives its own promise which is settled later by TransferablePromiseHolder::ResolveTask. In the nodejs isolate, microtasks run under the explicit policy, so the settlement only queues the await continuations. No checkpoint runs afterwards, and they sit in the queue until some other JS callback triggers one. In a process that's just holding a server or an IPC channel, that's never.

Why node 26.4

This stayed invisible because node used to run a checkpoint on every loop iteration, as a side effect of draining native immediates even when there was nothing to drain. nodejs/node#62969 (26.4.0) skips the empty drain, and the accidental checkpoint went with it.

Repro

// main.mjs, hangs on node >= 26.4
import ivm from 'isolated-vm';
import net from 'node:net';

net.createServer().listen(0); // just something to keep the process alive

const isolate = new ivm.Isolate();
const context = await isolate.createContext();
await context.evalClosure(
	'sleep = ms => $0.applySync(undefined, [ ms ], { result: { promise: true } })',
	[ ms => new Promise(resolve => setTimeout(resolve, ms)) ],
	{ arguments: { reference: true } });

await context.eval('sleep(5)', { promise: true });
console.log('settled');
process.exit();

Fix

The fix runs the checkpoint in ResolveTask::Run once the promise settles. Also adds a regression test, which fails on 26.4+ without the fix.

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.
@laverdet
laverdet merged commit 56ae6d7 into laverdet:main Aug 1, 2026
8 checks passed
skvelymake pushed a commit to integromat/isolated-vm that referenced this pull request Aug 5, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants