@@ -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+ */
297307void 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
358435CompileCacheHandler::CompileCacheHandler (Environment* env)
0 commit comments