umutex.c   [plain text]


/*
******************************************************************************
*
*   Copyright (C) 1997-2004, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
******************************************************************************
*
* File CMUTEX.C
*
* Modification History:
*
*   Date        Name        Description
*   04/02/97    aliu        Creation.
*   04/07/99    srl         updated
*   05/13/99    stephen     Changed to umutex (from cmutex).
*   11/22/99    aliu        Make non-global mutex autoinitialize [j151]
******************************************************************************
*/

/* Assume POSIX, and modify as necessary below */
#define POSIX

#if defined(_WIN32)
#undef POSIX
#endif
#if defined(macintosh)
#undef POSIX
#endif
#if defined(OS2)
#undef POSIX
#endif


#include "unicode/utypes.h"
#include "uassert.h"
#include "ucln_cmn.h"


#if defined(POSIX) && (ICU_USE_THREADS==1)
# include <pthread.h> /* must be first, so that we get the multithread versions of things. */

#endif /* POSIX && (ICU_USE_THREADS==1) */

#ifdef WIN32
# define WIN32_LEAN_AND_MEAN
# define VC_EXTRALEAN
# define NOUSER
# define NOSERVICE
# define NOIME
# define NOMCX
# include <windows.h>
#endif

#include "umutex.h"
#include "cmemory.h"

/*
 * A note on ICU Mutex Initialization and ICU startup:
 *
 *   ICU mutexes, as used through the rest of the ICU code, are self-initializing.
 *   To make this work, ICU uses the _ICU GLobal Mutex_ to synchronize the lazy init
 *   of other ICU mutexes.  For the global mutex itself, we need some other mechanism
 *   to safely initialize it on first use.  This becomes important if two or more
 *   threads were more or less simultaenously the first to use ICU in a process, and
 *   were racing into the mutex initialization code.
 *
 *   The solution for the global mutex init is platform dependent.
 *   On POSIX systems, C-style init can be used on a mutex, with the 
 *   macro PTHREAD_MUTEX_INITIALIZER.  The mutex is then ready for use, without
 *   first calling pthread_mutex_init().
 *
 *   Windows has no equivalent statically initialized mutex or CRITICAL SECION.
 *   InitializeCriticalSection() must be called.  If the global mutex does not
 *   appear to be initialized, a thread will create and initialize a new
 *   CRITICAL_SECTION, then use a Windows InterlockedCompareAndExchange to
 *   avoid problems with race conditions.
 *
 *   If an application has overridden the ICU mutex implementation
 *   by calling u_setMutexFunctions(), the user supplied init function must
 *   be safe in the event that multiple threads concurrently attempt to init
 *   the same mutex.  The first thread should do the init, and the others should
 *   have no effect.
 *
 */ 

#define  MAX_MUTEXES  30
static UMTX              gGlobalMutex          = NULL;
static UMTX              gIncDecMutex          = NULL;       
#if (ICU_USE_THREADS == 1)
static UBool             gMutexPoolInitialized = FALSE;
static char              gMutexesInUse[MAX_MUTEXES];   

#if defined(WIN32) 
/*-------------------------------------------------------------
 *
 *   WINDOWS  platform variable declarations
 *
 *-------------------------------------------------------------*/
static CRITICAL_SECTION  gMutexes[MAX_MUTEXES];
static CRITICAL_SECTION  gGlobalWinMutex;


/* On WIN32 mutexes are reentrant.  This makes it difficult to debug
 * deadlocking problems that show up on POSIXy platforms, where
 * mutexes deadlock upon reentry.  ICU contains checking code for
 * the global mutex as well as for other mutexes in the pool.
 *
 * This is for debugging purposes.
 *
 * This has no effect on non-WIN32 platforms, non-DEBUG builds, and
 * non-ICU_USE_THREADS builds.
 *
 * Note: The CRITICAL_SECTION structure already has a RecursionCount
 * member that can be used for this purpose, but portability to
 * Win98/NT/2K needs to be tested before use.  Works fine on XP.
 * After portability is confirmed, the built-in RecursionCount can be
 * used, and the gRecursionCountPool can be removed.
 *
 * Note: Non-global mutex checking only happens if there is no custom
 * pMutexLockFn defined.  Use one function, not two (don't use
 * pMutexLockFn and pMutexUnlockFn) so the increment and decrement of
 * the recursion count don't get out of sync.  Users might set just
 * one function, e.g., to perform a custom action, followed by a
 * standard call to EnterCriticalSection.
 */
#if defined(U_DEBUG) && (ICU_USE_THREADS==1)
static int32_t gRecursionCount = 0; /* detect global mutex locking */      
static int32_t gRecursionCountPool[MAX_MUTEXES]; /* ditto for non-global */
#endif


#elif defined(POSIX) 
/*-------------------------------------------------------------
 *
 *   POSIX   platform variable declarations
 *
 *-------------------------------------------------------------*/
static pthread_mutex_t   gMutexes[MAX_MUTEXES] = {
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER
};

#else 
/*-------------------------------------------------------------
 *
 *   UNKNOWN   platform  declarations
 *
 *-------------------------------------------------------------*/
static void *gMutexes[MAX_MUTEXES] = {
    NULL, NULL, NULL,
    NULL, NULL, NULL,
    NULL, NULL, NULL,
    NULL, NULL, NULL,
    NULL, NULL, NULL,
    NULL, NULL, NULL,
    NULL, NULL };

/* Unknown platform.  OK so long as ICU_USE_THREAD is not set.  
                      Note that user can still set mutex functions at run time,
                      and that the global mutex variable is still needed in that case. */
#if (ICU_USE_THREADS == 1)
#error no ICU mutex implementation for this platform
#endif
#endif
#endif /* ICU_USE_THREADS==1 */




/*
 *  User mutex implementation functions.  If non-null, call back to these rather than
 *  directly using the system (Posix or Windows) APIs.
 *    (declarations are in uclean.h)
 */
static UMtxInitFn    *pMutexInitFn    = NULL;
static UMtxFn        *pMutexDestroyFn = NULL;
static UMtxFn        *pMutexLockFn    = NULL;
static UMtxFn        *pMutexUnlockFn  = NULL;
static const void    *gMutexContext   = NULL;



/*
 *   umtx_lock
 */
U_CAPI void  U_EXPORT2
umtx_lock(UMTX *mutex)
{
    if (mutex == NULL) {
        mutex = &gGlobalMutex;
    }

    if (*mutex == NULL) {
        /* Lock of an uninitialized mutex.  Initialize it before proceeding.   */
        umtx_init(mutex);    
    }

    if (pMutexLockFn != NULL) {
        (*pMutexLockFn)(gMutexContext, mutex);
    } else {

#if (ICU_USE_THREADS == 1)
#if defined(WIN32)
        EnterCriticalSection((CRITICAL_SECTION*) *mutex);
#elif defined(POSIX)
        pthread_mutex_lock((pthread_mutex_t*) *mutex);
#endif   /* cascade of platforms */
#endif /* ICU_USE_THREADS==1 */
    }

#if defined(WIN32) && defined(U_DEBUG) && (ICU_USE_THREADS==1)
    if (mutex == &gGlobalMutex) {         /* Detect Reentrant locking of the global mutex.      */
        gRecursionCount++;                /* Recursion causes deadlocks on Unixes.              */
        U_ASSERT(gRecursionCount == 1);   /* Detection works on Windows.  Debug problems there. */
    }
    /* This handles gGlobalMutex too, but only if there is no pMutexLockFn */
    else if (pMutexLockFn == NULL) { /* see comments above */
        int i = ((CRITICAL_SECTION*)*mutex) - &gMutexes[0];
        U_ASSERT(i >= 0 && i < MAX_MUTEXES);
        ++gRecursionCountPool[i];

        U_ASSERT(gRecursionCountPool[i] == 1); /* !Detect Deadlock! */

        /* This works and is fast, but needs testing on Win98/NT/2K.
           See comments above. [alan]
          U_ASSERT((CRITICAL_SECTION*)*mutex >= &gMutexes[0] &&
                   (CRITICAL_SECTION*)*mutex <= &gMutexes[MAX_MUTEXES]);
          U_ASSERT(((CRITICAL_SECTION*)*mutex)->RecursionCount == 1);
        */
    }
#endif /*U_DEBUG*/
}



/*
 * umtx_unlock
 */
U_CAPI void  U_EXPORT2
umtx_unlock(UMTX* mutex)
{
    if(mutex == NULL) {
        mutex = &gGlobalMutex;
    }

    if(*mutex == NULL)    {
#if (ICU_USE_THREADS == 1)
        U_ASSERT(FALSE);  /* This mutex is not initialized.     */
#endif
        return; 
    }

#if defined (WIN32) && defined (U_DEBUG) && (ICU_USE_THREADS==1)
    if (mutex == &gGlobalMutex) {
        gRecursionCount--;
        U_ASSERT(gRecursionCount == 0);  /* Detect unlock of an already unlocked mutex */
    }
    /* This handles gGlobalMutex too, but only if there is no pMutexLockFn */
    else if (pMutexLockFn == NULL) { /* see comments above */
        int i = ((CRITICAL_SECTION*)*mutex) - &gMutexes[0];
        U_ASSERT(i >= 0 && i < MAX_MUTEXES);
        --gRecursionCountPool[i];

        U_ASSERT(gRecursionCountPool[i] == 0); /* !Detect Deadlock! */

        /* This works and is fast, but needs testing on Win98/NT/2K.
           Note that RecursionCount will be 1, not 0, since we haven't
           left the CRITICAL_SECTION yet.  See comments above. [alan]
          U_ASSERT((CRITICAL_SECTION*)*mutex >= &gMutexes[0] &&
                   (CRITICAL_SECTION*)*mutex <= &gMutexes[MAX_MUTEXES]);
          U_ASSERT(((CRITICAL_SECTION*)*mutex)->RecursionCount == 1);
        */
    }
#endif

    if (pMutexUnlockFn) {
        (*pMutexUnlockFn)(gMutexContext, mutex);
    } else {
#if (ICU_USE_THREADS==1)
#if defined (WIN32)
        LeaveCriticalSection((CRITICAL_SECTION*)*mutex);
#elif defined (POSIX)
        pthread_mutex_unlock((pthread_mutex_t*)*mutex);
#endif  /* cascade of platforms */
#endif /* ICU_USE_THREADS == 1 */
    }
}




/*
 *   initGlobalMutex    Do the platform specific initialization of the ICU global mutex.
 *                      Separated out from the other mutexes because it is different:
 *                      Mutex storage is static for POSIX, init must be thread safe 
 *                      without the use of another mutex.
 */
static void initGlobalMutex() {
    /*
     * If User Supplied mutex functions are in use
     *    init the icu global mutex using them.  
     */
    if (pMutexInitFn != NULL) {
        if (gGlobalMutex==NULL) {
            UErrorCode status = U_ZERO_ERROR;
            (*pMutexInitFn)(gMutexContext, &gGlobalMutex, &status);
            if (U_FAILURE(status)) {
                /* TODO:  how should errors here be handled? */
                return;
            }
        }
        return;
    }

    /* No user override of mutex functions.
     *   Use default ICU mutex implementations.
     */
#if (ICU_USE_THREADS == 1)
    /*
     *  for Windows, init the pool of critical sections that we
     *    will use as needed for ICU mutexes.
     */
#if defined (WIN32)
    if (gMutexPoolInitialized == FALSE) {
        int i;
        for (i=0; i<MAX_MUTEXES; i++) {
            InitializeCriticalSection(&gMutexes[i]);
#if defined (U_DEBUG)
            gRecursionCountPool[i] = 0; /* see comments above */
#endif
        }
        gMutexPoolInitialized = TRUE;
    }
#elif defined (POSIX)
    /*  TODO:  experimental code.  Shouldn't need to explicitly init the mutexes. */
    if (gMutexPoolInitialized == FALSE) {
        int i;
        for (i=0; i<MAX_MUTEXES; i++) {
            pthread_mutex_init(&gMutexes[i], NULL);
        }
        gMutexPoolInitialized = TRUE;
    }
#endif 

    /*
     * for both Windows & POSIX, the first mutex in the array is used
     *   for the ICU global mutex.
     */
    gGlobalMutex = &gMutexes[0];
    gMutexesInUse[0] = 1;

#else  /* ICU_USE_THREADS */
    gGlobalMutex = &gGlobalMutex;  /* With no threads, we must still set the mutex to
                                    * some non-null value to make the rest of the
                                    *   (not ifdefed) mutex code think that it is initialized.
                                    */
#endif /* ICU_USE_THREADS */
}





U_CAPI void  U_EXPORT2
umtx_init(UMTX *mutex)
{
    if (mutex == NULL || mutex == &gGlobalMutex) {
        initGlobalMutex();
    } else {
        umtx_lock(NULL);
        if (*mutex != NULL) {
            /* Another thread initialized this mutex first. */
            umtx_unlock(NULL);
            return;
        }

        if (pMutexInitFn != NULL) {
            UErrorCode status = U_ZERO_ERROR;
            (*pMutexInitFn)(gMutexContext, mutex, &status);
            /* TODO:  how to report failure on init? */
            umtx_unlock(NULL);
            return;
        }
        else {
#if (ICU_USE_THREADS == 1)
            /*  Search through our pool of pre-allocated mutexes for one that is not
            *  already in use.    */
            int i;
            for (i=0; i<MAX_MUTEXES; i++) {
                if (gMutexesInUse[i] == 0) {
                    gMutexesInUse[i] = 1;
                    *mutex = &gMutexes[i];
                    break;
                }
            }
#endif
        }
        umtx_unlock(NULL);

#if (ICU_USE_THREADS == 1)
        /* No more mutexes were available from our pre-allocated pool.  */
        /*   TODO:  how best to deal with this?                    */
        U_ASSERT(*mutex != NULL);
#endif
    }
}


/*
 *  umtx_destroy.    Un-initialize a mutex, releasing any underlying resources
 *                   that it may be holding.  Destroying an already destroyed
 *                   mutex has no effect.  Unlike umtx_init(), this function
 *                   is not thread safe;  two threads must not concurrently try to
 *                   destroy the same mutex.
 */                  
U_CAPI void  U_EXPORT2
umtx_destroy(UMTX *mutex) {
    if (mutex == NULL) {  /* destroy the global mutex */
        mutex = &gGlobalMutex;
    }
    
    if (*mutex == NULL) {  /* someone already did it. */
        return;
    }

    /*  The life of the inc/dec mutex is tied to that of the global mutex.  */
    if (mutex == &gGlobalMutex) {
        umtx_destroy(&gIncDecMutex);
    }

    if (pMutexDestroyFn != NULL) {
        /* Mutexes are being managed by the app.  Call back to it for the destroy. */
        (*pMutexDestroyFn)(gMutexContext, mutex);
    }
    else {
#if (ICU_USE_THREADS == 1)
        /* Return this mutex to the pool of available mutexes, if it came from the
         *  pool in the first place.
         */
        /* TODO use pointer math here, instead of iterating! */
        int i;
        for (i=0; i<MAX_MUTEXES; i++)  {
            if (*mutex == &gMutexes[i]) {
                gMutexesInUse[i] = 0;
                break;
            }
        }
#endif
    }

    *mutex = NULL;
}



U_CAPI void U_EXPORT2 
u_setMutexFunctions(const void *context, UMtxInitFn *i, UMtxFn *d, UMtxFn *l, UMtxFn *u,
                    UErrorCode *status) {
    if (U_FAILURE(*status)) {
        return;
    }

    /* Can not set a mutex function to a NULL value  */
    if (i==NULL || d==NULL || l==NULL || u==NULL) {
        *status = U_ILLEGAL_ARGUMENT_ERROR;
        return;
    }

    /* If ICU is not in an initial state, disallow this operation. */
    if (cmemory_inUse()) {
        *status = U_INVALID_STATE_ERROR;
        return;
    }

    /* Swap in the mutex function pointers.  */
    pMutexInitFn    = i;
    pMutexDestroyFn = d;
    pMutexLockFn    = l;
    pMutexUnlockFn  = u;
    gMutexContext   = context;
    gGlobalMutex    = NULL;         /* For POSIX, the global mutex will be pre-initialized */
                                    /*   Undo that, force re-initialization when u_init()  */
                                    /*   happens.                                          */
}



/*-----------------------------------------------------------------
 *
 *  Atomic Increment and Decrement
 *     umtx_atomic_inc
 *     umtx_atomic_dec
 *
 *----------------------------------------------------------------*/

/* Pointers to user-supplied inc/dec functions.  Null if no funcs have been set.  */
static UMtxAtomicFn  *pIncFn = NULL;
static UMtxAtomicFn  *pDecFn = NULL;
static void *gIncDecContext  = NULL;


U_CAPI int32_t U_EXPORT2
umtx_atomic_inc(int32_t *p)  {
    int32_t retVal;
    if (pIncFn) {
        retVal = (*pIncFn)(gIncDecContext, p);
    } else {
        #if defined (WIN32) && ICU_USE_THREADS == 1
            retVal = InterlockedIncrement((LONG*)p);
        #elif defined (POSIX) && ICU_USE_THREADS == 1
            umtx_lock(&gIncDecMutex);
            retVal = ++(*p);
            umtx_unlock(&gIncDecMutex);
        #else
            /* Unknown Platform, or ICU thread support compiled out. */
            retVal = ++(*p);
        #endif
    }
    return retVal;
}

U_CAPI int32_t U_EXPORT2
umtx_atomic_dec(int32_t *p) {
    int32_t retVal;
    if (pDecFn) {
        retVal = (*pDecFn)(gIncDecContext, p);
    } else {
        #if defined (WIN32) && ICU_USE_THREADS == 1
            retVal = InterlockedDecrement((LONG*)p);
        #elif defined (POSIX) && ICU_USE_THREADS == 1
            umtx_lock(&gIncDecMutex);
            retVal = --(*p);
            umtx_unlock(&gIncDecMutex);
        #else
            /* Unknown Platform, or ICU thread support compiled out. */
            retVal = --(*p);
        #endif
    }
    return retVal;
}

/* TODO:  Some POSIXy platforms have atomic inc/dec functions available.  Use them. */





U_CAPI void U_EXPORT2
u_setAtomicIncDecFunctions(const void *context, UMtxAtomicFn *ip, UMtxAtomicFn *dp,
                                UErrorCode *status) {
    int32_t   testInt;
    if (U_FAILURE(*status)) {
        return;
    }
    /* Can not set a mutex function to a NULL value  */
    if (ip==NULL || dp==NULL) {
        *status = U_ILLEGAL_ARGUMENT_ERROR;
        return;
    }
    /* If ICU is not in an initial state, disallow this operation. */
    if (cmemory_inUse()) {
        *status = U_INVALID_STATE_ERROR;
        return;
    }

    pIncFn = ip;
    pDecFn = dp;

    testInt = 0;
    U_ASSERT(umtx_atomic_inc(&testInt) == 1);     /* Sanity Check.    Do the functions work at all? */
    U_ASSERT(testInt == 1);
    U_ASSERT(umtx_atomic_dec(&testInt) == 0);
    U_ASSERT(testInt == 0);
}



/*
 *  Mutex Cleanup Function
 *
 *      Destroy the global mutex(es), and reset the mutex function callback pointers.
 */
U_CFUNC UBool umtx_cleanup(void) {
    umtx_destroy(NULL);
    pMutexInitFn    = NULL;
    pMutexDestroyFn = NULL;
    pMutexLockFn    = NULL;
    pMutexUnlockFn  = NULL;
    gMutexContext   = NULL;
    gGlobalMutex    = NULL;
    pIncFn          = NULL;
    pDecFn          = NULL;
    gIncDecMutex    = NULL;

#if (ICU_USE_THREADS == 1)
    if (gMutexPoolInitialized) {
        int i;
        for (i=0; i<MAX_MUTEXES; i++) {
            if (gMutexesInUse[i]) {
#if defined (WIN32)
                DeleteCriticalSection(&gMutexes[i]);
#elif defined (POSIX)
                pthread_mutex_destroy(&gMutexes[i]);
#endif
                gMutexesInUse[i] = 0;
            }
        }
    }
    gMutexPoolInitialized = FALSE;
#endif

    return TRUE;
}