join 1.0
lightweight network framework library
Loading...
Searching...
No Matches
timer.hpp
Go to the documentation of this file.
1
25#ifndef JOIN_CORE_TIMER_HPP
26#define JOIN_CORE_TIMER_HPP
27
28// libjoin.
29#include <join/proactor.hpp>
30#include <join/function.hpp>
31#include <join/backoff.hpp>
32#include <join/clock.hpp>
33
34// C++.
35#include <chrono>
36#include <atomic>
37#include <memory>
38#include <new>
39
40// C.
41#include <sys/timerfd.h>
42#include <unistd.h>
43#include <cstdlib>
44
45namespace join
46{
50 template <class ClockPolicy>
51 class BasicTimer : protected CompletionHandler
52 {
53 public:
57 enum class State
58 {
59 Idle,
60 Armed,
61 Invoking,
62 Closed,
63 };
64
70 : _handle (timerfd_create (ClockPolicy::type (), TFD_NONBLOCK | TFD_CLOEXEC))
71 , _proactor (proactor)
72 {
73 if (_handle == -1)
74 {
75 throw std::system_error (errno, std::system_category (), "timerfd_create failed"); // LCOV_EXCL_LINE
76 }
77
78 _ops->op = IoOperation::makeRead (_handle, &_ops->expirations,
79 static_cast<uint32_t> (sizeof (_ops->expirations)), this);
80
81 if (_proactor.submit (_ops->op, true, true) == -1)
82 {
83 // LCOV_EXCL_START
84 close (_handle);
85 throw std::system_error (lastError, "timerfd submit failed");
86 // LCOV_EXCL_STOP
87 }
88 }
89
94 BasicTimer (const BasicTimer& other) = delete;
95
101 BasicTimer& operator= (const BasicTimer& other) = delete;
102
107 BasicTimer (BasicTimer&& other) = delete;
108
114 BasicTimer& operator= (BasicTimer&& other) = delete;
115
119 ~BasicTimer () noexcept
120 {
121 cancel ();
122
123 Backoff backoff;
124 while (_state.load (std::memory_order_acquire) != State::Closed)
125 {
126 _proactor.cancel (_ops->op, true, true);
127 backoff ();
128 }
129
130 close (_handle);
131 }
132
138 template <class Rep, class Period, typename Func>
139 void setOneShot (std::chrono::duration<Rep, Period> duration, Func&& callback)
140 {
141 cancel ();
142
143 auto ns = std::chrono::duration_cast<std::chrono::nanoseconds> (duration);
144 _callback = std::forward<Func> (callback);
145 _oneShot = true;
146 _ns = std::chrono::nanoseconds::zero ();
147 _state.store (State::Armed, std::memory_order_release);
148
149 auto ts = toTimerSpec (ns);
150 timerfd_settime (_handle, 0, &ts, nullptr);
151 }
152
158 template <class Clock, class Duration, typename Func>
159 void setOneShot (std::chrono::time_point<Clock, Duration> timePoint, Func&& callback)
160 {
161 static_assert (
162 (std::is_same<ClockPolicy, RealTime>::value && std::is_same<Clock, std::chrono::system_clock>::value) ||
163 (std::is_same<ClockPolicy, Monotonic>::value &&
164 std::is_same<Clock, std::chrono::steady_clock>::value),
165 "Clock type mismatch timer policy");
166
167 cancel ();
168
169 auto elapsed = timePoint.time_since_epoch ();
170 auto ns = std::chrono::duration_cast<std::chrono::nanoseconds> (elapsed);
171 _callback = std::forward<Func> (callback);
172 _oneShot = true;
173 _ns = std::chrono::nanoseconds::zero ();
174 _state.store (State::Armed, std::memory_order_release);
175
176 auto ts = toTimerSpec (ns);
177 timerfd_settime (_handle, TFD_TIMER_ABSTIME, &ts, nullptr);
178 }
179
185 template <class Rep, class Period, typename Func>
186 void setInterval (std::chrono::duration<Rep, Period> duration, Func&& callback)
187 {
188 cancel ();
189
190 auto ns = std::chrono::duration_cast<std::chrono::nanoseconds> (duration);
191 _callback = std::forward<Func> (callback);
192 _oneShot = false;
193 _ns = ns;
194 _state.store (State::Armed, std::memory_order_release);
195
196 auto ts = toTimerSpec (ns, true);
197 timerfd_settime (_handle, 0, &ts, nullptr);
198 }
199
203 void cancel () noexcept
204 {
205 _oneShot = true;
206 _ns = std::chrono::nanoseconds::zero ();
207
208 struct itimerspec ts = {};
209 timerfd_settime (_handle, 0, &ts, nullptr);
210
211 if (JOIN_UNLIKELY (_proactor.isProactorThread ()))
212 {
213 _state.store (State::Idle, std::memory_order_release);
214 return;
215 }
216
217 Backoff backoff;
218 State expected = State::Armed;
219
220 while (!_state.compare_exchange_strong (expected, State::Idle, std::memory_order_acq_rel,
221 std::memory_order_acquire))
222 {
223 if (expected != State::Invoking)
224 {
225 return;
226 }
227
228 backoff ();
229 expected = State::Armed;
230 }
231
232 _callback = nullptr;
233 }
234
239 bool active () const noexcept
240 {
241 struct itimerspec ts = {};
242 timerfd_gettime (_handle, &ts);
243 const bool hasValue = (ts.it_value.tv_sec != 0 || ts.it_value.tv_nsec != 0);
244 const bool hasInterval = (ts.it_interval.tv_sec != 0 || ts.it_interval.tv_nsec != 0);
245 return hasValue || hasInterval;
246 }
247
252 std::chrono::nanoseconds remaining () const noexcept
253 {
254 struct itimerspec ts = {};
255 timerfd_gettime (_handle, &ts);
256 return std::chrono::seconds (ts.it_value.tv_sec) + std::chrono::nanoseconds (ts.it_value.tv_nsec);
257 }
258
263 std::chrono::nanoseconds interval () const noexcept
264 {
265 return _ns;
266 }
267
272 bool oneShot () const noexcept
273 {
274 return _oneShot;
275 }
276
281 static constexpr int type () noexcept
282 {
283 return ClockPolicy::type ();
284 }
285
286 private:
292 void onComplete ([[maybe_unused]] IoOperation& op, int result) override
293 {
294 uint64_t expirations = _ops->expirations;
295
296 if (JOIN_UNLIKELY (result != static_cast<int> (sizeof (_ops->expirations)) && result != -EAGAIN &&
297 result != -EINTR))
298 {
299 // LCOV_EXCL_START
300 _state.store (State::Closed, std::memory_order_release);
301 return;
302 // LCOV_EXCL_STOP
303 }
304
305 if (JOIN_UNLIKELY (_proactor.submit (_ops->op) == -1))
306 {
307 // LCOV_EXCL_START
308 _state.store (State::Closed, std::memory_order_release);
309 return;
310 // LCOV_EXCL_STOP
311 }
312
313 if (JOIN_UNLIKELY (result != static_cast<int> (sizeof (_ops->expirations))))
314 {
315 return; // LCOV_EXCL_LINE
316 }
317
318 State expected = State::Armed;
319
320 if (JOIN_LIKELY (_state.compare_exchange_strong (expected, State::Invoking, std::memory_order_acq_rel,
321 std::memory_order_acquire)))
322 {
323 for (uint64_t i = 0; i < expirations; ++i)
324 {
325 _callback ();
326 }
327
328 expected = State::Invoking;
329 _state.compare_exchange_strong (expected, State::Armed, std::memory_order_acq_rel,
330 std::memory_order_acquire);
331 }
332 }
333
339 void onCancel ([[maybe_unused]] IoOperation& op, [[maybe_unused]] int result) override
340 {
341 _state.store (State::Closed, std::memory_order_release);
342 }
343
350 static constexpr itimerspec toTimerSpec (std::chrono::nanoseconds ns, bool periodic = false) noexcept
351 {
352 itimerspec ts{};
353 if (periodic)
354 {
355 ts.it_interval.tv_sec = ns.count () / _nsPerSec;
356 ts.it_interval.tv_nsec = ns.count () % _nsPerSec;
357 }
358 if (ns.count () < 1)
359 {
360 ns = std::chrono::nanoseconds (1);
361 }
362 ts.it_value.tv_sec = ns.count () / _nsPerSec;
363 ts.it_value.tv_nsec = ns.count () % _nsPerSec;
364 return ts;
365 }
366
370 struct Ops
371 {
377 static void* operator new (size_t size)
378 {
379 void* mem = ::aligned_alloc (alignof (Ops), size);
380
381 if (mem == nullptr)
382 {
383 throw std::bad_alloc (); // LCOV_EXCL_LINE
384 }
385
386 return mem;
387 }
388
393 static void operator delete (void* mem) noexcept
394 {
395 ::free (mem);
396 }
397
399 IoOperation op = {};
400
402 uint64_t expirations = 0;
403 };
404
406 static constexpr uint64_t _nsPerSec = 1000000000ULL;
407
409 const std::unique_ptr<Ops> _ops{new Ops ()};
410
412 std::atomic<State> _state{State::Idle};
413
415 Function<void ()> _callback;
416
418 std::chrono::nanoseconds _ns{};
419
421 bool _oneShot = true;
422
424 int _handle = -1;
425
427 Proactor& _proactor;
428 };
429}
430
431#endif
adaptive backoff strategy for busy-wait loops.
Definition backoff.hpp:43
static BasicProactor & proactor()
get the Proactor instance owned by the singleton ProactorThread.
Definition proactor.hpp:982
basic proactor class.
Definition proactor.hpp:156
int submit(IoOperation &op, bool flush=false, bool sync=false) noexcept
submit an asynchronous operation to the proactor.
Definition proactor.hpp:655
bool isProactorThread() const noexcept
check if the calling thread is the proactor thread.
Definition proactor_epoll_impl.hpp:252
int cancel(IoOperation &op, bool flush=false, bool sync=false) noexcept
cancel an in-flight operation.
Definition proactor.hpp:703
base timer class.
Definition timer.hpp:52
std::chrono::nanoseconds interval() const noexcept
get the interval of the running periodic timer.
Definition timer.hpp:263
State
timer state.
Definition timer.hpp:58
std::chrono::nanoseconds remaining() const noexcept
get the remaining time until expiration.
Definition timer.hpp:252
BasicTimer(Proactor &proactor=ProactorThread::proactor())
create instance.
Definition timer.hpp:69
void setOneShot(std::chrono::duration< Rep, Period > duration, Func &&callback)
arm the timer as a one-shot timer.
Definition timer.hpp:139
void cancel() noexcept
cancel the timer.
Definition timer.hpp:203
~BasicTimer() noexcept
destroy instance.
Definition timer.hpp:119
BasicTimer(const BasicTimer &other)=delete
copy constructor.
void setInterval(std::chrono::duration< Rep, Period > duration, Func &&callback)
arm the timer as a periodic timer.
Definition timer.hpp:186
bool active() const noexcept
check if timer is running.
Definition timer.hpp:239
static constexpr int type() noexcept
get the timer type.
Definition timer.hpp:281
void setOneShot(std::chrono::time_point< Clock, Duration > timePoint, Func &&callback)
arm the timer as a one-shot timer with absolute time.
Definition timer.hpp:159
BasicTimer(BasicTimer &&other)=delete
move constructor.
bool oneShot() const noexcept
check if timer is a one-shot timer.
Definition timer.hpp:272
BasicTimer & operator=(const BasicTimer &other)=delete
copy assignment operator.
completion handler interface class.
Definition proactor.hpp:79
Definition acceptor.hpp:32
BasicProactor Proactor
Definition proactor.hpp:70
Describes a single asynchronous operation submitted to the Proactor.
Definition io_operation.hpp:47
static IoOperation makeRead(int fd, void *buf, uint32_t len, CompletionHandler *handler, bool linked=false) noexcept
build a regular read operation.
Definition io_operation.cpp:152
#define JOIN_LIKELY(x)
Definition utils.hpp:45
#define JOIN_UNLIKELY(x)
Definition utils.hpp:46