Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
WaitForIOReady.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Execution_WaitForIOReady_h_
5#define _Stroika_Foundation_Execution_WaitForIOReady_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <version>
10
11#include "Stroika/Foundation/Common/Common.h"
12#include "Stroika/Foundation/Containers/Collection.h"
13#include "Stroika/Foundation/Containers/Set.h"
18
19/**
20 * \file
21 *
22 * WaitForIOReady utility - portably provide facility to check a bunch of file descriptors/sockets
23 * if input is ready (like select, epoll, WaitForMutlipleObjects, etc)
24 *
25 * \note ***Windows takes sockets and nothing else.*** The underlying WSAPoll () accepts no other kind
26 * of handle, and WaitForMultipleObjectsEx () was tried instead and worked poorly. This is not just
27 * a restriction on callers: it is why the wakeup channel EventFD hands out has to be a (loopback
28 * UDP) socket there, where an event HANDLE would be cheaper. @see mkEventFD () and
29 * https://github.com/SophistSolutions/Stroika/issues/843
30 */
31
33
34/**
35 * Wake a pending wait through the waiting thread's std::stop_token, instead of depending on the
36 * thread-interrupt signal (POSIX) or APC (Windows) to interrupt the underlying poll () call.
37 *
38 * The interrupt signal cannot carry this reliably. Its whole effect is the EINTR it causes in a call that
39 * is already in flight, so a signal delivered while the thread is still setting up its poll set runs the
40 * (deliberately empty) handler and is spent, having interrupted nothing - and the thread then sleeps out
41 * its entire timeout. A std::stop_callback has the property the signal lacks: it is guaranteed to run, and
42 * to run DURING ITS OWN CONSTRUCTION if stop was already requested. Registering one to Set () a pollable
43 * EventFD, with that EventFD in the poll set, therefore leaves no window - however the abort and the wait
44 * are ordered, the descriptor ends up readable and poll () returns.
45 *
46 * Thread::Abort () already calls stop_source::request_stop (), so nothing on the aborting side changes.
47 *
48 * FIRST choice of three, because it is the only one that is portable - it covers Windows and macOS as well
49 * as Linux. @see qStroika_Foundation_Execution_WaitForIOReady_UsePPoll for the fallback used where the
50 * standard library has no jthread, and _BreakPollIntoTimedMillisecondChunks for the last resort.
51 *
52 * @see https://github.com/SophistSolutions/Stroika/issues/1165
53 */
54#ifndef qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup
55#if defined(__cpp_lib_jthread)
56#define qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup 1
57#else
58#define qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup 0
59#endif
60#endif
61
62/**
63 * SECOND choice, where the stop_token wakeup is unavailable: use ppoll () rather than poll (), blocking the
64 * thread-interrupt signal across the setup window and handing the original mask to the wait, which unblocks
65 * it atomically for exactly the duration of the wait. An abort arriving anywhere in the window then leaves
66 * the signal PENDING rather than spending it, and it fires the instant ppoll () unblocks it.
67 *
68 * This closes the race just as completely as the stop_token wakeup does, and it is worth having as well
69 * because the two are available in DIFFERENT configurations: ppoll () is glibc, so it does not care which
70 * C++ standard library is in use, while __cpp_lib_jthread is undefined for every libc++ before LLVM 20. So
71 * the clang++-NN-*-libc++ configurations get this one and not the stop_token path. @see the note on which
72 * standard libraries have jthread/stop_token, in Thread.h.
73 *
74 * Defined as 0 whenever the stop_token wakeup is available, so that exactly one mechanism is in play.
75 */
76#ifndef qStroika_Foundation_Execution_WaitForIOReady_UsePPoll
77#if !qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup && defined(__linux__)
78#define qStroika_Foundation_Execution_WaitForIOReady_UsePPoll 1
79#else
80#define qStroika_Foundation_Execution_WaitForIOReady_UsePPoll 0
81#endif
82#endif
83
84 namespace WaitForIOReady_Support {
85
86#if !qStroika_Foundation_Execution_WaitForIOReady_UseStopTokenAbortWakeup && !qStroika_Foundation_Execution_WaitForIOReady_UsePPoll
87 /**
88 * LAST choice of the three wakeup mechanisms, used only where neither the stop_token wakeup nor
89 * ppoll () is available. There, the wait is broken into chunks this long, re-checking for thread
90 * interruption between them.
91 *
92 * This is NOT hypothetical, despite being the fallback: macOS has no ppoll () at all (the UsePPoll
93 * default requires __linux__), and Apple's libc++ had no jthread before Xcode 26 - measured
94 * 2026-09-10, Xcode 15 and 16 lack __cpp_lib_jthread while 26.3 has it. So every XCode <= 16 build
95 * lands here, which includes the MacOS-15-XCode-16.4 CI job that runs on every push. Verified
96 * independently by forcing both macros to 0 on Linux/g++: all 54 regression tests pass.
97 *
98 * Unlike the other two, this only BOUNDS a lost wakeup rather than preventing one: an abort whose
99 * signal was spent before the wait began costs one chunk instead of the whole timeout. Nothing
100 * better is available in that configuration. It is deliberately NOT used when either other
101 * mechanism is in play - there, waking early could only mask a failure of the mechanism that is
102 * supposed to be doing the work, which is exactly how such a failure goes unnoticed.
103 *
104 * Smaller means an abort is noticed sooner, at the cost of more wakeups. Must be > 0.
105 *
106 * \note Deliberately not even DECLARED where one of the other mechanisms is available - which is
107 * every platform Stroika currently builds on except old XCode - so that it cannot be set
108 * somewhere it would have no effect.
109 *
110 * \note ***Set this before creating any threads.*** It is a variable rather than a macro on
111 * purpose: a macro could only be changed by rebuilding Stroika, whereas this lets an
112 * application tune it. But it is not synchronized, so changing it once threads are running
113 * is a data race.
114 *
115 * @see ConditionVariable's sConditionVariableWaitChunkTime, which this mirrors.
116 */
118#endif
119
120 /**
121 * This is the underlying native type 'HighLevelType objects must be converted to in order to
122 * be used with the operating-system poll/select feature.
123 */
124#if qStroika_Platform_Windows
125 using SDKPollableType = SOCKET;
126#else
127 using SDKPollableType = int;
128#endif
129
130 /**
131 */
132 class WaitForIOReady_Base {
133 public:
134 /**
135 * This is the underlying native type 'T' objects must be converted to in order to be used with the OLD poll/select feature.
136 */
138
139 public:
140 /**
141 * @todo consider adding more params - like out of band flags - but doesn't immediately seem helpful -- LGP 2017-04-16
142 *
143 * \note Common::DefaultNames<> supported
144 */
145 enum class TypeOfMonitor {
146 /**
147 * There is data to read.
148 *
149 * @see http://man7.org/linux/man-pages/man2/poll.2.html - POLLIN
150 */
151 eRead,
152
153 /**
154 * Writing is now possible.
155 *
156 * @see http://man7.org/linux/man-pages/man2/poll.2.html - POLLOUT
157 */
158 eWrite,
159
160 /**
161 * Poll Priority/Poll Urgent data (out-of-band data) is available for read - POLLPRI
162 *
163 * @see https://man7.org/linux/man-pages/man2/poll.2.html - It is used to detect "out-of-band" (OOB) data on sockets or high-priority, non-zero priority band data in STREAMS.
164 */
165 ePriority,
166
167 Stroika_Define_Enum_Bounds (eRead, ePriority)
168 };
169
170 public:
171 using TypeOfMonitorSet = Containers::Set<TypeOfMonitor>;
172
173 public:
174 /**
175 * default is to watch for read, error, and HUP (close) events (all but write).
176 */
177 // static inline const TypeOfMonitorSet kDefaultTypeOfMonitor{TypeOfMonitor::eRead, TypeOfMonitor::eError, TypeOfMonitor::eHUP};
178 static inline const TypeOfMonitorSet kDefaultTypeOfMonitor{TypeOfMonitor::eRead};
179
180 protected:
181 /**
182 * Take an array of pair<SDKPollableType, TypeOfMonitorSet> objects, and return a Set{} with the INDEXES of 'ready' pollable objects.
183 *
184 * \note Design Note: This could have returned a set of pointer to SDKPollableType which would in some sense be simpler and
185 * clearer, but its easier to validate/assert the returned INDEXES are valid than the returned POINTERS are valid.
186 */
187 static auto _WaitQuietlyUntil (const pair<SDKPollableType, TypeOfMonitorSet>* start, const pair<SDKPollableType, TypeOfMonitorSet>* end,
189 };
190
191 /**
192 * (Private) utility to allow select() to wakeup without sending EINTR such signals...
193 *
194 * \note idea originally from https://stackoverflow.com/questions/12050072/how-to-wake-up-a-thread-being-blocked-by-select-poll-poll-function-from-anothe/22239521
195 *
196 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
197 * Set () and Clear () may race, from any threads: each is one step, so whatever the interleaving,
198 * IsSet () and the readiness of GetWaitInfo ()'s descriptor agree once they return.
199 */
200 class EventFD {
201 public:
202 EventFD () = default;
203
204 public:
205 virtual ~EventFD () = default;
206
207 public:
208 virtual bool IsSet () const = 0;
209
210 public:
211 virtual void Set () = 0;
212
213 public:
214 virtual void Clear () = 0;
215
216 public:
217 // return low level FD + set of poll events
218 virtual pair<SDKPollableType, WaitForIOReady_Base::TypeOfMonitorSet> GetWaitInfo () = 0;
219 };
220 unique_ptr<EventFD> mkEventFD ();
221
222 template <typename T>
223 struct WaitForIOReady_Traits {
224 /**
225 * This is the type of object which is being wrapped (around a SDKPollableType object) and used with WaitForIOReady
226 */
227 using HighLevelType = T;
228
229 /**
230 * To use WaitForIOReady, the high level 'descriptor' objects used must be convertible to associated low level
231 * file descriptor objects to use with select/poll/etc...
232 */
233 static inline WaitForIOReady_Support::SDKPollableType GetSDKPollable (const HighLevelType& t)
234 {
235 return t;
236 }
237 };
238
239 }
240
241 /**
242 * Simple portable wrapper on OS select/2, pselect/2, poll/2, epoll (), and/or WaitForMultipleEvents(), etc,
243 * except you construct it with the 'select' arguments (file descriptors to watch), and then call wait
244 * on the 'waitable' object.
245 *
246 * \note An optional pollable2Wakeup descriptor lets another thread make a pending wait return early, without
247 * timing out and without ending the thread - @see the constructor.
248 *
249 * \note Poll-based, so every wait costs time linear in the number of descriptors watched. For how that compares
250 * with other libraries and APIs, and what would scale better: @see UpdatableWaitForIOReady and
251 * https://github.com/SophistSolutions/Stroika/issues/1176
252 *
253 * \par Example Usage
254 * \code
255 * Execution::WaitForIOReady waiter{fd};
256 * bool eof = false;
257 * while (not eof) {
258 * waiter.WaitQuietly (1);
259 * readALittleFromProcess (fd, stream, write2StdErrCache, &eof);
260 * }
261 * \endcode
262 *
263 * \par Example Usage
264 * \code
265 * Execution::WaitForIOReady sockSetPoller{socket2FDBijection.Image ()};
266 * while (true) {
267 * try {
268 * for (const auto& readyFD : sockSetPoller.WaitQuietly ()) {
269 * ConnectionOrientedMasterSocket::Ptr localSocketToAcceptOn = *socket2FDBijection.InverseLookup (readyFD);
270 * ConnectionOrientedStreamSocket::Ptr s = localSocketToAcceptOn.Accept ();
271 * fNewConnectionAcceptor (s);
272 * }
273 * }
274 * ...
275 * }
276 * \endcode
277 *
278 * \note WaitForIOReady internally uses SDKPollableType, which is a UNIX file descriptor or Windows SOCKET. TRAITS
279 * must be provided to map 'T' objects to that SDKPollableType. These are provided by default for most appropriate types.
280 *
281 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety">C++-Standard-Thread-Safety</a>
282 *
283 * \note pointless to ever create a WaitForIOReady and not use it, so [[nodiscard]] appropriate
284 */
285 template <typename T = WaitForIOReady_Support::SDKPollableType, typename TRAITS = WaitForIOReady_Support::WaitForIOReady_Traits<T>>
286 class [[nodiscard]] WaitForIOReady : public WaitForIOReady_Support::WaitForIOReady_Base {
287 public:
288 using TraitsType = TRAITS;
289
290 public:
291 /**
292 * \brief Watch each of fds for the events paired with it - or, in the other overloads, all of fds (or the one fd) for flags.
293 *
294 * fds is copied, so later changes to the container it came from have no effect here. For a descriptor set
295 * that can change while a wait is in progress, @see UpdatableWaitForIOReady.
296 *
297 * \par pollable2Wakeup
298 * An optional extra descriptor, and the events to watch it for (normally just eRead), whose becoming ready
299 * makes a pending Wait* return early. That is how another thread gets a wait to return WITHOUT ending it -
300 * to pick up a changed descriptor set, say. Aborting the waiting thread needs no such help: every Wait* is
301 * a cancelation point, and Thread::Ptr::Abort () ends it by itself.
302 *
303 * It is a raw SDKPollableType, not a T, so TRAITS does not map it and it never appears in a result: a wait
304 * it ends returns just the fds that happened to be ready too - often none. So:
305 * - WaitQuietly/WaitQuietlyUntil can return an empty set BEFORE the timeout, which from the result
306 * alone looks just like a timeout. To tell them apart, ask the wakeup (e.g. EventFD::IsSet ()).
307 * - Wait/WaitUntil throw only once the timeout has really passed, so they too can return an empty set.
308 *
309 * This class only ever polls it - it never reads, resets, or closes it. And poll () is level-triggered, so
310 * once ready it stays ready until its owner resets it, and until then EVERY wait returns at once. So:
311 * - Reset it BEFORE each wait, not after. Then a wakeup that races with the reset is not lost - at
312 * worst it makes the wait return at once.
313 * - Keep it open for as long as any Wait* on this object can run - including on copies, which share
314 * it rather than duplicate it.
315 *
316 * The usual choice is an EventFD, from WaitForIOReady_Support::mkEventFD (): pass its GetWaitInfo (),
317 * Set () it to wake the wait, and Clear () it to reset. That is what UpdatableWaitForIOReady does.
318 *
319 * \par Example Usage
320 * \code
321 * auto wakeup = WaitForIOReady_Support::mkEventFD (); // other threads call wakeup->Set () to end the wait early
322 * while (...) {
323 * wakeup->Clear (); // before the wait, not after - @see above
324 * WaitForIOReady waiter{fds, WaitForIOReady_Base::kDefaultTypeOfMonitor, wakeup->GetWaitInfo ()};
325 * for (auto fd : waiter.WaitQuietly (30s)) {
326 * ...
327 * }
328 * // if wakeup->IsSet (), a wakeup ended the wait - so recheck what fds should be
329 * }
330 * \endcode
331 *
332 * \note On Windows, like fds, pollable2Wakeup must be a socket - @see the note at the top of this file.
333 */
334 WaitForIOReady (const Traversal::Iterable<pair<T, TypeOfMonitorSet>>& fds,
335 optional<pair<SDKPollableType, TypeOfMonitorSet>> pollable2Wakeup = nullopt);
336 WaitForIOReady (const Traversal::Iterable<T>& fds, const TypeOfMonitorSet& flags = kDefaultTypeOfMonitor,
337 optional<pair<SDKPollableType, TypeOfMonitorSet>> pollable2Wakeup = nullopt); ///< \brief Watch all of fds for flags
338 WaitForIOReady (T fd, const TypeOfMonitorSet& flags = kDefaultTypeOfMonitor,
339 optional<pair<SDKPollableType, TypeOfMonitorSet>> pollable2Wakeup = nullopt); ///< \brief Watch just fd, for flags
340 WaitForIOReady (WaitForIOReady&&) noexcept = default; ///< \brief Copies and moves share the descriptors - none is duplicated
341 WaitForIOReady (const WaitForIOReady&) = default; ///< \brief Copies and moves share the descriptors - none is duplicated
342
343 public:
344 ~WaitForIOReady () = default;
345
346 public:
347 nonvirtual WaitForIOReady& operator= (WaitForIOReady&&) noexcept = default;
348 nonvirtual WaitForIOReady& operator= (const WaitForIOReady&) = default;
349
350 public:
351 /**
352 */
353 nonvirtual Traversal::Iterable<pair<T, TypeOfMonitorSet>> GetDescriptors () const;
354
355 public:
356 /**
357 * Waits the given amount of time, and returns as soon as any one (or more) requires service (see TypeOfMonitor), or pollable2Wakeup signaled (in which case may return empty set).
358 *
359 * \note Throws a timeout on timeout - @see Execution::ThrowError (errc::timed_out).
360 *
361 * \note ***Cancelation Point***
362 *
363 * @see WaitQuietly
364 * @see WaitUntil
365 * @see WaitQuietlyUntil
366 */
367 nonvirtual Containers::Set<T> Wait (Time::DurationSeconds waitFor = Time::kInfinity);
368
369 public:
370 /**
371 * Waits the given amount of time, and returns as soon as any one (or more) requires service (see TypeOfMonitor).
372 *
373 * Returns set of file descriptors which are ready, or empty set if timeout, or if signaled by pollable2Wakeup.
374 *
375 * \note ***Cancelation Point***
376 *
377 * @see Wait
378 * @see WaitUntil
379 * @see WaitQuietlyUntil
380 */
381 nonvirtual Containers::Set<T> WaitQuietly (Time::DurationSeconds waitFor = Time::kInfinity);
382
383 public:
384 /**
385 * Waits until the given timeoutAt, and returns as soon as any one (or more) requires service (see TypeOfMonitor), or pollable2Wakeup signaled (in which case may return empty set)..
386 *
387 * \note Throws a timeout on timeout - @see Execution::ThrowError (errc::timed_out).
388 *
389 * \note ***Cancelation Point***
390 *
391 * @see Wait
392 * @see WaitQuietly
393 * @see WaitQuietlyUntil
394 */
395 nonvirtual Containers::Set<T> WaitUntil (Time::TimePointSeconds timeoutAt = Time::TimePointSeconds{Time::kInfinity});
396
397 public:
398 /**
399 * Waits until the given timeoutAt, and returns as soon as any one (or more) requires service (see TypeOfMonitor), or pollable2Wakeup signaled (in which case may return empty set)..
400 *
401 * Returns set of file descriptors which are ready, or an empty set if time expired before any became ready.
402 *
403 * if timeout is <= 0, this will not wait (but may still find some file descriptors ready).
404 *
405 * \note ***Cancelation Point***
406 *
407 * @see Wait
408 * @see WaitQuietly
409 * @see WaitUntil
410 */
411 nonvirtual Containers::Set<T> WaitQuietlyUntil (Time::TimePointSeconds timeoutAt = Time::TimePointSeconds{Time::kInfinity});
412
413 private:
415 // @todo Consider Mapping<T, TypeOfMonitorSet> here instead of an Iterable of pairs - @see https://github.com/SophistSolutions/Stroika/issues/1176
417 const optional<pair<SDKPollableType, TypeOfMonitorSet>> fPollable2Wakeup_;
418 };
419
420}
421
422/*
423 ********************************************************************************
424 ***************************** Implementation Details ***************************
425 ********************************************************************************
426 */
427#include "WaitForIOReady.inl"
428
429#endif /*_Stroika_Foundation_Execution_WaitForIOReady_h_*/
#define Stroika_Define_Enum_Bounds(FIRST_ITEM, LAST_ITEM)
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 Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE
[[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this on things where its not b...
Definition StdCompat.h:481
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
WaitForIOReady(WaitForIOReady &&) noexcept=default
Copies and moves share the descriptors - none is duplicated.
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:234