blob: a7299a5cfd1603721fb58d7b3f5608d3d03131da [file] [log] [blame]
/*
******************************************************************************
*
* 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;
}