xref: /illumos-gate/usr/src/lib/libsip/common/sip_timeout.c (revision 8b464eb836173b92f2b7a65623cd06c8c3c59289)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2006 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 /*
30  * Simple implementation of timeout functionality. The granuality is a sec
31  */
32 #include <stdio.h>
33 #include <pthread.h>
34 #include <sys/errno.h>
35 #include <stdlib.h>
36 
37 #include "sip_miscdefs.h"
38 
39 uint_t		sip_timeout(void *arg, void (*callback_func)(void *),
40 		    struct timeval *timeout_time);
41 boolean_t	sip_untimeout(uint_t);
42 
43 typedef struct timeout {
44 	struct timeout *sip_timeout_next;
45 	hrtime_t sip_timeout_val;
46 	void (*sip_timeout_callback_func)(void *);
47 	void *sip_timeout_callback_func_arg;
48 	int   sip_timeout_id;
49 } sip_timeout_t;
50 
51 static pthread_mutex_t timeout_mutex = PTHREAD_MUTEX_INITIALIZER;
52 static pthread_cond_t  timeout_cond_var = PTHREAD_COND_INITIALIZER;
53 static sip_timeout_t *timeout_list;
54 static sip_timeout_t *timeout_current_start;
55 static sip_timeout_t *timeout_current_end;
56 
57 /*
58  * LONG_SLEEP_TIME = (24 * 60 * 60 * NANOSEC)
59  */
60 #define	LONG_SLEEP_TIME	(0x15180LL * 0x3B9ACA00LL)
61 
62 uint_t timer_id = 0;
63 
64 /*
65  * Invoke the callback function
66  */
67 /* ARGSUSED */
68 static void *
69 sip_run_to_functions(void *arg)
70 {
71 	sip_timeout_t *timeout = NULL;
72 
73 	(void) pthread_mutex_lock(&timeout_mutex);
74 	while (timeout_current_start != NULL) {
75 		timeout = timeout_current_start;
76 		if (timeout_current_end == timeout_current_start)
77 			timeout_current_start = timeout_current_end = NULL;
78 		else
79 			timeout_current_start = timeout->sip_timeout_next;
80 		(void) pthread_mutex_unlock(&timeout_mutex);
81 		timeout->sip_timeout_callback_func(
82 		    timeout->sip_timeout_callback_func_arg);
83 		free(timeout);
84 		(void) pthread_mutex_lock(&timeout_mutex);
85 	}
86 	(void) pthread_mutex_unlock(&timeout_mutex);
87 	pthread_exit(NULL);
88 	return ((void *)0);
89 }
90 
91 /*
92  * In the very very unlikely case timer id wraps around and we have two timers
93  * with the same id. If that happens timer with the least amount of time left
94  * will be deleted. In case both timers have same time left than the one that
95  * was scheduled first will be deleted as it will be in the front of the list.
96  */
97 boolean_t
98 sip_untimeout(uint_t id)
99 {
100 	boolean_t	ret = B_FALSE;
101 	sip_timeout_t	*current, *last;
102 
103 	last = NULL;
104 	(void) pthread_mutex_lock(&timeout_mutex);
105 
106 	/*
107 	 * Check if this is in the to-be run list
108 	 */
109 	if (timeout_current_start != NULL) {
110 		current = timeout_current_start;
111 		while (current != NULL) {
112 			if (current->sip_timeout_id == id) {
113 				if (current == timeout_current_start) {
114 					timeout_current_start =
115 					    current->sip_timeout_next;
116 				} else {
117 					last->sip_timeout_next =
118 					    current->sip_timeout_next;
119 				}
120 				if (current == timeout_current_end)
121 					timeout_current_end = last;
122 				if (current->sip_timeout_callback_func_arg !=
123 				    NULL) {
124 					free(current->
125 					    sip_timeout_callback_func_arg);
126 					current->sip_timeout_callback_func_arg =
127 					    NULL;
128 				}
129 				free(current);
130 				ret = B_TRUE;
131 				break;
132 			}
133 			last = current;
134 			current = current->sip_timeout_next;
135 		}
136 	}
137 
138 	/*
139 	 * Check if this is in the to-be scheduled list
140 	 */
141 	if (!ret && timeout_list != NULL) {
142 		last = NULL;
143 		current = timeout_list;
144 		while (current != NULL) {
145 			if (current->sip_timeout_id == id) {
146 				if (current == timeout_list) {
147 					timeout_list =
148 					    current->sip_timeout_next;
149 				} else {
150 					last->sip_timeout_next =
151 					    current->sip_timeout_next;
152 				}
153 				if (current->sip_timeout_callback_func_arg !=
154 				    NULL) {
155 					free(current->
156 					    sip_timeout_callback_func_arg);
157 					current->sip_timeout_callback_func_arg =
158 					    NULL;
159 				}
160 				free(current);
161 				ret = B_TRUE;
162 				break;
163 			}
164 			last = current;
165 			current = current->sip_timeout_next;
166 		}
167 	}
168 	(void) pthread_mutex_unlock(&timeout_mutex);
169 	return (ret);
170 }
171 
172 /*
173  * Add a new timeout
174  */
175 uint_t
176 sip_timeout(void *arg, void (*callback_func)(void *),
177     struct timeval *timeout_time)
178 {
179 	sip_timeout_t	*new_timeout;
180 	sip_timeout_t	*current, *last;
181 	hrtime_t	future_time;
182 	uint_t		tid;
183 #ifdef	__linux__
184 	struct timespec	tspec;
185 	hrtime_t	now;
186 #endif
187 
188 	new_timeout = malloc(sizeof (sip_timeout_t));
189 	if (new_timeout == NULL)
190 		return (0);
191 
192 #ifdef	__linux__
193 	if (clock_gettime(CLOCK_REALTIME, &tspec) != 0)
194 		return (0);
195 	now = (hrtime_t)tspec.tv_sec * (hrtime_t)NANOSEC + tspec.tv_nsec;
196 	future_time = (hrtime_t)timeout_time->tv_sec * (hrtime_t)NANOSEC +
197 	    (hrtime_t)(timeout_time->tv_usec * MILLISEC) + now;
198 #else
199 	future_time = (hrtime_t)timeout_time->tv_sec * (hrtime_t)NANOSEC +
200 	    (hrtime_t)(timeout_time->tv_usec * MILLISEC) + gethrtime();
201 #endif
202 	if (future_time <= 0L) {
203 		free(new_timeout);
204 		return (0);
205 	}
206 
207 	new_timeout->sip_timeout_next = NULL;
208 	new_timeout->sip_timeout_val = future_time;
209 	new_timeout->sip_timeout_callback_func = callback_func;
210 	new_timeout->sip_timeout_callback_func_arg = arg;
211 	(void) pthread_mutex_lock(&timeout_mutex);
212 	timer_id++;
213 	if (timer_id == 0)
214 		timer_id++;
215 	tid = timer_id;
216 	new_timeout->sip_timeout_id = tid;
217 	last = current = timeout_list;
218 	while (current != NULL) {
219 		if (current->sip_timeout_val <= new_timeout->sip_timeout_val) {
220 			last = current;
221 			current = current->sip_timeout_next;
222 		} else {
223 			break;
224 		}
225 	}
226 
227 	if (current == timeout_list) {
228 		new_timeout->sip_timeout_next  = timeout_list;
229 		timeout_list = new_timeout;
230 	} else {
231 		new_timeout->sip_timeout_next = current,
232 		last->sip_timeout_next = new_timeout;
233 	}
234 	(void) pthread_cond_signal(&timeout_cond_var);
235 	(void) pthread_mutex_unlock(&timeout_mutex);
236 	return (tid);
237 }
238 
239 /*
240  * Schedule the next timeout
241  */
242 static hrtime_t
243 sip_schedule_to_functions()
244 {
245 	sip_timeout_t		*timeout = NULL;
246 	sip_timeout_t		*last = NULL;
247 	boolean_t		create_thread = B_FALSE;
248 	hrtime_t		current_time;
249 #ifdef	__linux__
250 	struct timespec	tspec;
251 #endif
252 
253 	/*
254 	 * Thread is holding the mutex.
255 	 */
256 #ifdef	__linux__
257 	if (clock_gettime(CLOCK_REALTIME, &tspec) != 0)
258 		return ((hrtime_t)LONG_SLEEP_TIME + current_time);
259 	current_time = (hrtime_t)tspec.tv_sec * (hrtime_t)NANOSEC +
260 	    tspec.tv_nsec;
261 #else
262 	current_time = gethrtime();
263 #endif
264 	if (timeout_list == NULL)
265 		return ((hrtime_t)LONG_SLEEP_TIME + current_time);
266 	timeout = timeout_list;
267 
268 	/*
269 	 * Get all the timeouts that have fired.
270 	 */
271 	while (timeout != NULL && timeout->sip_timeout_val <= current_time) {
272 		last = timeout;
273 		timeout = timeout->sip_timeout_next;
274 	}
275 
276 	timeout = last;
277 	if (timeout != NULL) {
278 		if (timeout_current_end != NULL) {
279 			timeout_current_end->sip_timeout_next = timeout_list;
280 			timeout_current_end = timeout;
281 		} else {
282 			timeout_current_start = timeout_list;
283 			timeout_current_end = timeout;
284 			create_thread = B_TRUE;
285 		}
286 		timeout_list = timeout->sip_timeout_next;
287 		timeout->sip_timeout_next = NULL;
288 		if (create_thread) {
289 			pthread_t	thr;
290 
291 			(void) pthread_create(&thr, NULL, sip_run_to_functions,
292 			    NULL);
293 			(void) pthread_detach(thr);
294 		}
295 	}
296 	if (timeout_list != NULL)
297 		return (timeout_list->sip_timeout_val);
298 	else
299 		return ((hrtime_t)LONG_SLEEP_TIME + current_time);
300 }
301 
302 /*
303  * The timer routine
304  */
305 /* ARGSUSED */
306 static void *
307 sip_timer_thr(void *arg)
308 {
309 	timestruc_t	to;
310 	hrtime_t	current_time;
311 	hrtime_t	next_timeout;
312 	hrtime_t	delta;
313 #ifdef	__linux__
314 	struct timespec	tspec;
315 #endif
316 	to.tv_sec = time(NULL) + 5;
317 	to.tv_nsec = 0;
318 	(void) pthread_mutex_lock(&timeout_mutex);
319 	for (;;) {
320 		(void) pthread_cond_timedwait(&timeout_cond_var,
321 		    &timeout_mutex, &to);
322 		/*
323 		 * We return from timedwait because we either timed out
324 		 * or a new element was added and we need to reset the time
325 		 */
326 again:
327 		next_timeout =  sip_schedule_to_functions();
328 #ifdef	__linux__
329 		if (clock_gettime(CLOCK_REALTIME, &tspec) != 0)
330 			goto again; /* ??? */
331 		current_time = (hrtime_t)tspec.tv_sec * (hrtime_t)NANOSEC +
332 		    tspec.tv_nsec;
333 #else
334 		current_time = gethrtime();
335 #endif
336 		delta = next_timeout - current_time;
337 		if (delta <= 0)
338 			goto again;
339 		to.tv_sec = time(NULL) + (delta / NANOSEC);
340 		to.tv_nsec = delta % NANOSEC;
341 	}
342 	/* NOTREACHED */
343 	return ((void *)0);
344 }
345 
346 /*
347  * The init routine, starts the timer thread
348  */
349 void
350 sip_timeout_init()
351 {
352 	static boolean_t	timout_init = B_FALSE;
353 	pthread_t		thread1;
354 
355 	(void) pthread_mutex_lock(&timeout_mutex);
356 	if (timout_init == B_FALSE) {
357 		timout_init = B_TRUE;
358 		(void) pthread_mutex_unlock(&timeout_mutex);
359 	} else {
360 		(void) pthread_mutex_unlock(&timeout_mutex);
361 		return;
362 	}
363 	(void) pthread_create(&thread1, NULL, sip_timer_thr, NULL);
364 }
365