Hold the POSIX Lock With flock and Leave a Moved-Aside File to the User

An fcntl lock belongs to the process and goes with the first close of
any other descriptor to the lock file, so a backup or an export walking
the data dir could drop the guard's lock without a trace. On POSIX the
guard holds a flock on its own open file instead, which nothing else in
the process can release; Windows keeps LockFileEx.

The Windows write probe opened the target for writing and took a
sharing violation for a refusal, so a file another process merely held
open was not saved at all; only a real denial refuses now, since the
rename that follows moves an open destination aside. A file moved aside
by a refused rename may be the last copy of a file or a file since
deleted on purpose, so the sweep logs it and leaves it to the user
rather than removing or restoring it. The sweep throttles itself per
directory, so a load followed by a save reads each directory once, and
the identity check on the lock file runs at most once a second, so a
scan of hundreds of presets pays for it once. A config that stays
unwritable backs off for longer with each failure in a row, and its
backup copy is written after the config, never before. The Windows
identity helper is shared with the rename that already computed it, and
the header comment that had lost its indentation and its neighbour's
description is whole again.
This commit is contained in:
Hanif Koh
2026-09-25 00:27:44 +08:00
parent a4c925a445
commit 5603ed66c0
8 changed files with 130 additions and 83 deletions
+5 -2
View File
@@ -456,13 +456,16 @@ private:
// Preset for each machine
MachineSettingMap m_printer_settings;
// Writes the assembled config text, and on Windows its checksum and a backup copy; false when the
// config itself could not be written, in which case the caller stays dirty and retries. `checksum_source`
// config itself could not be written, in which case the caller stays dirty and retries. `checksum_source`
// is the text load() will verify, which for the JSON config ends before the trailing newline.
bool write_config_file(const std::string &path, std::string body, const std::string &checksum_source);
// Has any value been modified since the config.ini has been last saved or loaded?
bool m_dirty;
// After a failed write, save_due() is false until this point.
// After a failed write, save_due() is false until this point, which moves out
// ten seconds, then twenty, up to five minutes, for every failure in a row.
std::chrono::steady_clock::time_point m_retry_save_at{};
std::chrono::seconds m_retry_save_after{10};
// Original version found in the ini file before it was overwritten
Semver m_orig_version;
// Whether the existing version is before system profiles & configuration updating