#ifndef LOCK_SET_H #define LOCK_SET_H #include #include #include #include #include #include #include #include namespace smo { // Forward declarations template class SerializedAsynchronousContinuation; class Qutex; /** * @brief LockSet - Manages a collection of locks for acquisition/release */ template class LockSet { public: /** EXPLANATION: * Tracks both the Qutex that must be acquired, as well as the parent * LockerAndInvoker that this LockSet has registered into that Qutex's * queue. */ typedef std::pair< std::reference_wrapper, typename LockerAndInvokerBase::List::iterator> LockUsageDesc; public: /** * @brief Constructor * @param parentContinuation Reference to the parent * SerializedAsynchronousContinuation * @param qutexes Vector of Qutex references that must be acquired */ LockSet( SerializedAsynchronousContinuation &parentContinuation, std::vector> qutexes = {}) : parentContinuation(parentContinuation), allLocksAcquired(false), registeredInQutexQueues(false) { /* Convert Qutex references to LockUsageDesc (iterators will be filled * in during registration) */ locks.reserve(qutexes.size()); for (auto& qutexRef : qutexes) { locks.emplace_back( qutexRef, typename LockerAndInvokerBase::List::iterator{}); } } /** * @brief Register the LockSet with all its Qutex locks * @param lockvoker The LockerAndInvoker to register with each Qutex * * EXPLANATION: * I'm not sure an unregisterFromQutexQueues() method is needed. * Why? Because if an async sequence can't acquire all locks, it will * simply never leave the qutexQ until it eventually does. The only other * time it will leave the qutexQ is when the program terminates. * * I'm not sure we'll actually cancal all in-flight async sequences -- * and especially not all those that aren't even in any io_service queues. * To whatever extent these objects get cleaned up, they'll probably be * cleaned up in the qutexQ's std::list destructor -- and that won't * execute any fancy cleanup logic. It'll just clear() out the list. */ template void registerInQutexQueues( const typename SerializedAsynchronousContinuation::template LockerAndInvoker &lockvoker); /** * @brief Try to acquire all locks in order; back off if acquisition fails * @param lockvoker The LockerAndInvoker attempting to acquire the locks * @param firstFailedQutex Output parameter to receive the first Qutex that * failed acquisition (can be nullptr) * @return true if all locks were acquired, false otherwise */ bool tryAcquireOrBackOff( LockerAndInvokerBase &lockvoker, Qutex *firstFailedQutex = nullptr); void unregisterFromQutexQueues(); /** * @brief Release all locks * @param lockvoker The LockerAndInvoker that owns the locks */ void release(LockerAndInvokerBase &lockvoker); const LockUsageDesc &getLockUsageDesc(const Qutex &criterionLock) const { for (auto& lockUsageDesc : locks) { if (&lockUsageDesc.first.get() == &criterionLock) { return lockUsageDesc; } } // Should never happen if the LockSet is properly constructed throw std::runtime_error( std::string(__func__) + ": Qutex not found in this LockSet"); } private: SerializedAsynchronousContinuation &parentContinuation; std::vector locks; bool allLocksAcquired, registeredInQutexQueues; }; } // namespace smo #endif // LOCK_SET_H