Update Dasynq library and API usage
[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, struct timespec &timeout, struct timespec &interval,
66             timer_queue_t &queue, int fd, bool enable) noexcept
67     {
68         auto &ts = queue.node_data(timer_id);
69         ts.interval_time = interval;
70         ts.expiry_count = 0;
71         ts.enabled = enable;
72
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);
77             }
78         }
79         else {
80             if (queue.insert(timer_id, timeout)) {
81                 set_timer_from_queue(fd, queue);
82             }
83         }
84
85         // TODO locking (here and everywhere)
86     }
87
88     timer_queue_t & get_queue(clock_type clock)
89     {
90         switch(clock) {
91         case clock_type::SYSTEM:
92             return wallclock_queue;
93         case clock_type::MONOTONIC:
94             return timer_queue;
95         default:
96             DASYNQ_UNREACHABLE;
97         }
98     }
99
100     public:
101     template <typename T>
102     void receiveFdEvent(T &loop_mech, typename Base::FD_r fd_r, void * userdata, int flags)
103     {
104         if (userdata == &timerfd_fd) {
105             process_timer(clock_type::MONOTONIC, timerfd_fd, timer_queue);
106         }
107         else if (userdata == &systemtime_fd) {
108             process_timer(clock_type::SYSTEM, systemtime_fd, wallclock_queue);
109         }
110         else {
111             Base::receiveFdEvent(loop_mech, fd_r, userdata, flags);
112         }
113     }
114
115     template <typename T> void init(T *loop_mech)
116     {
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());
120         }
121         systemtime_fd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC | TFD_NONBLOCK);
122         if (systemtime_fd == -1) {
123             close (timerfd_fd);
124             throw std::system_error(errno, std::system_category());
125         }
126
127         try {
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);
131         }
132         catch (...) {
133             close(timerfd_fd);
134             close(systemtime_fd);
135             throw;
136         }
137     }
138
139     // Add timer, store into given handle
140     void addTimer(timer_handle_t &h, void *userdata, clock_type clock = clock_type::MONOTONIC)
141     {
142         timer_queue_t & queue = get_queue(clock);
143         queue.allocate(h, userdata);
144     }
145     
146     void removeTimer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
147     {
148         removeTimer_nolock(timer_id, clock);
149     }
150     
151     void removeTimer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
152     {
153         timer_queue_t & queue = get_queue(clock);
154         if (queue.is_queued(timer_id)) {
155             queue.remove(timer_id);
156         }
157         queue.deallocate(timer_id);
158     }
159
160     void stop_timer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
161     {
162         stop_timer_nolock(timer_id, clock);
163     }
164
165     void stop_timer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
166     {
167         timer_queue_t & queue = get_queue(clock);
168         if (queue.is_queued(timer_id)) {
169             queue.remove(timer_id);
170         }
171     }
172
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
177     {
178         switch (clock) {
179         case clock_type::SYSTEM:
180             setTimer(timer_id, timeout, interval, wallclock_queue, systemtime_fd, enable);
181             break;
182         case clock_type::MONOTONIC:
183             setTimer(timer_id, timeout, interval, timer_queue, timerfd_fd, enable);
184             break;
185         default:
186             DASYNQ_UNREACHABLE;
187         }
188     }
189
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
193     {
194         clockid_t sclock;
195         switch (clock) {
196         case clock_type::SYSTEM:
197             sclock = CLOCK_REALTIME;
198             break;
199         case clock_type::MONOTONIC:
200             sclock = CLOCK_MONOTONIC;
201             break;
202         default:
203             DASYNQ_UNREACHABLE;
204         }
205
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;
213             curtime.tv_sec++;
214         }
215
216         setTimer(timer_id, curtime, interval, enable, clock);
217     }
218     
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
221     {
222         enableTimer_nolock(timer_id, enable, clock);
223     }
224     
225     void enableTimer_nolock(timer_handle_t & timer_id, bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
226     {
227         timer_queue_t & queue = get_queue(clock);
228
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);
234         }
235         else {
236             queue.node_data(timer_id).enabled = enable;
237         }
238     }
239
240     ~TimerFdEvents()
241     {
242         close(timerfd_fd);
243         close(systemtime_fd);
244     }
245 };
246
247 }