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