Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/compile_cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,23 @@ void CompileCacheHandler::Persist() {
continue;
}
Debug(" -> %s\n", mkstemp_req.path);

// Avoid leaking the descriptor or the temporary file if persistence
// does not complete below.
bool tmp_fd_needs_close = true;
bool tmp_renamed = false;
auto cleanup_tmp_file = OnScopeLeave([&]() {
if (tmp_fd_needs_close) {
uv_fs_t req;
uv_fs_close(nullptr, &req, mkstemp_req.result, nullptr);
uv_fs_req_cleanup(&req);
}
if (!tmp_renamed) {
uv_fs_t req;
uv_fs_unlink(nullptr, &req, mkstemp_req.path, nullptr);
uv_fs_req_cleanup(&req);
}
});
Debug("[compile cache] writing cache for %s %s to temporary file %s [%d "
"%d %d "
"%d %d]...",
Expand Down Expand Up @@ -496,6 +513,7 @@ void CompileCacheHandler::Persist() {
auto cleanup_close =
OnScopeLeave([&close_req]() { uv_fs_req_cleanup(&close_req); });
err = uv_fs_close(nullptr, &close_req, mkstemp_req.result, nullptr);
tmp_fd_needs_close = false;

if (err < 0) {
Debug("failed: %s\n", uv_strerror(err));
Expand All @@ -521,6 +539,7 @@ void CompileCacheHandler::Persist() {
Debug("failed: %s\n", uv_strerror(err));
continue;
}
tmp_renamed = true;
Debug("success\n");
entry->persisted = true;
}
Expand Down
31 changes: 31 additions & 0 deletions test/parallel/test-compile-cache-write-failure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

// This tests that a compile cache persistence failure does not leave the
// temporary file used to write it behind.

const common = require('../common');
if (common.isWindows)
common.skip('no RLIMIT_FSIZE on Windows');
if (process.config.variables.node_shared)
common.skip('SIGXFSZ signal handler not installed in shared library mode');

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const tmpdir = require('../common/tmpdir');

tmpdir.refresh();
const cacheDir = tmpdir.resolve('compile_cache');
fs.writeFileSync(tmpdir.resolve('fixture.cjs'), 'module.exports = 42;\n');

const [cmd, opts] = common.escapePOSIXShell`ulimit -f 0 && "${process.execPath}" -e "require('./fixture.cjs')"`;
opts.env.NODE_COMPILE_CACHE = cacheDir;
opts.cwd = tmpdir.path;
const result = spawnSync('/bin/sh', ['-c', cmd], opts);
assert.strictEqual(result.status, 0, result.stderr.toString());

const subdirs = fs.readdirSync(cacheDir);
assert.strictEqual(subdirs.length, 1);
const leftover = fs.readdirSync(path.join(cacheDir, subdirs[0]));
assert.deepStrictEqual(leftover, []);
Loading