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.22 2020/02/24 00:17:44 aoyama 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 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 isc_boolean_t 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 isc_boolean_t 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 ISC_TRUE, 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 279 clock_gettime(CLOCK_MONOTONIC, &now); 280 timespecadd(&now, &timer->interval, &timer->idle); 281 } 282 283 void 284 isc_timer_detach(isc_timer_t **timerp) { 285 isc_timer_t *timer; 286 isc_boolean_t free_timer = ISC_FALSE; 287 288 /* 289 * Detach *timerp from its timer. 290 */ 291 292 REQUIRE(timerp != NULL); 293 timer = (isc_timer_t *)*timerp; 294 295 REQUIRE(timer->references > 0); 296 timer->references--; 297 if (timer->references == 0) 298 free_timer = ISC_TRUE; 299 300 if (free_timer) 301 destroy(timer); 302 303 *timerp = NULL; 304 } 305 306 static void 307 dispatch(isc_timermgr_t *manager, struct timespec *now) { 308 isc_boolean_t done = ISC_FALSE, post_event, need_schedule; 309 isc_timerevent_t *event; 310 isc_eventtype_t type = 0; 311 isc_timer_t *timer; 312 isc_result_t result; 313 isc_boolean_t idle; 314 315 /*! 316 * The caller must be holding the manager lock. 317 */ 318 319 while (manager->nscheduled > 0 && !done) { 320 timer = isc_heap_element(manager->heap, 1); 321 INSIST(timer != NULL); 322 if (timespeccmp(now, &timer->due, >=)) { 323 idle = ISC_FALSE; 324 325 if (timespecisset(&timer->idle) && timespeccmp(now, 326 &timer->idle, >=)) { 327 idle = ISC_TRUE; 328 } 329 if (idle) { 330 type = ISC_TIMEREVENT_IDLE; 331 post_event = ISC_TRUE; 332 need_schedule = ISC_FALSE; 333 } else { 334 /* 335 * Idle timer has been touched; 336 * reschedule. 337 */ 338 post_event = ISC_FALSE; 339 need_schedule = ISC_TRUE; 340 } 341 342 if (post_event) { 343 /* 344 * XXX We could preallocate this event. 345 */ 346 event = (isc_timerevent_t *)isc_event_allocate( 347 timer, 348 type, 349 timer->action, 350 timer->arg, 351 sizeof(*event)); 352 353 if (event != NULL) { 354 event->due = timer->due; 355 isc_task_send(timer->task, 356 ISC_EVENT_PTR(&event)); 357 } else 358 UNEXPECTED_ERROR(__FILE__, __LINE__, "%s", 359 "couldn't allocate event"); 360 } 361 362 timer->index = 0; 363 isc_heap_delete(manager->heap, 1); 364 manager->nscheduled--; 365 366 if (need_schedule) { 367 result = schedule(timer); 368 if (result != ISC_R_SUCCESS) 369 UNEXPECTED_ERROR(__FILE__, __LINE__, 370 "%s: %u", 371 "couldn't schedule timer", 372 result); 373 } 374 } else { 375 manager->due = timer->due; 376 done = ISC_TRUE; 377 } 378 } 379 } 380 381 static isc_boolean_t 382 sooner(void *v1, void *v2) { 383 isc_timer_t *t1, *t2; 384 385 t1 = v1; 386 t2 = v2; 387 388 if (timespeccmp(&t1->due, &t2->due, <)) 389 return (ISC_TRUE); 390 return (ISC_FALSE); 391 } 392 393 static void 394 set_index(void *what, unsigned int index) { 395 isc_timer_t *timer; 396 397 timer = what; 398 399 timer->index = index; 400 } 401 402 isc_result_t 403 isc_timermgr_create(isc_timermgr_t **managerp) { 404 isc_timermgr_t *manager; 405 isc_result_t result; 406 407 /* 408 * Create a timer manager. 409 */ 410 411 REQUIRE(managerp != NULL && *managerp == NULL); 412 413 if (timermgr != NULL) { 414 timermgr->refs++; 415 *managerp = (isc_timermgr_t *)timermgr; 416 return (ISC_R_SUCCESS); 417 } 418 419 manager = malloc(sizeof(*manager)); 420 if (manager == NULL) 421 return (ISC_R_NOMEMORY); 422 423 manager->done = ISC_FALSE; 424 INIT_LIST(manager->timers); 425 manager->nscheduled = 0; 426 timespecclear(&manager->due); 427 manager->heap = NULL; 428 result = isc_heap_create(sooner, set_index, 0, &manager->heap); 429 if (result != ISC_R_SUCCESS) { 430 INSIST(result == ISC_R_NOMEMORY); 431 free(manager); 432 return (ISC_R_NOMEMORY); 433 } 434 manager->refs = 1; 435 timermgr = manager; 436 437 *managerp = (isc_timermgr_t *)manager; 438 439 return (ISC_R_SUCCESS); 440 } 441 442 void 443 isc_timermgr_destroy(isc_timermgr_t **managerp) { 444 isc_timermgr_t *manager; 445 446 /* 447 * Destroy a timer manager. 448 */ 449 450 REQUIRE(managerp != NULL); 451 manager = (isc_timermgr_t *)*managerp; 452 453 manager->refs--; 454 if (manager->refs > 0) { 455 *managerp = NULL; 456 return; 457 } 458 timermgr = NULL; 459 460 isc_timermgr_dispatch((isc_timermgr_t *)manager); 461 462 REQUIRE(EMPTY(manager->timers)); 463 manager->done = ISC_TRUE; 464 465 /* 466 * Clean up. 467 */ 468 isc_heap_destroy(&manager->heap); 469 free(manager); 470 471 *managerp = NULL; 472 473 timermgr = NULL; 474 } 475 476 isc_result_t 477 isc_timermgr_nextevent(isc_timermgr_t *manager0, struct timespec *when) { 478 isc_timermgr_t *manager = (isc_timermgr_t *)manager0; 479 480 if (manager == NULL) 481 manager = timermgr; 482 if (manager == NULL || manager->nscheduled == 0) 483 return (ISC_R_NOTFOUND); 484 *when = manager->due; 485 return (ISC_R_SUCCESS); 486 } 487 488 void 489 isc_timermgr_dispatch(isc_timermgr_t *manager0) { 490 isc_timermgr_t *manager = (isc_timermgr_t *)manager0; 491 struct timespec now; 492 493 if (manager == NULL) 494 manager = timermgr; 495 if (manager == NULL) 496 return; 497 clock_gettime(CLOCK_MONOTONIC, &now); 498 dispatch(manager, &now); 499 } 500 501