Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
WaitForIOReady.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <mutex>
7#include <optional>
8#include <version>
9#if defined(__cpp_lib_jthread)
10#include <stop_token>
11#endif
12
13#if qStroika_Platform_POSIX
14#include <csignal>
15#include <fcntl.h>
16#include <poll.h>
17#include <pthread.h>
18#include <unistd.h>
19#if qStroika_Platform_Linux
20#include <sys/eventfd.h>
21#endif
22#elif qStroika_Platform_Windows
23#include <Windows.h>
24
25#include <winsock2.h>
26
27#include <ws2tcpip.h>
28#endif
29
30#include "Stroika/Foundation/Containers/Sequence.h"
33
34#include "Stroika/Foundation/Execution/Exceptions.h"
37#if qStroika_Platform_Windows
38#include "Stroika/Foundation/Execution/Platform/Windows/WaitSupport.h"
39#endif
40
41#include "Stroika/Foundation/IO/Network/ConnectionOrientedStreamSocket.h"
42#if qStroika_Platform_Windows
43#include "Stroika/Foundation/IO/Network/Platform/Windows/WinSock.h"
44#endif
45
46#include "WaitForIOReady.h"
47
48using namespace Stroika::Foundation;
51using namespace Stroika::Foundation::Execution;
52using namespace Stroika::Foundation::Execution::WaitForIOReady_Support;
54
55using std::byte;
56
60
61// Comment this in to turn on aggressive noisy DbgTrace in this module
62// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
63
64namespace {
65
66 /*
67 * The flag (IsSet ()) must agree with the descriptor's readability, and Set () and Clear () each change BOTH -
68 * so each must be ONE step, hence fMutex_. Two atomic steps are not an atomic pair: one call landing between
69 * the other's two steps leaves them disagreeing, for good - @see https://github.com/SophistSolutions/Stroika/issues/1175
70 * - "set" with nothing to read: every later Set () is a no-op, so a wait in progress is never woken
71 * - "clear" but readable: every later Clear () skips the drain, so every wait returns at once - a spin
72 *
73 * Holding fMutex_ across the syscall is safe, because no backend's _WriteOne () or _ReadAllAvail () can WAIT:
74 * - eventfd, pipe, UDP socket: the descriptors are non-blocking, so a read with nothing to read, or a
75 * write with no room, fails at once (EAGAIN / WSAEWOULDBLOCK, which they treat as done) instead of
76 * waiting. Not that a write ever finds no room: the flag allows only ONE unread write between drains.
77 * - socket pair: the read is ReadNonBlocking (). The write is an ordinary blocking Write (), but it
78 * could only block on a full socket buffer, and again there is at most one unread byte.
79 * So a Set () waits for a concurrent Clear () (or vice versa) for at most one such syscall - which matters,
80 * since Set () runs inside a std::stop_callback (@see SetEventFD_). Nor can holding it deadlock: nothing
81 * acquired while it is held (the socket pair's sockets take their own internal locks) ever waits on an EventFD.
82 *
83 * The syscall comes first, the flag second, so that one which throws leaves the two still agreeing.
84 *
85 * Clear ()'s drain also assumes a write that has returned is readable at once, so that it cannot arrive after
86 * the drain and leave "clear" but readable. Guaranteed for eventfd and pipe; what loopback sockets do in practice.
87 *
88 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
89 */
90 struct EventFD_Based_ : public EventFD {
91
92 EventFD_Based_ () = default;
93 virtual bool IsSet () const override
94 {
95 return fIsSet_;
96 }
97 virtual void Set () override
98 {
99 // If already set, nothing todo. To set, write so anybody selecting will wakeup, and set flag
100 scoped_lock critSec{fMutex_};
101 if (not fIsSet_) {
102 _WriteOne (); // so select calls wake
103 fIsSet_ = true;
104 }
105 }
106 virtual void Clear () override
107 {
108 scoped_lock critSec{fMutex_};
109 if (fIsSet_) {
110 _ReadAllAvail (); // so select calls don't prematurely wake
111 fIsSet_ = false;
112 }
113 }
114
115 protected:
116 virtual void _ReadAllAvail () = 0;
117 virtual void _WriteOne () = 0;
118
119 private:
120 mutex fMutex_; // makes Set () and Clear () each one step - flag and descriptor together
121 atomic<bool> fIsSet_{false}; // atomic only so IsSet () need not lock
122 };
123
124#if qStroika_Platform_Linux
125 /*
126 * Linux: an eventfd (2) - ONE descriptor, one syscall to create, and no network stack involved. In its default
127 * (non-semaphore) mode a write adds to a 64-bit counter and a single read returns it and resets it to zero, so
128 * _ReadAllAvail () is one read.
129 *
130 * Non-blocking, so neither call can wait, and hence neither can see EINTR: a read of a zero counter is EAGAIN,
131 * and a write only blocks at a counter of 2^64-2, unreachable at one write per Set ().
132 *
133 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
134 */
135 struct EventFD_Based_eventfd_ : EventFD_Based_ {
136 EventFD_Based_eventfd_ ()
137 : fFD_{ThrowPOSIXErrNoIfNegative (::eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC))}
138 {
139 }
140 ~EventFD_Based_eventfd_ ()
141 {
142 ::close (fFD_);
143 }
144 virtual pair<SDKPollableType, WaitForIOReady_Base::TypeOfMonitorSet> GetWaitInfo () override
145 {
146 return pair<SDKPollableType, WaitForIOReady_Base::TypeOfMonitorSet>{
147 fFD_, WaitForIOReady_Base::TypeOfMonitorSet{WaitForIOReady_Base::TypeOfMonitor::eRead}};
148 }
149 virtual void _ReadAllAvail () override
150 {
151 uint64_t counter;
152 if (::read (fFD_, &counter, sizeof (counter)) < 0 and errno != EAGAIN) {
154 }
155 }
156 virtual void _WriteOne () override
157 {
158 constexpr uint64_t kOne_{1};
159 ThrowPOSIXErrNoIfNegative (::write (fFD_, &kOne_, sizeof (kOne_)));
160 }
161 const int fFD_;
162 };
163#elif qStroika_Platform_POSIX
164 /*
165 * Other POSIX (macOS, BSD): a pipe. Two descriptors, but no network stack - where a TCP socket pair would also
166 * need a loopback connection set up.
167 *
168 * Both ends non-blocking, so neither call can wait (and hence see EINTR). A write cannot fill the pipe in
169 * practice - Set () writes once per Set/Clear cycle - and if it ever did, EAGAIN is harmless: the pipe is
170 * then readable, which is all Set () has to achieve.
171 *
172 * \note Not pipe2 (): macOS lacks it, so O_NONBLOCK and FD_CLOEXEC are set after the fact.
173 *
174 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
175 */
176 struct EventFD_Based_Pipe_ : EventFD_Based_ {
177 EventFD_Based_Pipe_ ()
178 {
179 int fds[2];
180 ThrowPOSIXErrNoIfNegative (::pipe (fds));
181 for (int fd : fds) {
182 if (::fcntl (fd, F_SETFL, ::fcntl (fd, F_GETFL) | O_NONBLOCK) < 0 or ::fcntl (fd, F_SETFD, FD_CLOEXEC) < 0) {
183 int e = errno;
184 ::close (fds[0]);
185 ::close (fds[1]);
186 ThrowPOSIXErrNo (e);
187 }
188 }
189 fReadFD_ = fds[0];
190 fWriteFD_ = fds[1];
191 }
192 ~EventFD_Based_Pipe_ ()
193 {
194 ::close (fReadFD_);
195 ::close (fWriteFD_);
196 }
197 virtual pair<SDKPollableType, WaitForIOReady_Base::TypeOfMonitorSet> GetWaitInfo () override
198 {
199 return pair<SDKPollableType, WaitForIOReady_Base::TypeOfMonitorSet>{
200 fReadFD_, WaitForIOReady_Base::TypeOfMonitorSet{WaitForIOReady_Base::TypeOfMonitor::eRead}};
201 }
202 virtual void _ReadAllAvail () override
203 {
204 byte buf[64];
205 ssize_t n;
206 while ((n = ::read (fReadFD_, buf, sizeof (buf))) > 0)
207 ;
208 if (n < 0 and errno != EAGAIN) {
210 }
211 }
212 virtual void _WriteOne () override
213 {
214 constexpr byte kOne_{1};
215 if (::write (fWriteFD_, &kOne_, 1) < 0 and errno != EAGAIN) {
217 }
218 }
219 int fReadFD_;
220 int fWriteFD_;
221 };
222#elif qStroika_Platform_Windows
223 /*
224 * Windows: WSAPoll () takes sockets and nothing else - no pipe, no event HANDLE (@see WaitForIOReady.h) - and
225 * only I/O on one of its sockets can wake it. The cheapest such socket is ONE loopback UDP socket connected to
226 * itself: the obvious alternative, a TCP socket pair, costs two sockets plus a listener while it connects, and a
227 * handshake, where this is a few local calls. Being connected, it also only accepts datagrams from itself, so
228 * nothing else on the machine can wake it.
229 *
230 * Non-blocking, so neither call can wait. A datagram to one's own loopback address is only lost to a full
231 * receive buffer, and Set () sends at most one per Set/Clear cycle.
232 *
233 * \note WSA_FLAG_NO_HANDLE_INHERIT - the Windows equivalent of CLOEXEC - so child processes do not get it.
234 *
235 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
236 */
237 struct EventFD_Based_UDPLoopback_ : EventFD_Based_ {
238 EventFD_Based_UDPLoopback_ ()
239 {
240 IO::Network::Platform::Windows::WinSock::AssureStarted ();
241 fSocket_ = ::WSASocketW (AF_INET, SOCK_DGRAM, IPPROTO_UDP, nullptr, 0, WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT);
242 if (fSocket_ == INVALID_SOCKET) {
243 ThrowSystemErrNo (::WSAGetLastError ());
244 }
245 sockaddr_in addr{};
246 addr.sin_family = AF_INET;
247 addr.sin_addr.s_addr = ::htonl (INADDR_LOOPBACK);
248 addr.sin_port = 0; // any free port - getsockname () says which
249 int addrLen = sizeof (addr);
250 u_long nonBlocking = 1;
251 if (::bind (fSocket_, reinterpret_cast<const sockaddr*> (&addr), sizeof (addr)) == SOCKET_ERROR or
252 ::getsockname (fSocket_, reinterpret_cast<sockaddr*> (&addr), &addrLen) == SOCKET_ERROR or
253 ::connect (fSocket_, reinterpret_cast<const sockaddr*> (&addr), sizeof (addr)) == SOCKET_ERROR or // to itself
254 ::ioctlsocket (fSocket_, FIONBIO, &nonBlocking) == SOCKET_ERROR) {
255 int e = ::WSAGetLastError ();
256 ::closesocket (fSocket_);
257 ThrowSystemErrNo (e);
258 }
259 }
260 ~EventFD_Based_UDPLoopback_ ()
261 {
262 ::closesocket (fSocket_);
263 }
264 virtual pair<SDKPollableType, WaitForIOReady_Base::TypeOfMonitorSet> GetWaitInfo () override
265 {
266 return pair<SDKPollableType, WaitForIOReady_Base::TypeOfMonitorSet>{
267 fSocket_, WaitForIOReady_Base::TypeOfMonitorSet{WaitForIOReady_Base::TypeOfMonitor::eRead}};
268 }
269 virtual void _ReadAllAvail () override
270 {
271 char buf[64];
272 while (::recv (fSocket_, buf, sizeof (buf), 0) != SOCKET_ERROR)
273 ;
274 if (int e = ::WSAGetLastError (); e != WSAEWOULDBLOCK) {
275 ThrowSystemErrNo (e);
276 }
277 }
278 virtual void _WriteOne () override
279 {
280 constexpr char kOne_{1};
281 if (::send (fSocket_, &kOne_, 1, 0) == SOCKET_ERROR) {
282 if (int e = ::WSAGetLastError (); e != WSAEWOULDBLOCK) {
283 ThrowSystemErrNo (e);
284 }
285 }
286 }
287 SOCKET fSocket_;
288 };
289#endif
290
291 /*
292 * The portable fallback: works anywhere with sockets, at the cost of two sockets and a loopback TCP connection
293 * to set up. No platform Stroika supports today selects it (@see mkEventFD) - it was the only implementation
294 * until https://github.com/SophistSolutions/Stroika/issues/843 - but it is kept compiled everywhere, as the
295 * reference implementation and for any platform Stroika might someday support that has none of the above.
296 *
297 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
298 */
299 struct EventFD_Based_SocketPair_ : EventFD_Based_ {
300 EventFD_Based_SocketPair_ ()
301 {
302 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("EventFD_Based_SocketPair_::CTOR")};
303 auto [r, w] = ConnectionOrientedStreamSocket::NewPair (SocketAddress::FamilyType::INET, Socket::Type::STREAM);
304 fReadSocket_ = r;
305 fWriteSocket_ = w;
306 }
307 ConnectionOrientedStreamSocket::Ptr fReadSocket_{nullptr};
308 ConnectionOrientedStreamSocket::Ptr fWriteSocket_{nullptr};
309
310 virtual pair<SDKPollableType, WaitForIOReady_Base::TypeOfMonitorSet> GetWaitInfo () override
311 {
312 // Poll on read FD to see if data available to read
313 return pair<SDKPollableType, WaitForIOReady_Base::TypeOfMonitorSet>{
314 fReadSocket_.GetNativeSocket (), WaitForIOReady_Base::TypeOfMonitorSet{WaitForIOReady_Base::TypeOfMonitor::eRead}};
315 }
316 virtual void _ReadAllAvail () override
317 {
318 // thread safety OK cuz only reading from Ptr (nobody writes) and socket rep internally synchronized
319 byte buf[1024];
320 while (fReadSocket_.ReadNonBlocking (buf))
321 ;
322 }
323 virtual void _WriteOne () override
324 {
325 // thread safety OK cuz only reading from Ptr (nobody writes) and socket rep internally synchronized
326 constexpr byte kOne_{1};
327 fWriteSocket_.Write (span{&kOne_, 1});
328 }
329 };
330
331}
332
333/*
334 ********************************************************************************
335 *********** Execution::WaitForIOReady::WaitForIOReady_Support::mkEventFD *******
336 ********************************************************************************
337 */
338unique_ptr<EventFD> WaitForIOReady_Support::mkEventFD ()
339{
340 Debug::TraceContextBumper ctx{"WaitForIOReady_Support::mkEventFD"};
341 // the cheapest pollable channel each platform offers - https://github.com/SophistSolutions/Stroika/issues/843
342#if qStroika_Platform_Linux
343 return make_unique<EventFD_Based_eventfd_> ();
344#elif qStroika_Platform_POSIX
345 return make_unique<EventFD_Based_Pipe_> ();
346#elif qStroika_Platform_Windows
347 return make_unique<EventFD_Based_UDPLoopback_> ();
348#else
349 return make_unique<EventFD_Based_SocketPair_> ();
350#endif
351}
352
353#if qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup
354namespace {
355 /*
356 * The wakeup channel a thread's abort writes to. One per thread, created on first wait and kept for the
357 * life of the thread: a thread can only be inside one of these waits at a time, so one suffices, and
358 * it costs descriptors for as long as it lives - @see mkEventFD () for what each platform uses.
359 */
360 EventFD& GetThreadAbortWakeupEventFD_ ()
361 {
362 static thread_local std::unique_ptr<EventFD> tWakeup_{WaitForIOReady_Support::mkEventFD ()};
363 return *tWakeup_;
364 }
365 /*
366 * Deliberately a named type rather than a lambda: std::stop_callback is a template on its callback
367 * type, so that type must be nameable to hold the registration in an optional<>.
368 *
369 * ***This must not throw, and that is not automatic.*** It runs on whichever thread called Abort () -
370 * or, when stop was already requested, on the WAITING thread from inside the stop_callback constructor.
371 * Either of those can itself be mid-abort, and EventFD::Set () may be a cancelation point - the socket pair
372 * fallback writes through a Stroika socket - so without suppressing interruption it can throw
373 * Thread::AbortException here, and a throw out of a stop_callback is std::terminate, both by this
374 * function's noexcept and by the standard's own rules for stop_callback. That is not theoretical: it
375 * terminated Tests/40 in 4 runs out of 6 before the suppression below was added.
376 *
377 * It also cannot block for long: EventFD::Set () writes at most one byte, and only when not already set, so it
378 * cannot stall on a full buffer - and its lock is only ever held for one such non-blocking syscall.
379 */
380 struct SetEventFD_ {
381 EventFD* fEventFD;
382 void operator() () const noexcept
383 {
384 Thread::SuppressInterruptionInContext suppressInterruption;
385 try {
386 fEventFD->Set ();
387 }
388 catch (...) {
389 // Nothing safe to do here but carry on: the waiter then falls back on its timeout, which is
390 // bad, but terminating the process is worse.
391 DbgTrace ("WARNING: WaitForIOReady abort wakeup Set () failed: {}"_f, Characters::ToString (std::current_exception ()));
392 }
393 }
394 };
395}
396#endif
397
398/*
399 ********************************************************************************
400 **************** Execution::WaitForIOReady::WaitForIOReady_Base ****************
401 ********************************************************************************
402 */
403auto WaitForIOReady_Base::_WaitQuietlyUntil (const pair<SDKPollableType, TypeOfMonitorSet>* start,
404 const pair<SDKPollableType, TypeOfMonitorSet>* end, TimePointSeconds timeoutAt) -> Containers::Set<size_t>
405{
406#if USE_NOISY_TRACE_IN_THIS_MODULE_
407 Debug::TraceContextBumper ctx{"WaitForIOReady_Base::_WaitQuietlyUntil", "args={}"_f, vector<pair<SDKPollableType, TypeOfMonitorSet>>{start, end}};
408#endif
409 DurationSeconds time2Wait = Math::AtLeast<DurationSeconds> (timeoutAt - Time::GetTickCount (), 0s);
411 const size_t nMonitored = static_cast<size_t> (end - start);
412 EventFD* abortWakeup_ = nullptr; // non-null once this wait can be woken by an abort
413#if qStroika_Foundation_Execution_WaitForIOReady_UsePPoll
414 /*
415 * The SECOND of the three wakeup mechanisms - @see
416 * qStroika_Foundation_Execution_WaitForIOReady_UsePPoll.
417 *
418 * Block the interrupt signal FIRST, and check for interruption with it blocked (which catches an abort
419 * that already happened). The original mask then goes to ppoll (), which unblocks the signal atomically
420 * for the duration of the wait, so an abort landing anywhere in the setup window leaves the signal
421 * pending instead of spending it.
422 */
423 sigset_t originalSignalMask;
424 {
425 sigset_t blockInterruptSignal;
426 ::sigemptyset (&blockInterruptSignal);
427 ::sigaddset (&blockInterruptSignal, Thread::SignalUsedForThreadInterrupt ());
428 Verify (::pthread_sigmask (SIG_BLOCK, &blockInterruptSignal, &originalSignalMask) == 0);
429 }
430 // must restore on EVERY exit path - the CheckForInterruption () below throws, and so can the wait
431 [[maybe_unused]] auto&& restoreSignalMask =
432 Finally ([&originalSignalMask] () noexcept { ::pthread_sigmask (SIG_SETMASK, &originalSignalMask, nullptr); });
433#endif
434#if qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup
435 /*
436 * Arm the abort wakeup BEFORE building the poll set, and keep it armed across the wait. @see the
437 * qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup docs for why this closes a window
438 * the interrupt signal alone cannot: the emplace () below runs the callback itself if stop was already
439 * requested, and Thread::Abort () runs it if stop is requested later - so the wakeup descriptor ends up
440 * readable either way, and no ordering of the two can lose it.
441 */
442 std::optional<std::stop_callback<SetEventFD_>> abortWakeupRegistration;
443 if (std::optional<std::stop_token> stopToken = Thread::GetCurrentThreadStopToken ()) {
444 /*
445 * If the abort has ALREADY landed - which includes landing in the window between the
446 * CheckForInterruption () above and here, the very window this code exists to close - then there is
447 * no wait to wake: just throw, which is where entering the wait would have had to end up anyway.
448 *
449 * Arming in that case would be actively wrong, not merely wasteful: std::stop_callback runs its
450 * callback during CONSTRUCTION when stop is already requested, and that construction happens right
451 * here, on a thread that is itself mid-abort. @see SetEventFD_.
452 */
453 if (stopToken->stop_requested ()) {
454 Thread::CheckForInterruption (); // throws - unless interruption is suppressed in this context,
455 // in which case arming below is safe for the same reason
456 }
457 abortWakeup_ = &GetThreadAbortWakeupEventFD_ ();
458 abortWakeup_->Clear (); // discard anything left over from an earlier wait on this thread
459 abortWakeupRegistration.emplace (*stopToken, SetEventFD_{abortWakeup_});
460 }
461#endif
462 StackBuffer<pollfd> pollData;
463 {
464 pollData.GrowToSize_uninitialized (nMonitored + (abortWakeup_ == nullptr ? 0 : 1));
465 size_t idx = 0;
466 for (auto i = start; i != end; ++i) {
467 short events = 0;
468 for (TypeOfMonitor ii : i->second) {
469 switch (ii) {
470 case TypeOfMonitor::eRead:
471 events |= POLLIN;
472 break;
473 case TypeOfMonitor::eWrite:
474 events |= POLLOUT;
475 break;
476 case TypeOfMonitor::ePriority:
477 events |= POLLPRI;
478 break;
479 }
480 }
481 pollData[idx] = pollfd{i->first, events, 0};
482 Assert (pollData[idx].revents == 0);
483 idx++;
484 }
485 if (abortWakeup_ != nullptr) {
486 Assert (idx == nMonitored);
487 // read-monitored: it becomes readable when the abort callback Set ()s it
488 pollData[idx] = pollfd{abortWakeup_->GetWaitInfo ().first, POLLIN, 0};
489 }
490 }
491 [[maybe_unused]] int timeoutMilliseconds = Math::Round<int> (time2Wait.count () * 1000);
492 Assert (timeoutMilliseconds >= 0);
493 int pollResult;
494 /*
495 * Exactly ONE wakeup mechanism is compiled in per configuration - @see the three
496 * qStroika_Foundation_Execution_WaitForIOReady_* macros. Stacking them would only let a failure of the
497 * primary one show up as a delay rather than as a failure.
498 */
499#if qStroika_Platform_Windows
500#if !qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup
501 Require (sPollWaitChunkTime > 0s);
502 while (true) {
504 DurationSeconds timeLeft2Wait = Math::AtLeast<DurationSeconds> (timeoutAt - Time::GetTickCount (), 0s);
505 DurationSeconds time2WaitThisLoop = clamp<DurationSeconds> (timeLeft2Wait, 0s, sPollWaitChunkTime);
506 int time2WaitMillisecondsThisLoop = static_cast<int> (time2WaitThisLoop.count () * 1000);
507 if ((pollResult = ::WSAPoll (pollData.begin (), static_cast<ULONG> (pollData.GetSize ()), time2WaitMillisecondsThisLoop)) == SOCKET_ERROR) {
508 ThrowSystemErrNo (::WSAGetLastError ());
509 }
510 if (pollResult != 0 or Time::GetTickCount () >= timeoutAt) {
511 break;
512 }
513 }
514#else
515 // abortWakeup_ is in this poll set, so one WSAPoll spanning the whole timeout wakes just as promptly
516 if ((pollResult = ::WSAPoll (pollData.begin (), static_cast<ULONG> (pollData.GetSize ()), timeoutMilliseconds)) == SOCKET_ERROR) {
517 // NB: this called Platform::Windows::Exception::Throw () - a name that has not existed since v3 -
518 // until the stop_token wakeup made this branch the live one. It was dead code, so nothing caught it.
519 ThrowSystemErrNo (::WSAGetLastError ());
520 }
521#endif
522#elif qStroika_Foundation_Execution_WaitForIOReady_UsePPoll
523 // the mask argument is what makes this race-free - @see the signal-blocking code above
524 struct timespec timeoutSpec{};
525 timeoutSpec.tv_sec = timeoutMilliseconds / 1000;
526 timeoutSpec.tv_nsec = (timeoutMilliseconds % 1000) * 1000000L;
527 pollResult = Handle_ErrNoResultInterruption (
528 [&] () { return ::ppoll (pollData.begin (), pollData.GetSize (), &timeoutSpec, &originalSignalMask); });
529#elif !qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup
530 Require (sPollWaitChunkTime > 0s);
531 while (true) {
532 DurationSeconds timeLeft2Wait = Math::AtLeast<DurationSeconds> (timeoutAt - Time::GetTickCount (), 0s);
533 DurationSeconds time2WaitThisLoop = clamp<DurationSeconds> (timeLeft2Wait, 0s, sPollWaitChunkTime);
534 int thisLoopMilliseconds = static_cast<int> (time2WaitThisLoop.count () * 1000);
535 // NB: Handle_ErrNoResultInterruption () both retries on EINTR and is itself a cancelation point
536 pollResult = Handle_ErrNoResultInterruption ([&] () { return ::poll (pollData.begin (), pollData.GetSize (), thisLoopMilliseconds); });
537 if (pollResult != 0 or Time::GetTickCount () >= timeoutAt) {
538 break;
539 }
540 }
541#else
542 // abortWakeup_ is in this poll set, so one poll () spanning the whole timeout wakes just as promptly
543 pollResult = Handle_ErrNoResultInterruption ([&] () { return ::poll (pollData.begin (), pollData.GetSize (), timeoutMilliseconds); });
544#endif
545 /*
546 * Where a wakeup becomes an abort - and also where an abort that arrived before the wait even started
547 * gets noticed, since arming the registration above will already have made the descriptor readable.
548 */
550 if (abortWakeup_ != nullptr) {
551 // No-op unless the wakeup fired. If it did fire and we still got here, interruption is suppressed, so
552 // drain it - otherwise every later wait on this thread would return immediately.
553 abortWakeup_->Clear ();
554 }
555 Set<size_t> result;
556#if USE_NOISY_TRACE_IN_THIS_MODULE_
558#endif
559 if (pollResult != 0) {
560 // nMonitored, NOT pollData.GetSize (): the wakeup channel is ours, not one of the caller's fds
561 for (size_t i = 0; i < nMonitored; ++i) {
562 if (pollData[i].revents != 0) {
563#if USE_NOISY_TRACE_IN_THIS_MODULE_
564 dbgResult += start[i].first;
565#endif
566 result.Add (i);
567 }
568 }
569 }
570#if USE_NOISY_TRACE_IN_THIS_MODULE_
571 DbgTrace ("returning {}"_f, dbgResult);
572#endif
573 return result;
574}
#define Verify(c)
Definition Assertions.h:422
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:327
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:288
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
nonvirtual PlatformNativeHandle GetNativeSocket() const
Definition Socket.inl:52
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
void ThrowPOSIXErrNo(errno_t errNo=errno)
treats argument errNo as a POSIX errno value, and calls ThrowError () with an error_code constructed ...
auto Handle_ErrNoResultInterruption(CALL call) -> decltype(call())
Handle UNIX EINTR system call behavior - fairly transparently - just effectively removes them from th...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)