diff options
Diffstat (limited to 'intl/icu/source/common/umutex.cpp')
-rw-r--r-- | intl/icu/source/common/umutex.cpp | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/intl/icu/source/common/umutex.cpp b/intl/icu/source/common/umutex.cpp new file mode 100644 index 0000000..a7299a5 --- /dev/null +++ b/intl/icu/source/common/umutex.cpp @@ -0,0 +1,483 @@ +/* +****************************************************************************** +* +* Copyright (C) 1997-2012, International Business Machines +* Corporation and others. All Rights Reserved. +* +****************************************************************************** +* +* File umutex.cpp +* +* 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" + +/* + * ICU Mutex wrappers. Wrap operating system mutexes, giving the rest of ICU a + * platform independent set of mutex operations. For internal ICU use only. + */ + +#if U_PLATFORM_HAS_WIN32_API + /* Prefer native Windows APIs even if POSIX is implemented (i.e., on Cygwin). */ +# undef POSIX +#elif U_PLATFORM_IMPLEMENTS_POSIX +# define POSIX +#else +# undef POSIX +#endif + +#if defined(POSIX) +# include <pthread.h> /* must be first, so that we get the multithread versions of things. */ +#endif /* POSIX */ + +#if U_PLATFORM_HAS_WIN32_API +# 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" + +#if U_PLATFORM_HAS_WIN32_API +#define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ + InterlockedCompareExchangePointer(dest, newval, oldval) + +#elif defined(POSIX) +#if (U_HAVE_GCC_ATOMICS == 1) +#define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ + __sync_val_compare_and_swap(dest, oldval, newval) +#else +#define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ + mutexed_compare_and_swap(dest, newval, oldval) +#endif + +#else +// Unknown platform. Note that user can still set mutex functions at run time. +#define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ + mutexed_compare_and_swap(dest, newval, oldval) +#endif + +static void *mutexed_compare_and_swap(void **dest, void *newval, void *oldval); + +// The ICU global mutex. Used when ICU implementation code passes NULL for the mutex pointer. +static UMutex globalMutex = U_MUTEX_INITIALIZER; + +// Implementation mutex. Used for compare & swap when no intrinsic is available, and +// for safe initialization of user defined mutexes. +static UMutex implMutex = U_MUTEX_INITIALIZER; + +// List of all user mutexes that have been initialized. +// Used to allow us to destroy them when cleaning up ICU. +// Normal platform mutexes are not kept track of in this way - they survive until the process is shut down. +// Normal platfrom mutexes don't allocate storage, so not cleaning them up won't trigger memory leak complaints. +// +// Note: putting this list in allocated memory would be awkward to arrange, because memory allocations +// are used as a flag to indicate that ICU has been initialized, and setting other ICU +// override functions will no longer work. +// +static const int MUTEX_LIST_LIMIT = 100; +static UMutex *gMutexList[MUTEX_LIST_LIMIT]; +static int gMutexListSize = 0; + + +/* + * User mutex implementation functions. If non-null, call back to these rather than + * directly using the system (Posix or Windows) APIs. See u_setMutexFunctions(). + * (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; + + +// Clean up (undo) the effects of u_setMutexFunctions(). +// +static void usrMutexCleanup() { + if (pMutexDestroyFn != NULL) { + for (int i = 0; i < gMutexListSize; i++) { + UMutex *m = gMutexList[i]; + U_ASSERT(m->fInitialized); + (*pMutexDestroyFn)(gMutexContext, &m->fUserMutex); + m->fInitialized = FALSE; + } + (*pMutexDestroyFn)(gMutexContext, &globalMutex.fUserMutex); + (*pMutexDestroyFn)(gMutexContext, &implMutex.fUserMutex); + } + gMutexListSize = 0; + pMutexInitFn = NULL; + pMutexDestroyFn = NULL; + pMutexLockFn = NULL; + pMutexUnlockFn = NULL; + gMutexContext = NULL; +} + + +/* + * User mutex lock. + * + * User mutexes need to be initialized before they can be used. We use the impl mutex + * to synchronize the initialization check. This could be sped up on platforms that + * support alternate ways to safely check the initialization flag. + * + */ +static void usrMutexLock(UMutex *mutex) { + UErrorCode status = U_ZERO_ERROR; + if (!(mutex == &implMutex || mutex == &globalMutex)) { + umtx_lock(&implMutex); + if (!mutex->fInitialized) { + (*pMutexInitFn)(gMutexContext, &mutex->fUserMutex, &status); + U_ASSERT(U_SUCCESS(status)); + mutex->fInitialized = TRUE; + U_ASSERT(gMutexListSize < MUTEX_LIST_LIMIT); + if (gMutexListSize < MUTEX_LIST_LIMIT) { + gMutexList[gMutexListSize] = mutex; + ++gMutexListSize; + } + } + umtx_unlock(&implMutex); + } + (*pMutexLockFn)(gMutexContext, &mutex->fUserMutex); +} + + + +#if defined(POSIX) + +// +// POSIX implementation of UMutex. +// +// Each UMutex has a corresponding pthread_mutex_t. +// All are statically initialized and ready for use. +// There is no runtime mutex initialization code needed. + +U_CAPI void U_EXPORT2 +umtx_lock(UMutex *mutex) { + if (mutex == NULL) { + mutex = &globalMutex; + } + if (pMutexLockFn) { + usrMutexLock(mutex); + } else { + #if U_DEBUG + // #if to avoid unused variable warnings in non-debug builds. + int sysErr = pthread_mutex_lock(&mutex->fMutex); + U_ASSERT(sysErr == 0); + #else + pthread_mutex_lock(&mutex->fMutex); + #endif + } +} + + +U_CAPI void U_EXPORT2 +umtx_unlock(UMutex* mutex) +{ + if (mutex == NULL) { + mutex = &globalMutex; + } + if (pMutexUnlockFn) { + (*pMutexUnlockFn)(gMutexContext, &mutex->fUserMutex); + } else { + #if U_DEBUG + // #if to avoid unused variable warnings in non-debug builds. + int sysErr = pthread_mutex_unlock(&mutex->fMutex); + U_ASSERT(sysErr == 0); + #else + pthread_mutex_unlock(&mutex->fMutex); + #endif + } +} + +#elif U_PLATFORM_HAS_WIN32_API +// +// Windows implementation of UMutex. +// +// Each UMutex has a corresponding Windows CRITICAL_SECTION. +// CRITICAL_SECTIONS must be initialized before use. This is done +// with a InitOnceExcuteOnce operation. +// +// InitOnceExecuteOnce was introduced with Windows Vista. For now ICU +// must support Windows XP, so we roll our own. ICU will switch to the +// native Windows InitOnceExecuteOnce when possible. + +typedef UBool (*U_PINIT_ONCE_FN) ( + U_INIT_ONCE *initOnce, + void *parameter, + void **context +); + +UBool u_InitOnceExecuteOnce( + U_INIT_ONCE *initOnce, + U_PINIT_ONCE_FN initFn, + void *parameter, + void **context) { + for (;;) { + long previousState = InterlockedCompareExchange( + &initOnce->fState, // Destination, + 1, // Exchange Value + 0); // Compare value + if (previousState == 2) { + // Initialization was already completed. + if (context != NULL) { + *context = initOnce->fContext; + } + return TRUE; + } + if (previousState == 1) { + // Initialization is in progress in some other thread. + // Loop until it completes. + Sleep(1); + continue; + } + + // Initialization needed. Execute the callback function to do it. + U_ASSERT(previousState == 0); + U_ASSERT(initOnce->fState == 1); + UBool success = (*initFn)(initOnce, parameter, &initOnce->fContext); + U_ASSERT(success); // ICU is not supporting the failure case. + + // Assign the state indicating that initialization has completed. + // Using InterlockedCompareExchange to do it ensures that all + // threads will have a consistent view of memory. + previousState = InterlockedCompareExchange(&initOnce->fState, 2, 1); + U_ASSERT(previousState == 1); + // Next loop iteration will see the initialization and return. + } +}; + +static UBool winMutexInit(U_INIT_ONCE *initOnce, void *param, void **context) { + UMutex *mutex = static_cast<UMutex *>(param); + U_ASSERT(sizeof(CRITICAL_SECTION) <= sizeof(mutex->fCS)); + InitializeCriticalSection((CRITICAL_SECTION *)mutex->fCS); + return TRUE; +} + +/* + * umtx_lock + */ +U_CAPI void U_EXPORT2 +umtx_lock(UMutex *mutex) { + if (mutex == NULL) { + mutex = &globalMutex; + } + if (pMutexLockFn) { + usrMutexLock(mutex); + } else { + u_InitOnceExecuteOnce(&mutex->fInitOnce, winMutexInit, mutex, NULL); + EnterCriticalSection((CRITICAL_SECTION *)mutex->fCS); + } +} + +U_CAPI void U_EXPORT2 +umtx_unlock(UMutex* mutex) +{ + if (mutex == NULL) { + mutex = &globalMutex; + } + if (pMutexUnlockFn) { + (*pMutexUnlockFn)(gMutexContext, &mutex->fUserMutex); + } else { + LeaveCriticalSection((CRITICAL_SECTION *)mutex->fCS); + } +} + +#endif // Windows Implementation + + +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; + } + + // Clean up any previously set user mutex functions. + // It's possible to call u_setMutexFunctions() more than once without without explicitly cleaning up, + // and the last call should take. Kind of a corner case, but it worked once, there is a test for + // it, so we keep it working. The global and impl mutexes will have been created by the + // previous u_setMutexFunctions(), and now need to be destroyed. + + usrMutexCleanup(); + + /* Swap in the mutex function pointers. */ + pMutexInitFn = i; + pMutexDestroyFn = d; + pMutexLockFn = l; + pMutexUnlockFn = u; + gMutexContext = context; + gMutexListSize = 0; + + /* Initialize the global and impl mutexes. Safe to do at this point because + * u_setMutexFunctions must be done in a single-threaded envioronment. Not thread safe. + */ + (*pMutexInitFn)(gMutexContext, &globalMutex.fUserMutex, status); + globalMutex.fInitialized = TRUE; + (*pMutexInitFn)(gMutexContext, &implMutex.fUserMutex, status); + implMutex.fInitialized = TRUE; +} + + + +/* synchronized compare and swap function, for use when OS or compiler built-in + * equivalents aren't available. + */ +static void *mutexed_compare_and_swap(void **dest, void *newval, void *oldval) { + umtx_lock(&implMutex); + void *temp = *dest; + if (temp == oldval) { + *dest = newval; + } + umtx_unlock(&implMutex); + + return temp; +} + + + +/*----------------------------------------------------------------- + * + * 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; + +#if defined (POSIX) && (U_HAVE_GCC_ATOMICS == 0) +static UMutex gIncDecMutex = U_MUTEX_INITIALIZER; +#endif + +U_CAPI int32_t U_EXPORT2 +umtx_atomic_inc(int32_t *p) { + int32_t retVal; + if (pIncFn) { + retVal = (*pIncFn)(gIncDecContext, p); + } else { + #if U_PLATFORM_HAS_WIN32_API + retVal = InterlockedIncrement((LONG*)p); + #elif defined(USE_MAC_OS_ATOMIC_INCREMENT) + retVal = OSAtomicIncrement32Barrier(p); + #elif (U_HAVE_GCC_ATOMICS == 1) + retVal = __sync_add_and_fetch(p, 1); + #elif defined (POSIX) + umtx_lock(&gIncDecMutex); + retVal = ++(*p); + umtx_unlock(&gIncDecMutex); + #else + /* Unknown Platform. */ + 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 U_PLATFORM_HAS_WIN32_API + retVal = InterlockedDecrement((LONG*)p); + #elif defined(USE_MAC_OS_ATOMIC_INCREMENT) + retVal = OSAtomicDecrement32Barrier(p); + #elif (U_HAVE_GCC_ATOMICS == 1) + retVal = __sync_sub_and_fetch(p, 1); + #elif defined (POSIX) + umtx_lock(&gIncDecMutex); + retVal = --(*p); + umtx_unlock(&gIncDecMutex); + #else + /* Unknown Platform. */ + retVal = --(*p); + #endif + } + return retVal; +} + + + +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_DEBUG + { + 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 + * Reset the mutex function callback pointers. + * Called from the global ICU u_cleanup() function. + */ +U_CFUNC UBool umtx_cleanup(void) { + /* Extra, do-nothing function call to suppress compiler warnings on platforms where + * mutexed_compare_and_swap is not otherwise used. */ + void *pv = &globalMutex; + mutexed_compare_and_swap(&pv, NULL, NULL); + usrMutexCleanup(); + + pIncFn = NULL; + pDecFn = NULL; + gIncDecContext = NULL; + + return TRUE; +} |