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.16 2020/02/16 21:08:15 florian Exp $ */ 18 19 /*! \file */ 20 21 22 #include <stdlib.h> 23 #include <isc/heap.h> 24 #include <isc/magic.h> 25 #include <isc/task.h> 26 #include <isc/time.h> 27 #include <isc/timer.h> 28 #include <isc/util.h> 29 30 #include "timer_p.h" 31 32 #define TIMER_MAGIC ISC_MAGIC('T', 'I', 'M', 'R') 33 #define VALID_TIMER(t) ISC_MAGIC_VALID(t, TIMER_MAGIC) 34 35 typedef struct isc__timer isc__timer_t; 36 typedef struct isc__timermgr isc__timermgr_t; 37 38 struct isc__timer { 39 /*! Not locked. */ 40 isc_timer_t common; 41 isc__timermgr_t * manager; 42 /*! Locked by timer lock. */ 43 unsigned int references; 44 struct timespec idle; 45 /*! Locked by manager lock. */ 46 struct timespec interval; 47 isc_task_t * task; 48 isc_taskaction_t action; 49 void * arg; 50 unsigned int index; 51 struct timespec due; 52 LINK(isc__timer_t) link; 53 }; 54 55 #define TIMER_MANAGER_MAGIC ISC_MAGIC('T', 'I', 'M', 'M') 56 #define VALID_MANAGER(m) ISC_MAGIC_VALID(m, TIMER_MANAGER_MAGIC) 57 58 struct isc__timermgr { 59 /* Not locked. */ 60 isc_timermgr_t common; 61 /* Locked by manager lock. */ 62 isc_boolean_t done; 63 LIST(isc__timer_t) timers; 64 unsigned int nscheduled; 65 struct timespec due; 66 unsigned int refs; 67 isc_heap_t * heap; 68 }; 69 70 /*% 71 * The following are intended for internal use (indicated by "isc__" 72 * prefix) but are not declared as static, allowing direct access from 73 * unit tests etc. 74 */ 75 76 isc_result_t 77 isc__timer_create(isc_timermgr_t *manager, const struct timespec *interval, 78 isc_task_t *task, isc_taskaction_t action, void *arg, 79 isc_timer_t **timerp); 80 isc_result_t 81 isc__timer_reset(isc_timer_t *timer, const struct timespec *interval, 82 isc_boolean_t purge); 83 isc_result_t 84 isc__timer_touch(isc_timer_t *timer); 85 void 86 isc__timer_attach(isc_timer_t *timer0, isc_timer_t **timerp); 87 void 88 isc__timer_detach(isc_timer_t **timerp); 89 isc_result_t 90 isc__timermgr_create(isc_timermgr_t **managerp); 91 void 92 isc__timermgr_destroy(isc_timermgr_t **managerp); 93 94 /*! 95 * If the manager is supposed to be shared, there can be only one. 96 */ 97 static isc__timermgr_t *timermgr = NULL; 98 99 static inline isc_result_t 100 schedule(isc__timer_t *timer, struct timespec *now, isc_boolean_t signal_ok) { 101 isc_result_t result; 102 isc__timermgr_t *manager; 103 struct timespec due; 104 105 /*! 106 * Note: the caller must ensure locking. 107 */ 108 109 UNUSED(signal_ok); 110 111 manager = timer->manager; 112 113 /* 114 * Compute the new due time. 115 */ 116 due = timer->idle; 117 118 /* 119 * Schedule the timer. 120 */ 121 122 if (timer->index > 0) { 123 /* 124 * Already scheduled. 125 */ 126 if (timespeccmp(&due, &timer->due, <)) 127 isc_heap_increased(manager->heap, timer->index); 128 else if (timespeccmp(&due, &timer->due, >)) 129 isc_heap_decreased(manager->heap, timer->index); 130 131 timer->due = due; 132 } else { 133 timer->due = due; 134 result = isc_heap_insert(manager->heap, timer); 135 if (result != ISC_R_SUCCESS) { 136 INSIST(result == ISC_R_NOMEMORY); 137 return (ISC_R_NOMEMORY); 138 } 139 manager->nscheduled++; 140 } 141 142 /* 143 * If this timer is at the head of the queue, we need to ensure 144 * that we won't miss it if it has a more recent due time than 145 * the current "next" timer. We do this either by waking up the 146 * run thread, or explicitly setting the value in the manager. 147 */ 148 if (timer->index == 1 && timespeccmp(&timer->due, &manager->due, <)) 149 manager->due = timer->due; 150 151 return (ISC_R_SUCCESS); 152 } 153 154 static inline void 155 deschedule(isc__timer_t *timer) { 156 isc__timermgr_t *manager; 157 158 /* 159 * The caller must ensure locking. 160 */ 161 162 manager = timer->manager; 163 if (timer->index > 0) { 164 isc_heap_delete(manager->heap, timer->index); 165 timer->index = 0; 166 INSIST(manager->nscheduled > 0); 167 manager->nscheduled--; 168 } 169 } 170 171 static void 172 destroy(isc__timer_t *timer) { 173 isc__timermgr_t *manager = timer->manager; 174 175 /* 176 * The caller must ensure it is safe to destroy the timer. 177 */ 178 179 (void)isc_task_purgerange(timer->task, 180 timer, 181 ISC_TIMEREVENT_FIRSTEVENT, 182 ISC_TIMEREVENT_LASTEVENT, 183 NULL); 184 deschedule(timer); 185 UNLINK(manager->timers, timer, link); 186 187 isc_task_detach(&timer->task); 188 timer->common.impmagic = 0; 189 timer->common.magic = 0; 190 free(timer); 191 } 192 193 isc_result_t 194 isc__timer_create(isc_timermgr_t *manager0, const struct timespec *interval, 195 isc_task_t *task, isc_taskaction_t action, void *arg, 196 isc_timer_t **timerp) 197 { 198 isc__timermgr_t *manager = (isc__timermgr_t *)manager0; 199 isc__timer_t *timer; 200 isc_result_t result; 201 struct timespec now; 202 203 /* 204 * Create a new 'type' timer managed by 'manager'. The timers 205 * parameters are specified by 'interval'. Events 206 * will be posted to 'task' and when dispatched 'action' will be 207 * called with 'arg' as the arg value. The new timer is returned 208 * in 'timerp'. 209 */ 210 211 REQUIRE(VALID_MANAGER(manager)); 212 REQUIRE(task != NULL); 213 REQUIRE(action != NULL); 214 REQUIRE(interval != NULL); 215 REQUIRE(timespecisset(interval)); 216 REQUIRE(timerp != NULL && *timerp == NULL); 217 218 /* 219 * Get current time. 220 */ 221 TIME_NOW(&now); 222 223 timer = malloc(sizeof(*timer)); 224 if (timer == NULL) 225 return (ISC_R_NOMEMORY); 226 227 timer->manager = manager; 228 timer->references = 1; 229 230 if (timespecisset(interval)) { 231 result = isc_time_add(&now, interval, &timer->idle); 232 if (result != ISC_R_SUCCESS) { 233 free(timer); 234 return (result); 235 } 236 } 237 238 timer->interval = *interval; 239 timer->task = NULL; 240 isc_task_attach(task, &timer->task); 241 timer->action = action; 242 /* 243 * Removing the const attribute from "arg" is the best of two 244 * evils here. If the timer->arg member is made const, then 245 * it affects a great many recipients of the timer event 246 * which did not pass in an "arg" that was truly const. 247 * Changing isc_timer_create() to not have "arg" prototyped as const, 248 * though, can cause compilers warnings for calls that *do* 249 * have a truly const arg. The caller will have to carefully 250 * keep track of whether arg started as a true const. 251 */ 252 DE_CONST(arg, timer->arg); 253 timer->index = 0; 254 ISC_LINK_INIT(timer, link); 255 timer->common.impmagic = TIMER_MAGIC; 256 timer->common.magic = ISCAPI_TIMER_MAGIC; 257 258 result = schedule(timer, &now, ISC_TRUE); 259 if (result == ISC_R_SUCCESS) 260 APPEND(manager->timers, timer, link); 261 262 if (result != ISC_R_SUCCESS) { 263 timer->common.impmagic = 0; 264 timer->common.magic = 0; 265 isc_task_detach(&timer->task); 266 free(timer); 267 return (result); 268 } 269 270 *timerp = (isc_timer_t *)timer; 271 272 return (ISC_R_SUCCESS); 273 } 274 275 isc_result_t 276 isc__timer_reset(isc_timer_t *timer0, const struct timespec *interval, 277 isc_boolean_t purge) 278 { 279 isc__timer_t *timer = (isc__timer_t *)timer0; 280 struct timespec now; 281 isc__timermgr_t *manager; 282 isc_result_t result; 283 284 /* 285 * Change the timer's type, expires, and interval values to the given 286 * values. If 'purge' is ISC_TRUE, any pending events from this timer 287 * are purged from its task's event queue. 288 */ 289 290 REQUIRE(VALID_TIMER(timer)); 291 manager = timer->manager; 292 REQUIRE(VALID_MANAGER(manager)); 293 REQUIRE(interval != NULL); 294 REQUIRE(timespecisset(interval)); 295 296 /* 297 * Get current time. 298 */ 299 TIME_NOW(&now); 300 301 if (purge) 302 (void)isc_task_purgerange(timer->task, 303 timer, 304 ISC_TIMEREVENT_FIRSTEVENT, 305 ISC_TIMEREVENT_LASTEVENT, 306 NULL); 307 timer->interval = *interval; 308 if (timespecisset(interval)) { 309 result = isc_time_add(&now, interval, &timer->idle); 310 } else { 311 timespecclear(&timer->idle); 312 result = ISC_R_SUCCESS; 313 } 314 315 if (result == ISC_R_SUCCESS) { 316 result = schedule(timer, &now, ISC_TRUE); 317 } 318 319 return (result); 320 } 321 322 isc_result_t 323 isc__timer_touch(isc_timer_t *timer0) { 324 isc__timer_t *timer = (isc__timer_t *)timer0; 325 isc_result_t result; 326 struct timespec now; 327 328 /* 329 * Set the last-touched time of 'timer' to the current time. 330 */ 331 332 REQUIRE(VALID_TIMER(timer)); 333 334 TIME_NOW(&now); 335 result = isc_time_add(&now, &timer->interval, &timer->idle); 336 337 return (result); 338 } 339 340 void 341 isc__timer_attach(isc_timer_t *timer0, isc_timer_t **timerp) { 342 isc__timer_t *timer = (isc__timer_t *)timer0; 343 344 /* 345 * Attach *timerp to timer. 346 */ 347 348 REQUIRE(VALID_TIMER(timer)); 349 REQUIRE(timerp != NULL && *timerp == NULL); 350 351 timer->references++; 352 353 *timerp = (isc_timer_t *)timer; 354 } 355 356 void 357 isc__timer_detach(isc_timer_t **timerp) { 358 isc__timer_t *timer; 359 isc_boolean_t free_timer = ISC_FALSE; 360 361 /* 362 * Detach *timerp from its timer. 363 */ 364 365 REQUIRE(timerp != NULL); 366 timer = (isc__timer_t *)*timerp; 367 REQUIRE(VALID_TIMER(timer)); 368 369 REQUIRE(timer->references > 0); 370 timer->references--; 371 if (timer->references == 0) 372 free_timer = ISC_TRUE; 373 374 if (free_timer) 375 destroy(timer); 376 377 *timerp = NULL; 378 } 379 380 static void 381 dispatch(isc__timermgr_t *manager, struct timespec *now) { 382 isc_boolean_t done = ISC_FALSE, post_event, need_schedule; 383 isc_timerevent_t *event; 384 isc_eventtype_t type = 0; 385 isc__timer_t *timer; 386 isc_result_t result; 387 isc_boolean_t idle; 388 389 /*! 390 * The caller must be holding the manager lock. 391 */ 392 393 while (manager->nscheduled > 0 && !done) { 394 timer = isc_heap_element(manager->heap, 1); 395 INSIST(timer != NULL); 396 if (timespeccmp(now, &timer->due, >=)) { 397 idle = ISC_FALSE; 398 399 if (timespecisset(&timer->idle) && timespeccmp(now, 400 &timer->idle, >=)) { 401 idle = ISC_TRUE; 402 } 403 if (idle) { 404 type = ISC_TIMEREVENT_IDLE; 405 post_event = ISC_TRUE; 406 need_schedule = ISC_FALSE; 407 } else { 408 /* 409 * Idle timer has been touched; 410 * reschedule. 411 */ 412 post_event = ISC_FALSE; 413 need_schedule = ISC_TRUE; 414 } 415 416 if (post_event) { 417 /* 418 * XXX We could preallocate this event. 419 */ 420 event = (isc_timerevent_t *)isc_event_allocate( 421 timer, 422 type, 423 timer->action, 424 timer->arg, 425 sizeof(*event)); 426 427 if (event != NULL) { 428 event->due = timer->due; 429 isc_task_send(timer->task, 430 ISC_EVENT_PTR(&event)); 431 } else 432 UNEXPECTED_ERROR(__FILE__, __LINE__, "%s", 433 "couldn't allocate event"); 434 } 435 436 timer->index = 0; 437 isc_heap_delete(manager->heap, 1); 438 manager->nscheduled--; 439 440 if (need_schedule) { 441 result = schedule(timer, now, ISC_FALSE); 442 if (result != ISC_R_SUCCESS) 443 UNEXPECTED_ERROR(__FILE__, __LINE__, 444 "%s: %u", 445 "couldn't schedule timer", 446 result); 447 } 448 } else { 449 manager->due = timer->due; 450 done = ISC_TRUE; 451 } 452 } 453 } 454 455 static isc_boolean_t 456 sooner(void *v1, void *v2) { 457 isc__timer_t *t1, *t2; 458 459 t1 = v1; 460 t2 = v2; 461 REQUIRE(VALID_TIMER(t1)); 462 REQUIRE(VALID_TIMER(t2)); 463 464 if (timespeccmp(&t1->due, &t2->due, <)) 465 return (ISC_TRUE); 466 return (ISC_FALSE); 467 } 468 469 static void 470 set_index(void *what, unsigned int index) { 471 isc__timer_t *timer; 472 473 timer = what; 474 REQUIRE(VALID_TIMER(timer)); 475 476 timer->index = index; 477 } 478 479 isc_result_t 480 isc__timermgr_create(isc_timermgr_t **managerp) { 481 isc__timermgr_t *manager; 482 isc_result_t result; 483 484 /* 485 * Create a timer manager. 486 */ 487 488 REQUIRE(managerp != NULL && *managerp == NULL); 489 490 if (timermgr != NULL) { 491 timermgr->refs++; 492 *managerp = (isc_timermgr_t *)timermgr; 493 return (ISC_R_SUCCESS); 494 } 495 496 manager = malloc(sizeof(*manager)); 497 if (manager == NULL) 498 return (ISC_R_NOMEMORY); 499 500 manager->common.impmagic = TIMER_MANAGER_MAGIC; 501 manager->common.magic = ISCAPI_TIMERMGR_MAGIC; 502 manager->done = ISC_FALSE; 503 INIT_LIST(manager->timers); 504 manager->nscheduled = 0; 505 timespecclear(&manager->due); 506 manager->heap = NULL; 507 result = isc_heap_create(sooner, set_index, 0, &manager->heap); 508 if (result != ISC_R_SUCCESS) { 509 INSIST(result == ISC_R_NOMEMORY); 510 free(manager); 511 return (ISC_R_NOMEMORY); 512 } 513 manager->refs = 1; 514 timermgr = manager; 515 516 *managerp = (isc_timermgr_t *)manager; 517 518 return (ISC_R_SUCCESS); 519 } 520 521 void 522 isc__timermgr_destroy(isc_timermgr_t **managerp) { 523 isc__timermgr_t *manager; 524 525 /* 526 * Destroy a timer manager. 527 */ 528 529 REQUIRE(managerp != NULL); 530 manager = (isc__timermgr_t *)*managerp; 531 REQUIRE(VALID_MANAGER(manager)); 532 533 manager->refs--; 534 if (manager->refs > 0) { 535 *managerp = NULL; 536 return; 537 } 538 timermgr = NULL; 539 540 isc__timermgr_dispatch((isc_timermgr_t *)manager); 541 542 REQUIRE(EMPTY(manager->timers)); 543 manager->done = ISC_TRUE; 544 545 /* 546 * Clean up. 547 */ 548 isc_heap_destroy(&manager->heap); 549 manager->common.impmagic = 0; 550 manager->common.magic = 0; 551 free(manager); 552 553 *managerp = NULL; 554 555 timermgr = NULL; 556 } 557 558 isc_result_t 559 isc__timermgr_nextevent(isc_timermgr_t *manager0, struct timespec *when) { 560 isc__timermgr_t *manager = (isc__timermgr_t *)manager0; 561 562 if (manager == NULL) 563 manager = timermgr; 564 if (manager == NULL || manager->nscheduled == 0) 565 return (ISC_R_NOTFOUND); 566 *when = manager->due; 567 return (ISC_R_SUCCESS); 568 } 569 570 void 571 isc__timermgr_dispatch(isc_timermgr_t *manager0) { 572 isc__timermgr_t *manager = (isc__timermgr_t *)manager0; 573 struct timespec now; 574 575 if (manager == NULL) 576 manager = timermgr; 577 if (manager == NULL) 578 return; 579 TIME_NOW(&now); 580 dispatch(manager, &now); 581 } 582 583 isc_result_t 584 isc_timermgr_create(isc_timermgr_t **managerp) { 585 return (isc__timermgr_create(managerp)); 586 } 587 588 void 589 isc_timermgr_destroy(isc_timermgr_t **managerp) { 590 REQUIRE(*managerp != NULL && ISCAPI_TIMERMGR_VALID(*managerp)); 591 592 isc__timermgr_destroy(managerp); 593 594 ENSURE(*managerp == NULL); 595 } 596 597 isc_result_t 598 isc_timer_create(isc_timermgr_t *manager, const struct timespec *interval, 599 isc_task_t *task, isc_taskaction_t action, void *arg, 600 isc_timer_t **timerp) 601 { 602 REQUIRE(ISCAPI_TIMERMGR_VALID(manager)); 603 604 return (isc__timer_create(manager, interval, 605 task, action, arg, timerp)); 606 } 607 608 void 609 isc_timer_detach(isc_timer_t **timerp) { 610 REQUIRE(timerp != NULL && ISCAPI_TIMER_VALID(*timerp)); 611 612 isc__timer_detach(timerp); 613 614 ENSURE(*timerp == NULL); 615 } 616 617 isc_result_t 618 isc_timer_reset(isc_timer_t *timer, const struct timespec *interval, 619 isc_boolean_t purge) 620 { 621 REQUIRE(ISCAPI_TIMER_VALID(timer)); 622 623 return (isc__timer_reset(timer, interval, purge)); 624 } 625 626 isc_result_t 627 isc_timer_touch(isc_timer_t *timer) { 628 REQUIRE(ISCAPI_TIMER_VALID(timer)); 629 630 return (isc__timer_touch(timer)); 631 } 632