230 lines
12 KiB
C++
230 lines
12 KiB
C++
/***
|
|
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: ILoopQueue.hpp
|
|
Date: 2022-2-12
|
|
Author: Reece
|
|
***/
|
|
#pragma once
|
|
|
|
namespace Aurora::IO::Loop
|
|
{
|
|
// ILoopQueue's are comparable to a CoreFoundation RunLoop with the input parameters of NT's WaitMultipleObjects and RegisterWaitForSingleObject
|
|
// This shouldn't be too much heavier than CF's libevent/NT abstraction
|
|
|
|
struct ILoopQueue
|
|
{
|
|
/**
|
|
* @brief
|
|
* [*] Source add/remove operations should be followed by a Commit() to commit the changes
|
|
*
|
|
* [*] Calling Source[Add/Remove] during a Wait[Any/All[Ex]] will cause the wait function to
|
|
* return false, allowing for the caller to rewait on the updated queue, or to allow
|
|
* the thread to account for the object-was-invalidated condition
|
|
*
|
|
* @param source
|
|
* @return
|
|
* @note thread safe / nonblocking | can be called alongside any other function marked as such
|
|
* @warning it is unsafe to add an ILoopSource more than once.
|
|
*/
|
|
virtual bool SourceAdd(const AuSPtr<ILoopSource> &source) = 0;
|
|
|
|
/**
|
|
* @brief Same behaviour as SourceAdd
|
|
* Note: Timeout is specified in MS and polled during WaitAny/All failure or completition
|
|
* Objects awaiting timeout will not preempt the loop
|
|
* @param source
|
|
* @param timeoutMS
|
|
* @return
|
|
* @note thread safe / nonblocking | can be called alongside any other function marked as such
|
|
* @warning it is unsafe to add an ILoopSource more than once.
|
|
*/
|
|
virtual bool SourceAddWithTimeout(const AuSPtr<ILoopSource> &source, AuUInt32 timeoutMS) = 0;
|
|
|
|
/**
|
|
* @brief Manually request a loop source dequeue to be scheduled on next ::Commit.
|
|
* Loop queues are generally removed by registering callbacks, once upon completion,
|
|
* the internal state hits a zero reference count condition, atomically removing the source.
|
|
* See: AddCallback
|
|
* @param source
|
|
* @note thread safe / nonblocking | can be called alongside any other function marked as such
|
|
*/
|
|
virtual bool SourceRemove(const AuSPtr<ILoopSource> &source) = 0;
|
|
|
|
/**
|
|
* @brief Updates the OS watchdog list cache concept after Source[Remove/Add[WithTimeout]]
|
|
* Commits may occur while another thread is waiting on the loop queue.
|
|
* In those circumstances, they are internally preempted and rescheduled.
|
|
* WaitAnd has undefined behaviour across MT commit. Linux *might* work.
|
|
* NT wont until an APC-as-a-preempt-signal hack is implemented.
|
|
* Rare edge case for all i care - it'll remain a blocking edge case for.now.
|
|
* @return
|
|
* @note thread safe
|
|
*/
|
|
virtual bool Commit() = 0;
|
|
|
|
/**
|
|
* @brief
|
|
* @return the amount of loop sources added to the queue
|
|
* @note thread safe | can be called alongside any other function marked as such
|
|
*/
|
|
virtual AuUInt32 GetSourceCount() = 0;
|
|
|
|
/**
|
|
* @brief Appends a callback handler to be paired with a loop source
|
|
*
|
|
* Once a loop source become signaled, and a Wait[Any/All] function call is made, loop source subscribers are called
|
|
* to filter the final should-remove operation. The final popped objects are returned from Wait[Any[Ex]] and forgotten.
|
|
*
|
|
* If no ILoopSourceSubscribers are registered, the edge case behaviour is that loop sources will not be automatically
|
|
* dequeued. No user code explicitly told us to do anything other than wait, so the default behaviour of Wait[Any[Ex]]
|
|
* shall be to not evict loop sources, until at least one callback is registered, or until SourceRemove is called.
|
|
*
|
|
* Returning true from a ILoopSourceSubscriber will allow the ILoopQueue to evict the loop source and callback.
|
|
* [*] A call to ::Commit is not required.
|
|
* [*] Other ILoopSourceSubscribers will be consulted on whether or not the loop source shall be evicted entirely.
|
|
*
|
|
* Returning false from a ILoopSourceSubscriber will prevent eviction, resulting in continued callbacks in the next tick.
|
|
* [*] Other ILoopSourceSubscribers will be consulted on whether or not the loop source shall be evicted entirely.
|
|
*
|
|
* Once all callbacks are evicited, loop sources are automatically dequeued; however, objects without a callback shall not be dequeued.
|
|
* Loop Sources may be kept around by a global ILoopSourceSubscriber, past this stage. See: other AddCallback function.
|
|
*
|
|
* @param source
|
|
* @param subscriber
|
|
* @return
|
|
* @note thread safe | can be called alongside any other function marked as such
|
|
*/
|
|
virtual bool AddCallback(const AuSPtr<ILoopSource> &source, const AuSPtr<ILoopSourceSubscriber> &subscriber) = 0;
|
|
|
|
/**
|
|
* @brief Identical behaviour to that of the basic method. In addition, ILoopSourceSubscriberEx::OnTimeout is called
|
|
* on violation of the timeout provided to SourceAddWithTimeout, and the OnFinished method has been extended to
|
|
* accept a position number. This is the position of the subscriber relative to the signal-state change tick.
|
|
* If the loop source is singleshot, and multiple callbacks are registered, all callbacks will be called with
|
|
* a position counter. Furthermore, extended callbacks are called __before__ non-ex variants.
|
|
* @param source
|
|
* @param subscriber
|
|
* @return
|
|
* @note thread safe | can be called alongside any other function marked as such
|
|
*/
|
|
virtual bool AddCallbackEx(const AuSPtr<ILoopSource> &source, const AuSPtr<ILoopSourceSubscriberEx> &subscriber) = 0;
|
|
|
|
/**
|
|
* @brief
|
|
* Registers a callback to be called when either, an loop source has no callbacks, or a loop source could be dequeued.
|
|
* Returning false from OnFinished shall prevent the loop queue from decommitting the loop source.
|
|
* @param subscriber
|
|
* @return
|
|
* @note thread safe | can be called alongside any other function marked as such
|
|
*/
|
|
virtual bool AddCallback(const AuSPtr<ILoopSourceSubscriber> &subscriber) = 0;
|
|
|
|
/**
|
|
* @brief Nonblocking check for alert objects in the loop queue
|
|
* @return
|
|
* @note thread safe / nonblocking | can be called alongside any other function marked as such
|
|
*/
|
|
virtual bool IsSignaledPeek() = 0;
|
|
|
|
/**
|
|
* @brief Nonblocking wait-any for all objects in the loop queue
|
|
* @warning may yield to ILoopSourceSubscriber delegate on the current context
|
|
* @warning (technically unsafe, use alloc-unsafe extended version to acknowledge unlocked sources)
|
|
* @return
|
|
* @note thread safe / nonblocking | can be called alongside any other function marked as such
|
|
*/
|
|
virtual AuUInt32 PumpNonblocking() = 0;
|
|
|
|
/**
|
|
* @brief Nonblocking wait-any for all objects in the loop queue
|
|
* @warning may yield to ILoopSourceSubscriber delegate on the current context
|
|
* @return
|
|
* @note thread safe / nonblocking | can be called alongside any other function marked as such
|
|
*/
|
|
virtual AuList<AuSPtr<ILoopSource>> PumpNonblockingEx() = 0;
|
|
|
|
/**
|
|
* @brief Waits on all the submitted loop sources until they are all complete or until the timeout has finished.
|
|
* Note: the completion of another Wait[All/Any[Ex]] call may result in a
|
|
* @param timeout timeout in MS, zero = indefinite
|
|
* @return
|
|
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
|
|
* @warning (thread safety might be limited to blocking callers of the object for.now. Linux might work. NT needs an APC hack.)
|
|
*/
|
|
virtual bool WaitAll (AuUInt32 timeout = 0) = 0;
|
|
|
|
/**
|
|
* @brief Waits on all the loop sources until at least one is signaled.
|
|
* @param timeout timeout in MS, zero = indefinite.
|
|
* Use IsSignaledPump for nonblocking.
|
|
* @return
|
|
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
|
|
* @warning (technically unsafe, use alloc-unsafe extended version to acknowledge unlocked sources)
|
|
* @note thread safe | can be called alongside any other function marked as such
|
|
*/
|
|
virtual AuUInt32 WaitAny (AuUInt32 timeout = 0) = 0;
|
|
|
|
/**
|
|
* @brief Waits on all the loop sources until at least one is signaled.
|
|
* @param timeout timeout in MS, zero = indefinite.
|
|
* Use IsSignaledPump for nonblocking.
|
|
* @return
|
|
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
|
|
* @note thread safe | can be called alongside any other function marked as such
|
|
*/
|
|
virtual AuList<AuSPtr<ILoopSource>> WaitAnyEx(AuUInt32 timeout = 0) = 0;
|
|
|
|
/**
|
|
* @brief
|
|
* Mostly relevant on Windows where the limit is 64 versus horrible UNIX-y socket
|
|
* optimized interfaces that can handle thousands of fds
|
|
*
|
|
* In large loop queues, a maximum of
|
|
* `sectionTickTime * (sectionDequeCount + clamp(platformSanity, sectionDequeCount))`
|
|
* kernel objects can be checked within a given time frame.
|
|
*
|
|
* What this effectively does is iterate over a subdivided work queue, aligned
|
|
* to platformSanity (NT: 64); [i, i+platform sanity] objects can then be waited on.
|
|
*
|
|
* On timeout of sectionTickTime,
|
|
* [i, i+sectionDequeCount] of the tick could be checked, however, we know
|
|
* [i, i+platform sanity] timeouted so, in the event of a timeout, we can
|
|
* asynchronously check [i+platform sanity, ...] to be nicer faster. Since
|
|
* we've already spent the maximum allocated time to poll on a mere subset,
|
|
* we may as well desperately check sectionDequeCount worth of objects, and
|
|
* write-off the time spent blocking. That way, we get upto platform sanity
|
|
* of is-signaled checks for free in the worst case scenario. The perspective
|
|
* that the overshoot is expensive doesn't make sense. The is-signaled
|
|
* check is basically free and the initial timeout/yield-to-kern over set 0
|
|
* was the bottleneck
|
|
*
|
|
* Otherwise on success, we know to check in the range of [i, platform sanity
|
|
* for at least one alerted object.
|
|
*
|
|
* Internal logic will dequeue platform coefficient aligned handles, and the timeout
|
|
* optimization shall check [i+platform sanity, i+platform sanity+sectionDequeCount].
|
|
* 1 handle =/= 1 loop source, tho usually 1 loop source ~= 1 handle
|
|
*
|
|
* tl'dr:
|
|
* [*] dont use if you cant stall for sectionTickTime if sectionDequeCount of your
|
|
* loop sources are unsignaled
|
|
* [*] chug paths are intended for observer threads and not for maintaining high
|
|
* performance. no sane traditional application would ever have 64+ signalable
|
|
* objects stuck in a loop. though if you do, and you dont configure anything,
|
|
* the worst case scenario is, WaitAny can take upto a millisecond to discover
|
|
* a signaled loop source.
|
|
*
|
|
* @param sectionTickTime
|
|
* @param sectionDequeCount
|
|
*/
|
|
virtual void ChugPathConfigure(AuUInt32 sectionTickTime, AuSInt sectionDequeCount) = 0;
|
|
|
|
/**
|
|
* @brief Hints that the calling program understands the kernel shouldnt schedule tne entire source list, and instead, we should long poll
|
|
*/
|
|
virtual void ChugHint(bool value) = 0;
|
|
};
|
|
|
|
AUKN_SYM AuSPtr<ILoopQueue> NewLoopQueue();
|
|
} |