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 <functional>
36#include <chrono>
37#include <atomic>
38#include <memory>
39#include <new>
40
41// C.
42#include <sys/timerfd.h>
43#include <unistd.h>
44#include <cstdlib>
45
46namespace join
47{
51 template <class ClockPolicy, class ProactorType>
52 class BasicTimer : protected CompletionHandler
53 {
54 public:
58 enum class State
59 {
60 Idle,
61 Armed,
62 Invoking,
63 Closed,
64 };
65
70 explicit BasicTimer (ProactorType& proactor = ProactorThread::proactor ())
71 : _handle (timerfd_create (ClockPolicy::type (), TFD_NONBLOCK | TFD_CLOEXEC))
72 , _proactor (proactor)
73 {
74 if (_handle == -1)
75 {
76 throw std::system_error (errno, std::system_category (), "timerfd_create failed"); // LCOV_EXCL_LINE
77 }
78
79 _ops->op = IoOperation::makeRead (_handle, &_ops->expirations,
80 static_cast<uint32_t> (sizeof (_ops->expirations)), this);
81
82 if (_proactor.submit (_ops->op, true, true) == -1)
83 {
84 // LCOV_EXCL_START
85 close (_handle);
86 throw std::system_error (lastError, "timerfd submit failed");
87 // LCOV_EXCL_STOP
88 }
89 }
90
95 BasicTimer (const BasicTimer& other) = delete;
96
102 BasicTimer& operator= (const BasicTimer& other) = delete;
103
108 BasicTimer (BasicTimer&& other) = delete;
109
115 BasicTimer& operator= (BasicTimer&& other) = delete;
116
120 ~BasicTimer () noexcept
121 {
122 cancel ();
123
124 Backoff backoff;
125 while (_state.load (std::memory_order_acquire) != State::Closed)
126 {
127 _proactor.cancel (_ops->op, true, true);
128 backoff ();
129 }
130
131 close (_handle);
132 }
133
140 template <class Rep, class Period, typename Func, typename... Args>
141 void setOneShot (std::chrono::duration<Rep, Period> duration, Func&& callback, Args&&... args)
142 {
143 cancel ();
144
145 auto ns = std::chrono::duration_cast<std::chrono::nanoseconds> (duration);
146 _callback = std::bind (std::forward<Func> (callback), std::forward<Args> (args)...);
147 _oneShot.store (true, std::memory_order_relaxed);
148 _ns.store (std::chrono::nanoseconds::zero (), std::memory_order_relaxed);
149 _state.store (State::Armed, std::memory_order_release);
150
151 auto ts = toTimerSpec (ns);
152 timerfd_settime (_handle, 0, &ts, nullptr);
153 }
154
161 template <class Clock, class Duration, typename Func, typename... Args>
162 void setOneShot (std::chrono::time_point<Clock, Duration> timePoint, Func&& callback, Args&&... args)
163 {
164 static_assert (
165 (std::is_same_v<ClockPolicy, RealTime> && std::is_same_v<Clock, std::chrono::system_clock>) ||
166 (std::is_same_v<ClockPolicy, Monotonic> && std::is_same_v<Clock, std::chrono::steady_clock>),
167 "Clock type mismatch timer policy");
168
169 cancel ();
170
171 auto elapsed = timePoint.time_since_epoch ();
172 auto ns = std::chrono::duration_cast<std::chrono::nanoseconds> (elapsed);
173 _callback = std::bind (std::forward<Func> (callback), std::forward<Args> (args)...);
174 _oneShot.store (true, std::memory_order_relaxed);
175 _ns.store (std::chrono::nanoseconds::zero (), std::memory_order_relaxed);
176 _state.store (State::Armed, std::memory_order_release);
177
178 auto ts = toTimerSpec (ns);
179 timerfd_settime (_handle, TFD_TIMER_ABSTIME, &ts, nullptr);
180 }
181
188 template <class Rep, class Period, typename Func, typename... Args>
189 void setInterval (std::chrono::duration<Rep, Period> duration, Func&& callback, Args&&... args)
190 {
191 cancel ();
192
193 auto ns = std::chrono::duration_cast<std::chrono::nanoseconds> (duration);
194 _callback = std::bind (std::forward<Func> (callback), std::forward<Args> (args)...);
195 _oneShot.store (false, std::memory_order_relaxed);
196 _ns.store (ns, std::memory_order_relaxed);
197 _state.store (State::Armed, std::memory_order_release);
198
199 auto ts = toTimerSpec (ns, true);
200 timerfd_settime (_handle, 0, &ts, nullptr);
201 }
202
206 void cancel () noexcept
207 {
208 _oneShot.store (true, std::memory_order_relaxed);
209 _ns.store (std::chrono::nanoseconds::zero (), std::memory_order_relaxed);
210
211 struct itimerspec ts = {};
212 timerfd_settime (_handle, 0, &ts, nullptr);
213
214 if (JOIN_UNLIKELY (_proactor.isProactorThread ()))
215 {
216 _state.store (State::Idle, std::memory_order_release);
217 return;
218 }
219
220 Backoff backoff;
221 State expected = State::Armed;
222
223 while (!_state.compare_exchange_strong (expected, State::Idle, std::memory_order_acq_rel,
224 std::memory_order_acquire))
225 {
226 if (expected != State::Invoking)
227 {
228 return;
229 }
230
231 backoff ();
232 expected = State::Armed;
233 }
234
235 _callback = nullptr;
236 }
237
242 bool active () const noexcept
243 {
244 struct itimerspec ts = {};
245 timerfd_gettime (_handle, &ts);
246 const bool hasValue = (ts.it_value.tv_sec != 0 || ts.it_value.tv_nsec != 0);
247 const bool hasInterval = (ts.it_interval.tv_sec != 0 || ts.it_interval.tv_nsec != 0);
248 return hasValue || hasInterval;
249 }
250
255 std::chrono::nanoseconds remaining () const noexcept
256 {
257 struct itimerspec ts = {};
258 timerfd_gettime (_handle, &ts);
259 return std::chrono::seconds (ts.it_value.tv_sec) + std::chrono::nanoseconds (ts.it_value.tv_nsec);
260 }
261
266 std::chrono::nanoseconds interval () const noexcept
267 {
268 return _ns.load (std::memory_order_relaxed);
269 }
270
275 bool oneShot () const noexcept
276 {
277 return _oneShot.load (std::memory_order_relaxed);
278 }
279
284 static constexpr int type () noexcept
285 {
286 return ClockPolicy::type ();
287 }
288
289 private:
295 void onComplete ([[maybe_unused]] IoOperation& op, int result) override
296 {
297 uint64_t expirations = _ops->expirations;
298
299 if (JOIN_UNLIKELY (result != static_cast<int> (sizeof (_ops->expirations)) && result != -EAGAIN &&
300 result != -EINTR))
301 {
302 // LCOV_EXCL_START
303 _state.store (State::Closed, std::memory_order_release);
304 return;
305 // LCOV_EXCL_STOP
306 }
307
308 if (JOIN_UNLIKELY (_proactor.submit (_ops->op) == -1))
309 {
310 // LCOV_EXCL_START
311 _state.store (State::Closed, std::memory_order_release);
312 return;
313 // LCOV_EXCL_STOP
314 }
315
316 if (JOIN_UNLIKELY (result != static_cast<int> (sizeof (_ops->expirations))))
317 {
318 return; // LCOV_EXCL_LINE
319 }
320
321 State expected = State::Armed;
322
323 if (JOIN_LIKELY (_state.compare_exchange_strong (expected, State::Invoking, std::memory_order_acq_rel,
324 std::memory_order_acquire)))
325 {
326 for (uint64_t i = 0; i < expirations; ++i)
327 {
328 _callback ();
329 }
330
331 expected = State::Invoking;
332 _state.compare_exchange_strong (expected, State::Armed, std::memory_order_acq_rel,
333 std::memory_order_acquire);
334 }
335 }
336
342 void onCancel ([[maybe_unused]] IoOperation& op, [[maybe_unused]] int result) override
343 {
344 _state.store (State::Closed, std::memory_order_release);
345 }
346
353 static constexpr itimerspec toTimerSpec (std::chrono::nanoseconds ns, bool periodic = false) noexcept
354 {
355 itimerspec ts{};
356 if (periodic)
357 {
358 ts.it_interval.tv_sec = ns.count () / _nsPerSec;
359 ts.it_interval.tv_nsec = ns.count () % _nsPerSec;
360 }
361 if (ns.count () < 1)
362 {
363 ns = std::chrono::nanoseconds (1);
364 }
365 ts.it_value.tv_sec = ns.count () / _nsPerSec;
366 ts.it_value.tv_nsec = ns.count () % _nsPerSec;
367 return ts;
368 }
369
373 struct Ops
374 {
380 static void* operator new (size_t size)
381 {
382 void* mem = ::aligned_alloc (alignof (Ops), size);
383
384 if (mem == nullptr)
385 {
386 throw std::bad_alloc (); // LCOV_EXCL_LINE
387 }
388
389 return mem;
390 }
391
396 static void operator delete (void* mem) noexcept
397 {
398 ::free (mem);
399 }
400
402 IoOperation op = {};
403
405 uint64_t expirations = 0;
406 };
407
409 static constexpr uint64_t _nsPerSec = 1000000000ULL;
410
412 const std::unique_ptr<Ops> _ops{new Ops ()};
413
415 std::atomic<State> _state{State::Idle};
416
418 Function<void ()> _callback;
419
421 std::atomic<std::chrono::nanoseconds> _ns{std::chrono::nanoseconds::zero ()};
422
424 std::atomic_bool _oneShot{true};
425
427 int _handle = -1;
428
430 ProactorType& _proactor;
431 };
432}
433
434#endif
adaptive backoff for busy-wait loops, requires Rdtsc::calibrate ().
Definition backoff.hpp:47
static BasicProactor & proactor()
get the Proactor instance owned by the singleton ProactorThread.
Definition proactor.hpp:1053
base timer class.
Definition timer.hpp:53
std::chrono::nanoseconds remaining() const noexcept
get the remaining time until expiration.
Definition timer.hpp:255
void setInterval(std::chrono::duration< Rep, Period > duration, Func &&callback, Args &&... args)
arm the timer as a periodic timer.
Definition timer.hpp:189
void setOneShot(std::chrono::duration< Rep, Period > duration, Func &&callback, Args &&... args)
arm the timer as a one-shot timer.
Definition timer.hpp:141
State
timer state.
Definition timer.hpp:59
void cancel() noexcept
cancel the timer.
Definition timer.hpp:206
bool oneShot() const noexcept
check if timer is a one-shot timer.
Definition timer.hpp:275
BasicTimer(BasicTimer &&other)=delete
move constructor.
bool active() const noexcept
check if timer is running.
Definition timer.hpp:242
BasicTimer(const BasicTimer &other)=delete
copy constructor.
static constexpr int type() noexcept
get the timer type.
Definition timer.hpp:284
BasicTimer(ProactorType &proactor=ProactorThread::proactor())
create instance.
Definition timer.hpp:70
std::chrono::nanoseconds interval() const noexcept
get the interval of the running periodic timer.
Definition timer.hpp:266
void setOneShot(std::chrono::time_point< Clock, Duration > timePoint, Func &&callback, Args &&... args)
arm the timer as a one-shot timer with absolute time.
Definition timer.hpp:162
~BasicTimer() noexcept
destroy instance.
Definition timer.hpp:120
BasicTimer & operator=(const BasicTimer &other)=delete
copy assignment operator.
completion handler interface class.
Definition proactor.hpp:81
Definition acceptor.hpp:32
Describes a single asynchronous operation submitted to the Proactor.
Definition io_operation.hpp:48
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:160
#define JOIN_LIKELY(x)
Definition utils.hpp:50
#define JOIN_UNLIKELY(x)
Definition utils.hpp:51