1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 10:41:16 +02:00

optimise-store: Fix race condition in temporary link creation

Replace rand() with atomic counter for unique temp link names and add
retry loop to handle EEXIST errors when multiple processes optimize
the store simultaneously.
This commit is contained in:
Jörg Thalheim 2025-06-12 17:40:11 +02:00
parent 371fcf91c3
commit ef05a2fa0f

View file

@ -223,7 +223,9 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
its timestamp back to 0. */ its timestamp back to 0. */
MakeReadOnly makeReadOnly(mustToggle ? dirOfPath : ""); MakeReadOnly makeReadOnly(mustToggle ? dirOfPath : "");
std::filesystem::path tempLink = fmt("%1%/.tmp-link-%2%-%3%", config->realStoreDir, getpid(), rand()); /* Retry loop for temporary link creation to handle race conditions */
while (true) {
Path tempLink = makeTempPath(config->realStoreDir.get(), ".tmp-link");
try { try {
std::filesystem::create_hard_link(linkPath, tempLink); std::filesystem::create_hard_link(linkPath, tempLink);
@ -237,24 +239,37 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
printInfo("'%1%' has maximum number of links", linkPath); printInfo("'%1%' has maximum number of links", linkPath);
return; return;
} }
throw; throw SysError("creating temporary link '%1%'", tempLink);
} }
/* Atomically replace the old file with the new hard link. */ /* Atomically replace the old file with the new hard link. */
try { try {
std::filesystem::rename(tempLink, path); std::filesystem::rename(tempLink, path);
break; /* Success! */
} catch (std::filesystem::filesystem_error & e) { } catch (std::filesystem::filesystem_error & e) {
try {
std::filesystem::remove(tempLink); std::filesystem::remove(tempLink);
printError("unable to unlink '%1%'", tempLink); } catch (...) {
/* Ignore errors removing the temp link */
}
if (e.code() == std::errc::too_many_links) { if (e.code() == std::errc::too_many_links) {
/* Some filesystems generate too many links on the rename, /* Some filesystems generate too many links on the rename,
rather than on the original link. (Probably it rather than on the original link. (Probably it
temporarily increases the st_nlink field before temporarily increases the st_nlink field before
decreasing it again.) */ decreasing it again.) */
debug("'%s' has reached maximum number of links", linkPath); debug("%s has reached maximum number of links", linkPath);
return; return;
} }
throw;
if (e.code() == std::errc::file_exists) {
/* Race condition: another process created the same temp link.
Try again with a different name. */
continue;
}
throw SysError("renaming temporary link '%1%' to '%2%'", tempLink, path);
}
} }
stats.filesLinked++; stats.filesLinked++;