Incorporate Dasynq library changes.
[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         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
69
70         auto &ts = queue.node_data(timer_id);
71         ts.interval_time = interval;
72         ts.expiry_count = 0;
73         ts.enabled = enable;
74
75         if (queue.is_queued(timer_id)) {
76             // Already queued; alter timeout
77             if (queue.set_priority(timer_id, timeout)) {
78                 set_timer_from_queue(fd, queue);
79             }
80         }
81         else {
82             if (queue.insert(timer_id, timeout)) {
83                 set_timer_from_queue(fd, queue);
84             }
85         }
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         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
143         timer_queue_t & queue = get_queue(clock);
144         queue.allocate(h, userdata);
145     }
146     
147     void removeTimer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
148     {
149         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
150         removeTimer_nolock(timer_id, clock);
151     }
152     
153     void removeTimer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
154     {
155         timer_queue_t & queue = get_queue(clock);
156         if (queue.is_queued(timer_id)) {
157             queue.remove(timer_id);
158         }
159         queue.deallocate(timer_id);
160     }
161
162     void stop_timer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
163     {
164         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
165         stop_timer_nolock(timer_id, clock);
166     }
167
168     void stop_timer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
169     {
170         timer_queue_t & queue = get_queue(clock);
171         if (queue.is_queued(timer_id)) {
172             queue.remove(timer_id);
173         }
174     }
175
176     // starts (if not started) a timer to timeout at the given time. Resets the expiry count to 0.
177     //   enable: specifies whether to enable reporting of timeouts/intervals
178     void setTimer(timer_handle_t & timer_id, struct timespec &timeout, struct timespec &interval,
179             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
180     {
181         switch (clock) {
182         case clock_type::SYSTEM:
183             setTimer(timer_id, timeout, interval, wallclock_queue, systemtime_fd, enable);
184             break;
185         case clock_type::MONOTONIC:
186             setTimer(timer_id, timeout, interval, timer_queue, timerfd_fd, enable);
187             break;
188         default:
189             DASYNQ_UNREACHABLE;
190         }
191     }
192
193     // Set timer relative to current time:    
194     void setTimerRel(timer_handle_t & timer_id, struct timespec &timeout, struct timespec &interval,
195             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
196     {
197         clockid_t sclock;
198         switch (clock) {
199         case clock_type::SYSTEM:
200             sclock = CLOCK_REALTIME;
201             break;
202         case clock_type::MONOTONIC:
203             sclock = CLOCK_MONOTONIC;
204             break;
205         default:
206             DASYNQ_UNREACHABLE;
207         }
208
209         // TODO consider caching current time somehow; need to decide then when to update cached value.
210         struct timespec curtime;
211         clock_gettime(sclock, &curtime);
212         curtime.tv_sec += timeout.tv_sec;
213         curtime.tv_nsec += timeout.tv_nsec;
214         if (curtime.tv_nsec > 1000000000) {
215             curtime.tv_nsec -= 1000000000;
216             curtime.tv_sec++;
217         }
218
219         setTimer(timer_id, curtime, interval, enable, clock);
220     }
221     
222     // Enables or disabling report of timeouts (does not stop timer)
223     void enableTimer(timer_handle_t & timer_id, bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
224     {
225         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
226         enableTimer_nolock(timer_id, enable, clock);
227     }
228     
229     void enableTimer_nolock(timer_handle_t & timer_id, bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
230     {
231         timer_queue_t & queue = get_queue(clock);
232
233         auto &node_data = queue.node_data(timer_id);
234         auto expiry_count = node_data.expiry_count;
235         if (expiry_count != 0) {
236             node_data.expiry_count = 0;
237             Base::receiveTimerExpiry(timer_id, node_data.userdata, expiry_count);
238         }
239         else {
240             queue.node_data(timer_id).enabled = enable;
241         }
242     }
243
244     ~TimerFdEvents()
245     {
246         close(timerfd_fd);
247         close(systemtime_fd);
248     }
249 };
250
251 }