@@ -219,6 +219,8 @@ CompileCacheEntry* CompileCacheHandler::GetOrInsert(
219
219
return loaded->second .get ();
220
220
}
221
221
222
+ // If the code hash mismatches, the code has changed, discard the stale entry
223
+ // and create a new one.
222
224
auto emplaced =
223
225
compiler_cache_store_.emplace (key, std::make_unique<CompileCacheEntry>());
224
226
auto * result = emplaced.first ->second .get ();
@@ -287,23 +289,26 @@ void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry,
287
289
MaybeSaveImpl (entry, func, rejected);
288
290
}
289
291
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
+ */
297
307
void CompileCacheHandler::Persist () {
298
308
DCHECK (!compile_cache_dir_.empty ());
299
309
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.
307
312
for (auto & pair : compiler_cache_store_) {
308
313
auto * entry = pair.second .get ();
309
314
if (entry->cache == nullptr ) {
@@ -316,6 +321,11 @@ void CompileCacheHandler::Persist() {
316
321
entry->source_filename );
317
322
continue ;
318
323
}
324
+ if (entry->persisted == true ) {
325
+ Debug (" [compile cache] skip %s because cache was already persisted\n " ,
326
+ entry->source_filename );
327
+ continue ;
328
+ }
319
329
320
330
DCHECK_EQ (entry->cache ->buffer_policy ,
321
331
v8::ScriptCompiler::CachedData::BufferOwned);
@@ -332,27 +342,94 @@ void CompileCacheHandler::Persist() {
332
342
headers[kCodeHashOffset ] = entry->code_hash ;
333
343
headers[kCacheHashOffset ] = cache_hash;
334
344
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]..." ,
336
372
entry->source_filename ,
337
- entry-> cache_filename ,
373
+ mkstemp_req. path ,
338
374
headers[kMagicNumberOffset ],
339
375
headers[kCodeSizeOffset ],
340
376
headers[kCacheSizeOffset ],
341
377
headers[kCodeHashOffset ],
342
378
headers[kCacheHashOffset ]);
343
379
380
+ // Write to the temporary file.
344
381
uv_buf_t headers_buf = uv_buf_init (reinterpret_cast <char *>(headers.data ()),
345
382
headers.size () * sizeof (uint32_t ));
346
383
uv_buf_t data_buf = uv_buf_init (cache_ptr, entry->cache ->length );
347
384
uv_buf_t bufs[] = {headers_buf, data_buf};
348
385
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 );
350
421
if (err < 0 ) {
351
422
Debug (" failed: %s\n " , uv_strerror (err));
352
- } else {
353
- Debug (" success\n " );
423
+ continue ;
354
424
}
425
+ Debug (" success\n " );
426
+ entry->persisted = true ;
355
427
}
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 ();
356
433
}
357
434
358
435
CompileCacheHandler::CompileCacheHandler (Environment* env)
0 commit comments