#ifndef CRUCIBLE_LOCKSET_H #define CRUCIBLE_LOCKSET_H #include "crucible/error.h" #include "crucible/process.h" #include #include #include #include #include #include #include namespace crucible { using namespace std; template class LockSet { public: using set_type = map; using key_type = typename set_type::key_type; private: set_type m_set; mutex m_mutex; condition_variable m_condvar; size_t m_max_size = numeric_limits::max(); bool full(); bool locked(const key_type &name); class Lock { LockSet &m_lockset; key_type m_name; bool m_locked; Lock() = delete; Lock(const Lock &) = delete; Lock& operator=(const Lock &) = delete; Lock(Lock &&that) = delete; Lock& operator=(Lock &&that) = delete; public: ~Lock(); Lock(LockSet &lockset, const key_type &name, bool start_locked = true); void lock(); void unlock(); bool try_lock(); }; public: ~LockSet(); LockSet() = default; void lock(const key_type &name); void unlock(const key_type &name); bool try_lock(const key_type &name); size_t size(); bool empty(); set_type copy(); void max_size(size_t max); class LockHandle { shared_ptr m_lock; public: LockHandle(LockSet &lockset, const key_type &name, bool start_locked = true) : m_lock(make_shared(lockset, name, start_locked)) {} void lock() { m_lock->lock(); } void unlock() { m_lock->unlock(); } bool try_lock() { return m_lock->try_lock(); } }; LockHandle make_lock(const key_type &name, bool start_locked = true); }; template LockSet::~LockSet() { if (!m_set.empty()) { cerr << "ERROR: " << m_set.size() << " locked items still in set at destruction" << endl; } // We will crash later. Might as well crash now. assert(m_set.empty()); } template bool LockSet::full() { return m_set.size() >= m_max_size; } template bool LockSet::locked(const key_type &name) { return m_set.count(name); } template void LockSet::max_size(size_t s) { m_max_size = s; } template void LockSet::lock(const key_type &name) { unique_lock lock(m_mutex); while (full() || locked(name)) { m_condvar.wait(lock); } auto rv = m_set.insert(make_pair(name, crucible::gettid())); THROW_CHECK0(runtime_error, rv.second); } template bool LockSet::try_lock(const key_type &name) { unique_lock lock(m_mutex); if (full() || locked(name)) { return false; } auto rv = m_set.insert(make_pair(name, crucible::gettid())); THROW_CHECK1(runtime_error, name, rv.second); return true; } template void LockSet::unlock(const key_type &name) { unique_lock lock(m_mutex); auto erase_count = m_set.erase(name); m_condvar.notify_all(); THROW_CHECK1(invalid_argument, erase_count, erase_count == 1); } template size_t LockSet::size() { unique_lock lock(m_mutex); return m_set.size(); } template bool LockSet::empty() { unique_lock lock(m_mutex); return m_set.empty(); } template typename LockSet::set_type LockSet::copy() { unique_lock lock(m_mutex); // Make temporary copy of set while protected by mutex auto rv = m_set; // Return temporary copy after releasing lock return rv; } template void LockSet::Lock::lock() { if (m_locked) return; m_lockset.lock(m_name); m_locked = true; } template bool LockSet::Lock::try_lock() { if (m_locked) return true; m_locked = m_lockset.try_lock(m_name); return m_locked; } template void LockSet::Lock::unlock() { if (!m_locked) return; m_lockset.unlock(m_name); m_locked = false; } template LockSet::Lock::~Lock() { if (m_locked) { unlock(); } } template LockSet::Lock::Lock(LockSet &lockset, const key_type &name, bool start_locked) : m_lockset(lockset), m_name(name), m_locked(false) { if (start_locked) { lock(); } } template typename LockSet::LockHandle LockSet::make_lock(const key_type &name, bool start_locked) { return LockHandle(*this, name, start_locked); } } #endif // CRUCIBLE_LOCKSET_H