Qutexes: Implement them and supporting classes

Implements: LockSet, SerializedAsynchronousContinuation,
	LockerAndInvoker, LockerAndInvokerBase, Qutex.

Very big leap in functionality here. See qutexes.md for
an explanation of what we've done.
This commit is contained in:
2025-09-20 18:16:46 -04:00
parent f05c465d61
commit 32179eee5e
10 changed files with 825 additions and 211 deletions
+78 -48
View File
@@ -5,82 +5,112 @@
#include <functional>
#include <atomic>
#include <stdexcept>
#include <utility>
#include <memory>
#include <spinLock.h>
#include <lockerAndInvokerBase.h>
namespace smo {
// Forward declarations
template <class OriginalCbFnT>
class SerializedAsynchronousContinuation;
class Qutex;
/**
* @brief LockSet - Manages a collection of locks for acquisition/release
*/
template <class OriginalCbFnT>
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<Qutex>,
typename LockerAndInvokerBase::List::iterator> LockUsageDesc;
public:
/**
* @brief Constructor
* @param requiredLocks Vector of lock references that must be acquired
* @param parentContinuation Reference to the parent
* SerializedAsynchronousContinuation
* @param qutexes Vector of Qutex references that must be acquired
*/
LockSet(std::vector<std::reference_wrapper<SpinLock>> requiredLocks = {})
: requiredLocks(std::move(requiredLocks)), allLocksAcquired(false)
{}
LockSet(
SerializedAsynchronousContinuation<OriginalCbFnT> &parentContinuation,
std::vector<std::reference_wrapper<Qutex>> 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 Try to acquire all locks in order
* @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 <class InvocationTargetT>
void registerInQutexQueues(
const typename SerializedAsynchronousContinuation<OriginalCbFnT>::template LockerAndInvoker<InvocationTargetT> &lockvoker);
/**
* @brief Try to acquire all locks in order; back off if acquisition fails
* @param lockvoker The LockerAndInvoker attempting to acquire the locks
* @return true if all locks were acquired, false otherwise
*/
bool tryAcquire()
{
if (allLocksAcquired)
{
throw std::runtime_error(
std::string(__func__) +
": LockSet::tryAcquire() called but allLocksAcquired is "
"already true");
}
// Try to acquire all required locks
int nAcquired = 0;
for (auto& lockRef : requiredLocks)
{
if (!lockRef.get().tryAcquire()) { break; }
nAcquired++;
}
if (nAcquired < static_cast<int>(requiredLocks.size()))
{
// Release any locks we managed to acquire
for (int i = 0; i < nAcquired; i++) {
requiredLocks[i].get().release();
}
allLocksAcquired = false;
return false;
}
allLocksAcquired = true;
return true;
}
bool tryAcquireOrBackOff(LockerAndInvokerBase &lockvoker);
void unregisterFromQutexQueues();
/**
* @brief Release all locks
* @param lockvoker The LockerAndInvoker that owns the locks
*/
void release()
void release(LockerAndInvokerBase &lockvoker);
const LockUsageDesc &getLockUsageDesc(const Qutex &criterionLock) const
{
if (!allLocksAcquired)
for (auto& lockUsageDesc : locks)
{
throw std::runtime_error(
std::string(__func__) +
": LockSet::release() called but allLocksAcquired is false");
if (&lockUsageDesc.first.get() == &criterionLock) {
return lockUsageDesc;
}
}
for (auto& lockRef : requiredLocks) {
lockRef.get().release();
}
allLocksAcquired = false;
// Should never happen if the LockSet is properly constructed
throw std::runtime_error(
std::string(__func__) +
": Qutex not found in this LockSet");
}
private:
std::vector<std::reference_wrapper<SpinLock>> requiredLocks;
std::atomic<bool> allLocksAcquired;
SerializedAsynchronousContinuation<OriginalCbFnT> &parentContinuation;
std::vector<LockUsageDesc> locks;
bool allLocksAcquired, registeredInQutexQueues;
};
} // namespace smo