OpenSolaris_b135/lib/libsip/common/sip_timeout.c

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Simple implementation of timeout functionality. The granuality is a sec
 */
#include <pthread.h>
#include <stdlib.h>

uint_t		sip_timeout(void *arg, void (*callback_func)(void *),
		    struct timeval *timeout_time);
boolean_t	sip_untimeout(uint_t);

typedef struct timeout {
	struct timeout *sip_timeout_next;
	hrtime_t sip_timeout_val;
	void (*sip_timeout_callback_func)(void *);
	void *sip_timeout_callback_func_arg;
	int   sip_timeout_id;
} sip_timeout_t;

static pthread_mutex_t timeout_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  timeout_cond_var = PTHREAD_COND_INITIALIZER;
static sip_timeout_t *timeout_list;
static sip_timeout_t *timeout_current_start;
static sip_timeout_t *timeout_current_end;

/*
 * LONG_SLEEP_TIME = (24 * 60 * 60 * NANOSEC)
 */
#define	LONG_SLEEP_TIME	(0x15180LL * 0x3B9ACA00LL)

uint_t timer_id = 0;

/*
 * Invoke the callback function
 */
/* ARGSUSED */
static void *
sip_run_to_functions(void *arg)
{
	sip_timeout_t *timeout = NULL;

	(void) pthread_mutex_lock(&timeout_mutex);
	while (timeout_current_start != NULL) {
		timeout = timeout_current_start;
		if (timeout_current_end == timeout_current_start)
			timeout_current_start = timeout_current_end = NULL;
		else
			timeout_current_start = timeout->sip_timeout_next;
		(void) pthread_mutex_unlock(&timeout_mutex);
		timeout->sip_timeout_callback_func(
		    timeout->sip_timeout_callback_func_arg);
		free(timeout);
		(void) pthread_mutex_lock(&timeout_mutex);
	}
	(void) pthread_mutex_unlock(&timeout_mutex);
	pthread_exit(NULL);
	return ((void *)0);
}

/*
 * In the very very unlikely case timer id wraps around and we have two timers
 * with the same id. If that happens timer with the least amount of time left
 * will be deleted. In case both timers have same time left than the one that
 * was scheduled first will be deleted as it will be in the front of the list.
 */
boolean_t
sip_untimeout(uint_t id)
{
	boolean_t	ret = B_FALSE;
	sip_timeout_t	*current, *last;

	last = NULL;
	(void) pthread_mutex_lock(&timeout_mutex);

	/*
	 * Check if this is in the to-be run list
	 */
	if (timeout_current_start != NULL) {
		current = timeout_current_start;
		while (current != NULL) {
			if (current->sip_timeout_id == id) {
				if (current == timeout_current_start) {
					timeout_current_start =
					    current->sip_timeout_next;
				} else {
					last->sip_timeout_next =
					    current->sip_timeout_next;
				}
				if (current == timeout_current_end)
					timeout_current_end = last;
				if (current->sip_timeout_callback_func_arg !=
				    NULL) {
					free(current->
					    sip_timeout_callback_func_arg);
					current->sip_timeout_callback_func_arg =
					    NULL;
				}
				free(current);
				ret = B_TRUE;
				break;
			}
			last = current;
			current = current->sip_timeout_next;
		}
	}

	/*
	 * Check if this is in the to-be scheduled list
	 */
	if (!ret && timeout_list != NULL) {
		last = NULL;
		current = timeout_list;
		while (current != NULL) {
			if (current->sip_timeout_id == id) {
				if (current == timeout_list) {
					timeout_list =
					    current->sip_timeout_next;
				} else {
					last->sip_timeout_next =
					    current->sip_timeout_next;
				}
				if (current->sip_timeout_callback_func_arg !=
				    NULL) {
					free(current->
					    sip_timeout_callback_func_arg);
					current->sip_timeout_callback_func_arg =
					    NULL;
				}
				free(current);
				ret = B_TRUE;
				break;
			}
			last = current;
			current = current->sip_timeout_next;
		}
	}
	(void) pthread_mutex_unlock(&timeout_mutex);
	return (ret);
}

/*
 * Add a new timeout
 */
uint_t
sip_timeout(void *arg, void (*callback_func)(void *),
    struct timeval *timeout_time)
{
	sip_timeout_t	*new_timeout;
	sip_timeout_t	*current;
	sip_timeout_t	*last;
	hrtime_t	future_time;
	uint_t		tid;
#ifdef	__linux__
	struct timespec	tspec;
	hrtime_t	now;
#endif

	new_timeout = malloc(sizeof (sip_timeout_t));
	if (new_timeout == NULL)
		return (0);

#ifdef	__linux__
	if (clock_gettime(CLOCK_REALTIME, &tspec) != 0)
		return (0);
	now = (hrtime_t)tspec.tv_sec * (hrtime_t)NANOSEC + tspec.tv_nsec;
	future_time = (hrtime_t)timeout_time->tv_sec * (hrtime_t)NANOSEC +
	    (hrtime_t)(timeout_time->tv_usec * MILLISEC) + now;
#else
	future_time = (hrtime_t)timeout_time->tv_sec * (hrtime_t)NANOSEC +
	    (hrtime_t)(timeout_time->tv_usec * MILLISEC) + gethrtime();
#endif
	if (future_time <= 0L) {
		free(new_timeout);
		return (0);
	}

	new_timeout->sip_timeout_next = NULL;
	new_timeout->sip_timeout_val = future_time;
	new_timeout->sip_timeout_callback_func = callback_func;
	new_timeout->sip_timeout_callback_func_arg = arg;
	(void) pthread_mutex_lock(&timeout_mutex);
	timer_id++;
	if (timer_id == 0)
		timer_id++;
	tid = timer_id;
	new_timeout->sip_timeout_id = tid;
	last = current = timeout_list;
	while (current != NULL) {
		if (current->sip_timeout_val <= new_timeout->sip_timeout_val) {
			last = current;
			current = current->sip_timeout_next;
		} else {
			break;
		}
	}

	if (current == timeout_list) {
		new_timeout->sip_timeout_next  = timeout_list;
		timeout_list = new_timeout;
	} else {
		new_timeout->sip_timeout_next = current,
		last->sip_timeout_next = new_timeout;
	}
	(void) pthread_cond_signal(&timeout_cond_var);
	(void) pthread_mutex_unlock(&timeout_mutex);
	return (tid);
}

/*
 * Schedule the next timeout
 */
static hrtime_t
sip_schedule_to_functions()
{
	sip_timeout_t		*timeout = NULL;
	sip_timeout_t		*last = NULL;
	boolean_t		create_thread = B_FALSE;
	hrtime_t		current_time;
#ifdef	__linux__
	struct timespec	tspec;
#endif

	/*
	 * Thread is holding the mutex.
	 */
#ifdef	__linux__
	if (clock_gettime(CLOCK_REALTIME, &tspec) != 0)
		return ((hrtime_t)LONG_SLEEP_TIME + current_time);
	current_time = (hrtime_t)tspec.tv_sec * (hrtime_t)NANOSEC +
	    tspec.tv_nsec;
#else
	current_time = gethrtime();
#endif
	if (timeout_list == NULL)
		return ((hrtime_t)LONG_SLEEP_TIME + current_time);
	timeout = timeout_list;

	/*
	 * Get all the timeouts that have fired.
	 */
	while (timeout != NULL && timeout->sip_timeout_val <= current_time) {
		last = timeout;
		timeout = timeout->sip_timeout_next;
	}

	timeout = last;
	if (timeout != NULL) {
		if (timeout_current_end != NULL) {
			timeout_current_end->sip_timeout_next = timeout_list;
			timeout_current_end = timeout;
		} else {
			timeout_current_start = timeout_list;
			timeout_current_end = timeout;
			create_thread = B_TRUE;
		}
		timeout_list = timeout->sip_timeout_next;
		timeout->sip_timeout_next = NULL;
		if (create_thread) {
			pthread_t	thr;

			(void) pthread_create(&thr, NULL, sip_run_to_functions,
			    NULL);
			(void) pthread_detach(thr);
		}
	}
	if (timeout_list != NULL)
		return (timeout_list->sip_timeout_val);
	else
		return ((hrtime_t)LONG_SLEEP_TIME + current_time);
}

/*
 * The timer routine
 */
/* ARGSUSED */
static void *
sip_timer_thr(void *arg)
{
	timestruc_t	to;
	hrtime_t	current_time;
	hrtime_t	next_timeout;
	hrtime_t	delta;
	struct timeval tim;
#ifdef	__linux__
	struct timespec	tspec;
#endif
	delta = (hrtime_t)5 * NANOSEC;
	(void) pthread_mutex_lock(&timeout_mutex);
	for (;;) {
		(void) gettimeofday(&tim, NULL);
		to.tv_sec = tim.tv_sec + (delta / NANOSEC);
		to.tv_nsec = (hrtime_t)(tim.tv_usec * MILLISEC) +
		    (delta % NANOSEC);
		if (to.tv_nsec > NANOSEC) {
			to.tv_sec += (to.tv_nsec / NANOSEC);
			to.tv_nsec %= NANOSEC;
		}
		(void) pthread_cond_timedwait(&timeout_cond_var,
		    &timeout_mutex, &to);
		/*
		 * We return from timedwait because we either timed out
		 * or a new element was added and we need to reset the time
		 */
again:
		next_timeout =  sip_schedule_to_functions();
#ifdef	__linux__
		if (clock_gettime(CLOCK_REALTIME, &tspec) != 0)
			goto again; /* ??? */
		current_time = (hrtime_t)tspec.tv_sec * (hrtime_t)NANOSEC +
		    tspec.tv_nsec;
#else
		current_time = gethrtime();
#endif
		delta = next_timeout - current_time;
		if (delta <= 0)
			goto again;
	}
	/* NOTREACHED */
	return ((void *)0);
}

/*
 * The init routine, starts the timer thread
 */
void
sip_timeout_init()
{
	static boolean_t	timout_init = B_FALSE;
	pthread_t		thread1;

	(void) pthread_mutex_lock(&timeout_mutex);
	if (timout_init == B_FALSE) {
		timout_init = B_TRUE;
		(void) pthread_mutex_unlock(&timeout_mutex);
	} else {
		(void) pthread_mutex_unlock(&timeout_mutex);
		return;
	}
	(void) pthread_create(&thread1, NULL, sip_timer_thr, NULL);
}