/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Loop.Unix.cpp Date: 2021-10-1 Author: Reece ***/ #include #include "Loop.Unix.hpp" #include "ILoopSourceEx.hpp" #include // 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 &pLoopSource); AuList> WaitMultipleOrObjects(const AuList> &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> triggered; // fallback AuList> loopSourceExs1; AuList> loopSourceExs21; AuList handleArray1; AuList> handleIndicies1; // 4k~ of stack AuSPtr loopSourceExs12[128]; AuSPtr loopSourceExs22[128]; pollfd handleArray2[128]; AuPair handleIndicies2[128]; // phead AuSPtr * pLoopSourceExs1; AuSPtr * pLoopSourceExs2; pollfd * pHandleArray; AuPair * 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(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> &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> triggered; // fallback AuList> loopSourceExs1; AuList> loopSourceExs21; AuList handleArray1; AuList handleArrayPersistent1; AuList> handleIndicies1; // 4k~ of stack AuSPtr loopSourceExs12[128]; AuSPtr loopSourceExs22[128]; pollfd handleArray2[128]; pollfd handleArrayPersistent2[128]; AuPair handleIndicies2[128]; // phead AuSPtr * pLoopSourceExs1; AuSPtr * pLoopSourceExs2; pollfd * pHandleArray; pollfd * pHandleArrayPersistent; AuPair * 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(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(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> &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; } }