diff --git a/doc/api/cli.md b/doc/api/cli.md index e213d055b8e..e956d79a50b 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -3726,8 +3726,9 @@ Enable the [module compile cache][] for the Node.js instance. See the documentat ### `NODE_COMPILE_CACHE_PORTABLE=1` -When set to 1, the [module compile cache][] can be reused across different directory -locations as long as the module layout relative to the cache directory remains the same. +When set to 1, the [module compile cache][] can be reused across different directory +locations as long as the module layout relative to the cache directory remains the same, +and by any user (the cache subdirectory is not suffixed with the creating user's uid). ### `NODE_DEBUG=module[,…]` diff --git a/doc/api/module.md b/doc/api/module.md index 956b10942ba..2cc52fb00d1 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -407,6 +407,14 @@ to the cache directory remains the same. This would be done on a best-effort bas Node.js cannot compute the location of a module relative to the cache directory, the module will not be cached. +A portable cache is also not split by user: on platforms with uids the +cache subdirectory of a non-portable cache is suffixed with the uid of the +user who created it, so it is only found by that user, while a portable +cache uses the same subdirectory for every user. This lets a cache generated +once (for example at build time, then shipped read-only with an application) +be read by whoever runs the code; a user who cannot write to the directory +still reads it, and a failed write only means the module is compiled again. + There are two ways to enable the portable mode: 1. Using the portable option in [`module.enableCompileCache()`][]: diff --git a/src/compile_cache.cc b/src/compile_cache.cc index dd097acd86f..f24a26e9bab 100644 --- a/src/compile_cache.cc +++ b/src/compile_cache.cc @@ -44,16 +44,23 @@ uint32_t GetHash(const char* data, size_t size) { return crc32(crc, reinterpret_cast(data), size); } -std::string GetCacheVersionTag() { +std::string GetCacheVersionTag(EnableOption option) { + std::string tag = std::string(NODE_VERSION) + '-' + std::string(NODE_ARCH) + + '-' + Uint32ToHex(ScriptCompiler::CachedDataVersionTag()); +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS // On platforms where uids are available, use different folders for // different users to avoid cache miss due to permission incompatibility. // On platforms where uids are not available, bare with the cache miss. // This should be fine on Windows, as there local directories tend to be // user-specific. - std::string tag = std::string(NODE_VERSION) + '-' + std::string(NODE_ARCH) + - '-' + Uint32ToHex(ScriptCompiler::CachedDataVersionTag()); -#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS - tag += '-' + std::to_string(getuid()); + // A portable cache is meant to be reused wherever the same layout is + // found, including by other users (e.g. a cache generated at build time + // and shipped read-only with an application), so it is not split by uid: + // a user who cannot write to it still reads it, and a failed write is + // only a cache miss. + if (option != EnableOption::PORTABLE) { + tag += '-' + std::to_string(getuid()); + } #endif return tag; } @@ -532,11 +539,12 @@ CompileCacheHandler::CompileCacheHandler(Environment* env) // Directory structure: // - Compile cache directory (from NODE_COMPILE_CACHE) // - $NODE_VERSION-$ARCH-$CACHE_DATA_VERSION_TAG-$UID +// ($UID is omitted for a portable cache) // - $FILENAME_AND_MODULE_TYPE_HASH.cache: a hash of filename + module type CompileCacheEnableResult CompileCacheHandler::Enable(Environment* env, const std::string& dir, EnableOption option) { - std::string cache_tag = GetCacheVersionTag(); + std::string cache_tag = GetCacheVersionTag(option); std::string absolute_cache_dir_base = PathResolve(env, {dir}); std::string cache_dir_with_tag = absolute_cache_dir_base + kPathSeparator + cache_tag; diff --git a/test/parallel/test-compile-cache-api-portable.js b/test/parallel/test-compile-cache-api-portable.js index 3c7c84b3220..620b3bdce87 100644 --- a/test/parallel/test-compile-cache-api-portable.js +++ b/test/parallel/test-compile-cache-api-portable.js @@ -57,6 +57,14 @@ const NODE_TEST_COMPILE_CACHE_OPTIONS = JSON.stringify({ directory: cacheRel, po ); } +// The cache subdirectory of a portable cache is not suffixed with the uid, +// so a cache generated by one user is found by another. +{ + const [tag] = fs.readdirSync(path.join(workDir, cacheRel)); + // $NODE_VERSION-$ARCH-$CACHE_DATA_VERSION_TAG, with no -$UID. + assert.match(tag, new RegExp(`^${process.version}-${process.arch}-[0-9a-f]+$`)); +} + // Second run — moved directory, but same relative cache path { const movedWorkDir = `${workDir}_moved`;