J Reece Wilson
02826d2365
[+] AuLoop::kWaitMultipleFlagBreakAfterAPC [+] Alternative Wait AND implementations for NT, POSIX, and generic [+] IOConfig::... [*] LoopQueue improvements [+] ILoopQueue::ConfigureDoIOApcCallbacks [+] ILoopQueue::ConfigureDoIOApcCallbacks
595 lines
22 KiB
C++
595 lines
22 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: Loop.Unix.cpp
|
|
Date: 2021-10-1
|
|
Author: Reece
|
|
***/
|
|
#include <Source/RuntimeInternal.hpp>
|
|
#include "Loop.Unix.hpp"
|
|
#include "ILoopSourceEx.hpp"
|
|
#include <poll.h>
|
|
|
|
// TODO:
|
|
#if defined(AURORA_IS_LINUX_DERIVED)
|
|
|
|
namespace Aurora::IO::UNIX
|
|
{
|
|
bool LinuxOverlappedYield(bool bUserspaceOnly);
|
|
}
|
|
|
|
#endif
|
|
|
|
namespace Aurora::IO::Loop
|
|
{
|
|
extern AuRWRenterableLock gWaitForMultipleALLLock;
|
|
|
|
void ResetLoopSourceFalseAlarm(const AuSPtr<Loop::ILoopSource> &pLoopSource);
|
|
|
|
AuList<AuSPtr<ILoopSource>> WaitMultipleOrObjects(const AuList<AuSPtr<ILoopSource>> &objects, bool bAlert, bool bBreakAPCs, bool bZeroTick, AuUInt32 dwTimeoutReq, bool bAllowOthers, bool &bTooMany)
|
|
{
|
|
#if defined(AURORA_HAS_NO_POSIX_POLL)
|
|
// do the WinNT falllback into loop queue.
|
|
//
|
|
// loop queues should have long term persistency in a user-space ABI (kqueue) or in the kernel (epoll)
|
|
// they may take longer to setup, may seem wasteful to create and throw away, but it's probably the best we can do under BSD kernels.
|
|
// why? kevents exist for posix aio objects. they dont use fds.
|
|
//
|
|
// BSDs with posix AIO based async file transactions, >2.2 linux kernels, macos with corefoundation interop with kqueues, and other posix kernels with alternative aio will probably need
|
|
// need to use the win32 64 <= handles path.
|
|
bTooMany = true;
|
|
return false;
|
|
#else
|
|
AuList<AuSPtr<ILoopSource>> triggered;
|
|
|
|
// fallback
|
|
AuList<AuSPtr<ILoopSourceEx>> loopSourceExs1;
|
|
AuList<AuSPtr<ILoopSourceEx>> loopSourceExs21;
|
|
AuList<pollfd> handleArray1;
|
|
AuList<AuPair<AuUInt, bool>> handleIndicies1;
|
|
|
|
// 4k~ of stack
|
|
AuSPtr<ILoopSourceEx> loopSourceExs12[128];
|
|
AuSPtr<ILoopSourceEx> loopSourceExs22[128];
|
|
pollfd handleArray2[128];
|
|
AuPair<AuUInt, bool> handleIndicies2[128];
|
|
|
|
// phead
|
|
AuSPtr<ILoopSourceEx> * pLoopSourceExs1;
|
|
AuSPtr<ILoopSourceEx> * pLoopSourceExs2;
|
|
pollfd * pHandleArray;
|
|
AuPair<AuUInt, bool> * pHandleIndicies;
|
|
AuUInt32 uHandleOffset, uFDOffset, uHandleOffset2;
|
|
|
|
// length / size
|
|
uHandleOffset = 0;
|
|
uHandleOffset2 = 0;
|
|
uFDOffset = 0;
|
|
|
|
// default heads
|
|
pLoopSourceExs1 = loopSourceExs12;
|
|
pLoopSourceExs2 = loopSourceExs22;
|
|
pHandleArray = handleArray2;
|
|
pHandleIndicies = handleIndicies2;
|
|
|
|
#define HANDLE_PUSH_CHILD_Z(value, aaa, bbb, ccc, ggg, FF) \
|
|
{ \
|
|
if (FF > AuArraySize(aaa)) \
|
|
{ \
|
|
ccc.push_back(value); \
|
|
bbb = ccc.data(); \
|
|
} \
|
|
else if (FF == AuArraySize(aaa)) \
|
|
{ \
|
|
ccc.insert(ccc.begin(), &ggg[0], &ggg[AuArraySize(aaa)]); \
|
|
ccc.push_back(value); \
|
|
bbb = ccc.data(); \
|
|
} \
|
|
else \
|
|
{ \
|
|
bbb[FF] = value; \
|
|
} \
|
|
}
|
|
|
|
#define HANDLE_PUSH_MAIN2(value) \
|
|
HANDLE_PUSH_CHILD_Z(value, loopSourceExs22, pLoopSourceExs2, loopSourceExs21, pLoopSourceExs2, uCurHandle)
|
|
|
|
#define HANDLE_PUSH_MAIN(value, handle) \
|
|
HANDLE_PUSH_CHILD_Z(value, loopSourceExs22, pLoopSourceExs1, loopSourceExs1, pLoopSourceExs1, handle)
|
|
|
|
#define HANDLE_PUSH_CHILD(value) \
|
|
{ \
|
|
auto uCurFD = uFDOffset++; \
|
|
HANDLE_PUSH_CHILD_Z(value, loopSourceExs22, pHandleArray, handleArray1, handleArray2, uCurFD) \
|
|
}
|
|
|
|
#define HANDLE_PUSH_CHILD2(value) \
|
|
{ \
|
|
HANDLE_PUSH_CHILD_Z(value, loopSourceExs22, pHandleIndicies, handleIndicies1, handleIndicies2, (uFDOffset - 1)) \
|
|
}
|
|
|
|
try
|
|
{
|
|
if (AuArraySize(loopSourceExs22) < objects.size())
|
|
{
|
|
loopSourceExs1.reserve(objects.size());
|
|
loopSourceExs21.reserve(objects.size());
|
|
handleArray1.reserve(objects.size());
|
|
handleIndicies1.reserve(triggered.size());
|
|
}
|
|
|
|
triggered.reserve(triggered.size());
|
|
|
|
for (const auto &source : objects)
|
|
{
|
|
if (!source)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (auto extended = AuDynamicCast<ILoopSourceEx>(source))
|
|
{
|
|
auto uCurHandle = uHandleOffset++;
|
|
|
|
HANDLE_PUSH_MAIN2(extended)
|
|
|
|
if (extended->Singular())
|
|
{
|
|
auto handle = extended->GetHandle();
|
|
auto handleWrite = extended->GetWriteHandle();
|
|
|
|
auto i = uHandleOffset2++;
|
|
HANDLE_PUSH_MAIN(extended, i);
|
|
|
|
if (handle != -1)
|
|
{
|
|
pollfd poll;
|
|
poll.fd = handle;
|
|
poll.events = POLLIN;
|
|
poll.revents = 0;
|
|
|
|
HANDLE_PUSH_CHILD(poll);
|
|
HANDLE_PUSH_CHILD2(AuMakePair(i, false));
|
|
}
|
|
|
|
if (handleWrite != -1)
|
|
{
|
|
pollfd poll;
|
|
poll.fd = handleWrite;
|
|
poll.events = POLLOUT;
|
|
poll.revents = 0;
|
|
|
|
HANDLE_PUSH_CHILD(poll);
|
|
HANDLE_PUSH_CHILD2(AuMakePair(i, false));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto handles = extended->GetHandles();
|
|
auto handlesWrite = extended->GetWriteHandles();
|
|
|
|
auto i = uHandleOffset2++;
|
|
HANDLE_PUSH_MAIN(extended, i);
|
|
|
|
for (const auto &handle : handles)
|
|
{
|
|
pollfd poll;
|
|
if (handle == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
poll.fd = handle;
|
|
poll.events = POLLIN;
|
|
poll.revents = 0;
|
|
|
|
HANDLE_PUSH_CHILD(poll);
|
|
HANDLE_PUSH_CHILD2(AuMakePair(i, false));
|
|
}
|
|
|
|
for (const auto &handle : handlesWrite)
|
|
{
|
|
pollfd poll;
|
|
if (handle == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
poll.fd = handle;
|
|
poll.events = POLLOUT;
|
|
poll.revents = 0;
|
|
|
|
HANDLE_PUSH_CHILD(poll);
|
|
HANDLE_PUSH_CHILD2(AuMakePair(i, false));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
// must be 1:1 - do not mess with
|
|
for (AU_ITERATE_N(i, uHandleOffset))
|
|
{
|
|
pLoopSourceExs2[i]->OnPresleep();
|
|
}
|
|
|
|
AuUInt32 uTimeout {};
|
|
if (bZeroTick)
|
|
{
|
|
uTimeout = 0;
|
|
}
|
|
else if (!dwTimeoutReq)
|
|
{
|
|
uTimeout = -1;
|
|
}
|
|
else
|
|
{
|
|
uTimeout = dwTimeoutReq;
|
|
}
|
|
|
|
int ret;
|
|
do
|
|
{
|
|
// TODO: bAlert awareness (cannot enter alert sleep yet. no such impl on linux)
|
|
// (we need an io_submit posix poll shim + apc event + queue that we dont have yet)
|
|
ret = poll(pHandleArray, uFDOffset, uTimeout);
|
|
uTimeout = 0;
|
|
|
|
if (ret > 0)
|
|
{
|
|
AU_LOCK_GLOBAL_GUARD(gWaitForMultipleALLLock->AsReadable());
|
|
|
|
for (AU_ITERATE_N(i, uFDOffset))
|
|
{
|
|
if (!pHandleArray[i].revents)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto uIndex = AuGet<0>(pHandleIndicies[i]);
|
|
auto bRead = AuGet<1>(pHandleIndicies[i]);
|
|
auto pLoopSource = AuExchange(pLoopSourceExs1[uIndex], nullptr);
|
|
if (!pLoopSource)
|
|
{
|
|
// TODO: notify other? special types go here?
|
|
continue;
|
|
}
|
|
|
|
if (!pLoopSource->OnTrigger(pHandleArray[i].fd))
|
|
{
|
|
pLoopSourceExs1[uIndex] = pLoopSource;
|
|
continue;
|
|
}
|
|
|
|
triggered.push_back(pLoopSource);
|
|
|
|
if (!bAllowOthers)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (ret == -1 &&
|
|
errno == EINTR);
|
|
|
|
// must be 1:1 - do not mess with
|
|
for (AU_ITERATE_N(i, uHandleOffset))
|
|
{
|
|
pLoopSourceExs2[i]->OnFinishSleep();
|
|
}
|
|
|
|
if (bAlert)
|
|
{
|
|
#if defined(AURORA_IS_LINUX_DERIVED)
|
|
// Do not syscall after read or select again under Linux
|
|
UNIX::LinuxOverlappedYield(true);
|
|
#else
|
|
IOYield();
|
|
#endif
|
|
}
|
|
|
|
return AuMove(triggered);
|
|
#endif
|
|
}
|
|
|
|
bool WaitMultipleAndObjects_(const AuList<AuSPtr<ILoopSource>> &objects, bool bAlert, bool bBreakAPCs, bool bZeroTick, AuUInt64 qwTimeoutAbs, bool &bTooMany, bool &bTimeout)
|
|
{
|
|
#if defined(AURORA_HAS_NO_POSIX_POLL)
|
|
// do the WinNT falllback into loop queue.
|
|
//
|
|
// loop queues should have long term persistency in a user-space ABI (kqueue) or in the kernel (epoll)
|
|
// they may take longer to setup, may seem wasteful to create and throw away, but it's probably the best we can do under BSD kernels.
|
|
// why? kevents exist for posix aio objects. they dont use fds.
|
|
//
|
|
// BSDs with posix AIO based async file transactions, >2.2 linux kernels, macos with corefoundation interop with kqueues, and other posix kernels with alternative aio will probably need
|
|
// need to use the win32 64 <= handles path.
|
|
bTooMany = true;
|
|
return false;
|
|
#else
|
|
AuList<AuSPtr<ILoopSource>> triggered;
|
|
|
|
// fallback
|
|
AuList<AuSPtr<ILoopSourceEx>> loopSourceExs1;
|
|
AuList<AuSPtr<ILoopSourceEx>> loopSourceExs21;
|
|
AuList<pollfd> handleArray1;
|
|
AuList<pollfd> handleArrayPersistent1;
|
|
AuList<AuPair<AuUInt, bool>> handleIndicies1;
|
|
|
|
// 4k~ of stack
|
|
AuSPtr<ILoopSourceEx> loopSourceExs12[128];
|
|
AuSPtr<ILoopSourceEx> loopSourceExs22[128];
|
|
pollfd handleArray2[128];
|
|
pollfd handleArrayPersistent2[128];
|
|
AuPair<AuUInt, bool> handleIndicies2[128];
|
|
|
|
// phead
|
|
AuSPtr<ILoopSourceEx> * pLoopSourceExs1;
|
|
AuSPtr<ILoopSourceEx> * pLoopSourceExs2;
|
|
pollfd * pHandleArray;
|
|
pollfd * pHandleArrayPersistent;
|
|
AuPair<AuUInt, bool> * pHandleIndicies;
|
|
AuUInt32 uHandleOffset, uFDOffset, uHandleOffset2;
|
|
|
|
// length / size
|
|
uHandleOffset = 0;
|
|
uHandleOffset2 = 0;
|
|
uFDOffset = 0;
|
|
|
|
// default heads
|
|
pLoopSourceExs1 = loopSourceExs12;
|
|
pLoopSourceExs2 = loopSourceExs22;
|
|
pHandleArray = handleArray2;
|
|
pHandleIndicies = handleIndicies2;
|
|
pHandleArrayPersistent = handleArrayPersistent2;
|
|
|
|
AuUInt32 uTotal { 0 };
|
|
|
|
|
|
#define HANDLE_PUSH_CHILD3(value) \
|
|
{ \
|
|
HANDLE_PUSH_CHILD_Z(value, loopSourceExs22, pHandleArrayPersistent, handleArrayPersistent1, handleArrayPersistent2, (uFDOffset - 1)) \
|
|
}
|
|
|
|
try
|
|
{
|
|
if (AuArraySize(loopSourceExs22) < objects.size())
|
|
{
|
|
loopSourceExs1.reserve(objects.size());
|
|
loopSourceExs21.reserve(objects.size());
|
|
handleArray1.reserve(objects.size());
|
|
handleIndicies1.reserve(triggered.size());
|
|
}
|
|
|
|
triggered.reserve(triggered.size());
|
|
|
|
for (const auto &source : objects)
|
|
{
|
|
if (!source)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (auto extended = AuDynamicCast<ILoopSourceEx>(source))
|
|
{
|
|
auto uCurHandle = uHandleOffset++;
|
|
|
|
HANDLE_PUSH_MAIN2(extended)
|
|
|
|
if (extended->Singular())
|
|
{
|
|
auto handle = extended->GetHandle();
|
|
auto handleWrite = extended->GetWriteHandle();
|
|
|
|
auto i = uHandleOffset2++;
|
|
HANDLE_PUSH_MAIN(extended, i);
|
|
|
|
if (handle != -1)
|
|
{
|
|
pollfd poll;
|
|
poll.fd = handle;
|
|
poll.events = POLLIN;
|
|
poll.revents = 0;
|
|
|
|
HANDLE_PUSH_CHILD(poll);
|
|
HANDLE_PUSH_CHILD2(AuMakePair(i, false));
|
|
HANDLE_PUSH_CHILD3(poll)
|
|
}
|
|
|
|
if (handleWrite != -1)
|
|
{
|
|
pollfd poll;
|
|
poll.fd = handleWrite;
|
|
poll.events = POLLOUT;
|
|
poll.revents = 0;
|
|
|
|
HANDLE_PUSH_CHILD(poll);
|
|
HANDLE_PUSH_CHILD2(AuMakePair(i, false));
|
|
HANDLE_PUSH_CHILD3(poll)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto handles = extended->GetHandles();
|
|
auto handlesWrite = extended->GetWriteHandles();
|
|
|
|
auto i = uHandleOffset2++;
|
|
HANDLE_PUSH_MAIN(extended, i);
|
|
|
|
for (const auto &handle : handles)
|
|
{
|
|
pollfd poll;
|
|
if (handle == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
poll.fd = handle;
|
|
poll.events = POLLIN;
|
|
poll.revents = 0;
|
|
|
|
HANDLE_PUSH_CHILD(poll);
|
|
HANDLE_PUSH_CHILD2(AuMakePair(i, false));
|
|
HANDLE_PUSH_CHILD3(poll)
|
|
}
|
|
|
|
for (const auto &handle : handlesWrite)
|
|
{
|
|
pollfd poll;
|
|
if (handle == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
poll.fd = handle;
|
|
poll.events = POLLOUT;
|
|
poll.revents = 0;
|
|
|
|
HANDLE_PUSH_CHILD(poll);
|
|
HANDLE_PUSH_CHILD2(AuMakePair(i, false));
|
|
HANDLE_PUSH_CHILD3(poll)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
// must be 1:1 - do not mess with
|
|
for (AU_ITERATE_N(i, uHandleOffset))
|
|
{
|
|
pLoopSourceExs2[i]->OnPresleep();
|
|
}
|
|
|
|
int ret;
|
|
do
|
|
{
|
|
AuUInt32 uTimeout;
|
|
|
|
if (bZeroTick)
|
|
{
|
|
uTimeout = 0;
|
|
}
|
|
else if (qwTimeoutAbs)
|
|
{
|
|
auto uNow = AuTime::SteadyClockNS();
|
|
if (uNow >= qwTimeoutAbs)
|
|
{
|
|
uTimeout = 0;
|
|
}
|
|
else
|
|
{
|
|
uTimeout = AuNSToMS<AuUInt32>(qwTimeoutAbs - uNow);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uTimeout = -1;
|
|
}
|
|
|
|
ret = poll(pHandleArray, uFDOffset, uTimeout);
|
|
uTimeout = 0;
|
|
|
|
if (ret > 0)
|
|
{
|
|
for (AU_ITERATE_N(i, uFDOffset))
|
|
{
|
|
if (!pHandleArray[i].revents)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
uTotal += 1;
|
|
pHandleArrayPersistent[i] = pHandleArray[i];
|
|
pHandleArray[i].fd = -1;
|
|
}
|
|
}
|
|
|
|
if (qwTimeoutAbs)
|
|
{
|
|
auto uNow = AuTime::SteadyClockNS();
|
|
if (uNow >= qwTimeoutAbs)
|
|
{
|
|
bTimeout = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while ((ret == -1 &&
|
|
errno == EINTR) || (uTotal != uFDOffset));
|
|
|
|
bool bRet = uTotal == uFDOffset;
|
|
|
|
if (bRet)
|
|
{
|
|
AU_LOCK_GLOBAL_GUARD(gWaitForMultipleALLLock->AsWritable());
|
|
|
|
for (AU_ITERATE_N(i, uFDOffset))
|
|
{
|
|
auto uIndex = AuGet<0>(pHandleIndicies[i]);
|
|
auto bRead = AuGet<1>(pHandleIndicies[i]);
|
|
auto pLoopSource = AuExchange(pLoopSourceExs1[uIndex], nullptr);
|
|
if (!pLoopSource)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!pLoopSource->OnTrigger(pHandleArrayPersistent[i].fd))
|
|
{
|
|
for (AU_ITERATE_N(z, i))
|
|
{
|
|
ResetLoopSourceFalseAlarm(pLoopSourceExs1[z]);
|
|
}
|
|
|
|
bRet = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// must be 1:1 - do not mess with
|
|
for (AU_ITERATE_N(i, uHandleOffset))
|
|
{
|
|
pLoopSourceExs2[i]->OnFinishSleep();
|
|
}
|
|
|
|
return bRet;
|
|
#endif
|
|
}
|
|
|
|
bool WaitMultipleAndObjects(const AuList<AuSPtr<ILoopSource>> &objects, bool bAlert, bool bBreakAPCs, bool bZeroTick, AuUInt64 qwTimeoutAbs, bool &bTooMany, bool &bTimeout)
|
|
{
|
|
bool bRet {};
|
|
do
|
|
{
|
|
bool bAPCDispatch {};
|
|
|
|
bRet = WaitMultipleAndObjects_(objects, bAlert, bBreakAPCs, bZeroTick, qwTimeoutAbs, bTooMany, bTimeout);
|
|
|
|
if (bAlert)
|
|
{
|
|
#if defined(AURORA_IS_LINUX_DERIVED)
|
|
// Do not syscall after read or select again under Linux
|
|
bAPCDispatch = UNIX::LinuxOverlappedYield(true);
|
|
#else
|
|
bAPCDispatch = IOYield();
|
|
#endif
|
|
}
|
|
|
|
if (bBreakAPCs && bAPCDispatch)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
while (!bRet && !bTimeout);
|
|
|
|
return bRet;
|
|
}
|
|
} |