[+] AuIO::Loop::NewLSTimerHighResolution

[*] ILSTimer::UpdateTime -> ILSTimer::UpdateTimeWall
[*] ILSTimer::UpdateTimeNs -> ILSTimer::UpdateTimeWallNs
[+] ILSTimer::UpdateTimeSteady
[+] ILSTimer::UpdateTimeSteadyNs
This commit is contained in:
Reece Wilson 2024-09-08 07:26:23 +01:00
parent 4d4f5e2501
commit 35761edaec
12 changed files with 529 additions and 38 deletions

View File

@ -65,9 +65,12 @@ namespace Aurora::IO::Loop
{ {
/* Warning: IO timers use wall time (CurrentClock[M/NS), not SteadyClock[M/N]S()). /* 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. */ Use auasync timers for steady-clock timing and tick timing information. */
// Update: not anymore
virtual void UpdateTime(AuUInt64 absTimeMs) = 0; virtual void UpdateTimeWall(AuUInt64 absTimeMs) = 0;
virtual void UpdateTimeNs(AuUInt64 absTimeNs) = 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 UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero = 0, AuUInt32 maxIterationsOrZero = 0) = 0;
virtual void UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero = 0, AuUInt32 maxIterationsOrZero = 0) = 0; virtual void UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero = 0, AuUInt32 maxIterationsOrZero = 0) = 0;
virtual void Stop() = 0; virtual void Stop() = 0;
@ -78,7 +81,8 @@ namespace Aurora::IO::Loop
virtual void *GetLastSignalInfo() = 0; virtual void *GetLastSignalInfo() = 0;
}; };
AUKN_SYM AuSPtr<ITimer> NewLSTimer(AuUInt64 absStartTimeMs /*CurrentClockMS()*/, AuUInt32 reschedStepMsOrZero = 0, AuUInt32 maxIterationsOrZero = 0, bool bSingleshot = false /*cannot be changed*/); // warn: no IPC counterpart AUKN_SYM AuSPtr<ITimer> 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<ITimer> 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<ILSMutex> NewLSMutex(); AUKN_SYM AuSPtr<ILSMutex> NewLSMutex();
AUKN_SYM AuSPtr<ILSMutex> NewLSMutexSlow(); // interop-ready (usable with DbgLoopSourceToReadFd) AUKN_SYM AuSPtr<ILSMutex> NewLSMutexSlow(); // interop-ready (usable with DbgLoopSourceToReadFd)
AUKN_SYM AuSPtr<ILSEvent> NewLSEvent(bool bTriggered = false, bool bAtomicRelease = true, bool bPermitMultipleTriggers = false); AUKN_SYM AuSPtr<ILSEvent> NewLSEvent(bool bTriggered = false, bool bAtomicRelease = true, bool bPermitMultipleTriggers = false);

View File

@ -260,8 +260,25 @@ namespace Aurora
struct IOConfig struct IOConfig
{ {
AuUInt32 uProtocolStackDefaultBufferSize { 64 * 1024 }; AuUInt32 uProtocolStackDefaultBufferSize { 64 * 1024 };
bool bIsVeryLargeIOApplication { false }; 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 struct Win32Config

View File

@ -182,7 +182,15 @@ namespace Aurora::Async
{ {
try try
{ {
AuStaticCast<AuAsync::ThreadPool>(entry.pool)->Run(entry.target, entry.runnable, false); if (entry.pool)
{
AuStaticCast<AuAsync::ThreadPool>(entry.pool)->Run(entry.target, entry.runnable, false);
}
else
{
// IO timer hack!
entry.runnable->RunAsync();
}
} }
catch (...) catch (...)
{ {
@ -280,7 +288,10 @@ namespace Aurora::Async
{ {
AU_LOCK_GUARD(gSchedLock); AU_LOCK_GUARD(gSchedLock);
AU_DEBUG_MEMCRUNCH; AU_DEBUG_MEMCRUNCH;
AuAtomicAdd(&pool->uAtomicCounter, 1u); if (pool)
{
AuAtomicAdd(&pool->uAtomicCounter, 1u);
}
SchedNextTime(ns); SchedNextTime(ns);
return AuTryInsert(gOrderedEntries, return AuTryInsert(gOrderedEntries,
AuConstReference(ns), AuConstReference(ns),

View File

@ -14,6 +14,7 @@ namespace Aurora::Async
void InitSched(); void InitSched();
void DeinitSched(); void DeinitSched();
void StartSched(); void StartSched();
void StartSched2();
bool Schedule(AuUInt64 ns, IThreadPoolInternal *pool, WorkerId_t target, AuSPtr<IAsyncRunnable> runnable); bool Schedule(AuUInt64 ns, IThreadPoolInternal *pool, WorkerId_t target, AuSPtr<IAsyncRunnable> runnable);
void TerminateSceduledTasks(IThreadPoolInternal *pool, WorkerId_t target); void TerminateSceduledTasks(IThreadPoolInternal *pool, WorkerId_t target);

View File

@ -76,7 +76,7 @@ namespace Aurora::IO
AuUInt64 IOWaitableIOTimer::SetTargetTimeAbs(AuUInt64 ns) AuUInt64 IOWaitableIOTimer::SetTargetTimeAbs(AuUInt64 ns)
{ {
auto old = AuExchange(this->targetTimeAbsNs, ns); auto old = AuExchange(this->targetTimeAbsNs, ns);
this->source->UpdateTimeNs(ns); this->source->UpdateTimeSteadyNs(ns);
return old; return old;
} }
@ -89,7 +89,14 @@ namespace Aurora::IO
return {}; return {};
} }
timer->source = AuLoop::NewLSTimer(0); if (gRuntimeConfig.ioConfig.bUseHighResTimerUnderIOWaitableTimer)
{
timer->source = AuLoop::NewLSTimerHighResolution(0);
}
else
{
timer->source = AuLoop::NewLSTimer(0);
}
if (!timer->IsValid()) if (!timer->IsValid())
{ {

View File

@ -31,34 +31,47 @@ namespace Aurora::IO::Loop
} }
} }
void LSTimer::UpdateTime(AuUInt64 absTimeMs)
{
this->targetTime_ = AuMSToNS<AuUInt64>(absTimeMs);
UpdateTimeInternal(this->targetTime_);
}
void LSTimer::UpdateTimeNs(AuUInt64 absTimeNs)
{
this->targetTime_ = absTimeNs;
UpdateTimeInternal(this->targetTime_);
}
void LSTimer::UpdateTimeInternal(AuUInt64 absTimeNs) 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 {}; itimerspec spec {};
AuTime::auabsns2ts(&spec.it_value, this->targetTime_); AuTime::ns2ts(&spec.it_value, this->targetTime_);
AuTime::ns2ts(&spec.it_interval, this->reschedStepNsOrZero_); AuTime::ns2ts(&spec.it_interval, this->reschedStepNsOrZero_);
SysAssert(::timerfd_settime(this->handle, TFD_TIMER_ABSTIME, &spec, nullptr) == 0); SysAssert(::timerfd_settime(this->handle, TFD_TIMER_ABSTIME, &spec, nullptr) == 0);
} }
void LSTimer::UpdateTimeWall(AuUInt64 absTimeMs)
{
this->UpdateTimeInternal(AuMSToNS<AuUInt64>(absTimeMs));
}
void LSTimer::UpdateTimeWallNs(AuUInt64 absTimeNs)
{
this->UpdateTimeInternal(absTimeNs);
}
void LSTimer::UpdateTimeSteady(AuUInt64 absTimeMs)
{
this->UpdateTimeInternalSteady(AuMSToNS<AuUInt64>(absTimeMs));
}
void LSTimer::UpdateTimeSteadyNs(AuUInt64 absTimeNs)
{
this->UpdateTimeInternalSteady(absTimeNs);
}
void LSTimer::UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) void LSTimer::UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero)
{ {
this->reschedStepNsOrZero_ = AuMSToNS<AuUInt64>(reschedStepMsOrZero); this->reschedStepNsOrZero_ = AuMSToNS<AuUInt64>(reschedStepMsOrZero);
this->maxIterationsOrZero_ = maxIterationsOrZero; this->maxIterationsOrZero_ = maxIterationsOrZero;
this->count_ = 0; this->count_ = 0;
this->UpdateTimeInternal(this->targetTime_); this->UpdateTimeInternalSteady(this->targetTime_);
} }
void LSTimer::UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) void LSTimer::UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero)
@ -66,7 +79,7 @@ namespace Aurora::IO::Loop
this->reschedStepNsOrZero_ = reschedStepNsOrZero; this->reschedStepNsOrZero_ = reschedStepNsOrZero;
this->maxIterationsOrZero_ = maxIterationsOrZero; this->maxIterationsOrZero_ = maxIterationsOrZero;
this->count_ = 0; this->count_ = 0;
this->UpdateTimeInternal(this->targetTime_); this->UpdateTimeInternalSteady(this->targetTime_);
} }
bool LSTimer::OnTrigger(AuUInt handle) bool LSTimer::OnTrigger(AuUInt handle)
@ -121,15 +134,15 @@ namespace Aurora::IO::Loop
if (ok && !this->reschedStepNsOrZero_ && !bSingleshot) if (ok && !this->reschedStepNsOrZero_ && !bSingleshot)
{ {
// Ensure non-step timers remain signaled despite the read above us caneling them // Ensure non-step timers remain signaled despite the read above us caneling them
this->UpdateTimeInternal(this->targetTime_); this->UpdateTimeInternalSteady(this->targetTime_);
} }
return ok; return ok;
} }
AUKN_SYM AuSPtr<ITimer> NewLSTimer(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot) AuSPtr<ITimer> 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) if (fd == -1)
{ {
SysPushErrorIO(); SysPushErrorIO();
@ -146,7 +159,14 @@ namespace Aurora::IO::Loop
if (qwAbsStartTimeMs) if (qwAbsStartTimeMs)
{ {
pTimer->UpdateTime(qwAbsStartTimeMs); if (bIsStartTimeSteady)
{
pTimer->UpdateTimeSteady(qwAbsStartTimeMs);
}
else
{
pTimer->UpdateTimeWall(qwAbsStartTimeMs);
}
} }
return pTimer; return pTimer;

View File

@ -17,8 +17,10 @@ namespace Aurora::IO::Loop
PROXY_LOOP_LOOPSOURCE_EXT_WAIT_APIS_; PROXY_LOOP_LOOPSOURCE_EXT_WAIT_APIS_;
virtual void UpdateTime(AuUInt64 absTimeMs) override; virtual void UpdateTimeWall(AuUInt64 absTimeMs) override;
virtual void UpdateTimeNs(AuUInt64 absTimeNs) 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 UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) override;
virtual void UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) override; virtual void UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) override;
virtual void Stop() override; virtual void Stop() override;
@ -31,6 +33,7 @@ namespace Aurora::IO::Loop
virtual ELoopSource GetType() override; virtual ELoopSource GetType() override;
void UpdateTimeInternal(AuUInt64 absTimeMs); void UpdateTimeInternal(AuUInt64 absTimeMs);
void UpdateTimeInternalSteady(AuUInt64 absTimeMs);
private: private:
AuUInt32 maxIterationsOrZero_ {}; AuUInt32 maxIterationsOrZero_ {};

View File

@ -27,16 +27,26 @@ namespace Aurora::IO::Loop
this->handle = kInvalidHandle; this->handle = kInvalidHandle;
} }
void LSTimer::UpdateTime(AuUInt64 absTimeMs) void LSTimer::UpdateTimeWall(AuUInt64 absTimeMs)
{ {
UpdateTimeInternalNTWall(AuTime::ConvertTimestamp(absTimeMs)); UpdateTimeInternalNTWall(AuTime::ConvertTimestamp(absTimeMs));
} }
void LSTimer::UpdateTimeNs(AuUInt64 absTimeNs) void LSTimer::UpdateTimeWallNs(AuUInt64 absTimeNs)
{ {
UpdateTimeInternalNTWall(AuTime::ConvertTimestampNs(absTimeNs)); UpdateTimeInternalNTWall(AuTime::ConvertTimestampNs(absTimeNs));
} }
void LSTimer::UpdateTimeSteady(AuUInt64 absTimeMs)
{
UpdateTimeSteadyNs(AuMSToNS<AuUInt64>(absTimeMs));
}
void LSTimer::UpdateTimeSteadyNs(AuUInt64 absTimeNs)
{
UpdateTimeWallNs(absTimeNs - AuTime::SteadyClockNS() + AuTime::CurrentClockNS());
}
void LSTimer::UpdateTimeInternalNTWall(AuUInt64 absTimeNs) void LSTimer::UpdateTimeInternalNTWall(AuUInt64 absTimeNs)
{ {
LARGE_INTEGER i; LARGE_INTEGER i;
@ -120,7 +130,7 @@ namespace Aurora::IO::Loop
return ELoopSource::eSourceTimer; return ELoopSource::eSourceTimer;
} }
AUKN_SYM AuSPtr<ITimer> NewLSTimer(AuUInt64 absStartTimeMs, AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot) AuSPtr<ITimer> NewLSTimerOS(AuUInt64 absStartTimeMs, AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady)
{ {
// https://docs.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-setthreadpoolwait // https://docs.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-setthreadpoolwait
// TODO: Is that any better with an event? // TODO: Is that any better with an event?
@ -142,7 +152,14 @@ namespace Aurora::IO::Loop
if (absStartTimeMs) if (absStartTimeMs)
{ {
object->UpdateTime(absStartTimeMs); if (bIsStartTimeSteady)
{
object->UpdateTimeSteady(absStartTimeMs);
}
else
{
object->UpdateTimeWall(absStartTimeMs);
}
} }
return object; return object;

View File

@ -17,9 +17,10 @@ namespace Aurora::IO::Loop
PROXY_LOOP_LOOPSOURCE_EXT_APIS_; PROXY_LOOP_LOOPSOURCE_EXT_APIS_;
virtual void UpdateTime(AuUInt64 absTimeMs) override; virtual void UpdateTimeWall(AuUInt64 absTimeMs) override;
virtual void UpdateTimeNs(AuUInt64 absTimeNs) override; virtual void UpdateTimeWallNs(AuUInt64 absTimeNs) override;
virtual void UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) 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 UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) override;
virtual void Stop() override; virtual void Stop() override;

52
Source/IO/Loop/LSTimer.cpp Executable file
View File

@ -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 <Source/RuntimeInternal.hpp>
namespace Aurora::IO::Loop
{
AuSPtr<ITimer> NewLSTimerOS(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady);
AuSPtr<ITimer> 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<ITimer> 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<ITimer> 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);
}
}
}

View File

@ -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 <Source/RuntimeInternal.hpp>
#include <Source/Async/IAsyncRunnable.hpp>
#include <Source/Async/AuSchedular.hpp>
#include "LSTimerNoKernelScheduler.hpp"
#include <Source/Threading/Primitives/SMTYield.hpp>
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<AuUInt64>(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<AuUInt64>(absTimeMs));
}
void LSTimerIP::UpdateTimeWallNs(AuUInt64 absTimeNs)
{
this->UpdateTimeSteadyNs(absTimeNs - AuTime::CurrentClockNS() + AuTime::SteadyClockNS());
}
void LSTimerIP::UpdateTimeSteady(AuUInt64 absTimeMs)
{
this->UpdateTimeSteadyNs(AuMSToNS<AuUInt64>(absTimeMs));
}
void LSTimerIP::UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero)
{
this->UpdateTickRateIfAnyNs(AuMSToNS<AuUInt64>(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<LSTimerIPDispatcher> 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<LSTimerIPDispatcher>();
}
else
{
pNext = this->pDispatcher;
}
}
else
{
pNext = this->pDispatcher = AuMakeShared<LSTimerIPDispatcher>();
}
}
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<AuUInt32>(&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<ITimer> NewLSTimerIP(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady)
{
AuAsync::StartSched2();
auto pTimer = AuMakeShared<LSTimerIP>(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;
}
}

View File

@ -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<LSTimerIPDispatcher> pDispatcher;
bool IsSignaledNonblocking();
};
}