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