dasynq-timerfd.h 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #include <vector>
  2. #include <utility>
  3. #include <sys/timerfd.h>
  4. #include <time.h>
  5. #include "dasynq-timerbase.h"
  6. namespace dasynq {
  7. // Timer implementation based on Linux's "timerfd".
  8. // We could use one timerfd per timer, but then we need to differentiate timer
  9. // descriptors from regular file descriptors when events are reported by the loop
  10. // mechanism so that we can correctly report a timer event or fd event.
  11. // With a file descriptor or signal, we can use the item itself as the identifier for
  12. // adding/removing watches. For timers, it's more complicated. When we add a timer,
  13. // we are given a handle; we need to use this to modify the watch. We delegate the
  14. // process of allocating a handle to a priority heap implementation (BinaryHeap).
  15. template <class Base> class timer_fd_events : public timer_base<Base>
  16. {
  17. private:
  18. int timerfd_fd = -1;
  19. int systemtime_fd = -1;
  20. // Set the timerfd timeout to match the first timer in the queue (disable the timerfd
  21. // if there are no active timers).
  22. static void set_timer_from_queue(int fd, timer_queue_t &queue) noexcept
  23. {
  24. struct itimerspec newtime;
  25. if (queue.empty()) {
  26. newtime.it_value = {0, 0};
  27. newtime.it_interval = {0, 0};
  28. }
  29. else {
  30. newtime.it_value = queue.get_root_priority();
  31. newtime.it_interval = {0, 0};
  32. }
  33. timerfd_settime(fd, TFD_TIMER_ABSTIME, &newtime, nullptr);
  34. }
  35. void process_timer(clock_type clock, int fd) noexcept
  36. {
  37. timer_queue_t &queue = this->queue_for_clock(clock);
  38. struct timespec curtime;
  39. switch (clock) {
  40. case clock_type::SYSTEM:
  41. clock_gettime(CLOCK_REALTIME, &curtime);
  42. break;
  43. case clock_type::MONOTONIC:
  44. clock_gettime(CLOCK_MONOTONIC, &curtime);
  45. break;
  46. default:
  47. DASYNQ_UNREACHABLE;
  48. }
  49. timer_base<Base>::process_timer_queue(queue, curtime);
  50. // arm timerfd with timeout from head of queue
  51. set_timer_from_queue(fd, queue);
  52. }
  53. void set_timer(timer_handle_t & timer_id, const time_val &timeouttv, const time_val &intervaltv,
  54. timer_queue_t &queue, int fd, bool enable) noexcept
  55. {
  56. timespec timeout = timeouttv;
  57. timespec interval = intervaltv;
  58. std::lock_guard<decltype(Base::lock)> guard(Base::lock);
  59. auto &ts = queue.node_data(timer_id);
  60. ts.interval_time = interval;
  61. ts.expiry_count = 0;
  62. ts.enabled = enable;
  63. if (queue.is_queued(timer_id)) {
  64. // Already queued; alter timeout
  65. if (queue.set_priority(timer_id, timeout)) {
  66. set_timer_from_queue(fd, queue);
  67. }
  68. }
  69. else {
  70. if (queue.insert(timer_id, timeout)) {
  71. set_timer_from_queue(fd, queue);
  72. }
  73. }
  74. }
  75. public:
  76. class traits_t : public Base::traits_t
  77. {
  78. constexpr static bool full_timer_support = true;
  79. };
  80. template <typename T>
  81. std::tuple<int, typename traits_t::fd_s>
  82. receive_fd_event(T &loop_mech, typename traits_t::fd_r fd_r_a, void * userdata, int flags)
  83. {
  84. if (userdata == &timerfd_fd) {
  85. process_timer(clock_type::MONOTONIC, timerfd_fd);
  86. return std::make_tuple(IN_EVENTS, typename traits_t::fd_s(timerfd_fd));
  87. }
  88. else if (userdata == &systemtime_fd) {
  89. process_timer(clock_type::SYSTEM, systemtime_fd);
  90. if (Base::traits_t::supports_non_oneshot_fd) {
  91. return std::make_tuple(0, typename traits_t::fd_s(systemtime_fd));
  92. }
  93. return std::make_tuple(IN_EVENTS, typename traits_t::fd_s(systemtime_fd));
  94. }
  95. else {
  96. return Base::receive_fd_event(loop_mech, fd_r_a, userdata, flags);
  97. }
  98. }
  99. template <typename T> void init(T *loop_mech)
  100. {
  101. timerfd_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
  102. if (timerfd_fd == -1) {
  103. throw std::system_error(errno, std::system_category());
  104. }
  105. systemtime_fd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC | TFD_NONBLOCK);
  106. if (systemtime_fd == -1) {
  107. close (timerfd_fd);
  108. throw std::system_error(errno, std::system_category());
  109. }
  110. try {
  111. loop_mech->add_fd_watch(timerfd_fd, &timerfd_fd, IN_EVENTS);
  112. loop_mech->add_fd_watch(systemtime_fd, &systemtime_fd, IN_EVENTS);
  113. Base::init(loop_mech);
  114. }
  115. catch (...) {
  116. close(timerfd_fd);
  117. close(systemtime_fd);
  118. throw;
  119. }
  120. }
  121. void stop_timer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
  122. {
  123. std::lock_guard<decltype(Base::lock)> guard(Base::lock);
  124. stop_timer_nolock(timer_id, clock);
  125. }
  126. void stop_timer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
  127. {
  128. timer_queue_t &queue = this->queue_for_clock(clock);
  129. int fd = (clock == clock_type::MONOTONIC) ? timerfd_fd : systemtime_fd;
  130. if (queue.is_queued(timer_id)) {
  131. bool was_first = (&queue.get_root()) == &timer_id;
  132. queue.remove(timer_id);
  133. if (was_first) {
  134. set_timer_from_queue(fd, queue);
  135. }
  136. }
  137. }
  138. // starts (if not started) a timer to timeout at the given time. Resets the expiry count to 0.
  139. // enable: specifies whether to enable reporting of timeouts/intervals
  140. void set_timer(timer_handle_t & timer_id, const time_val &timeouttv, const time_val &intervaltv,
  141. bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
  142. {
  143. timespec timeout = timeouttv;
  144. timespec interval = intervaltv;
  145. timer_queue_t &queue = this->queue_for_clock(clock);
  146. switch (clock) {
  147. case clock_type::SYSTEM:
  148. set_timer(timer_id, timeout, interval, queue, systemtime_fd, enable);
  149. break;
  150. case clock_type::MONOTONIC:
  151. set_timer(timer_id, timeout, interval, queue, timerfd_fd, enable);
  152. break;
  153. default:
  154. DASYNQ_UNREACHABLE;
  155. }
  156. }
  157. // Set timer relative to current time:
  158. void set_timer_rel(timer_handle_t & timer_id, const time_val &timeout, const time_val &interval,
  159. bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
  160. {
  161. time_val alarmtime;
  162. this->get_time(alarmtime, clock, false);
  163. alarmtime += timeout;
  164. set_timer(timer_id, alarmtime, interval, enable, clock);
  165. }
  166. ~timer_fd_events()
  167. {
  168. close(timerfd_fd);
  169. close(systemtime_fd);
  170. }
  171. };
  172. }