4 #include <sys/timerfd.h>
7 #include "dasynq-timerbase.h"
11 // We could use one timerfd per timer, but then we need to differentiate timer
12 // descriptors from regular file descriptors when events are reported by the loop
13 // mechanism so that we can correctly report a timer event or fd event.
15 // With a file descriptor or signal, we can use the item itself as the identifier for
16 // adding/removing watches. For timers, it's more complicated. When we add a timer,
17 // we are given a handle; we need to use this to modify the watch. We delegate the
18 // process of allocating a handle to a priority heap implementation (BinaryHeap).
20 template <class Base> class TimerFdEvents : public timer_base<Base>
24 int systemtime_fd = -1;
26 timer_queue_t timer_queue;
27 timer_queue_t wallclock_queue;
29 // Set the timerfd timeout to match the first timer in the queue (disable the timerfd
30 // if there are no active timers).
31 static void set_timer_from_queue(int fd, timer_queue_t &queue) noexcept
33 struct itimerspec newtime;
35 newtime.it_value = {0, 0};
36 newtime.it_interval = {0, 0};
39 newtime.it_value = queue.get_root_priority();
40 newtime.it_interval = {0, 0};
42 timerfd_settime(fd, TFD_TIMER_ABSTIME, &newtime, nullptr);
45 void process_timer(clock_type clock, int fd, timer_queue_t &queue) noexcept
47 struct timespec curtime;
49 case clock_type::SYSTEM:
50 clock_gettime(CLOCK_REALTIME, &curtime);
52 case clock_type::MONOTONIC:
53 clock_gettime(CLOCK_MONOTONIC, &curtime);
59 timer_base<Base>::process_timer_queue(queue, curtime);
61 // arm timerfd with timeout from head of queue
62 set_timer_from_queue(fd, queue);
65 void setTimer(timer_handle_t & timer_id, struct timespec &timeout, struct timespec &interval,
66 timer_queue_t &queue, int fd, bool enable) noexcept
68 auto &ts = queue.node_data(timer_id);
69 ts.interval_time = interval;
73 if (queue.is_queued(timer_id)) {
74 // Already queued; alter timeout
75 if (queue.set_priority(timer_id, timeout)) {
76 set_timer_from_queue(fd, queue);
80 if (queue.insert(timer_id, timeout)) {
81 set_timer_from_queue(fd, queue);
85 // TODO locking (here and everywhere)
88 timer_queue_t & get_queue(clock_type clock)
91 case clock_type::SYSTEM:
92 return wallclock_queue;
93 case clock_type::MONOTONIC:
101 template <typename T>
102 void receiveFdEvent(T &loop_mech, typename Base::FD_r fd_r, void * userdata, int flags)
104 if (userdata == &timerfd_fd) {
105 process_timer(clock_type::MONOTONIC, timerfd_fd, timer_queue);
107 else if (userdata == &systemtime_fd) {
108 process_timer(clock_type::SYSTEM, systemtime_fd, wallclock_queue);
111 Base::receiveFdEvent(loop_mech, fd_r, userdata, flags);
115 template <typename T> void init(T *loop_mech)
117 timerfd_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
118 if (timerfd_fd == -1) {
119 throw std::system_error(errno, std::system_category());
121 systemtime_fd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC | TFD_NONBLOCK);
122 if (systemtime_fd == -1) {
124 throw std::system_error(errno, std::system_category());
128 loop_mech->addFdWatch(timerfd_fd, &timerfd_fd, IN_EVENTS);
129 loop_mech->addFdWatch(systemtime_fd, &systemtime_fd, IN_EVENTS);
130 Base::init(loop_mech);
134 close(systemtime_fd);
139 // Add timer, store into given handle
140 void addTimer(timer_handle_t &h, void *userdata, clock_type clock = clock_type::MONOTONIC)
142 timer_queue_t & queue = get_queue(clock);
143 queue.allocate(h, userdata);
146 void removeTimer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
148 removeTimer_nolock(timer_id, clock);
151 void removeTimer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
153 timer_queue_t & queue = get_queue(clock);
154 if (queue.is_queued(timer_id)) {
155 queue.remove(timer_id);
157 queue.deallocate(timer_id);
160 void stop_timer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
162 stop_timer_nolock(timer_id, clock);
165 void stop_timer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
167 timer_queue_t & queue = get_queue(clock);
168 if (queue.is_queued(timer_id)) {
169 queue.remove(timer_id);
173 // starts (if not started) a timer to timeout at the given time. Resets the expiry count to 0.
174 // enable: specifies whether to enable reporting of timeouts/intervals
175 void setTimer(timer_handle_t & timer_id, struct timespec &timeout, struct timespec &interval,
176 bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
179 case clock_type::SYSTEM:
180 setTimer(timer_id, timeout, interval, wallclock_queue, systemtime_fd, enable);
182 case clock_type::MONOTONIC:
183 setTimer(timer_id, timeout, interval, timer_queue, timerfd_fd, enable);
190 // Set timer relative to current time:
191 void setTimerRel(timer_handle_t & timer_id, struct timespec &timeout, struct timespec &interval,
192 bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
196 case clock_type::SYSTEM:
197 sclock = CLOCK_REALTIME;
199 case clock_type::MONOTONIC:
200 sclock = CLOCK_MONOTONIC;
206 // TODO consider caching current time somehow; need to decide then when to update cached value.
207 struct timespec curtime;
208 clock_gettime(sclock, &curtime);
209 curtime.tv_sec += timeout.tv_sec;
210 curtime.tv_nsec += timeout.tv_nsec;
211 if (curtime.tv_nsec > 1000000000) {
212 curtime.tv_nsec -= 1000000000;
216 setTimer(timer_id, curtime, interval, enable, clock);
219 // Enables or disabling report of timeouts (does not stop timer)
220 void enableTimer(timer_handle_t & timer_id, bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
222 enableTimer_nolock(timer_id, enable, clock);
225 void enableTimer_nolock(timer_handle_t & timer_id, bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
227 timer_queue_t & queue = get_queue(clock);
229 auto &node_data = queue.node_data(timer_id);
230 auto expiry_count = node_data.expiry_count;
231 if (expiry_count != 0) {
232 node_data.expiry_count = 0;
233 Base::receiveTimerExpiry(timer_id, node_data.userdata, expiry_count);
236 queue.node_data(timer_id).enabled = enable;
243 close(systemtime_fd);