diff options
author | Peter Breitenlohner <peb@mppmu.mpg.de> | 2011-08-10 12:03:08 +0000 |
---|---|---|
committer | Peter Breitenlohner <peb@mppmu.mpg.de> | 2011-08-10 12:03:08 +0000 |
commit | 79b8b3a76d8eda7372bb4dedae0ec7aebdaebc12 (patch) | |
tree | 9e4992c8719febb576deb6120740611380d71f7a /Build/source/libs/icu/icu-4.8.1/common/umutex.c | |
parent | 8cd13eb8f4bb7b92c6d9bb50aff9a62c37df026c (diff) |
icu 4.8.1
git-svn-id: svn://tug.org/texlive/trunk@23480 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/libs/icu/icu-4.8.1/common/umutex.c')
-rw-r--r-- | Build/source/libs/icu/icu-4.8.1/common/umutex.c | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/Build/source/libs/icu/icu-4.8.1/common/umutex.c b/Build/source/libs/icu/icu-4.8.1/common/umutex.c new file mode 100644 index 00000000000..e3bbda6df4c --- /dev/null +++ b/Build/source/libs/icu/icu-4.8.1/common/umutex.c @@ -0,0 +1,619 @@ +/* +****************************************************************************** +* +* Copyright (C) 1997-2011, International Business Machines +* Corporation and others. All Rights Reserved. +* +****************************************************************************** +* +* File umutex.c +* +* 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. + */ + +/* Assume POSIX, and modify as necessary below */ +#define POSIX + +#if defined(U_WINDOWS) +#undef POSIX +#endif +#if defined(macintosh) +#undef POSIX +#endif +#if defined(OS2) +#undef POSIX +#endif + +#if defined(POSIX) && (ICU_USE_THREADS==1) +# include <pthread.h> /* must be first, so that we get the multithread versions of things. */ + +#endif /* POSIX && (ICU_USE_THREADS==1) */ + +#ifdef U_WINDOWS +# 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" + +/* + * A note on ICU Mutex Initialization and ICU startup: + * + * ICU mutexes, as used through the rest of the ICU code, are self-initializing. + * To make this work, ICU uses the _ICU GLobal Mutex_ to synchronize the lazy init + * of other ICU mutexes. For the global mutex itself, we need some other mechanism + * to safely initialize it on first use. This becomes important when two or more + * threads are more or less simultaenously the first to use ICU in a process, and + * are racing into the mutex initialization code. + * + * + * The solution for the global mutex init is platform dependent. + * On POSIX systems, plain C-style initialization can be used on a mutex, with the + * macro PTHREAD_MUTEX_INITIALIZER. The mutex is then ready for use, without + * first calling pthread_mutex_init(). + * + * Windows has no equivalent statically initialized mutex or CRITICAL SECION. + * InitializeCriticalSection() must be called. If the global mutex does not + * appear to be initialized, a thread will create and initialize a new + * CRITICAL_SECTION, then use a Windows InterlockedCompareAndExchange to + * swap it in as the global mutex while avoid problems with race conditions. + */ + +/* On WIN32 mutexes are reentrant. On POSIX platforms they are not, and a deadlock + * will occur if a thread attempts to acquire a mutex it already has locked. + * ICU mutexes (in debug builds) include checking code that will cause an assertion + * failure if a mutex is reentered. If you are having deadlock problems + * on a POSIX machine, debugging may be easier on Windows. + */ + + +#if (ICU_USE_THREADS == 0) +#define MUTEX_TYPE void * +#define PLATFORM_MUTEX_INIT(m) +#define PLATFORM_MUTEX_LOCK(m) +#define PLATFORM_MUTEX_UNLOCK(m) +#define PLATFORM_MUTEX_DESTROY(m) +#define PLATFORM_MUTEX_INITIALIZER NULL +#define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ + mutexed_compare_and_swap(dest, newval, oldval) + + +#elif defined(U_WINDOWS) +#define MUTEX_TYPE CRITICAL_SECTION +#define PLATFORM_MUTEX_INIT(m) InitializeCriticalSection(m) +#define PLATFORM_MUTEX_LOCK(m) EnterCriticalSection(m) +#define PLATFORM_MUTEX_UNLOCK(m) LeaveCriticalSection(m) +#define PLATFORM_MUTEX_DESTROY(m) DeleteCriticalSection(m) +#define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ + InterlockedCompareExchangePointer(dest, newval, oldval) + + +#elif defined(POSIX) +#define MUTEX_TYPE pthread_mutex_t +#define PLATFORM_MUTEX_INIT(m) pthread_mutex_init(m, NULL) +#define PLATFORM_MUTEX_LOCK(m) pthread_mutex_lock(m) +#define PLATFORM_MUTEX_UNLOCK(m) pthread_mutex_unlock(m) +#define PLATFORM_MUTEX_DESTROY(m) pthread_mutex_destroy(m) +#define PLATFORM_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +#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 MUTEX_TYPE void * +#define PLATFORM_MUTEX_INIT(m) +#define PLATFORM_MUTEX_LOCK(m) +#define PLATFORM_MUTEX_UNLOCK(m) +#define PLATFORM_MUTEX_DESTROY(m) +#define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ + mutexed_compare_and_swap(dest, newval, oldval) + +#endif + +/* Forward declarations */ +static void *mutexed_compare_and_swap(void **dest, void *newval, void *oldval); +typedef struct ICUMutex ICUMutex; + +/* + * ICUMutex One of these is set up for each UMTX that is used by other ICU code. + * The opaque UMTX points to the corresponding ICUMutex struct. + * + * Because the total number of ICU mutexes is quite small, no effort has + * been made to squeeze every byte out of this struct. + */ +struct ICUMutex { + UMTX *owner; /* Points back to the UMTX corrsponding to this */ + /* ICUMutex object. */ + + UBool heapAllocated; /* Set if this ICUMutex is heap allocated, and */ + /* will need to be deleted. The global mutex */ + /* is static on POSIX platforms; all others */ + /* will be heap allocated. */ + + ICUMutex *next; /* All ICUMutexes are chained into a list so that */ + /* they can be found and deleted by u_cleanup(). */ + + int32_t recursionCount; /* For debugging, detect recursive mutex locks. */ + + MUTEX_TYPE platformMutex; /* The underlying OS mutex being wrapped. */ + + UMTX userMutex; /* For use with u_setMutexFunctions operations, */ + /* corresponds to platformMutex. */ +}; + + +/* The global ICU mutex. + * For POSIX platforms, it gets a C style initialization, and is ready to use + * at program startup. + * For Windows, it will be lazily instantiated on first use. + */ + +#if defined(POSIX) +static UMTX globalUMTX; +static ICUMutex globalMutex = {&globalUMTX, FALSE, NULL, 0, PLATFORM_MUTEX_INITIALIZER, NULL}; +static UMTX globalUMTX = &globalMutex; +#else +static UMTX globalUMTX = NULL; +#endif + +/* Head of the list of all ICU mutexes. + * Linked list is through ICUMutex::next + * Modifications to the list are synchronized with the global mutex. + * The list is used by u_cleanup(), which needs to dispose of all of the ICU mutexes. + * + * The statically initialized global mutex on POSIX platforms does not get added to this + * mutex list, but that's not a problem - the global mutex gets special handling + * during u_cleanup(). + */ +static ICUMutex *mutexListHead; + + +/* + * 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; + + +/* + * umtx_lock + */ +U_CAPI void U_EXPORT2 +umtx_lock(UMTX *mutex) +{ + ICUMutex *m; + + if (mutex == NULL) { + mutex = &globalUMTX; + } + m = (ICUMutex *)*mutex; + if (m == NULL) { + /* See note on lazy initialization, above. We can get away with it here, with mutexes, + * where we couldn't with normal user level data. + */ + umtx_init(mutex); + m = (ICUMutex *)*mutex; + } + U_ASSERT(m->owner == mutex); + + if (pMutexLockFn != NULL) { + (*pMutexLockFn)(gMutexContext, &m->userMutex); + } else { + PLATFORM_MUTEX_LOCK(&m->platformMutex); + } + +#if defined(U_DEBUG) + m->recursionCount++; /* Recursion causes deadlock on Unixes. */ + U_ASSERT(m->recursionCount == 1); /* Recursion detection works on Windows. */ + /* Assertion failure on non-Windows indicates a */ + /* problem with the mutex implementation itself. */ +#endif +} + + + +/* + * umtx_unlock + */ +U_CAPI void U_EXPORT2 +umtx_unlock(UMTX* mutex) +{ + ICUMutex *m; + if(mutex == NULL) { + mutex = &globalUMTX; + } + m = (ICUMutex *)*mutex; + if (m == NULL) { + U_ASSERT(FALSE); /* This mutex is not initialized. */ + return; + } + U_ASSERT(m->owner == mutex); + +#if defined (U_DEBUG) + m->recursionCount--; + U_ASSERT(m->recursionCount == 0); /* Detect unlock of an already unlocked mutex */ +#endif + + if (pMutexUnlockFn) { + (*pMutexUnlockFn)(gMutexContext, &m->userMutex); + } else { + PLATFORM_MUTEX_UNLOCK(&m->platformMutex); + } +} + + +/* umtx_ct Allocate and initialize a new ICUMutex. + * If a non-null pointer is supplied, initialize an existing ICU Mutex. + */ +static ICUMutex *umtx_ct(ICUMutex *m) { + if (m == NULL) { + m = (ICUMutex *)uprv_malloc(sizeof(ICUMutex)); + m->heapAllocated = TRUE; + } + m->next = NULL; /* List of mutexes is maintained at a higher level. */ + m->recursionCount = 0; + m->userMutex = NULL; + if (pMutexInitFn != NULL) { + UErrorCode status = U_ZERO_ERROR; + (*pMutexInitFn)(gMutexContext, &m->userMutex, &status); + U_ASSERT(U_SUCCESS(status)); + } else { + PLATFORM_MUTEX_INIT(&m->platformMutex); + } + return m; +} + + +/* umtx_dt Delete a ICUMutex. Destroy the underlying OS Platform mutex. + * Does not touch the linked list of ICU Mutexes. + */ +static void umtx_dt(ICUMutex *m) { + if (pMutexDestroyFn != NULL) { + (*pMutexDestroyFn)(gMutexContext, &m->userMutex); + m->userMutex = NULL; + } else { + PLATFORM_MUTEX_DESTROY(&m->platformMutex); + } + + if (m->heapAllocated) { + uprv_free(m); + } +} + + +U_CAPI void U_EXPORT2 +umtx_init(UMTX *mutex) { + ICUMutex *m = NULL; + void *originalValue; + + if (*mutex != NULL) { + /* Mutex is already initialized. + * Multiple umtx_init()s of a UMTX by other ICU code are explicitly permitted. + */ + return; + } +#if defined(POSIX) + if (mutex == &globalUMTX) { + m = &globalMutex; + } +#endif + + m = umtx_ct(m); + originalValue = SYNC_COMPARE_AND_SWAP(mutex, NULL, m); + if (originalValue != NULL) { + umtx_dt(m); + return; + } + + m->owner = mutex; + + /* Hook the new mutex into the list of all ICU mutexes, so that we can find and + * delete it for u_cleanup(). + */ + + umtx_lock(NULL); + m->next = mutexListHead; + mutexListHead = m; + umtx_unlock(NULL); + return; +} + + +/* + * umtx_destroy. Un-initialize a mutex, releasing any underlying resources + * that it may be holding. Destroying an already destroyed + * mutex has no effect. Unlike umtx_init(), this function + * is not thread safe; two threads must not concurrently try to + * destroy the same mutex. + */ +U_CAPI void U_EXPORT2 +umtx_destroy(UMTX *mutex) { + ICUMutex *m; + + /* No one should be deleting the global ICU mutex. + * (u_cleanup() does delete it, but does so explicitly, not by passing NULL) + */ + U_ASSERT(mutex != NULL); + if (mutex == NULL) { + return; + } + + m = (ICUMutex *)*mutex; + if (m == NULL) { /* Mutex not initialized, or already destroyed. */ + return; + } + + U_ASSERT(m->owner == mutex); + if (m->owner != mutex) { + return; + } + + /* Remove this mutex from the linked list of mutexes. */ + umtx_lock(NULL); + if (mutexListHead == m) { + mutexListHead = m->next; + } else { + ICUMutex *prev; + for (prev = mutexListHead; prev!=NULL && prev->next!=m; prev = prev->next); + /* Empty for loop body */ + if (prev != NULL) { + prev->next = m->next; + } + } + umtx_unlock(NULL); + + umtx_dt(m); /* Delete the internal ICUMutex */ + *mutex = NULL; /* Clear the caller's UMTX */ +} + + + +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; + } + + /* Kill any existing global mutex. POSIX platforms have a global mutex + * even before any other part of ICU is initialized. + */ + umtx_destroy(&globalUMTX); + + /* Swap in the mutex function pointers. */ + pMutexInitFn = i; + pMutexDestroyFn = d; + pMutexLockFn = l; + pMutexUnlockFn = u; + gMutexContext = context; + +#if defined (POSIX) + /* POSIX platforms must have a pre-initialized global mutex + * to allow other mutexes to initialize safely. */ + umtx_init(&globalUMTX); +#endif +} + + +/* synchronized compare and swap function, for use when OS or compiler built-in + * equivalents aren't available. + * + * This operation relies on the ICU global mutex for synchronization. + * + * There are two cases where this function can be entered when the global mutex is not + * yet initialized - at the end u_cleanup(), and at the end of u_setMutexFunctions, both + * of which re-init the global mutex. But neither function is thread-safe, so the lack of + * synchronization at these points doesn't matter. + */ +static void *mutexed_compare_and_swap(void **dest, void *newval, void *oldval) { + void *temp; + UBool needUnlock = FALSE; + + if (globalUMTX != NULL) { + umtx_lock(&globalUMTX); + needUnlock = TRUE; + } + + temp = *dest; + if (temp == oldval) { + *dest = newval; + } + + if (needUnlock) { + umtx_unlock(&globalUMTX); + } + 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; + +static UMTX gIncDecMutex = NULL; + +U_CAPI int32_t U_EXPORT2 +umtx_atomic_inc(int32_t *p) { + int32_t retVal; + if (pIncFn) { + retVal = (*pIncFn)(gIncDecContext, p); + } else { + #if defined (U_WINDOWS) && ICU_USE_THREADS == 1 + 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) && ICU_USE_THREADS == 1 + umtx_lock(&gIncDecMutex); + retVal = ++(*p); + umtx_unlock(&gIncDecMutex); + #else + /* Unknown Platform, or ICU thread support compiled out. */ + 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 defined (U_WINDOWS) && ICU_USE_THREADS == 1 + 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) && ICU_USE_THREADS == 1 + umtx_lock(&gIncDecMutex); + retVal = --(*p); + umtx_unlock(&gIncDecMutex); + #else + /* Unknown Platform, or ICU thread support compiled out. */ + 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_RELEASE + { + 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 + * + * Destroy the global mutex(es), and reset the mutex function callback pointers. + */ +U_CFUNC UBool umtx_cleanup(void) { + ICUMutex *thisMutex = NULL; + ICUMutex *nextMutex = NULL; + + /* Extra, do-nothing function call to suppress compiler warnings on platforms where + * mutexed_compare_and_swap is not otherwise used. */ + mutexed_compare_and_swap(&globalUMTX, NULL, NULL); + + /* Delete all of the ICU mutexes. Do the global mutex last because it is used during + * the umtx_destroy operation of other mutexes. + */ + for (thisMutex=mutexListHead; thisMutex!=NULL; thisMutex=nextMutex) { + UMTX *umtx = thisMutex->owner; + nextMutex = thisMutex->next; + U_ASSERT(*umtx = (void *)thisMutex); + if (umtx != &globalUMTX) { + umtx_destroy(umtx); + } + } + umtx_destroy(&globalUMTX); + + pMutexInitFn = NULL; + pMutexDestroyFn = NULL; + pMutexLockFn = NULL; + pMutexUnlockFn = NULL; + gMutexContext = NULL; + pIncFn = NULL; + pDecFn = NULL; + gIncDecContext = NULL; + gIncDecMutex = NULL; + +#if defined (POSIX) + /* POSIX platforms must come out of u_cleanup() with a functioning global mutex + * to permit the safe resumption of use of ICU in multi-threaded environments. + */ + umtx_init(&globalUMTX); +#endif + return TRUE; +} + + |