From 35761edaec5d75287efa3f2c3c90603da83f72bf Mon Sep 17 00:00:00 2001 From: J Reece Wilson Date: Sun, 8 Sep 2024 07:26:23 +0100 Subject: [PATCH] [+] AuIO::Loop::NewLSTimerHighResolution [*] ILSTimer::UpdateTime -> ILSTimer::UpdateTimeWall [*] ILSTimer::UpdateTimeNs -> ILSTimer::UpdateTimeWallNs [+] ILSTimer::UpdateTimeSteady [+] ILSTimer::UpdateTimeSteadyNs --- Include/Aurora/IO/Loop/Loop.hpp | 10 +- Include/Aurora/RuntimeConfig.hpp | 21 +- Source/Async/AuSchedular.cpp | 15 +- Source/Async/AuSchedular.hpp | 1 + Source/IO/AuIOWaitableIOTimer.cpp | 11 +- Source/IO/Loop/LSTimer.Linux.cpp | 60 ++-- Source/IO/Loop/LSTimer.Linux.hpp | 7 +- Source/IO/Loop/LSTimer.NT.cpp | 25 +- Source/IO/Loop/LSTimer.NT.hpp | 7 +- Source/IO/Loop/LSTimer.cpp | 52 ++++ Source/IO/Loop/LSTimerNoKernelScheduler.cpp | 290 ++++++++++++++++++++ Source/IO/Loop/LSTimerNoKernelScheduler.hpp | 68 +++++ 12 files changed, 529 insertions(+), 38 deletions(-) create mode 100755 Source/IO/Loop/LSTimer.cpp create mode 100755 Source/IO/Loop/LSTimerNoKernelScheduler.cpp create mode 100755 Source/IO/Loop/LSTimerNoKernelScheduler.hpp diff --git a/Include/Aurora/IO/Loop/Loop.hpp b/Include/Aurora/IO/Loop/Loop.hpp index 244e0a1e..89ba7232 100644 --- a/Include/Aurora/IO/Loop/Loop.hpp +++ b/Include/Aurora/IO/Loop/Loop.hpp @@ -65,9 +65,12 @@ namespace Aurora::IO::Loop { /* Warning: IO timers use wall time (CurrentClock[M/NS), not SteadyClock[M/N]S()). Use auasync timers for steady-clock timing and tick timing information. */ + // Update: not anymore - virtual void UpdateTime(AuUInt64 absTimeMs) = 0; - virtual void UpdateTimeNs(AuUInt64 absTimeNs) = 0; + virtual void UpdateTimeWall(AuUInt64 absTimeMs) = 0; + virtual void UpdateTimeWallNs(AuUInt64 absTimeNs) = 0; + virtual void UpdateTimeSteady(AuUInt64 absTimeMs) = 0; + virtual void UpdateTimeSteadyNs(AuUInt64 absTimeNs) = 0; virtual void UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero = 0, AuUInt32 maxIterationsOrZero = 0) = 0; virtual void UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero = 0, AuUInt32 maxIterationsOrZero = 0) = 0; virtual void Stop() = 0; @@ -78,7 +81,8 @@ namespace Aurora::IO::Loop virtual void *GetLastSignalInfo() = 0; }; - AUKN_SYM AuSPtr NewLSTimer(AuUInt64 absStartTimeMs /*CurrentClockMS()*/, AuUInt32 reschedStepMsOrZero = 0, AuUInt32 maxIterationsOrZero = 0, bool bSingleshot = false /*cannot be changed*/); // warn: no IPC counterpart + AUKN_SYM AuSPtr NewLSTimer(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero = 0, AuUInt32 dwMaxIterationsOrZero = 0, bool bSingleshot = false /*cannot be changed*/, bool bIsStartTimeSteady = false); // warn: no IPC counterpart + AUKN_SYM AuSPtr NewLSTimerHighResolution(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero = 0, AuUInt32 dwMaxIterationsOrZero = 0, bool bSingleshot = false /*cannot be changed*/, bool bIsStartTimeSteady = false); // warn: no IPC counterpart AUKN_SYM AuSPtr NewLSMutex(); AUKN_SYM AuSPtr NewLSMutexSlow(); // interop-ready (usable with DbgLoopSourceToReadFd) AUKN_SYM AuSPtr NewLSEvent(bool bTriggered = false, bool bAtomicRelease = true, bool bPermitMultipleTriggers = false); diff --git a/Include/Aurora/RuntimeConfig.hpp b/Include/Aurora/RuntimeConfig.hpp index e2c3c341..1d59b177 100644 --- a/Include/Aurora/RuntimeConfig.hpp +++ b/Include/Aurora/RuntimeConfig.hpp @@ -260,8 +260,25 @@ namespace Aurora struct IOConfig { - AuUInt32 uProtocolStackDefaultBufferSize { 64 * 1024 }; - bool bIsVeryLargeIOApplication { false }; + AuUInt32 uProtocolStackDefaultBufferSize { 64 * 1024 }; + bool bIsVeryLargeIOApplication { false }; + + // On Win32, NewLSTimer has very bad resolution. + // On linux, timerfd is good enough. + // On other POSIX platforms, its best to keep timers emulated in process. + // By default, you have to opt into a higher res in-process, if required by the platform. + // This does not bypass IO yielding or timeouts; we simply use our own semaphore and scheduler instead of the kernels. + // It only takes 8k ns to 60k ns depending on the platform to wake a thread, and we can hit AuAsync scheduler without too much error, we can just do this instead of relying on historically shitty IO primitives. + // Assiging this to true, on any platform, will bypass the kernels timer ticker. + bool bForceAltOSTimerPrimitives { false }; + + // High resolution timers will be emulated in process, if this is assigned false, and we're on a platform with good IO timers + // For niche POSIX targets and Windows, this value will not be respected. + // For Linux, this value can be used to experience/test/benchmark Win32-like in-process scheduling. + bool bTrustOSTimerPrimitiveIfKnownGood { true }; + + // + bool bUseHighResTimerUnderIOWaitableTimer { false }; }; struct Win32Config diff --git a/Source/Async/AuSchedular.cpp b/Source/Async/AuSchedular.cpp index 8a056cc8..9c1fd521 100644 --- a/Source/Async/AuSchedular.cpp +++ b/Source/Async/AuSchedular.cpp @@ -182,7 +182,15 @@ namespace Aurora::Async { try { - AuStaticCast(entry.pool)->Run(entry.target, entry.runnable, false); + if (entry.pool) + { + AuStaticCast(entry.pool)->Run(entry.target, entry.runnable, false); + } + else + { + // IO timer hack! + entry.runnable->RunAsync(); + } } catch (...) { @@ -280,7 +288,10 @@ namespace Aurora::Async { AU_LOCK_GUARD(gSchedLock); AU_DEBUG_MEMCRUNCH; - AuAtomicAdd(&pool->uAtomicCounter, 1u); + if (pool) + { + AuAtomicAdd(&pool->uAtomicCounter, 1u); + } SchedNextTime(ns); return AuTryInsert(gOrderedEntries, AuConstReference(ns), diff --git a/Source/Async/AuSchedular.hpp b/Source/Async/AuSchedular.hpp index c1f4f78c..c4e49287 100644 --- a/Source/Async/AuSchedular.hpp +++ b/Source/Async/AuSchedular.hpp @@ -14,6 +14,7 @@ namespace Aurora::Async void InitSched(); void DeinitSched(); void StartSched(); + void StartSched2(); bool Schedule(AuUInt64 ns, IThreadPoolInternal *pool, WorkerId_t target, AuSPtr runnable); void TerminateSceduledTasks(IThreadPoolInternal *pool, WorkerId_t target); diff --git a/Source/IO/AuIOWaitableIOTimer.cpp b/Source/IO/AuIOWaitableIOTimer.cpp index 894be31e..ca82d1b5 100644 --- a/Source/IO/AuIOWaitableIOTimer.cpp +++ b/Source/IO/AuIOWaitableIOTimer.cpp @@ -76,7 +76,7 @@ namespace Aurora::IO AuUInt64 IOWaitableIOTimer::SetTargetTimeAbs(AuUInt64 ns) { auto old = AuExchange(this->targetTimeAbsNs, ns); - this->source->UpdateTimeNs(ns); + this->source->UpdateTimeSteadyNs(ns); return old; } @@ -89,7 +89,14 @@ namespace Aurora::IO return {}; } - timer->source = AuLoop::NewLSTimer(0); + if (gRuntimeConfig.ioConfig.bUseHighResTimerUnderIOWaitableTimer) + { + timer->source = AuLoop::NewLSTimerHighResolution(0); + } + else + { + timer->source = AuLoop::NewLSTimer(0); + } if (!timer->IsValid()) { diff --git a/Source/IO/Loop/LSTimer.Linux.cpp b/Source/IO/Loop/LSTimer.Linux.cpp index c1a3eb10..af3247ca 100644 --- a/Source/IO/Loop/LSTimer.Linux.cpp +++ b/Source/IO/Loop/LSTimer.Linux.cpp @@ -31,34 +31,47 @@ namespace Aurora::IO::Loop } } - void LSTimer::UpdateTime(AuUInt64 absTimeMs) - { - this->targetTime_ = AuMSToNS(absTimeMs); - UpdateTimeInternal(this->targetTime_); - } - - void LSTimer::UpdateTimeNs(AuUInt64 absTimeNs) - { - this->targetTime_ = absTimeNs; - UpdateTimeInternal(this->targetTime_); - } - void LSTimer::UpdateTimeInternal(AuUInt64 absTimeNs) { - this->targetTime_ = absTimeNs; + this->targetTime_ = absTimeNs - AuTime::CurrentClockNS() + AuTime::SteadyClockNS(); + UpdateTimeInternalSteady(this->targetTime_); + } + void LSTimer::UpdateTimeInternalSteady(AuUInt64 absTimeNs) + { + this->targetTime_ = absTimeNs; itimerspec spec {}; - AuTime::auabsns2ts(&spec.it_value, this->targetTime_); + AuTime::ns2ts(&spec.it_value, this->targetTime_); AuTime::ns2ts(&spec.it_interval, this->reschedStepNsOrZero_); SysAssert(::timerfd_settime(this->handle, TFD_TIMER_ABSTIME, &spec, nullptr) == 0); } + void LSTimer::UpdateTimeWall(AuUInt64 absTimeMs) + { + this->UpdateTimeInternal(AuMSToNS(absTimeMs)); + } + + void LSTimer::UpdateTimeWallNs(AuUInt64 absTimeNs) + { + this->UpdateTimeInternal(absTimeNs); + } + + void LSTimer::UpdateTimeSteady(AuUInt64 absTimeMs) + { + this->UpdateTimeInternalSteady(AuMSToNS(absTimeMs)); + } + + void LSTimer::UpdateTimeSteadyNs(AuUInt64 absTimeNs) + { + this->UpdateTimeInternalSteady(absTimeNs); + } + void LSTimer::UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) { this->reschedStepNsOrZero_ = AuMSToNS(reschedStepMsOrZero); this->maxIterationsOrZero_ = maxIterationsOrZero; this->count_ = 0; - this->UpdateTimeInternal(this->targetTime_); + this->UpdateTimeInternalSteady(this->targetTime_); } void LSTimer::UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) @@ -66,7 +79,7 @@ namespace Aurora::IO::Loop this->reschedStepNsOrZero_ = reschedStepNsOrZero; this->maxIterationsOrZero_ = maxIterationsOrZero; this->count_ = 0; - this->UpdateTimeInternal(this->targetTime_); + this->UpdateTimeInternalSteady(this->targetTime_); } bool LSTimer::OnTrigger(AuUInt handle) @@ -121,15 +134,15 @@ namespace Aurora::IO::Loop if (ok && !this->reschedStepNsOrZero_ && !bSingleshot) { // Ensure non-step timers remain signaled despite the read above us caneling them - this->UpdateTimeInternal(this->targetTime_); + this->UpdateTimeInternalSteady(this->targetTime_); } return ok; } - AUKN_SYM AuSPtr NewLSTimer(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot) + AuSPtr NewLSTimerOS(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady) { - int fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC); + int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (fd == -1) { SysPushErrorIO(); @@ -146,7 +159,14 @@ namespace Aurora::IO::Loop if (qwAbsStartTimeMs) { - pTimer->UpdateTime(qwAbsStartTimeMs); + if (bIsStartTimeSteady) + { + pTimer->UpdateTimeSteady(qwAbsStartTimeMs); + } + else + { + pTimer->UpdateTimeWall(qwAbsStartTimeMs); + } } return pTimer; diff --git a/Source/IO/Loop/LSTimer.Linux.hpp b/Source/IO/Loop/LSTimer.Linux.hpp index 70fe5625..9ec70f6e 100644 --- a/Source/IO/Loop/LSTimer.Linux.hpp +++ b/Source/IO/Loop/LSTimer.Linux.hpp @@ -17,8 +17,10 @@ namespace Aurora::IO::Loop PROXY_LOOP_LOOPSOURCE_EXT_WAIT_APIS_; - virtual void UpdateTime(AuUInt64 absTimeMs) override; - virtual void UpdateTimeNs(AuUInt64 absTimeNs) override; + virtual void UpdateTimeWall(AuUInt64 absTimeMs) override; + virtual void UpdateTimeWallNs(AuUInt64 absTimeNs) override; + virtual void UpdateTimeSteady(AuUInt64 absTimeMs) override; + virtual void UpdateTimeSteadyNs(AuUInt64 absTimeNs) override; virtual void UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) override; virtual void UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) override; virtual void Stop() override; @@ -31,6 +33,7 @@ namespace Aurora::IO::Loop virtual ELoopSource GetType() override; void UpdateTimeInternal(AuUInt64 absTimeMs); + void UpdateTimeInternalSteady(AuUInt64 absTimeMs); private: AuUInt32 maxIterationsOrZero_ {}; diff --git a/Source/IO/Loop/LSTimer.NT.cpp b/Source/IO/Loop/LSTimer.NT.cpp index 6e7c5a6a..9e54ea1a 100644 --- a/Source/IO/Loop/LSTimer.NT.cpp +++ b/Source/IO/Loop/LSTimer.NT.cpp @@ -27,16 +27,26 @@ namespace Aurora::IO::Loop this->handle = kInvalidHandle; } - void LSTimer::UpdateTime(AuUInt64 absTimeMs) + void LSTimer::UpdateTimeWall(AuUInt64 absTimeMs) { UpdateTimeInternalNTWall(AuTime::ConvertTimestamp(absTimeMs)); } - void LSTimer::UpdateTimeNs(AuUInt64 absTimeNs) + void LSTimer::UpdateTimeWallNs(AuUInt64 absTimeNs) { UpdateTimeInternalNTWall(AuTime::ConvertTimestampNs(absTimeNs)); } + void LSTimer::UpdateTimeSteady(AuUInt64 absTimeMs) + { + UpdateTimeSteadyNs(AuMSToNS(absTimeMs)); + } + + void LSTimer::UpdateTimeSteadyNs(AuUInt64 absTimeNs) + { + UpdateTimeWallNs(absTimeNs - AuTime::SteadyClockNS() + AuTime::CurrentClockNS()); + } + void LSTimer::UpdateTimeInternalNTWall(AuUInt64 absTimeNs) { LARGE_INTEGER i; @@ -120,7 +130,7 @@ namespace Aurora::IO::Loop return ELoopSource::eSourceTimer; } - AUKN_SYM AuSPtr NewLSTimer(AuUInt64 absStartTimeMs, AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot) + AuSPtr NewLSTimerOS(AuUInt64 absStartTimeMs, AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady) { // https://docs.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-setthreadpoolwait // TODO: Is that any better with an event? @@ -142,7 +152,14 @@ namespace Aurora::IO::Loop if (absStartTimeMs) { - object->UpdateTime(absStartTimeMs); + if (bIsStartTimeSteady) + { + object->UpdateTimeSteady(absStartTimeMs); + } + else + { + object->UpdateTimeWall(absStartTimeMs); + } } return object; diff --git a/Source/IO/Loop/LSTimer.NT.hpp b/Source/IO/Loop/LSTimer.NT.hpp index dada1669..9229b30e 100644 --- a/Source/IO/Loop/LSTimer.NT.hpp +++ b/Source/IO/Loop/LSTimer.NT.hpp @@ -17,9 +17,10 @@ namespace Aurora::IO::Loop PROXY_LOOP_LOOPSOURCE_EXT_APIS_; - virtual void UpdateTime(AuUInt64 absTimeMs) override; - virtual void UpdateTimeNs(AuUInt64 absTimeNs) override; - virtual void UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) override; + virtual void UpdateTimeWall(AuUInt64 absTimeMs) override; + virtual void UpdateTimeWallNs(AuUInt64 absTimeNs) override; + virtual void UpdateTimeSteady(AuUInt64 absTimeMs) override; + virtual void UpdateTimeSteadyNs(AuUInt64 absTimeNs) override; virtual void UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) override; virtual void UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) override; virtual void Stop() override; diff --git a/Source/IO/Loop/LSTimer.cpp b/Source/IO/Loop/LSTimer.cpp new file mode 100755 index 00000000..887295fc --- /dev/null +++ b/Source/IO/Loop/LSTimer.cpp @@ -0,0 +1,52 @@ +/*** + Copyright (C) 2021-2024 Jamie Reece Wilson (a/k/a "Reece"). All rights reserved. + + File: LSTimer.cpp + Date: 2021-10-1 - 2024-09-08 + Author: Reece +***/ +#include + +namespace Aurora::IO::Loop +{ + AuSPtr NewLSTimerOS(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady); + AuSPtr NewLSTimerIP(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady); + + static bool MustUseAltTimer() + { + return gRuntimeConfig.ioConfig.bForceAltOSTimerPrimitives; + } + + static bool IsHPInProcess() + { + #if defined(AURORA_IS_MODERNNT_DERIVED) + return true; + #else + return !gRuntimeConfig.ioConfig.bTrustOSTimerPrimitiveIfKnownGood; + #endif + } + + AUKN_SYM AuSPtr NewLSTimerHighResolution(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady) + { + if (IsHPInProcess()) + { + return NewLSTimerIP(qwAbsStartTimeMs, dwReschedStepMsOrZero, dwMaxIterationsOrZero, bSingleshot, bIsStartTimeSteady); + } + else + { + return NewLSTimerOS(qwAbsStartTimeMs, dwReschedStepMsOrZero, dwMaxIterationsOrZero, bSingleshot, bIsStartTimeSteady); + } + } + + AUKN_SYM AuSPtr NewLSTimer(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady) + { + if (MustUseAltTimer()) + { + return NewLSTimerIP(qwAbsStartTimeMs, dwReschedStepMsOrZero, dwMaxIterationsOrZero, bSingleshot, bIsStartTimeSteady); + } + else + { + return NewLSTimerOS(qwAbsStartTimeMs, dwReschedStepMsOrZero, dwMaxIterationsOrZero, bSingleshot, bIsStartTimeSteady); + } + } +} \ No newline at end of file diff --git a/Source/IO/Loop/LSTimerNoKernelScheduler.cpp b/Source/IO/Loop/LSTimerNoKernelScheduler.cpp new file mode 100755 index 00000000..1350b6e1 --- /dev/null +++ b/Source/IO/Loop/LSTimerNoKernelScheduler.cpp @@ -0,0 +1,290 @@ +/*** + Copyright (C) 2021-2024 Jamie Reece Wilson (a/k/a "Reece"). All rights reserved. + + File: LSTimerNoKernelScheduler.cpp + Date: 2024-09-08 + Author: Reece +***/ +#include +#include +#include +#include "LSTimerNoKernelScheduler.hpp" +#include + +namespace Aurora::IO::Loop +{ + void LSTimerIPDispatcher::RunAsync() + { + AU_LOCK_GUARD(this->mutex); + if (this->pParent) + { + AU_LOCK_GUARD(this->pParent->cs); + this->pParent->Set(); + this->pParent = nullptr; + } + } + + LSTimerIP::LSTimerIP(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot) : + bSingleshot(bSingleshot), + reschedStepNsOrZero_(AuMSToNS(reschedStepMsOrZero)), + maxIterationsOrZero_(maxIterationsOrZero) + { + this->targetTime_ = AuTime::CurrentClockNS(); + } + + LSTimerIP::~LSTimerIP() + { + if (this->pDispatcher) + { + AU_LOCK_GUARD(this->pDispatcher->mutex); + this->pDispatcher->pParent = nullptr; + } + } + + void LSTimerIP::UpdateTimeWall(AuUInt64 absTimeMs) + { + this->UpdateTimeWallNs(AuMSToNS(absTimeMs)); + } + + void LSTimerIP::UpdateTimeWallNs(AuUInt64 absTimeNs) + { + this->UpdateTimeSteadyNs(absTimeNs - AuTime::CurrentClockNS() + AuTime::SteadyClockNS()); + } + + void LSTimerIP::UpdateTimeSteady(AuUInt64 absTimeMs) + { + this->UpdateTimeSteadyNs(AuMSToNS(absTimeMs)); + } + + void LSTimerIP::UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) + { + this->UpdateTickRateIfAnyNs(AuMSToNS(reschedStepMsOrZero), maxIterationsOrZero); + } + + void LSTimerIP::UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) + { + AU_LOCK_GUARD(this->cs); + + this->Stop(); + + this->reschedStepNsOrZero_ = reschedStepNsOrZero; + this->maxIterationsOrZero_ = maxIterationsOrZero; + this->count_ = 0; + + if (this->targetTime_) + { + this->UpdateTimeSteadyNs(this->targetTime_); + } + } + + void LSTimerIP::UpdateTimeSteadyNs(AuUInt64 absTimeNs) + { + AU_DEBUG_MEMCRUNCH; + AU_LOCK_GUARD(this->cs); + + AuSPtr pNext; + + { + AU_LOCK_GUARD(this->dispatcherMutex); + + if (this->pDispatcher) + { + AU_LOCK_GUARD(this->pDispatcher->mutex); + + if (this->pDispatcher->pParent) + { + // abort + this->pDispatcher->pParent = nullptr; + pNext = this->pDispatcher = AuMakeShared(); + } + else + { + pNext = this->pDispatcher; + } + } + else + { + pNext = this->pDispatcher = AuMakeShared(); + } + } + + SysAssert(pNext, "impossible OOM during timer op"); + + pNext->pParent = this; + AuAsync::Schedule(absTimeNs, nullptr, {}, pNext); + this->targetTime_ = absTimeNs; + } + + void LSTimerIP::Stop() + { + AU_LOCK_GUARD(this->cs); + + AU_LOCK_GUARD(this->dispatcherMutex); + if (this->pDispatcher) + { + AU_LOCK_GUARD(this->pDispatcher->mutex); + this->pDispatcher->pParent = nullptr; + this->pDispatcher = nullptr; + } + } + + bool LSTimerIP::IsSignaledNonblocking() + { + AU_LOCK_GUARD(this->cs); + + auto uCurrentTime = AuTime::SteadyClockNS(); + auto uNextTickTarget = AuAtomicLoad(&this->targetTime_); + + if (uCurrentTime < uNextTickTarget) + { + return false; + } + + auto uNextTick = uNextTickTarget + this->reschedStepNsOrZero_; + + if (uCurrentTime > uNextTick) + { + this->targetTime_ = uCurrentTime; + uNextTick = uCurrentTime + this->reschedStepNsOrZero_; + } + + if (!this->maxIterationsOrZero_) + { + this->UpdateTimeSteadyNs(uNextTick); + return true; + } + + if (AuAtomicAdd(&this->count_, 1) <= this->maxIterationsOrZero_) + { + this->UpdateTimeSteadyNs(uNextTick); + return true; + } + else + { + Stop(); + return false; + } + } + + bool LSTimerIP::OnTrigger(AuUInt handle) + { + AU_LOCK_GUARD(this->cs); + + if (IsSignaledNoSpinIfUserland()) + { + return true; + } + else + { + LSEvent::Reset(); + return false; + } + } + + bool LSTimerIP::TryTakeWaitNS(AuUInt64 uTimeoutAbs) + { + auto uNextTime = AuAtomicLoad(&this->targetTime_); + + if (!uNextTime) + { + return false; + } + + if (this->IsSignaledNonblocking()) + { + return true; + } + + auto uNow = AuTime::CurrentClockNS(); + if (uNow >= uNextTime) + { + return this->IsSignaledNonblocking(); + } + + // Win32 note: there is quite a bit of hackery behind SleepNs + // Win 7 and lower may spin for a bit + // Various CPUs may enter a user-space montior condition + // Dumb CPUs will SMT yield + // Alderlake et al can tpause + if (uTimeoutAbs) + { + AuThreading::SleepNs(AuMin(uNextTime, uTimeoutAbs) - uNow); + return this->IsSignaledNonblocking(); + } + else + { + AuThreading::SleepNs(uNextTime - uNow); + return this->IsSignaledNonblocking(); + } + } + + ELoopSource LSTimerIP::GetType() + { + return ELoopSource::eSourceTimer; + } + + bool LSTimerIP::TryInit() + { + return LSEvent::TryInit(false, false, true); + } + + bool LSTimerIP::IsSignaled() + { + return this->TryTakeSpin(); + } + + bool LSTimerIP::IsSignaledNoSpinIfUserland() + { + return this->IsSignaledNonblocking(); + } + + bool LSTimerIP::TryTakeNoSpin() + { + return this->IsSignaledNonblocking(); + } + + bool LSTimerIP::TryTakeSpin() + { + return Threading::Primitives::DoTryIf([&] + { + return this->TryTakeNoSpin(); + }); + } + + bool LSTimerIP::TryTake() + { + return this->TryTakeSpin(); + } + + AuSPtr NewLSTimerIP(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady) + { + AuAsync::StartSched2(); + + auto pTimer = AuMakeShared(dwReschedStepMsOrZero, dwMaxIterationsOrZero, bSingleshot); + if (!pTimer) + { + SysPushErrorMem(); + return {}; + } + + if (!pTimer->TryInit()) + { + SysPushErrorMem(); + return {}; + } + + if (qwAbsStartTimeMs) + { + if (bIsStartTimeSteady) + { + pTimer->UpdateTimeSteady(qwAbsStartTimeMs); + } + else + { + pTimer->UpdateTimeWall(qwAbsStartTimeMs); + } + } + + return pTimer; + } +} \ No newline at end of file diff --git a/Source/IO/Loop/LSTimerNoKernelScheduler.hpp b/Source/IO/Loop/LSTimerNoKernelScheduler.hpp new file mode 100755 index 00000000..5a5f496c --- /dev/null +++ b/Source/IO/Loop/LSTimerNoKernelScheduler.hpp @@ -0,0 +1,68 @@ +/*** + Copyright (C) 2021-2024 Jamie Reece Wilson (a/k/a "Reece"). All rights reserved. + + File: LSTimerNoKernelScheduler.hpp + Date: 2024-09-08 + Author: Reece +***/ +#pragma once + +#include "LSHandle.hpp" +#include "LSEvent.hpp" + +namespace Aurora::IO::Loop +{ + struct LSTimerIP; + + struct LSTimerIPDispatcher : AuAsync::IAsyncRunnable + { + AuMutex mutex; + LSTimerIP *pParent {}; + + void RunAsync() override; + }; + + struct LSTimerIP : LSEvent, ITimer + { + LSTimerIP(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot); + ~LSTimerIP(); + + PROXY_LOOP_LOOPSOURCE_EXT_WAIT_APIS_; + + virtual void UpdateTimeWall(AuUInt64 absTimeMs) override; + virtual void UpdateTimeWallNs(AuUInt64 absTimeNs) override; + virtual void UpdateTimeSteady(AuUInt64 absTimeMs) override; + virtual void UpdateTimeSteadyNs(AuUInt64 absTimeNs) override; + virtual void UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) override; + virtual void UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) override; + virtual void Stop() override; + + bool TryInit(); + + bool IsSignaled() override; + bool IsSignaledNoSpinIfUserland() override; + ELoopSource GetType() override; + + virtual bool OnTrigger(AuUInt handle) override; + + bool TryTakeNoSpin(); + bool TryTakeSpin(); + bool TryTake(); + bool TryTakeWaitNS(AuUInt64 timeout); + + void UpdateTimeInternal(AuUInt64 absTimeMs); + void UpdateTimeInternalSteady(AuUInt64 absTimeMs); + + private: + friend struct LSTimerIPDispatcher; + AuUInt32 maxIterationsOrZero_ {}; + AuUInt64 reschedStepNsOrZero_ {}, targetTime_ {}; + AuUInt32 count_ {}; + bool bSingleshot {}; + + AuCriticalSection cs; + AuMutex dispatcherMutex; + AuSPtr pDispatcher; + bool IsSignaledNonblocking(); + }; +} \ No newline at end of file