Toolbox snapshot
The Reactive C++ Toolbox
Loading...
Searching...
No Matches
Reactor.cpp
Go to the documentation of this file.
1// The Reactive C++ Toolbox.
2// Copyright (C) 2013-2019 Swirly Cloud Limited
3// Copyright (C) 2021 Reactive Markets Limited
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17#include "Reactor.hpp"
18
20#include <toolbox/sys/Log.hpp>
21#include <toolbox/sys/Trace.hpp>
22
23namespace toolbox {
24inline namespace io {
25using namespace std;
26namespace {
27constexpr size_t MaxEvents{128};
28
29int dispatch_low_priority_timers(CyclTime now, TimerQueue& tq, bool idle_cycle)
30{
31 int work_done = 0;
32 if (idle_cycle) {
33 work_done = tq.dispatch(now, 1);
34 } else if (!tq.empty()) {
35 // actively execute low priority timers if they've been delayed by 100ms or more.
36 if ((now.mono_time() - tq.front().expiry()) > 100ms) {
37 work_done = tq.dispatch(now, 1);
38 }
39 }
40 return work_done;
41}
42} // namespace
43
45{
46 const auto notify = notify_.fd();
47 data_.resize(max<size_t>(notify + 1, size_hint));
48 epoll_.add(notify, 0, EpollIn);
49}
50
52{
53 epoll_.del(notify_.fd());
54}
55
56Reactor::Handle Reactor::subscribe(int fd, unsigned events, IoSlot slot)
57{
58 assert(fd >= 0);
59 assert(slot);
60 if (fd >= static_cast<int>(data_.size())) {
61 data_.resize(fd + 1);
62 }
63 auto& ref = data_[fd];
64 epoll_.add(fd, ++ref.sid, events);
65 ref.events = events;
66 ref.slot = slot;
67 ref.priority = Priority::Low;
68 return {*this, fd, ref.sid};
69}
70
72{
73 enum { High = 0, Low = 1 };
74 using namespace chrono;
75
76 // If timeout is zero then the wait_until time should also be zero to signify no wait.
78 if (!is_zero(timeout) && end_of_cycle_no_wait_hooks.empty()) {
79 const MonoTime next
80 = next_expiry(timeout == NoTimeout ? MonoClock::max() : now.mono_time() + timeout);
81 if (next > now.mono_time()) {
82 wait_until = next;
83 }
84 }
85 // TODO: consider using a dynamic buffer that scales with increased demand.
87
88 int n;
89 error_code ec;
90 if (wait_until < MonoClock::max()) {
91 // The wait function will not block if time is zero.
92 n = epoll_.wait(buf, MaxEvents, wait_until, ec);
93 } else {
94 // Block indefinitely.
95 n = epoll_.wait(buf, MaxEvents, ec);
96 }
97 // Update cycle time after epoll() returns.
98 now = CyclTime::now();
99 last_time_priority_io_polled_ = now.mono_time();
100 last_time_user_hook_polled_ = now.mono_time();
101
102 if (ec) {
103 if (ec.value() != EINTR) {
104 throw system_error{ec};
105 }
106 return 0;
107 }
108 cycle_work_ = 0;
109 TOOLBOX_PROBE_SCOPED(reactor, dispatch, cycle_work_);
110 // High priority timers.
111 cycle_work_ = tqs_[High].dispatch(now);
112 // I/O events.
113 cycle_work_ += dispatch(now, buf, n, Priority::High);
114 cycle_work_ += dispatch(now, buf, n, Priority::Low);
115 // Low priority timers (typically only dispatched during empty cycles).
116 cycle_work_ += dispatch_low_priority_timers(now, tqs_[Low], cycle_work_ == 0);
117 // End of cycle hooks.
118 if (cycle_work_ > 0) {
119 io::dispatch(now, end_of_event_dispatch_hooks_);
120 }
121 io::dispatch(now, end_of_cycle_no_wait_hooks);
122 return cycle_work_;
123}
124
126{
127 // Best effort.
128 std::error_code ec;
129 notify_.write(1, ec);
130}
131
132MonoTime Reactor::next_expiry(MonoTime next) const
133{
134 enum { High = 0, Low = 1 };
135 using namespace chrono;
136 {
137 const auto& tq = tqs_[High];
138 if (!tq.empty()) {
139 // Duration until next expiry. Mitigate scheduler latency by preempting the
140 // high-priority timer and busy-waiting for 200us ahead of timer expiry.
141 next = min(next, tq.front().expiry() - 200us);
142 }
143 }
144 {
145 const auto& tq = tqs_[Low];
146 if (!tq.empty()) {
147 // Duration until next expiry.
148 next = min(next, tq.front().expiry());
149 }
150 }
151 return next;
152}
153
155{
156 if (currently_handling_priority_events_) [[unlikely]] {
157 return;
158 }
159
160 MonoTime now = MonoClock::now();
161 cycle_work_ += do_io_priority_poll(now);
162 cycle_work_ += do_user_priority_poll(now);
163}
164
165int Reactor::do_io_priority_poll(MonoTime now) noexcept
166{
167 int ret = 0;
168 try {
169 const bool enabled = priority_io_poll_threshold_ != Micros::max();
170 const auto delta = duration_cast<Micros>(now - last_time_priority_io_polled_);
171 const bool breached = delta > priority_io_poll_threshold_;
172
173 if (enabled && breached) {
174 const auto update_poll_time = make_finally(
175 [this]() noexcept { last_time_priority_io_polled_ = MonoClock::now(); });
176
177 error_code ec;
178 Event buf[MaxEvents];
179
180 int n = epoll_.wait(buf, MaxEvents, MonoTime{}, ec);
181 if (ec) {
182 if (ec.value() != EINTR) {
183 TOOLBOX_ERROR << "epoll failure during high priority io poll: " << ec << " ["
184 << ec.message() << ']';
185 }
186 } else {
187 ret = dispatch(CyclTime::current(), buf, n, Priority::High);
188 }
189 }
190 } catch (const std::exception& e) {
191 TOOLBOX_ERROR << "exception during high priority io poll: " << e.what();
192 }
193
194 return ret;
195}
196
197int Reactor::do_user_priority_poll(MonoTime now) noexcept
198{
199 int ret = 0;
200 try {
201 const bool enabled
202 = (user_hook_poll_threshold_ != Micros::max()) && priority_poll_user_hook_;
203 const auto delta = duration_cast<Micros>(now - last_time_user_hook_polled_);
204 const bool breached = delta > user_hook_poll_threshold_;
205
206 if (enabled && breached) {
207 const auto update_poll_time = make_finally(
208 [this]() noexcept { last_time_user_hook_polled_ = MonoClock::now(); });
209
210 currently_handling_priority_events_ = true;
211 const auto reset_flag
212 = make_finally([this]() noexcept { currently_handling_priority_events_ = false; });
213
214 ret = priority_poll_user_hook_(CyclTime::current());
215 }
216 } catch (const std::exception& e) {
217 TOOLBOX_ERROR << "exception during user high priority hook: " << e.what();
218 }
219
220 return ret;
221}
222
223int Reactor::dispatch(CyclTime now, Event* buf, int size, Priority priority)
224{
225 if (priority == Priority::High) {
226 assert(!currently_handling_priority_events_);
227 currently_handling_priority_events_ = true;
228 }
229 const auto reset_flag
230 = make_finally([this]() noexcept { currently_handling_priority_events_ = false; });
231
232 int work{0};
233 for (int i{0}; i < size; ++i) {
234
235 auto& ev = buf[i];
236 const auto fd = Epoll::fd(ev);
237 const auto& ref = data_[fd];
238
239 if (ref.priority != priority) {
240 continue;
241 }
242
243 if (fd == notify_.fd()) {
244 notify_.read();
245 continue;
246 }
247
248 if (!ref.slot) {
249 // Ignore timerfd.
250 continue;
251 }
252
253 const auto sid = Epoll::sid(ev);
254 // Skip this socket if it was modified after the call to wait().
255 if (ref.sid > sid) {
256 continue;
257 }
258 // Apply the interest events to filter-out any events that the user may have removed from
259 // the events since the call to wait() was made. This would typically happen via a reentrant
260 // call into the reactor from an event-handler. N.B. EpollErr and EpollHup are always
261 // reported if they occur, regardless of whether they are specified in events.
262 const auto events = ev.events & (ref.events | EpollErr | EpollHup);
263 if (!events) {
264 continue;
265 }
266
267 try {
268 ref.slot(now, fd, events);
269 } catch (const std::exception& e) {
270 TOOLBOX_ERROR << "exception in i/o event handler: " << e.what();
271 }
272 ++work;
273 }
274 return work;
275}
276
277void Reactor::set_events(int fd, int sid, unsigned events, IoSlot slot, error_code& ec) noexcept
278{
279 auto& ref = data_[fd];
280 if (ref.sid == sid) {
281 if (ref.events != events) {
282 epoll_.mod(fd, sid, events, ec);
283 if (ec) {
284 return;
285 }
286 ref.events = events;
287 }
288 ref.slot = slot;
289 }
290}
291
292void Reactor::set_events(int fd, int sid, unsigned events, IoSlot slot)
293{
294 auto& ref = data_[fd];
295 if (ref.sid == sid) {
296 if (ref.events != events) {
297 epoll_.mod(fd, sid, events);
298 ref.events = events;
299 }
300 ref.slot = slot;
301 }
302}
303
304void Reactor::set_events(int fd, int sid, unsigned events, error_code& ec) noexcept
305{
306 auto& ref = data_[fd];
307 if (ref.sid == sid && ref.events != events) {
308 epoll_.mod(fd, sid, events, ec);
309 if (ec) {
310 return;
311 }
312 ref.events = events;
313 }
314}
315
316void Reactor::set_events(int fd, int sid, unsigned events)
317{
318 auto& ref = data_[fd];
319 if (ref.sid == sid && ref.events != events) {
320 epoll_.mod(fd, sid, events);
321 ref.events = events;
322 }
323}
324
325void Reactor::unsubscribe(int fd, int sid) noexcept
326{
327 auto& ref = data_[fd];
328 if (ref.sid == sid) {
329 epoll_.del(fd);
330 ref.events = 0;
331 ref.slot.reset();
332 ref.priority = Priority::Low;
333 }
334}
335
336void Reactor::set_io_priority(int fd, int sid, Priority priority) noexcept
337{
338 auto& ref = data_[fd];
339 if (ref.sid == sid && ref.priority != priority) {
340 ref.priority = priority;
341 }
342}
343
344} // namespace io
345} // namespace toolbox
#define TOOLBOX_ERROR
Definition Log.hpp:93
#define TOOLBOX_PROBE_SCOPED(provider, name,...)
Definition Trace.hpp:46
static constexpr int sid(const Event &ev) noexcept
Definition Epoll.hpp:165
void del(int fd) noexcept
Definition Epoll.hpp:231
void mod(int fd, int sid, unsigned events, std::error_code &ec) noexcept
Definition Epoll.hpp:239
static constexpr int fd(const Event &ev) noexcept
Definition Epoll.hpp:161
void add(int fd, int sid, unsigned events)
Definition Epoll.hpp:225
int wait(Event buf[], std::size_t size, std::error_code &ec) noexcept
Returns the number of file descriptors that are ready.
Definition Epoll.hpp:193
void write(std::int64_t val, std::error_code &ec) noexcept
Definition EventFd.hpp:76
std::int64_t read()
Definition EventFd.hpp:67
int fd() const noexcept
Definition EventFd.hpp:66
Reactor(std::size_t size_hint=0)
Definition Reactor.cpp:44
~Reactor() override
Definition Reactor.cpp:51
Handle subscribe(int fd, unsigned events, IoSlot slot)
Definition Reactor.cpp:56
void yield() noexcept
Definition Reactor.cpp:154
int poll(CyclTime now, Duration timeout=NoTimeout)
Definition Reactor.cpp:71
void do_wakeup() noexcept final
Thread-safe.
Definition Reactor.cpp:125
MonoTime mono_time() const noexcept
Definition Time.hpp:139
STL namespace.
int64_t min(const Histogram &h) noexcept
Definition Utility.cpp:37
constexpr Duration NoTimeout
Definition Reactor.hpp:29
void dispatch(CyclTime now, const HookList &l) noexcept
Definition Hook.cpp:24
@ EpollIn
The associated file is available for read(2) operations.
Definition Epoll.hpp:115
BasicSlot< void(CyclTime, int, unsigned)> IoSlot
Definition Reactor.hpp:31
constexpr bool is_zero(std::chrono::duration< RepT, PeriodT > d) noexcept
Definition Time.hpp:155
MonoClock::time_point MonoTime
Definition Time.hpp:112
Nanos Duration
Definition Time.hpp:42
auto make_finally(FnT fn) noexcept
Definition Finally.hpp:48
constexpr std::size_t size(const detail::Struct< detail::Member< TagsT, ValuesT >... > &s)
Definition Struct.hpp:98
constexpr auto bind() noexcept
Definition Slot.hpp:97