Skip to content

Commit f9b19d7

Browse files
joyeecheungtargos
authored andcommitted
module: write compile cache to temporary file and then rename it
This works better in terms of avoiding race conditions. PR-URL: #54971 Fixes: #54770 Fixes: #54465 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 0876f78 commit f9b19d7

File tree

4 files changed

+107
-20
lines changed

4 files changed

+107
-20
lines changed

src/compile_cache.cc

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ CompileCacheEntry* CompileCacheHandler::GetOrInsert(
219219
return loaded->second.get();
220220
}
221221

222+
// If the code hash mismatches, the code has changed, discard the stale entry
223+
// and create a new one.
222224
auto emplaced =
223225
compiler_cache_store_.emplace(key, std::make_unique<CompileCacheEntry>());
224226
auto* result = emplaced.first->second.get();
@@ -287,23 +289,26 @@ void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry,
287289
MaybeSaveImpl(entry, func, rejected);
288290
}
289291

290-
// Layout of a cache file:
291-
// [uint32_t] magic number
292-
// [uint32_t] code size
293-
// [uint32_t] code hash
294-
// [uint32_t] cache size
295-
// [uint32_t] cache hash
296-
// .... compile cache content ....
292+
/**
293+
* Persist the compile cache accumulated in memory to disk.
294+
*
295+
* To avoid race conditions, the cache file includes hashes of the original
296+
* source code and the cache content. It's first written to a temporary file
297+
* before being renamed to the target name.
298+
*
299+
* Layout of a cache file:
300+
* [uint32_t] magic number
301+
* [uint32_t] code size
302+
* [uint32_t] code hash
303+
* [uint32_t] cache size
304+
* [uint32_t] cache hash
305+
* .... compile cache content ....
306+
*/
297307
void CompileCacheHandler::Persist() {
298308
DCHECK(!compile_cache_dir_.empty());
299309

300-
// NOTE(joyeecheung): in most circumstances the code caching reading
301-
// writing logic is lenient enough that it's fine even if someone
302-
// overwrites the cache (that leads to either size or hash mismatch
303-
// in subsequent loads and the overwritten cache will be ignored).
304-
// Also in most use cases users should not change the files on disk
305-
// too rapidly. Therefore locking is not currently implemented to
306-
// avoid the cost.
310+
// TODO(joyeecheung): do this using a separate event loop to utilize the
311+
// libuv thread pool and do the file system operations concurrently.
307312
for (auto& pair : compiler_cache_store_) {
308313
auto* entry = pair.second.get();
309314
if (entry->cache == nullptr) {
@@ -316,6 +321,11 @@ void CompileCacheHandler::Persist() {
316321
entry->source_filename);
317322
continue;
318323
}
324+
if (entry->persisted == true) {
325+
Debug("[compile cache] skip %s because cache was already persisted\n",
326+
entry->source_filename);
327+
continue;
328+
}
319329

320330
DCHECK_EQ(entry->cache->buffer_policy,
321331
v8::ScriptCompiler::CachedData::BufferOwned);
@@ -332,27 +342,94 @@ void CompileCacheHandler::Persist() {
332342
headers[kCodeHashOffset] = entry->code_hash;
333343
headers[kCacheHashOffset] = cache_hash;
334344

335-
Debug("[compile cache] writing cache for %s in %s [%d %d %d %d %d]...",
345+
// Generate the temporary filename.
346+
// The temporary file should be placed in a location like:
347+
//
348+
// $NODE_COMPILE_CACHE_DIR/v23.0.0-pre-arm64-5fad6d45-501/e7f8ef7f.cache.tcqrsK
349+
//
350+
// 1. $NODE_COMPILE_CACHE_DIR either comes from the $NODE_COMPILE_CACHE
351+
// environment
352+
// variable or `module.enableCompileCache()`.
353+
// 2. v23.0.0-pre-arm64-5fad6d45-501 is the sub cache directory and
354+
// e7f8ef7f is the hash for the cache (see
355+
// CompileCacheHandler::Enable()),
356+
// 3. tcqrsK is generated by uv_fs_mkstemp() as a temporary indentifier.
357+
uv_fs_t mkstemp_req;
358+
auto cleanup_mkstemp =
359+
OnScopeLeave([&mkstemp_req]() { uv_fs_req_cleanup(&mkstemp_req); });
360+
std::string cache_filename_tmp = entry->cache_filename + ".XXXXXX";
361+
Debug("[compile cache] Creating temporary file for cache of %s...",
362+
entry->source_filename);
363+
int err = uv_fs_mkstemp(
364+
nullptr, &mkstemp_req, cache_filename_tmp.c_str(), nullptr);
365+
if (err < 0) {
366+
Debug("failed. %s\n", uv_strerror(err));
367+
continue;
368+
}
369+
Debug(" -> %s\n", mkstemp_req.path);
370+
Debug("[compile cache] writing cache for %s to temporary file %s [%d %d %d "
371+
"%d %d]...",
336372
entry->source_filename,
337-
entry->cache_filename,
373+
mkstemp_req.path,
338374
headers[kMagicNumberOffset],
339375
headers[kCodeSizeOffset],
340376
headers[kCacheSizeOffset],
341377
headers[kCodeHashOffset],
342378
headers[kCacheHashOffset]);
343379

380+
// Write to the temporary file.
344381
uv_buf_t headers_buf = uv_buf_init(reinterpret_cast<char*>(headers.data()),
345382
headers.size() * sizeof(uint32_t));
346383
uv_buf_t data_buf = uv_buf_init(cache_ptr, entry->cache->length);
347384
uv_buf_t bufs[] = {headers_buf, data_buf};
348385

349-
int err = WriteFileSync(entry->cache_filename.c_str(), bufs, 2);
386+
uv_fs_t write_req;
387+
auto cleanup_write =
388+
OnScopeLeave([&write_req]() { uv_fs_req_cleanup(&write_req); });
389+
err = uv_fs_write(
390+
nullptr, &write_req, mkstemp_req.result, bufs, 2, 0, nullptr);
391+
if (err < 0) {
392+
Debug("failed: %s\n", uv_strerror(err));
393+
continue;
394+
}
395+
396+
uv_fs_t close_req;
397+
auto cleanup_close =
398+
OnScopeLeave([&close_req]() { uv_fs_req_cleanup(&close_req); });
399+
err = uv_fs_close(nullptr, &close_req, mkstemp_req.result, nullptr);
400+
401+
if (err < 0) {
402+
Debug("failed: %s\n", uv_strerror(err));
403+
continue;
404+
}
405+
406+
Debug("success\n");
407+
408+
// Rename the temporary file to the actual cache file.
409+
uv_fs_t rename_req;
410+
auto cleanup_rename =
411+
OnScopeLeave([&rename_req]() { uv_fs_req_cleanup(&rename_req); });
412+
std::string cache_filename_final = entry->cache_filename;
413+
Debug("[compile cache] Renaming %s to %s...",
414+
mkstemp_req.path,
415+
cache_filename_final);
416+
err = uv_fs_rename(nullptr,
417+
&rename_req,
418+
mkstemp_req.path,
419+
cache_filename_final.c_str(),
420+
nullptr);
350421
if (err < 0) {
351422
Debug("failed: %s\n", uv_strerror(err));
352-
} else {
353-
Debug("success\n");
423+
continue;
354424
}
425+
Debug("success\n");
426+
entry->persisted = true;
355427
}
428+
429+
// Clear the map at the end in one go instead of during the iteration to
430+
// avoid rehashing costs.
431+
Debug("[compile cache] Clear deserialized cache.\n");
432+
compiler_cache_store_.clear();
356433
}
357434

358435
CompileCacheHandler::CompileCacheHandler(Environment* env)

src/compile_cache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ struct CompileCacheEntry {
3030
std::string source_filename;
3131
CachedCodeType type;
3232
bool refreshed = false;
33+
bool persisted = false;
34+
3335
// Copy the cache into a new store for V8 to consume. Caller takes
3436
// ownership.
3537
v8::ScriptCompiler::CachedData* CopyCache() const;

src/env.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ CompileCacheEnableResult Environment::EnableCompileCache(
11541154
compile_cache_handler_ = std::move(handler);
11551155
AtExit(
11561156
[](void* env) {
1157-
static_cast<Environment*>(env)->compile_cache_handler()->Persist();
1157+
static_cast<Environment*>(env)->FlushCompileCache();
11581158
},
11591159
this);
11601160
}
@@ -1171,6 +1171,13 @@ CompileCacheEnableResult Environment::EnableCompileCache(
11711171
return result;
11721172
}
11731173

1174+
void Environment::FlushCompileCache() {
1175+
if (!compile_cache_handler_ || compile_cache_handler_->cache_dir().empty()) {
1176+
return;
1177+
}
1178+
compile_cache_handler_->Persist();
1179+
}
1180+
11741181
void Environment::ExitEnv(StopFlags::Flags flags) {
11751182
// Should not access non-thread-safe methods here.
11761183
set_stopping(true);

src/env.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,7 @@ class Environment final : public MemoryRetainer {
10451045
// Enable built-in compile cache if it has not yet been enabled.
10461046
// The cache will be persisted to disk on exit.
10471047
CompileCacheEnableResult EnableCompileCache(const std::string& cache_dir);
1048+
void FlushCompileCache();
10481049

10491050
void RunAndClearNativeImmediates(bool only_refed = false);
10501051
void RunAndClearInterrupts();

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy