diff options
author | Mathias Gumz <akira at fluxbox dot org> | 2012-08-28 08:51:55 (GMT) |
---|---|---|
committer | Mathias Gumz <akira at fluxbox dot org> | 2012-08-28 08:51:55 (GMT) |
commit | 541c8c407b7ba8dd10f85bb48bcb5900270b3f84 (patch) | |
tree | 71a6abc0f2a43bcfd33f80b3b30b878f234cbf05 /src/FbTk/Timer.cc | |
parent | 60a53113e05db443af4d520883ec3145680642a8 (diff) | |
download | fluxbox-541c8c407b7ba8dd10f85bb48bcb5900270b3f84.zip fluxbox-541c8c407b7ba8dd10f85bb48bcb5900270b3f84.tar.bz2 |
changed timing functions to use a monotonic increasing clock
gettimeofday() is subject to be changed on daylight-saving or to ntp-related
(think leap-seconds). even worse, it is subject to be changed BACK in time. this
is hard to fix correctly (see commit 45726d3016e and bug #3560509). it is
irrelevant for timers to know the nano-seconds since the epoch anyways.
Diffstat (limited to 'src/FbTk/Timer.cc')
-rw-r--r-- | src/FbTk/Timer.cc | 232 |
1 files changed, 87 insertions, 145 deletions
diff --git a/src/FbTk/Timer.cc b/src/FbTk/Timer.cc index 8b144db..6a478bd 100644 --- a/src/FbTk/Timer.cc +++ b/src/FbTk/Timer.cc | |||
@@ -1,5 +1,5 @@ | |||
1 | // Timer.cc for FbTk - Fluxbox Toolkit | 1 | // Timer.cc for FbTk - Fluxbox Toolkit |
2 | // Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org) | 2 | // Copyright (c) 2003 - 2012 Henrik Kinnunen (fluxgen at fluxbox dot org) |
3 | // | 3 | // |
4 | // Timer.cc for Blackbox - An X11 Window Manager | 4 | // Timer.cc for Blackbox - An X11 Window Manager |
5 | // Copyright (c) 1997 - 2000 Brad Hughes (bhughes at tcac.net) | 5 | // Copyright (c) 1997 - 2000 Brad Hughes (bhughes at tcac.net) |
@@ -32,10 +32,6 @@ | |||
32 | #define _GNU_SOURCE | 32 | #define _GNU_SOURCE |
33 | #endif // _GNU_SOURCE | 33 | #endif // _GNU_SOURCE |
34 | 34 | ||
35 | #ifdef HAVE_CONFIG_H | ||
36 | #include "config.h" | ||
37 | #endif // HAVE_CONFIG_H | ||
38 | |||
39 | #ifdef HAVE_CASSERT | 35 | #ifdef HAVE_CASSERT |
40 | #include <cassert> | 36 | #include <cassert> |
41 | #else | 37 | #else |
@@ -55,9 +51,48 @@ | |||
55 | # include <winsock.h> | 51 | # include <winsock.h> |
56 | #endif | 52 | #endif |
57 | 53 | ||
58 | namespace FbTk { | 54 | #include <cstdio> |
55 | #include <set> | ||
56 | |||
57 | |||
58 | namespace { | ||
59 | |||
60 | struct TimerCompare { | ||
61 | bool operator() (const FbTk::Timer* a, const FbTk::Timer* b) { | ||
62 | return a->getEndTime() < b->getEndTime(); | ||
63 | } | ||
64 | }; | ||
65 | typedef std::set<FbTk::Timer*, TimerCompare> TimerList; | ||
66 | |||
67 | TimerList s_timerlist; | ||
68 | |||
69 | |||
70 | /// add a timer to the static list | ||
71 | void addTimer(FbTk::Timer *timer) { | ||
72 | |||
73 | assert(timer); | ||
74 | int interval = timer->getInterval(); | ||
75 | |||
76 | // interval timers have their timeout change every time they are started! | ||
77 | if (interval != 0) { | ||
78 | timer->setTimeout(interval * FbTk::FbTime::IN_SECONDS); | ||
79 | } | ||
80 | |||
81 | s_timerlist.insert(timer); | ||
82 | } | ||
59 | 83 | ||
60 | Timer::TimerList Timer::m_timerlist; | 84 | /// remove a timer from the static list |
85 | void removeTimer(FbTk::Timer *timer) { | ||
86 | |||
87 | assert(timer); | ||
88 | s_timerlist.erase(timer); | ||
89 | } | ||
90 | |||
91 | |||
92 | } | ||
93 | |||
94 | |||
95 | namespace FbTk { | ||
61 | 96 | ||
62 | Timer::Timer():m_timing(false), m_once(false), m_interval(0) { | 97 | Timer::Timer():m_timing(false), m_once(false), m_interval(0) { |
63 | 98 | ||
@@ -76,22 +111,8 @@ Timer::~Timer() { | |||
76 | } | 111 | } |
77 | 112 | ||
78 | 113 | ||
79 | void Timer::setTimeout(time_t t) { | 114 | void Timer::setTimeout(uint64_t timeout) { |
80 | m_timeout.tv_sec = t / 1000; | 115 | m_timeout = timeout; |
81 | m_timeout.tv_usec = t; | ||
82 | m_timeout.tv_usec -= (m_timeout.tv_sec * 1000); | ||
83 | m_timeout.tv_usec *= 1000; | ||
84 | } | ||
85 | |||
86 | |||
87 | void Timer::setTimeout(const timeval &t) { | ||
88 | m_timeout.tv_sec = t.tv_sec; | ||
89 | m_timeout.tv_usec = t.tv_usec; | ||
90 | } | ||
91 | |||
92 | void Timer::setTimeout(unsigned int secs, unsigned int usecs) { | ||
93 | m_timeout.tv_sec = secs; | ||
94 | m_timeout.tv_usec = usecs; | ||
95 | } | 116 | } |
96 | 117 | ||
97 | void Timer::setCommand(const RefCount<Slot<void> > &cmd) { | 118 | void Timer::setCommand(const RefCount<Slot<void> > &cmd) { |
@@ -99,28 +120,24 @@ void Timer::setCommand(const RefCount<Slot<void> > &cmd) { | |||
99 | } | 120 | } |
100 | 121 | ||
101 | void Timer::start() { | 122 | void Timer::start() { |
102 | gettimeofday(&m_start, 0); | 123 | |
124 | m_start = FbTk::FbTime::now(); | ||
103 | 125 | ||
104 | // only add Timers that actually DO something | 126 | // only add Timers that actually DO something |
105 | if ((! m_timing || m_interval != 0) && m_handler) { | 127 | if ((! m_timing || m_interval != 0) && m_handler) { |
106 | m_timing = true; | 128 | m_timing = true; |
107 | addTimer(this); //add us to the list | 129 | ::addTimer(this); |
108 | } | 130 | } |
109 | } | 131 | } |
110 | 132 | ||
111 | 133 | ||
112 | void Timer::stop() { | 134 | void Timer::stop() { |
113 | m_timing = false; | 135 | m_timing = false; |
114 | removeTimer(this); //remove us from the list | 136 | ::removeTimer(this); |
115 | } | 137 | } |
116 | 138 | ||
117 | void Timer::makeEndTime(timeval &tm) const { | 139 | uint64_t Timer::getEndTime() const { |
118 | tm.tv_sec = m_start.tv_sec + m_timeout.tv_sec; | 140 | return m_start + m_timeout; |
119 | tm.tv_usec = m_start.tv_usec + m_timeout.tv_usec; | ||
120 | if (tm.tv_usec >= 1000000) { | ||
121 | tm.tv_usec -= 1000000; | ||
122 | tm.tv_sec++; | ||
123 | } | ||
124 | } | 141 | } |
125 | 142 | ||
126 | 143 | ||
@@ -129,42 +146,34 @@ void Timer::fireTimeout() { | |||
129 | (*m_handler)(); | 146 | (*m_handler)(); |
130 | } | 147 | } |
131 | 148 | ||
149 | |||
132 | void Timer::updateTimers(int fd) { | 150 | void Timer::updateTimers(int fd) { |
151 | |||
133 | fd_set rfds; | 152 | fd_set rfds; |
134 | timeval now, tm, *timeout = 0; | 153 | timeval tm; |
154 | timeval* timeout = 0; | ||
155 | TimerList::iterator it; | ||
135 | 156 | ||
136 | FD_ZERO(&rfds); | 157 | FD_ZERO(&rfds); |
137 | FD_SET(fd, &rfds); | 158 | FD_SET(fd, &rfds); |
138 | 159 | ||
139 | bool overdue = false; | 160 | bool overdue = false; |
161 | uint64_t now = FbTime::now(); | ||
162 | uint64_t end_time; | ||
140 | 163 | ||
141 | // see, if the first timer in the | 164 | // see, if the first timer in the |
142 | // list is overdue | 165 | // list is overdue |
143 | if (!m_timerlist.empty()) { | 166 | if (!s_timerlist.empty()) { |
144 | gettimeofday(&now, 0); | ||
145 | |||
146 | Timer *timer = m_timerlist.front(); | ||
147 | |||
148 | timer->makeEndTime(tm); | ||
149 | 167 | ||
150 | tm.tv_sec -= now.tv_sec; | 168 | Timer* timer = *s_timerlist.begin(); |
151 | tm.tv_usec -= now.tv_usec; | 169 | end_time = timer->getEndTime(); |
152 | 170 | ||
153 | while (tm.tv_usec < 0) { | 171 | if (end_time < now) { |
154 | if (tm.tv_sec > 0) { | ||
155 | tm.tv_sec--; | ||
156 | tm.tv_usec += 1000000; | ||
157 | } else { | ||
158 | overdue = true; | ||
159 | tm.tv_usec = 0; | ||
160 | break; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | if (tm.tv_sec < 0) { // usec zero-ed above if negative | ||
165 | tm.tv_sec = 0; | ||
166 | tm.tv_usec = 0; | ||
167 | overdue = true; | 172 | overdue = true; |
173 | } else { | ||
174 | uint64_t diff = (end_time - now); | ||
175 | tm.tv_sec = diff / FbTime::IN_SECONDS; | ||
176 | tm.tv_usec = diff % FbTime::IN_SECONDS; | ||
168 | } | 177 | } |
169 | 178 | ||
170 | timeout = &tm; | 179 | timeout = &tm; |
@@ -173,100 +182,41 @@ void Timer::updateTimers(int fd) { | |||
173 | // if not overdue, wait for the next xevent via the blocking | 182 | // if not overdue, wait for the next xevent via the blocking |
174 | // select(), so OS sends fluxbox to sleep. the select() will | 183 | // select(), so OS sends fluxbox to sleep. the select() will |
175 | // time out when the next timer has to be handled | 184 | // time out when the next timer has to be handled |
176 | if (!overdue && select(fd + 1, &rfds, 0, 0, timeout) != 0) | 185 | if (!overdue && select(fd + 1, &rfds, 0, 0, timeout) != 0) { |
177 | // didn't time out! x events are pending | 186 | // didn't time out! x events are pending |
178 | return; | 187 | return; |
179 | |||
180 | TimerList::iterator it; | ||
181 | |||
182 | // check for timer timeout | ||
183 | gettimeofday(&now, 0); | ||
184 | |||
185 | // someone set the date of the machine BACK | ||
186 | // so we have to adjust the start_time | ||
187 | static time_t last_time = 0; | ||
188 | if (now.tv_sec < last_time) { | ||
189 | |||
190 | time_t delta = last_time - now.tv_sec; | ||
191 | |||
192 | for (it = m_timerlist.begin(); it != m_timerlist.end(); ++it) { | ||
193 | (*it)->m_start.tv_sec -= delta; | ||
194 | } | ||
195 | } | 188 | } |
196 | last_time = now.tv_sec; | ||
197 | |||
198 | 189 | ||
199 | //must check end ...the timer might remove | 190 | now = FbTime::now(); |
200 | //it self from the list (should be fixed in the future) | 191 | for (it = s_timerlist.begin(); it != s_timerlist.end(); ) { |
201 | for(it = m_timerlist.begin(); it != m_timerlist.end(); ) { | ||
202 | //This is to make sure we don't get an invalid iterator | ||
203 | //when we do fireTimeout | ||
204 | Timer &t = *(*it); | ||
205 | 192 | ||
206 | t.makeEndTime(tm); | 193 | // t->fireTimeout() might add timers to the list |
207 | 194 | // this invalidates 'it'. thus we store the current | |
208 | if (((now.tv_sec < tm.tv_sec) || | 195 | // item here |
209 | (now.tv_sec == tm.tv_sec && now.tv_usec < tm.tv_usec))) | 196 | Timer* t = *it; |
197 | if (now < t->getEndTime()) { | ||
210 | break; | 198 | break; |
211 | |||
212 | t.fireTimeout(); | ||
213 | // restart the current timer so that the start time is updated | ||
214 | if (! t.doOnce()) { | ||
215 | // must erase so that it's put into the right place in the list | ||
216 | it = m_timerlist.erase(it); | ||
217 | t.m_timing = false; | ||
218 | t.start(); | ||
219 | } else { | ||
220 | // Since the default stop behaviour results in the timer | ||
221 | // being removed, we must remove it here, so that the iterator | ||
222 | // lives well. Another option would be to add it to another | ||
223 | // list, and then just go through that list and stop them all. | ||
224 | it = m_timerlist.erase(it); | ||
225 | t.stop(); | ||
226 | } | 199 | } |
227 | } | ||
228 | 200 | ||
229 | } | 201 | t->fireTimeout(); |
230 | 202 | ||
231 | void Timer::addTimer(Timer *timer) { | 203 | // find the iterator to the timer again |
232 | assert(timer); | 204 | // and continue working on the list |
233 | int interval = timer->getInterval(); | 205 | it = s_timerlist.find(t); |
234 | // interval timers have their timeout change every time they are started! | 206 | it++; |
235 | timeval tm; | 207 | s_timerlist.erase(t); |
236 | if (interval != 0) { | ||
237 | tm.tv_sec = timer->getStartTime().tv_sec; | ||
238 | tm.tv_usec = timer->getStartTime().tv_usec; | ||
239 | |||
240 | // now convert to interval | ||
241 | tm.tv_sec = interval - (tm.tv_sec % interval) - 1; | ||
242 | tm.tv_usec = 1000000 - tm.tv_usec; | ||
243 | if (tm.tv_usec == 1000000) { | ||
244 | tm.tv_usec = 0; | ||
245 | tm.tv_sec += 1; | ||
246 | } | ||
247 | timer->setTimeout(tm); | ||
248 | } | ||
249 | |||
250 | // set timeval to the time-of-trigger | ||
251 | timer->makeEndTime(tm); | ||
252 | 208 | ||
253 | // timer list is sorted by trigger time (i.e. start plus timeout) | 209 | if (! t->doOnce()) { // restart the current timer |
254 | TimerList::iterator it = m_timerlist.begin(); | 210 | t->m_timing = false; |
255 | TimerList::iterator it_end = m_timerlist.end(); | 211 | t->start(); |
256 | for (; it != it_end; ++it) { | 212 | } else { |
257 | timeval trig; | 213 | t->stop(); |
258 | (*it)->makeEndTime(trig); | ||
259 | |||
260 | if ((trig.tv_sec > tm.tv_sec) || | ||
261 | (trig.tv_sec == tm.tv_sec && | ||
262 | trig.tv_usec >= tm.tv_usec)) { | ||
263 | break; | ||
264 | } | 214 | } |
265 | } | 215 | } |
266 | m_timerlist.insert(it, timer); | ||
267 | 216 | ||
268 | } | 217 | } |
269 | 218 | ||
219 | |||
270 | Command<void> *DelayedCmd::parse(const std::string &command, | 220 | Command<void> *DelayedCmd::parse(const std::string &command, |
271 | const std::string &args, bool trusted) { | 221 | const std::string &args, bool trusted) { |
272 | 222 | ||
@@ -280,7 +230,7 @@ Command<void> *DelayedCmd::parse(const std::string &command, | |||
280 | if (cmd == 0) | 230 | if (cmd == 0) |
281 | return 0; | 231 | return 0; |
282 | 232 | ||
283 | int delay = 200000; | 233 | int delay = 200; |
284 | StringUtil::fromString<int>(args.c_str() + err, delay); | 234 | StringUtil::fromString<int>(args.c_str() + err, delay); |
285 | 235 | ||
286 | return new DelayedCmd(cmd, delay); | 236 | return new DelayedCmd(cmd, delay); |
@@ -294,10 +244,7 @@ DelayedCmd::DelayedCmd(const RefCount<Slot<void> > &cmd, unsigned int timeout) { | |||
294 | } | 244 | } |
295 | 245 | ||
296 | void DelayedCmd::initTimer(unsigned int timeout) { | 246 | void DelayedCmd::initTimer(unsigned int timeout) { |
297 | timeval to; | 247 | m_timer.setTimeout(timeout * FbTime::IN_MILLISECONDS); |
298 | to.tv_sec = timeout/1000000; | ||
299 | to.tv_usec = timeout % 1000000; | ||
300 | m_timer.setTimeout(to); | ||
301 | m_timer.fireOnce(true); | 248 | m_timer.fireOnce(true); |
302 | } | 249 | } |
303 | 250 | ||
@@ -307,9 +254,4 @@ void DelayedCmd::execute() { | |||
307 | m_timer.start(); | 254 | m_timer.start(); |
308 | } | 255 | } |
309 | 256 | ||
310 | void Timer::removeTimer(Timer *timer) { | ||
311 | assert(timer); | ||
312 | m_timerlist.remove(timer); | ||
313 | } | ||
314 | |||
315 | } // end namespace FbTk | 257 | } // end namespace FbTk |