/* ****************************************************************************** * * Copyright (C) 1997-2006, 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] ****************************************************************************** */ #include "unicode/utypes.h" #include "uassert.h" #include "ucln_cmn.h" #if defined(U_DARWIN) #include <AvailabilityMacros.h> #if (ICU_USE_THREADS == 1) && defined(MAC_OS_X_VERSION_10_4) && defined(MAC_OS_X_VERSION_MIN_REQUIRED) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) #include <libkern/OSAtomic.h> #define USE_MAC_OS_ATOMIC_INCREMENT 1 #endif #endif /* Assume POSIX, and modify as necessary below */ #define POSIX #if defined(U_WINDOWS) #undef POSIX #endif #if defined(macintosh) #undef POSIX #endif #if defined(OS2) #undef POSIX #endif #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 U_WINDOWS # 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(U_WINDOWS) /*------------------------------------------------------------- * * 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(U_WINDOWS) 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(U_WINDOWS) && 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 */ size_t 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 (U_WINDOWS) && 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 */ size_t 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 (U_WINDOWS) 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 (U_WINDOWS) 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 const 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 (U_WINDOWS) && ICU_USE_THREADS == 1 retVal = InterlockedIncrement((LONG*)p); #elif defined(USE_MAC_OS_ATOMIC_INCREMENT) retVal = OSAtomicIncrement32Barrier(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 (U_WINDOWS) && ICU_USE_THREADS == 1 retVal = InterlockedDecrement((LONG*)p); #elif defined(USE_MAC_OS_ATOMIC_INCREMENT) retVal = OSAtomicDecrement32Barrier(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) { 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; gIncDecContext = context; #if !U_RELEASE { int32_t 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); } #endif } /* * 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; gIncDecContext = NULL; gIncDecMutex = NULL; #if (ICU_USE_THREADS == 1) if (gMutexPoolInitialized) { int i; for (i=0; i<MAX_MUTEXES; i++) { if (gMutexesInUse[i]) { #if defined (U_WINDOWS) DeleteCriticalSection(&gMutexes[i]); #elif defined (POSIX) pthread_mutex_destroy(&gMutexes[i]); #endif gMutexesInUse[i] = 0; } } } gMutexPoolInitialized = FALSE; #endif return TRUE; }