Lock the Preset Scan Per File and Reopen a Lock File Replaced on Disk

Holding the lock across the whole preset scan meant a reload on a
background thread, which the login path runs, blocked a save on the GUI
thread for the scan's duration through the in-process mutex, which has
no timeout. Each file is locked on its own now, which keeps a file whole
under a reader without keeping the saver waiting.

A guard kept its handle to the lock file for good, so a lock file that
someone deleted or recreated left this instance locking a file no other
instance could see. The guard compares what the path names against what
it opened and reopens when they differ. Preset::save() serialises before
it takes the lock, so the exclusive window is the two file writes.

On Windows an unlocked reader, which the CLI and a timed-out instance
are by design, made the rename fail at once and the write go in place
under that reader; the rename is retried for half a second first, since
a reader is done in milliseconds, and the fallback when no temporary
can be created is logged like the other one. The sweep matches only the
exact <name>.<pid>.<n>.tmp shape and waits an hour, since hosts sharing a
data dir may disagree on the time. Real write access is checked with
access(), the read-only tests skip as root, and the dead permissions
block after the rename is gone.
This commit is contained in:
Hanif Koh
2026-09-24 20:54:02 +08:00
parent 5490320b8b
commit 758b802dec
7 changed files with 149 additions and 56 deletions
+8
View File
@@ -1,5 +1,9 @@
#include <catch2/catch_all.hpp>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <boost/filesystem.hpp>
#include <boost/crc.hpp>
#include <cereal/archives/binary.hpp>
@@ -1358,6 +1362,10 @@ TEST_CASE("a header claiming more body than the file holds is rejected", "[Vendo
TEST_CASE("a failed write leaves the previous cache in place", "[VendorCache]")
{
#ifndef _WIN32
if (::geteuid() == 0)
SKIP("a read-only file does not stop root");
#endif
TempDir tmp;
const std::string cache = (tmp.path / "Durable.opc").string();
REQUIRE(save_one_vendor(cache, one_vendor("Durable"), "Durable", "1.0.0"));