summaryrefslogtreecommitdiff
path: root/intl/icu/source/common/umutex.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/icu/source/common/umutex.cpp')
-rw-r--r--intl/icu/source/common/umutex.cpp483
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;
+}