Minor fixes to tests.
[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 obsolete) POSIX itimer interface.
12
13 // With this timer implementation, we only use one clock, and allow no distinction between the
14 // monotonic and system time.
15
16 template <class Base> class itimer_events : public timer_base<Base>
17 {
18     private:
19     
20     // Set the alarm timeout to match the first timer in the queue (disable the alarm if there are no
21     // active timers).
22     void set_timer_from_queue()
23     {
24         time_val newtime;
25         struct itimerval newalarm;
26
27         bool interval_set = false;
28         time_val interval_tv = {0, 0};
29
30         auto &timer_queue = this->queue_for_clock(clock_type::SYSTEM);
31         if (! timer_queue.empty()) {
32             newtime = timer_queue.get_root_priority();
33
34             time_val curtimev;
35             timer_base<Base>::get_time(curtimev, clock_type::SYSTEM, true);
36
37             // interval before next timeout:
38             if (curtimev < newtime) {
39                 interval_tv = newtime - curtimev;
40             }
41
42             interval_set = true;
43         }
44
45 #ifdef CLOCK_MONOTONIC
46         auto &mono_timer_queue = this->queue_for_clock(clock_type::MONOTONIC);
47
48         if (! mono_timer_queue.empty()) {
49
50             // If we have a separate monotonic clock, we get the interval for the expiry of the next monotonic
51             // timer and use the lesser of the system interval and monotonic interval:
52             time_val mono_newtime = mono_timer_queue.get_root_priority();
53
54             time_val curtimev_mono;
55             timer_base<Base>::get_time(curtimev_mono, clock_type::MONOTONIC, true);
56
57             time_val interval_mono = {0, 0};
58             if (curtimev_mono < mono_newtime) {
59                 interval_mono = mono_newtime - curtimev_mono;
60             }
61
62             if (! interval_set || interval_mono < interval_tv) {
63                 interval_tv = interval_mono;
64             }
65
66             interval_set = true;
67         }
68 #endif
69
70         newalarm.it_value.tv_sec = interval_tv.seconds();
71         newalarm.it_value.tv_usec = interval_tv.nseconds() / 1000;
72         newalarm.it_interval.tv_sec = 0;
73         newalarm.it_interval.tv_usec = 0;
74
75         if (interval_set && newalarm.it_value.tv_sec == 0 && newalarm.it_value.tv_usec == 0) {
76             // We passed the timeout: set alarm to expire immediately (we must use {0,1} as
77             // {0,0} disables the timer).
78             // TODO: it would be better if we just processed the appropriate timers here,
79             //       but that is complicated to get right especially if the event loop
80             //       is multi-threaded.
81             newalarm.it_value.tv_sec = 0;
82             newalarm.it_value.tv_usec = 1;
83         }
84
85         setitimer(ITIMER_REAL, &newalarm, nullptr);
86     }
87     
88     protected:
89     
90     using sigdata_t = typename Base::sigdata_t;
91
92     template <typename T>
93     bool receive_signal(T & loop_mech, sigdata_t &siginfo, void *userdata)
94     {
95         if (siginfo.get_signo() == SIGALRM) {
96             auto &timer_queue = this->queue_for_clock(clock_type::SYSTEM);
97             if (! timer_queue.empty()) {
98                 struct timespec curtime;
99                 timer_base<Base>::get_time(curtime, clock_type::SYSTEM, true);
100                 timer_base<Base>::process_timer_queue(timer_queue, curtime);
101             }
102             
103 #ifdef CLOCK_MONOTONIC
104             auto &mono_timer_queue = this->queue_for_clock(clock_type::MONOTONIC);
105             if (! mono_timer_queue.empty()) {
106                 struct timespec curtime_mono;
107                 timer_base<Base>::get_time(curtime_mono, clock_type::MONOTONIC, true);
108                 timer_base<Base>::process_timer_queue(mono_timer_queue, curtime_mono);
109             }
110 #endif
111
112             // arm alarm with timeout from head of queue
113             set_timer_from_queue();
114             return false; // don't disable signal watch
115         }
116         else {
117             return Base::receive_signal(loop_mech, siginfo, userdata);
118         }
119     }
120         
121     public:
122
123     class traits_t : public Base::traits_t
124     {
125         constexpr static bool full_timer_support = false;
126     };
127
128     template <typename T> void init(T *loop_mech)
129     {
130         sigset_t sigmask;
131         this->sigmaskf(SIG_UNBLOCK, nullptr, &sigmask);
132         sigaddset(&sigmask, SIGALRM);
133         this->sigmaskf(SIG_SETMASK, &sigmask, nullptr);
134         loop_mech->add_signal_watch(SIGALRM, nullptr);
135         Base::init(loop_mech);
136     }
137     
138     // starts (if not started) a timer to timeout at the given time. Resets the expiry count to 0.
139     //   enable: specifies whether to enable reporting of timeouts/intervals
140     void set_timer(timer_handle_t &timer_id, const time_val &timeouttv, const time_val &intervaltv,
141             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
142     {
143         auto &timer_queue = this->queue_for_clock(clock);
144         timespec timeout = timeouttv;
145         timespec interval = intervaltv;
146
147         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
148
149         auto &ts = timer_queue.node_data(timer_id);
150         ts.interval_time = interval;
151         ts.expiry_count = 0;
152         ts.enabled = enable;
153
154         if (timer_queue.is_queued(timer_id)) {
155             // Already queued; alter timeout
156             if (timer_queue.set_priority(timer_id, timeout)) {
157                 set_timer_from_queue();
158             }
159         }
160         else {
161             if (timer_queue.insert(timer_id, timeout)) {
162                 set_timer_from_queue();
163             }
164         }
165     }
166
167     // Set timer relative to current time:    
168     void set_timer_rel(timer_handle_t &timer_id, const time_val &timeouttv, const time_val &intervaltv,
169             bool enable, clock_type clock = clock_type::MONOTONIC) noexcept
170     {
171         timespec timeout = timeouttv;
172         timespec interval = intervaltv;
173
174         struct timespec curtime;
175         timer_base<Base>::get_time(curtime, clock, false);
176         curtime.tv_sec += timeout.tv_sec;
177         curtime.tv_nsec += timeout.tv_nsec;
178         if (curtime.tv_nsec > 1000000000) {
179             curtime.tv_nsec -= 1000000000;
180             curtime.tv_sec++;
181         }
182         set_timer(timer_id, curtime, interval, enable, clock);
183     }
184     
185     void stop_timer(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
186     {
187         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
188         stop_timer_nolock(timer_id, clock);
189     }
190
191     void stop_timer_nolock(timer_handle_t &timer_id, clock_type clock = clock_type::MONOTONIC) noexcept
192     {
193         auto &timer_queue = this->queue_for_clock(clock);
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 }