Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
timer_ev_emitter.hpp
Go to the documentation of this file.
1/* Flow-IPC: Core
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
21#include "ipc/util/util_fwd.hpp"
22#include <flow/async/single_thread_task_loop.hpp>
23#include <boost/unordered/unordered_flat_map.hpp>
24#include <boost/asio/readable_pipe.hpp>
25#include <boost/asio/writable_pipe.hpp>
26#include <boost/move/unique_ptr.hpp>
27#include <boost/noncopyable.hpp>
28#include <string>
29#include <vector>
30
31namespace ipc::util::sync_io
32{
33
34/**
35 * An object of this type, used internally to implement `sync_io`-pattern objects that require timer events,
36 * runs a thread dedicated exclusively to running timer waits on the `sync_io` object's behalf, so that when
37 * such a timer fires, it emits a pipe-readable event to be detected by the user's event loop, which it then reports to
38 * `sync_io` object.
39 *
40 * The thread is started lazily: by the first timer_async_wait() call, if any; from then on it lives for the rest of
41 * `*this` lifetime. This is an optimization that makes a `*this` particularly light for (potentially quite common)
42 * cases where the user `sync_io` object may not actually need to do any timer work; but a `*this` can still be
43 * constructed greedily and merely take some memory until/unless it's actually needed.
44 *
45 * @see ipc::util::sync_io for discussion of the `sync_io` pattern. This is necessary background for the present
46 * class which is used in implementing objects within that pattern.
47 *
48 * ### Rationale ###
49 * In the `sync_io` pattern, suppose there is ipc::transport or ipc::session object X (e.g.,
50 * ipc::transport::sync_io::Native_socket_stream_impl). By definition of the `sync_io` pattern, X itself
51 * endeavours to not async-wait on various events itself, in its own threads it might start, but rather
52 * instructs the user to wait on Native_handle being readable or writable and to inform X of such an event
53 * (see sync_io::Event_wait_func doc header). For example `Native_socket_stream_impl`, when awaiting in-traffic,
54 * uses #Event_wait_func to inform itself when the Unix-domain socket has become readable -- the user uses
55 * their own `[e]poll*()` or boost.asio loop or ... to detect the readability and then informs X when this happens.
56 *
57 * Some objects, in particular at least various `Blob_receiver`s and `Blob_sender`s, also at times wait on
58 * timer events; in implementing transport::Blob_receiver::idle_timer_run() and transport::Blob_sender::auto_ping()
59 * respectively. For example if the idle timer fires, then `Blob_receiver` X needs to know; and if there is
60 * an outstanding `Blob_receiver::async_receive_blob()`, it should not continue being outstanding but rather:
61 * (1) The user's event loop -- which might otherwise be in the middle of a blocking `epoll_wait()` -- must
62 * exit any such wait; and (2) X should be informed, in this case so the outstanding receive-op would complete
63 * with the idle-timeout error, detectable by the user's event loop before the next `epoll_wait()`.
64 *
65 * But how to even accomplish (1)? ((2) is relatively simple in comparison.) Possibilities:
66 * - The user can be told of the next-soonest timer's expiration time, so that the `epoll_wait()` (or `poll()` or
67 * ...) can be set to time out at that time, latest.
68 * - This is viable but presents a tricky API design problem for the `sync_io` pattern. The next-soonest
69 * timer expiration time will change over time, as new timers pop or existing timers are rescheduled.
70 * #Event_wait_func semantics have to be expanded beyond us specifying a mere (FD, read-vs-write flag)
71 * pair; it would now specify a timer, and somehow the next-soonest timer across *all* `sync_io` objects
72 * in use by the user (not just X alone) would need to be computable. Doable but much more complex
73 * than a set of decoupled FD+flag pairs, one per #Event_wait_func invocation.
74 * - The OS can be somehow told to tickle a dedicated-to-this-purpose FD (per timer) at a particular time,
75 * while #Event_wait_func is used (same as it is for Unix domain sockets, etc.) to wait for readability of
76 * that FD. `epoll_wait()` (or ...) would wake up like on any other event and inform X, and X would even
77 * know which timer it is, by being informed via `Event_wait_func`'s `on_active_ev_func` arg.
78 *
79 * The latter option is much more attractive in terms of API simplicity from the user's point of view (at least).
80 * So we go with that. Having made this decision, how to actually make it work? Possibilities:
81 * - The OS has these mechanisms. Linux, at least, has `timerfd_create()` and buddies, specifically for this.
82 * One can schedule a timer, so the kernel keeps track of all that and does it in the background; when
83 * ready the FD becomes readable. Done.
84 * - This is a very reasonable approach. However it is not very portable, and the API is hairy enough to
85 * require a bunch of (internal to us) wrapping code: specifying the time along the proper clock,
86 * rescheduling, canceling....
87 * - boost.asio, of course, has `flow::util::Timer` (a/k/a `waitable_timer`). `Timer::async_wait(F)` starts
88 * a wait, invoking `F` in whatever thread(s) are running the `Timer`'s attached `Task_engine`
89 * (a/k/a `io_context`); `Timer::expires_after()` (re)schedules the timer; etc.
90 * - This is a much nicer API, and it is portable and oft-used in the Flow world. The only problem is it
91 * is not attached -- in a public way -- to any particular FD. Although boost.asio source shows
92 * internally `timerfd_create()` is used (at least when certain compile-time flags are set),
93 * there's no `Timer::native_handle()` that exposes the timer FD. That said `F()` can do whatever we
94 * want... including pinging some simple (internally-setup) IPC mechanism, like an anonymous pipe,
95 * whose read-end is accessed via an FD.
96 *
97 * Bottom line, we went with the latter approach. It is more elegant. It does, however, require two things
98 * which the kernel-timer approach does not:
99 * - The IPC mechanism. We use a pipe (per timer). boost.asio even provides portable support for this.
100 * - A background thread in which `F()` would write a byte to this pipe, when the timer fires.
101 * - Threads are easy to set up for us. Isn't this a performance problem though? Answer: No; at least not
102 * in our use case. The overhead is in 1, the context switch *at the time a timer actually fires* and
103 * 2, the pipe write and resulting read. These theoretically add latency but *only* when a timer
104 * fires. If we were using timers for something frequent -- like, I don't know, packet pacing -- this
105 * would be an issue. We do not: idle timers and auto-ping timers fire at worst seconds apart.
106 * There is no real latency impact there over time.
107 *
108 * So there you have it. We need:
109 * - A thread for 1+ timers. E.g., typically, one object X would require one thread for all its timers.
110 * - An unnamed pipe per timer. The thread would write to the write end, while object X would read from
111 * the read end and give the read-end FD to the user to wait-on via #Event_wait_func.
112 * - Some simple logic such that when a given timer fires, the handler (in the aforementioned thread)
113 * merely writes a byte to the associated pipe.
114 *
115 * Timer_event_emitter supplies those things.
116 *
117 * ### How to use ###
118 * Construct Timer_event_emitter. This sets up -- but does not yet start -- the worker thread; the first
119 * timer_async_wait() starts it. Once started it remains totally idle with the sole exception of a pipe-write
120 * executing when a timer actually fires.
121 *
122 * Call create_timer(). This just returns a totally normal `flow::util::Timer` to be saved in the
123 * `sync_io`-pattern-implementing object. create_timer() merely associates the `*this` worker thread (started
124 * or not yet) with that timer, so that when `Timer::async_wait(F)` eventually causes `F()` to execute, it will
125 * execute `F()` in that thread.
126 *
127 * Call create_timer_signal_pipe() (for each timer one plans to use). This creates a pipe; saves both ends
128 * inside `*this`; and returns a pointer to the read-end. The `sync_io`-pattern object saves this,
129 * including so that it can pass the associated Native_handle (FD) to #Event_wait_func, whenever it
130 * begins a timer-wait.
131 *
132 * Use the `Timer` completely as normal, including `Timer::expires_after()` to schedule and reschedule
133 * the firing time... with one exception:
134 *
135 * Instead of the usual `Timer::async_wait()`, call instead timer_async_wait(), which takes:
136 * - Pointer to the `Timer`. `*this` will `->async_wait(F)` on that `Timer`.
137 * - Pointer to the associated pipe read-end. `*this` will use it as a key to determine the associated
138 * write-end. `F()` will simply perform the 1-byte-write to that write-end.
139 *
140 * (timer_async_wait() is, basically, a convenience. We could instead have had create_timer_signal_pipe()
141 * return both the read-end and write-end; and had the `*this` user memorize both and itself do
142 * the `Timer::async_wait()`, with the handler writing to the write-end. It is nicer to keep that stuff
143 * to `*this`.)
144 *
145 * Lastly, when #Event_wait_func `on_active_ev_func()` informs the `sync_io`-pattern-implementing object
146 * of a timer-associated FD being readable, it must read the byte off the pipe by calling
147 * consume_timer_firing_signal(). (Again, it's just a convenience. We could have had the user object
148 * read the byte themselves. It is nicer to keep the protocol inside `*this`.)
149 *
150 * ### Thread safety note ###
151 * A given `*this` itself has boring thread-safety properties: you may not call a non-`const` method
152 * while calling another method. That, however, is unlikely to be interesting.
153 *
154 * It is not safe to concurrently call `timer_async_wait(&T)`, where `T` is a `Timer`, with any other
155 * method of `T`. You can think, in this context, of `timer_async_wait(&T)` as being equivalent to
156 * `T->async_wait()`.
157 */
159 public flow::log::Log_context,
160 private boost::noncopyable // And non-movable.
161{
162public:
163 // Types.
164
165 /**
166 * Object representing the read end of IPC mechanism, where readable status indicates the associated
167 * timer_async_wait() call has resulted in the timer firing. Formally the `*this` user shall perform only
168 * the following operations on such an object R, after obtaining it from create_timer_signal_pipe():
169 * - Load your Asio_waitable_native_handle `E` via `E.assign(Native_handle{R.native_handle()})`
170 * or equivalent.
171 * - Call `Event_wait_func`, passing in `&E`, just ahead of Timer_event_emitter::timer_async_wait().
172 * - Call `consume_timer_firing_signal(&R)` in the handler for the so-registered-via-`Event_wait_func`
173 * event (meaning, if the timer indeed fired).
174 */
175 using Timer_fired_read_end = boost::asio::readable_pipe;
176
177 // Constructors/destructor.
178
179 /**
180 * Constructs emitter. No thread is yet started; the first timer_async_wait() starts it lazily.
181 *
182 * @param logger_ptr
183 * Logger to use for subsequently logging.
184 * @param nickname_str
185 * Human-readable nickname of the new object, as of this writing for use in `operator<<(ostream)` and
186 * logging only.
187 */
188 explicit Timer_event_emitter(flow::log::Logger* logger_ptr, String_view nickname_str);
189
190 /// Ends background thread, if any started; hence no timer-firings will execute after.
192
193 // Methods.
194
195 /**
196 * Creates idle timer for use with timer_async_wait() subsequently. It is associated with the `*this` worker
197 * thread (whether or not it has been lazily started yet), meaning completion handlers shall execute in that
198 * thread.
199 *
200 * Formally, behavior is undefined if T is the returned timer or one moved-from it, and one invokes
201 * `T.async_wait()` on it; you must use `timer_async_wait(&T, ...)` instead. You may call other `T`
202 * methods; in particular the `expires_*()` mutators will be useful. You may in particular call
203 * `T.cancel()` and destroy `T`. However see timer_async_wait() regarding the effects of these various
204 * methods/calls.
205 *
206 * @return See above.
207 */
208 flow::util::Timer create_timer();
209
210 /**
211 * Creates, and internally stores, an IPC mechanism instance intended for use with a given create_timer()-returned
212 * `Timer`; returns pointer to the read-end of this mechanism.
213 *
214 * The returned object is valid through `*this` lifetime.
215 *
216 * @see #Timer_fired_read_end doc header for instructions on the acceptable/expected uses of the returned object.
217 *
218 * @return See above.
219 */
221
222 /**
223 * To be used on a timer `T` returned by create_timer(), this is the replacement for the usual
224 * `T.async_wait(F)` call that loads a completion handler to execute at the expiration time. Instead of
225 * supplying the completion handler `F()`, one supplies a read-end returned by create_timer_signal_pipe();
226 * when the timer fires a payload shall be written to the associated write-end. You must call
227 * your #Event_wait_func, passing it an Asio_waitable_native_handle, wrapping the handle in the same `*read_end`,
228 * ahead of this timer_async_wait() call.
229 *
230 * You may not call timer_async_wait() on the same `*read_end` until one of the following things occurs:
231 * - The timer fires; and the user event loop's async-wait indeed wakes up and passes that to your object;
232 * and your object therefore invokes `consume_timer_firing_signal(read_end)`.
233 * - The async-wait is *successfully* canceled: `T.cancel()` or `T.expires_*()` returns non-zero (in fact, 1).
234 *
235 * @param timer
236 * The timer to arm.
237 * @param read_end
238 * A value returned by create_timer_signal_pipe().
239 */
240 void timer_async_wait(flow::util::Timer* timer, Timer_fired_read_end* read_end);
241
242 /**
243 * Must be called after a timer_async_wait()-triggered timer fired, before invoking that method for the same
244 * `Timer` again. It reads the small payload that was written at the time of the timer's firing (in
245 * the internal-IPC mechanism used for this purpose).
246 *
247 * @param read_end
248 * See timer_async_wait() and create_timer_signal_pipe().
249 */
251
252 // Data.
253
254 /// Nickname as passed to ctor.
255 const std::string m_nickname;
256
257private:
258 // Data.
259
260 /// Whether `m_worker.start()` has already been lazily called (first timer_async_wait()); initially `false`.
262
263 /**
264 * The thread where (only) timer-firing events (from create_timer()-created `Timer`s) execute.
265 *
266 * ### Order subtleties ###
267 * Members #m_signal_pipe_readers and #m_signal_pipe_writers hold boost.asio objects that store/use references to
268 * `*(m_worker.task_engine())` (a/k/a an `io_context`), which is owned by `m_worker`. Specifically their
269 * destructors can and do access this; bottom line is the `Task_engine`, therefore `m_worker`, must outlive
270 * those data members -- or really the objects stored in those containers. On the other hand, if `m_worker`
271 * background thread is still running, after `m_signal_pipe_writers` and `m_signal_pipe_readers` are destroyed,
272 * then briefly handlers posted onto `m_worker` could use-after-free. So no default destruction order solves
273 * both dangers.
274 *
275 * This is not special; the situation arises in various places that deal with `Single_thread_task_loop` et al.
276 * We deal with it by declaring `m_worker` first, so the `Task_engine` lives longest; and use `.stop()` in dtor
277 * to avoid the other problem.
278 */
279 flow::async::Single_thread_task_loop m_worker;
280
281 /**
282 * The readers (never null) returned by create_timer_signal_pipe(). The order is immaterial, as
283 * this is just a place #m_signal_pipe_writers map keys can point into. Wrapped into `unique_ptr`, so that
284 * the address of a #Timer_fired_read_end (as returned by create_timer_signal_pipe()) remains valid until `*this`
285 * dies.
286 */
287 std::vector<boost::movelib::unique_ptr<Timer_fired_read_end>> m_signal_pipe_readers;
288
289 /**
290 * Stores the write-ends of the pipes created in create_timer_signal_pipe(), indexed by
291 * pointer to their respective read-ends. timer_async_wait() can therefore look-up a write-end
292 * based on the read-end pointer it gave to the user, which the user must pass-to timer_async_wait().
293 */
294 boost::unordered_flat_map<Timer_fired_read_end*,
295 boost::movelib::unique_ptr<boost::asio::writable_pipe>> m_signal_pipe_writers;
296}; // class Timer_event_emitter
297
298// Free functions: in *_fwd.hpp.
299
300} // namespace ipc::util::sync_io
An object of this type, used internally to implement sync_io-pattern objects that require timer event...
bool m_worker_started
Whether m_worker.start() has already been lazily called (first timer_async_wait()); initially false.
boost::asio::readable_pipe Timer_fired_read_end
Object representing the read end of IPC mechanism, where readable status indicates the associated tim...
boost::unordered_flat_map< Timer_fired_read_end *, boost::movelib::unique_ptr< boost::asio::writable_pipe > > m_signal_pipe_writers
Stores the write-ends of the pipes created in create_timer_signal_pipe(), indexed by pointer to their...
const std::string m_nickname
Nickname as passed to ctor.
void consume_timer_firing_signal(Timer_fired_read_end *read_end)
Must be called after a timer_async_wait()-triggered timer fired, before invoking that method for the ...
void timer_async_wait(flow::util::Timer *timer, Timer_fired_read_end *read_end)
To be used on a timer T returned by create_timer(), this is the replacement for the usual T....
~Timer_event_emitter()
Ends background thread, if any started; hence no timer-firings will execute after.
Timer_event_emitter(flow::log::Logger *logger_ptr, String_view nickname_str)
Constructs emitter.
flow::util::Timer create_timer()
Creates idle timer for use with timer_async_wait() subsequently.
flow::async::Single_thread_task_loop m_worker
The thread where (only) timer-firing events (from create_timer()-created Timers) execute.
Timer_fired_read_end * create_timer_signal_pipe()
Creates, and internally stores, an IPC mechanism instance intended for use with a given create_timer(...
std::vector< boost::movelib::unique_ptr< Timer_fired_read_end > > m_signal_pipe_readers
The readers (never null) returned by create_timer_signal_pipe().
Contains common code, as well as important explanatory documentation in the following text,...
Definition: util_fwd.hpp:217
flow::util::String_view String_view
Short-hand for Flow's String_view.
Definition: util_fwd.hpp:112