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