Files
libspinscale/include/spinscale/co/coQutex.h
T

218 lines
5.1 KiB
C++
Raw Normal View History

2026-05-17 16:52:04 -04:00
#ifndef CO_QUTEX_H
#define CO_QUTEX_H
#include <config.h>
#include <cassert>
#include <coroutine>
#include <deque>
#include <stdexcept>
2026-05-24 16:10:30 -04:00
#include <string>
2026-05-17 16:52:04 -04:00
#include <type_traits>
#ifdef CONFIG_LIBSSCL_DEBUG_CO
#include <iostream>
#include <thread>
#endif
2026-05-30 11:57:57 -04:00
#include <boost/asio/io_context.hpp>
2026-05-17 16:52:04 -04:00
#include <boost/asio/post.hpp>
#include <spinscale/componentThread.h>
#include <spinscale/co/promiseChainWalker.h>
#include <spinscale/spinLock.h>
namespace sscl::co {
class CoQutex
{
public:
class ReleaseHandle;
CoQutex() noexcept = default;
2026-05-24 16:10:30 -04:00
CoQutex([[maybe_unused]] const std::string &_name) noexcept
:
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
name(_name),
#endif
isOwned(false)
{}
2026-05-17 16:52:04 -04:00
CoQutex(const CoQutex &) = delete;
CoQutex(CoQutex &&) noexcept = delete;
CoQutex &operator=(const CoQutex &) = delete;
CoQutex &operator=(CoQutex &&) noexcept = delete;
~CoQutex() = default;
struct AcquireInvocationAndSuspensionPolicy
{
AcquireInvocationAndSuspensionPolicy(CoQutex &_coQutex) noexcept
: coQutex(_coQutex)
{}
~AcquireInvocationAndSuspensionPolicy() noexcept = default;
struct WaitingCoroutine
{
WaitingCoroutine(
std::coroutine_handle<void> _callerSchedHandle,
2026-05-30 11:57:57 -04:00
boost::asio::io_context &_callerIoContext,
2026-05-17 16:52:04 -04:00
PromiseChainLink &_waitingPromise) noexcept
: callerSchedHandle(_callerSchedHandle),
callerIoContext(_callerIoContext),
waitingPromise(_waitingPromise)
{}
std::coroutine_handle<void> callerSchedHandle;
2026-05-30 11:57:57 -04:00
boost::asio::io_context &callerIoContext;
2026-05-17 16:52:04 -04:00
PromiseChainLink &waitingPromise;
};
bool await_ready() noexcept { return false; }
template <typename Promise>
bool await_suspend(std::coroutine_handle<Promise> callerSchedHandle)
{
static_assert(
std::is_base_of_v<PromiseChainLink, Promise>,
"CoQutex acquire requires a promise type derived from PromiseChainLink");
acquirerChainLink = &callerSchedHandle.promise();
walkCallerPromiseChainFrom(
static_cast<const PromiseChainLink &>(callerSchedHandle.promise()),
[this](const PromiseChainLink &link)
{
#ifdef CONFIG_LIBSSCL_DEBUG_CO
std::cout << __func__ << ": " << std::this_thread::get_id() << " Walking caller promise chain.\n";
#endif
if (link.holdsAcquiredLock(coQutex)) {
2026-05-24 16:10:30 -04:00
std::string message =
"Deadlock detected: CoQutex re-acquire on caller promise chain";
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
message += " (" + coQutex.name + ")";
#endif
message += ".";
throw std::runtime_error(message);
2026-05-17 16:52:04 -04:00
}
});
sscl::SpinLock::Guard guard(coQutex.spinLock);
if (!coQutex.isOwned) {
coQutex.isOwned = true;
return false;
}
coQutex.waitingCoroutines.emplace_back(
std::coroutine_handle<void>::from_address(callerSchedHandle.address()),
2026-05-30 11:57:57 -04:00
sscl::ComponentThread::getSelf()->getIoContext(),
2026-05-17 16:52:04 -04:00
*acquirerChainLink);
return true;
}
ReleaseHandle
// [[nodiscard("store co_await result; lock is held until ReleaseHandle is released")]]
await_resume() noexcept;
CoQutex &coQutex;
private:
PromiseChainLink *acquirerChainLink = nullptr;
};
AcquireInvocationAndSuspensionPolicy
// [[nodiscard("store co_await result; lock is held until ReleaseHandle is released")]]
getAcquireInvocationAndSuspensionPolicy() noexcept
{
return AcquireInvocationAndSuspensionPolicy(*this);
}
private:
friend class ReleaseHandle;
void release() noexcept
{
sscl::SpinLock::Guard guard(spinLock);
assert(isOwned);
if (waitingCoroutines.empty()) {
isOwned = false;
return;
}
auto &frontWaitingCoroutine = waitingCoroutines.front();
boost::asio::post(
frontWaitingCoroutine.callerIoContext,
frontWaitingCoroutine.callerSchedHandle);
waitingCoroutines.pop_front();
}
2026-05-24 16:10:30 -04:00
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
std::string name;
#endif
2026-05-17 16:52:04 -04:00
sscl::SpinLock spinLock;
bool isOwned = false;
std::deque<AcquireInvocationAndSuspensionPolicy::WaitingCoroutine> waitingCoroutines;
};
//[[nodiscard("store co_await result; lock is held until ReleaseHandle is released")]]
class CoQutex::ReleaseHandle
{
public:
ReleaseHandle(PromiseChainLink &promiseChainLinkIn, CoQutex &coQutexIn) noexcept
: promiseChainLink(promiseChainLinkIn),
coQutex(coQutexIn)
{}
ReleaseHandle(const ReleaseHandle &) = delete;
ReleaseHandle &operator=(const ReleaseHandle &) = delete;
ReleaseHandle(ReleaseHandle &&other) noexcept
: promiseChainLink(other.promiseChainLink),
coQutex(other.coQutex),
armed(other.armed)
{
other.armed = false;
}
ReleaseHandle &operator=(ReleaseHandle &&other) noexcept = delete;
~ReleaseHandle() noexcept
{
if (armed)
{ release(); }
}
void release() noexcept
{
if (!armed)
{ return; }
armed = false;
promiseChainLink.removeAcquiredLock(coQutex);
coQutex.release();
}
void operator()() noexcept
{
release();
}
private:
PromiseChainLink &promiseChainLink;
CoQutex &coQutex;
bool armed = true;
};
inline CoQutex::ReleaseHandle
// [[nodiscard("store co_await result; lock is held until ReleaseHandle is released")]]
CoQutex::AcquireInvocationAndSuspensionPolicy::await_resume() noexcept
{
assert(acquirerChainLink != nullptr);
acquirerChainLink->addAcquiredLock(coQutex);
return CoQutex::ReleaseHandle(*acquirerChainLink, coQutex);
}
} // namespace sscl::co
#endif // CO_QUTEX_H