Incorporate changes from Dasynq upstream.
[oweals/dinit.git] / src / dasynq / dasynq-timerfd.h
1 #include <vector>
2 #include <utility>
3
4 #include <sys/timerfd.h>
5 #include <time.h>
6
7 #include "dasynq-timerbase.h"
8
9 namespace dasynq {
10
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.
14
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).
19
20 template <class Base> class TimerFdEvents : public timer_base<Base>
21 {
22     private:
23     int timerfd_fd = -1;
24     int systemtime_fd = -1;
25
26     timer_queue_t timer_queue;
27     timer_queue_t wallclock_queue;
28     
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
32     {
33         struct itimerspec newtime;
34         if (queue.empty()) {
35             newtime.it_value = {0, 0};
36             newtime.it_interval = {0, 0};
37         }
38         else {
39             newtime.it_value = queue.get_root_priority();
40             newtime.it_interval = {0, 0};
41         }
42         timerfd_settime(fd, TFD_TIMER_ABSTIME, &newtime, nullptr);
43     }
44     
45     void process_timer(clock_type clock, int fd, timer_queue_t &queue) noexcept
46     {
47         struct timespec curtime;
48         switch (clock) {
49         case clock_type::SYSTEM:
50             clock_gettime(CLOCK_REALTIME, &curtime);
51             break;
52         case clock_type::MONOTONIC:
53             clock_gettime(CLOCK_MONOTONIC, &curtime);
54             break;
55         default:
56             DASYNQ_UNREACHABLE;
57         }
58
59         timer_base<Base>::process_timer_queue(queue, curtime);
60
61         // arm timerfd with timeout from head of queue
62         set_timer_from_queue(fd, queue);
63     }
64
65     void setTimer(timer_handle_t & timer_id, const time_val &timeouttv, const time_val &intervaltv,
66             timer_queue_t &queue, int fd, bool enable) noexcept
67     {
68         timespec timeout = timeouttv;
69         timespec interval = intervaltv;
70
71         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
72
73         auto &ts = queue.node_data(timer_id);
74         ts.interval_time = interval;
75         ts.expiry_count = 0;
76         ts.enabled = enable;
77
78         if (queue.is_queued(timer_id)) {
79             // Already queued; alter timeout
80             if (queue.set_priority(timer_id, timeout)) {
81                 set_timer_from_queue(fd, queue);
82             }
83         }
84         else {
85             if (queue.insert(timer_id, timeout)) {
86                 set_timer_from_queue(fd, queue);
87             }
88         }
89     }
90
91     timer_queue_t & get_queue(clock_type clock)
92     {
93         switch(clock) {
94         case clock_type::SYSTEM:
95             return wallclock_queue;
96         case clock_type::MONOTONIC:
97             return timer_queue;
98         default:
99             DASYNQ_UNREACHABLE;
100         }
101     }
102
103     public:
104     template <typename T>
105     void receiveFdEvent(T &loop_mech, typename Base::FD_r fd_r, void * userdata, int flags)
106     {
107         if (userdata == &timerfd_fd) {
108             process_timer(clock_type::MONOTONIC, timerfd_fd, timer_queue);
109         }
110         else if (userdata == &systemtime_fd) {
111             process_timer(clock_type::SYSTEM, systemtime_fd, wallclock_queue);
112         }
113         else {
114             Base::receiveFdEvent(loop_mech, fd_r, userdata, flags);
115         }
116     }
117
118     template <typename T> void init(T *loop_mech)
119     {
120         timerfd_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
121         if (timerfd_fd == -1) {
122             throw std::system_error(errno, std::system_category());
123         }
124         systemtime_fd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC | TFD_NONBLOCK);
125         if (systemtime_fd == -1) {
126             close (timerfd_fd);
127             throw std::system_error(errno, std::system_category());
128         }
129
130         try {
131             loop_mech->addFdWatch(timerfd_fd, &timerfd_fd, IN_EVENTS);
132             loop_mech->addFdWatch(systemtime_fd, &systemtime_fd, IN_EVENTS);
133             Base::init(loop_mech);
134         }
135         catch (...) {
136             close(timerfd_fd);
137             close(systemtime_fd);
138             throw;
139         }
140     }
141
142     // Add timer, store into given handle
143     void addTimer(timer_handle_t &h, void *userdata, clock_type clock = clock_type::MONOTONIC)
144     {
145         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
146         timer_queue_t & queue = get_queue(clock);
147         queue.allocate(h, userdata);
148     }
149     
150     void removeTimer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
151     {
152         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
153         removeTimer_nolock(timer_id, clock);
154     }
155     
156     void removeTimer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
157     {
158         timer_queue_t & queue = get_queue(clock);
159         if (queue.is_queued(timer_id)) {
160             queue.remove(timer_id);
161         }
162         queue.deallocate(timer_id);
163     }
164
165     void stop_timer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
166     {
167         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
168         stop_timer_nolock(timer_id, clock);
169     }
170
171     void stop_timer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
172     {
173         timer_queue_t & queue = get_queue(clock);
174         if (queue.is_queued(timer_id)) {
175             queue.remove(timer_id);
176         }
177     }
178
179     // starts (if not started) a timer to timeout at the given time. Resets the expiry count to 0.
180     //   enable: specifies whether to enable reporting of timeouts/intervals
181     void setTimer(timer_handle_t & timer_id, const time_val &timeouttv, const time_val &intervaltv,
182             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
183     {
184         timespec timeout = timeouttv;
185         timespec interval = intervaltv;
186
187         switch (clock) {
188         case clock_type::SYSTEM:
189             setTimer(timer_id, timeout, interval, wallclock_queue, systemtime_fd, enable);
190             break;
191         case clock_type::MONOTONIC:
192             setTimer(timer_id, timeout, interval, timer_queue, timerfd_fd, enable);
193             break;
194         default:
195             DASYNQ_UNREACHABLE;
196         }
197     }
198
199     // Set timer relative to current time:    
200     void setTimerRel(timer_handle_t & timer_id, const time_val &timeouttv, const time_val &intervaltv,
201             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
202     {
203         timespec timeout = timeouttv;
204         timespec interval = intervaltv;
205
206         clockid_t sclock;
207         switch (clock) {
208         case clock_type::SYSTEM:
209             sclock = CLOCK_REALTIME;
210             break;
211         case clock_type::MONOTONIC:
212             sclock = CLOCK_MONOTONIC;
213             break;
214         default:
215             DASYNQ_UNREACHABLE;
216         }
217
218         // TODO consider caching current time somehow; need to decide then when to update cached value.
219         struct timespec curtime;
220         clock_gettime(sclock, &curtime);
221         curtime.tv_sec += timeout.tv_sec;
222         curtime.tv_nsec += timeout.tv_nsec;
223         if (curtime.tv_nsec > 1000000000) {
224             curtime.tv_nsec -= 1000000000;
225             curtime.tv_sec++;
226         }
227
228         setTimer(timer_id, curtime, interval, enable, clock);
229     }
230     
231     // Enables or disabling report of timeouts (does not stop timer)
232     void enableTimer(timer_handle_t & timer_id, bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
233     {
234         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
235         enableTimer_nolock(timer_id, enable, clock);
236     }
237     
238     void enableTimer_nolock(timer_handle_t & timer_id, bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
239     {
240         timer_queue_t & queue = get_queue(clock);
241
242         auto &node_data = queue.node_data(timer_id);
243         auto expiry_count = node_data.expiry_count;
244         if (expiry_count != 0) {
245             node_data.expiry_count = 0;
246             Base::receiveTimerExpiry(timer_id, node_data.userdata, expiry_count);
247         }
248         else {
249             queue.node_data(timer_id).enabled = enable;
250         }
251     }
252
253     void get_time(time_val &tv, clock_type clock, bool force_update) noexcept
254     {
255         timespec ts;
256         get_time(ts, clock, force_update);
257         tv = ts;
258     }
259
260     void get_time(timespec &ts, clock_type clock, bool force_update) noexcept
261     {
262         int posix_clock_id = (clock == clock_type::MONOTONIC) ? CLOCK_MONOTONIC : CLOCK_REALTIME;
263         clock_gettime(posix_clock_id, &ts);
264     }
265
266     ~TimerFdEvents()
267     {
268         close(timerfd_fd);
269         close(systemtime_fd);
270     }
271 };
272
273 }