J Reece Wilson
02826d2365
[+] AuLoop::kWaitMultipleFlagBreakAfterAPC [+] Alternative Wait AND implementations for NT, POSIX, and generic [+] IOConfig::... [*] LoopQueue improvements [+] ILoopQueue::ConfigureDoIOApcCallbacks [+] ILoopQueue::ConfigureDoIOApcCallbacks
195 lines
12 KiB
C++
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.
|
|
}
|