umutex.cpp   [plain text]


/*
******************************************************************************
*
*   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;
}