+// © 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.
*
******************************************************************************
******************************************************************************
*/
+#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 <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)
+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<UMutex *>(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<std::mutex> 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<std::mutex> 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;
}