Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 20 additions & 4 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -4051,6 +4051,15 @@ Wed May 12 2021 20:30:48 GMT+0100 (Irish Standard Time)

### `UV_THREADPOOL_SIZE=size`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/61533
description: Node.js now automatically sets `UV_THREADPOOL_SIZE` to the
available CPU parallelism (with a minimum of 4 and a maximum
of 1024) when the environment variable is not already set.
-->

Set the number of threads used in libuv's threadpool to `size` threads.

Asynchronous system APIs are used by Node.js whenever possible, but where they
Expand All @@ -4064,15 +4073,22 @@ on synchronous system APIs. Node.js APIs that use the threadpool are:
* `dns.lookup()`
* all `zlib` APIs, other than those that are explicitly synchronous

If this environment variable is not set, Node.js automatically sizes the
threadpool based on the available CPU parallelism, using a minimum of `4`
threads and a maximum of `1024`. This ensures better default performance on
machines with many CPU cores, where the previous fixed default of `4` threads
could become a bottleneck.

Because libuv's threadpool has a fixed size, it means that if for whatever
reason any of these APIs takes a long time, other (seemingly unrelated) APIs
that run in libuv's threadpool will experience degraded performance. In order to
mitigate this issue, one potential solution is to increase the size of libuv's
threadpool by setting the `'UV_THREADPOOL_SIZE'` environment variable to a value
greater than `4` (its current default value). However, setting this from inside
the process using `process.env.UV_THREADPOOL_SIZE=size` is not guranteed to work
as the threadpool would have been created as part of the runtime initialisation
much before user code is run. For more information, see the [libuv threadpool documentation][].
greater than the automatically computed default. However, setting this from
inside the process using `process.env.UV_THREADPOOL_SIZE=size` is not guaranteed
to work as the threadpool would have been created as part of the runtime
initialisation much before user code is run. For more information, see the
[libuv threadpool documentation][].

## Useful V8 options

Expand Down
18 changes: 18 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,24 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
#endif // HAVE_OPENSSL
}

// Set UV_THREADPOOL_SIZE based on available parallelism if not already set
// by the user. The libuv threadpool defaults to 4 threads, which can be
// suboptimal on machines with many CPU cores. Use uv_available_parallelism()
// as a heuristic, with a minimum of 4 (the previous default) and a maximum
// of 1024 (libuv's upper bound).
{
char buf[64];
size_t buf_size = sizeof(buf);
int rc = uv_os_getenv("UV_THREADPOOL_SIZE", buf, &buf_size);
if (rc == UV_ENOENT) {
unsigned int parallelism = uv_available_parallelism();
unsigned int threadpool_size = std::min(std::max(4u, parallelism), 1024u);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth just deferring the maximum to libuv here, since it already caps to MAX_THREADPOOL_SIZE, rather than hardcoding it a second time?

char size_str[16];
snprintf(size_str, sizeof(size_str), "%u", threadpool_size);
uv_os_setenv("UV_THREADPOOL_SIZE", size_str);
}
}

if (!(flags & ProcessInitializationFlags::kNoInitializeNodeV8Platform)) {
uv_thread_setname("node-MainThread");
per_process::v8_platform.Initialize(
Expand Down
40 changes: 40 additions & 0 deletions test/parallel/test-uv-threadpool-size-auto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

Check failure on line 1 in test/parallel/test-uv-threadpool-size-auto.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Mandatory module "common" must be loaded before any other modules

Check failure on line 1 in test/parallel/test-uv-threadpool-size-auto.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Mandatory module "common" must be loaded
const { spawnSyncAndAssert } = require('../common/child_process');
const assert = require('assert');
const os = require('os');

const expectedSize = Math.min(Math.max(4, os.availableParallelism()), 1024);

// When UV_THREADPOOL_SIZE is not set, Node.js should auto-size it based on
// uv_available_parallelism(), with a minimum of 4 and a maximum of 1024.
{
const env = { ...process.env };
delete env.UV_THREADPOOL_SIZE;

spawnSyncAndAssert(
process.execPath,
['-e', 'console.log(process.env.UV_THREADPOOL_SIZE)'],
{ env },
{
stdout(output) {
assert.strictEqual(output.trim(), String(expectedSize));
},
},
);
}

// When UV_THREADPOOL_SIZE is explicitly set, Node.js should not override it.
{
const env = { ...process.env, UV_THREADPOOL_SIZE: '8' };

spawnSyncAndAssert(
process.execPath,
['-e', 'console.log(process.env.UV_THREADPOOL_SIZE)'],
{ env },
{
stdout(output) {
assert.strictEqual(output.trim(), '8');
},
},
);
}
Loading