/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: AuRWLock.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "AuRWLock.hpp" namespace Aurora::Threading::Primitives { template void RWLockAccessView::Unlock() { if constexpr (bIsReadView) { this->parent_.UnlockRead(); } else { this->parent_.UnlockWrite(); } } template bool RWLockAccessView::Lock(AuUInt64 timeout) { if constexpr (bIsReadView) { return this->parent_.LockReadNS(AuMSToNS(timeout)); } else { return this->parent_.LockWriteNS(AuMSToNS(timeout)); } } template bool RWLockAccessView::LockNS(AuUInt64 timeout) { if constexpr (bIsReadView) { return this->parent_.LockReadNS(timeout); } else { return this->parent_.LockWriteNS(timeout); } } template bool RWLockAccessView::TryLock() { if constexpr (bIsReadView) { return this->parent_.TryLockRead(); } else { return this->parent_.TryLockWrite(); } } template RWLockImpl::RWLockImpl() : read_(*this), write_(*this), condition_(AuUnsafeRaiiToShared(&this->mutex_)) { } template RWLockImpl::~RWLockImpl() { } template bool RWLockImpl::Init() { return true; } template bool RWLockImpl::LockReadNS(AuUInt64 uTimeout) { if (this->state_ < 0 && this->reentrantWriteLockHandle_ == AuThreads::GetThreadId()) { 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; } uSecondTimeout = AuNSToMS(uSecondTimeout); if (!uSecondTimeout) { return false; } } if (!this->condition_.WaitForSignal(uSecondTimeout)) { return false; } if (this->writersPending_) { this->condition_.Broadcast(); continue; } } } } while (iCurState < 0 || AuAtomicCompareExchange(&this->state_, iCurState + 1, iCurState) != iCurState); return true; } template bool RWLockImpl::LockWriteNS(AuUInt64 uTimeout) { if constexpr (!bIsWriteRecursionAllowed) { if (TryLockWrite()) { return true; } } else { auto uOld = this->state_; if (uOld < 0) { if (this->reentrantWriteLockHandle_ == AuThreads::GetThreadId()) { AuAtomicSub(&this->state_, 1); return true; } } else if (uOld == 0) { if (AuAtomicCompareExchange(&this->state_, -1, uOld) == uOld) { this->reentrantWriteLockHandle_ = AuThreads::GetThreadId(); 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; } uSecondTimeout = AuNSToMS(uSecondTimeout); if (!uSecondTimeout) { this->writersPending_--; return false; } } if (!this->condition_.WaitForSignal(uSecondTimeout)) { this->writersPending_--; return false; } } if (AuAtomicCompareExchange(&this->state_, -1, 0) == 0) { this->reentrantWriteLockHandle_ = AuThreads::GetThreadId(); this->writersPending_--; return true; } } return true; } template bool RWLockImpl::TryLockRead() { auto iCurState = this->state_; if (iCurState == -1) { return this->reentrantWriteLockHandle_ == AuThreads::GetThreadId(); } return AuAtomicCompareExchange(&this->state_, iCurState + 1, iCurState) == iCurState; } template bool RWLockImpl::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_ == AuThreads::GetThreadId()) { AuAtomicSub(&this->state_, 1); return true; } else { AuThreading::ContextYield(); continue; } } } if (curVal != 0) { continue; } if (AuAtomicCompareExchange(&this->state_, -1, curVal) == curVal) { this->reentrantWriteLockHandle_ = AuThreads::GetThreadId(); return true; } } return false; } template void RWLockImpl::UnlockRead() { if (this->state_ < 0) { SysAssertDbg(this->reentrantWriteLockHandle_ == AuThreads::GetThreadId()); return; } AU_LOCK_GUARD(this->mutex_); auto val = AuAtomicSub(&this->state_, 1); if ((val == 1) && (this->bElevaterPending_)) { this->condition_.Signal(); } if (val == 0) { this->condition_.Signal(); } } template void RWLockImpl::UnlockWrite() { AU_LOCK_GUARD(this->mutex_); if constexpr (!bIsWriteRecursionAllowed) { this->reentrantWriteLockHandle_ = 0; this->state_ = 0; this->condition_.Broadcast(); } else { if (AuAtomicAdd(&this->state_, 1) == 0) { this->reentrantWriteLockHandle_ = 0; this->condition_.Broadcast(); } } } template bool RWLockImpl::UpgradeReadToWrite(AuUInt64 timeout) { AU_LOCK_GUARD(this->mutex_); while (this->state_ != 1) { this->bElevaterPending_ = true; if (!this->condition_.WaitForSignal(timeout)) { return false; } } this->bElevaterPending_ = false; this->reentrantWriteLockHandle_ = AuThreads::GetThreadId(); this->state_ = -1; return true; } template bool RWLockImpl::DowngradeWriteToRead() { AU_LOCK_GUARD(this->mutex_); if (this->state_ != -1) { return false; } this->state_ = 1; this->condition_.Broadcast(); return true; } template IWaitable *RWLockImpl::AsReadable() { return &this->read_; } template IWaitable *RWLockImpl::AsWritable() { return &this->write_; } AUKN_SYM IRWLock *RWLockNew() { auto pRwLock = _new RWLockImpl(); if (!pRwLock) { return nullptr; } if (!pRwLock->Init()) { delete pRwLock; return nullptr; } return pRwLock; } AUKN_SYM void RWLockRelease(IRWLock *pRwLock) { AuSafeDelete *>(pRwLock); } AUKN_SYM IRWLock *RWRenterableLockNew() { auto pRwLock = _new RWLockImpl(); if (!pRwLock) { return nullptr; } if (!pRwLock->Init()) { delete pRwLock; return nullptr; } return pRwLock; } AUKN_SYM void RWRenterableLockRelease(IRWLock *pRwLock) { AuSafeDelete *>(pRwLock); } }