X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/51004dcb01e06fef634b61be77ed73dd61cb6db9..3d1f044b704633e2e541231cd17ae9ecf9ad5c7a:/icuSources/common/umutex.cpp diff --git a/icuSources/common/umutex.cpp b/icuSources/common/umutex.cpp index a7299a5c..774071ee 100644 --- a/icuSources/common/umutex.cpp +++ b/icuSources/common/umutex.cpp @@ -1,7 +1,9 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html /* ****************************************************************************** * -* Copyright (C) 1997-2012, International Business Machines +* Copyright (C) 1997-2016, International Business Machines * Corporation and others. All Rights Reserved. * ****************************************************************************** @@ -18,466 +20,135 @@ ****************************************************************************** */ +#include "umutex.h" + #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 /* 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 -#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) +U_NAMESPACE_BEGIN -#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) +#if defined(U_USER_MUTEX_CPP) +// Support for including an alternate implementation of mutexes has been withdrawn. +// See issue ICU-20185. +#error U_USER_MUTEX_CPP not supported #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. + * ICU Mutex wrappers. * - */ -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. +// The ICU global mutex. Used when ICU implementation code passes NULL for the mutex pointer. +static UMutex *globalMutex() { + static UMutex *m = STATIC_NEW(UMutex); + return m; +} 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 + if (mutex == nullptr) { + mutex = globalMutex(); } + mutex->fMutex.lock(); } 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 + if (mutex == nullptr) { + mutex = globalMutex(); } + mutex->fMutex.unlock(); } -#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(param); - U_ASSERT(sizeof(CRITICAL_SECTION) <= sizeof(mutex->fCS)); - InitializeCriticalSection((CRITICAL_SECTION *)mutex->fCS); - return TRUE; -} +/************************************************************************************************* + * + * UInitOnce Implementation + * + *************************************************************************************************/ -/* - * 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); - } +static std::mutex &initMutex() { + static std::mutex *m = STATIC_NEW(std::mutex); + return *m; } -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); - } +static std::condition_variable &initCondition() { + static std::condition_variable *cv = STATIC_NEW(std::condition_variable); + return *cv; } -#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; - } +// This function is called when a test of a UInitOnce::fState reveals that +// initialization has not completed, that we either need to call the init +// function on this thread, or wait for some other thread to complete. +// +// The actual call to the init function is made inline by template code +// that knows the C++ types involved. This function returns true if +// the caller needs to call the Init function. +// +U_COMMON_API UBool U_EXPORT2 +umtx_initImplPreInit(UInitOnce &uio) { + std::unique_lock lock(initMutex()); - /* If ICU is not in an initial state, disallow this operation. */ - if (cmemory_inUse()) { - *status = U_INVALID_STATE_ERROR; - return; + if (umtx_loadAcquire(uio.fState) == 0) { + umtx_storeRelease(uio.fState, 1); + return true; // Caller will next call the init function. + } else { + while (umtx_loadAcquire(uio.fState) == 1) { + // Another thread is currently running the initialization. + // Wait until it completes. + initCondition().wait(lock); + } + U_ASSERT(uio.fState == 2); + return false; } - - // 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; } +// This function is called by the thread that ran an initialization function, +// just after completing the function. +// Some threads may be waiting on the condition, requiring the broadcast wakeup. +// Some threads may be racing to test the fState variable outside of the mutex, +// requiring the use of store/release when changing its value. -/* 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; +U_COMMON_API void U_EXPORT2 +umtx_initImplPostInit(UInitOnce &uio) { + { + std::unique_lock lock(initMutex()); + umtx_storeRelease(uio.fState, 2); } - umtx_unlock(&implMutex); - - return temp; + initCondition().notify_all(); } +U_NAMESPACE_END - -/*----------------------------------------------------------------- +/************************************************************************************************* * - * Atomic Increment and Decrement - * umtx_atomic_inc - * umtx_atomic_dec + * Deprecated functions for setting user mutexes. * - *----------------------------------------------------------------*/ - -/* 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 +U_DEPRECATED void U_EXPORT2 +u_setMutexFunctions(const void * /*context */, UMtxInitFn *, UMtxFn *, + UMtxFn *, UMtxFn *, UErrorCode *status) { + if (U_SUCCESS(*status)) { + *status = U_UNSUPPORTED_ERROR; } - return retVal; + return; } -U_CAPI void U_EXPORT2 -u_setAtomicIncDecFunctions(const void *context, UMtxAtomicFn *ip, UMtxAtomicFn *dp, - UErrorCode *status) { - if (U_FAILURE(*status)) { - return; +U_DEPRECATED void U_EXPORT2 +u_setAtomicIncDecFunctions(const void * /*context */, UMtxAtomicFn *, UMtxAtomicFn *, + UErrorCode *status) { + if (U_SUCCESS(*status)) { + *status = U_UNSUPPORTED_ERROR; } - /* 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; + return; }