Upgrade bundled Dasynq to 1.1.2.
[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 // Timer implementation based on Linux's "timerfd".
12
13 // We could use one timerfd per timer, but then we need to differentiate timer
14 // descriptors from regular file descriptors when events are reported by the loop
15 // mechanism so that we can correctly report a timer event or fd event.
16
17 // With a file descriptor or signal, we can use the item itself as the identifier for
18 // adding/removing watches. For timers, it's more complicated. When we add a timer,
19 // we are given a handle; we need to use this to modify the watch. We delegate the
20 // process of allocating a handle to a priority heap implementation (BinaryHeap).
21
22 template <class Base> class timer_fd_events : public timer_base<Base>
23 {
24     private:
25     int timerfd_fd = -1;
26     int systemtime_fd = -1;
27     
28     // Set the timerfd timeout to match the first timer in the queue (disable the timerfd
29     // if there are no active timers).
30     static void set_timer_from_queue(int fd, timer_queue_t &queue) noexcept
31     {
32         struct itimerspec newtime;
33         if (queue.empty()) {
34             newtime.it_value = {0, 0};
35             newtime.it_interval = {0, 0};
36         }
37         else {
38             newtime.it_value = queue.get_root_priority();
39             newtime.it_interval = {0, 0};
40         }
41         timerfd_settime(fd, TFD_TIMER_ABSTIME, &newtime, nullptr);
42     }
43     
44     void process_timer(clock_type clock, int fd) noexcept
45     {
46         timer_queue_t &queue = this->queue_for_clock(clock);
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 set_timer(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     public:
92
93     class traits_t : public Base::traits_t
94     {
95         constexpr static bool full_timer_support = true;
96     };
97
98     template <typename T>
99     std::tuple<int, typename traits_t::fd_s>
100     receive_fd_event(T &loop_mech, typename traits_t::fd_r fd_r_a, void * userdata, int flags)
101     {
102         if (userdata == &timerfd_fd) {
103             process_timer(clock_type::MONOTONIC, timerfd_fd);
104             return std::make_tuple(IN_EVENTS, typename traits_t::fd_s(timerfd_fd));
105         }
106         else if (userdata == &systemtime_fd) {
107             process_timer(clock_type::SYSTEM, systemtime_fd);
108             if (Base::traits_t::supports_non_oneshot_fd) {
109                 return std::make_tuple(0, typename traits_t::fd_s(systemtime_fd));
110             }
111             return std::make_tuple(IN_EVENTS, typename traits_t::fd_s(systemtime_fd));
112         }
113         else {
114             return Base::receive_fd_event(loop_mech, fd_r_a, 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->add_fd_watch(timerfd_fd, &timerfd_fd, IN_EVENTS);
132             loop_mech->add_fd_watch(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     void stop_timer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
143     {
144         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
145         stop_timer_nolock(timer_id, clock);
146     }
147
148     void stop_timer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
149     {
150         timer_queue_t &queue = this->queue_for_clock(clock);
151         int fd = (clock == clock_type::MONOTONIC) ? timerfd_fd : systemtime_fd;
152         if (queue.is_queued(timer_id)) {
153             bool was_first = (&queue.get_root()) == &timer_id;
154             queue.remove(timer_id);
155             if (was_first) {
156                 set_timer_from_queue(fd, queue);
157             }
158         }
159     }
160
161     // starts (if not started) a timer to timeout at the given time. Resets the expiry count to 0.
162     //   enable: specifies whether to enable reporting of timeouts/intervals
163     void set_timer(timer_handle_t & timer_id, const time_val &timeouttv, const time_val &intervaltv,
164             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
165     {
166         timespec timeout = timeouttv;
167         timespec interval = intervaltv;
168         timer_queue_t &queue = this->queue_for_clock(clock);
169
170         switch (clock) {
171         case clock_type::SYSTEM:
172             set_timer(timer_id, timeout, interval, queue, systemtime_fd, enable);
173             break;
174         case clock_type::MONOTONIC:
175             set_timer(timer_id, timeout, interval, queue, timerfd_fd, enable);
176             break;
177         default:
178             DASYNQ_UNREACHABLE;
179         }
180     }
181
182     // Set timer relative to current time:    
183     void set_timer_rel(timer_handle_t & timer_id, const time_val &timeout, const time_val &interval,
184             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
185     {
186         time_val alarmtime;
187         this->get_time(alarmtime, clock, false);
188         alarmtime += timeout;
189
190         set_timer(timer_id, alarmtime, interval, enable, clock);
191     }
192     
193     ~timer_fd_events()
194     {
195         close(timerfd_fd);
196         close(systemtime_fd);
197     }
198 };
199
200 }