Skip to content

Commit a68394a

Browse files
committed
src: set UV_THREADPOOL_SIZE based on available parallelism
When UV_THREADPOOL_SIZE is not set, Node.js will auto-size it based on uv_available_parallelism(), with a minimum of 4 and a maximum of 1024.
1 parent fbf8276 commit a68394a

File tree

6 files changed

+80
-1
lines changed

6 files changed

+80
-1
lines changed

doc/api/cli.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4066,6 +4066,15 @@ Wed May 12 2021 20:30:48 GMT+0100 (Irish Standard Time)
40664066

40674067
### `UV_THREADPOOL_SIZE=size`
40684068

4069+
<!-- YAML
4070+
changes:
4071+
- version: REPLACEME
4072+
pr-url: https://github.com/nodejs/node/pull/61533
4073+
description: Node.js now automatically sets `UV_THREADPOOL_SIZE` to the
4074+
available CPU parallelism (with a minimum of 4 and a maximum
4075+
of 1024) when the environment variable is not already set.
4076+
-->
4077+
40694078
Set the number of threads used in libuv's threadpool to `size` threads.
40704079

40714080
Asynchronous system APIs are used by Node.js whenever possible, but where they

src/node.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,25 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
12201220
#endif // HAVE_OPENSSL
12211221
}
12221222

1223+
// Set UV_THREADPOOL_SIZE based on available parallelism if not already set
1224+
// by the user. The libuv threadpool defaults to 4 threads, which can be
1225+
// suboptimal on machines with many CPU cores. Use uv_available_parallelism()
1226+
// as a heuristic, with a minimum of 4 (the previous default) and a maximum
1227+
// of 1024 (libuv's upper bound).
1228+
{
1229+
char buf[64];
1230+
size_t buf_size = sizeof(buf);
1231+
int rc = uv_os_getenv("UV_THREADPOOL_SIZE", buf, &buf_size);
1232+
if (rc == UV_ENOENT &&
1233+
!per_process::dotenv_file.HasKey("UV_THREADPOOL_SIZE")) {
1234+
unsigned int parallelism = uv_available_parallelism();
1235+
unsigned int threadpool_size = std::min(std::max(4u, parallelism), 1024u);
1236+
char size_str[16];
1237+
snprintf(size_str, sizeof(size_str), "%u", threadpool_size);
1238+
uv_os_setenv("UV_THREADPOOL_SIZE", size_str);
1239+
}
1240+
}
1241+
12231242
if (!(flags & ProcessInitializationFlags::kNoInitializeNodeV8Platform)) {
12241243
uv_thread_setname("node-MainThread");
12251244
per_process::v8_platform.Initialize(

src/node_dotenv.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,10 @@ Dotenv::ParseResult Dotenv::ParsePath(const std::string_view path) {
352352
return ParseResult::Valid;
353353
}
354354

355+
bool Dotenv::HasKey(const std::string_view key) const {
356+
return store_.contains(std::string(key));
357+
}
358+
355359
void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) const {
356360
auto match = store_.find("NODE_OPTIONS");
357361

src/node_dotenv.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Dotenv {
2828
void ParseContent(const std::string_view content);
2929
ParseResult ParsePath(const std::string_view path);
3030
void AssignNodeOptionsIfAvailable(std::string* node_options) const;
31+
bool HasKey(const std::string_view key) const;
3132
v8::Maybe<void> SetEnvironment(Environment* env);
3233
v8::MaybeLocal<v8::Object> ToObject(Environment* env) const;
3334

test/node-api/test_uv_threadpool_size/node-options.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ const code = `
1919
assert.strictEqual(size, 4);
2020
test(size);
2121
`.trim();
22+
// Strip UV_THREADPOOL_SIZE from the inherited environment so that the
23+
// --env-file value is not shadowed by the dynamic default set by the parent.
24+
const env = { ...process.env };
25+
delete env.UV_THREADPOOL_SIZE;
2226
const child = spawnSync(
2327
process.execPath,
2428
[ `--env-file=${uvThreadPoolPath}`, '--eval', code ],
25-
{ cwd: __dirname, encoding: 'utf-8' },
29+
{ cwd: __dirname, encoding: 'utf-8', env },
2630
);
2731
assert.strictEqual(child.stderr, '');
2832
assert.strictEqual(child.status, 0);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
require('../common');
3+
4+
const { spawnSyncAndAssert } = require('../common/child_process');
5+
const assert = require('assert');
6+
const os = require('os');
7+
8+
const expectedSize = Math.min(Math.max(4, os.availableParallelism()), 1024);
9+
10+
// When UV_THREADPOOL_SIZE is not set, Node.js should auto-size it based on
11+
// uv_available_parallelism(), with a minimum of 4 and a maximum of 1024.
12+
{
13+
const env = { ...process.env };
14+
delete env.UV_THREADPOOL_SIZE;
15+
16+
spawnSyncAndAssert(
17+
process.execPath,
18+
['-e', 'console.log(process.env.UV_THREADPOOL_SIZE)'],
19+
{ env },
20+
{
21+
stdout(output) {
22+
assert.strictEqual(output.trim(), String(expectedSize));
23+
},
24+
},
25+
);
26+
}
27+
28+
// When UV_THREADPOOL_SIZE is explicitly set, Node.js should not override it.
29+
{
30+
const env = { ...process.env, UV_THREADPOOL_SIZE: '8' };
31+
32+
spawnSyncAndAssert(
33+
process.execPath,
34+
['-e', 'console.log(process.env.UV_THREADPOOL_SIZE)'],
35+
{ env },
36+
{
37+
stdout(output) {
38+
assert.strictEqual(output.trim(), '8');
39+
},
40+
},
41+
);
42+
}

0 commit comments

Comments
 (0)