/* ****************************************************************************** * * 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; }