Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
416e7e1
src: add NewIsolate() overload taking a platform delegate
jasnell Nov 5, 2026
a66a5ad
green_threads: add experimental node:green_threads module
jasnell Nov 5, 2026
f81d192
green_threads: schedule isolates on any pool thread
jasnell Nov 5, 2026
b0df385
green_threads: fix teardown, timer and task lifetime issues
jasnell Nov 5, 2026
4bd9c66
green_threads: add CPU and memory limits and a warm pool
jasnell Nov 5, 2026
7b2bd5c
benchmark: add green_threads benchmarks
jasnell Nov 5, 2026
ba73dbf
green_threads: resolve setWarmPoolSize() once the pool is full
jasnell Nov 5, 2026
8d51d3a
green_threads: fix ArrayBuffer memory limit before the first GC
jasnell Nov 5, 2026
bacc9d3
green_threads: add isolate groups
jasnell Nov 5, 2026
a9f4942
green_threads: stop green threads that ignore termination
jasnell Nov 5, 2026
7991936
green_threads: add setUnresponsiveTimeouts()
jasnell Nov 5, 2026
3ab2a5a
src: fix build with the V8 sandbox enabled
jasnell Nov 5, 2026
c9ba622
green_threads: initialize warm isolates in the background
jasnell Nov 5, 2026
ea04442
green_threads: add EventTarget and AbortController
jasnell Nov 5, 2026
4fe1761
green_threads: add RPC
jasnell Nov 5, 2026
40d0917
green_threads: transfer ArrayBuffers and share SharedArrayBuffers
jasnell Nov 5, 2026
3a0468c
worker: allow MessagePortData owners other than MessagePorts
jasnell Nov 5, 2026
f374015
green_threads: add MessageChannel and MessagePort
jasnell Nov 5, 2026
3bf1991
green_threads: add ES modules and exports
jasnell Nov 6, 2026
1100e62
green_threads: keep the properties of errors
jasnell Nov 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions benchmark/green_threads/roundtrip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';
const common = require('../common.js');
const { once } = require('events');

const bench = common.createBenchmark(main, {
kind: ['green-thread', 'worker'],
concurrency: [1, 16],
n: [10000],
}, {
flags: ['--experimental-green-threads', '--no-warnings'],
});

// Sends `n` messages in total, spread over `concurrency` threads, each of
// which echoes every message back before it gets the next one.
async function main({ kind, concurrency, n }) {
let threads;
if (kind === 'green-thread') {
const { GreenThread } = require('node:green_threads');
threads = Array.from({ length: concurrency }, () => new GreenThread(
'onmessage = ({ data }) => postMessage(data);'));
} else {
const { Worker } = require('worker_threads');
threads = Array.from({ length: concurrency }, () => new Worker(`
const { parentPort } = require('worker_threads');
parentPort.on('message', (data) => parentPort.postMessage(data));
`, { eval: true }));
await Promise.all(threads.map((w) => once(w, 'online')));
}
const perThread = Math.ceil(n / concurrency);
bench.start();
await Promise.all(threads.map(async (thread) => {
for (let i = 0; i < perThread; i++) {
const reply = once(thread, 'message');
thread.postMessage(i);
await reply;
}
}));
bench.end(perThread * concurrency);
await Promise.all(threads.map((thread) => thread.terminate()));
}
52 changes: 52 additions & 0 deletions benchmark/green_threads/rpc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';
const common = require('../common.js');

const bench = common.createBenchmark(main, {
direction: ['green-to-host', 'host-to-green'],
concurrency: [1, 16],
n: [10000],
}, {
flags: ['--experimental-green-threads', '--no-warnings'],
});

// Makes `n` RPC calls in total, spread over `concurrency` green threads, each
// of which makes or receives one call at a time.
async function main({ direction, concurrency, n }) {
const { GreenThread } = require('node:green_threads');
const perThread = Math.ceil(n / concurrency);
let started;
const ready = new Promise((resolve) => { started = resolve; });
const echoes = [];
const greenThreads = Array.from({ length: concurrency }, () => {
return new GreenThread(direction === 'green-to-host' ? `
onmessage = async ({ data: n }) => {
for (let i = 0; i < n; i++) await env.echo(i);
postMessage(0);
};
` : 'env.register((value) => value);', {
env: {
echo: (value) => value,
register(echo) {
echoes.push(echo);
if (echoes.length === concurrency) started();
},
},
});
});
if (direction === 'host-to-green') await ready;
bench.start();
await Promise.all(greenThreads.map(async (greenThread, index) => {
if (direction === 'green-to-host') {
const done = new Promise((resolve) => {
greenThread.once('message', resolve);
});
greenThread.postMessage(perThread);
await done;
} else {
const echo = echoes[index];
for (let i = 0; i < perThread; i++) await echo(i);
}
}));
bench.end(perThread * concurrency);
await Promise.all(greenThreads.map((greenThread) => greenThread.terminate()));
}
49 changes: 49 additions & 0 deletions benchmark/green_threads/startup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';
const common = require('../common.js');
const { once } = require('events');

const bench = common.createBenchmark(main, {
kind: ['green-thread', 'green-thread-warm', 'worker', 'vm-context'],
n: [50],
}, {
flags: ['--experimental-green-threads', '--no-warnings'],
});

// Starts `n` of `kind` at once, and measures until each has sent a message.
async function main({ kind, n }) {
switch (kind) {
case 'green-thread':
case 'green-thread-warm': {
const { GreenThread, setWarmPoolSize } = require('node:green_threads');
if (kind === 'green-thread-warm') await setWarmPoolSize(n);
bench.start();
const greenThreads = Array.from(
{ length: n }, () => new GreenThread('postMessage(0);'));
await Promise.all(greenThreads.map((g) => once(g, 'message')));
bench.end(n);
await setWarmPoolSize(0);
await Promise.all(greenThreads.map((g) => g.terminate()));
break;
}
case 'worker': {
const { Worker } = require('worker_threads');
bench.start();
const workers = Array.from({ length: n }, () => new Worker(
"require('worker_threads').parentPort.postMessage(0);",
{ eval: true }));
await Promise.all(workers.map((w) => once(w, 'message')));
bench.end(n);
await Promise.all(workers.map((w) => w.terminate()));
break;
}
case 'vm-context': {
const vm = require('vm');
bench.start();
for (let i = 0; i < n; i++) {
vm.runInContext('0', vm.createContext());
}
bench.end(n);
break;
}
}
}
12 changes: 12 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,16 @@ added:

Enable exposition of [EventSource Web API][] on the global scope.

### `--experimental-green-threads`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

Enable the experimental [`node:green_threads`][] module.

### `--experimental-import-meta-resolve`

<!-- YAML
Expand Down Expand Up @@ -4309,6 +4319,7 @@ one is included in the list below.
* `--experimental-detect-module`
* `--experimental-dtls`
* `--experimental-eventsource`
* `--experimental-green-threads`
* `--experimental-import-meta-resolve`
* `--experimental-import-text`
* `--experimental-json-modules`
Expand Down Expand Up @@ -5000,6 +5011,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`inspector.open()`]: inspector.md#inspectoropenport-host-wait
[`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: net.md#netgetdefaultautoselectfamilyattempttimeout
[`node:ffi`]: ffi.md
[`node:green_threads`]: green_threads.md
[`node:sqlite`]: sqlite.md
[`node:stream/iter`]: stream_iter.md
[`node:vfs`]: vfs.md
Expand Down
53 changes: 53 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,56 @@ file in chunks.
The number of file system events queued without being handled exceeded the size specified in
`maxQueue` in `fs.watch()`.

<a id="ERR_GREEN_THREAD_CPU_LIMIT"></a>

### `ERR_GREEN_THREAD_CPU_LIMIT`

A [green thread][] was terminated because it reached one of its CPU time
limits.

<a id="ERR_GREEN_THREAD_EXITED"></a>

### `ERR_GREEN_THREAD_EXITED`

An operation was attempted on a [green thread][] that has exited, or a pending
[`greenThread.evaluate()`][] call was interrupted because the green thread
exited.

<a id="ERR_GREEN_THREAD_ISOLATE_GROUPS_UNSUPPORTED"></a>

### `ERR_GREEN_THREAD_ISOLATE_GROUPS_UNSUPPORTED`

An [`IsolateGroup`][] was created, or a [green thread][] was asked to use a new
isolate group, but this build of Node.js does not support isolate groups.
They require building Node.js with `--experimental-enable-pointer-compression`.

<a id="ERR_GREEN_THREAD_OUT_OF_MEMORY"></a>

### `ERR_GREEN_THREAD_OUT_OF_MEMORY`

A [green thread][] was terminated because it reached its heap limit.

<a id="ERR_GREEN_THREAD_RPC_DISPOSED"></a>

### `ERR_GREEN_THREAD_RPC_DISPOSED`

A method of an RPC stub of a [green thread][] was called after the stub was
disposed.

<a id="ERR_GREEN_THREAD_RPC_NO_SUCH_METHOD"></a>

### `ERR_GREEN_THREAD_RPC_NO_SUCH_METHOD`

An RPC call between a [green thread][] and its host was made for a method that
the target doesn't have.

<a id="ERR_GREEN_THREAD_RPC_UNKNOWN_EXPORT"></a>

### `ERR_GREEN_THREAD_RPC_UNKNOWN_EXPORT`

An RPC call between a [green thread][] and its host was made to a target that
was already released.

<a id="ERR_HTTP2_ALTSVC_INVALID_ORIGIN"></a>

### `ERR_HTTP2_ALTSVC_INVALID_ORIGIN`
Expand Down Expand Up @@ -4798,6 +4848,7 @@ An error occurred trying to allocate memory. This should never happen.
[`ERR_REQUIRE_ASYNC_MODULE`]: #err_require_async_module
[`Error.isError`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/isError
[`EventEmitter`]: events.md#class-eventemitter
[`IsolateGroup`]: green_threads.md#class-isolategroup
[`MessagePort`]: worker_threads.md#class-messageport
[`Object.getPrototypeOf`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf
[`Object.setPrototypeOf`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
Expand Down Expand Up @@ -4833,6 +4884,7 @@ An error occurred trying to allocate memory. This should never happen.
[`fs.symlinkSync()`]: fs.md#fssymlinksynctarget-path-type
[`fs.unlink`]: fs.md#fsunlinkpath-callback
[`fs`]: fs.md
[`greenThread.evaluate()`]: green_threads.md#greenthreadevaluatesource-options
[`hash.digest()`]: crypto.md#hashdigestencoding
[`hash.update()`]: crypto.md#hashupdatedata-inputencoding
[`http`]: http.md
Expand Down Expand Up @@ -4875,6 +4927,7 @@ An error occurred trying to allocate memory. This should never happen.
[domains]: domain.md
[event emitter-based]: events.md#class-eventemitter
[file descriptors]: https://en.wikipedia.org/wiki/File_descriptor
[green thread]: green_threads.md
[package map]: packages.md#package-maps
[relative URL]: https://url.spec.whatwg.org/#relative-url-string
[self-reference a package using its name]: packages.md#self-referencing-a-package-using-its-name
Expand Down
Loading
Loading