/*** 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 (isread) { this->parent_.UnlockRead(); } else { this->parent_.UnlockWrite(); } } template bool RWLockAccessView::Lock(AuUInt64 timeout) { if constexpr (isread) { return this->parent_.LockRead(timeout); } else { return this->parent_.LockWrite(timeout); } } template bool RWLockAccessView::TryLock() { if constexpr (isread) { return this->parent_.TryLockRead(); } else { return this->parent_.TryLockWrite(); } } RWLockImpl::RWLockImpl() : read_(*this), write_(*this), condition_(AuUnsafeRaiiToShared(&this->mutex_)) { } RWLockImpl::~RWLockImpl() { } bool RWLockImpl::Init() { return true; } bool RWLockImpl::LockRead(AuUInt64 timeout) { #if 0 AU_LOCK_GUARD(mutex_); if (this->state_ == -1 && this->reentrantWriteLockHandle_ == AuThreads::GetThreadId()) { return true; } while (this->state_ < 0 /* || this->writersPending_*/) { if (!this->condition_->WaitForSignal(timeout)) { return false; } if (this->writersPending_) { // Meh, let's just DoS the cpu with the readers until we find the writer for low unlock-from-final-read latency // The writer should be prio, and it's already having a terrible day by stalling. Let's not play bounce the signal through contexts (we were likely a signal, not a broadcast) this->condition_->Broadcast(); continue; } } this->state_++; #else if (this->state_ == -1 && this->reentrantWriteLockHandle_ == AuThreads::GetThreadId()) { return true; } AuInt64 uEndTime = timeout ? AuTime::SteadyClockNS() + AuMSToNS(timeout) : 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 (timeout) { 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 == -1 || AuAtomicCompareExchange((AuInt32*)&this->state_, iCurState + 1, iCurState) != iCurState); #endif return true; } bool RWLockImpl::LockWrite(AuUInt64 timeout) { if (AuAtomicCompareExchange((AuInt32 *)&this->state_, -1, 0) == 0) { this->reentrantWriteLockHandle_ = AuThreads::GetThreadId(); return true; } AU_LOCK_GUARD(this->mutex_); this->writersPending_++; AuInt64 uEndTime = timeout ? AuTime::SteadyClockNS() + AuMSToNS(timeout) : 0; while (true) { while (this->state_ != 0) { AuInt64 uSecondTimeout = 0; if (timeout) { 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((AuInt32 *)&this->state_, -1, 0) == 0) { this->reentrantWriteLockHandle_ = AuThreads::GetThreadId(); this->writersPending_--; return true; } } return true; } bool RWLockImpl::TryLockRead() { auto iCurState = this->state_; if (iCurState == -1) { return this->reentrantWriteLockHandle_ == AuThreads::GetThreadId(); } return AuAtomicCompareExchange((AuInt32 *)&this->state_, iCurState + 1, iCurState) == iCurState; } bool RWLockImpl::TryLockWrite() { //AU_LOCK_GUARD(this->mutex_); for (AuUInt i = 0; i < 20; i++) { auto curVal = this->state_; if (curVal == -1) { AuThreading::ContextYield(); continue; } if (curVal != 0) { continue; } if (AuAtomicCompareExchange((AuInt32 *)&this->state_, -1, curVal) == curVal) { this->reentrantWriteLockHandle_ = AuThreads::GetThreadId(); return true; } } return false; } void RWLockImpl::UnlockRead() { AU_LOCK_GUARD(this->mutex_); if (this->state_ == -1) { SysAssertDbg(this->reentrantWriteLockHandle_ == AuThreads::GetThreadId()); return; } auto val = AuAtomicSub((AuInt32*)&this->state_, 1); if ((val == 1) && (this->bElevaterPending_)) { this->condition_.Signal(); } if (val == 0) { this->condition_.Signal(); } } void RWLockImpl::UnlockWrite() { AU_LOCK_GUARD(this->mutex_); this->state_ = 0; this->condition_.Broadcast(); this->reentrantWriteLockHandle_ = 0; } 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; } bool RWLockImpl::DowngradeWriteToRead() { AU_LOCK_GUARD(this->mutex_); if (this->state_ != -1) { return false; } this->state_ = 1; this->condition_.Broadcast(); return true; } IWaitable *RWLockImpl::AsReadable() { return &this->read_; } 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); } }