]>
Commit | Line | Data |
---|---|---|
88840341 | 1 | /* |
88840341 RC |
2 | chronyd/chronyc - Programs for keeping computer clocks accurate. |
3 | ||
4 | ********************************************************************** | |
6672f045 | 5 | * Copyright (C) Richard P. Curnow 1997-2003 |
a4e3f836 | 6 | * Copyright (C) Miroslav Lichvar 2011, 2013-2016 |
88840341 RC |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of version 2 of the GNU General Public License as | |
10 | * published by the Free Software Foundation. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License along | |
18 | * with this program; if not, write to the Free Software Foundation, Inc., | |
8e23110a | 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
88840341 RC |
20 | * |
21 | ********************************************************************** | |
22 | ||
23 | ======================================================================= | |
24 | ||
25 | This file contains the scheduling loop and the timeout queue. | |
26 | ||
27 | */ | |
28 | ||
da2c8d90 ML |
29 | #include "config.h" |
30 | ||
88840341 RC |
31 | #include "sysincl.h" |
32 | ||
ba875fc0 | 33 | #include "array.h" |
88840341 RC |
34 | #include "sched.h" |
35 | #include "memory.h" | |
36 | #include "util.h" | |
37 | #include "local.h" | |
38 | #include "logging.h" | |
39 | ||
40 | /* ================================================== */ | |
41 | ||
42 | /* Flag indicating that we are initialised */ | |
43 | static int initialised = 0; | |
44 | ||
45 | /* ================================================== */ | |
46 | ||
88840341 RC |
47 | /* One more than the highest file descriptor that is registered */ |
48 | static unsigned int one_highest_fd; | |
49 | ||
a2b40f52 ML |
50 | #ifndef FD_SETSIZE |
51 | /* If FD_SETSIZE is not defined, assume that fd_set is implemented | |
52 | as a fixed size array of bits, possibly embedded inside a record */ | |
53 | #define FD_SETSIZE (sizeof(fd_set) * 8) | |
54 | #endif | |
88840341 RC |
55 | |
56 | typedef struct { | |
57 | SCH_FileHandler handler; | |
58 | SCH_ArbitraryArgument arg; | |
d8d096aa | 59 | int events; |
88840341 RC |
60 | } FileHandlerEntry; |
61 | ||
ba875fc0 | 62 | static ARR_Instance file_handlers; |
88840341 | 63 | |
91749ebb | 64 | /* Timestamp when last select() returned */ |
d0dfa1de | 65 | static struct timespec last_select_ts, last_select_ts_raw; |
91749ebb | 66 | static double last_select_ts_err; |
e9ae3d0a | 67 | |
bb0553e4 ML |
68 | #define TS_MONO_PRECISION_NS 10000000U |
69 | ||
70 | /* Monotonic low-precision timestamp measuring interval since the start */ | |
71 | static double last_select_ts_mono; | |
72 | static uint32_t last_select_ts_mono_ns; | |
73 | ||
88840341 RC |
74 | /* ================================================== */ |
75 | ||
76 | /* Variables to handler the timer queue */ | |
77 | ||
78 | typedef struct _TimerQueueEntry | |
79 | { | |
80 | struct _TimerQueueEntry *next; /* Forward and back links in the list */ | |
81 | struct _TimerQueueEntry *prev; | |
d0dfa1de | 82 | struct timespec ts; /* Local system time at which the |
88840341 RC |
83 | timeout is to expire. Clearly this |
84 | must be in terms of what the | |
85 | operating system thinks of as | |
86 | system time, because it will be an | |
87 | argument to select(). Therefore, | |
88 | any fudges etc that our local time | |
89 | driver module would apply to time | |
90 | that we pass to clients etc doesn't | |
91 | apply to this. */ | |
92 | SCH_TimeoutID id; /* ID to allow client to delete | |
93 | timeout */ | |
94 | SCH_TimeoutClass class; /* The class that the epoch is in */ | |
95 | SCH_TimeoutHandler handler; /* The handler routine to use */ | |
96 | SCH_ArbitraryArgument arg; /* The argument to pass to the handler */ | |
97 | ||
98 | } TimerQueueEntry; | |
99 | ||
100 | /* The timer queue. We only use the next and prev entries of this | |
101 | record, these chain to the real entries. */ | |
102 | static TimerQueueEntry timer_queue; | |
103 | static unsigned long n_timer_queue_entries; | |
104 | static SCH_TimeoutID next_tqe_id; | |
105 | ||
106 | /* Pointer to head of free list */ | |
107 | static TimerQueueEntry *tqe_free_list = NULL; | |
108 | ||
0078705b | 109 | /* Timestamp when was last timeout dispatched for each class */ |
d0dfa1de | 110 | static struct timespec last_class_dispatch[SCH_NumberOfClasses]; |
0078705b | 111 | |
88840341 RC |
112 | /* ================================================== */ |
113 | ||
3812ec2a ML |
114 | /* Flag terminating the main loop, which can be set from a signal handler */ |
115 | static volatile int need_to_exit; | |
88840341 RC |
116 | |
117 | /* ================================================== */ | |
118 | ||
119 | static void | |
d0dfa1de ML |
120 | handle_slew(struct timespec *raw, |
121 | struct timespec *cooked, | |
88840341 | 122 | double dfreq, |
88840341 | 123 | double doffset, |
44c9744d | 124 | LCL_ChangeType change_type, |
88840341 RC |
125 | void *anything); |
126 | ||
127 | /* ================================================== */ | |
128 | ||
129 | void | |
130 | SCH_Initialise(void) | |
131 | { | |
ba875fc0 ML |
132 | file_handlers = ARR_CreateInstance(sizeof (FileHandlerEntry)); |
133 | ||
88840341 RC |
134 | n_timer_queue_entries = 0; |
135 | next_tqe_id = 0; | |
136 | ||
137 | timer_queue.next = &timer_queue; | |
138 | timer_queue.prev = &timer_queue; | |
139 | ||
140 | need_to_exit = 0; | |
141 | ||
142 | LCL_AddParameterChangeHandler(handle_slew, NULL); | |
143 | ||
6fa11a85 ML |
144 | LCL_ReadRawTime(&last_select_ts_raw); |
145 | last_select_ts = last_select_ts_raw; | |
bb0553e4 ML |
146 | last_select_ts_mono = 0.0; |
147 | last_select_ts_mono_ns = 0; | |
6fa11a85 | 148 | |
88840341 | 149 | initialised = 1; |
88840341 RC |
150 | } |
151 | ||
152 | ||
153 | /* ================================================== */ | |
154 | ||
155 | void | |
156 | SCH_Finalise(void) { | |
ba875fc0 ML |
157 | ARR_DestroyInstance(file_handlers); |
158 | ||
3e537416 ML |
159 | LCL_RemoveParameterChangeHandler(handle_slew, NULL); |
160 | ||
88840341 | 161 | initialised = 0; |
88840341 RC |
162 | } |
163 | ||
164 | /* ================================================== */ | |
165 | ||
166 | void | |
0a105453 ML |
167 | SCH_AddFileHandler |
168 | (int fd, int events, SCH_FileHandler handler, SCH_ArbitraryArgument arg) | |
88840341 | 169 | { |
ba875fc0 | 170 | FileHandlerEntry *ptr; |
88840341 | 171 | |
6b0198c2 | 172 | assert(initialised); |
0a105453 ML |
173 | assert(events); |
174 | assert(fd >= 0); | |
88840341 | 175 | |
07f7f280 | 176 | if (fd >= FD_SETSIZE) |
f282856c | 177 | LOG_FATAL("Too many file descriptors"); |
07f7f280 | 178 | |
d8d096aa ML |
179 | /* Resize the array if the descriptor is highest so far */ |
180 | while (ARR_GetSize(file_handlers) <= fd) { | |
181 | ptr = ARR_GetNewElement(file_handlers); | |
182 | ptr->handler = NULL; | |
183 | ptr->arg = NULL; | |
184 | ptr->events = 0; | |
185 | } | |
186 | ||
187 | ptr = ARR_GetElement(file_handlers, fd); | |
188 | ||
88840341 RC |
189 | /* Don't want to allow the same fd to register a handler more than |
190 | once without deleting a previous association - this suggests | |
191 | a bug somewhere else in the program. */ | |
d8d096aa | 192 | assert(!ptr->handler); |
ba875fc0 | 193 | |
ba875fc0 ML |
194 | ptr->handler = handler; |
195 | ptr->arg = arg; | |
d8d096aa | 196 | ptr->events = events; |
88840341 | 197 | |
d8d096aa | 198 | if (one_highest_fd < fd + 1) |
88840341 | 199 | one_highest_fd = fd + 1; |
88840341 RC |
200 | } |
201 | ||
202 | ||
203 | /* ================================================== */ | |
204 | ||
205 | void | |
0a105453 | 206 | SCH_RemoveFileHandler(int fd) |
88840341 | 207 | { |
d8d096aa | 208 | FileHandlerEntry *ptr; |
88840341 | 209 | |
6b0198c2 | 210 | assert(initialised); |
88840341 | 211 | |
d8d096aa | 212 | ptr = ARR_GetElement(file_handlers, fd); |
88840341 | 213 | |
d8d096aa ML |
214 | /* Check that a handler was registered for the fd in question */ |
215 | assert(ptr->handler); | |
88840341 | 216 | |
d8d096aa ML |
217 | ptr->handler = NULL; |
218 | ptr->arg = NULL; | |
219 | ptr->events = 0; | |
88840341 RC |
220 | |
221 | /* Find new highest file descriptor */ | |
d8d096aa ML |
222 | while (one_highest_fd > 0) { |
223 | ptr = ARR_GetElement(file_handlers, one_highest_fd - 1); | |
224 | if (ptr->handler) | |
225 | break; | |
226 | one_highest_fd--; | |
88840341 | 227 | } |
88840341 RC |
228 | } |
229 | ||
230 | /* ================================================== */ | |
231 | ||
0a105453 | 232 | void |
2c4c2351 | 233 | SCH_SetFileHandlerEvent(int fd, int event, int enable) |
0a105453 ML |
234 | { |
235 | FileHandlerEntry *ptr; | |
236 | ||
0a105453 | 237 | ptr = ARR_GetElement(file_handlers, fd); |
2c4c2351 ML |
238 | |
239 | if (enable) | |
240 | ptr->events |= event; | |
241 | else | |
242 | ptr->events &= ~event; | |
0a105453 ML |
243 | } |
244 | ||
245 | /* ================================================== */ | |
246 | ||
e9ae3d0a | 247 | void |
d0dfa1de | 248 | SCH_GetLastEventTime(struct timespec *cooked, double *err, struct timespec *raw) |
e9ae3d0a | 249 | { |
58f76892 ML |
250 | if (cooked) { |
251 | *cooked = last_select_ts; | |
252 | if (err) | |
253 | *err = last_select_ts_err; | |
254 | } | |
255 | if (raw) | |
256 | *raw = last_select_ts_raw; | |
e9ae3d0a ML |
257 | } |
258 | ||
259 | /* ================================================== */ | |
260 | ||
bb0553e4 ML |
261 | double |
262 | SCH_GetLastEventMonoTime(void) | |
263 | { | |
264 | return last_select_ts_mono; | |
265 | } | |
266 | ||
267 | /* ================================================== */ | |
268 | ||
88840341 RC |
269 | #define TQE_ALLOC_QUANTUM 32 |
270 | ||
271 | static TimerQueueEntry * | |
272 | allocate_tqe(void) | |
273 | { | |
274 | TimerQueueEntry *new_block; | |
275 | TimerQueueEntry *result; | |
276 | int i; | |
277 | if (tqe_free_list == NULL) { | |
278 | new_block = MallocArray(TimerQueueEntry, TQE_ALLOC_QUANTUM); | |
279 | for (i=1; i<TQE_ALLOC_QUANTUM; i++) { | |
280 | new_block[i].next = &(new_block[i-1]); | |
281 | } | |
282 | new_block[0].next = NULL; | |
283 | tqe_free_list = &(new_block[TQE_ALLOC_QUANTUM - 1]); | |
284 | } | |
285 | ||
286 | result = tqe_free_list; | |
287 | tqe_free_list = tqe_free_list->next; | |
288 | return result; | |
289 | } | |
290 | ||
291 | /* ================================================== */ | |
292 | ||
293 | static void | |
294 | release_tqe(TimerQueueEntry *node) | |
295 | { | |
296 | node->next = tqe_free_list; | |
297 | tqe_free_list = node; | |
88840341 RC |
298 | } |
299 | ||
300 | /* ================================================== */ | |
301 | ||
0076458e ML |
302 | static SCH_TimeoutID |
303 | get_new_tqe_id(void) | |
304 | { | |
38910424 ML |
305 | TimerQueueEntry *ptr; |
306 | ||
307 | try_again: | |
0076458e ML |
308 | next_tqe_id++; |
309 | if (!next_tqe_id) | |
38910424 ML |
310 | goto try_again; |
311 | ||
312 | /* Make sure the ID isn't already used */ | |
313 | for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) | |
314 | if (ptr->id == next_tqe_id) | |
315 | goto try_again; | |
0076458e ML |
316 | |
317 | return next_tqe_id; | |
318 | } | |
319 | ||
320 | /* ================================================== */ | |
321 | ||
88840341 | 322 | SCH_TimeoutID |
d0dfa1de | 323 | SCH_AddTimeout(struct timespec *ts, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg) |
88840341 RC |
324 | { |
325 | TimerQueueEntry *new_tqe; | |
326 | TimerQueueEntry *ptr; | |
327 | ||
6b0198c2 | 328 | assert(initialised); |
88840341 RC |
329 | |
330 | new_tqe = allocate_tqe(); | |
331 | ||
0076458e | 332 | new_tqe->id = get_new_tqe_id(); |
88840341 RC |
333 | new_tqe->handler = handler; |
334 | new_tqe->arg = arg; | |
d0dfa1de | 335 | new_tqe->ts = *ts; |
88840341 RC |
336 | new_tqe->class = SCH_ReservedTimeoutValue; |
337 | ||
338 | /* Now work out where to insert the new entry in the list */ | |
339 | for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) { | |
d0dfa1de | 340 | if (UTI_CompareTimespecs(&new_tqe->ts, &ptr->ts) == -1) { |
88840341 RC |
341 | /* If the new entry comes before the current pointer location in |
342 | the list, we want to insert the new entry just before ptr. */ | |
343 | break; | |
344 | } | |
345 | } | |
346 | ||
347 | /* At this stage, we want to insert the new entry immediately before | |
348 | the entry identified by 'ptr' */ | |
349 | ||
350 | new_tqe->next = ptr; | |
351 | new_tqe->prev = ptr->prev; | |
352 | ptr->prev->next = new_tqe; | |
353 | ptr->prev = new_tqe; | |
354 | ||
355 | n_timer_queue_entries++; | |
356 | ||
357 | return new_tqe->id; | |
358 | } | |
359 | ||
360 | /* ================================================== */ | |
361 | /* This queues a timeout to elapse at a given delta time relative to | |
362 | the current (raw) time */ | |
363 | ||
364 | SCH_TimeoutID | |
365 | SCH_AddTimeoutByDelay(double delay, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg) | |
366 | { | |
d0dfa1de | 367 | struct timespec now, then; |
88840341 | 368 | |
6b0198c2 | 369 | assert(initialised); |
bab7ba22 | 370 | assert(delay >= 0.0); |
88840341 RC |
371 | |
372 | LCL_ReadRawTime(&now); | |
d0dfa1de ML |
373 | UTI_AddDoubleToTimespec(&now, delay, &then); |
374 | if (UTI_CompareTimespecs(&now, &then) > 0) { | |
f282856c | 375 | LOG_FATAL("Timeout overflow"); |
ea7fae52 ML |
376 | } |
377 | ||
88840341 RC |
378 | return SCH_AddTimeout(&then, handler, arg); |
379 | ||
380 | } | |
381 | ||
382 | /* ================================================== */ | |
383 | ||
384 | SCH_TimeoutID | |
833022b7 | 385 | SCH_AddTimeoutInClass(double min_delay, double separation, double randomness, |
88840341 RC |
386 | SCH_TimeoutClass class, |
387 | SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg) | |
388 | { | |
389 | TimerQueueEntry *new_tqe; | |
390 | TimerQueueEntry *ptr; | |
d0dfa1de | 391 | struct timespec now; |
833022b7 | 392 | double diff, r; |
88840341 RC |
393 | double new_min_delay; |
394 | ||
6b0198c2 | 395 | assert(initialised); |
bab7ba22 | 396 | assert(min_delay >= 0.0); |
0078705b | 397 | assert(class < SCH_NumberOfClasses); |
833022b7 ML |
398 | |
399 | if (randomness > 0.0) { | |
4b0ef092 | 400 | uint32_t rnd; |
dfc96e47 ML |
401 | |
402 | UTI_GetRandomBytes(&rnd, sizeof (rnd)); | |
4b0ef092 | 403 | r = rnd * (randomness / (uint32_t)-1) + 1.0; |
833022b7 ML |
404 | min_delay *= r; |
405 | separation *= r; | |
406 | } | |
88840341 RC |
407 | |
408 | LCL_ReadRawTime(&now); | |
409 | new_min_delay = min_delay; | |
410 | ||
0078705b | 411 | /* Check the separation from the last dispatched timeout */ |
cfe706f0 | 412 | diff = UTI_DiffTimespecsToDouble(&now, &last_class_dispatch[class]); |
0078705b ML |
413 | if (diff < separation && diff >= 0.0 && diff + new_min_delay < separation) { |
414 | new_min_delay = separation - diff; | |
415 | } | |
416 | ||
88840341 RC |
417 | /* Scan through list for entries in the same class and increase min_delay |
418 | if necessary to keep at least the separation away */ | |
419 | for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) { | |
420 | if (ptr->class == class) { | |
cfe706f0 | 421 | diff = UTI_DiffTimespecsToDouble(&ptr->ts, &now); |
88840341 RC |
422 | if (new_min_delay > diff) { |
423 | if (new_min_delay - diff < separation) { | |
424 | new_min_delay = diff + separation; | |
425 | } | |
4373918a | 426 | } else { |
88840341 RC |
427 | if (diff - new_min_delay < separation) { |
428 | new_min_delay = diff + separation; | |
429 | } | |
430 | } | |
431 | } | |
432 | } | |
433 | ||
434 | for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) { | |
cfe706f0 | 435 | diff = UTI_DiffTimespecsToDouble(&ptr->ts, &now); |
88840341 RC |
436 | if (diff > new_min_delay) { |
437 | break; | |
438 | } | |
439 | } | |
440 | ||
441 | /* We have located the insertion point */ | |
442 | new_tqe = allocate_tqe(); | |
443 | ||
0076458e | 444 | new_tqe->id = get_new_tqe_id(); |
88840341 RC |
445 | new_tqe->handler = handler; |
446 | new_tqe->arg = arg; | |
d0dfa1de | 447 | UTI_AddDoubleToTimespec(&now, new_min_delay, &new_tqe->ts); |
88840341 RC |
448 | new_tqe->class = class; |
449 | ||
450 | new_tqe->next = ptr; | |
451 | new_tqe->prev = ptr->prev; | |
452 | ptr->prev->next = new_tqe; | |
453 | ptr->prev = new_tqe; | |
454 | n_timer_queue_entries++; | |
455 | ||
456 | return new_tqe->id; | |
457 | } | |
458 | ||
459 | /* ================================================== */ | |
460 | ||
461 | void | |
462 | SCH_RemoveTimeout(SCH_TimeoutID id) | |
463 | { | |
464 | TimerQueueEntry *ptr; | |
88840341 | 465 | |
6b0198c2 | 466 | assert(initialised); |
88840341 | 467 | |
0076458e ML |
468 | if (!id) |
469 | return; | |
470 | ||
88840341 RC |
471 | for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) { |
472 | ||
473 | if (ptr->id == id) { | |
474 | /* Found the required entry */ | |
475 | ||
476 | /* Unlink from the queue */ | |
477 | ptr->next->prev = ptr->prev; | |
478 | ptr->prev->next = ptr->next; | |
479 | ||
480 | /* Decrement entry count */ | |
481 | --n_timer_queue_entries; | |
482 | ||
483 | /* Release memory back to the operating system */ | |
484 | release_tqe(ptr); | |
485 | ||
8803ab27 | 486 | return; |
88840341 RC |
487 | } |
488 | } | |
8803ab27 ML |
489 | |
490 | /* Catch calls with invalid non-zero ID */ | |
491 | assert(0); | |
88840341 RC |
492 | } |
493 | ||
494 | /* ================================================== */ | |
dce2366b ML |
495 | /* Try to dispatch any timeouts that have already gone by, and |
496 | keep going until all are done. (The earlier ones may take so | |
497 | long to do that the later ones come around by the time they are | |
498 | completed). */ | |
88840341 | 499 | |
dce2366b | 500 | static void |
d0dfa1de | 501 | dispatch_timeouts(struct timespec *now) { |
59e8b790 | 502 | unsigned long n_done, n_entries_on_start; |
88840341 | 503 | TimerQueueEntry *ptr; |
1d6b94b4 ML |
504 | SCH_TimeoutHandler handler; |
505 | SCH_ArbitraryArgument arg; | |
59e8b790 ML |
506 | |
507 | n_entries_on_start = n_timer_queue_entries; | |
508 | n_done = 0; | |
dce2366b | 509 | |
e7897eb9 | 510 | do { |
dce2366b ML |
511 | LCL_ReadRawTime(now); |
512 | ||
513 | if (!(n_timer_queue_entries > 0 && | |
d0dfa1de | 514 | UTI_CompareTimespecs(now, &timer_queue.next->ts) >= 0)) { |
dce2366b ML |
515 | break; |
516 | } | |
88840341 | 517 | |
88840341 RC |
518 | ptr = timer_queue.next; |
519 | ||
0078705b ML |
520 | last_class_dispatch[ptr->class] = *now; |
521 | ||
1d6b94b4 ML |
522 | handler = ptr->handler; |
523 | arg = ptr->arg; | |
524 | ||
525 | SCH_RemoveTimeout(ptr->id); | |
526 | ||
88840341 | 527 | /* Dispatch the handler */ |
1d6b94b4 | 528 | (handler)(arg); |
88840341 RC |
529 | |
530 | /* Increment count of timeouts handled */ | |
531 | ++n_done; | |
88840341 | 532 | |
59e8b790 ML |
533 | /* If the number of dispatched timeouts is significantly larger than the |
534 | length of the queue on start and now, assume there is a bug causing | |
535 | an infinite loop by constantly adding a timeout with a zero or negative | |
536 | delay. Check the actual rate of timeouts to avoid false positives in | |
537 | case the execution slowed down so much (e.g. due to memory thrashing) | |
538 | that it repeatedly takes more time to handle the timeout than is its | |
539 | delay. This is a safety mechanism intended to stop a full-speed flood | |
540 | of NTP requests due to a bug in the NTP polling. */ | |
541 | ||
542 | if (n_done > 20 && | |
543 | n_done > 4 * MAX(n_timer_queue_entries, n_entries_on_start) && | |
544 | fabs(UTI_DiffTimespecsToDouble(now, &last_select_ts_raw)) / n_done < 0.01) | |
f282856c | 545 | LOG_FATAL("Possible infinite loop in scheduling"); |
e7897eb9 ML |
546 | |
547 | } while (!need_to_exit); | |
88840341 RC |
548 | } |
549 | ||
550 | /* ================================================== */ | |
551 | ||
57fc2ff1 | 552 | /* nfd is the number of bits set in all fd_sets */ |
88840341 RC |
553 | |
554 | static void | |
c169ad3f | 555 | dispatch_filehandlers(int nfd, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds) |
88840341 | 556 | { |
ba875fc0 | 557 | FileHandlerEntry *ptr; |
57fc2ff1 | 558 | int fd; |
88840341 | 559 | |
57fc2ff1 | 560 | for (fd = 0; nfd && fd < one_highest_fd; fd++) { |
c169ad3f ML |
561 | if (except_fds && FD_ISSET(fd, except_fds)) { |
562 | /* This descriptor has an exception, dispatch its handler */ | |
563 | ptr = (FileHandlerEntry *)ARR_GetElement(file_handlers, fd); | |
a8167b79 ML |
564 | if (ptr->handler) |
565 | (ptr->handler)(fd, SCH_FILE_EXCEPTION, ptr->arg); | |
c169ad3f ML |
566 | nfd--; |
567 | ||
568 | /* Don't try to read from it now */ | |
569 | if (read_fds && FD_ISSET(fd, read_fds)) { | |
570 | FD_CLR(fd, read_fds); | |
571 | nfd--; | |
572 | } | |
573 | } | |
574 | ||
57fc2ff1 | 575 | if (read_fds && FD_ISSET(fd, read_fds)) { |
88840341 | 576 | /* This descriptor can be read from, dispatch its handler */ |
57fc2ff1 | 577 | ptr = (FileHandlerEntry *)ARR_GetElement(file_handlers, fd); |
a8167b79 ML |
578 | if (ptr->handler) |
579 | (ptr->handler)(fd, SCH_FILE_INPUT, ptr->arg); | |
57fc2ff1 | 580 | nfd--; |
88840341 RC |
581 | } |
582 | ||
57fc2ff1 ML |
583 | if (write_fds && FD_ISSET(fd, write_fds)) { |
584 | /* This descriptor can be written to, dispatch its handler */ | |
585 | ptr = (FileHandlerEntry *)ARR_GetElement(file_handlers, fd); | |
a8167b79 ML |
586 | if (ptr->handler) |
587 | (ptr->handler)(fd, SCH_FILE_OUTPUT, ptr->arg); | |
57fc2ff1 ML |
588 | nfd--; |
589 | } | |
88840341 | 590 | } |
88840341 RC |
591 | } |
592 | ||
593 | /* ================================================== */ | |
594 | ||
595 | static void | |
d0dfa1de ML |
596 | handle_slew(struct timespec *raw, |
597 | struct timespec *cooked, | |
88840341 | 598 | double dfreq, |
88840341 | 599 | double doffset, |
44c9744d | 600 | LCL_ChangeType change_type, |
88840341 RC |
601 | void *anything) |
602 | { | |
603 | TimerQueueEntry *ptr; | |
41805d57 | 604 | double delta; |
59c68d24 | 605 | int i; |
88840341 | 606 | |
44c9744d | 607 | if (change_type != LCL_ChangeAdjust) { |
0bdac2c7 ML |
608 | /* Make sure this handler is invoked first in order to not shift new timers |
609 | added from other handlers */ | |
610 | assert(LCL_IsFirstParameterChangeHandler(handle_slew)); | |
611 | ||
41805d57 | 612 | /* If a step change occurs, just shift all raw time stamps by the offset */ |
88840341 RC |
613 | |
614 | for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) { | |
d0dfa1de | 615 | UTI_AddDoubleToTimespec(&ptr->ts, -doffset, &ptr->ts); |
88840341 RC |
616 | } |
617 | ||
59c68d24 | 618 | for (i = 0; i < SCH_NumberOfClasses; i++) { |
d0dfa1de | 619 | UTI_AddDoubleToTimespec(&last_class_dispatch[i], -doffset, &last_class_dispatch[i]); |
59c68d24 | 620 | } |
0bf34725 | 621 | |
d0dfa1de | 622 | UTI_AddDoubleToTimespec(&last_select_ts_raw, -doffset, &last_select_ts_raw); |
88840341 | 623 | } |
41805d57 | 624 | |
d0dfa1de | 625 | UTI_AdjustTimespec(&last_select_ts, cooked, &last_select_ts, &delta, dfreq, doffset); |
88840341 RC |
626 | } |
627 | ||
628 | /* ================================================== */ | |
629 | ||
d8d096aa | 630 | static void |
c169ad3f | 631 | fill_fd_sets(fd_set **read_fds, fd_set **write_fds, fd_set **except_fds) |
d8d096aa ML |
632 | { |
633 | FileHandlerEntry *handlers; | |
c169ad3f | 634 | fd_set *rd, *wr, *ex; |
d8d096aa ML |
635 | int i, n, events; |
636 | ||
637 | n = ARR_GetSize(file_handlers); | |
638 | handlers = ARR_GetElements(file_handlers); | |
c169ad3f | 639 | rd = wr = ex = NULL; |
d8d096aa ML |
640 | |
641 | for (i = 0; i < n; i++) { | |
642 | events = handlers[i].events; | |
643 | ||
644 | if (!events) | |
645 | continue; | |
646 | ||
647 | if (events & SCH_FILE_INPUT) { | |
648 | if (!rd) { | |
649 | rd = *read_fds; | |
650 | FD_ZERO(rd); | |
651 | } | |
652 | FD_SET(i, rd); | |
653 | } | |
57fc2ff1 ML |
654 | |
655 | if (events & SCH_FILE_OUTPUT) { | |
656 | if (!wr) { | |
657 | wr = *write_fds; | |
658 | FD_ZERO(wr); | |
659 | } | |
660 | FD_SET(i, wr); | |
661 | } | |
c169ad3f ML |
662 | |
663 | if (events & SCH_FILE_EXCEPTION) { | |
664 | if (!ex) { | |
665 | ex = *except_fds; | |
666 | FD_ZERO(ex); | |
667 | } | |
668 | FD_SET(i, ex); | |
669 | } | |
d8d096aa ML |
670 | } |
671 | ||
672 | if (!rd) | |
673 | *read_fds = NULL; | |
57fc2ff1 ML |
674 | if (!wr) |
675 | *write_fds = NULL; | |
c169ad3f ML |
676 | if (!ex) |
677 | *except_fds = NULL; | |
d8d096aa ML |
678 | } |
679 | ||
680 | /* ================================================== */ | |
681 | ||
a3e60c93 | 682 | #define JUMP_DETECT_THRESHOLD 10 |
91749ebb | 683 | |
a3e60c93 | 684 | static int |
d0dfa1de | 685 | check_current_time(struct timespec *prev_raw, struct timespec *raw, int timeout, |
e63bd490 ML |
686 | struct timeval *orig_select_tv, |
687 | struct timeval *rem_select_tv) | |
91749ebb | 688 | { |
d0dfa1de | 689 | struct timespec elapsed_min, elapsed_max, orig_select_ts, rem_select_ts; |
e63bd490 | 690 | double step, elapsed; |
a3e60c93 | 691 | |
d0dfa1de ML |
692 | UTI_TimevalToTimespec(orig_select_tv, &orig_select_ts); |
693 | ||
e63bd490 ML |
694 | /* Get an estimate of the time spent waiting in the select() call. On some |
695 | systems (e.g. Linux) the timeout timeval is modified to return the | |
696 | remaining time, use that information. */ | |
697 | if (timeout) { | |
d0dfa1de | 698 | elapsed_max = elapsed_min = orig_select_ts; |
e63bd490 ML |
699 | } else if (rem_select_tv && rem_select_tv->tv_sec >= 0 && |
700 | rem_select_tv->tv_sec <= orig_select_tv->tv_sec && | |
701 | (rem_select_tv->tv_sec != orig_select_tv->tv_sec || | |
702 | rem_select_tv->tv_usec != orig_select_tv->tv_usec)) { | |
d0dfa1de ML |
703 | UTI_TimevalToTimespec(rem_select_tv, &rem_select_ts); |
704 | UTI_DiffTimespecs(&elapsed_min, &orig_select_ts, &rem_select_ts); | |
e63bd490 ML |
705 | elapsed_max = elapsed_min; |
706 | } else { | |
707 | if (rem_select_tv) | |
d0dfa1de | 708 | elapsed_max = orig_select_ts; |
e63bd490 | 709 | else |
d0dfa1de ML |
710 | UTI_DiffTimespecs(&elapsed_max, raw, prev_raw); |
711 | UTI_ZeroTimespec(&elapsed_min); | |
e63bd490 ML |
712 | } |
713 | ||
714 | if (last_select_ts_raw.tv_sec + elapsed_min.tv_sec > | |
715 | raw->tv_sec + JUMP_DETECT_THRESHOLD) { | |
f282856c | 716 | LOG(LOGS_WARN, "Backward time jump detected!"); |
e63bd490 ML |
717 | } else if (prev_raw->tv_sec + elapsed_max.tv_sec + JUMP_DETECT_THRESHOLD < |
718 | raw->tv_sec) { | |
f282856c | 719 | LOG(LOGS_WARN, "Forward time jump detected!"); |
a3e60c93 ML |
720 | } else { |
721 | return 1; | |
722 | } | |
91749ebb | 723 | |
cfe706f0 ML |
724 | step = UTI_DiffTimespecsToDouble(&last_select_ts_raw, raw); |
725 | elapsed = UTI_TimespecToDouble(&elapsed_min); | |
e63bd490 | 726 | step += elapsed; |
91749ebb | 727 | |
a3e60c93 | 728 | /* Cooked time may no longer be valid after dispatching the handlers */ |
e63bd490 | 729 | LCL_NotifyExternalTimeStep(raw, raw, step, fabs(step)); |
91749ebb | 730 | |
a3e60c93 | 731 | return 0; |
91749ebb ML |
732 | } |
733 | ||
734 | /* ================================================== */ | |
735 | ||
bb0553e4 ML |
736 | static void |
737 | update_monotonic_time(struct timespec *now, struct timespec *before) | |
738 | { | |
739 | struct timespec diff; | |
740 | ||
741 | /* Avoid frequent floating-point operations and handle small | |
742 | increments to a large value */ | |
743 | ||
744 | UTI_DiffTimespecs(&diff, now, before); | |
745 | if (diff.tv_sec == 0) { | |
746 | last_select_ts_mono_ns += diff.tv_nsec; | |
747 | } else { | |
748 | last_select_ts_mono += fabs(UTI_TimespecToDouble(&diff) + | |
749 | last_select_ts_mono_ns / 1.0e9); | |
750 | last_select_ts_mono_ns = 0; | |
751 | } | |
752 | ||
753 | if (last_select_ts_mono_ns > TS_MONO_PRECISION_NS) { | |
754 | last_select_ts_mono += last_select_ts_mono_ns / 1.0e9; | |
755 | last_select_ts_mono_ns = 0; | |
756 | } | |
757 | } | |
758 | ||
759 | /* ================================================== */ | |
760 | ||
88840341 RC |
761 | void |
762 | SCH_MainLoop(void) | |
763 | { | |
c169ad3f ML |
764 | fd_set read_fds, write_fds, except_fds; |
765 | fd_set *p_read_fds, *p_write_fds, *p_except_fds; | |
f7802f01 | 766 | int status, errsv; |
e63bd490 | 767 | struct timeval tv, saved_tv, *ptv; |
d0dfa1de | 768 | struct timespec ts, now, saved_now, cooked; |
91749ebb | 769 | double err; |
88840341 | 770 | |
6b0198c2 | 771 | assert(initialised); |
88840341 RC |
772 | |
773 | while (!need_to_exit) { | |
dce2366b ML |
774 | /* Dispatch timeouts and fill now with current raw time */ |
775 | dispatch_timeouts(&now); | |
e63bd490 | 776 | saved_now = now; |
88840341 | 777 | |
5cb7e6c9 ML |
778 | /* The timeout handlers may request quit */ |
779 | if (need_to_exit) | |
780 | break; | |
781 | ||
88840341 RC |
782 | /* Check whether there is a timeout and set it up */ |
783 | if (n_timer_queue_entries > 0) { | |
d0dfa1de ML |
784 | UTI_DiffTimespecs(&ts, &timer_queue.next->ts, &now); |
785 | assert(ts.tv_sec > 0 || ts.tv_nsec > 0); | |
88840341 | 786 | |
d0dfa1de | 787 | UTI_TimespecToTimeval(&ts, &tv); |
88840341 | 788 | ptv = &tv; |
e63bd490 | 789 | saved_tv = tv; |
88840341 RC |
790 | } else { |
791 | ptv = NULL; | |
1afb285a | 792 | saved_tv.tv_sec = saved_tv.tv_usec = 0; |
88840341 RC |
793 | } |
794 | ||
d8d096aa | 795 | p_read_fds = &read_fds; |
57fc2ff1 | 796 | p_write_fds = &write_fds; |
c169ad3f ML |
797 | p_except_fds = &except_fds; |
798 | fill_fd_sets(&p_read_fds, &p_write_fds, &p_except_fds); | |
d8d096aa | 799 | |
88840341 RC |
800 | /* if there are no file descriptors being waited on and no |
801 | timeout set, this is clearly ridiculous, so stop the run */ | |
57fc2ff1 | 802 | if (!ptv && !p_read_fds && !p_write_fds) |
f282856c | 803 | LOG_FATAL("Nothing to do"); |
5cb7e6c9 | 804 | |
c169ad3f | 805 | status = select(one_highest_fd, p_read_fds, p_write_fds, p_except_fds, ptv); |
f7802f01 | 806 | errsv = errno; |
88840341 | 807 | |
91749ebb ML |
808 | LCL_ReadRawTime(&now); |
809 | LCL_CookTime(&now, &cooked, &err); | |
810 | ||
f15f6a86 ML |
811 | update_monotonic_time(&now, &last_select_ts_raw); |
812 | ||
a3e60c93 | 813 | /* Check if the time didn't jump unexpectedly */ |
e63bd490 | 814 | if (!check_current_time(&saved_now, &now, status == 0, &saved_tv, ptv)) { |
a3e60c93 ML |
815 | /* Cook the time again after handling the step */ |
816 | LCL_CookTime(&now, &cooked, &err); | |
91749ebb ML |
817 | } |
818 | ||
819 | last_select_ts_raw = now; | |
820 | last_select_ts = cooked; | |
821 | last_select_ts_err = err; | |
822 | ||
88840341 | 823 | if (status < 0) { |
f7802f01 | 824 | if (!need_to_exit && errsv != EINTR) { |
f282856c | 825 | LOG_FATAL("select() failed : %s", strerror(errsv)); |
f7802f01 | 826 | } |
88840341 | 827 | } else if (status > 0) { |
57fc2ff1 | 828 | /* A file descriptor is ready for input or output */ |
c169ad3f | 829 | dispatch_filehandlers(status, p_read_fds, p_write_fds, p_except_fds); |
88840341 | 830 | } else { |
88840341 RC |
831 | /* No descriptors readable, timeout must have elapsed. |
832 | Therefore, tv must be non-null */ | |
6b0198c2 | 833 | assert(ptv); |
88840341 RC |
834 | |
835 | /* There's nothing to do here, since the timeouts | |
836 | will be dispatched at the top of the next loop | |
837 | cycle */ | |
838 | ||
839 | } | |
840 | } | |
88840341 RC |
841 | } |
842 | ||
843 | /* ================================================== */ | |
844 | ||
845 | void | |
846 | SCH_QuitProgram(void) | |
847 | { | |
88840341 RC |
848 | need_to_exit = 1; |
849 | } | |
850 | ||
851 | /* ================================================== */ | |
852 |