From d270a8e7ed99457f2efe55a2345753ff89d39945 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sat, 10 Aug 2024 00:34:06 +0200 Subject: [PATCH 1/6] src: add JS APIs for compile cache and NODE_DISABLE_COMPILE_CACHE This patch adds the following API for tools to enable compile cache dynamically and query its status. - module.enableCompileCache(cacheDir) - module.getCompileCacheDir() In addition this adds a NODE_DISABLE_COMPILE_CACHE environment variable to disable the code cache enabled by the APIs as an escape hatch to avoid unexpected/undesired effects of the compile cache (e.g. less precise test coverage). When the module.enableCompileCache() method is invoked without a specified directory, Node.js will use the value of the NODE_COMPILE_CACHE environment variable if it's set, or defaults to `path.join(os.tmpdir(), 'node-compile-cache')` otherwise. Therefore it's recommended for tools to call this method without specifying the directory to allow overrides. --- doc/api/cli.md | 34 ++-- doc/api/module.md | 149 +++++++++++++++++- lib/internal/modules/helpers.js | 51 ++++++ lib/module.js | 8 + src/compile_cache.cc | 8 +- src/compile_cache.h | 7 +- src/env.cc | 15 +- src/node_modules.cc | 45 ++++++ test/fixtures/compile-cache-wrapper.js | 21 +++ test/parallel/test-compile-cache-api-env.js | 81 ++++++++++ .../test-compile-cache-api-permission.js | 56 +++++++ .../test-compile-cache-api-success.js | 79 ++++++++++ .../parallel/test-compile-cache-api-tmpdir.js | 80 ++++++++++ test/parallel/test-compile-cache-disable.js | 39 +++++ 14 files changed, 643 insertions(+), 30 deletions(-) create mode 100644 test/fixtures/compile-cache-wrapper.js create mode 100644 test/parallel/test-compile-cache-api-env.js create mode 100644 test/parallel/test-compile-cache-api-permission.js create mode 100644 test/parallel/test-compile-cache-api-success.js create mode 100644 test/parallel/test-compile-cache-api-tmpdir.js create mode 100644 test/parallel/test-compile-cache-disable.js diff --git a/doc/api/cli.md b/doc/api/cli.md index f5c30a29994bc7..4e03f25435da3e 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -2808,25 +2808,8 @@ added: v22.1.0 > Stability: 1.1 - Active Development -When set, whenever Node.js compiles a CommonJS or a ECMAScript Module, -it will use on-disk [V8 code cache][] persisted in the specified directory -to speed up the compilation. This may slow down the first load of a -module graph, but subsequent loads of the same module graph may get -a significant speedup if the contents of the modules do not change. - -To clean up the generated code cache, simply remove the directory. -It will be recreated the next time the same directory is used for -`NODE_COMPILE_CACHE`. - -Compilation cache generated by one version of Node.js may not be used -by a different version of Node.js. Cache generated by different versions -of Node.js will be stored separately if the same directory is used -to persist the cache, so they can co-exist. - -Caveat: currently when using this with [V8 JavaScript code coverage][], the -coverage being collected by V8 may be less precise in functions that are -deserialized from the code cache. It's recommended to turn this off when -running tests to generate precise coverage. +Enable the [module compile cache][] for the Node.js instance. See the documentatoin of +[module compile cache][] for details. ### `NODE_DEBUG=module[,…]` @@ -2848,6 +2831,17 @@ added: v0.3.0 When set, colors will not be used in the REPL. +### `NODE_DISABLE_COMPILE_CACHE=1` + + + +> Stability: 1.1 - Active Development + +Disable the [module compile cache][] for the Node.js instance. See the documentatoin of +[module compile cache][] for details. + ### `NODE_EXTRA_CA_CERTS=file` + +> Stability: 1.1 - Active Development + +The following constants are returned as the `status` field in the object returned by +[`module.enableCompileCache()`][] to indicate the result of the attempt to enable the +[module compile cache][]. + + + + + + + + + + + + + + + + + + + + + + +
ConstantDescription
ENABLED + Node.js has enabled the compile cache successfully. The directory used to store the + compile cache will be returned in the directory field in the + returned object. +
ALREADY_ENABLED + The compile cache has already been enabled before, either by a previous call to + module.enableCompileCache(), or by the NODE_COMPILE_CACHE=dir + environment variable. The directory used to store the + compile cache will be returned in the directory field in the + returned object. +
FAILED + Node.js fails to enable the compile cache. This can be caused by the lack of + permission to use the specified directory, or various kinds of file system errors. + The detail of the failure will be returned in the message field in the + returned object. +
DISABLED + Node.js cannot enable the compile cache because the environment variable + NODE_DISABLE_COMPILE_CACHE=1 has been set. +
+ +### `module.enableCompileCache([cacheDir])` + + + +> Stability: 1.1 - Active Development + +* `cacheDir` {string|undefined} Optional path to specify the directory where the compile cache + will be stored/retrieved. +* Returns: {Object} + * `status` {integer} One of the [`module.constants.compileCacheStatus`][] + * `message` {string|undefined} If Node.js cannot enable the compile cache, this contains + the error message. Only set if `status` is `module.constants.compileCacheStatus.FAILED`. + * `directory` {string|undefined} If the compile cache is enabled, this contains the directory + where the compile cache is stored. Only set if `status` is + `module.constants.compileCacheStatus.ENABLED` or + `module.constants.compileCacheStatus.ALREADY_ENABLED`. + +Enable [module compile cache][] in the current Node.js instance. + +If `cacheDir` is not specified, Node.js will either use the directory specified by the +[`NODE_COMPILE_CACHE=dir`][] environment variable if it's set, or use +`path.join(os.tmpdir(), 'node-compile-cache')` otherwise. For general use cases, it's +recommended to call `module.enableCompileCache()` without specifying the `cacheDir`, +so that the directory can be overriden by the `NODE_COMPILE_CACHE` environment +variable when necessary. + +Since compile cache is supposed to be a quiet optimization that is not required for the +application to be functional, this method is designed to not throw any exception when the +compile cache cannot be enabled. Instead, it will return an object containing an error +message in the `message` field to aid debugging. +If compile cache is enabled successefully, the `directory` field in the returned object +contains the path to the directory where the compile cache is stored. The `status` +field in the returned object would be one of the `module.constants.compileCacheStatus` +values to indicate the result of the attempt to enable the [module compile cache][]. + +This method only affects the current Node.js instance. To enable it in child worker threads, +either call this method in child worker threads too, or set the +`process.env.NODE_COMPILE_CACHE` value to compile cache directory so the behavior can +be inheritend into the child workers. The directory can be obtained either from the +`directory` field returned by this method, or with [`module.getCompileCacheDir()`][]. + +#### Module compile cache + +The module compile cache can be enabled either using the [`module.enableCompileCache()`][] +method or the [`NODE_COMPILE_CACHE=dir`][] environemnt variable. After it's enabled, +whenever Node.js compiles a CommonJS or a ECMAScript Module, it will use on-disk +[V8 code cache][] persisted in the specified directory to speed up the compilation. +This may slow down the first load of a module graph, but subsequent loads of the same module +graph may get a significant speedup if the contents of the modules do not change. + +To clean up the generated compile cache on disk, simply remove the cache directory. The cache +directory will be recreated the next time the same directory is used for for compile cache +storage. To avoid filling up the disk with stale cache, it is recommended to use a directory +under the [`os.tmpdir()`][]. If the compile cache is enabled by a call to +[`module.enableCompileCache()`][] without specifying the directory, Node.js will use +the [`NODE_DISABLE_COMPILE_CACHE=1`][] environment variable if it's set, or defaults +to `path.join(os.tmpdir(), 'node-compile-cache')` otherwise. To locate the compile cache +directory used by a running Node.js instance, use [`module.getCompileCacheDir()`][]. + +Currently when using the compile cache with [V8 JavaScript code coverage][], the +coverage being collected by V8 may be less precise in functions that are +deserialized from the code cache. It's recommended to turn this off when +running tests to generate precise coverage. + +The enabled module compile cache can be disabled by the [`NODE_DISABLE_COMPILE_CACHE=1`][] +environment variable. This can be useful when the compile cache leads to unexpected or +undesired behaviors (e.g. less precise test coverage). + +Compilation cache generated by one version of Node.js can not be reused by a different +version of Node.js. Cache generated by different versions of Node.js will be stored +separately if the same base directory is used to persist the cache, so they can co-exist. + +### `module.getCompileCacheDir()` + + + +> Stability: 1.1 - Active Development + +* Returns: {string|undefined} Path to the [module compile cache][] directory if it is enabled, + or undefined otherwise. + ### `module.isBuiltin(moduleName)` + The module compile cache can be enabled either using the [`module.enableCompileCache()`][] method or the [`NODE_COMPILE_CACHE=dir`][] environemnt variable. After it's enabled, whenever Node.js compiles a CommonJS or a ECMAScript Module, it will use on-disk @@ -200,7 +208,7 @@ added: REPLACEME > Stability: 1.1 - Active Development * Returns: {string|undefined} Path to the [module compile cache][] directory if it is enabled, - or undefined otherwise. + or `undefined` otherwise. ### `module.isBuiltin(moduleName)` diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index 4f83941ca1090d..d7120a3bbb8d0a 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -466,12 +466,12 @@ function enableCompileCache(cacheDir) { return result; } -const compileCacheStatus = {}; +const compileCacheStatus = { __proto__: null }; for (let i = 0; i < _compileCacheStatus.length; ++i) { compileCacheStatus[_compileCacheStatus[i]] = i; } ObjectFreeze(compileCacheStatus); -const constants = { compileCacheStatus }; +const constants = { __proto__: null, compileCacheStatus }; ObjectFreeze(constants); /** diff --git a/src/compile_cache.h b/src/compile_cache.h index 1bec1c5ed03528..975a3f2080f7a7 100644 --- a/src/compile_cache.h +++ b/src/compile_cache.h @@ -36,10 +36,10 @@ struct CompileCacheEntry { }; #define COMPILE_CACHE_STATUS(V) \ - V(FAILED) /* Failed to enable the cache */ \ - V(ENABLED) /* Was not enabled before, and now enabled. */ \ + V(FAILED) /* Failed to enable the cache */ \ + V(ENABLED) /* Was not enabled before, and now enabled. */ \ V(ALREADY_ENABLED) /* Was already enabled. */ \ - V(DISABLED) /* Has been disabled by NODE_DISABLE_COMPILE_CACHE. */ + V(DISABLED) /* Has been disabled by NODE_DISABLE_COMPILE_CACHE. */ enum class CompileCacheEnableStatus : uint8_t { #define V(status) status, From 3bd39dd1e2e3afe5404c0c10c52253b5b03436aa Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 23 Aug 2024 23:53:56 +0200 Subject: [PATCH 3/6] fixup! fixup! src: add JS APIs for compile cache and NODE_DISABLE_COMPILE_CACHE --- lib/internal/modules/helpers.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index d7120a3bbb8d0a..4141f42d070ec0 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -29,9 +29,10 @@ const assert = require('internal/assert'); const { Buffer } = require('buffer'); const { getOptionValue } = require('internal/options'); -const { setOwnProperty } = require('internal/util'); +const { setOwnProperty, getLazy } = require('internal/util'); const { inspect } = require('internal/util/inspect'); -const { tmpdir } = require('os'); + +const lazyTmpdir = getLazy(() => require('os').tmpdir()); const { join } = path; const { canParse: URLCanParse } = internalBinding('url'); @@ -453,7 +454,7 @@ function stringify(body) { */ function enableCompileCache(cacheDir) { if (cacheDir === undefined) { - cacheDir = join(tmpdir(), 'node-compile-cache'); + cacheDir = join(lazyTmpdir(), 'node-compile-cache'); } const nativeResult = _enableCompileCache(cacheDir); const result = { status: nativeResult[0] }; From ff7ba9266c6833baf06da6fc17e5e091ebdb99bc Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 27 Aug 2024 11:30:53 +0200 Subject: [PATCH 4/6] fixup! src: add JS APIs for compile cache and NODE_DISABLE_COMPILE_CACHE --- test/parallel/test-compile-cache-api-tmpdir.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-compile-cache-api-tmpdir.js b/test/parallel/test-compile-cache-api-tmpdir.js index 6f6669cd9da1dd..b111f409e0c0c7 100644 --- a/test/parallel/test-compile-cache-api-tmpdir.js +++ b/test/parallel/test-compile-cache-api-tmpdir.js @@ -9,6 +9,7 @@ const assert = require('assert'); const fixtures = require('../common/fixtures'); const tmpdir = require('../common/tmpdir'); const fs = require('fs'); +const path = require('path'); { // Test that it works with non-existent directory. @@ -43,9 +44,10 @@ const fs = require('fs'); } }); - const cacheDir = fs.readdirSync(tmpdir.path); + const baseDir = path.join(tmpdir.path, 'node-compile-cache'); + const cacheDir = fs.readdirSync(baseDir); assert.strictEqual(cacheDir.length, 1); - const entries = fs.readdirSync(tmpdir.resolve(cacheDir[0])); + const entries = fs.readdirSync(path.join(baseDir, cacheDir[0])); assert.strictEqual(entries.length, 1); // Second run reads the cache, but no need to re-write because it didn't change. From bd97c0ac99659dd5a0555d0976aba9c44db33f16 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 28 Aug 2024 15:35:42 +0200 Subject: [PATCH 5/6] fixup! src: add JS APIs for compile cache and NODE_DISABLE_COMPILE_CACHE --- test/parallel/test-compile-cache-api-tmpdir.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-compile-cache-api-tmpdir.js b/test/parallel/test-compile-cache-api-tmpdir.js index b111f409e0c0c7..86e12f61497427 100644 --- a/test/parallel/test-compile-cache-api-tmpdir.js +++ b/test/parallel/test-compile-cache-api-tmpdir.js @@ -24,7 +24,7 @@ const path = require('path'); NODE_DEBUG_NATIVE: 'COMPILE_CACHE', NODE_COMPILE_CACHE: undefined, NODE_TEST_COMPILE_CACHE_DIR: undefined, - TMPDIR: tmpdir.path + TMP: tmpdir.path }, cwd: tmpdir.path }, @@ -60,7 +60,7 @@ const path = require('path'); NODE_DEBUG_NATIVE: 'COMPILE_CACHE', NODE_COMPILE_CACHE: undefined, NODE_TEST_COMPILE_CACHE_DIR: undefined, - TMPDIR: tmpdir.path + TMP: tmpdir.path }, cwd: tmpdir.path }, From 73c03b1eb3fdec4fc675d82bc64f0d8e473b2e91 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 28 Aug 2024 17:17:19 +0200 Subject: [PATCH 6/6] fixup! src: add JS APIs for compile cache and NODE_DISABLE_COMPILE_CACHE --- test/parallel/test-compile-cache-api-tmpdir.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-compile-cache-api-tmpdir.js b/test/parallel/test-compile-cache-api-tmpdir.js index 86e12f61497427..3014be594b9bda 100644 --- a/test/parallel/test-compile-cache-api-tmpdir.js +++ b/test/parallel/test-compile-cache-api-tmpdir.js @@ -24,7 +24,12 @@ const path = require('path'); NODE_DEBUG_NATIVE: 'COMPILE_CACHE', NODE_COMPILE_CACHE: undefined, NODE_TEST_COMPILE_CACHE_DIR: undefined, - TMP: tmpdir.path + // TMPDIR is ignored on Windows while on other platforms, TMPDIR takes precedence. + // Override all related environment variables to ensure the tmpdir is configured properly + // regardless of the global environment variables used to run the test. + TMPDIR: tmpdir.path, + TEMP: tmpdir.path, + TMP: tmpdir.path, }, cwd: tmpdir.path }, @@ -60,7 +65,12 @@ const path = require('path'); NODE_DEBUG_NATIVE: 'COMPILE_CACHE', NODE_COMPILE_CACHE: undefined, NODE_TEST_COMPILE_CACHE_DIR: undefined, - TMP: tmpdir.path + // TMPDIR is ignored on Windows while on other platforms, TMPDIR takes precedence. + // Override all related environment variables to ensure the tmpdir is configured properly + // regardless of the global environment variables used to run the test. + TMPDIR: tmpdir.path, + TEMP: tmpdir.path, + TMP: tmpdir.path, }, cwd: tmpdir.path },