1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <stdlib.h> 30 #include <limits.h> 31 #include <sys/time.h> 32 #include <sys/types.h> 33 #include <sys/sysmacros.h> 34 #include <sys/stropts.h> /* INFTIM */ 35 36 #include <libinetutil.h> 37 #include "libinetutil_impl.h" 38 39 static iu_timer_node_t *pending_delete_chain = NULL; 40 41 static void destroy_timer(iu_tq_t *, iu_timer_node_t *); 42 static iu_timer_id_t get_timer_id(iu_tq_t *); 43 static void release_timer_id(iu_tq_t *, iu_timer_id_t); 44 45 /* 46 * iu_tq_create(): creates, initializes and returns a timer queue for use 47 * 48 * input: void 49 * output: iu_tq_t *: the new timer queue 50 */ 51 52 iu_tq_t * 53 iu_tq_create(void) 54 { 55 return (calloc(1, sizeof (iu_tq_t))); 56 } 57 58 /* 59 * iu_tq_destroy(): destroys an existing timer queue 60 * 61 * input: iu_tq_t *: the timer queue to destroy 62 * output: void 63 */ 64 65 void 66 iu_tq_destroy(iu_tq_t *tq) 67 { 68 iu_timer_node_t *node, *next_node; 69 70 for (node = tq->iutq_head; node != NULL; node = next_node) { 71 next_node = node->iutn_next; 72 destroy_timer(tq, node); 73 } 74 75 free(tq); 76 } 77 78 /* 79 * insert_timer(): inserts a timer node into a tq's timer list 80 * 81 * input: iu_tq_t *: the timer queue 82 * iu_timer_node_t *: the timer node to insert into the list 83 * uint64_t: the number of milliseconds before this timer fires 84 * output: void 85 */ 86 87 static void 88 insert_timer(iu_tq_t *tq, iu_timer_node_t *node, uint64_t msec) 89 { 90 iu_timer_node_t *after = NULL; 91 92 /* 93 * find the node to insert this new node "after". we do this 94 * instead of the more intuitive "insert before" because with 95 * the insert before approach, a null `before' node pointer 96 * is overloaded in meaning (it could be null because there 97 * are no items in the list, or it could be null because this 98 * is the last item on the list, which are very different cases). 99 */ 100 101 node->iutn_abs_timeout = gethrtime() + (msec * (NANOSEC / MILLISEC)); 102 103 if (tq->iutq_head != NULL && 104 tq->iutq_head->iutn_abs_timeout < node->iutn_abs_timeout) 105 for (after = tq->iutq_head; after->iutn_next != NULL; 106 after = after->iutn_next) 107 if (after->iutn_next->iutn_abs_timeout > 108 node->iutn_abs_timeout) 109 break; 110 111 node->iutn_next = after ? after->iutn_next : tq->iutq_head; 112 node->iutn_prev = after; 113 if (after == NULL) 114 tq->iutq_head = node; 115 else 116 after->iutn_next = node; 117 118 if (node->iutn_next != NULL) 119 node->iutn_next->iutn_prev = node; 120 } 121 122 /* 123 * remove_timer(): removes a timer node from the tq's timer list 124 * 125 * input: iu_tq_t *: the timer queue 126 * iu_timer_node_t *: the timer node to remove from the list 127 * output: void 128 */ 129 130 static void 131 remove_timer(iu_tq_t *tq, iu_timer_node_t *node) 132 { 133 if (node->iutn_next != NULL) 134 node->iutn_next->iutn_prev = node->iutn_prev; 135 if (node->iutn_prev != NULL) 136 node->iutn_prev->iutn_next = node->iutn_next; 137 else 138 tq->iutq_head = node->iutn_next; 139 } 140 141 /* 142 * destroy_timer(): destroy a timer node 143 * 144 * input: iu_tq_t *: the timer queue the timer node is associated with 145 * iu_timer_node_t *: the node to free 146 * output: void 147 */ 148 149 static void 150 destroy_timer(iu_tq_t *tq, iu_timer_node_t *node) 151 { 152 release_timer_id(tq, node->iutn_timer_id); 153 154 /* 155 * if we're in expire, don't delete the node yet, since it may 156 * still be referencing it (through the expire_next pointers) 157 */ 158 159 if (tq->iutq_in_expire) { 160 node->iutn_pending_delete++; 161 node->iutn_next = pending_delete_chain; 162 pending_delete_chain = node; 163 } else 164 free(node); 165 166 } 167 168 /* 169 * iu_schedule_timer(): creates and inserts a timer in the tq's timer list 170 * 171 * input: iu_tq_t *: the timer queue 172 * uint32_t: the number of seconds before this timer fires 173 * iu_tq_callback_t *: the function to call when the timer fires 174 * void *: an argument to pass to the called back function 175 * output: iu_timer_id_t: the new timer's timer id on success, -1 on failure 176 */ 177 178 iu_timer_id_t 179 iu_schedule_timer(iu_tq_t *tq, uint32_t sec, iu_tq_callback_t *callback, 180 void *arg) 181 { 182 return (iu_schedule_timer_ms(tq, sec * MILLISEC, callback, arg)); 183 } 184 185 /* 186 * iu_schedule_ms_timer(): creates and inserts a timer in the tq's timer list, 187 * using millisecond granularity 188 * 189 * input: iu_tq_t *: the timer queue 190 * uint64_t: the number of milliseconds before this timer fires 191 * iu_tq_callback_t *: the function to call when the timer fires 192 * void *: an argument to pass to the called back function 193 * output: iu_timer_id_t: the new timer's timer id on success, -1 on failure 194 */ 195 iu_timer_id_t 196 iu_schedule_timer_ms(iu_tq_t *tq, uint64_t ms, iu_tq_callback_t *callback, 197 void *arg) 198 { 199 iu_timer_node_t *node = calloc(1, sizeof (iu_timer_node_t)); 200 201 if (node == NULL) 202 return (-1); 203 204 node->iutn_callback = callback; 205 node->iutn_arg = arg; 206 node->iutn_timer_id = get_timer_id(tq); 207 if (node->iutn_timer_id == -1) { 208 free(node); 209 return (-1); 210 } 211 212 insert_timer(tq, node, ms); 213 214 return (node->iutn_timer_id); 215 } 216 217 /* 218 * iu_cancel_timer(): cancels a pending timer from a timer queue's timer list 219 * 220 * input: iu_tq_t *: the timer queue 221 * iu_timer_id_t: the timer id returned from iu_schedule_timer 222 * void **: if non-NULL, a place to return the argument passed to 223 * iu_schedule_timer 224 * output: int: 1 on success, 0 on failure 225 */ 226 227 int 228 iu_cancel_timer(iu_tq_t *tq, iu_timer_id_t timer_id, void **arg) 229 { 230 iu_timer_node_t *node; 231 232 if (timer_id == -1) 233 return (0); 234 235 for (node = tq->iutq_head; node != NULL; node = node->iutn_next) { 236 if (node->iutn_timer_id == timer_id) { 237 if (arg != NULL) 238 *arg = node->iutn_arg; 239 remove_timer(tq, node); 240 destroy_timer(tq, node); 241 return (1); 242 } 243 } 244 return (0); 245 } 246 247 /* 248 * iu_adjust_timer(): adjusts the fire time of a timer in the tq's timer list 249 * 250 * input: iu_tq_t *: the timer queue 251 * iu_timer_id_t: the timer id returned from iu_schedule_timer 252 * uint32_t: the number of seconds before this timer fires 253 * output: int: 1 on success, 0 on failure 254 */ 255 256 int 257 iu_adjust_timer(iu_tq_t *tq, iu_timer_id_t timer_id, uint32_t sec) 258 { 259 iu_timer_node_t *node; 260 261 if (timer_id == -1) 262 return (0); 263 264 for (node = tq->iutq_head; node != NULL; node = node->iutn_next) { 265 if (node->iutn_timer_id == timer_id) { 266 remove_timer(tq, node); 267 insert_timer(tq, node, sec * MILLISEC); 268 return (1); 269 } 270 } 271 return (0); 272 } 273 274 /* 275 * iu_earliest_timer(): returns the time until the next timer fires on a tq 276 * 277 * input: iu_tq_t *: the timer queue 278 * output: int: the number of milliseconds until the next timer (up to 279 * a maximum value of INT_MAX), or INFTIM if no timers are pending. 280 */ 281 282 int 283 iu_earliest_timer(iu_tq_t *tq) 284 { 285 unsigned long long timeout_interval; 286 hrtime_t current_time = gethrtime(); 287 288 if (tq->iutq_head == NULL) 289 return (INFTIM); 290 291 /* 292 * event might've already happened if we haven't gotten a chance to 293 * run in a while; return zero and pretend it just expired. 294 */ 295 296 if (tq->iutq_head->iutn_abs_timeout <= current_time) 297 return (0); 298 299 /* 300 * since the timers are ordered in absolute time-to-fire, just 301 * subtract from the head of the list. 302 */ 303 304 timeout_interval = 305 (tq->iutq_head->iutn_abs_timeout - current_time) / 1000000; 306 307 return (MIN(timeout_interval, INT_MAX)); 308 } 309 310 /* 311 * iu_expire_timers(): expires all pending timers on a given timer queue 312 * 313 * input: iu_tq_t *: the timer queue 314 * output: int: the number of timers expired 315 */ 316 317 int 318 iu_expire_timers(iu_tq_t *tq) 319 { 320 iu_timer_node_t *node, *next_node; 321 int n_expired = 0; 322 hrtime_t current_time = gethrtime(); 323 324 /* 325 * in_expire is in the iu_tq_t instead of being passed through as 326 * an argument to remove_timer() below since the callback 327 * function may call iu_cancel_timer() itself as well. 328 */ 329 330 tq->iutq_in_expire++; 331 332 /* 333 * this function builds another linked list of timer nodes 334 * through `expire_next' because the normal linked list 335 * may be changed as a result of callbacks canceling and 336 * scheduling timeouts, and thus can't be trusted. 337 */ 338 339 for (node = tq->iutq_head; node != NULL; node = node->iutn_next) 340 node->iutn_expire_next = node->iutn_next; 341 342 for (node = tq->iutq_head; node != NULL; 343 node = node->iutn_expire_next) { 344 345 if (node->iutn_abs_timeout > current_time) 346 break; 347 348 /* 349 * fringe condition: two timers fire at the "same 350 * time" (i.e., they're both scheduled called back in 351 * this loop) and one cancels the other. in this 352 * case, the timer which has already been "cancelled" 353 * should not be called back. 354 */ 355 356 if (node->iutn_pending_delete) 357 continue; 358 359 /* 360 * we remove the timer before calling back the callback 361 * so that a callback which accidentally tries to cancel 362 * itself (through whatever means) doesn't succeed. 363 */ 364 365 n_expired++; 366 remove_timer(tq, node); 367 destroy_timer(tq, node); 368 node->iutn_callback(tq, node->iutn_arg); 369 } 370 371 tq->iutq_in_expire--; 372 373 /* 374 * any cancels that took place whilst we were expiring timeouts 375 * ended up on the `pending_delete_chain'. delete them now 376 * that it's safe. 377 */ 378 379 for (node = pending_delete_chain; node != NULL; node = next_node) { 380 next_node = node->iutn_next; 381 free(node); 382 } 383 pending_delete_chain = NULL; 384 385 return (n_expired); 386 } 387 388 /* 389 * get_timer_id(): allocates a timer id from the pool 390 * 391 * input: iu_tq_t *: the timer queue 392 * output: iu_timer_id_t: the allocated timer id, or -1 if none available 393 */ 394 395 static iu_timer_id_t 396 get_timer_id(iu_tq_t *tq) 397 { 398 unsigned int map_index; 399 unsigned char map_bit; 400 boolean_t have_wrapped = B_FALSE; 401 402 for (; ; tq->iutq_next_timer_id++) { 403 404 if (tq->iutq_next_timer_id >= IU_TIMER_ID_MAX) { 405 if (have_wrapped) 406 return (-1); 407 408 have_wrapped = B_TRUE; 409 tq->iutq_next_timer_id = 0; 410 } 411 412 map_index = tq->iutq_next_timer_id / CHAR_BIT; 413 map_bit = tq->iutq_next_timer_id % CHAR_BIT; 414 415 if ((tq->iutq_timer_id_map[map_index] & (1 << map_bit)) == 0) 416 break; 417 } 418 419 tq->iutq_timer_id_map[map_index] |= (1 << map_bit); 420 return (tq->iutq_next_timer_id++); 421 } 422 423 /* 424 * release_timer_id(): releases a timer id back into the pool 425 * 426 * input: iu_tq_t *: the timer queue 427 * iu_timer_id_t: the timer id to release 428 * output: void 429 */ 430 431 static void 432 release_timer_id(iu_tq_t *tq, iu_timer_id_t timer_id) 433 { 434 unsigned int map_index = timer_id / CHAR_BIT; 435 unsigned char map_bit = timer_id % CHAR_BIT; 436 437 tq->iutq_timer_id_map[map_index] &= ~(1 << map_bit); 438 } 439