From 4a4a2de89f618b4ad9fced3faa4db64273901ede Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Sun, 13 Nov 2022 21:59:21 -0500 Subject: [PATCH] multilocker: serialize conflicting parallel operations For performance or workaround reasons we sometimes have to avoid doing two conflicting operations at the same time, but we can still run any number of non-conflicting operations in parallel. MultiLocker (suggestions for a better class name welcome) blocks the calling thread until there are no threads attempting to run a conflicting operation. Signed-off-by: Zygo Blaxell --- include/crucible/multilock.h | 40 ++++++++++++++++++++ lib/Makefile | 1 + lib/multilock.cc | 72 ++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 include/crucible/multilock.h create mode 100644 lib/multilock.cc diff --git a/include/crucible/multilock.h b/include/crucible/multilock.h new file mode 100644 index 0000000..6b7fb6c --- /dev/null +++ b/include/crucible/multilock.h @@ -0,0 +1,40 @@ +#ifndef CRUCIBLE_MULTILOCK_H +#define CRUCIBLE_MULTILOCK_H + +#include +#include +#include +#include +#include + +namespace crucible { + using namespace std; + + class MultiLocker { + mutex m_mutex; + condition_variable m_cv; + map m_counters; + + class LockHandle { + const string m_type; + MultiLocker &m_parent; + bool m_locked = false; + void set_locked(bool state); + public: + ~LockHandle(); + LockHandle(const string &type, MultiLocker &parent); + friend class MultiLocker; + }; + + friend class LockHandle; + + bool is_lock_available(const string &type); + void put_lock(const string &type); + shared_ptr get_lock_private(const string &type); + public: + static shared_ptr get_lock(const string &type); + }; + +} + +#endif // CRUCIBLE_MULTILOCK_H diff --git a/lib/Makefile b/lib/Makefile index 622abf9..16557a2 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -13,6 +13,7 @@ CRUCIBLE_OBJS = \ extentwalker.o \ fd.o \ fs.o \ + multilock.o \ ntoa.o \ path.o \ process.o \ diff --git a/lib/multilock.cc b/lib/multilock.cc new file mode 100644 index 0000000..8a56271 --- /dev/null +++ b/lib/multilock.cc @@ -0,0 +1,72 @@ +#include "crucible/multilock.h" + +#include "crucible/error.h" + +namespace crucible { + using namespace std; + + MultiLocker::LockHandle::LockHandle(const string &type, MultiLocker &parent) : + m_type(type), + m_parent(parent) + { + } + + void + MultiLocker::LockHandle::set_locked(const bool state) + { + m_locked = state; + } + + MultiLocker::LockHandle::~LockHandle() + { + if (m_locked) { + m_parent.put_lock(m_type); + m_locked = false; + } + } + + bool + MultiLocker::is_lock_available(const string &type) + { + for (const auto &i : m_counters) { + if (i.second != 0 && i.first != type) { + return false; + } + } + return true; + } + + void + MultiLocker::put_lock(const string &type) + { + unique_lock lock(m_mutex); + auto &counter = m_counters[type]; + THROW_CHECK2(runtime_error, type, counter, counter > 0); + --counter; + if (counter == 0) { + m_cv.notify_all(); + } + } + + shared_ptr + MultiLocker::get_lock_private(const string &type) + { + unique_lock lock(m_mutex); + m_counters.insert(make_pair(type, size_t(0))); + while (!is_lock_available(type)) { + m_cv.wait(lock); + } + const auto rv = make_shared(type, *this); + ++m_counters[type]; + rv->set_locked(true); + return rv; + } + + shared_ptr + MultiLocker::get_lock(const string &type) + { + static MultiLocker s_process_instance; + return s_process_instance.get_lock_private(type); + } + +}