c22faac1a9
X-SVN-Rev: 14272
552 lines
16 KiB
C
552 lines
16 KiB
C
/*
|
|
******************************************************************************
|
|
*
|
|
* Copyright (C) 1997-2003, International Business Machines
|
|
* Corporation and others. All Rights Reserved.
|
|
*
|
|
******************************************************************************
|
|
*
|
|
* File CMUTEX.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]
|
|
******************************************************************************
|
|
*/
|
|
|
|
/* Assume POSIX, and modify as necessary below */
|
|
#define POSIX
|
|
|
|
#if defined(_WIN32)
|
|
#undef POSIX
|
|
#endif
|
|
#if defined(macintosh)
|
|
#undef POSIX
|
|
#endif
|
|
#if defined(OS2)
|
|
#undef POSIX
|
|
#endif
|
|
|
|
|
|
#include "unicode/utypes.h"
|
|
#include "uassert.h"
|
|
#include "ucln_cmn.h"
|
|
|
|
|
|
#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 WIN32
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# define NOGDI
|
|
# 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 if two or more
|
|
* threads were more or less simultaenously the first to use ICU in a process, and
|
|
* were racing into the mutex initialization code.
|
|
*
|
|
* The solution for the global mutex init is platform dependent.
|
|
* On POSIX systems, C-style init 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
|
|
* avoid problems with race conditions.
|
|
*
|
|
* If an application has overridden the ICU mutex implementation
|
|
* by calling u_setMutexFunctions(), the user supplied init function must
|
|
* be safe in the event that multiple threads concurrently attempt to init
|
|
* the same mutex. The first thread should do the init, and the others should
|
|
* have no effect.
|
|
*
|
|
*/
|
|
|
|
/* The global ICU mutex. */
|
|
#if defined(WIN32)
|
|
static UMTX gGlobalMutex = NULL;
|
|
|
|
#elif defined(POSIX)
|
|
#if (ICU_USE_THREADS == 1)
|
|
static pthread_mutex_t gGlobalPosixMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static UMTX gGlobalMutex = &gGlobalPosixMutex;
|
|
static UMTX gIncDecMutex = NULL;
|
|
#else
|
|
static UMTX gGlobalMutex = NULL;
|
|
static UMTX gIncDecMutex = NULL;
|
|
#endif
|
|
|
|
#else
|
|
/* Unknown platform. OK so long as ICU_USE_THREAD is not set.
|
|
Note that user can still set mutex functions at run time,
|
|
and that the global mutex variable is still needed in that case. */
|
|
static UMTX gGlobalMutex = NULL;
|
|
#if (ICU_USE_THREADS == 1)
|
|
#error no ICU mutex implementation for this platform
|
|
#endif
|
|
#endif
|
|
|
|
/* Detect Recursive locking of the global mutex. For debugging only. */
|
|
#if defined(WIN32) && defined(_DEBUG) && (ICU_USE_THREADS==1)
|
|
static int32_t gRecursionCount = 0;
|
|
#endif
|
|
|
|
|
|
/*
|
|
* User mutex implementation functions. If non-null, call back to these rather than
|
|
* directly using the system (Posix or Windows) APIs.
|
|
* (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)
|
|
{
|
|
if (mutex == NULL) {
|
|
mutex = &gGlobalMutex;
|
|
}
|
|
|
|
if (*mutex == NULL) {
|
|
/* Lock of an uninitialized mutex. Initialize it before proceeding. */
|
|
umtx_init(mutex);
|
|
}
|
|
|
|
if (pMutexLockFn != NULL) {
|
|
(*pMutexLockFn)(gMutexContext, mutex);
|
|
} else {
|
|
|
|
#if (ICU_USE_THREADS == 1)
|
|
#if defined(WIN32)
|
|
EnterCriticalSection((CRITICAL_SECTION*) *mutex);
|
|
#elif defined(POSIX)
|
|
pthread_mutex_lock((pthread_mutex_t*) *mutex);
|
|
#endif /* cascade of platforms */
|
|
#endif /* ICU_USE_THREADS==1 */
|
|
}
|
|
|
|
#if defined(WIN32) && defined(_DEBUG) && (ICU_USE_THREADS==1)
|
|
if (mutex == &gGlobalMutex) { /* Detect Reentrant locking of the global mutex. */
|
|
gRecursionCount++; /* Recursion causes deadlocks on Unixes. */
|
|
U_ASSERT(gRecursionCount == 1); /* Detection works on Windows. Debug problems there. */
|
|
}
|
|
#endif /*_DEBUG*/
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* umtx_unlock
|
|
*/
|
|
U_CAPI void U_EXPORT2
|
|
umtx_unlock(UMTX* mutex)
|
|
{
|
|
if(mutex == NULL) {
|
|
mutex = &gGlobalMutex;
|
|
}
|
|
|
|
if(*mutex == NULL) {
|
|
U_ASSERT(FALSE); /* This mutex is not initialized. */
|
|
return;
|
|
}
|
|
|
|
#if defined (WIN32) && defined (_DEBUG) && (ICU_USE_THREADS==1)
|
|
if (mutex == &gGlobalMutex) {
|
|
gRecursionCount--;
|
|
U_ASSERT(gRecursionCount == 0); /* Detect unlock of an already unlocked mutex */
|
|
}
|
|
#endif
|
|
|
|
if (pMutexUnlockFn) {
|
|
(*pMutexUnlockFn)(gMutexContext, mutex);
|
|
} else {
|
|
#if (ICU_USE_THREADS==1)
|
|
#if defined (WIN32)
|
|
LeaveCriticalSection((CRITICAL_SECTION*)*mutex);
|
|
#elif defined (POSIX)
|
|
pthread_mutex_unlock((pthread_mutex_t*)*mutex);
|
|
#endif /* cascade of platforms */
|
|
#endif /* ICU_USE_THREADS == 1 */
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* umtx_raw_init Do the platform specific mutex allocation and initialization
|
|
* for all ICU mutexes _except_ the ICU global mutex.
|
|
*/
|
|
static void umtx_raw_init(UMTX *mutex) {
|
|
if (pMutexInitFn != NULL) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
(*pMutexInitFn)(gMutexContext, mutex, &status);
|
|
if (U_FAILURE(status)) {
|
|
/* TODO: how should errors here be handled? */
|
|
return;
|
|
}
|
|
} else {
|
|
|
|
#if (ICU_USE_THREADS == 1)
|
|
#if defined (WIN32)
|
|
CRITICAL_SECTION *cs = uprv_malloc(sizeof(CRITICAL_SECTION));
|
|
if (cs == NULL) {
|
|
return;
|
|
}
|
|
InitializeCriticalSection(cs);
|
|
*mutex = cs;
|
|
#elif defined( POSIX )
|
|
pthread_mutex_t *m = (pthread_mutex_t *)uprv_malloc(sizeof(pthread_mutex_t));
|
|
if (m == NULL) {
|
|
return;
|
|
}
|
|
# if defined (HPUX_CMA)
|
|
pthread_mutex_init(m, pthread_mutexattr_default);
|
|
# else
|
|
pthread_mutex_init(m, NULL);
|
|
# endif
|
|
*mutex = m;
|
|
#endif /* cascade of platforms */
|
|
#else /* ICU_USE_THREADS */
|
|
*mutex = mutex; /* With no threads, we must still set the mutex to
|
|
* some non-null value to make the rest of the
|
|
* (not ifdefed) mutex code think that it is initialized.
|
|
*/
|
|
#endif /* ICU_USE_THREADS */
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* initGlobalMutex Do the platform specific initialization of the ICU global mutex.
|
|
* Separated out from the other mutexes because it is different:
|
|
* Mutex storage is static for POSIX, init must be thread safe
|
|
* without the use of another mutex.
|
|
*/
|
|
static void initGlobalMutex() {
|
|
/*
|
|
* Call user mutex init function if one has been specified and the global mutex
|
|
* is not already initialized.
|
|
*/
|
|
if (pMutexInitFn != NULL) {
|
|
if (gGlobalMutex==NULL) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
(*pMutexInitFn)(gMutexContext, &gGlobalMutex, &status);
|
|
if (U_FAILURE(status)) {
|
|
/* TODO: how should errors here be handled? */
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* No user override of mutex functions.
|
|
* Use default ICU mutex implementations.
|
|
*/
|
|
#if (ICU_USE_THREADS == 1)
|
|
#if defined (WIN32)
|
|
{
|
|
void *t;
|
|
CRITICAL_SECTION *ourCritSec = uprv_malloc(sizeof(CRITICAL_SECTION));
|
|
InitializeCriticalSection(ourCritSec);
|
|
#if defined (InterlockedCompareExchangePointer) || defined (_WIN64)
|
|
t = InterlockedCompareExchangePointer(&gGlobalMutex, ourCritSec, NULL);
|
|
#else
|
|
/* Note that the headers from Microsoft's WIndows SDK define InterlockedCompareExchangePointer
|
|
* for all platforms, but the old headers included with MSVC 6 do not.
|
|
*/
|
|
t = (void *)InterlockedCompareExchange(&gGlobalMutex, ourCritSec, NULL);
|
|
#endif
|
|
if (t != NULL) {
|
|
/* Some other thread stored into gGlobalMutex first. Discard the critical
|
|
* section we just created; the system will go with the other one.
|
|
*/
|
|
DeleteCriticalSection(ourCritSec);
|
|
uprv_free(ourCritSec);
|
|
}
|
|
}
|
|
#elif defined( POSIX )
|
|
/* No Action Required. Global mutex set up with C static initialization. */
|
|
U_ASSERT(gGlobalMutex == &gGlobalPosixMutex);
|
|
#endif /* cascade of platforms */
|
|
#else /* ICU_USE_THREADS */
|
|
gGlobalMutex = &gGlobalMutex; /* With no threads, we must still set the mutex to
|
|
* some non-null value to make the rest of the
|
|
* (not ifdefed) mutex code think that it is initialized.
|
|
*/
|
|
#endif /* ICU_USE_THREADS */
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
U_CAPI void U_EXPORT2
|
|
umtx_init(UMTX *mutex)
|
|
{
|
|
if (mutex == NULL || mutex == &gGlobalMutex) {
|
|
initGlobalMutex();
|
|
} else {
|
|
/*
|
|
* Thread safe initialization of mutexes other than the global one,
|
|
* using the global mutex.
|
|
*/
|
|
UBool isInitialized;
|
|
UMTX tMutex = NULL;
|
|
|
|
umtx_lock(NULL);
|
|
isInitialized = (*mutex != NULL);
|
|
umtx_unlock(NULL);
|
|
if (isInitialized) {
|
|
return;
|
|
}
|
|
|
|
umtx_raw_init(&tMutex);
|
|
|
|
umtx_lock(NULL);
|
|
if (*mutex == NULL) {
|
|
*mutex = tMutex;
|
|
tMutex = NULL;
|
|
}
|
|
umtx_unlock(NULL);
|
|
|
|
if (tMutex != NULL) {
|
|
umtx_destroy(&tMutex);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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) {
|
|
if (mutex == NULL) { /* destroy the global mutex */
|
|
mutex = &gGlobalMutex;
|
|
}
|
|
|
|
if (*mutex == NULL) { /* someone already did it. */
|
|
return;
|
|
}
|
|
|
|
#if defined (POSIX)
|
|
/* The life of the inc/dec mutex for POSIX is tied to that of the global mutex. */
|
|
if (mutex == &gGlobalMutex) {
|
|
umtx_destroy(&gIncDecMutex);
|
|
}
|
|
#endif
|
|
|
|
if (pMutexDestroyFn != NULL) {
|
|
(*pMutexDestroyFn)(gMutexContext, mutex);
|
|
*mutex = NULL;
|
|
} else {
|
|
#if (ICU_USE_THREADS == 1)
|
|
#if defined (WIN32)
|
|
DeleteCriticalSection((CRITICAL_SECTION*)*mutex);
|
|
uprv_free(*mutex);
|
|
*mutex = NULL;
|
|
#elif defined (POSIX)
|
|
if (*mutex != &gGlobalPosixMutex) {
|
|
/* Only POSIX mutexes other than the ICU global mutex get destroyed. */
|
|
pthread_mutex_destroy((pthread_mutex_t*)*mutex);
|
|
uprv_free(*mutex);
|
|
*mutex = NULL;
|
|
}
|
|
#endif /* chain of platforms */
|
|
#else /* ICU_USE_THREADS==1 */
|
|
/* NO ICU Threads. We still need to zero out the mutex pointer, so that
|
|
* it appears to be uninitialized */
|
|
*mutex = NULL;
|
|
#endif /* ICU_USE_THREADS */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
/* Swap in the mutex function pointers. */
|
|
pMutexInitFn = i;
|
|
pMutexDestroyFn = d;
|
|
pMutexLockFn = l;
|
|
pMutexUnlockFn = u;
|
|
gMutexContext = context;
|
|
gGlobalMutex = NULL; /* For POSIX, the global mutex will be pre-initialized */
|
|
/* Undo that, force re-initialization when u_init() */
|
|
/* happens. */
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------
|
|
*
|
|
* 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 void *gIncDecContext = 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 (WIN32) && ICU_USE_THREADS == 1
|
|
retVal = InterlockedIncrement(p);
|
|
#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 (WIN32) && ICU_USE_THREADS == 1
|
|
retVal = InterlockedDecrement(p);
|
|
#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;
|
|
}
|
|
|
|
/* TODO: Some POSIXy platforms have atomic inc/dec functions available. Use them. */
|
|
|
|
|
|
|
|
|
|
|
|
U_CAPI void U_EXPORT2
|
|
u_setAtomicIncDecFunctions(const void *context, UMtxAtomicFn *ip, UMtxAtomicFn *dp,
|
|
UErrorCode *status) {
|
|
int32_t testInt;
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Mutex Cleanup Function
|
|
*
|
|
* Destroy the global mutex(es), and reset the mutex function callback pointers.
|
|
*/
|
|
U_CFUNC UBool umtx_cleanup(void) {
|
|
umtx_destroy(NULL);
|
|
pMutexInitFn = NULL;
|
|
pMutexDestroyFn = NULL;
|
|
pMutexLockFn = NULL;
|
|
pMutexUnlockFn = NULL;
|
|
gMutexContext = NULL;
|
|
|
|
gGlobalMutex = NULL;
|
|
#if defined (POSIX)
|
|
gIncDecMutex = NULL;
|
|
#if (ICU_USE_THREADS == 1)
|
|
gGlobalMutex = &gGlobalPosixMutex;
|
|
#endif
|
|
#endif
|
|
pIncFn = NULL;
|
|
pDecFn = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|