]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/common/umutex.c
ICU-6.2.4.tar.gz
[apple/icu.git] / icuSources / common / umutex.c
index d48b8057a429161769d0316795197b44c4d15dc7..5cf9e4ff3cdc236d57c25b5c6edfd541f13e1954 100644 (file)
@@ -1,7 +1,7 @@
 /*
 ******************************************************************************
 *
-*   Copyright (C) 1997-2003, International Business Machines
+*   Copyright (C) 1997-2004, International Business Machines
 *   Corporation and others.  All Rights Reserved.
 *
 ******************************************************************************
 #endif
 
 
-/* Check our settings... */
 #include "unicode/utypes.h"
 #include "uassert.h"
+#include "ucln_cmn.h"
 
 
 #if defined(POSIX) && (ICU_USE_THREADS==1)
-  /* Usage: uncomment the following, and breakpoint WeAreDeadlocked to
-     find reentrant issues. */
-/* # define POSIX_DEBUG_REENTRANCY 1 */
 # include <pthread.h> /* must be first, so that we get the multithread versions of things. */
 
-# ifdef POSIX_DEBUG_REENTRANCY
- pthread_t      gLastThread;
- UBool         gInMutex;
-
- U_EXPORT void WeAreDeadlocked();
-
- void WeAreDeadlocked()
- {
-    puts("ARGH!! We're deadlocked.. break on WeAreDeadlocked() next time.");
- }
-# endif /* POSIX_DEBUG_REENTRANCY */
 #endif /* POSIX && (ICU_USE_THREADS==1) */
 
 #ifdef WIN32
 # define WIN32_LEAN_AND_MEAN
-# define NOGDI
+# define VC_EXTRALEAN
 # define NOUSER
 # define NOSERVICE
 # define NOIME
 #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.
+ *
+ */ 
+
+#define  MAX_MUTEXES  30
+static UMTX              gGlobalMutex          = NULL;
+static UMTX              gIncDecMutex          = NULL;       
 #if (ICU_USE_THREADS == 1)
+static UBool             gMutexPoolInitialized = FALSE;
+static char              gMutexesInUse[MAX_MUTEXES];   
+
+#if defined(WIN32) 
+/*-------------------------------------------------------------
+ *
+ *   WINDOWS  platform variable declarations
+ *
+ *-------------------------------------------------------------*/
+static CRITICAL_SECTION  gMutexes[MAX_MUTEXES];
+static CRITICAL_SECTION  gGlobalWinMutex;
+
+
+/* On WIN32 mutexes are reentrant.  This makes it difficult to debug
+ * deadlocking problems that show up on POSIXy platforms, where
+ * mutexes deadlock upon reentry.  ICU contains checking code for
+ * the global mutex as well as for other mutexes in the pool.
+ *
+ * This is for debugging purposes.
+ *
+ * This has no effect on non-WIN32 platforms, non-DEBUG builds, and
+ * non-ICU_USE_THREADS builds.
+ *
+ * Note: The CRITICAL_SECTION structure already has a RecursionCount
+ * member that can be used for this purpose, but portability to
+ * Win98/NT/2K needs to be tested before use.  Works fine on XP.
+ * After portability is confirmed, the built-in RecursionCount can be
+ * used, and the gRecursionCountPool can be removed.
+ *
+ * Note: Non-global mutex checking only happens if there is no custom
+ * pMutexLockFn defined.  Use one function, not two (don't use
+ * pMutexLockFn and pMutexUnlockFn) so the increment and decrement of
+ * the recursion count don't get out of sync.  Users might set just
+ * one function, e.g., to perform a custom action, followed by a
+ * standard call to EnterCriticalSection.
+ */
+#if defined(U_DEBUG) && (ICU_USE_THREADS==1)
+static int32_t gRecursionCount = 0; /* detect global mutex locking */      
+static int32_t gRecursionCountPool[MAX_MUTEXES]; /* ditto for non-global */
+#endif
 
-/* the global mutex. Use it proudly and wash it often. */
-static UMTX    gGlobalMutex = NULL;
-# ifdef _DEBUG
-static int32_t gRecursionCount = 0;       /* Detect Recursive entries.  For debugging only. */
-# endif
 
-#if defined(WIN32)
-static CRITICAL_SECTION gPlatformMutex;
-
-#elif defined(POSIX)
-static pthread_mutex_t gPlatformMutex;    /* The global ICU mutex   */
-static pthread_mutex_t gIncDecMutex;      /* For use by atomic inc/dec, on Unixes only */    
+#elif defined(POSIX) 
+/*-------------------------------------------------------------
+ *
+ *   POSIX   platform variable declarations
+ *
+ *-------------------------------------------------------------*/
+static pthread_mutex_t   gMutexes[MAX_MUTEXES] = {
+    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
+    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
+    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
+    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
+    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
+    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
+    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER
+};
 
+#else 
+/*-------------------------------------------------------------
+ *
+ *   UNKNOWN   platform  declarations
+ *
+ *-------------------------------------------------------------*/
+static void *gMutexes[MAX_MUTEXES] = {
+    NULL, NULL, NULL,
+    NULL, NULL, NULL,
+    NULL, NULL, NULL,
+    NULL, NULL, NULL,
+    NULL, NULL, NULL,
+    NULL, NULL, NULL,
+    NULL, NULL };
+
+/* 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. */
+#if (ICU_USE_THREADS == 1)
+#error no ICU mutex implementation for this platform
+#endif
 #endif
 #endif /* ICU_USE_THREADS==1 */
 
 
 
-U_CAPI UBool U_EXPORT2
-umtx_isInitialized(UMTX *mutex)
-{
-#if (ICU_USE_THREADS == 1)
-    if (mutex == NULL)
-    {
-        return (UBool)(gGlobalMutex != NULL);
-    } else {
-        UBool isInited;
-        umtx_lock(NULL);
-        isInited = (*mutex != NULL);
-        umtx_unlock(NULL);
-        return isInited;
-    }
-#else
-    return TRUE;    /* Since we don't use threads, it's considered initialized. */
-#endif /* ICU_USE_THREADS==1 */
-}
 
+/*
+ *  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 (ICU_USE_THREADS == 1)
-    if (mutex == NULL)
-    {
+    if (mutex == NULL) {
         mutex = &gGlobalMutex;
     }
 
-    if (*mutex == NULL)
-    {
-        /* Lazy init of a non-global mutexes on first lock is NOT safe on processors
-         *  that reorder memory operations.  */
-        /* U_ASSERT(FALSE);    TODO:  Turn this back on */
-        if (mutex != &gGlobalMutex) {
-            umtx_init(mutex);
-        } else {
-            umtx_init(NULL);  /* initialize the global mutex - only get 
-                                 here if C++ static init is NOT working,
-                                 and u_init() hasn't been called.
-                                 
-                                 Not thread-safe if this call is contended! */
-        }
+    if (*mutex == NULL) {
+        /* Lock of an uninitialized mutex.  Initialize it before proceeding.   */
+        umtx_init(mutex);    
     }
 
-#if defined(WIN32)
-
-    EnterCriticalSection((CRITICAL_SECTION*) *mutex);
-    #ifdef _DEBUG
-    if (mutex == &gGlobalMutex) {
-        gRecursionCount++;
-        U_ASSERT(gRecursionCount == 1);
-    }
-    #endif /*_DEBUG*/
+    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 */
+    }
 
-#  ifdef POSIX_DEBUG_REENTRANCY
-    if (gInMutex == TRUE && mutex == &gGlobalMutex) /* in the mutex -- possible deadlock*/
-        if(pthread_equal(gLastThread, pthread_self()))
-            WeAreDeadlocked();
-#  endif
-    pthread_mutex_lock((pthread_mutex_t*) *mutex);
-
-#  ifdef POSIX_DEBUG_REENTRANCY
-    if (mutex == &gGlobalMutex) {
-        gLastThread = pthread_self();
-        gInMutex = TRUE;
+#if defined(WIN32) && defined(U_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
-#endif
-#endif /* ICU_USE_THREADS==1 */
+    /* This handles gGlobalMutex too, but only if there is no pMutexLockFn */
+    else if (pMutexLockFn == NULL) { /* see comments above */
+        int i = ((CRITICAL_SECTION*)*mutex) - &gMutexes[0];
+        U_ASSERT(i >= 0 && i < MAX_MUTEXES);
+        ++gRecursionCountPool[i];
+
+        U_ASSERT(gRecursionCountPool[i] == 1); /* !Detect Deadlock! */
+
+        /* This works and is fast, but needs testing on Win98/NT/2K.
+           See comments above. [alan]
+          U_ASSERT((CRITICAL_SECTION*)*mutex >= &gMutexes[0] &&
+                   (CRITICAL_SECTION*)*mutex <= &gMutexes[MAX_MUTEXES]);
+          U_ASSERT(((CRITICAL_SECTION*)*mutex)->RecursionCount == 1);
+        */
+    }
+#endif /*U_DEBUG*/
 }
 
+
+
+/*
+ * umtx_unlock
+ */
 U_CAPI void  U_EXPORT2
 umtx_unlock(UMTX* mutex)
 {
-#if (ICU_USE_THREADS==1)
-    if(mutex == NULL)
-    {
+    if(mutex == NULL) {
         mutex = &gGlobalMutex;
     }
 
-    if(*mutex == NULL)
-    {
-        return; /* jitterbug 135, fix for multiprocessor machines */
+    if(*mutex == NULL)    {
+#if (ICU_USE_THREADS == 1)
+        U_ASSERT(FALSE);  /* This mutex is not initialized.     */
+#endif
+        return; 
     }
 
-#if defined (WIN32)
-    #ifdef _DEBUG
+#if defined (WIN32) && defined (U_DEBUG) && (ICU_USE_THREADS==1)
     if (mutex == &gGlobalMutex) {
         gRecursionCount--;
-        U_ASSERT(gRecursionCount == 0);
+        U_ASSERT(gRecursionCount == 0);  /* Detect unlock of an already unlocked mutex */
     }
-    #endif /*_DEBUG*/
-    LeaveCriticalSection((CRITICAL_SECTION*)*mutex);
-
-#elif defined (POSIX)
-    pthread_mutex_unlock((pthread_mutex_t*)*mutex);
-
-#ifdef POSIX_DEBUG_REENTRANCY
-    if (mutex == &gGlobalMutex) {
-        gInMutex = FALSE;
+    /* This handles gGlobalMutex too, but only if there is no pMutexLockFn */
+    else if (pMutexLockFn == NULL) { /* see comments above */
+        int i = ((CRITICAL_SECTION*)*mutex) - &gMutexes[0];
+        U_ASSERT(i >= 0 && i < MAX_MUTEXES);
+        --gRecursionCountPool[i];
+
+        U_ASSERT(gRecursionCountPool[i] == 0); /* !Detect Deadlock! */
+
+        /* This works and is fast, but needs testing on Win98/NT/2K.
+           Note that RecursionCount will be 1, not 0, since we haven't
+           left the CRITICAL_SECTION yet.  See comments above. [alan]
+          U_ASSERT((CRITICAL_SECTION*)*mutex >= &gMutexes[0] &&
+                   (CRITICAL_SECTION*)*mutex <= &gMutexes[MAX_MUTEXES]);
+          U_ASSERT(((CRITICAL_SECTION*)*mutex)->RecursionCount == 1);
+        */
     }
 #endif
 
-#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
+ *   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() {
+    /*
+     * If User Supplied mutex functions are in use
+     *    init the icu global mutex using them.  
+     */
+    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)
-static UMTX umtx_raw_init(void  *mem) {
-    #if defined (WIN32)
-        if (mem == NULL) {
-            mem = uprv_malloc(sizeof(CRITICAL_SECTION));
-            if (mem == NULL) {return NULL;}
+    /*
+     *  for Windows, init the pool of critical sections that we
+     *    will use as needed for ICU mutexes.
+     */
+#if defined (WIN32)
+    if (gMutexPoolInitialized == FALSE) {
+        int i;
+        for (i=0; i<MAX_MUTEXES; i++) {
+            InitializeCriticalSection(&gMutexes[i]);
+#if defined (U_DEBUG)
+            gRecursionCountPool[i] = 0; /* see comments above */
+#endif
         }
-        InitializeCriticalSection((CRITICAL_SECTION*)mem);
-    #elif defined( POSIX )
-        if (mem == NULL) {
-            mem = uprv_malloc(sizeof(pthread_mutex_t));
-            if (mem == NULL) {return NULL;}
+        gMutexPoolInitialized = TRUE;
+    }
+#elif defined (POSIX)
+    /*  TODO:  experimental code.  Shouldn't need to explicitly init the mutexes. */
+    if (gMutexPoolInitialized == FALSE) {
+        int i;
+        for (i=0; i<MAX_MUTEXES; i++) {
+            pthread_mutex_init(&gMutexes[i], NULL);
         }
-        # if defined (HPUX_CMA)
-            pthread_mutex_init((pthread_mutex_t*)mem, pthread_mutexattr_default);
-        # else
-            pthread_mutex_init((pthread_mutex_t*)mem, NULL);
-        # endif
-    #endif
-    return (UMTX *)mem;
+        gMutexPoolInitialized = TRUE;
+    }
+#endif 
+
+    /*
+     * for both Windows & POSIX, the first mutex in the array is used
+     *   for the ICU global mutex.
+     */
+    gGlobalMutex = &gMutexes[0];
+    gMutexesInUse[0] = 1;
+
+#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 */
 }
-#endif  /* ICU_USE_THREADS */
 
 
-U_CAPI void  U_EXPORT2
-umtx_init(UMTX *mutex)
-{
-#if (ICU_USE_THREADS == 1)
-
-    if (mutex == NULL) /* initialize the global mutex */
-    {
-        /* Note:  The initialization of the global mutex is NOT thread safe.   */
-        if (gGlobalMutex != NULL) {
-            return;
-        }
-        gGlobalMutex = umtx_raw_init(&gPlatformMutex);
 
-       # ifdef POSIX_DEBUG_REENTRANCY
-           gInMutex = FALSE;
-       # endif
-       #ifdef _DEBUG
-           gRecursionCount = 0;
-       #endif
 
-       #ifdef POSIX
-       umtx_raw_init(&gIncDecMutex);
-       #endif
 
+U_CAPI void  U_EXPORT2
+umtx_init(UMTX *mutex)
+{
+    if (mutex == NULL || mutex == &gGlobalMutex) {
+        initGlobalMutex();
     } else {
-        /* Not the global mutex.
-         *  Thread safe initialization, using the global mutex.
-         */  
-        UBool isInitialized; 
-        UMTX tMutex = NULL;
-
         umtx_lock(NULL);
-        isInitialized = (*mutex != NULL);
-        umtx_unlock(NULL);
-        if (isInitialized) {  
+        if (*mutex != NULL) {
+            /* Another thread initialized this mutex first. */
+            umtx_unlock(NULL);
             return;
         }
 
-        tMutex = umtx_raw_init(NULL);
-
-        umtx_lock(NULL);
-        if (*mutex == NULL) {
-            *mutex = tMutex;
-            tMutex = NULL;
+        if (pMutexInitFn != NULL) {
+            UErrorCode status = U_ZERO_ERROR;
+            (*pMutexInitFn)(gMutexContext, mutex, &status);
+            /* TODO:  how to report failure on init? */
+            umtx_unlock(NULL);
+            return;
+        }
+        else {
+#if (ICU_USE_THREADS == 1)
+            /*  Search through our pool of pre-allocated mutexes for one that is not
+            *  already in use.    */
+            int i;
+            for (i=0; i<MAX_MUTEXES; i++) {
+                if (gMutexesInUse[i] == 0) {
+                    gMutexesInUse[i] = 1;
+                    *mutex = &gMutexes[i];
+                    break;
+                }
+            }
+#endif
         }
         umtx_unlock(NULL);
-        
-        umtx_destroy(&tMutex);  /* NOP if (tmutex == NULL)  */
+
+#if (ICU_USE_THREADS == 1)
+        /* No more mutexes were available from our pre-allocated pool.  */
+        /*   TODO:  how best to deal with this?                    */
+        U_ASSERT(*mutex != NULL);
+#endif
     }
-#endif /* ICU_USE_THREADS==1 */
 }
 
+
+/*
+ *  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 (ICU_USE_THREADS == 1)
-    if (mutex == NULL) /* destroy the global mutex */
-    {
+    if (mutex == NULL) {  /* destroy the global mutex */
         mutex = &gGlobalMutex;
     }
-
-    if (*mutex == NULL) /* someone already did it. */
+    
+    if (*mutex == NULL) {  /* someone already did it. */
         return;
+    }
 
-#if defined (WIN32)
-    DeleteCriticalSection((CRITICAL_SECTION*)*mutex);
-
-#elif defined (POSIX)
-    pthread_mutex_destroy((pthread_mutex_t*)*mutex);
+    /*  The life of the inc/dec mutex is tied to that of the global mutex.  */
+    if (mutex == &gGlobalMutex) {
+        umtx_destroy(&gIncDecMutex);
+    }
 
+    if (pMutexDestroyFn != NULL) {
+        /* Mutexes are being managed by the app.  Call back to it for the destroy. */
+        (*pMutexDestroyFn)(gMutexContext, mutex);
+    }
+    else {
+#if (ICU_USE_THREADS == 1)
+        /* Return this mutex to the pool of available mutexes, if it came from the
+         *  pool in the first place.
+         */
+        /* TODO use pointer math here, instead of iterating! */
+        int i;
+        for (i=0; i<MAX_MUTEXES; i++)  {
+            if (*mutex == &gMutexes[i]) {
+                gMutexesInUse[i] = 0;
+                break;
+            }
+        }
 #endif
-
-    if (*mutex != gGlobalMutex)
-    {
-        uprv_free(*mutex);
     }
 
     *mutex = NULL;
-#endif /* ICU_USE_THREADS==1 */
 }
 
 
-#if (ICU_USE_THREADS == 1) 
 
+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;
+    }
 
-/*
- *  umtx_atomic_inc
- *  umtx_atomic_dec
- */
+    /* 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 defined (WIN32)
-/*
- * Win32 - use the Windows API functions for atomic increment and decrement.
- */
-U_CAPI int32_t U_EXPORT2
-umtx_atomic_inc(int32_t *p)
-{
-    return InterlockedIncrement(p);
-}
+    /* If ICU is not in an initial state, disallow this operation. */
+    if (cmemory_inUse()) {
+        *status = U_INVALID_STATE_ERROR;
+        return;
+    }
 
-U_CAPI int32_t U_EXPORT2
-umtx_atomic_dec(int32_t *p)
-{
-    return InterlockedDecrement(p); 
+    /* 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.                                          */
 }
 
-#elif defined (POSIX)
-/*
- * POSIX platforms without specific atomic operations.  Use a posix mutex
- *   to protect the increment and decrement.
- *   The IncDecMutex is in static storage so we don't have to come back and delete it
- *   when the process exits.
- */
 
-U_CAPI int32_t U_EXPORT2
-umtx_atomic_inc(int32_t *p)
-{
-    int32_t    retVal;
 
-    pthread_mutex_lock(&gIncDecMutex);
-    retVal = ++(*p);
-    pthread_mutex_unlock(&gIncDecMutex);
-    return retVal;
-}
+/*-----------------------------------------------------------------
+ *
+ *  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_dec(int32_t *p)
-{
-    int32_t    retVal;
+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((LONG*)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;
+}
 
-    pthread_mutex_lock(&gIncDecMutex);
-    retVal = --(*p);
-    pthread_mutex_unlock(&gIncDecMutex);
+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((LONG*)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. */
 
-#else 
-   
-/* No recognized platform.  */
-#error  No atomic increment and decrement defined for this platform. \
-        Either use the --disable-threads configure option, or define those functions in this file.
 
-#endif   /* Platform selection for atomic_inc and dec. */
 
 
-#else  /* (ICU_USE_THREADS == 0) */
 
-/* Threads disabled here */
+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;
 
-U_CAPI int32_t U_EXPORT2
-umtx_atomic_inc(int32_t *p) {
-    return ++(*p);
+    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);
 }
 
-U_CAPI int32_t U_EXPORT2
-umtx_atomic_dec(int32_t *p) {
-    return --(*p);
-}
 
-#endif /* (ICU_USE_THREADS == 1) */
 
+/*
+ *  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;
+    pIncFn          = NULL;
+    pDecFn          = NULL;
+    gIncDecMutex    = NULL;
+
+#if (ICU_USE_THREADS == 1)
+    if (gMutexPoolInitialized) {
+        int i;
+        for (i=0; i<MAX_MUTEXES; i++) {
+            if (gMutexesInUse[i]) {
+#if defined (WIN32)
+                DeleteCriticalSection(&gMutexes[i]);
+#elif defined (POSIX)
+                pthread_mutex_destroy(&gMutexes[i]);
+#endif
+                gMutexesInUse[i] = 0;
+            }
+        }
+    }
+    gMutexPoolInitialized = FALSE;
+#endif
 
+    return TRUE;
+}