Files
OrcaSlicer/src/libslic3r/InstanceLock.hpp
T
Hanif Koh 16f44a9dfd Move a Refused Target Aside Rather Than Removing It and Wait Only for a Reader
The two-step rename fallback removed the target before its second try,
and rename(2) reports the same errors for a source it cannot move, so a
refusal about the source cost the caller its existing file. The target
is moved aside and put back if the second step fails too.

The wait for a reader holding the target open was layered on the write
helper and ran on every platform and for every refusal, so a read-only
target on Windows cost half a second before failing anyway, while G-code
exports through rename_file() got no wait at all. It lives in
rename_file() now, on Windows only, and only for a target this process
could write. A failed config write clears the dirty flag as it always
did, so the idle handler does not repeat it on every tick.

The lock file is created readable and writable by every user, since
another user sharing the data dir has to open it read-write; the check
that the file behind the path is still the one opened runs at most every
few seconds rather than once per preset during a scan; the physical
printer loader reads under the lock; the stat headers join the existing
platform include block; and the utility tests keep their file's tag.
2026-09-24 22:05:30 +08:00

64 lines
2.8 KiB
C++

#pragma once
#include <chrono>
#include <mutex>
#include <string>
namespace Slic3r {
// Scoped write lock on a file shared by every running instance of the
// application, such as the app config or the user preset directory. Threads
// of this process are serialised through a recursive mutex, other processes
// through an advisory OS file lock on `lock_file_path`, created on first use
// and kept: the lock state lives in the kernel on the open file, so deleting
// the file on release would let a third instance lock a fresh file while the
// second still holds the old one. The OS releases the lock when its holder
// exits, so a crashed instance never leaves a stale lock behind.
//
// The lock is best effort: when the lock file cannot be created, or another
// instance still holds it after `timeout`, the guard keeps only the in-process
// mutex and locked() reports false. Writes then proceed unprotected rather than
// letting one hung instance block every other one from saving. After such a
// timeout the same lock file is not waited on again for `cooldown`, so a batch
// of saves pays the wait once rather than once per file; a lock file that could
// not be opened, or a lock call that fails outright (a share without a lock
// service), is likewise retried only after `cooldown`.
//
// Lock order: the preset collection mutex may be held when a guard is taken
// (set_sync_info_and_save() calls save_info() under it), never the reverse.
// That is why the guards sit at the leaf readers and writers, and why a
// batch-level guard around save_user_presets(), which takes the collection
// mutex through delete_preset(), must not be added.
class InstanceLock
{
public:
static constexpr std::chrono::milliseconds default_timeout{2000};
// How long a lock file is left alone after a timed-out wait, a failed open
// or a failing lock call. Mutable so tests can shorten it.
static inline std::chrono::milliseconds cooldown{10000};
// How often a guard re-checks that the lock file behind the path is still
// the one it opened. Mutable so tests can shorten it.
static inline std::chrono::milliseconds identity_check_interval{5000};
// An empty path makes the guard a no-op.
explicit InstanceLock(const std::string &lock_file_path, std::chrono::milliseconds timeout = default_timeout);
~InstanceLock();
InstanceLock(const InstanceLock &) = delete;
InstanceLock &operator=(const InstanceLock &) = delete;
// True while this process holds the cross-process file lock.
bool locked() const { return m_locked; }
private:
struct Slot;
static Slot &slot_for(const std::string &lock_file_path);
static bool open_lock_file(Slot &slot, const std::string &lock_file_path);
Slot *m_slot{nullptr};
std::unique_lock<std::recursive_mutex> m_slot_guard;
bool m_locked{false};
};
} // namespace Slic3r