AuroraRuntime/Include/Aurora/IO/Loop/Loop.hpp
J Reece Wilson 02826d2365 [+] AuLoop::kWaitMultipleFlagNoIOCallbacks
[+] AuLoop::kWaitMultipleFlagBreakAfterAPC
[+] Alternative Wait AND implementations for NT, POSIX, and generic
[+] IOConfig::...
[*] LoopQueue improvements
[+] ILoopQueue::ConfigureDoIOApcCallbacks
[+] ILoopQueue::ConfigureDoIOApcCallbacks
2024-10-10 11:03:26 +01:00

195 lines
12 KiB
C++

/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: Loop.hpp
Date: 2021-8-21
Author: Reece
Notes: This API class is specifically for kernel objects or similar userland schedular handles, this is not comparable to the async subsystem.
While you may drive low perf user facing apps and small services from this, other services should divide and conquer
* Allow the network subsystem to load balance sockets across a predefined amount of workers,
* Use semaphores instead of massive lengthy arrays of mutexes
* Use grouped long-polling LoopSources to convert kernel or waitable objects to a single time-polled loop source (freq based thread runners, alt to eEfficient)
***/
#pragma once
#include "ELoopSource.hpp"
#include "ILoopSource.hpp"
#include "ILoopSourceSubscriber.hpp"
#include "ILoopQueue.hpp"
namespace Aurora::IO::Loop
{
// optTimeoutMS = {} | indefinite
// optTimeoutMS = 0 | poll
// optTimeoutMS = 1 | 1ms
AUKN_SYM bool WaitMultipleLoopSources(const AuList<AuSPtr<Loop::ILoopSource>> &lsList,
AuList<AuSPtr<Loop::ILoopSource>> &signaled,
bool bAny = { true },
AuOptional<AuUInt32> optTimeoutMS = {});
static const AuUInt64 kWaitMultipleFlagAny = 1 << 0; // ~= NT: ~MWMO_WAITALL, !bWaitAll
static const AuUInt64 kWaitMultipleFlagNoSpin = 1 << 1;
static const AuUInt64 kWaitMultipleFlagAvoidKern = 1 << 2;
static const AuUInt64 kWaitMultipleFlagBreakAfterOne = 1 << 3; // ~= NT: WaitForMultipleObjects return value
static const AuUInt64 kWaitMultipleFlagBreakAfterAPC = 1 << 4; // ~= NT: WAIT_IO_COMPLETION return value
static const AuUInt64 kWaitMultipleFlagNoIOCallbacks = 1 << 5; // ~= NT: !bAlert, !fAlert, ~MWMO_ALERTABLE
static const AuUInt64 kWaitMultipleFlagPragAnyAPC = 1 << 6; // See notes
/**
* Parameter "signaled" indicates the exact loop sources that were signaled.
* If this "signaled" array is empty, and WaitMultipleLoopSourcesEx returned true, then an APC was served.
* In addition, win32 waitalls may return an empty signaled[] if lsList contained a win32 message queue loop source.
*
* If kWaitMultipleFlagAny is specified, signaled may contain any amonut of lsList entries
* If kWaitMultipleFlagAny is not specified, signaled must be an array of the same elements, or empty if some other reason caused the wait to beak.
*
* If kWaitMultipleFlagBreakAfterOne and kWaitMultipleFlagAny are specified, signaled will only contain no more than one entry.
*
* @returns false: timeout
* true: APC dispatched, io callback dispatched, all of (~uFlags) & kWaitMultipleFlagAny woke up, or at least one of (uFlags) & kWaitMultipleFlagAny woke up.
*
* @warning depending on the platform, and provided parameters, wait all may consume semaphore and event objects in order, followed by any mutexes in order.
* @warning depending on the platform, kWaitMultipleFlagPragAnyAPC may be required to recieve IAsyncTransaction::SetCallback callbacks of transaction objects not covered by lsList sources.
* @warning IAsyncTransaction::SetCallback does not respect kWaitMultipleFlagNoIOCallbacks.
* @warning kWaitMultipleFlagNoIOCallbacks only filters user queued APCs and implicit io callbacks.
*/
AUKN_SYM bool WaitMultipleLoopSourcesEx(const AuList<AuSPtr<Loop::ILoopSource>> &lsList,
AuList<AuSPtr<Loop::ILoopSource>> &signaled,
AuUInt64 uFlags = { kWaitMultipleFlagAny },
AuOptional<AuUInt32> optTimeoutMS = {});
struct ILSSemaphore : virtual ILoopSource
{
virtual bool AddOne() = 0;
virtual bool AddMany(AuUInt32 uCount) = 0;
};
struct ILSEvent : virtual ILoopSource
{
virtual bool Set() = 0;
virtual bool Reset() = 0;
};
struct ILSMutex : virtual ILoopSource
{
virtual bool Unlock() = 0;
};
struct ITimer : virtual ILoopSource
{
/* 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 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;
};
struct ILSSignalCatcher : virtual ILoopSource
{
virtual void *GetLastSignalInfo() = 0;
};
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> NewLSMutexSlow(); // interop-ready (usable with DbgLoopSourceToReadFd)
AUKN_SYM AuSPtr<ILSEvent> NewLSEvent(bool bTriggered = false, bool bAtomicRelease = true, bool bPermitMultipleTriggers = false);
AUKN_SYM AuSPtr<ILSEvent> NewLSEventSlow(bool bTriggered = false, bool bAtomicRelease = true, bool bPermitMultipleTriggers = false); // interop-ready (usable with DbgLoopSourceToReadFd)
AUKN_SYM AuSPtr<ILSSemaphore> NewLSSemaphoreSlow(AuUInt32 uInitialCount = 0); // interop-ready (usable with DbgLoopSourceToReadFd)
AUKN_SYM AuSPtr<ILSSemaphore> NewLSSemaphore(AuUInt32 uInitialCount = 0);
AUKN_SYM AuSPtr<ILSSemaphore> NewLSSemaphoreEx(AuUInt32 uInitialCount = 0, AuUInt32 uMaxCount = 0); // warn: no IPC counterpart
AUKN_SYM AuSPtr<ILoopSource> NewLSOSHandle(AuUInt);
AUKN_SYM AuSPtr<ILoopSource> NewLSAsync(Aurora::Async::WorkerPId_t workerPid); // warn: no IPC counterpart
AUKN_SYM AuSPtr<ILoopSource> NewLSFile(const AuSPtr<IO::IAsyncTransaction> &fileTransaction); // warn: no IPC counterpart
AUKN_SYM AuSPtr<ILoopSource> NewStdIn();
AUKN_SYM AuSPtr<ILoopSource> NewLSWin32Source(bool dispatchMessages);
AUKN_SYM AuSPtr<ILoopSource> NewLSAppleSource();
AUKN_SYM AuSPtr<ILoopSource> NewLSIOHandle(const AuSPtr<IIOHandle> &pHandle);
// See: AuIO::IPC for IPC event loop objects
// warn: Only works on singular loop sources
// warn: You should only use trust the interop-ready sources to serve the HANDLE/fd you expect.
// The primary loop-source primitives are capable of bypassing the kernels io scheduler via in-process atomics.
// These can be leveraged with AuLoop::WaitMultipleLoopSources with kWaitMultipleFlagAvoidKern (and future planned ILoopSource methods.)
// For scheduling io events, the atomic optimizations are pointless. They can, however, serve to deduplicate multiple event triggers
// and optimize the wait-on operation of win32-on-unix/win32 emulators.
// In addition, D3D, Winsock, NT IO, and other subsystems of a modern coreos system expects (or at least, are documented to expect)
// kernel event handle objects for synchronization - not semaphores with some atomic word in a process god knows where.
AUKN_SYM AuInt64 DbgLoopSourceToReadFd(AuSPtr<ILoopSource> pLoopSource);
// warn: Only works on singular loop sources
AUKN_SYM AuInt64 DbgLoopSourceToWriteFd(AuSPtr<ILoopSource> pLoopSource);
#if defined(X_PROTOCOL)
static AuSPtr<ILoopSource> NewLSX11(Display *display)
{
return NewLSOSHandle(ConnectionNumber(display));
}
#endif
#if defined(AURORA_IS_POSIX_DERIVED)
static AuSPtr<ILoopSource> NewLSFd(int fd)
{
if (fd < 0)
{
return {};
}
return NewLSOSHandle(fd);
}
#endif
#if defined(AURORA_IS_MODERNNT_DERIVED) && defined(_AU_SAW_WIN32_EARLY)
static AuSPtr<ILoopSource> NewLSHandle(HANDLE handle) // even though a handle is just a void*
{
if (handle == INVALID_HANDLE_VALUE /*and we could just hard-code the literal here*/) return {};
return NewLSOSHandle(reinterpret_cast<AuUInt>(handle));
}
#endif
#if defined(AURORA_IS_LINUX_DERIVED)
AUKN_SYM AuSPtr<ILSSignalCatcher> NewLSSignalCatcher(const AuList<int> &signals);
#endif
// Note for Win32-like emulators (Xbox, Xbox 360, GameOS sandboxes, etc)
// Aurora does not parrot exact win32 smenatics.
// On OVERLAPPED COMPLETIONs, you should implement an APC queue yourself, and dequeue notificaitons once WaitForMultipleLoopSources completes.
// On SetTimer callback, aurora does not * yet * implement this, even tho we could soon and other oses do.
// Attach all thread local timers to the input loopsource list and dispatch the cb in your emulated APC queue.
// Usage of kWaitMultipleFlagBreakAfterAPC may be used by some users as a replacement for WAIT_IO_COMPLETION; to others, it returns spuriously far too many times.
// Async file transaction callbacks will occur even in non-alert mode, if you explicitly specify an IAsyncTransactions' loop source.
// Additional ICompletionGroup callbacks may occur even in non-alert mode, if you explicitly sleep on its' loop source.
// WaitMultipleLoopSourcesEx makes for a really good stateless yield function for these users; however, a naive shim cannot be used to recreate WaitForMultipleObjectsEx.
// ...likewise, network loop source quirks means you might need to alert sleep under some oses.
// ...likewise, async transaction loop source quirks means the b/fAlert state of the emulated application
// Note on limits:
// lsList should be kept under NTs limit of 64; however, you can go above this with poor long polling.
// a user facing application on NT will not simply throw its' hands into the air the moment it exceeds Microsofts limit.
// Both Linux and Win32 have a fallback for their fast path (POSIX.1-2008 poll, WaitForMultipeObjects) using an ILoopQueue (epoll, also WaitForMultipeObjects).
// POSIX and NT platforms should use stateless kernel resources on WaitMultipleLoopSources[Ex] calls.
// NT: lsList.size() <= 64 generally does not allocate so much (also noting: signaled return array values always alloc with reserved memory active).
// Linux: lsList.size() <= 128 generally does not allocate so much (also noting: signaled return array values always alloc with reserved memory active).
// Notes on callback lists
// All allocations done under WaitMultipleLoopSources are performed under a AU_DEBUG_MEMCRUNCH scope.
// Notes on IO callbacks
// If no io callback suppression flag is provided, the application always:
// * enters an NT bAlert=true/MWMO_ALERTABLE sleep,
// * dispatches completed io transactions underneath sleep routines (ILoopSource::WaitOn(...), AuIO::IOSleep(...), AuIO::IOYield(), etc)
// Notes all around
// If you squint your eyes, or realize all of the edge cases in emulating Win32 do not apply to your high level use cases,
// WaitMultipleLoopSources[Ex] APIs are great for porting Win32 applications to POSIX. The primitives defined in Loop.hpp
// should match the expected behaviour of Win9x / NT sync primitives, except in some cases where they can utilize fast paths
// to bypass the kernel all together.
}