xref: /illumos-gate/usr/src/lib/libinetutil/common/tq.c (revision 4de2612967d06c4fdbf524a62556a1e8118a006f)
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