AuroraRuntime/Source/IO/Loop/Loop.Unix.cpp

595 lines
22 KiB
C++
Raw Normal View History

/***
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;
}
}