/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details. 
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/*
 * crit.c: Critical section abstraction. Used in threaded servers to protect
 *         areas where two threads can interfere with each other.
 *
 *         Condvars are condition variables that are used for thread-thread 
 *         synchronization.
 * 
 * Rob McCool
 */

#include "systems.h"

#include "netsite.h"
#include "crit.h"
#include "pool.h"

#ifdef USE_NSPR
#include "nspr.h"

#include "prthread.h"
#include "prlock.h"
#include "prcvar.h"
#include "prinrval.h"

/*
 * Defined to replace PR_Monitor() with PR_Lock().
 */
typedef struct critical {
	PRLock		*lock;
	PRUint32	count;
	PRThread	*owner;
} ns_critical_t;

typedef struct condvar {
	ns_critical_t	*lock;
	PRCondVar	*cvar;
} ns_condvar_t;

#endif
/* -------------------------- critical sections --------------------------- */

/* Useful for ASSERTs only. Returns non-zero if the current thread is the owner.
 */
NSAPI_PUBLIC int crit_owner_is_me(CRITICAL id)
{
#ifdef USE_NSPR
    ns_critical_t *crit = (ns_critical_t*)id;

    return (crit->owner == PR_GetCurrentThread());
#else
    return 1;
#endif
}

NSAPI_PUBLIC CRITICAL crit_init(void)
{
#ifdef USE_NSPR
    ns_critical_t *crit = (ns_critical_t*)PERM_MALLOC(sizeof(ns_critical_t)) ;

    if (crit) {
        if (!(crit->lock = PR_NewLock())) {
            PERM_FREE(crit);
            return NULL;
        }
        crit->count = 0;
        crit->owner = 0;
    }
    return (void *) crit;
#else
    return NULL;
#endif
}

NSAPI_PUBLIC void crit_enter(CRITICAL id)
{
#ifdef USE_NSPR
    ns_critical_t *crit = (ns_critical_t*)id;
    PRThread *me = PR_GetCurrentThread();

    if ( crit->owner == me) {
        PR_ASSERT(crit->count > 0);
        crit->count++;
    } 
    else {
        PR_Lock(crit->lock);
        PR_ASSERT(crit->count == 0);
        crit->count = 1;
        crit->owner = me;
    }
#endif
}

NSAPI_PUBLIC void crit_exit(CRITICAL id)
{
#ifdef USE_NSPR
    ns_critical_t	*crit = (ns_critical_t*)id;

    if (crit->owner != PR_GetCurrentThread()) 
        return;

    if ( --crit->count == 0) {
        crit->owner = 0;
        PR_Unlock(crit->lock);
    }
#endif
}

NSAPI_PUBLIC void crit_terminate(CRITICAL id)
{
#ifdef USE_NSPR
    ns_critical_t	*crit = (ns_critical_t*)id;

    PR_DestroyLock((PRLock*)crit->lock);
    PERM_FREE(crit);
#endif
}


/* ------------------------- condition variables -------------------------- */


NSAPI_PUBLIC CONDVAR condvar_init(CRITICAL id)
{
#ifdef USE_NSPR
    ns_critical_t	*crit = (ns_critical_t*)id;

    ns_condvar_t *cvar = (ns_condvar_t*)PERM_MALLOC(sizeof(ns_condvar_t)) ;

    if (crit) {
        cvar->lock = crit;
        if ((cvar->cvar = PR_NewCondVar((PRLock *)cvar->lock->lock)) == 0) {
            PERM_FREE(cvar);
            return NULL;
        }
    }
    return (void *) cvar;
#endif
}

NSAPI_PUBLIC void condvar_wait(CONDVAR _cv)
{
#ifdef USE_NSPR
    ns_condvar_t *cv = (ns_condvar_t *)_cv;
    /* Save away recursion count so we can restore it after the wait */
    int saveCount = cv->lock->count;
    PRThread *saveOwner = cv->lock->owner;

    PR_ASSERT(cv->lock->owner == PR_GetCurrentThread());
    cv->lock->count = 0;
    cv->lock->owner = 0;

    PR_WaitCondVar(cv->cvar, PR_INTERVAL_NO_TIMEOUT);

    cv->lock->count = saveCount;
    cv->lock->owner = saveOwner;
#endif
}


NSAPI_PUBLIC void ns_condvar_timed_wait(CONDVAR _cv, long secs)
{
#ifdef USE_NSPR
    ns_condvar_t *cv = (ns_condvar_t *)_cv;
    /* Save away recursion count so we can restore it after the wait */
    int saveCount = cv->lock->count;
    PRThread *saveOwner = cv->lock->owner;
 
    PR_ASSERT(cv->lock->owner == PR_GetCurrentThread());
    cv->lock->count = 0;
    cv->lock->owner = 0;
    
    PRIntervalTime timeout = PR_INTERVAL_NO_TIMEOUT;
    if (secs > 0) 
	timeout = PR_SecondsToInterval(secs); 
    PR_WaitCondVar(cv->cvar, timeout);
 
    cv->lock->count = saveCount;
    cv->lock->owner = saveOwner;
#endif
}



NSAPI_PUBLIC void condvar_notify(CONDVAR _cv)
{
#ifdef USE_NSPR
    ns_condvar_t *cv = (ns_condvar_t *)_cv;
    PR_ASSERT(cv->lock->owner == PR_GetCurrentThread());
    PR_NotifyCondVar(cv->cvar);
#endif
}

NSAPI_PUBLIC void condvar_notifyAll(CONDVAR _cv)
{
#ifdef USE_NSPR
    ns_condvar_t *cv = (ns_condvar_t *)_cv;
    PR_ASSERT(cv->lock->owner == PR_GetCurrentThread());
    PR_NotifyAllCondVar(cv->cvar);
#endif
}

NSAPI_PUBLIC void ns_condvar_terminate(CONDVAR _cv)
{
#ifdef USE_NSPR
    ns_condvar_t *cv = (ns_condvar_t *)_cv;
    PR_DestroyCondVar(cv->cvar);
    PERM_FREE(cv);
#endif
}

/* ----------------------- Counting semaphores ---------------------------- */
/* These are currently not listed in crit.h because they aren't yet used; 
 * although they do work.
 */

/*
 * XXXMB - these should be in NSPR.  
 *
 */

#if defined(SOLARIS) && defined(HW_THREADS)
#include <synch.h>
typedef sema_t counting_sem_t;
#elif defined(IRIX) && defined(HW_THREADS)
#include <ulocks.h>
typedef usema_t *counting_sem_t;
#else
typedef struct counting_sem_t {
	CRITICAL lock;
	CRITICAL cv_lock;
	CONDVAR cv;
	int count;
	int max;
} counting_sem_t;
#endif

NSAPI_PUBLIC COUNTING_SEMAPHORE
cs_init(int initial_count)
{
	counting_sem_t *cs = (counting_sem_t *)PERM_MALLOC(sizeof(counting_sem_t));
#if defined(SOLARIS) && defined(HW_THREADS)
    if ( sema_init(cs, initial_count, USYNC_THREAD, NULL) < 0) {
		ereport(LOG_FAILURE, XP_GetAdminStr(DBT_csInitFailureS_), system_errmsg());
		PERM_FREE(cs);
		return NULL;
	}

	return (COUNTING_SEMAPHORE)cs;
#elif defined(IRIX) && defined(HW_THREADS)
	usptr_t *arena;

	usconfig(CONF_INITSIZE, 64*1024);
	if ( (arena = usinit("/tmp/cs.locks")) == NULL) 
		return NULL;

	if ( (cs = (counting_sem_t *)usnewsema(arena, 0)) == NULL)
		return NULL;

	return cs;
	
#else

	cs->count = initial_count;
	cs->lock = crit_init();
	cs->cv_lock = crit_init();
	cs->cv = condvar_init(cs->cv_lock);

	return (COUNTING_SEMAPHORE)cs;
#endif
}

NSAPI_PUBLIC void
cs_terminate(COUNTING_SEMAPHORE csp)
{
	counting_sem_t *cs = (counting_sem_t *)csp;

#if defined(SOLARIS) && defined(HW_THREADS)
    if ( sema_destroy(cs) < 0 ) {
		ereport(LOG_FAILURE, XP_GetAdminStr(DBT_csTerminateFailureS_), system_errmsg());
	}
    PERM_FREE(cs);
	return;
#elif defined(IRIX) && defined(HW_THREADS)
	/* usfreesema() */
	return;
#else
	ns_condvar_terminate(cs->cv);
	crit_terminate(cs->cv_lock);
	crit_terminate(cs->lock);
	PERM_FREE(cs);

	return;
#endif
}

NSAPI_PUBLIC int
cs_wait(COUNTING_SEMAPHORE csp)
{
	counting_sem_t *cs = (counting_sem_t *)csp;
#if defined(SOLARIS) && defined(HW_THREADS)
	int ret;

    if ( (ret = sema_wait(cs)) < 0 ) {
		ereport(LOG_FAILURE, XP_GetAdminStr(DBT_csWaitFailureS_), system_errmsg());
		return -1;
	}
	return ret;
#elif defined(IRIX) && defined(HW_THREADS)
	uspsema(cs);
	return 0;
#else
	crit_enter(cs->lock);
	while ( cs->count == 0 ) {
		crit_enter(cs->cv_lock);
		crit_exit(cs->lock);
		condvar_wait(cs->cv);
		crit_exit(cs->cv_lock);
		crit_enter(cs->lock);
	}
	--(cs->count);
	crit_exit(cs->lock);

	return 0;
#endif
}

NSAPI_PUBLIC int
cs_trywait(COUNTING_SEMAPHORE csp)
{
	counting_sem_t *cs = (counting_sem_t *)csp;

#if defined(SOLARIS) && defined(HW_THREADS)
	int ret;
	ret = sema_trywait(cs)?-1:0;
    return ret;
#elif defined(IRIX) && defined(HW_THREADS)
	int ret;
	ret = uscpsema(cs);
	return (ret == 1)?0:-1;
#else
	crit_enter(cs->lock);
	if (cs->count > 0) {
		--(cs->count);
		crit_exit(cs->lock);
        return 0;
    }
    crit_exit(cs->lock);
	return -1;

#endif
}

NSAPI_PUBLIC int 
cs_release(COUNTING_SEMAPHORE csp)
{
	counting_sem_t *cs = (counting_sem_t *)csp;

#if defined(SOLARIS) && defined(HW_THREADS)
	int ret;
    if ( (ret = sema_post(cs)) < 0 ) {
		ereport(LOG_FAILURE, XP_GetAdminStr(DBT_csPostFailureS_), system_errmsg());
		return -1;
	}
	return ret;
#elif defined(IRIX) && defined(HW_THREADS)
	usvsema(cs);
	return 0;
#else
	crit_enter(cs->lock);
	++(cs->count);
	if (cs->count == 1) {
		crit_enter(cs->cv_lock);
		condvar_notify(cs->cv);
		crit_exit(cs->cv_lock);
	}
	crit_exit(cs->lock);

	return 0;
#endif
}
