Flow 3.0.0
Flow project: Full implementation reference.
timed_concurrent_task_loop.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 <atomic>
25
26namespace flow::async
27{
28
29// Types.
30
31/**
32 * Decorator of a Concurrent_task_loop with same or greater lifetime that accumulates time elapsed in any tasks posted
33 * or scheduled onto that loop. While this class template is publicly available, most likely the user shall choose
34 * one of 2 related classes:
35 * - Timed_concurrent_task_loop: This is essentially an alias of `Timed_concurrent_task_loop_impl<T>`, where `T` is
36 * an `atomic` suitable for accumulating times from tasks executing across a multi-thread pool.
37 * Use this if you have a general Concurrent_task_loop (of whatever actual non-abstract type) which may
38 * at times use multiple threads. In other words use it if you want a Concurrent_task_loop... but timed.
39 * - Timed_single_thread_task_loop: This is a refinement (subclass) of Single_thread_task_loop.
40 * Internally it uses a `Timed_concurrent_task_loop<perf::duration_rep_t>`, since no `atomic` is needed with
41 * only 1 thread of execution. In other words use it if you want a Single_thread_task_loop... but timed.
42 *
43 * @see Timed_concurrent_task_loop doc header for instructions/synopsis of timing functionality.
44 *
45 * @tparam Time_accumulator
46 * As of this writing this should be either perf::duration_rep_t; or `atomic<perf::duration_rep_t>`,
47 * depending on whether the `Timed_*_loop` that uses this template instance is guaranteed to be
48 * single-threaded or potentially a multi-thread pool.
49 */
50template<typename Time_accumulator>
52{
53public:
54 // Types.
55
56 /// Short-hand for the exchanger function taken by the ctor.
57 using Exchanger_func = Function<perf::duration_rep_t (Time_accumulator*)>;
58
59 // Constructors/destructor.
60
61 /**
62 * Constructs a time-accumulating version of existing loop `*loop`. `*loop` must exist at least until
63 * `*this` is destroyed.
64 *
65 * @param clock_type
66 * See timed_function(). Use a perf::Clock_type that tracks thread time, not process time, typically.
67 * That said, in single-thread loops without competing concurrent work on the same machine,
68 * more precise non-per-thread clocks may be suitable (it is a trade-off).
69 * @param loop
70 * The loop object being decorated with timing functionality.
71 * @param exchanger_func_moved
72 * If `A` is a `Time_accumulator`, then `exchanger_func_moved()` must return
73 * the perf::duration_rep_t currently stored in `A` and -- as atomically as possible --
74 * store `0` in `A` before returning that. More specifically, if `Time_accumulator` is an `atomic`,
75 * then use `.exchange(0)`; if it's simply perf::duration_rep_t, then it can save the value, set it to 0,
76 * then return the saved thing (as concurrency is not a concern).
77 */
79 Exchanger_func&& exchanger_func_moved);
80
81 // Methods.
82
83 /**
84 * Implements superclass API.
85 * @param init_task_or_empty
86 * See superclass API.
87 * @param thread_init_func_or_empty
88 * See superclass API.
89 */
90 void start(Task&& init_task_or_empty = {}, const Thread_init_func& thread_init_func_or_empty = {}) override;
91
92 /// Implements superclass API.
93 void stop() override;
94
95 /**
96 * Implements superclass API.
97 * @return See superclass API.
98 */
99 size_t n_threads() const override;
100
101 /**
102 * Implements superclass API.
103 * @return See superclass API.
104 */
105 Op create_op() override;
106
107 /**
108 * Implements superclass API.
109 * @return See superclass API.
110 */
111 const Op_list& per_thread_ops() override;
112
113 /**
114 * Implements superclass API. In this implementation: `task` will execute as soon as a thread is available, because
115 * no other applicable work is forthcoming.
116 *
117 * @param task
118 * See superclass API.
119 * @param synchronicity
120 * See superclass API.
121 */
122 void post(Task&& task, Synchronicity synchronicity = Synchronicity::S_ASYNC) override;
123
124 /**
125 * Implements superclass API.
126 *
127 * In this implementation: `task` will execute as soon as a thread is available, because
128 * no other applicable work is forthcoming, plus one more constraint: It will not allow `task` to execute concurrently
129 * to any other `Task` also associated with `op`. The latter is accomplished on account of
130 * `Op` internally being associated with a util::Strand.
131 *
132 * @param op
133 * See superclass API.
134 * @param task
135 * See superclass API.
136 * @param synchronicity
137 * See superclass API.
138 */
139 void post(const Op& op, Task&& task, Synchronicity synchronicity = Synchronicity::S_ASYNC) override;
140
141 /**
142 * Implements superclass API.
143 * @param from_now
144 * See superclass API.
145 * @param task
146 * See superclass API.
147 * @return See superclass API.
148 */
150
151 /**
152 * Implements superclass API.
153 * @param at
154 * See superclass API.
155 * @param task
156 * See superclass API.
157 * @return See superclass API.
158 */
160
161 /**
162 * Implements superclass API.
163 * @param op
164 * See superclass API.
165 * @param from_now
166 * See superclass API.
167 * @param task
168 * See superclass API.
169 * @return See superclass API.
170 */
172 const Fine_duration& from_now, Scheduled_task&& task) override;
173
174 /**
175 * See superclass API.
176 * @param op
177 * See superclass API.
178 * @param at
179 * See superclass API.
180 * @param task
181 * See superclass API.
182 * @return See superclass API.
183 */
185 const Fine_time_pt& at, Scheduled_task&& task) override;
186
187 /**
188 * See superclass API.
189 * @return See superclass API.
190 */
192
193 /**
194 * Returns the accumulated time spent on tasks this thread pool handles since its last invocation; and reset the
195 * accumulated time.
196 *
197 * @see Constructor which controls the behavior of the timing.
198 *
199 * @return See above.
200 */
202
203 /**
204 * Given a boost.asio *completion handler* `handler` for a boost.asio `async_*()` action on some boost.asio I/O
205 * object to be initiated in the immediate near future, returns a wrapped handler with the same signature
206 * to be passed as the handler arg to that `async_*()` action, so that `handler()` will be properly timed.
207 *
208 * The mechanics of using this are explained in the class doc header. Using this in any other
209 * fashion leads to undefined behavior.
210 *
211 * @tparam Handler
212 * See asio_handler_via_op(): same here.
213 * @param handler_moved
214 * Completion handler for the boost.asio `async_*()` operation to be initiated soon.
215 * It may be `move`d and saved.
216 * @return A completion handler that will act as `handler()` but with the addition of timing as
217 * returned by accumulated_time().
218 */
219 template<typename Handler>
220 auto asio_handler_timed(Handler&& handler_moved);
221
222private:
223 // Data.
224
225 /// See constructor.
227
228 /// See constructor.
230
231 /// See constructor.
233
234 /// Accumulates time ticks, of clock type #m_clock_type, spent in tasks posted onto #m_loop.
235 Time_accumulator m_time_accumulator;
236}; // class Timed_concurrent_task_loop_impl
237
238/**
239 * Decorates a general -- potentially multi-threaded -- Concurrent_task_loop of any kind but with timing capabilities.
240 *
241 * Using one of these is identical to using the underlying Concurrent_task_loop; except that after constructing it
242 * it must be passed to this class's ctor. From that point on, `*this` is identical to that underlying loop object,
243 * except that all relevant handlers posted onto it will be timed. One can then access the result (and reset the
244 * accumulator to zero) simply by calling accumulated_time().
245 *
246 * The following tasks shall be timed, with results accessible via accumulated_time():
247 * - Anything posted via post().
248 * - Anything scheduled via `schedule_task_*()`.
249 * - Anything invoked directly through a boost.asio `async_*()` API, as long as:
250 * - either it is wrapped via asio_handler_via_op() (to execute through an async::Op);
251 * - or it is wrapped via asio_handler_timed() (otherwise).
252 *
253 * @warning Unfortunately, any "naked" handler passed to a boost.asio async procedure will not be timed;
254 * so don't forget to use a wrapper (presumably asio_handler_timed()).
255 * The key point: 3/4 posting techniques listed above are identical in use to general use of
256 * Concurrent_task_loop as explained in that class's doc header. The exception is the need to
257 * use asio_handler_timed() when dealing with "naked" boost.asio handlers (the last bullet point above).
258 */
259class Timed_concurrent_task_loop : public Timed_concurrent_task_loop_impl<std::atomic<perf::duration_rep_t>>
260{
261public:
262 // Constructors/destructor.
263
264 /**
265 * Constructs a time-accumulating version of existing loop `*loop`. `*loop` must exist at least until
266 * `*this` is destroyed.
267 *
268 * @param clock_type
269 * See Timed_concurrent_task_loop_impl ctor.
270 * @param loop
271 * See Timed_concurrent_task_loop_impl ctor.
272 */
275
276private:
277 // Types.
278
279 /// Convenience alias to our thread-safe tick accumulator type.
280 using Time_accumulator = std::atomic<perf::duration_rep_t>;
281
282 /// Convenience alias to our superclass.
284}; // class Timed_concurrent_task_loop
285
286// Template implementations.
287
288template<typename Time_accumulator>
290 (Concurrent_task_loop* loop, perf::Clock_type clock_type, Exchanger_func&& exchanger_func_moved) :
291 m_clock_type(clock_type),
292 m_loop(loop),
293 m_exchanger_func(std::move(exchanger_func_moved)),
294 m_time_accumulator(0)
295{
296 // That's it.
297}
298
299template<typename Time_accumulator>
301 (Task&& init_task_or_empty, const Thread_init_func& thread_init_func_or_empty)
302{
303 m_loop->start(std::move(init_task_or_empty), thread_init_func_or_empty);
304}
305
306template<typename Time_accumulator>
308{
309 m_loop->stop();
310}
311
312template<typename Time_accumulator>
314{
315 return m_loop->n_threads();
316}
317
318template<typename Time_accumulator>
320{
321 return m_loop->create_op();
322}
323
324template<typename Time_accumulator>
326{
327 return m_loop->per_thread_ops();
328}
329
330template<typename Time_accumulator>
332{
333 m_loop->post(perf::timed_function(m_clock_type, &m_time_accumulator, std::move(task)),
334 synchronicity);
335}
336
337template<typename Time_accumulator>
339{
340 m_loop->post(op,
341 perf::timed_function(m_clock_type, &m_time_accumulator, std::move(task)),
342 synchronicity);
343}
344
345template<typename Time_accumulator>
347 (const Fine_duration& from_now, Scheduled_task&& task)
348{
349 return m_loop->schedule_from_now(from_now,
350 perf::timed_function(m_clock_type, &m_time_accumulator, std::move(task)));
351}
352
353template<typename Time_accumulator>
355 (const Fine_time_pt& at, Scheduled_task&& task)
356{
357 return m_loop->schedule_at(at,
358 perf::timed_function(m_clock_type, &m_time_accumulator, std::move(task)));
359}
360
361template<typename Time_accumulator>
363 (const Op& op, const Fine_duration& from_now, Scheduled_task&& task)
364{
365 return m_loop->schedule_from_now(op, from_now,
366 perf::timed_function(m_clock_type, &m_time_accumulator, std::move(task)));
367}
368
369template<typename Time_accumulator>
371 (const Op& op, const Fine_time_pt& at, Scheduled_task&& task)
372{
373 return m_loop->schedule_at(op, at,
374 perf::timed_function(m_clock_type, &m_time_accumulator, std::move(task)));
375}
376
377template<typename Time_accumulator>
379{
380 return m_loop->task_engine();
381}
382
383template<typename Time_accumulator>
385{
386 return perf::Duration{m_exchanger_func(&m_time_accumulator)};
387}
388
389template<typename Time_accumulator>
390template<typename Handler>
392{
393 return perf::timed_handler(m_clock_type, &m_time_accumulator, std::move(handler_moved));
394 // Note that timed_handler() specifically promises to not "lose" any executor (e.g., strand) bound to `handler_moved`.
395}
396
397} // namespace flow::async
The core flow::async interface, providing an optionally multi-threaded thread pool onto which runnabl...
Function< void(size_t thread_idx)> Thread_init_func
Short-hand for the thread-initializer-function optional arg type to start().
Simple, immutable vector-like sequence of N opaque async::Op objects, usually corresponding to N work...
Definition: op.hpp:58
Decorator of a Concurrent_task_loop with same or greater lifetime that accumulates time elapsed in an...
void start(Task &&init_task_or_empty={}, const Thread_init_func &thread_init_func_or_empty={}) override
Implements superclass API.
Concurrent_task_loop *const m_loop
See constructor.
size_t n_threads() const override
Implements superclass API.
const flow::perf::Clock_type m_clock_type
See constructor.
util::Scheduled_task_handle schedule_at(const Fine_time_pt &at, Scheduled_task &&task) override
Implements superclass API.
void post(const Op &op, Task &&task, Synchronicity synchronicity=Synchronicity::S_ASYNC) override
Implements superclass API.
const Exchanger_func m_exchanger_func
See constructor.
perf::Duration accumulated_time()
Returns the accumulated time spent on tasks this thread pool handles since its last invocation; and r...
Time_accumulator m_time_accumulator
Accumulates time ticks, of clock type m_clock_type, spent in tasks posted onto m_loop.
util::Scheduled_task_handle schedule_from_now(const Fine_duration &from_now, Scheduled_task &&task) override
Implements superclass API.
util::Scheduled_task_handle schedule_at(const Op &op, const Fine_time_pt &at, Scheduled_task &&task) override
See superclass API.
Op create_op() override
Implements superclass API.
const Op_list & per_thread_ops() override
Implements superclass API.
auto asio_handler_timed(Handler &&handler_moved)
Given a boost.asio completion handler handler for a boost.asio async_*() action on some boost....
Task_engine_ptr task_engine() override
See superclass API.
void stop() override
Implements superclass API.
void post(Task &&task, Synchronicity synchronicity=Synchronicity::S_ASYNC) override
Implements superclass API.
util::Scheduled_task_handle schedule_from_now(const Op &op, const Fine_duration &from_now, Scheduled_task &&task) override
Implements superclass API.
Timed_concurrent_task_loop_impl(Concurrent_task_loop *loop, perf::Clock_type clock_type, Exchanger_func &&exchanger_func_moved)
Constructs a time-accumulating version of existing loop *loop.
Decorates a general – potentially multi-threaded – Concurrent_task_loop of any kind but with timing c...
Timed_concurrent_task_loop(Concurrent_task_loop *loop, perf::Clock_type clock_type=perf::Clock_type::S_CPU_THREAD_TOTAL_HI_RES)
Constructs a time-accumulating version of existing loop *loop.
std::atomic< perf::duration_rep_t > Time_accumulator
Convenience alias to our thread-safe tick accumulator type.
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
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...
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_conte...
Definition: async_fwd.hpp:198
auto timed_function(Clock_type clock_type, Accumulator *accumulator, Func &&function)
Constructs a closure that times and executes void-returning function(), adding the elapsed time with ...
Fine_duration Duration
Short-hand for a high-precision boost.chrono duration, formally equivalent to flow::Fine_duration.
Duration::rep duration_rep_t
The raw type used in Duration to store its clock ticks.
auto timed_handler(Clock_type clock_type, Accumulator *accumulator, Handler &&handler)
Identical to timed_function() but suitable for boost.asio-targeted handler functions.
Clock_type
Clock types supported by flow::perf module facilities, perf::Checkpointing_timer in particular.
@ S_CPU_THREAD_TOTAL_HI_RES
Similar to S_CPU_TOTAL_HI_RES but applied to the calling thread as opposed to entire process.
boost::shared_ptr< Scheduled_task_handle_state > Scheduled_task_handle
Black-box type that represents a handle to a scheduled task as scheduled by schedule_task_at() or sch...
Fine_clock::duration Fine_duration
A high-res time duration as computed from two Fine_time_pts.
Definition: common.hpp:405
Fine_clock::time_point Fine_time_pt
A high-res time point as returned by Fine_clock::now() and suitable for precise time math in general.
Definition: common.hpp:402