/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */

/* 
 * rwlock.c --
 *
 *	Routines for read/write locks.  Read/write locks differ from a mutex
 *	in that multiple threads can aquire the read lock until a single
 *	thread aquires a write lock.  This code is adapted from that in 
 *	Steven's Unix Network Programming, Volume 3. 
 *
 *	Note:  Read/write locks are not often a good idea.  The reason
 *	is, like critical sections, the number of actual lock operations
 *	is doubled which makes them more expensive to use.  Cases where the
 *	overhead are justified are then often subject to read locks being 
 *	held longer than writer threads can wait and/or writer threads holding
 *	the lock so long that many reader threads back up.  In these cases,
 *	specific reference counting techniques (e.g., the management of
 *	the Req structures in op.c) normally work better.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsthread/rwlock.c,v 1.2 2002/06/12 11:30:44 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "thread.h"

/*
 * The following structure defines a read/write lock including a mutex
 * to protect access to the structure and condition variables for waiting
 * reader and writer threads.
 */

typedef struct RwLock {
    Ns_Mutex  mutex;    /* Mutex guarding lock structure. */
    Ns_Cond   rcond;    /* Condition variable for waiting readers. */
    Ns_Cond   wcond;    /* condition variable for waiting writers. */
    int       nreaders; /* Number of readers waiting for lock. */
    int       nwriters; /* Number of writers waiting for lock. */
    int       lockcnt;  /* Lock count, > 0 indicates # of shared
			 * readers, -1 indicates exclusive writer. */
} RwLock;

static RwLock *GetRwLock(Ns_RWLock *rwPtr);


/*
 *----------------------------------------------------------------------
 *
 * Ns_RWLockInit --
 *
 *	Initialize a read/write lock.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Lock memory is allocated from the heap and initialized.
 *
 *----------------------------------------------------------------------
 */
	
void
Ns_RWLockInit(Ns_RWLock *rwPtr)
{
    RwLock *lockPtr;
    static unsigned int nextid = 0;
    
    lockPtr = ns_calloc(1, sizeof(RwLock));
    NsMutexInitNext(&lockPtr->mutex, "rw", &nextid);
    Ns_CondInit(&lockPtr->rcond);
    Ns_CondInit(&lockPtr->wcond);
    lockPtr->nreaders = 0;
    lockPtr->nwriters = 0;
    lockPtr->lockcnt = 0;
    *rwPtr = (Ns_RWLock) lockPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_RWLockDestroy --
 *
 *	Destory a read/write lock if it was previously initialized.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Read/write lock objects are destroy and the lock memory is
 *	returned to the heap.
 *
 *----------------------------------------------------------------------
 */

void
Ns_RWLockDestroy(Ns_RWLock *rwPtr)
{
    RwLock *lockPtr = (RwLock *) *rwPtr;

    if (lockPtr != NULL) {
    	Ns_MutexDestroy(&lockPtr->mutex);
    	Ns_CondDestroy(&lockPtr->rcond);
    	Ns_CondDestroy(&lockPtr->wcond);
    	ns_free(lockPtr);
    	*rwPtr = NULL;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_RWLockRdLock --
 *
 *	Aquire a read lock.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Thread may wait on a condition variable if the read/write lock
 *	currently has a write lock.
 *
 *----------------------------------------------------------------------
 */

void
Ns_RWLockRdLock(Ns_RWLock *rwPtr)
{
    RwLock *lockPtr = GetRwLock(rwPtr);

    Ns_MutexLock(&lockPtr->mutex);

    /*
     * Wait on the read condition while the lock is write-locked or
     * some other thread is waiting for a write lock.
     */

    while (lockPtr->lockcnt < 0 || lockPtr->nwriters > 0) {
	lockPtr->nreaders++;
	Ns_CondWait(&lockPtr->rcond, &lockPtr->mutex);
	lockPtr->nreaders--;
    }

    lockPtr->lockcnt++;
    Ns_MutexUnlock(&lockPtr->mutex);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_RWLockWrLock --
 *
 *	Aquire a write lock.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Thread may wait on the write condition if other threads either
 *	have the lock read or write locked.
 *
 *----------------------------------------------------------------------
 */

void
Ns_RWLockWrLock(Ns_RWLock *rwPtr)
{
    RwLock *lockPtr = GetRwLock(rwPtr);

    Ns_MutexLock(&lockPtr->mutex);
    while (lockPtr->lockcnt != 0) {
	lockPtr->nwriters++;
	Ns_CondWait(&lockPtr->wcond, &lockPtr->mutex);
	lockPtr->nwriters--;
    }
    lockPtr->lockcnt = -1;
    Ns_MutexUnlock(&lockPtr->mutex);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_RWLockUnlock --
 *
 *	Unlock a read/write lock.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Read or write condition may be signaled.
 *
 *----------------------------------------------------------------------
 */

void
Ns_RWLockUnlock(Ns_RWLock *rwPtr)
{
    RwLock *lockPtr = (RwLock *) *rwPtr;

    Ns_MutexLock(&lockPtr->mutex);
    if (--lockPtr->lockcnt < 0) {
	lockPtr->lockcnt = 0;
    }
    if (lockPtr->nwriters) {
	Ns_CondSignal(&lockPtr->wcond);
    } else if (lockPtr->nreaders) {
	Ns_CondBroadcast(&lockPtr->rcond);
    }
    Ns_MutexUnlock (&lockPtr->mutex);
}


/*
 *----------------------------------------------------------------------
 *
 * GetRwLock --
 *
 *	Return the read/write lock structure, initializing it if needed.
 *
 * Results:
 *	Pointer to lock.
 *
 * Side effects:
 *	Lock may be initialized.
 *
 *----------------------------------------------------------------------
 */

static RwLock *
GetRwLock(Ns_RWLock *rwPtr)
{
    if (*rwPtr == NULL) {
	Ns_MasterLock();
	if (*rwPtr == NULL) {
	    Ns_RWLockInit(rwPtr);
	}
	Ns_MasterUnlock();
    }
    return (RwLock *) *rwPtr;
}
