Flow 1.0.1
Flow project: Full implementation reference.
async_fwd.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
24#include <boost/any.hpp>
25
26
27/**
28 * Flow module containing tools enabling multi-threaded event loops operating under the asynchronous-task proactor
29 * pattern, by providing a streamlined API around boost.asio event loops with added advanced task- and
30 * thread-scheduling features. There is also support for single-threaded event loops.
31 *
32 * In simpler terms, at its core -- including when the "pool" has just one thread, which
33 * is very common -- it provides a compact way of both starting thread(s) *and* posting/scheduling tasks and I/O
34 * to run in such thread(s). By default one must worry about each of those 2 concerns separately and decide how
35 * exactly to hook them up; which is not rocket science, but it *is* a ton of boiler-plate, and it *is* easy to
36 * make mistakes and/or omit useful startup/shutdown practices, logging, and more. This module provides, via
37 * Concurrent_task_loop and its implementations, at least that consistency/standardization. Plus, it provides
38 * certain advanced features as mentioned above.
39 *
40 * - boost.asio provides the core algorithmic abilities of an optionally multi-threaded task-executing loop,
41 * particularly through classes util::Task_engine (a/k/a `boost::asio::io_service`), util::Strand
42 * (a/k/a `boost::asio::io_service::strand`), and util::Timer. flow::async Flow module somewhat streamlines
43 * this API in such a way as to keep the user's focus on their conceptual async-task-driven algorithm as opposed
44 * to details of threads, handlers, cores, etc. The async::Op opaque type is central to this streamlined API,
45 * plus the central class Concurrent_task_loop.
46 * - The bottom line is the user thinks about their algorithm in
47 * terms of tasks; while the internals of the chosen Concurrent_task_loop concrete object worry about the
48 * actual scheduling of these tasks across threads.
49 * - boost.asio doesn't really provide ways to specify how threads should be assigned to processor cores; it only
50 * controls what code is executed on which thread. These abilities are available natively. flow::async Flow
51 * module allows one to set certain knobs controlling this behavior, and the user can continue to only worry
52 * about their algorithm and not threading details.
53 * - The combination of the generalized async::Op mechanism and these thread-hardware-scheduling features
54 * in an integrated whole is what hopefully makes the Flow `async` module a value-add over just boost.asio,
55 * or over just boost.asio with some thread-core-affinity utility functions on the side.
56 *
57 * @see The central type is the interface class Concurrent_task_loop. For single-thread async work,
58 * which is very common, see Single_thread_task_loop, a simplified adapter, similar to how `std::queue<T>`
59 * is commongly a simplified `std::deque<T>` or `list` underneath.
60 * @see async::Op.
61 *
62 * @internal
63 *
64 * @todo The thread-to-core optimizations provided at this time are, at least, a good start, but more advanced logic
65 * can be devised with more low-level experience and/or by using certain open-source libraries. It's possible that
66 * a more knowledgeable person would devise more or better knobs and/or require less manual specification of
67 * values. The following background reading may help devise
68 * more advanced logic and/or knobs:
69 * [ https://eli.thegreenplace.net/2016/c11-threads-affinity-and-hyperthreading/ |
70 * https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.2016.07.31a.pdf |
71 * https://lwn.net/Articles/255364/ | "hwloc" library (portable lib for detailed hardware topology info) |
72 * libNUMA ].
73 */
74namespace flow::async
75{
76// Types.
77
78// Find doc headers near the bodies of these compound types.
79
80class Cross_thread_task_loop;
81class Concurrent_task_loop;
82class Op_list;
83class Segregated_thread_task_loop;
84class Single_thread_task_loop;
85class Timed_single_thread_task_loop;
86class Timed_concurrent_task_loop;
87template<typename Time_accumulator>
88class Timed_concurrent_task_loop_impl;
89
90/**
91 * Short-hand for a task that can be posted for execution by a Concurrent_task_loop or flow::util::Task_engine;
92 * it is simply something callable via `()` with no arguments and returning nothing.
93 *
94 * By convention in comments we represent `Task`s with the letters F, G, H.
95 */
96using Task = Function<void ()>;
97
98/**
99 * An object of this opaque type represents a collection of 1 or more async::Task, past or future, such that:
100 * *if* one performs `C->post(J, F)` and `C->post(K, G)` (where C is `Concurrent_task_loop*`, JK are the same
101 * `async::Op&`, or one refers to a transitive copy of the other, and FG are both `Task`s), *then*
102 * F and G will NOT execute concurrently.
103 *
104 * In addition, it is guaranteed that copying (via constructor or assignment) of async::Op is
105 * has performance characteristics no worse than those of `shared_ptr`. I.e., it is to be thought of as light-weight.
106 *
107 * The value `Op()` is designated as a null/sentinel value and must not be passed to Concurrent_task_loop::post()
108 * or anything built on it.
109 *
110 * That's the formal definition. We reiterate that copying these is cheap; and moreover two `Op`s such that
111 * one is a copy (of a copy, of a copy, of a copy...) of another, then these are conceptually isomorphic: they
112 * represent the same op, or collection of `Task`s that must never execute concurrently. Finally, tip: Don't think
113 * of an `Op` as a collection of 2+ `Task`s; but rather a tag or label that associates 2+ `Task`s with each other.
114 * (Also, nothing prevents an async::Task being a part of 2+ `Op`s simultaneously, though informally speaking
115 * it's arguably best not to make code maintainers grok such a design.)
116 *
117 * By convention in comments we represent `Op`s with the letters J, K, L.
118 *
119 * ### When to use an `Op` versus just a stand-alone `Task`? ###
120 * When choosing a Concurrent_task_loop::post() (the one with `Op` vs. one without; or similar choices in more
121 * advanced cases), here are some things to remember. These can be derived independently and are only included
122 * as a convenience/refresher:
123 *
124 * - An `Op` prevents `Task`s from executing concurrently. If there is exactly 1 thread in a pool, then they couldn't
125 * anyway, so an `Op` is not needed...
126 * - ...except as future-proofing, in case conceivably 1 thread might soon turn into 2+ after all.
127 * - It is quite common to follow the pattern wherein, as the very last statement to execute within a `Task`,
128 * one `post()`s (or similar) exactly 1 `Task` to asynchronously execute next. Since it's the last statement,
129 * and `post()` and similar are explicitly thread-safe, this ensures the current and next `Task`s do not execute
130 * concurrently. So an `Op` is not needed...
131 * - ...except as future-proofing. It is sometimes easier to maintain, and more expressive to read, when many
132 * `Task`s are "officially" run under the banner of a single `Op`, even if some parts of the async handling of
133 * the "conceptual" operation are serial and hence don't technically require an `Op`. Example: In a web
134 * server it is reasonable to create an `Op` for the entire request, with all `Task`s (even serially called ones)
135 * being associated with that per-request `Op`; then, simply, no locking is necessary for per-request data
136 * structure(s). It's much easier to explain, "you don't need to lock 'em," vs. "you don't need to lock 'em,
137 * unless the logic changes in such a way as to...."
138 *
139 * To be fair, those tips ignore performance; they implicitly assume using `Op` pointlessly (functionally, not
140 * stylistically, so) is otherwise "free." It is *not* free; depending on internal details using `Op` might involve
141 * a util::Strand and/or pinning stuff to a specific thread. Informally, this is seldom a big deal in practice;
142 * but in performance-sensitive projects one must remember there is a cost.
143 *
144 * @internal
145 *
146 * `boost::any` (storing nothing heavier-weight than a `shared_ptr` to satisfy that explicit guarantee above) is one
147 * way to do this. A rigid polymorphic hierarchy (`virtual`) is another. Performance-wise they're similar, costing
148 * essentially a single `virtual` pointer lookup per Concurrent_task_loop::post(). boost.any is much pithier, not
149 * requiring a class hierarchy at all, and otherwise it's pretty much the same in terms of how they're used internally.
150 * Some might say the polymorphic hierarchy is clearer, because it is explicit, but I feel comments alone, too, can
151 * be just as clear, and brevity is a virtue.
152 */
153using Op = boost::any;
154
155/**
156 * Similar to flow::async::Task but used for scheduled-in-future tasks as opposed to to-be-run-ASAP tasks.
157 * In practice it's the same thing but takes a single `bool` argument with the meaning explained
158 * in util::schedule_task_from_now() doc header (spoiler alert: whether it ran as scheduled or was short-fired by
159 * user, as of this writing).
160 *
161 * @note Whenever a comment explains how `Task`s are dealt with, one may usually assume the same extends equally to
162 * a `Scheduled_task`, merely at a different point in time. We omit that explicit language for brevity;
163 * it is to be assumed.
164 */
166
167/**
168 * Short-hand for a boost.asio completion handler: The minimal type, taking only a flow::Error_code
169 * a/k/a `boost::system::error_code`.
170 */
171using Task_asio_err = Function<void (const Error_code&)>;
172
173/**
174 * Short-hand for a boost.asio completion handler: The type that takes a `size_t` count of things successfully
175 * transferred usually.
176 */
177using Task_asio_err_sz = Function<void (const Error_code&, size_t)>;
178
179/**
180 * Short-hand for reference-counting pointer to a mutable util::Task_engine (a/k/a `boost::asio::io_service`).
181 * This is generally how classes in the Concurrent_task_loop hierarchy refer to their internally used
182 * `Task_engine`s but also in advanced cases may be communicated to their user.
183 *
184 * @internal
185 *
186 * ### Rationale ###
187 * Why do that instead of using raw `Task_engine*`? It may not be obvious as you read
188 * this now, but all kinds of pain goes away due to the possibilities of who
189 * happens to own an underlying `Task_engine` -- it can be shared by all threads in pool, or each can have its own
190 * `Task_engine`, depending on the chosen Concurrent_task_loop subclass -- and in what order those things
191 * might execute their destructors. By simply using a `shared_ptr<>` everywhere, with small overhead we ensure
192 * an underlying `Task_engine` does not get destroyed until everything that uses it is destroyed first, and
193 * that could be a number of things. By using `shared_ptr<>` we needn't break our heads worrying about executing
194 * de-init pieces of code in just the right order just to avoid early-free. (This is said from experience.)
195 *
196 * This is reinforced in those semi-advanced cases where a `Task_engine` is passed to user via public API.
197 */
198using Task_engine_ptr = boost::shared_ptr<util::Task_engine>;
199
200/**
201 * Short-hand for ref-counted pointer to util::Strand.
202 *
203 * @internal
204 *
205 * ### Rationale ###
206 * We at times return new `Strand`s (Cross_thread_task_loop::create_op()), so universally use ref-counted pointers
207 * to `Strand`s to not have to worry about `Strand` lifetimes too hard.
208 *
209 * Key fact: The type Cross_thread_task_loop loads into superclass's async::Op (`boost::any`) is `Strand_ptr`.
210 * That is, a `Strand` is the mechanism used to bundle together non-concurrent tasks in Cross_thread_task_loop.
211 */
212using Strand_ptr = boost::shared_ptr<util::Strand>;
213
214/**
215 * Enumeration indicating the manner in which asio_exec_ctx_post(), and various boost.asio "post" operations like
216 * it or based on it, are to actually execute the given task in relation to when the "posting" routine, itself,
217 * returns control to its caller. Basically it indicates whether the execution should be synchronous or asynchronous
218 * and how, if it all, to wait for its completion -- or its initiation.
219 * The `enum` members' meanings are the key things to understand;
220 * and there's some discussion in their doc headers that might be useful as a boost.asio refresher.
221 */
223{
224 /**
225 * Simply post the given task to execute asynchronously in some execution context -- as soon as the context's
226 * scheduler deems wise but specifically *not* inside the posting routine itself; and return as soon as possible
227 * having thus posted it. That is: work in the manner of boost.asio `post(Task_engine, F)`.
228 *
229 * In particular, suppose you're calling `POST(F)` in this mode, where
230 * `POST()` is some posting routine controlling a thread pool P, and `F()` is the task; and suppose the scheduler
231 * would deem wise to run `F()` in some thread P.W in that pool (perhaps for load-balancing reasons). Then:
232 * - If `POST()` is being called outside of pool P, or it is being called from a sibling
233 * thread P.W' but not W itself, then `F()` will run at some point in the future (possibly even
234 * concurrently with `POST()` itself), in thread P.W.
235 * - One typical case is when some external user of P loads work onto P.
236 * - The other is if some task or completion handler already in P loads async work back
237 * onto its own pool P, but the scheduler decides it's best for it to run in a different thread than
238 * the posting code.
239 * - This is only possible with 2 or more threads in P (by no means always the case).
240 * - If `POST()` is being called from W itself, meaning the scheduler decided that the task should load
241 * on the same thread as the posting task, then `F()` will run at some point in the future strictly after the
242 * `POST()` returns.
243 * - This usually means some task or completion handler in pool P is loading async work back onto its own
244 * pool, and either that pool contains only 1 thread (so there is no other choice), or else
245 * the scheduler decided the calling thread is still the best choice for task `F()` at this time
246 * (e.g., maybe the other thread(s) are loaded with queued work).
247 *
248 * Either way, `POST()` will return quickly. Then `F()` will either run concurrently or after this return -- but
249 * never *in* `POST()` synchronously.
250 */
251 S_ASYNC,
252
253 /**
254 * Same as Synchronicity::S_ASYNC but the posting routine then waits as long as necessary for the given task to
255 * complete; and only then returns. That is: work in the manner of boost.asio `post(Task_engine, F)`, but
256 * wait until `F()` actually runs and returns. This must only be used when posting from a thread *outside* the
257 * target thread pool; or undefined behavior will result.
258 *
259 * @warning One must *not* use this mode when posting onto a thread pool from inside
260 * that thread pool: boost.asio `post()`-like function by definition won't execute a task synchronously
261 * inside itself, yet by the definition of this mode it must also wait for the task to run and complete.
262 * So if `post()` (or similar) were to decide the task belongs on the calling thread, an inifinite
263 * block (deadlock) occurs, as it will be waiting for something to happen that the wait prevents from
264 * happening.
265 *
266 * This mode is reminiscent of the promise/future concept and allows one to easily solve the age-old problem of
267 * "how do I ask a thread/pool to do a thing and then wait for get a result?". One might do this by manually
268 * using a promise/future pair; or even mutex/condition variable pair; but by using this such boiler-plate is
269 * reduced (along with fewer bugs).
270 *
271 * @warning Be aware that the wait for completion will block infinitely, if one were to do something that would
272 * prevent the task from ever running. When working with boost.asio `Task_engine`s, this may occur
273 * when one `stop()`s or simply destroys the `Task_engine` (though the time period during which
274 * one would have to do this is short, assuming the task is quick). Naturally the way to avoid this is
275 * by not stopping or destroying the execution context during a posting call in
276 * mode `S_ASYNC_AND_AWAIT_CONCURRENT_COMPLETION`. For example, the `Task_engine::stop()` call might
277 * be placed in the same thread as the posting; then they cannot be concurrent. If this is triggered from
278 * a SIGTERM/INT handler, one might only set or post something that will trigger the destruction in an
279 * orderly fashion at the proper time as opposed to doing it directly from the sig handler itself.
280 * This warning isn't anything that should be particularly new -- orderly shutdown is typically concerned
281 * with such logic anyway -- but it seemed worth putting in perspective of the fact this mode involves
282 * a wait for something that doesn't necessarily ever run, unless you actively make sure it does.
283 *
284 * @todo Much like the promise/future mechanism provides optional timed wait functionality, it might make sense
285 * to provide the API ability to set an optional time limit for any wait invoked by
286 * Synchronicity::S_ASYNC_AND_AWAIT_CONCURRENT_COMPLETION or Synchronicity::S_ASYNC_AND_AWAIT_CONCURRENT_START.
287 * Probably best to add this only once a need clearly arises though.
288 */
290
291 /**
292 * Same as Synchronicity::S_ASYNC but the posting routine then waits as long as necessary for the given task to
293 * *just* about to begin executing concurrently (so that any subsequent `Task_engine::stop()` shall be unable to
294 * prevent it from executing and eventually finishing) -- and only then returns.
295 *
296 * This is most similar to `S_ASYNC_AND_AWAIT_CONCURRENT_COMPLETION` but may improve responsiveness of the
297 * calling thread, if what one needs to achieve is a guarantee that `F()` *will definitely* execute and complete,
298 * but does *not* need to wait for this to happen. So it's a weapon against a "dangling" `post()` that might
299 * be followed immediately by `Task_engine::stop()` -- while not blocking until the posted thing finishes.
300 *
301 * @warning One must *not* use this mode when posting onto a thread pool from inside
302 * that thread pool; as with `S_ASYNC_AND_AWAIT_CONCURRENT_COMPLETION` that may hang the thread.
303 */
305
306 /**
307 * Execute the given task synchronously, if the scheduler determines that the calling thread is in its thread pool
308 * *and* is the best thread for the task; otherwise act identically to Synchronicity::S_ASYNC. That is: work in the
309 * manner of boost.asio `dispatch(Task_engine, F)`. This can be useful for performance, since when the opportunity
310 * presents itself this way avoids exiting a task only to immediately enter the posted task, when one could just
311 * synchronously execute one after the other.
312 *
313 * @warning Do *not* presume to know when a given scheduler will actually decide it will invoke the given task
314 * synchronously, unless documentation very clearly explains such rules. Just because it can does not
315 * mean it will. For example, boost.asio `post()` says that the task "might" run synchronously when this is
316 * possible; not that it "will." Assumptions about when it might in fact do so can lead to subtle and
317 * difficult-to-reproduce bugs. (Example of broken assumptions: Suppose it's a 1-thread pool, and one
318 * posts F from task G. Surely it must run F synchronously -- there's no other thread! But what if some
319 * other task or completion handler was already queued up to run before F was? That's not even the point
320 * though; the scheduler is still free to not do it, say because of some spurious lock-related logic that
321 * is there for some obscure performance reason.)
322 *
323 * @warning If you choose to use this mode (or `dispatch()`-like routines in general), it is almost never a good idea
324 * to do so from anywhere except just before returning from a task (or from outside the thread pool).
325 * If called from the middle of a task, you now cannot be sure if A happens before B or B happens before A.
326 * Usually that makes things complicated unnecessarily.
327 */
329}; // enum class Synchronicity
330
331// Free functions.
332
333/**
334 * An extension of boost.asio's `post()` and `dispatch()` free function templates, this free function template
335 * allows the user to more easily select the synchronicity behavior as the given task is posted onto the
336 * given execution context (util::Task_engine or util::Strand at least). It also adds TRACE logging including
337 * that book-ending the task's execution (aiding debugging, etc.). The `synchronicity` argument controls the
338 * specific way in which `task` is posted onto `*exec_ctx`; see #Synchronicity doc header.
339 *
340 * This call causes `task` to execute in a thread controlled by `*exec_ctx`. The latter, at this time, must be
341 * either util::Task_engine or util::Strand (which itself is born of a `Task_engine`). It is likely that it will
342 * work equally well for other entities satisfying the boost.asio `ExecutionContext` concept (see boost.asio docs), but
343 * this is untested and not thought through formally, so officially such uses cause undefined behavior as of this
344 * writing.
345 *
346 * Semantics can be found in #Synchronicity doc headers which are required reading before using this function.
347 * However, briefly and informally, the utility of this function is as follows:
348 * - `post()` works on a `Task_engine` or `Strand` already and equals
349 * mode Synchronicity::S_ASYNC; with this function you'll get some added debug logging as well.
350 * - `dispatch()` and Synchronicity::S_OPPORTUNISTIC_SYNC_ELSE_ASYNC are similarly related: same thing
351 * but more logging.
352 * - Synchronicity::S_ASYNC_AND_AWAIT_CONCURRENT_COMPLETION equals a `post()` with a promise/future pair,
353 * wherein the caller performs `unique_future.wait()` after the `post()`, while the task always sets
354 * `promise.set_value()` just before returning (and, again, more logging).
355 * - Lastly, Synchronicity::S_ASYNC_AND_AWAIT_CONCURRENT_START is similar, but the `promise.set_value()`
356 * executes just *before* executing `task()`. Hence any tasks queued before `task()` will first execute
357 * (same as previous bullet); and *then* asio_exec_ctx_post() will return, just as `task()` begins executing
358 * as opposed to wait for its completion.
359 * This is useful to prevent `E.stop()` after our return (where `E` is the `Task_engine` that either is
360 * or produced `*exec_ctx`) will be too late to prevent `task()` from executing and completing.
361 *
362 * In all cases, one gets more logging and arguably a bit of syntactic sugar, but
363 * `S_ASYNC_AND_AWAIT_CONCURRENT_COMPLETION` and `S_ASYNC_AND_AWAIT_CONCURRENT_START` in particular eliminate
364 * quite a bit of tedious and hairy code and explanations.
365 *
366 * Lastly, if `*exec_ctx` is currently not running, then the semantics described in #Synchronicity doc header
367 * still apply but are deferred until it does run. In particular in mode
368 * Synchronicity::S_ASYNC_AND_AWAIT_CONCURRENT_COMPLETION and Synchronicity::S_ASYNC_AND_AWAIT_CONCURRENT_START
369 * this function shall not return until `*exec_ctx` at least
370 * does begin running in at least 1 thread; while the other two modes reduce to `post()`, which returns immediately,
371 * leaving `task()` to run once `*exec_ctx` starts.
372 *
373 * @tparam Execution_context
374 * util::Task_engine or util::Strand. See note above regarding other possibilities.
375 * @param logger_ptr
376 * Logger to use in this function.
377 * @param exec_ctx
378 * The execution context controlling the thread pool onto which to load the `task` for ASAP execution.
379 * @param synchronicity
380 * Controls the precise behavior. See above.
381 * @param task
382 * The task -- taking no arguments and returning no value -- to load onto `*exec_ctx`.
383 */
384template<typename Execution_context>
385void asio_exec_ctx_post(log::Logger* logger_ptr, Execution_context* exec_ctx, Synchronicity synchronicity, Task&& task);
386
387/**
388 * Assuming a planned thread pool will be receiving ~symmetrical load, and its UX-affecting (in particular, per-op
389 * latency-affecting) operations are largely between processor and RAM: Returns the # of threads to store in that pool
390 * for efficient performance.
391 *
392 * @see After the threads are created, use optimize_pinning_in_thread_pool() to complete
393 * the work that targets this good performance.
394 *
395 * This will be used, by doc header contract, by all (as of this writing) Concurrent_task_loop subclasses if
396 * so specified via `n_threads_or_zero == 0`. So in that context one needn't call this directly. However, it may be
397 * useful directly when one is operating a thread pool but without a Concurrent_task_loop.
398 *
399 * @param logger_ptr
400 * Logger to use in this function.
401 * @param est_hw_core_sharing_helps_algo
402 * Set this to `true` if you estimate the intended use for this thread pool is such that 2+ identically
403 * loaded pool threads sharing 1 physical core would handle the load (in total over those 2+ threads) better than
404 * just 1 thread using that same core would. Set it to `false` otherwise.
405 * Note that, generally, this should be assumed `false`, unless there is significant cache locality between
406 * those 2+ threads, meaning they tend to work on the same cacheably-small area in memory at ~the same time.
407 * For example, parallel matrix multiplication algorithms can thus benefit and would set it to `true`; but
408 * that is the not the case by default; one would have to prove it, or design the algorithm with that in mind.
409 * @return The number of threads mandated for the thread pool in question.
410 */
412 bool est_hw_core_sharing_helps_algo);
413
414/**
415 * Assuming the same situation as documented for optimal_worker_thread_count_per_pool(), and that indeed
416 * the pool now contains that number of running threads: Attempts to optimize thread-core-pinning behavior in that
417 * pool for efficient performance.
418 *
419 * @see optimal_worker_thread_count_per_pool() first. The two functions work together (one before, the other after
420 * spawning the threads). Behavior is undefined if the two aren't used in coherent fashion, meaning one
421 * passed different values for same-named args.
422 *
423 * @note There is a to-do, as of this writing, to allow one to query system to auto-determine
424 * `hw_threads_is_grouping_collated` if desired. See `namespace` flow::async doc header.
425 *
426 * @todo For the Darwin/Mac platform only: There is likely a bug in optimize_pinning_in_thread_pool() regarding
427 * certain low-level pinning calls, the effect of which is that this function is probably effectively a no-op for
428 * now in Macs. The bug is explained inside the body of the function.
429 *
430 * @param logger_ptr
431 * Logger to use in this function.
432 * @param threads_in_pool
433 * These raw threads, which must number `optimal_worker_thread_count_per_pool(same-relevant-args)`,
434 * comprise the pool in question. They must be already spawned (e.g., have had some caller's code execute OK).
435 * @param est_hw_core_sharing_helps_algo
436 * See optimal_worker_thread_count_per_pool().
437 * @param est_hw_core_pinning_helps_algo
438 * Set this to `true` if you have reason to believe that pinning each of the pool's threads to N (N >= 1)
439 * logical cores would improve performance in some way. Set it to `false` otherwise.
440 * As of this writing I don't know why it would be `true` specifically; but it can be researched; and I know
441 * in practice some applications do (in fact) do it, so it's not necessarily worthless, at least.
442 * @param hw_threads_is_grouping_collated
443 * When the number of physical cores does not equal # of logical cores (hardware threads) -- otherwise
444 * this arg is ignored -- this determines the pattern in which each set of
445 * 2+ core-sharing hardware threads is arranged vs. the other sets. When `false`, it's like ABCDABCD, meaning
446 * logical cores 0,4 share core, 1,5 share different core, 2,6 yet another, etc. When `true`, it's like
447 * AABBCCDD instead. It seems `true` is either rare or non-existent, but I do not know for sure.
448 */
450 const std::vector<util::Thread*>& threads_in_pool,
451 bool est_hw_core_sharing_helps_algo,
452 bool est_hw_core_pinning_helps_algo,
453 bool hw_threads_is_grouping_collated);
454
455/**
456 * Given a boost.asio *completion handler* `handler` for a boost.asio `async_*()` action on some boost.asio I/O
457 * object to be initiated in the immediate near future, returns a wrapped handler with the same signature
458 * to be passed as the handler arg to that `async_*()` action, so that `handler()` will execute non-concurrently
459 * with other tasks in `Op op`. This is analogous to boost.asio's `bind_executor(Strand, Handler)` (which
460 * replaces boost.asio's now-deprecated `util::Strand::wrap(Handler)`).
461 *
462 * The mechanics of using this are explained in Concurrent_task_loop doc header. Using this in any other
463 * fashion leads to undefined behavior.
464 *
465 * @tparam Handler
466 * boost.asio handlers are, essentially, all `void`-returning but can take various arg sets.
467 * E.g., util::Timer (a/k/a `boost::asio::basic_waitable_timer`) expects a handler that takes only an
468 * `Error_code`; while `boost::asio::ip:tcp::socket::read_some()` expects one to take bytes-received `size_t`
469 * and an `Error_code`. This template supports all handlers via `auto` magic.
470 * @param loop
471 * Active loop that spawned `Op op`.
472 * @param op
473 * See 3-arg Concurrent_task_loop::post().
474 * @param handler
475 * Completion handler for the boost.asio `async_*()` operation to be initiated soon.
476 * It may be `move`d and saved.
477 * @return A completion handler that will act as `handler()` but also satisfying the constraints of
478 * `Op op`.
479 */
480template<typename Handler>
481auto asio_handler_via_op(Concurrent_task_loop* loop, const Op& op, Handler&& handler);
482
483/**
484 * Template specialization model for operation that obtains the underlying execution context, such as a
485 * util::Task_engine or util::Strand, stored in an async::Op generated by the given Concurrent_task_loop.
486 * Each subclass (impl) of Concurrent_task_loop shall provide a specialization of this template with
487 * `Exec_ctx_ptr` template param being the appropriate boost.asio-compatible execution context type for that
488 * loop type's `Op create_op()`.
489 *
490 * The mechanics of using this are explained in Concurrent_task_loop doc header. Beyond that please see the particular
491 * specialization's doc header.
492 *
493 * @relatesalso Concurrent_task_loop
494 * @tparam Exec_ctx_ptr
495 * A pointer type (raw or smart) pointing to an execution context type satisfying
496 * boost.asio's "execution context" concept. As of this writing the known values would be
497 * pointers to util::Task_engine and util::Strand, but really it depends on the particular
498 * subclass of Concurrent_task_loop for the `*loop` arg. See its doc header near
499 * the particular Concurrent_task_loop subclass.
500 * @param loop
501 * Loop object that, one way or another, generated and returned `op`.
502 * @param op
503 * async::Op from `*loop` from which to extract the execution context object on which you'd like to perform
504 * custom boost.asio work.
505 * @return Pointer to a mutable execution context object.
506 */
507template<typename Exec_ctx_ptr>
508Exec_ctx_ptr op_to_exec_ctx(Concurrent_task_loop* loop, const Op& op);
509
510/**
511 * Template specialization for operation that obtains the underlying execution context, in this case
512 * a util::Task_engine, stored in an async::Op generated by the given Segregated_thread_task_loop.
513 * While `*loop` is running, the Task_engine is running in exactly 1 thread.
514 *
515 * Note Concurrent_task_loop::task_engine() is spiritually related to this function; but while that one gives one
516 * a random thread's util::Task_engine, this one returns the specific thread's assigned to a multi-step async op `op`.
517 *
518 * @see Concurrent_task_loop doc header for discussion.
519 * @relatesalso Segregated_thread_task_loop
520 *
521 * @param loop
522 * Loop object that, one way or another, generated and returned `op`.
523 * Behavior is undefined if the concrete pointed-to type is not Segregated_thread_task_loop.
524 * (assertion may trip).
525 * @param op
526 * async::Op from `*loop` from which to extract the execution context on which you'd like to perform
527 * custom boost.asio work. Behavior is undefined if it is not from `*loop` (assertion may trip).
528 * @return Pointer to a mutable util::Task_engine `E` used by `*loop`.
529 */
530template<>
532
533/**
534 * Template specialization for operation that obtains the underlying execution context, in this case
535 * a util::Strand, stored in an async::Op generated by the given Cross_thread_task_loop.
536 *
537 * boost.asio tip: The returned util::Strand may be useful not only as an argument to `bind_executor()`
538 * (formerly `Strand::wrap()`, now deprecated) but can also be passed in lieu of a util::Task_engine into
539 * boost.asio-enabled I/O object constructors (util::Timer, `boost::asio::ip::tcp::socket`, etc.). The latter use
540 * uses the `Strand` as an "execution context."
541 *
542 * Note Concurrent_task_loop::task_engine() is spiritually related to this function; but while that one gives one
543 * a util::Task_engine, which corresponds to the entire thread pool, this one returns an execution context specifically
544 * assigned to a multi-step async op `op`.
545 *
546 * @see Concurrent_task_loop doc header for discussion.
547 * @relatesalso Cross_thread_task_loop
548 *
549 * @param loop
550 * Loop object that, one way or another, generated and returned `op`.
551 * Behavior is undefined if the concrete pointed-to type is not Cross_thread_task_loop.
552 * (assertion may trip).
553 * @param op
554 * async::Op from `*loop` from which to extract the execution context on which you'd like to perform
555 * custom boost.asio work. Behavior is undefined if it is not from `*loop` (assertion may trip).
556 * @return Pointer to a mutable util::Strand created from util::Task_engine `E` such that
557 * `loop->task_engine() == &E`.
558 */
559template<>
561
562} // namespace flow::async
The core flow::async interface, providing an optionally multi-threaded thread pool onto which runnabl...
Exec_ctx_ptr op_to_exec_ctx(Concurrent_task_loop *loop, const Op &op)
Template specialization model for operation that obtains the underlying execution context,...
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291
Flow module containing tools enabling multi-threaded event loops operating under the asynchronous-tas...
Definition: async_fwd.hpp:75
boost::any Op
An object of this opaque type represents a collection of 1 or more async::Task, past or future,...
Definition: async_fwd.hpp:153
void optimize_pinning_in_thread_pool(flow::log::Logger *logger_ptr, const std::vector< util::Thread * > &threads_in_pool, bool est_hw_core_sharing_helps_algo, bool est_hw_core_pinning_helps_algo, bool hw_threads_is_grouping_collated)
Assuming the same situation as documented for optimal_worker_thread_count_per_pool(),...
Synchronicity
Enumeration indicating the manner in which asio_exec_ctx_post(), and various boost....
Definition: async_fwd.hpp:223
@ S_ASYNC
Simply post the given task to execute asynchronously in some execution context – as soon as the conte...
@ S_ASYNC_AND_AWAIT_CONCURRENT_COMPLETION
Same as Synchronicity::S_ASYNC but the posting routine then waits as long as necessary for the given ...
@ S_ASYNC_AND_AWAIT_CONCURRENT_START
Same as Synchronicity::S_ASYNC but the posting routine then waits as long as necessary for the given ...
@ S_OPPORTUNISTIC_SYNC_ELSE_ASYNC
Execute the given task synchronously, if the scheduler determines that the calling thread is in its t...
void asio_exec_ctx_post(log::Logger *logger_ptr, Execution_context *exec_ctx, Synchronicity synchronicity, Task &&task)
An extension of boost.asio's post() and dispatch() free function templates, this free function templa...
Definition: util.hpp:31
boost::shared_ptr< util::Strand > Strand_ptr
Short-hand for ref-counted pointer to util::Strand.
Definition: async_fwd.hpp:212
Strand_ptr op_to_exec_ctx< Strand_ptr >(Concurrent_task_loop *loop, const Op &op)
Template specialization for operation that obtains the underlying execution context,...
boost::shared_ptr< util::Task_engine > Task_engine_ptr
Short-hand for reference-counting pointer to a mutable util::Task_engine (a/k/a boost::asio::io_servi...
Definition: async_fwd.hpp:198
unsigned int optimal_worker_thread_count_per_pool(flow::log::Logger *logger_ptr, bool est_hw_core_sharing_helps_algo)
Assuming a planned thread pool will be receiving ~symmetrical load, and its UX-affecting (in particul...
Task_engine_ptr op_to_exec_ctx< Task_engine_ptr >(Concurrent_task_loop *loop, const Op &op)
Template specialization for operation that obtains the underlying execution context,...
Function< void()> Task
Short-hand for a task that can be posted for execution by a Concurrent_task_loop or flow::util::Task_...
Definition: async_fwd.hpp:96
auto asio_handler_via_op(Concurrent_task_loop *loop, const Op &op, Handler &&handler)
Given a boost.asio completion handler handler for a boost.asio async_*() action on some boost....
Function< void(bool short_fire)> Scheduled_task
Short-hand for tasks that can be scheduled/fired by schedule_task_from_now() and similar.
boost::system::error_code Error_code
Short-hand for a boost.system error code (which basically encapsulates an integer/enum error code and...
Definition: common.hpp:502