Toolbox snapshot
The Reactive C++ Toolbox
Loading...
Searching...
No Matches
Reactor.ut.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
22
23#include <boost/test/unit_test.hpp>
24#include <string_view>
25#include <thread>
26
27using namespace std;
28using namespace toolbox;
29
30namespace {
31
32struct TestHandler : RefCount<TestHandler, ThreadUnsafePolicy> {
33 void on_input(CyclTime /*now*/, int fd, unsigned /*events*/)
34 {
35 char buf[4];
36 os::recv(fd, buf, 4, 0);
37 if (strcmp(buf, "foo") == 0) {
38 ++matches;
39 }
40 }
41 int matches{};
42};
43
44} // namespace
45
47
49{
50 using namespace literals::chrono_literals;
51
52 Reactor r{1024};
54
56 const auto sub = r.subscribe(*socks.second, EpollIn, bind<&TestHandler::on_input>(h.get()));
57
58 const auto now = CyclTime::now();
59 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 0);
60 BOOST_CHECK_EQUAL(h->matches, 0);
61
62 socks.first.send("foo", 4, 0);
63 socks.first.send("foo", 4, 0);
64 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 1);
65 BOOST_CHECK_EQUAL(h->matches, 1);
66 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 1);
67 BOOST_CHECK_EQUAL(h->matches, 2);
68
69 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 0);
70 BOOST_CHECK_EQUAL(h->matches, 2);
71
72 socks.first.send("foo", 4, 0);
73 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 1);
74 BOOST_CHECK_EQUAL(h->matches, 3);
75
76 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 0);
77 BOOST_CHECK_EQUAL(h->matches, 3);
78}
79
81{
82 using namespace literals::chrono_literals;
83
84 Reactor r{1024};
85
87
88 std::vector<int> fd_process_order;
89 auto handler = [&](CyclTime, int fd, unsigned) {
90 char buf[5];
91 auto rcvd = os::recv(fd, buf, 5, 0);
93
94 std::string_view bsv(buf, buf + sizeof(buf));
95 BOOST_CHECK_EQUAL(bsv, "Hello");
96 fd_process_order.push_back(fd);
97 };
98
99 auto sub1 = r.subscribe(*first_sock, EpollIn, bind(&handler));
100 auto sub2 = r.subscribe(*second_sock, EpollIn, bind(&handler));
101
102 // test send data from first_sock --> second_sock
103 first_sock.send("Hello", 5, 0);
104 r.poll(CyclTime::now(), 0ms);
107 fd_process_order.clear();
108
109 // test send data from second_sock --> first_sock
110 second_sock.send("Hello", 5, 0);
111 r.poll(CyclTime::now(), 0ms);
114 fd_process_order.clear();
115
116 // send data to both sockets -- but from `first_sock` first
117 // Set priority of `first_sock` to be High.
118 // `second_sock` will receive data first, but because of High priority
119 // of `first_sock`, it will be processed first.
120 sub1.set_io_priority(Priority::High);
121 sub2.set_io_priority(Priority::Low);
122 first_sock.send("Hello", 5, 0);
123 std::this_thread::sleep_for(100ms);
124 second_sock.send("Hello", 5, 0);
125 r.poll(CyclTime::now(), 0ms);
129 fd_process_order.clear();
130
131 // Do same again, except switch priorities.
132 sub1.set_io_priority(Priority::Low);
133 sub2.set_io_priority(Priority::High);
134 first_sock.send("Hello", 5, 0);
135 std::this_thread::sleep_for(100ms);
136 second_sock.send("Hello", 5, 0);
137 r.poll(CyclTime::now(), 0ms);
141 fd_process_order.clear();
142}
143
145{
146 Reactor r{1024};
148
149 // install high priority user hook
150 std::size_t hook_invocation_count = 0;
151 auto high_prio_hook = [&](CyclTime) {
153 return 0;
154 };
155 r.set_user_high_priority_hook(bind(&high_prio_hook));
156
157 auto [s1, s2] = socketpair(UnixStreamProtocol{});
158
159 // This is handler for data on s1. It is an expensive function taking ~250ms
160 std::size_t yield_count = 0;
161 auto on_data_received = [&](CyclTime, int, unsigned) {
162 MonoTime now = MonoClock::now();
163 MonoTime end = now + 250ms;
164 while (now < end) {
165 // wait for 1ms, then yield
166 auto next_stop = MonoClock::now() + 1ms;
167 while (now < next_stop) {
168 now = MonoClock::now();
169 }
170 r.yield();
171 yield_count++;
172 };
173 };
174
175 // data to s1 will be sent => on_data_received will be invoked by the reactor.
176 // every 100us on_data_received will yield to the reactor, at which point the reactor
177 // should invoke our custom high priority hook.
178 auto handle = r.subscribe(*s1, EpollIn, bind(&on_data_received));
179
180 s2.send("Hello", 5, 0);
181 r.poll(CyclTime::now(), 0ms);
182
185}
186
188{
189 using namespace literals::chrono_literals;
190
191 Reactor r{1024};
193
194 // h0, h1 will be high priority
195 // l0, l1 will be low priority
196 auto [h0, h1] = socketpair(UnixStreamProtocol{});
197 auto [l0, l1] = socketpair(UnixStreamProtocol{});
198
199 auto send_data_to = [&](IoSock& sock) {
200 IoSock& corresponding = (sock == h0) ? h1 : (sock == h1) ? h0 : (sock == l0) ? l1 : l0;
201 corresponding.send("Hello", 5, 0);
202 };
203
204 // this will track entry/exit of handlers of each of the sockets
205 struct Audit {
206 enum { Entry, Exit } what;
207 int fd;
208 bool operator==(const Audit& o) const = default;
209 };
210 std::vector<Audit> audit_trail;
211
212 // each invocation will take ~100ms
213 auto spin_and_yield_periodically = [&r]() {
214 MonoTime now = MonoClock::now();
215 MonoTime end = now + 100ms;
216 while (now < end) {
217 // wait for 10us, then yield
218 auto next_stop = now + 10us;
219 while (now < next_stop) {
220 now = MonoClock::now();
221 }
222 r.yield();
223 };
224 };
225
226 // reads data from fd, then pretends to do work (by spinning),
227 // yielding periodically to the reactor.
228 auto high_handler = [&](CyclTime, int fd, unsigned) {
229 audit_trail.push_back({.what = Audit::Entry, .fd = fd});
230 char buf[5];
231 auto rcvd = os::recv(fd, buf, 5, 0);
234 audit_trail.push_back({.what = Audit::Exit, .fd = fd});
235 };
236
237 // reads data from fd, sends message to h0/h1, then pretends to do work (by spinning),
238 // yielding periodically to the reactor. In those yields, the messages sent to the
239 // high priority sockets should be processed.
240 auto low_handler = [&]<bool H0, bool H1>(CyclTime, int fd, unsigned) {
241 audit_trail.push_back({.what = Audit::Entry, .fd = fd});
242
243 char buf[5];
244 auto rcvd = os::recv(fd, buf, 5, 0);
246
247 // send data to both high priority sockets
248 if constexpr (H0) {
250 }
251 if constexpr (H1) {
253 }
254
256 audit_trail.push_back({.what = Audit::Exit, .fd = fd});
257 };
258
259 auto l0_handler = [&low_handler](CyclTime ct, int fd, unsigned int e) {
260 low_handler.template operator()<true, true>(ct, fd, e);
261 };
262
263 auto l1_handler = [&low_handler](CyclTime ct, int fd, unsigned int e) {
264 low_handler.template operator()<false, true>(ct, fd, e);
265 };
266
267 auto sub_l0 = r.subscribe(*l0, EpollIn, bind(&l0_handler));
268 auto sub_l1 = r.subscribe(*l1, EpollIn, bind(&l1_handler));
269 auto sub_h0 = r.subscribe(*h0, EpollIn, bind(&high_handler));
270 auto sub_h1 = r.subscribe(*h1, EpollIn, bind(&high_handler));
271
272 sub_h0.set_io_priority(Priority::High);
273 sub_h1.set_io_priority(Priority::High);
274
275 // start off with l0, l1, h0 having data to read
279
280 r.poll(CyclTime::now(), 0ms);
281
282 std::vector<Audit> valid_seq1 = {
283 // high priority always first
284 Audit{Audit::Entry, *h0}, Audit{Audit::Exit, *h0},
285
286 // AT THIS POINT l0/l1 handlers could be executed in any order
287 // Lets assume l0 chosen first [choices so far: (l0)]
288 Audit{Audit::Entry, *l0}, // l0 handler sends data to h0 and h1
289
290 // Reactor will poll on high priority sockets when l0 handler yields
291 // AT THIS POINT h0/h1 handlers could be executed in any order
292 // Lets assume h0 is chosen first [choices so far: (l0, h0)]
293 Audit{Audit::Entry, *h0}, Audit{Audit::Exit, *h0}, Audit{Audit::Entry, *h1},
294 Audit{Audit::Exit, *h1},
295
296 // control returned back to l0 handler
297 Audit{Audit::Exit, *l0},
298
299 // Now, l1 handler executes
300 Audit{Audit::Entry, *l1}, // l1 handler sends data to h1
301 Audit{Audit::Entry, *h1}, Audit{Audit::Exit, *h1},
302 Audit{Audit::Exit, *l1}, // l1 handler sends data to h1
303 };
304
305 std::vector<Audit> valid_seq2 = {
306 // high priority always first
307 Audit{Audit::Entry, *h0}, Audit{Audit::Exit, *h0},
308
309 // AT THIS POINT l0/l1 handlers could be executed in any order
310 // Lets assume l0 chosen first [choices so far: (l0)]
311 Audit{Audit::Entry, *l0}, // l0 handler sends data to h0 and h1
312
313 // Reactor will poll on high priority sockets when l0 handler yields
314 // AT THIS POINT h0/h1 handlers could be executed in any order
315 // Lets assume h1 chosen first [choices so far: (l0, h1)]
316 Audit{Audit::Entry, *h1}, Audit{Audit::Exit, *h1}, Audit{Audit::Entry, *h0},
317 Audit{Audit::Exit, *h0},
318
319 // control returned back to l0 handler
320 Audit{Audit::Exit, *l0},
321
322 // Now, l1 handler executes
323 Audit{Audit::Entry, *l1}, // l1 handler sends data to h1
324 Audit{Audit::Entry, *h1}, Audit{Audit::Exit, *h1},
325 Audit{Audit::Exit, *l1}, // l1 handler sends data to h1
326 };
327
328 std::vector<Audit> valid_seq3 = {
329 // high priority always first
330 Audit{Audit::Entry, *h0},
331 Audit{Audit::Exit, *h0},
332
333 // AT THIS POINT l0/l1 handlers could be executed in any order
334 // Lets assume l0 chosen first [choices so far: (l1)]
335 Audit{Audit::Entry, *l1}, // l1 handler sends data to h1
336 Audit{Audit::Entry, *h1},
337 Audit{Audit::Exit, *h1},
338 Audit{Audit::Exit, *l1},
339
340 // Now, l0 handler executes
341 Audit{Audit::Entry, *l0}, // l0 handler sends data to h0 and h1
342 // Reactor will poll on high priority sockets when l0 handler yields
343 // AT THIS POINT h0/h1 handlers could be executed in any order
344 // Lets assume h0 chosen first [choices so far: (l1, h0)]
345 Audit{Audit::Entry, *h0},
346 Audit{Audit::Exit, *h0},
347 Audit{Audit::Entry, *h1},
348 Audit{Audit::Exit, *h1},
349
350 // control returned back to l0 handler
351 Audit{Audit::Exit, *l0},
352 };
353
354 std::vector<Audit> valid_seq4 = {
355 // high priority always first
356 Audit{Audit::Entry, *h0},
357 Audit{Audit::Exit, *h0},
358
359 // AT THIS POINT l0/l1 handlers could be executed in any order
360 // Lets assume l0 chosen first [choices so far: (l1)]
361 Audit{Audit::Entry, *l1}, // l1 handler sends data to h1
362 Audit{Audit::Entry, *h1},
363 Audit{Audit::Exit, *h1},
364 Audit{Audit::Exit, *l1},
365
366 // Now, l0 handler executes
367 Audit{Audit::Entry, *l0}, // l0 handler sends data to h0 and h1
368 // Reactor will poll on high priority sockets when l0 handler yields
369 // AT THIS POINT h0/h1 handlers could be executed in any order
370 // Lets assume h1 chosen first [choices so far: (l1, h1)]
371 Audit{Audit::Entry, *h1},
372 Audit{Audit::Exit, *h1},
373 Audit{Audit::Entry, *h0},
374 Audit{Audit::Exit, *h0},
375
376 // control returned back to l0 handler
377 Audit{Audit::Exit, *l0},
378 };
379
383}
384
386{
387 using namespace literals::chrono_literals;
388
389 Reactor r{1024};
391
393 auto sub = r.subscribe(*socks.second, EpollIn | EpollEt, bind<&TestHandler::on_input>(h.get()));
394
395 const auto now = CyclTime::now();
396 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 0);
397 BOOST_CHECK_EQUAL(h->matches, 0);
398
399 socks.first.send("foo", 4, 0);
400 socks.first.send("foo", 4, 0);
401 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 1);
402 BOOST_CHECK_EQUAL(h->matches, 1);
403
404 // No notification for second message.
405 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 0);
406 BOOST_CHECK_EQUAL(h->matches, 1);
407
408 // Revert to level-triggered.
409 sub.set_events(EpollIn);
410 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 1);
411 BOOST_CHECK_EQUAL(h->matches, 2);
412
413 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 0);
414 BOOST_CHECK_EQUAL(h->matches, 2);
415
416 socks.first.send("foo", 4, 0);
417 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 1);
418 BOOST_CHECK_EQUAL(h->matches, 3);
419
420 BOOST_CHECK_EQUAL(r.poll(now, 0ms), 0);
421 BOOST_CHECK_EQUAL(h->matches, 3);
422}
423
425{
426 int i{0};
427 auto fn = [&i](CyclTime) { ++i; };
428
429 Reactor r{1024};
430
431 Hook h{bind(&fn)};
432 r.add_hook(h);
433
434 BOOST_CHECK_EQUAL(r.poll(CyclTime::now(), 0ms), 0);
436}
437
439{
440 Reactor r{1024};
441
442 struct counter {
443 int invocation_count{0};
444 void operator()(CyclTime, Timer&) { ++invocation_count; }
445 };
446 counter lpc;
447 counter hpc;
448
449 auto now = CyclTime::now();
450
451 // schedule 2 low priority timers for immediate execution
452 Timer lpts[] = {
453 r.timer(now.mono_time(), Priority::Low, bind<&counter::operator()>(&lpc)),
454 r.timer(now.mono_time(), Priority::Low, bind<&counter::operator()>(&lpc)),
455 };
456
457 Timer hpt;
458
459 int num_of_times_polled = 0;
460 const auto end = now.mono_time() + 95ms;
461
462 // using 95ms instead of 100ms, because Reactor::poll internally
463 // uses own CyclTime, not the one we pass to it.
464
465 while (now.mono_time() < end) {
466
467 // schedule a high priority timer for immediate execution
468 hpt = r.timer(now.mono_time(), Priority::High, bind<&counter::operator()>(&hpc));
469
470 // low priority timers won't be executed because it will be a busy cycle
471 // due to high priority timer that is due execution.
472 r.poll(now, 0s);
473
474 BOOST_CHECK_EQUAL(hpc.invocation_count, ++num_of_times_polled);
475 BOOST_CHECK_EQUAL(lpc.invocation_count, 0);
476
477 now = CyclTime::now();
478 }
479
480 std::this_thread::sleep_for(10ms);
481
482 // at this point, both low priority timers are delayed by >100ms.
483 // they will now activately executed (one per cycle) even if cycle is busy.
484 for (int i = 0; i < 2; i++) {
485 hpt = r.timer(now.mono_time(), Priority::High, bind<&counter::operator()>(&hpc));
486
487 r.poll(now, 0s);
488
489 BOOST_CHECK_EQUAL(hpc.invocation_count, ++num_of_times_polled);
490 BOOST_CHECK_EQUAL(lpc.invocation_count, i + 1);
491
492 now = CyclTime::now();
493 }
494}
495
BOOST_CHECK_EQUAL(v.size(), 10U)
BOOST_AUTO_TEST_CASE(ReactorLevelCase)
void set_user_hook_poll_threshold(Micros thresh)
Definition Reactor.hpp:181
void set_high_priority_poll_threshold(Micros thresh)
Definition Reactor.hpp:178
Base class for atomic referenced counted objects.
Definition RefCount.hpp:61
STL namespace.
@ EpollIn
The associated file is available for read(2) operations.
Definition Epoll.hpp:115
constexpr bool operator==(const BasicHandle< PolicyT > &lhs, const BasicHandle< PolicyT > &rhs)
Definition Handle.hpp:90
std::pair< IoSock, IoSock > socketpair(ProtocolT protocol)
Definition IoSock.hpp:97
ssize_t recv(int sockfd, void *buf, std::size_t len, int flags, std::error_code &ec) noexcept
Receive a message from a socket.
Definition Socket.hpp:376
MonoClock::time_point MonoTime
Definition Time.hpp:112
constexpr auto bind() noexcept
Definition Slot.hpp:97