454 lines
12 KiB
C++
454 lines
12 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: AuRWLock.cpp
|
|
Date: 2021-6-12
|
|
Author: Reece
|
|
***/
|
|
#include <Source/RuntimeInternal.hpp>
|
|
//#define RWLOCK_VIEW_HAS_PARENT
|
|
#include "AuRWLock.hpp"
|
|
#include "SMTYield.hpp"
|
|
|
|
namespace Aurora::Threading::Primitives
|
|
{
|
|
#if defined(RWLOCK_VIEW_HAS_PARENT)
|
|
#define ViewParent (&this->parent_)
|
|
#else
|
|
#define ViewParent ((T *)(((char *)this) - (bIsReadView ? RWLockImpl<true>::kOffsetOfRead : RWLockImpl<true>::kOffsetOfWrite)))
|
|
#endif
|
|
|
|
template<bool bIsReadView, typename T>
|
|
void RWLockAccessView<bIsReadView, T>::Unlock()
|
|
{
|
|
|
|
if constexpr (bIsReadView)
|
|
{
|
|
ViewParent->UnlockRead();
|
|
}
|
|
else
|
|
{
|
|
ViewParent->UnlockWrite();
|
|
}
|
|
}
|
|
|
|
template<bool bIsReadView, typename T>
|
|
bool RWLockAccessView<bIsReadView, T>::LockMS(AuUInt64 timeout)
|
|
{
|
|
if constexpr (bIsReadView)
|
|
{
|
|
return ViewParent->LockReadNS(AuMSToNS<AuUInt64>(timeout));
|
|
}
|
|
else
|
|
{
|
|
return ViewParent->LockWriteNS(AuMSToNS<AuUInt64>(timeout));
|
|
}
|
|
}
|
|
|
|
template<bool bIsReadView, typename T>
|
|
bool RWLockAccessView<bIsReadView, T>::LockNS(AuUInt64 timeout)
|
|
{
|
|
if constexpr (bIsReadView)
|
|
{
|
|
return ViewParent->LockReadNS(timeout);
|
|
}
|
|
else
|
|
{
|
|
return ViewParent->LockWriteNS(timeout);
|
|
}
|
|
}
|
|
|
|
template<bool bIsReadView, typename T>
|
|
bool RWLockAccessView<bIsReadView, T>::TryLock()
|
|
{
|
|
if constexpr (bIsReadView)
|
|
{
|
|
return ViewParent->TryLockRead();
|
|
}
|
|
else
|
|
{
|
|
return ViewParent->TryLockWrite();
|
|
}
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
RWLockImpl<bIsWriteRecursionAllowed>::RWLockImpl()
|
|
#if defined(RWLOCK_VIEW_HAS_PARENT)
|
|
: read_(*this),
|
|
write_(*this)
|
|
#endif
|
|
#if 0
|
|
, condition_(AuUnsafeRaiiToShared(&this->mutex_)),
|
|
conditionWriter_(AuUnsafeRaiiToShared(&this->mutex_))
|
|
#endif
|
|
{
|
|
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
RWLockImpl<bIsWriteRecursionAllowed>::~RWLockImpl()
|
|
{
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
ConditionVariableImpl &RWLockImpl<bIsWriteRecursionAllowed>::GetCondition()
|
|
{
|
|
#if !defined(AURWLOCK_NO_SIZE_OPTIMIZED_CONDVAR)
|
|
return *(ConditionVariableImpl *)this->conditionVariable_;
|
|
#else
|
|
return this->condition_;
|
|
#endif
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
ConditionVariableImpl &RWLockImpl<bIsWriteRecursionAllowed>::GetConditionWriter()
|
|
{
|
|
#if !defined(AURWLOCK_NO_SIZE_OPTIMIZED_CONDVAR)
|
|
return *(ConditionVariableImpl *)this->conditionVariableWriter_;
|
|
#else
|
|
return this->conditionWriter_;
|
|
#endif
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
bool RWLockImpl<bIsWriteRecursionAllowed>::LockReadNS(AuUInt64 uTimeout)
|
|
{
|
|
if (this->state_ < 0 &&
|
|
this->reentrantWriteLockHandle_ == GetThreadCookie())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
AuInt64 uEndTime = uTimeout ? AuTime::SteadyClockNS() + uTimeout : 0;
|
|
|
|
AuInt32 iCurState {};
|
|
do
|
|
{
|
|
iCurState = this->state_;
|
|
|
|
if (iCurState < 0)
|
|
{
|
|
AU_LOCK_GUARD(this->mutex_);
|
|
|
|
iCurState = this->state_;
|
|
if (iCurState < 0)
|
|
{
|
|
AuInt64 uSecondTimeout = 0;
|
|
|
|
if (uTimeout)
|
|
{
|
|
uSecondTimeout = uEndTime - AuTime::SteadyClockNS();
|
|
|
|
if (uSecondTimeout <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#if defined(AURWLOCK_NO_SIZE_OPTIMIZED_CONDVAR)
|
|
if (!this->GetCondition().WaitForSignalNS(uSecondTimeout))
|
|
#else
|
|
if (!this->GetCondition().WaitForSignalNsEx(AuUnsafeRaiiToShared(&this->mutex_), uSecondTimeout))
|
|
#endif
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (this->writersPending_)
|
|
{
|
|
this->GetConditionWriter().Broadcast();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (iCurState < 0 ||
|
|
AuAtomicCompareExchange(&this->state_, iCurState + 1, iCurState) != iCurState);
|
|
|
|
return true;
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
bool RWLockImpl<bIsWriteRecursionAllowed>::LockWriteNS(AuUInt64 uTimeout)
|
|
{
|
|
if constexpr (!bIsWriteRecursionAllowed)
|
|
{
|
|
if (TryLockWrite())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto uOld = this->state_;
|
|
if (uOld < 0)
|
|
{
|
|
if (this->reentrantWriteLockHandle_ == GetThreadCookie())
|
|
{
|
|
AuAtomicSub(&this->state_, 1);
|
|
return true;
|
|
}
|
|
}
|
|
else if (uOld == 0)
|
|
{
|
|
if (AuAtomicCompareExchange(&this->state_, -1, uOld) == uOld)
|
|
{
|
|
this->reentrantWriteLockHandle_ = GetThreadCookie();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
AU_LOCK_GUARD(this->mutex_);
|
|
this->writersPending_++;
|
|
|
|
AuInt64 uEndTime = uTimeout ? AuTime::SteadyClockNS() + uTimeout : 0;
|
|
|
|
while (true)
|
|
{
|
|
while (this->state_ != 0)
|
|
{
|
|
AuInt64 uSecondTimeout = 0;
|
|
|
|
if (uTimeout)
|
|
{
|
|
uSecondTimeout = uEndTime - AuTime::SteadyClockNS();
|
|
|
|
if (uSecondTimeout <= 0)
|
|
{
|
|
this->writersPending_--;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#if defined(AURWLOCK_NO_SIZE_OPTIMIZED_CONDVAR)
|
|
if (!this->GetConditionWriter().WaitForSignalNS(uSecondTimeout))
|
|
#else
|
|
if (!this->GetConditionWriter().WaitForSignalNsEx(AuUnsafeRaiiToShared(&this->mutex_), uSecondTimeout))
|
|
#endif
|
|
{
|
|
this->writersPending_--;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (AuAtomicCompareExchange(&this->state_, -1, 0) == 0)
|
|
{
|
|
this->reentrantWriteLockHandle_ = GetThreadCookie();
|
|
this->writersPending_--;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
this->GetConditionWriter().Broadcast();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
bool RWLockImpl<bIsWriteRecursionAllowed>::TryLockRead()
|
|
{
|
|
return DoTryIf([=]()
|
|
{
|
|
auto iCurState = this->state_;
|
|
|
|
if (iCurState < 0)
|
|
{
|
|
return this->reentrantWriteLockHandle_ == GetThreadCookie();
|
|
}
|
|
|
|
return AuAtomicCompareExchange(&this->state_, iCurState + 1, iCurState) == iCurState;
|
|
});
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
bool RWLockImpl<bIsWriteRecursionAllowed>::TryLockWrite()
|
|
{
|
|
for (AuUInt i = 0; i < 20; i++)
|
|
{
|
|
auto curVal = this->state_;
|
|
|
|
if (curVal < 0)
|
|
{
|
|
if constexpr (!bIsWriteRecursionAllowed)
|
|
{
|
|
AuThreading::ContextYield();
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (this->reentrantWriteLockHandle_ == GetThreadCookie())
|
|
{
|
|
AuAtomicSub(&this->state_, 1);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
AuThreading::ContextYield();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (curVal != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (AuAtomicCompareExchange(&this->state_, -1, curVal) == curVal)
|
|
{
|
|
this->reentrantWriteLockHandle_ = GetThreadCookie();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
void RWLockImpl<bIsWriteRecursionAllowed>::UnlockRead()
|
|
{
|
|
if (this->state_ < 0)
|
|
{
|
|
SysAssertDbg(this->reentrantWriteLockHandle_ == GetThreadCookie());
|
|
return;
|
|
}
|
|
|
|
AuInt32 val {};
|
|
bool bElevation {};
|
|
|
|
{
|
|
AU_LOCK_GUARD(this->mutex_);
|
|
val = AuAtomicSub(&this->state_, 1);
|
|
bElevation = this->bElevaterPending_;
|
|
}
|
|
|
|
if ((val == 0) && (bElevation))
|
|
{
|
|
this->GetConditionWriter().Signal();
|
|
}
|
|
|
|
if (val == 0)
|
|
{
|
|
this->GetCondition().Signal();
|
|
this->GetConditionWriter().Signal();
|
|
}
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
void RWLockImpl<bIsWriteRecursionAllowed>::UnlockWrite()
|
|
{
|
|
|
|
if constexpr (!bIsWriteRecursionAllowed)
|
|
{
|
|
{
|
|
AU_LOCK_GUARD(this->mutex_);
|
|
this->reentrantWriteLockHandle_ = 0;
|
|
this->state_ = 0;
|
|
}
|
|
|
|
this->GetConditionWriter().Broadcast();
|
|
this->GetCondition().Broadcast();
|
|
}
|
|
else
|
|
{
|
|
AuInt32 val {};
|
|
|
|
{
|
|
AU_LOCK_GUARD(this->mutex_);
|
|
val = AuAtomicAdd(&this->state_, 1);
|
|
this->reentrantWriteLockHandle_ = 0;
|
|
}
|
|
|
|
if (val == 0)
|
|
{
|
|
this->GetConditionWriter().Broadcast();
|
|
this->GetCondition().Broadcast();
|
|
}
|
|
}
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
bool RWLockImpl<bIsWriteRecursionAllowed>::UpgradeReadToWrite(AuUInt64 timeout)
|
|
{
|
|
AU_LOCK_GUARD(this->mutex_);
|
|
|
|
while (this->state_ != 1)
|
|
{
|
|
this->bElevaterPending_ = true;
|
|
|
|
if (!this->GetConditionWriter().WaitForSignal(timeout))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
this->bElevaterPending_ = false;
|
|
this->reentrantWriteLockHandle_ = GetThreadCookie();
|
|
this->state_ = -1;
|
|
return true;
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
bool RWLockImpl<bIsWriteRecursionAllowed>::DowngradeWriteToRead()
|
|
{
|
|
AU_LOCK_GUARD(this->mutex_);
|
|
|
|
if (this->state_ != -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
this->state_ = 1;
|
|
this->GetCondition().Broadcast();
|
|
return true;
|
|
}
|
|
|
|
template<bool bIsWriteRecursionAllowed>
|
|
IWaitable *RWLockImpl<bIsWriteRecursionAllowed>::AsReadable()
|
|
{
|
|
return &this->read_;
|
|
}
|
|
template<bool bIsWriteRecursionAllowed>
|
|
IWaitable *RWLockImpl<bIsWriteRecursionAllowed>::AsWritable()
|
|
{
|
|
return &this->write_;
|
|
}
|
|
|
|
AUKN_SYM IRWLock *RWLockNew()
|
|
{
|
|
auto pRwLock = _new RWLockImpl<false>();
|
|
if (!pRwLock)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return pRwLock;
|
|
}
|
|
|
|
AUKN_SYM void RWLockRelease(IRWLock *pRwLock)
|
|
{
|
|
AuSafeDelete<RWLockImpl<false> *>(pRwLock);
|
|
}
|
|
|
|
AUKN_SYM IRWLock *RWRenterableLockNew()
|
|
{
|
|
auto pRwLock = _new RWLockImpl<true>();
|
|
if (!pRwLock)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return pRwLock;
|
|
}
|
|
|
|
AUKN_SYM void RWRenterableLockRelease(IRWLock *pRwLock)
|
|
{
|
|
AuSafeDelete<RWLockImpl<true> *>(pRwLock);
|
|
}
|
|
|
|
AUROXTL_INTERFACE_SOO_SRC_EX(AURORA_SYMBOL_EXPORT, RWRenterableLock, RWLockImpl<true>)
|
|
AUROXTL_INTERFACE_SOO_SRC_EX(AURORA_SYMBOL_EXPORT, RWLock, RWLockImpl<false>)
|
|
} |