/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: RWLock.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "RWLock.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) { } RWLockImpl::~RWLockImpl() { this->mutex_.reset(); this->condition_.reset(); } bool RWLockImpl::Init() { this->mutex_ = ConditionMutexUnique(); if (!this->mutex_) { return false; } this->condition_ = ConditionVariableUnique(AuUnsafeRaiiToShared(mutex_)); if (!this->condition_) { return false; } return true; } bool RWLockImpl::LockRead(AuUInt64 timeout) { 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_++; return true; } bool RWLockImpl::LockWrite(AuUInt64 timeout) { AU_LOCK_GUARD(this->mutex_); this->writersPending_++; while (this->state_ != 0) { if (!this->condition_->WaitForSignal(timeout)) { this->writersPending_--; return false; } } this->reentrantWriteLockHandle_ = AuThreads::GetThreadId(); this->writersPending_--; this->state_ = -1; return true; } bool RWLockImpl::TryLockRead() { if (this->state_ == -1) { return this->reentrantWriteLockHandle_ == AuThreads::GetThreadId(); } AU_LOCK_GUARD(mutex_); this->state_++; return true; } bool RWLockImpl::TryLockWrite() { AU_LOCK_GUARD(this->mutex_); if (this->state_ > 0) { return false; } this->reentrantWriteLockHandle_ = AuThreads::GetThreadId(); this->state_ = -1; return true; } void RWLockImpl::UnlockRead() { AU_LOCK_GUARD(this->mutex_); if (this->state_ == -1) { SysAssertDbg(this->reentrantWriteLockHandle_ == AuThreads::GetThreadId()); return; } auto val = --this->state_; 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(); } 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->condition_->Broadcast(); this->state_ = 1; return true; } IWaitable *RWLockImpl::AsReadable() { return &this->read_; } IWaitable *RWLockImpl::AsWritable() { return &this->write_; } AUKN_SYM RWLock *RWLockNew() { auto ret = _new RWLockImpl(); if (!ret) { return nullptr; } if (!ret->Init()) { delete ret; return nullptr; } return ret; } AUKN_SYM void RWLockRelease(RWLock *waitable) { AuSafeDelete(waitable); } }