mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 10:21:00 +00:00
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:
@@ -93,6 +93,10 @@ TEST_CASE("write_file_atomically reports a missing directory and writes nothing"
|
||||
}
|
||||
|
||||
TEST_CASE("write_file_atomically refuses a read-only target and leaves it untouched", "[Utils]") {
|
||||
#ifndef _WIN32
|
||||
if (::geteuid() == 0)
|
||||
SKIP("a read-only file does not stop root");
|
||||
#endif
|
||||
ScopedTemporaryDir dir;
|
||||
const boost::filesystem::path target = dir.path() / "pinned.json";
|
||||
REQUIRE_FALSE(write_file_atomically(target.string(), "pinned"));
|
||||
@@ -164,33 +168,33 @@ TEST_CASE("write_file_atomically survives two threads writing one target", "[Uti
|
||||
REQUIRE(temporaries == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("remove_stale_temp_files removes only old <name>.<pid>.tmp files", "[Utils]") {
|
||||
TEST_CASE("remove_stale_temp_files removes only old <name>.<pid>.<n>.tmp files", "[Utils]") {
|
||||
ScopedTemporaryDir dir;
|
||||
for (const char *name : { "a.json.123.tmp", "b.info.4.tmp", "c.json", "d.tmp", "e.json.x.tmp", "f.json..tmp", "a.json.99", "a.json.12.3.tmp" }) {
|
||||
for (const char *name : { "a.json.123.7.tmp", "b.info.4.0.tmp", "c.json", "d.tmp", "e.json.x.1.tmp", "f.json..tmp", "a.json.99", "a.json.12.tmp" }) {
|
||||
REQUIRE_FALSE(write_file_atomically((dir.path() / name).string(), "x"));
|
||||
// An hour old: long past the age below which a temporary may still be in flight.
|
||||
boost::filesystem::last_write_time(dir.path() / name, std::time(nullptr) - 3600);
|
||||
}
|
||||
// Just written: possibly another instance's in-flight save, so it stays.
|
||||
REQUIRE_FALSE(write_file_atomically((dir.path() / "g.json.7.tmp").string(), "x"));
|
||||
REQUIRE_FALSE(write_file_atomically((dir.path() / "g.json.7.2.tmp").string(), "x"));
|
||||
|
||||
SECTION("with a name prefix only matching names go, and a numbered backup is not a temporary") {
|
||||
REQUIRE(remove_stale_temp_files(dir.path(), "a.json") == 2);
|
||||
REQUIRE_FALSE(boost::filesystem::exists(dir.path() / "a.json.123.tmp"));
|
||||
REQUIRE_FALSE(boost::filesystem::exists(dir.path() / "a.json.12.3.tmp"));
|
||||
SECTION("with a name prefix only matching names go; a numbered backup or a one-segment name is not a temporary") {
|
||||
REQUIRE(remove_stale_temp_files(dir.path(), "a.json") == 1);
|
||||
REQUIRE_FALSE(boost::filesystem::exists(dir.path() / "a.json.123.7.tmp"));
|
||||
REQUIRE(boost::filesystem::exists(dir.path() / "a.json.99"));
|
||||
REQUIRE(boost::filesystem::exists(dir.path() / "b.info.4.tmp"));
|
||||
REQUIRE(boost::filesystem::exists(dir.path() / "a.json.12.tmp"));
|
||||
REQUIRE(boost::filesystem::exists(dir.path() / "b.info.4.0.tmp"));
|
||||
}
|
||||
SECTION("without a prefix every stale temporary goes and nothing else") {
|
||||
REQUIRE(remove_stale_temp_files(dir.path()) == 3);
|
||||
REQUIRE(remove_stale_temp_files(dir.path()) == 2);
|
||||
size_t entries = 0;
|
||||
for (auto &entry : boost::filesystem::directory_iterator(dir.path())) {
|
||||
(void) entry;
|
||||
++entries;
|
||||
}
|
||||
REQUIRE(entries == 6);
|
||||
REQUIRE(entries == 7);
|
||||
REQUIRE(boost::filesystem::exists(dir.path() / "a.json.99"));
|
||||
REQUIRE(boost::filesystem::exists(dir.path() / "g.json.7.tmp"));
|
||||
REQUIRE(boost::filesystem::exists(dir.path() / "g.json.7.2.tmp"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user