]> git.hungrycats.org Git - bees/commitdiff
multilock: document MultiLocker with Doxygen
authorZygo Blaxell <bees@furryterror.org>
Sat, 13 Jun 2026 20:43:33 +0000 (16:43 -0400)
committerZygo Blaxell <bees@furryterror.org>
Sun, 30 Aug 2026 23:48:46 +0000 (19:48 -0400)
The comments capture the non-obvious contract: locks sharing a type
string run concurrently, but distinct types are mutually exclusive, so
at any instant all held locks belong to a single category.  Document the
process-wide singleton access pattern through the static get_lock() /
enable_locking() entry points, the handle-lifetime release model, and
the m_mutex precondition on is_lock_available().

Assisted-by: Claude-Code:claude-opus-4-8
Signed-off-by: Zygo Blaxell <bees@furryterror.org>
include/crucible/multilock.h

index 4d55558a8ea198a44ab63c70809ef74030e09628..71805ea93a3f9e30838ee663d4010d8c70fd0840 100644 (file)
 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);
        };