#pragma once #include #include #include 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 m_slot_guard; bool m_locked{false}; }; } // namespace Slic3r