/* ****************************************************************************** * * 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 /* 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 NOGDI # define NOUSER # define NOSERVICE # define NOIME # define NOMCX # include #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