namespace crucible {
using namespace std;
+ /** \brief Categorical mutual exclusion for a process-wide set of named lock types.
+
+ MultiLocker allows any number of concurrent holders that share the
+ same \c type string, while guaranteeing that two holders with
+ different \c type strings are never active at the same time. In
+ other words, at any instant the held locks all belong to a single
+ category; a request for a different category blocks until every lock
+ of every other category has been released.
+
+ This is used to serialize btrfs ioctls that must not run
+ concurrently with each other (e.g. \c "logical_ino" versus
+ \c "dedupe") while still permitting many calls of the same kind to
+ proceed in parallel.
+
+ There is a single hidden process-wide instance. All access is
+ through the static methods: acquire a lock by calling get_lock()
+ and hold the returned handle for the duration of the critical
+ section; the lock is released when the handle is destroyed. Locking
+ can be globally disabled with enable_locking(), in which case
+ get_lock() returns a null handle and imposes no serialization.
+ */
class MultiLocker {
+ /// Protects m_counters and serializes waiters on m_cv.
mutex m_mutex;
+ /// Signalled whenever a category's holder count drops to zero, so blocked waiters can re-test availability.
condition_variable m_cv;
+ /// Number of currently-held locks per type; at most one type may have a non-zero count at a time.
map<string, size_t> m_counters;
+ /// When false, get_lock() returns a null handle and no serialization is performed.
bool m_do_locking = true;
+ /** \brief Movable-by-shared_ptr token representing one held lock of a given type.
+
+ Created and reference-counted by MultiLocker; releasing the
+ lock happens in the destructor. Not constructed directly by
+ callers — obtain one via MultiLocker::get_lock().
+ */
class LockHandle {
+ /// The category this handle holds, used to decrement the right counter on release.
const string m_type;
+ /// The owning MultiLocker, notified on release.
MultiLocker &m_parent;
+ /// Whether this handle currently owns the lock (guards against releasing on a moved-from/unlocked handle).
bool m_locked = false;
+ /// Records ownership state; set true at acquisition and false after release.
void set_locked(bool state);
public:
+ /// Releases the lock (if still held) back to the owning MultiLocker.
~LockHandle();
+ /// Binds the handle to a category and its owning MultiLocker; does not itself acquire.
LockHandle(const string &type, MultiLocker &parent);
friend class MultiLocker;
};
friend class LockHandle;
+ /// \brief Returns true if a lock of \p type may be granted now, i.e. no other type currently has holders. Caller must hold m_mutex.
bool is_lock_available(const string &type);
+ /// \brief Releases one held lock of \p type, notifying waiters when its count reaches zero. Throws if the count was already zero.
void put_lock(const string &type);
+ /// \brief Blocks until a lock of \p type is available, increments its counter, and returns a handle owning the release.
shared_ptr<LockHandle> get_lock_private(const string &type);
public:
+ /** \brief Acquire a lock of the given category on the process-wide instance.
+
+ Blocks until no locks of any other category are held, then
+ returns a handle that holds the lock until it is destroyed.
+ If locking is disabled (see enable_locking()), returns a null
+ shared_ptr and performs no serialization.
+ */
static shared_ptr<LockHandle> get_lock(const string &type);
+ /// \brief Globally enable or disable locking on the process-wide instance; when disabled, get_lock() is a no-op returning null.
static void enable_locking(bool enabled);
};