diff --git a/doc/api/cli.md b/doc/api/cli.md index edaebed0cefaba..680fb41ab57036 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -4051,6 +4051,15 @@ Wed May 12 2021 20:30:48 GMT+0100 (Irish Standard Time) ### `UV_THREADPOOL_SIZE=size` + + 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 @@ -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 diff --git a/src/node.cc b/src/node.cc index 5f25a5229675e9..2235a7c1ee0fe6 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1233,6 +1233,24 @@ InitializeOncePerProcessInternal(const std::vector& 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); + 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( diff --git a/test/parallel/test-uv-threadpool-size-auto.js b/test/parallel/test-uv-threadpool-size-auto.js new file mode 100644 index 00000000000000..23c403789f17a7 --- /dev/null +++ b/test/parallel/test-uv-threadpool-size-auto.js @@ -0,0 +1,40 @@ +'use strict'; +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'); + }, + }, + ); +}