Skip to content

Commit 1e3f772

Browse files
joyeecheungRafaelGSS
authored andcommitted
src: update compile cache storage structure
This refactors the compile cache handler in preparation for the JS API, and updates the compile cache storage structure into: - $NODE_COMPILE_CACHE_DIR - $NODE_VERION-$ARCH-$CACHE_DATA_VERSION_TAG-$UID - $FILENAME_AND_MODULE_TYPE_HASH.cache This also adds a magic number to the beginning of the cache files for verification, and returns the status, compile cache directory and/or error message of enabling the compile cache in a structure, which can be converted as JS counterparts by the upcoming JS API. PR-URL: #54291 Refs: #53639 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 92fa89f commit 1e3f772

File tree

5 files changed

+137
-62
lines changed

5 files changed

+137
-62
lines changed

Diff for: src/compile_cache.cc

+75-42
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "compile_cache.h"
2+
#include <string>
23
#include "debug_utils-inl.h"
34
#include "env-inl.h"
45
#include "node_file.h"
@@ -27,15 +28,19 @@ uint32_t GetHash(const char* data, size_t size) {
2728
return crc32(crc, reinterpret_cast<const Bytef*>(data), size);
2829
}
2930

30-
uint32_t GetCacheVersionTag() {
31-
std::string_view node_version(NODE_VERSION);
32-
uint32_t v8_tag = v8::ScriptCompiler::CachedDataVersionTag();
33-
uLong crc = crc32(0L, Z_NULL, 0);
34-
crc = crc32(crc, reinterpret_cast<const Bytef*>(&v8_tag), sizeof(uint32_t));
35-
crc = crc32(crc,
36-
reinterpret_cast<const Bytef*>(node_version.data()),
37-
node_version.size());
38-
return crc;
31+
std::string GetCacheVersionTag() {
32+
// On platforms where uids are available, use different folders for
33+
// different users to avoid cache miss due to permission incompatibility.
34+
// On platforms where uids are not available, bare with the cache miss.
35+
// This should be fine on Windows, as there local directories tend to be
36+
// user-specific.
37+
std::string tag = std::string(NODE_VERSION) + '-' + std::string(NODE_ARCH) +
38+
'-' +
39+
Uint32ToHex(v8::ScriptCompiler::CachedDataVersionTag());
40+
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
41+
tag += '-' + std::to_string(getuid());
42+
#endif
43+
return tag;
3944
}
4045

4146
uint32_t GetCacheKey(std::string_view filename, CachedCodeType type) {
@@ -63,6 +68,10 @@ v8::ScriptCompiler::CachedData* CompileCacheEntry::CopyCache() const {
6368
data, cache_size, v8::ScriptCompiler::CachedData::BufferOwned);
6469
}
6570

71+
// Used for identifying and verifying a file is a compile cache file.
72+
// See comments in CompileCacheHandler::Persist().
73+
constexpr uint32_t kCacheMagicNumber = 0x8adfdbb2;
74+
6675
void CompileCacheHandler::ReadCacheFile(CompileCacheEntry* entry) {
6776
Debug("[compile cache] reading cache from %s for %s %s...",
6877
entry->cache_filename,
@@ -100,12 +109,20 @@ void CompileCacheHandler::ReadCacheFile(CompileCacheEntry* entry) {
100109
return;
101110
}
102111

103-
Debug("[%d %d %d %d]...",
112+
Debug("[%d %d %d %d %d]...",
113+
headers[kMagicNumberOffset],
104114
headers[kCodeSizeOffset],
105115
headers[kCacheSizeOffset],
106116
headers[kCodeHashOffset],
107117
headers[kCacheHashOffset]);
108118

119+
if (headers[kMagicNumberOffset] != kCacheMagicNumber) {
120+
Debug("magic number mismatch: expected %d, actual %d\n",
121+
kCacheMagicNumber,
122+
headers[kMagicNumberOffset]);
123+
return;
124+
}
125+
109126
// Check the code size and hash which are already computed.
110127
if (headers[kCodeSizeOffset] != entry->code_size) {
111128
Debug("code size mismatch: expected %d, actual %d\n",
@@ -202,11 +219,14 @@ CompileCacheEntry* CompileCacheHandler::GetOrInsert(
202219
compiler_cache_store_.emplace(key, std::make_unique<CompileCacheEntry>());
203220
auto* result = emplaced.first->second.get();
204221

222+
std::u8string cache_filename_u8 =
223+
(compile_cache_dir_ / Uint32ToHex(key)).u8string();
205224
result->code_hash = code_hash;
206225
result->code_size = code_utf8.length();
207226
result->cache_key = key;
208227
result->cache_filename =
209-
(compile_cache_dir_ / Uint32ToHex(result->cache_key)).string();
228+
std::string(cache_filename_u8.begin(), cache_filename_u8.end()) +
229+
".cache";
210230
result->source_filename = filename_utf8.ToString();
211231
result->cache = nullptr;
212232
result->type = type;
@@ -264,6 +284,7 @@ void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry,
264284
}
265285

266286
// Layout of a cache file:
287+
// [uint32_t] magic number
267288
// [uint32_t] code size
268289
// [uint32_t] code hash
269290
// [uint32_t] cache size
@@ -301,14 +322,16 @@ void CompileCacheHandler::Persist() {
301322

302323
// Generating headers.
303324
std::vector<uint32_t> headers(kHeaderCount);
325+
headers[kMagicNumberOffset] = kCacheMagicNumber;
304326
headers[kCodeSizeOffset] = entry->code_size;
305327
headers[kCacheSizeOffset] = cache_size;
306328
headers[kCodeHashOffset] = entry->code_hash;
307329
headers[kCacheHashOffset] = cache_hash;
308330

309-
Debug("[compile cache] writing cache for %s in %s [%d %d %d %d]...",
331+
Debug("[compile cache] writing cache for %s in %s [%d %d %d %d %d]...",
310332
entry->source_filename,
311333
entry->cache_filename,
334+
headers[kMagicNumberOffset],
312335
headers[kCodeSizeOffset],
313336
headers[kCacheSizeOffset],
314337
headers[kCodeHashOffset],
@@ -335,53 +358,63 @@ CompileCacheHandler::CompileCacheHandler(Environment* env)
335358

336359
// Directory structure:
337360
// - Compile cache directory (from NODE_COMPILE_CACHE)
338-
// - <cache_version_tag_1>: hash of CachedDataVersionTag + NODE_VERESION
339-
// - <cache_version_tag_2>
340-
// - <cache_version_tag_3>
341-
// - <cache_file_1>: a hash of filename + module type
342-
// - <cache_file_2>
343-
// - <cache_file_3>
344-
bool CompileCacheHandler::InitializeDirectory(Environment* env,
345-
const std::string& dir) {
346-
compiler_cache_key_ = GetCacheVersionTag();
347-
std::string compiler_cache_key_string = Uint32ToHex(compiler_cache_key_);
348-
std::vector<std::string_view> paths = {dir, compiler_cache_key_string};
349-
std::string cache_dir = PathResolve(env, paths);
350-
361+
// - $NODE_VERION-$ARCH-$CACHE_DATA_VERSION_TAG-$UID
362+
// - $FILENAME_AND_MODULE_TYPE_HASH.cache: a hash of filename + module type
363+
CompileCacheEnableResult CompileCacheHandler::Enable(Environment* env,
364+
const std::string& dir) {
365+
std::string cache_tag = GetCacheVersionTag();
366+
std::string absolute_cache_dir_base = PathResolve(env, {dir});
367+
std::filesystem::path cache_dir_with_tag =
368+
std::filesystem::path(absolute_cache_dir_base) / cache_tag;
369+
std::u8string cache_dir_with_tag_u8 = cache_dir_with_tag.u8string();
370+
std::string cache_dir_with_tag_str(cache_dir_with_tag_u8.begin(),
371+
cache_dir_with_tag_u8.end());
372+
CompileCacheEnableResult result;
351373
Debug("[compile cache] resolved path %s + %s -> %s\n",
352374
dir,
353-
compiler_cache_key_string,
354-
cache_dir);
375+
cache_tag,
376+
cache_dir_with_tag_str);
355377

356378
if (UNLIKELY(!env->permission()->is_granted(
357-
env, permission::PermissionScope::kFileSystemWrite, cache_dir))) {
358-
Debug("[compile cache] skipping cache because write permission for %s "
359-
"is not granted\n",
360-
cache_dir);
361-
return false;
379+
env,
380+
permission::PermissionScope::kFileSystemWrite,
381+
cache_dir_with_tag_str))) {
382+
result.message = "Skipping compile cache because write permission for " +
383+
cache_dir_with_tag_str + " is not granted";
384+
result.status = CompileCacheEnableStatus::kFailed;
385+
return result;
362386
}
363387

364388
if (UNLIKELY(!env->permission()->is_granted(
365-
env, permission::PermissionScope::kFileSystemRead, cache_dir))) {
366-
Debug("[compile cache] skipping cache because read permission for %s "
367-
"is not granted\n",
368-
cache_dir);
369-
return false;
389+
env,
390+
permission::PermissionScope::kFileSystemRead,
391+
cache_dir_with_tag_str))) {
392+
result.message = "Skipping compile cache because read permission for " +
393+
cache_dir_with_tag_str + " is not granted";
394+
result.status = CompileCacheEnableStatus::kFailed;
395+
return result;
370396
}
371397

372398
fs::FSReqWrapSync req_wrap;
373-
int err = fs::MKDirpSync(nullptr, &(req_wrap.req), cache_dir, 0777, nullptr);
399+
int err = fs::MKDirpSync(
400+
nullptr, &(req_wrap.req), cache_dir_with_tag_str, 0777, nullptr);
374401
if (is_debug_) {
375402
Debug("[compile cache] creating cache directory %s...%s\n",
376-
cache_dir,
403+
cache_dir_with_tag_str,
377404
err < 0 ? uv_strerror(err) : "success");
378405
}
379406
if (err != 0 && err != UV_EEXIST) {
380-
return false;
407+
result.message =
408+
"Cannot create cache directory: " + std::string(uv_strerror(err));
409+
result.status = CompileCacheEnableStatus::kFailed;
410+
return result;
381411
}
382412

383-
compile_cache_dir_ = std::filesystem::path(cache_dir);
384-
return true;
413+
compile_cache_dir_str_ = absolute_cache_dir_base;
414+
result.cache_directory = absolute_cache_dir_base;
415+
compile_cache_dir_ = cache_dir_with_tag;
416+
result.status = CompileCacheEnableStatus::kEnabled;
417+
return result;
385418
}
386419

387420
} // namespace node

Diff for: src/compile_cache.h

+27-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <filesystem>
88
#include <memory>
99
#include <string>
10+
#include <string_view>
1011
#include <unordered_map>
1112
#include "v8.h"
1213

@@ -34,10 +35,27 @@ struct CompileCacheEntry {
3435
v8::ScriptCompiler::CachedData* CopyCache() const;
3536
};
3637

38+
#define COMPILE_CACHE_STATUS(V) \
39+
V(kFailed) /* Failed to enable the cache */ \
40+
V(kEnabled) /* Was not enabled before, and now enabled. */ \
41+
V(kAlreadyEnabled) /* Was already enabled. */
42+
43+
enum class CompileCacheEnableStatus : uint8_t {
44+
#define V(status) status,
45+
COMPILE_CACHE_STATUS(V)
46+
#undef V
47+
};
48+
49+
struct CompileCacheEnableResult {
50+
CompileCacheEnableStatus status;
51+
std::string cache_directory;
52+
std::string message; // Set in case of failure.
53+
};
54+
3755
class CompileCacheHandler {
3856
public:
3957
explicit CompileCacheHandler(Environment* env);
40-
bool InitializeDirectory(Environment* env, const std::string& dir);
58+
CompileCacheEnableResult Enable(Environment* env, const std::string& dir);
4159

4260
void Persist();
4361

@@ -50,6 +68,7 @@ class CompileCacheHandler {
5068
void MaybeSave(CompileCacheEntry* entry,
5169
v8::Local<v8::Module> mod,
5270
bool rejected);
71+
std::string_view cache_dir() { return compile_cache_dir_str_; }
5372

5473
private:
5574
void ReadCacheFile(CompileCacheEntry* entry);
@@ -62,19 +81,18 @@ class CompileCacheHandler {
6281
template <typename... Args>
6382
inline void Debug(const char* format, Args&&... args) const;
6483

65-
static constexpr size_t kCodeSizeOffset = 0;
66-
static constexpr size_t kCacheSizeOffset = 1;
67-
static constexpr size_t kCodeHashOffset = 2;
68-
static constexpr size_t kCacheHashOffset = 3;
69-
static constexpr size_t kHeaderCount = 4;
84+
static constexpr size_t kMagicNumberOffset = 0;
85+
static constexpr size_t kCodeSizeOffset = 1;
86+
static constexpr size_t kCacheSizeOffset = 2;
87+
static constexpr size_t kCodeHashOffset = 3;
88+
static constexpr size_t kCacheHashOffset = 4;
89+
static constexpr size_t kHeaderCount = 5;
7090

7191
v8::Isolate* isolate_ = nullptr;
7292
bool is_debug_ = false;
7393

94+
std::string compile_cache_dir_str_;
7495
std::filesystem::path compile_cache_dir_;
75-
// The compile cache is stored in a directory whose name is the hex string of
76-
// compiler_cache_key_.
77-
uint32_t compiler_cache_key_ = 0;
7896
std::unordered_map<uint32_t, std::unique_ptr<CompileCacheEntry>>
7997
compiler_cache_store_;
8098
};

Diff for: src/env.cc

+29-8
Original file line numberDiff line numberDiff line change
@@ -1129,15 +1129,36 @@ void Environment::InitializeCompileCache() {
11291129
dir_from_env.empty()) {
11301130
return;
11311131
}
1132-
auto handler = std::make_unique<CompileCacheHandler>(this);
1133-
if (handler->InitializeDirectory(this, dir_from_env)) {
1134-
compile_cache_handler_ = std::move(handler);
1135-
AtExit(
1136-
[](void* env) {
1137-
static_cast<Environment*>(env)->compile_cache_handler()->Persist();
1138-
},
1139-
this);
1132+
EnableCompileCache(dir_from_env);
1133+
}
1134+
1135+
CompileCacheEnableResult Environment::EnableCompileCache(
1136+
const std::string& cache_dir) {
1137+
CompileCacheEnableResult result;
1138+
1139+
if (!compile_cache_handler_) {
1140+
std::unique_ptr<CompileCacheHandler> handler =
1141+
std::make_unique<CompileCacheHandler>(this);
1142+
result = handler->Enable(this, cache_dir);
1143+
if (result.status == CompileCacheEnableStatus::kEnabled) {
1144+
compile_cache_handler_ = std::move(handler);
1145+
AtExit(
1146+
[](void* env) {
1147+
static_cast<Environment*>(env)->compile_cache_handler()->Persist();
1148+
},
1149+
this);
1150+
}
1151+
if (!result.message.empty()) {
1152+
Debug(this,
1153+
DebugCategory::COMPILE_CACHE,
1154+
"[compile cache] %s\n",
1155+
result.message);
1156+
}
1157+
} else {
1158+
result.status = CompileCacheEnableStatus::kAlreadyEnabled;
1159+
result.cache_directory = compile_cache_handler_->cache_dir();
11401160
}
1161+
return result;
11411162
}
11421163

11431164
void Environment::ExitEnv(StopFlags::Flags flags) {

Diff for: src/env.h

+3
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,9 @@ class Environment final : public MemoryRetainer {
10281028
inline CompileCacheHandler* compile_cache_handler();
10291029
inline bool use_compile_cache() const;
10301030
void InitializeCompileCache();
1031+
// Enable built-in compile cache if it has not yet been enabled.
1032+
// The cache will be persisted to disk on exit.
1033+
CompileCacheEnableResult EnableCompileCache(const std::string& cache_dir);
10311034

10321035
void RunAndClearNativeImmediates(bool only_refed = false);
10331036
void RunAndClearInterrupts();

Diff for: test/parallel/test-compile-cache-permission-disallowed.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function testDisallowed(dummyDir, cacheDirInPermission, cacheDirInEnv) {
3939
},
4040
{
4141
stderr(output) {
42-
assert.match(output, /skipping cache because write permission for .* is not granted/);
42+
assert.match(output, /Skipping compile cache because write permission for .* is not granted/);
4343
return true;
4444
}
4545
});
@@ -63,7 +63,7 @@ function testDisallowed(dummyDir, cacheDirInPermission, cacheDirInEnv) {
6363
},
6464
{
6565
stderr(output) {
66-
assert.match(output, /skipping cache because write permission for .* is not granted/);
66+
assert.match(output, /Skipping compile cache because write permission for .* is not granted/);
6767
return true;
6868
}
6969
});
@@ -86,7 +86,7 @@ function testDisallowed(dummyDir, cacheDirInPermission, cacheDirInEnv) {
8686
},
8787
{
8888
stderr(output) {
89-
assert.match(output, /skipping cache because read permission for .* is not granted/);
89+
assert.match(output, /Skipping compile cache because read permission for .* is not granted/);
9090
return true;
9191
}
9292
});

0 commit comments

Comments
 (0)