Incorporate Dasynq library changes.
[oweals/dinit.git] / src / dasynq / dasynq-itimer.h
1 #include <vector>
2 #include <utility>
3
4 #include <sys/time.h>
5 #include <time.h>
6
7 #include "dasynq-timerbase.h"
8
9 namespace dasynq {
10
11 // Timer implementation based on the (basically obselete) POSIX itimer interface.
12
13 template <class Base> class ITimerEvents : public timer_base<Base>
14 {
15     private:
16     int timerfd_fd = -1;
17
18     timer_queue_t timer_queue;
19     
20     // Set the timerfd timeout to match the first timer in the queue (disable the timerfd
21     // if there are no active timers).
22     void set_timer_from_queue()
23     {
24         struct timespec newtime;
25         struct itimerval newalarm;
26         if (timer_queue.empty()) {
27             newalarm.it_value = {0, 0};
28             newalarm.it_interval = {0, 0};
29             setitimer(ITIMER_REAL, &newalarm, nullptr);
30             return;
31         }
32
33         newtime = timer_queue.get_root_priority();
34         
35         struct timespec curtime;
36 #if defined(__APPLE__)
37         struct timeval curtime_tv;
38         gettimeofday(&curtime_tv, nullptr);
39         curtime.tv_sec = curtime_tv.tv_sec;
40         curtime.tv_nsec = curtime_tv.tv_usec * 1000;
41 #else
42         clock_gettime(CLOCK_MONOTONIC, &curtime);
43 #endif
44         newalarm.it_interval = {0, 0};
45         newalarm.it_value.tv_sec = newtime.tv_sec - curtime.tv_sec;
46         newalarm.it_value.tv_usec = (newtime.tv_nsec - curtime.tv_nsec) / 1000;
47
48         if (newalarm.it_value.tv_usec < 0) {
49             newalarm.it_value.tv_usec += 1000000;
50             newalarm.it_value.tv_sec--;
51         }
52         setitimer(ITIMER_REAL, &newalarm, nullptr);
53     }
54     
55     protected:
56     
57     using SigInfo = typename Base::SigInfo;
58
59     template <typename T>
60     bool receiveSignal(T & loop_mech, SigInfo &siginfo, void *userdata)
61     {
62         if (siginfo.get_signo() == SIGALRM) {
63             struct timespec curtime;
64 #if defined(__APPLE__)
65             struct timeval curtime_tv;
66             gettimeofday(&curtime_tv, nullptr);
67             curtime.tv_sec = curtime_tv.tv_sec;
68             curtime.tv_nsec = curtime_tv.tv_usec * 1000;
69 #else
70             clock_gettime(CLOCK_MONOTONIC, &curtime);
71             // should we use the REALTIME clock instead? I have no idea :/
72 #endif
73             
74             timer_base<Base>::process_timer_queue(timer_queue, curtime);
75
76             // arm timerfd with timeout from head of queue
77             set_timer_from_queue();
78             // loop_mech.rearmSignalWatch_nolock(SIGALRM);
79             return false; // don't disable signal watch
80         }
81         else {
82             return Base::receiveSignal(loop_mech, siginfo, userdata);
83         }
84     }
85         
86     public:
87
88     template <typename T> void init(T *loop_mech)
89     {
90         sigset_t sigmask;
91         sigprocmask(SIG_UNBLOCK, nullptr, &sigmask);
92         sigaddset(&sigmask, SIGALRM);
93         sigprocmask(SIG_SETMASK, &sigmask, nullptr);
94         loop_mech->addSignalWatch(SIGALRM, nullptr);
95         Base::init(loop_mech);
96     }
97
98     void addTimer(timer_handle_t &h, void *userdata, clock_type clock = clock_type::MONOTONIC)
99     {
100         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
101         timer_queue.allocate(h, userdata);
102     }
103     
104     void removeTimer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
105     {
106         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
107         removeTimer_nolock(timer_id, clock);
108     }
109     
110     void removeTimer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
111     {
112         if (timer_queue.is_queued(timer_id)) {
113             timer_queue.remove(timer_id);
114         }
115         timer_queue.deallocate(timer_id);
116     }
117     
118     // starts (if not started) a timer to timeout at the given time. Resets the expiry count to 0.
119     //   enable: specifies whether to enable reporting of timeouts/intervals
120     void setTimer(timer_handle_t &timer_id, struct timespec &timeout, struct timespec &interval,
121             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
122     {
123         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
124
125         auto &ts = timer_queue.node_data(timer_id);
126         ts.interval_time = interval;
127         ts.expiry_count = 0;
128         ts.enabled = enable;
129
130         if (timer_queue.is_queued(timer_id)) {
131             // Already queued; alter timeout
132             if (timer_queue.set_priority(timer_id, timeout)) {
133                 set_timer_from_queue();
134             }
135         }
136         else {
137             if (timer_queue.insert(timer_id, timeout)) {
138                 set_timer_from_queue();
139             }
140         }
141     }
142
143     // Set timer relative to current time:    
144     void setTimerRel(timer_handle_t &timer_id, struct timespec &timeout, struct timespec &interval,
145             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
146     {
147         // TODO consider caching current time somehow; need to decide then when to update cached value.
148         struct timespec curtime;
149 #if defined(__APPLE__)
150         struct timeval curtime_tv;
151         gettimeofday(&curtime_tv, nullptr);
152         curtime.tv_sec = curtime_tv.tv_sec;
153         curtime.tv_nsec = curtime_tv.tv_usec * 1000;
154 #else
155         clock_gettime(CLOCK_MONOTONIC, &curtime);
156 #endif
157         curtime.tv_sec += timeout.tv_sec;
158         curtime.tv_nsec += timeout.tv_nsec;
159         if (curtime.tv_nsec > 1000000000) {
160             curtime.tv_nsec -= 1000000000;
161             curtime.tv_sec++;
162         }
163         setTimer(timer_id, curtime, interval, enable);
164     }
165     
166     // Enables or disabling report of timeouts (does not stop timer)
167     void enableTimer(timer_handle_t &timer_id, bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
168     {
169         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
170         enableTimer_nolock(timer_id, enable, clock);
171     }
172     
173     void enableTimer_nolock(timer_handle_t &timer_id, bool enable, clock_type = clock_type::MONOTONIC) noexcept
174     {
175         auto &node_data = timer_queue.node_data(timer_id);
176         auto expiry_count = node_data.expiry_count;
177         if (expiry_count != 0) {
178             node_data.expiry_count = 0;
179             Base::receiveTimerExpiry(timer_id, node_data.userdata, expiry_count);
180         }
181         else {
182             timer_queue.node_data(timer_id).enabled = enable;
183         }
184     }
185
186     void stop_timer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
187     {
188         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
189         stop_timer_nolock(timer_id, clock);
190     }
191
192     void stop_timer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
193     {
194         if (timer_queue.is_queued(timer_id)) {
195             bool was_first = (&timer_queue.get_root()) == &timer_id;
196             timer_queue.remove(timer_id);
197             if (was_first) {
198                 set_timer_from_queue();
199             }
200         }
201     }
202 };
203
204 }