/******************************************************
The interface to the operating system
synchronization primitives.

(c) 1995 Innobase Oy

Created 9/6/1995 Heikki Tuuri
*******************************************************/

#include "os0sync.h"
#ifdef UNIV_NONINL
#include "os0sync.ic"
#endif

#ifdef __WIN__
#include <windows.h>
#endif

#include "ut0mem.h"
#include "srv0start.h"

/* Type definition for an operating system mutex struct */
struct os_mutex_struct{ 
	void*		handle;	/* OS handle to mutex */
	ulint		count;	/* we use this counter to check
				that the same thread does not
				recursively lock the mutex: we
				do not assume that the OS mutex
				supports recursive locking, though
				NT seems to do that */
	UT_LIST_NODE_T(os_mutex_str_t) os_mutex_list;
				/* list of all 'slow' OS mutexes created */
};

/* Mutex protecting counts and the lists of OS mutexes and events */
os_mutex_t	os_sync_mutex;
ibool		os_sync_mutex_inited	= FALSE;

/* This is incremented by 1 in os_thread_create and decremented by 1 in
os_thread_exit */
ulint	os_thread_count		= 0;

/* The list of all events created */
UT_LIST_BASE_NODE_T(os_event_struct_t)	os_event_list;

/* The list of all OS 'slow' mutexes */
UT_LIST_BASE_NODE_T(os_mutex_str_t)	os_mutex_list;

ulint	os_event_count		= 0;
ulint	os_mutex_count		= 0;
ulint	os_fast_mutex_count	= 0;


/*************************************************************
Initializes global event and OS 'slow' mutex lists. */

void
os_sync_init(void)
/*==============*/
{
	UT_LIST_INIT(os_event_list);
	UT_LIST_INIT(os_mutex_list);

	os_sync_mutex = os_mutex_create(NULL);

	os_sync_mutex_inited = TRUE;
}

/*************************************************************
Frees created events and OS 'slow' mutexes. */

void
os_sync_free(void)
/*==============*/
{
	os_event_t	event;
	os_mutex_t	mutex;

	event = UT_LIST_GET_FIRST(os_event_list);

	while (event) {

	      os_event_free(event);

	      event = UT_LIST_GET_FIRST(os_event_list);
	}

	mutex = UT_LIST_GET_FIRST(os_mutex_list);

	while (mutex) {
	      if (mutex == os_sync_mutex) {
		      /* Set the flag to FALSE so that we do not try to
		      reserve os_sync_mutex any more in remaining freeing
		      operations in shutdown */
		      os_sync_mutex_inited = FALSE;
	      }

	      os_mutex_free(mutex);

	      mutex = UT_LIST_GET_FIRST(os_mutex_list);
	}
}

/*************************************************************
Creates an event semaphore, i.e., a semaphore which may just have two
states: signaled and nonsignaled. The created event is manual reset: it
must be reset explicitly by calling sync_os_reset_event. */

os_event_t
os_event_create(
/*============*/
				/* out: the event handle */
	const char*	name)	/* in: the name of the event, if NULL
				the event is created without a name */
{
#ifdef __WIN__
        os_event_t event;

	event = ut_malloc(sizeof(struct os_event_struct));

	event->handle = CreateEvent(NULL,/* No security attributes */
			TRUE,		/* Manual reset */
			FALSE,		/* Initial state nonsignaled */
			name);
	if (!event->handle) {
	        fprintf(stderr,
"InnoDB: Could not create a Windows event semaphore; Windows error %lu\n",
		  (ulong) GetLastError());
	}
#else /* Unix */
	os_event_t	event;

	UT_NOT_USED(name);

	event = ut_malloc(sizeof(struct os_event_struct));

	os_fast_mutex_init(&(event->os_mutex));

#if defined(UNIV_HOTBACKUP) && defined(UNIV_HPUX10)
	ut_a(0 == pthread_cond_init(&(event->cond_var),
					pthread_condattr_default));
#else
	ut_a(0 == pthread_cond_init(&(event->cond_var), NULL));
#endif
	event->is_set = FALSE;
	event->signal_count = 0;
#endif /* __WIN__ */

        /* Put to the list of events */
	os_mutex_enter(os_sync_mutex);

	UT_LIST_ADD_FIRST(os_event_list, os_event_list, event);

	os_event_count++;

	os_mutex_exit(os_sync_mutex);

	return(event);
}

#ifdef __WIN__
/*************************************************************
Creates an auto-reset event semaphore, i.e., an event which is automatically
reset when a single thread is released. Works only in Windows. */

os_event_t
os_event_create_auto(
/*=================*/
				/* out: the event handle */
	const char*	name)	/* in: the name of the event, if NULL
				the event is created without a name */
{
        os_event_t event;

	event = ut_malloc(sizeof(struct os_event_struct));

	event->handle = CreateEvent(NULL,/* No security attributes */
			FALSE,		/* Auto-reset */
			FALSE,		/* Initial state nonsignaled */
			name);

	if (!event->handle) {
	        fprintf(stderr,
"InnoDB: Could not create a Windows auto event semaphore; Windows error %lu\n",
		  (ulong) GetLastError());
	}

        /* Put to the list of events */
	os_mutex_enter(os_sync_mutex);

	UT_LIST_ADD_FIRST(os_event_list, os_event_list, event);

	os_event_count++;

	os_mutex_exit(os_sync_mutex);

	return(event);
}
#endif

/**************************************************************
Sets an event semaphore to the signaled state: lets waiting threads
proceed. */

void
os_event_set(
/*=========*/
	os_event_t	event)	/* in: event to set */
{
#ifdef __WIN__	
	ut_a(event);
	ut_a(SetEvent(event->handle));
#else
	ut_a(event);

	os_fast_mutex_lock(&(event->os_mutex));

	if (event->is_set) {
		/* Do nothing */
	} else {
		event->is_set = TRUE;
		event->signal_count += 1;
		ut_a(0 == pthread_cond_broadcast(&(event->cond_var)));
	}

	os_fast_mutex_unlock(&(event->os_mutex));
#endif		
}

/**************************************************************
Resets an event semaphore to the nonsignaled state. Waiting threads will
stop to wait for the event. */

void
os_event_reset(
/*===========*/
	os_event_t	event)	/* in: event to reset */
{
#ifdef __WIN__
	ut_a(event);

	ut_a(ResetEvent(event->handle));
#else
	ut_a(event);

	os_fast_mutex_lock(&(event->os_mutex));

	if (!event->is_set) {
		/* Do nothing */
	} else {
		event->is_set = FALSE;
	}

	os_fast_mutex_unlock(&(event->os_mutex));
#endif
}

/**************************************************************
Frees an event object. */

void
os_event_free(
/*==========*/
	os_event_t	event)	/* in: event to free */
	
{
#ifdef __WIN__
	ut_a(event);

	ut_a(CloseHandle(event->handle));
#else
	ut_a(event);

	os_fast_mutex_free(&(event->os_mutex));
	ut_a(0 == pthread_cond_destroy(&(event->cond_var)));
#endif
        /* Remove from the list of events */

	os_mutex_enter(os_sync_mutex);

	UT_LIST_REMOVE(os_event_list, os_event_list, event);

	os_event_count--;

	os_mutex_exit(os_sync_mutex);

	ut_free(event);
}

/**************************************************************
Waits for an event object until it is in the signaled state. If
srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS this also exits the
waiting thread when the event becomes signaled (or immediately if the
event is already in the signaled state). */

void
os_event_wait(
/*==========*/
	os_event_t	event)	/* in: event to wait */
{
#ifdef __WIN__
	DWORD	err;

	ut_a(event);

	/* Specify an infinite time limit for waiting */
	err = WaitForSingleObject(event->handle, INFINITE);

	ut_a(err == WAIT_OBJECT_0);

	if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) {
	        os_thread_exit(NULL);
	}
#else
	ib_longlong	old_signal_count;

	os_fast_mutex_lock(&(event->os_mutex));

	old_signal_count = event->signal_count;
loop:
	if (event->is_set == TRUE
            || event->signal_count != old_signal_count) {

		os_fast_mutex_unlock(&(event->os_mutex));

		if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) {

		        os_thread_exit(NULL);
		}
		/* Ok, we may return */

		return;
	}

	pthread_cond_wait(&(event->cond_var), &(event->os_mutex));

	/* Solaris manual said that spurious wakeups may occur: we have to
	check if the event really has been signaled after we came here to
	wait */

	goto loop;
#endif
}

/**************************************************************
Waits for an event object until it is in the signaled state or
a timeout is exceeded. In Unix the timeout is always infinite. */

ulint
os_event_wait_time(
/*===============*/
				/* out: 0 if success, OS_SYNC_TIME_EXCEEDED if
				timeout was exceeded */
	os_event_t	event,	/* in: event to wait */
	ulint		time)	/* in: timeout in microseconds, or
				OS_SYNC_INFINITE_TIME */
{
#ifdef __WIN__
	DWORD	err;

	ut_a(event);

	if (time != OS_SYNC_INFINITE_TIME) {
		err = WaitForSingleObject(event->handle, (DWORD) time / 1000);
	} else {
		err = WaitForSingleObject(event->handle, INFINITE);
	}
	
	if (err == WAIT_OBJECT_0) {

		return(0);
	} else if (err == WAIT_TIMEOUT) {

		return(OS_SYNC_TIME_EXCEEDED);
	} else {
		ut_error;
		return(1000000); /* dummy value to eliminate compiler warn. */
	}
#else
	UT_NOT_USED(time);
	
	/* In Posix this is just an ordinary, infinite wait */

	os_event_wait(event);

	return(0);
#endif
}

#ifdef __WIN__
/**************************************************************
Waits for any event in an OS native event array. Returns if even a single
one is signaled or becomes signaled. */

ulint
os_event_wait_multiple(
/*===================*/
					/* out: index of the event
					which was signaled */
	ulint			n,	/* in: number of events in the
					array */
	os_native_event_t*	native_event_array)
					/* in: pointer to an array of event
					handles */
{
	DWORD	index;

	ut_a(native_event_array);
	ut_a(n > 0);

	index = WaitForMultipleObjects((DWORD) n, native_event_array,
					FALSE,	   /* Wait for any 1 event */
					INFINITE); /* Infinite wait time
						   limit */
	ut_a(index >= WAIT_OBJECT_0);	/* NOTE: Pointless comparision */
	ut_a(index < WAIT_OBJECT_0 + n);

	if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) {
	        os_thread_exit(NULL);
	}

	return(index - WAIT_OBJECT_0);
}
#endif

/*************************************************************
Creates an operating system mutex semaphore. Because these are slow, the
mutex semaphore of InnoDB itself (mutex_t) should be used where possible. */

os_mutex_t
os_mutex_create(
/*============*/
				/* out: the mutex handle */
	const char*	name)	/* in: the name of the mutex, if NULL
				the mutex is created without a name */
{
#ifdef __WIN__
	HANDLE		mutex;
	os_mutex_t	mutex_str;

	mutex = CreateMutex(NULL,	/* No security attributes */
			FALSE,		/* Initial state: no owner */
			name);
	ut_a(mutex);
#else
	os_fast_mutex_t*	mutex;
	os_mutex_t		mutex_str;

	UT_NOT_USED(name);
	
	mutex = ut_malloc(sizeof(os_fast_mutex_t));

	os_fast_mutex_init(mutex);
#endif	
	mutex_str = ut_malloc(sizeof(os_mutex_str_t));

	mutex_str->handle = mutex;
	mutex_str->count = 0;

	if (os_sync_mutex_inited) {
		/* When creating os_sync_mutex itself we cannot reserve it */
	        os_mutex_enter(os_sync_mutex);
	}

	UT_LIST_ADD_FIRST(os_mutex_list, os_mutex_list, mutex_str);

	os_mutex_count++;

	if (os_sync_mutex_inited) {
		os_mutex_exit(os_sync_mutex);
	}

	return(mutex_str);
}

/**************************************************************
Acquires ownership of a mutex semaphore. */

void
os_mutex_enter(
/*===========*/
	os_mutex_t	mutex)	/* in: mutex to acquire */
{
#ifdef __WIN__
	DWORD	err;

	ut_a(mutex);

	/* Specify infinite time limit for waiting */
	err = WaitForSingleObject(mutex->handle, INFINITE);

	ut_a(err == WAIT_OBJECT_0);

	(mutex->count)++;
	ut_a(mutex->count == 1);
#else
	os_fast_mutex_lock(mutex->handle);

	(mutex->count)++;

	ut_a(mutex->count == 1);
#endif
}

/**************************************************************
Releases ownership of a mutex. */

void
os_mutex_exit(
/*==========*/
	os_mutex_t	mutex)	/* in: mutex to release */
{
	ut_a(mutex);

	ut_a(mutex->count == 1);

	(mutex->count)--;
#ifdef __WIN__
	ut_a(ReleaseMutex(mutex->handle));
#else
	os_fast_mutex_unlock(mutex->handle);
#endif	
}

/**************************************************************
Frees a mutex object. */

void
os_mutex_free(
/*==========*/
	os_mutex_t	mutex)	/* in: mutex to free */
{
	ut_a(mutex);

	if (os_sync_mutex_inited) {
		os_mutex_enter(os_sync_mutex);
	}

	UT_LIST_REMOVE(os_mutex_list, os_mutex_list, mutex);
	
	os_mutex_count--;

	if (os_sync_mutex_inited) {
		os_mutex_exit(os_sync_mutex);
	}

#ifdef __WIN__
	ut_a(CloseHandle(mutex->handle));

	ut_free(mutex);
#else
	os_fast_mutex_free(mutex->handle);
	ut_free(mutex->handle);
	ut_free(mutex);
#endif
}

/*************************************************************
Initializes an operating system fast mutex semaphore. */

void
os_fast_mutex_init(
/*===============*/
	os_fast_mutex_t*	fast_mutex)	/* in: fast mutex */
{
#ifdef __WIN__
	ut_a(fast_mutex);
	
	InitializeCriticalSection((LPCRITICAL_SECTION) fast_mutex);
#else
#if defined(UNIV_HOTBACKUP) && defined(UNIV_HPUX10)
	ut_a(0 == pthread_mutex_init(fast_mutex, pthread_mutexattr_default));
#else
	ut_a(0 == pthread_mutex_init(fast_mutex, MY_MUTEX_INIT_FAST));
#endif
#endif
	if (os_sync_mutex_inited) {
		/* When creating os_sync_mutex itself (in Unix) we cannot
		reserve it */

		os_mutex_enter(os_sync_mutex);
	}

	os_fast_mutex_count++;

	if (os_sync_mutex_inited) {
	        os_mutex_exit(os_sync_mutex);
	}
}

/**************************************************************
Acquires ownership of a fast mutex. */

void
os_fast_mutex_lock(
/*===============*/
	os_fast_mutex_t*	fast_mutex)	/* in: mutex to acquire */
{
#ifdef __WIN__
	EnterCriticalSection((LPCRITICAL_SECTION) fast_mutex);
#else
	pthread_mutex_lock(fast_mutex);
#endif
}

/**************************************************************
Releases ownership of a fast mutex. */

void
os_fast_mutex_unlock(
/*=================*/
	os_fast_mutex_t*	fast_mutex)	/* in: mutex to release */
{
#ifdef __WIN__
	LeaveCriticalSection(fast_mutex);
#else
	pthread_mutex_unlock(fast_mutex);
#endif
}

/**************************************************************
Frees a mutex object. */

void
os_fast_mutex_free(
/*===============*/
	os_fast_mutex_t*	fast_mutex)	/* in: mutex to free */
{
#ifdef __WIN__
	ut_a(fast_mutex);

	DeleteCriticalSection((LPCRITICAL_SECTION) fast_mutex);
#else
	int	ret;
	
	ret = pthread_mutex_destroy(fast_mutex);

	if (ret != 0) {
		ut_print_timestamp(stderr);
		fprintf(stderr,
"  InnoDB: error: return value %lu when calling\n"
"InnoDB: pthread_mutex_destroy().\n", (ulint)ret);
		fprintf(stderr,
"InnoDB: Byte contents of the pthread mutex at %p:\n", fast_mutex);
		ut_print_buf(stderr, (const byte*)fast_mutex,
						sizeof(os_fast_mutex_t));
		fprintf(stderr, "\n");
	}
#endif
	if (os_sync_mutex_inited) {
		/* When freeing the last mutexes, we have
		already freed os_sync_mutex */

		os_mutex_enter(os_sync_mutex);
	}

	os_fast_mutex_count--;

	if (os_sync_mutex_inited) {
		os_mutex_exit(os_sync_mutex);
	}
}
