What is the problem this feature will solve?
Node.js' single-threaded Event Loop excels at asynchronous non-blocking I/O. However, synchronous CPU-bound operations (cryptography, AST/JSON parsing, image manipulation, template rendering, ML embeddings) freeze the main Event Loop, leading to latency spikes and request timeouts.
Currently, developers face several limitations when trying to offload work:
- Manual
new Worker() per task induces high memory allocation and V8 Isolate bootstrap latency (~20ms per task).
- Existing userland worker pools (
piscina, workerpool) treat worker threads strictly as disposable, stateless function runners, discarding in-memory worker heap state between tasks.
- In-process background tasks & side-effects (e.g. the Transactional Outbox pattern: sending an email or webhook after writing to the DB) have no standard core abstraction. Developers either do naive fire-and-forget on the main thread (risking Event Loop blocking during template compilation and unhandled promise crashes) or are forced to add external infrastructure like Redis + BullMQ / RabbitMQ just for simple in-process background jobs.
In our empirical benchmarks, executing CPU tasks synchronously on the main thread froze the Event Loop for 2,207ms, completely starving incoming I/O during computation.
What is the feature you are proposing to solve the problem?
We propose adding a built-in Persistent Worker Runtime to Node.js (either as node:worker_runtime or as an enhancement under node:worker_threads), operating on the core axiom:
"The Event Loop coordinates. Persistent Workers execute."
Core Capabilities:
- Dual Execution Model:
runtime.execute(task): Interactive request-response mode. Offloads computation and awaits the result with near-zero Event Loop lag (~6.5ms in benchmarks).
runtime.dispatch(task): Background dispatch mode for Transactional Outbox / fire-and-confirm patterns. Returns an unblocked TaskHandle immediately, allowing sub-millisecond HTTP responses while exposing .onComplete(cb) and .onError(cb) for asynchronous confirmation.
- Bounded Concurrency Batch Execution (
executeAll):
- Native
Promise.all semantics, but bounded strictly to the worker pool's hardware capacity (e.g., 4 workers processing 40 tasks in parallel without CPU thrashing).
- Stateful Workers with Persistent L1 Heap Memory:
- Dedicated workers can retain warm in-memory heaps (caches, loaded models, compiled WASM modules) across tasks with worker affinity. Benchmarks show a 33.2x latency speedup (1.58ms vs 52.37ms per query) compared to stateless reloading.
- Diagnostic & Backpressure Integration:
- Native
AsyncResource (node:async_hooks) propagation to preserve OpenTelemetry / APM distributed tracing context across threads.
- Non-blocking queue backpressure with timeout (
TaskQueueTimeoutError) to prevent process OOM.
- Supervisor with automatic crash detection and worker isolate replacement.
- Zero-Copy Memory Transfer:
- Full support for
transferList to transfer ArrayBuffer instances in <0.1ms without memory copying.
Reference Implementation:
We have developed a functional, production-grade reference implementation written in pure vanilla JavaScript (ESM, zero external runtime dependencies) with 100% passing tests on Node.js 22/24, 9 Architecture Decision Records (ADRs), and empirical benchmarks:
👉 https://github.com/FelipeMiiller/persistent-worker-runtime
What alternatives have you considered?
- Userland libraries (
piscina, workerpool):
While Piscina is excellent for stateless function execution, it lacks native support for persistent stateful workers with warm L1 memory affinity, in-process outbox dispatch with asynchronous event confirmation, and standard zero-dependency stdlib availability.
- External Queues (Redis + BullMQ / RabbitMQ / SQS):
Heavy operational and infrastructure overhead for applications that only require reliable in-process background job offloading.
libuv Threadpool (UV_THREADPOOL_SIZE):
Only handles internal asynchronous I/O and specific C++ crypto/zlib operations; cannot be used to run arbitrary userland JavaScript code.
- Child Processes (
child_process.fork):
Full OS process sandboxing incurs 30-50MB RAM per process and higher IPC serialization latency compared to lightweight V8 Isolates with zero-copy transferList.
What is the problem this feature will solve?
Node.js' single-threaded Event Loop excels at asynchronous non-blocking I/O. However, synchronous CPU-bound operations (cryptography, AST/JSON parsing, image manipulation, template rendering, ML embeddings) freeze the main Event Loop, leading to latency spikes and request timeouts.
Currently, developers face several limitations when trying to offload work:
new Worker()per task induces high memory allocation and V8 Isolate bootstrap latency (~20ms per task).piscina,workerpool) treat worker threads strictly as disposable, stateless function runners, discarding in-memory worker heap state between tasks.In our empirical benchmarks, executing CPU tasks synchronously on the main thread froze the Event Loop for 2,207ms, completely starving incoming I/O during computation.
What is the feature you are proposing to solve the problem?
We propose adding a built-in Persistent Worker Runtime to Node.js (either as
node:worker_runtimeor as an enhancement undernode:worker_threads), operating on the core axiom:Core Capabilities:
runtime.execute(task): Interactive request-response mode. Offloads computation and awaits the result with near-zero Event Loop lag (~6.5ms in benchmarks).runtime.dispatch(task): Background dispatch mode for Transactional Outbox / fire-and-confirm patterns. Returns an unblockedTaskHandleimmediately, allowing sub-millisecond HTTP responses while exposing.onComplete(cb)and.onError(cb)for asynchronous confirmation.executeAll):Promise.allsemantics, but bounded strictly to the worker pool's hardware capacity (e.g., 4 workers processing 40 tasks in parallel without CPU thrashing).AsyncResource(node:async_hooks) propagation to preserve OpenTelemetry / APM distributed tracing context across threads.TaskQueueTimeoutError) to prevent process OOM.transferListto transferArrayBufferinstances in <0.1ms without memory copying.Reference Implementation:
We have developed a functional, production-grade reference implementation written in pure vanilla JavaScript (ESM, zero external runtime dependencies) with 100% passing tests on Node.js 22/24, 9 Architecture Decision Records (ADRs), and empirical benchmarks:
👉 https://github.com/FelipeMiiller/persistent-worker-runtime
What alternatives have you considered?
piscina,workerpool):While Piscina is excellent for stateless function execution, it lacks native support for persistent stateful workers with warm L1 memory affinity, in-process outbox dispatch with asynchronous event confirmation, and standard zero-dependency stdlib availability.
Heavy operational and infrastructure overhead for applications that only require reliable in-process background job offloading.
libuvThreadpool (UV_THREADPOOL_SIZE):Only handles internal asynchronous I/O and specific C++ crypto/zlib operations; cannot be used to run arbitrary userland JavaScript code.
child_process.fork):Full OS process sandboxing incurs 30-50MB RAM per process and higher IPC serialization latency compared to lightweight V8 Isolates with zero-copy
transferList.