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