1 /* 2 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 9 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 11 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 13 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 * PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 /* $Id: timer.c,v 1.25 2020/09/14 08:40:44 florian Exp $ */ 18 19 /*! \file */ 20 21 #include <sys/time.h> 22 #include <stdlib.h> 23 #include <time.h> 24 #include <isc/heap.h> 25 #include <isc/task.h> 26 #include <isc/timer.h> 27 #include <isc/util.h> 28 29 #include "timer_p.h" 30 31 struct isc_timer { 32 /*! Not locked. */ 33 isc_timermgr_t * manager; 34 /*! Locked by timer lock. */ 35 unsigned int references; 36 struct timespec idle; 37 /*! Locked by manager lock. */ 38 struct timespec interval; 39 isc_task_t * task; 40 isc_taskaction_t action; 41 void * arg; 42 unsigned int index; 43 struct timespec due; 44 LINK(isc_timer_t) link; 45 }; 46 47 struct isc_timermgr { 48 /* Not locked. */ 49 /* Locked by manager lock. */ 50 int done; 51 LIST(isc_timer_t) timers; 52 unsigned int nscheduled; 53 struct timespec due; 54 unsigned int refs; 55 isc_heap_t * heap; 56 }; 57 58 /*% 59 * The following are intended for internal use (indicated by "isc__" 60 * prefix) but are not declared as static, allowing direct access from 61 * unit tests etc. 62 */ 63 64 /*! 65 * If the manager is supposed to be shared, there can be only one. 66 */ 67 static isc_timermgr_t *timermgr = NULL; 68 69 static inline isc_result_t 70 schedule(isc_timer_t *timer) { 71 isc_result_t result; 72 isc_timermgr_t *manager; 73 struct timespec due; 74 75 /*! 76 * Note: the caller must ensure locking. 77 */ 78 79 manager = timer->manager; 80 81 /* 82 * Compute the new due time. 83 */ 84 due = timer->idle; 85 86 /* 87 * Schedule the timer. 88 */ 89 90 if (timer->index > 0) { 91 /* 92 * Already scheduled. 93 */ 94 if (timespeccmp(&due, &timer->due, <)) 95 isc_heap_increased(manager->heap, timer->index); 96 else if (timespeccmp(&due, &timer->due, >)) 97 isc_heap_decreased(manager->heap, timer->index); 98 99 timer->due = due; 100 } else { 101 timer->due = due; 102 result = isc_heap_insert(manager->heap, timer); 103 if (result != ISC_R_SUCCESS) { 104 INSIST(result == ISC_R_NOMEMORY); 105 return (ISC_R_NOMEMORY); 106 } 107 manager->nscheduled++; 108 } 109 110 /* 111 * If this timer is at the head of the queue, we need to ensure 112 * that we won't miss it if it has a more recent due time than 113 * the current "next" timer. We do this either by waking up the 114 * run thread, or explicitly setting the value in the manager. 115 */ 116 if (timer->index == 1 && timespeccmp(&timer->due, &manager->due, <)) 117 manager->due = timer->due; 118 119 return (ISC_R_SUCCESS); 120 } 121 122 static inline void 123 deschedule(isc_timer_t *timer) { 124 isc_timermgr_t *manager; 125 126 /* 127 * The caller must ensure locking. 128 */ 129 130 manager = timer->manager; 131 if (timer->index > 0) { 132 isc_heap_delete(manager->heap, timer->index); 133 timer->index = 0; 134 INSIST(manager->nscheduled > 0); 135 manager->nscheduled--; 136 } 137 } 138 139 static void 140 destroy(isc_timer_t *timer) { 141 isc_timermgr_t *manager = timer->manager; 142 143 /* 144 * The caller must ensure it is safe to destroy the timer. 145 */ 146 147 (void)isc_task_purgerange(timer->task, 148 timer, 149 ISC_TIMEREVENT_FIRSTEVENT, 150 ISC_TIMEREVENT_LASTEVENT, 151 NULL); 152 deschedule(timer); 153 UNLINK(manager->timers, timer, link); 154 155 isc_task_detach(&timer->task); 156 free(timer); 157 } 158 159 isc_result_t 160 isc_timer_create(isc_timermgr_t *manager0, const struct timespec *interval, 161 isc_task_t *task, isc_taskaction_t action, void *arg, 162 isc_timer_t **timerp) 163 { 164 isc_timermgr_t *manager = (isc_timermgr_t *)manager0; 165 isc_timer_t *timer; 166 isc_result_t result; 167 struct timespec now; 168 169 /* 170 * Create a new 'type' timer managed by 'manager'. The timers 171 * parameters are specified by 'interval'. Events 172 * will be posted to 'task' and when dispatched 'action' will be 173 * called with 'arg' as the arg value. The new timer is returned 174 * in 'timerp'. 175 */ 176 177 REQUIRE(task != NULL); 178 REQUIRE(action != NULL); 179 REQUIRE(interval != NULL); 180 REQUIRE(timespecisset(interval)); 181 REQUIRE(timerp != NULL && *timerp == NULL); 182 183 /* 184 * Get current time. 185 */ 186 clock_gettime(CLOCK_MONOTONIC, &now); 187 188 timer = malloc(sizeof(*timer)); 189 if (timer == NULL) 190 return (ISC_R_NOMEMORY); 191 192 timer->manager = manager; 193 timer->references = 1; 194 195 if (timespecisset(interval)) 196 timespecadd(&now, interval, &timer->idle); 197 198 timer->interval = *interval; 199 timer->task = NULL; 200 isc_task_attach(task, &timer->task); 201 timer->action = action; 202 /* 203 * Removing the const attribute from "arg" is the best of two 204 * evils here. If the timer->arg member is made const, then 205 * it affects a great many recipients of the timer event 206 * which did not pass in an "arg" that was truly const. 207 * Changing isc_timer_create() to not have "arg" prototyped as const, 208 * though, can cause compilers warnings for calls that *do* 209 * have a truly const arg. The caller will have to carefully 210 * keep track of whether arg started as a true const. 211 */ 212 DE_CONST(arg, timer->arg); 213 timer->index = 0; 214 ISC_LINK_INIT(timer, link); 215 216 result = schedule(timer); 217 if (result == ISC_R_SUCCESS) 218 APPEND(manager->timers, timer, link); 219 220 if (result != ISC_R_SUCCESS) { 221 isc_task_detach(&timer->task); 222 free(timer); 223 return (result); 224 } 225 226 *timerp = (isc_timer_t *)timer; 227 228 return (ISC_R_SUCCESS); 229 } 230 231 isc_result_t 232 isc_timer_reset(isc_timer_t *timer, const struct timespec *interval, 233 int purge) 234 { 235 struct timespec now; 236 isc_result_t result; 237 238 /* 239 * Change the timer's type, expires, and interval values to the given 240 * values. If 'purge' is 1, any pending events from this timer 241 * are purged from its task's event queue. 242 */ 243 244 REQUIRE(interval != NULL); 245 REQUIRE(timespecisset(interval)); 246 247 /* 248 * Get current time. 249 */ 250 clock_gettime(CLOCK_MONOTONIC, &now); 251 252 if (purge) 253 (void)isc_task_purgerange(timer->task, 254 timer, 255 ISC_TIMEREVENT_FIRSTEVENT, 256 ISC_TIMEREVENT_LASTEVENT, 257 NULL); 258 timer->interval = *interval; 259 if (timespecisset(interval)) { 260 timespecadd(&now, interval, &timer->idle); 261 } else { 262 timespecclear(&timer->idle); 263 } 264 265 result = schedule(timer); 266 267 return (result); 268 } 269 270 void 271 isc_timer_touch(isc_timer_t *timer) { 272 struct timespec now; 273 274 /* 275 * Set the last-touched time of 'timer' to the current time. 276 */ 277 278 clock_gettime(CLOCK_MONOTONIC, &now); 279 timespecadd(&now, &timer->interval, &timer->idle); 280 } 281 282 void 283 isc_timer_detach(isc_timer_t **timerp) { 284 isc_timer_t *timer; 285 int free_timer = 0; 286 287 /* 288 * Detach *timerp from its timer. 289 */ 290 291 REQUIRE(timerp != NULL); 292 timer = (isc_timer_t *)*timerp; 293 294 REQUIRE(timer->references > 0); 295 timer->references--; 296 if (timer->references == 0) 297 free_timer = 1; 298 299 if (free_timer) 300 destroy(timer); 301 302 *timerp = NULL; 303 } 304 305 static void 306 dispatch(isc_timermgr_t *manager, struct timespec *now) { 307 int done = 0, post_event, need_schedule; 308 isc_timerevent_t *event; 309 isc_eventtype_t type = 0; 310 isc_timer_t *timer; 311 isc_result_t result; 312 int idle; 313 314 /*! 315 * The caller must be holding the manager lock. 316 */ 317 318 while (manager->nscheduled > 0 && !done) { 319 timer = isc_heap_element(manager->heap, 1); 320 INSIST(timer != NULL); 321 if (timespeccmp(now, &timer->due, >=)) { 322 idle = 0; 323 324 if (timespecisset(&timer->idle) && timespeccmp(now, 325 &timer->idle, >=)) { 326 idle = 1; 327 } 328 if (idle) { 329 type = ISC_TIMEREVENT_IDLE; 330 post_event = 1; 331 need_schedule = 0; 332 } else { 333 /* 334 * Idle timer has been touched; 335 * reschedule. 336 */ 337 post_event = 0; 338 need_schedule = 1; 339 } 340 341 if (post_event) { 342 /* 343 * XXX We could preallocate this event. 344 */ 345 event = (isc_timerevent_t *)isc_event_allocate( 346 timer, 347 type, 348 timer->action, 349 timer->arg, 350 sizeof(*event)); 351 352 if (event != NULL) { 353 event->due = timer->due; 354 isc_task_send(timer->task, 355 ISC_EVENT_PTR(&event)); 356 } else 357 UNEXPECTED_ERROR(__FILE__, __LINE__, "%s", 358 "couldn't allocate event"); 359 } 360 361 timer->index = 0; 362 isc_heap_delete(manager->heap, 1); 363 manager->nscheduled--; 364 365 if (need_schedule) { 366 result = schedule(timer); 367 if (result != ISC_R_SUCCESS) 368 UNEXPECTED_ERROR(__FILE__, __LINE__, 369 "%s: %u", 370 "couldn't schedule timer", 371 result); 372 } 373 } else { 374 manager->due = timer->due; 375 done = 1; 376 } 377 } 378 } 379 380 static int 381 sooner(void *v1, void *v2) { 382 isc_timer_t *t1, *t2; 383 384 t1 = v1; 385 t2 = v2; 386 387 if (timespeccmp(&t1->due, &t2->due, <)) 388 return (1); 389 return (0); 390 } 391 392 static void 393 set_index(void *what, unsigned int index) { 394 isc_timer_t *timer; 395 396 timer = what; 397 398 timer->index = index; 399 } 400 401 isc_result_t 402 isc_timermgr_create(isc_timermgr_t **managerp) { 403 isc_timermgr_t *manager; 404 isc_result_t result; 405 406 /* 407 * Create a timer manager. 408 */ 409 410 REQUIRE(managerp != NULL && *managerp == NULL); 411 412 if (timermgr != NULL) { 413 timermgr->refs++; 414 *managerp = (isc_timermgr_t *)timermgr; 415 return (ISC_R_SUCCESS); 416 } 417 418 manager = malloc(sizeof(*manager)); 419 if (manager == NULL) 420 return (ISC_R_NOMEMORY); 421 422 manager->done = 0; 423 INIT_LIST(manager->timers); 424 manager->nscheduled = 0; 425 timespecclear(&manager->due); 426 manager->heap = NULL; 427 result = isc_heap_create(sooner, set_index, 0, &manager->heap); 428 if (result != ISC_R_SUCCESS) { 429 INSIST(result == ISC_R_NOMEMORY); 430 free(manager); 431 return (ISC_R_NOMEMORY); 432 } 433 manager->refs = 1; 434 timermgr = manager; 435 436 *managerp = (isc_timermgr_t *)manager; 437 438 return (ISC_R_SUCCESS); 439 } 440 441 void 442 isc_timermgr_destroy(isc_timermgr_t **managerp) { 443 isc_timermgr_t *manager; 444 445 /* 446 * Destroy a timer manager. 447 */ 448 449 REQUIRE(managerp != NULL); 450 manager = (isc_timermgr_t *)*managerp; 451 452 manager->refs--; 453 if (manager->refs > 0) { 454 *managerp = NULL; 455 return; 456 } 457 timermgr = NULL; 458 459 isc_timermgr_dispatch((isc_timermgr_t *)manager); 460 461 REQUIRE(EMPTY(manager->timers)); 462 manager->done = 1; 463 464 /* 465 * Clean up. 466 */ 467 isc_heap_destroy(&manager->heap); 468 free(manager); 469 470 *managerp = NULL; 471 472 timermgr = NULL; 473 } 474 475 isc_result_t 476 isc_timermgr_nextevent(isc_timermgr_t *manager0, struct timespec *when) { 477 isc_timermgr_t *manager = (isc_timermgr_t *)manager0; 478 479 if (manager == NULL) 480 manager = timermgr; 481 if (manager == NULL || manager->nscheduled == 0) 482 return (ISC_R_NOTFOUND); 483 *when = manager->due; 484 return (ISC_R_SUCCESS); 485 } 486 487 void 488 isc_timermgr_dispatch(isc_timermgr_t *manager0) { 489 isc_timermgr_t *manager = (isc_timermgr_t *)manager0; 490 struct timespec now; 491 492 if (manager == NULL) 493 manager = timermgr; 494 if (manager == NULL) 495 return; 496 clock_gettime(CLOCK_MONOTONIC, &now); 497 dispatch(manager, &now); 498 } 499 500