X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/51004dcb01e06fef634b61be77ed73dd61cb6db9..cecc3f9394f261e71def48cf396d137687dbd0a7:/icuSources/common/umutex.cpp?ds=inline diff --git a/icuSources/common/umutex.cpp b/icuSources/common/umutex.cpp index a7299a5c..29dbc90e 100644 --- a/icuSources/common/umutex.cpp +++ b/icuSources/common/umutex.cpp @@ -1,7 +1,9 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html /* ****************************************************************************** * -* Copyright (C) 1997-2012, International Business Machines +* Copyright (C) 1997-2016, International Business Machines * Corporation and others. All Rights Reserved. * ****************************************************************************** @@ -18,466 +20,359 @@ ****************************************************************************** */ +#include "umutex.h" + #include "unicode/utypes.h" #include "uassert.h" -#include "ucln_cmn.h" +#include "cmemory.h" + + +// The ICU global mutex. Used when ICU implementation code passes NULL for the mutex pointer. +static UMutex globalMutex = U_MUTEX_INITIALIZER; /* * 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 /* 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 -#endif - -#include "umutex.h" -#include "cmemory.h" +#if defined(U_USER_MUTEX_CPP) +// Build time user mutex hook: #include "U_USER_MUTEX_CPP" +#include U_MUTEX_XSTR(U_USER_MUTEX_CPP) -#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 +#elif U_PLATFORM_USES_ONLY_WIN32_API -#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) +#if defined U_NO_PLATFORM_ATOMICS +#error ICU on Win32 requires support for low level atomic operations. +// Visual Studio, gcc, clang are OK. Shouldn't get here. #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. +// This function is called when a test of a UInitOnce::fState reveals that +// initialization has not completed, that we either need to call the +// function on this thread, or wait for some other thread to complete. // -// 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. +// The actual call to the init function is made inline by template code +// that knows the C++ types involved. This function returns TRUE if +// the caller needs to call the Init function. // -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; +U_NAMESPACE_BEGIN + +U_COMMON_API UBool U_EXPORT2 umtx_initImplPreInit(UInitOnce &uio) { + for (;;) { + int32_t previousState = InterlockedCompareExchange( + (LONG volatile *) // this is the type given in the API doc for this function. + &uio.fState, // Destination + 1, // Exchange Value + 0); // Compare value + + if (previousState == 0) { + return true; // Caller will next call the init function. + // Current state == 1. + } else if (previousState == 2) { + // Another thread already completed the initialization. + // We can simply return FALSE, indicating no + // further action is needed by the caller. + return FALSE; + } else { + // Another thread is currently running the initialization. + // Wait until it completes. + do { + Sleep(1); + previousState = umtx_loadAcquire(uio.fState); + } while (previousState == 1); } - (*pMutexDestroyFn)(gMutexContext, &globalMutex.fUserMutex); - (*pMutexDestroyFn)(gMutexContext, &implMutex.fUserMutex); } - gMutexListSize = 0; - pMutexInitFn = NULL; - pMutexDestroyFn = NULL; - pMutexLockFn = NULL; - pMutexUnlockFn = NULL; - gMutexContext = NULL; } +// This function is called by the thread that ran an initialization function, +// just after completing the function. -/* - * 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); +U_COMMON_API void U_EXPORT2 umtx_initImplPostInit(UInitOnce &uio) { + umtx_storeRelease(uio.fState, 2); } - - -#if defined(POSIX) +U_NAMESPACE_END -// -// 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. +static void winMutexInit(CRITICAL_SECTION *cs) { + InitializeCriticalSection(cs); + return; +} 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 - } + CRITICAL_SECTION *cs = &mutex->fCS; + umtx_initOnce(mutex->fInitOnce, winMutexInit, cs); + EnterCriticalSection(cs); } - U_CAPI void U_EXPORT2 umtx_unlock(UMutex* mutex) { if (mutex == NULL) { mutex = &globalMutex; } - if (pMutexUnlockFn) { - (*pMutexUnlockFn)(gMutexContext, &mutex->fUserMutex); + LeaveCriticalSection(&mutex->fCS); +} + + +U_CAPI void U_EXPORT2 +umtx_condBroadcast(UConditionVar *condition) { + // We require that the associated mutex be held by the caller, + // so access to fWaitCount is protected and safe. No other thread can + // call condWait() while we are here. + if (condition->fWaitCount == 0) { + return; + } + ResetEvent(condition->fExitGate); + SetEvent(condition->fEntryGate); +} + +U_CAPI void U_EXPORT2 +umtx_condSignal(UConditionVar * /* condition */) { + // Function not implemented. There is no immediate requirement from ICU to have it. + // Once ICU drops support for Windows XP and Server 2003, ICU Condition Variables will be + // changed to be thin wrappers on native Windows CONDITION_VARIABLEs, and this function + // becomes trivial to provide. + U_ASSERT(FALSE); +} + +U_CAPI void U_EXPORT2 +umtx_condWait(UConditionVar *condition, UMutex *mutex) { + if (condition->fEntryGate == NULL) { + // Note: because the associated mutex must be locked when calling + // wait, we know that there can not be multiple threads + // running here with the same condition variable. + // Meaning that lazy initialization is safe. + U_ASSERT(condition->fExitGate == NULL); + condition->fEntryGate = CreateEvent(NULL, // Security Attributes + TRUE, // Manual Reset + FALSE, // Initially reset + NULL); // Name. + U_ASSERT(condition->fEntryGate != NULL); + condition->fExitGate = CreateEvent(NULL, TRUE, TRUE, NULL); + U_ASSERT(condition->fExitGate != NULL); + } + + condition->fWaitCount++; + umtx_unlock(mutex); + WaitForSingleObject(condition->fEntryGate, INFINITE); + umtx_lock(mutex); + condition->fWaitCount--; + if (condition->fWaitCount == 0) { + // All threads that were waiting at the entry gate have woken up + // and moved through. Shut the entry gate and open the exit gate. + ResetEvent(condition->fEntryGate); + SetEvent(condition->fExitGate); } 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 + umtx_unlock(mutex); + WaitForSingleObject(condition->fExitGate, INFINITE); + umtx_lock(mutex); } } -#elif U_PLATFORM_HAS_WIN32_API -// -// Windows implementation of UMutex. + +#elif U_PLATFORM_IMPLEMENTS_POSIX + +//------------------------------------------------------------------------------------------- // -// Each UMutex has a corresponding Windows CRITICAL_SECTION. -// CRITICAL_SECTIONS must be initialized before use. This is done -// with a InitOnceExcuteOnce operation. +// POSIX specific definitions // -// 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(param); - U_ASSERT(sizeof(CRITICAL_SECTION) <= sizeof(mutex->fCS)); - InitializeCriticalSection((CRITICAL_SECTION *)mutex->fCS); - return TRUE; -} +//------------------------------------------------------------------------------------------- + +# include + +// Each UMutex consists of a pthread_mutex_t. +// All are statically initialized and ready for use. +// There is no runtime mutex initialization code needed. -/* - * 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); - } + int sysErr = pthread_mutex_lock(&mutex->fMutex); + (void)sysErr; // Suppress unused variable warnings. + U_ASSERT(sysErr == 0); } + 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); + int sysErr = pthread_mutex_unlock(&mutex->fMutex); + (void)sysErr; // Suppress unused variable warnings. + U_ASSERT(sysErr == 0); +} + + +U_CAPI void U_EXPORT2 +umtx_condWait(UConditionVar *cond, UMutex *mutex) { + if (mutex == NULL) { + mutex = &globalMutex; } + int sysErr = pthread_cond_wait(&cond->fCondition, &mutex->fMutex); + (void)sysErr; + U_ASSERT(sysErr == 0); } -#endif // Windows Implementation +U_CAPI void U_EXPORT2 +umtx_condBroadcast(UConditionVar *cond) { + int sysErr = pthread_cond_broadcast(&cond->fCondition); + (void)sysErr; + U_ASSERT(sysErr == 0); +} +U_CAPI void U_EXPORT2 +umtx_condSignal(UConditionVar *cond) { + int sysErr = pthread_cond_signal(&cond->fCondition); + (void)sysErr; + U_ASSERT(sysErr == 0); +} -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; - } +U_NAMESPACE_BEGIN + +static pthread_mutex_t initMutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t initCondition = PTHREAD_COND_INITIALIZER; - // 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; + +// This function is called when a test of a UInitOnce::fState reveals that +// initialization has not completed, that we either need to call the +// function on this thread, or wait for some other thread to complete. +// +// The actual call to the init function is made inline by template code +// that knows the C++ types involved. This function returns TRUE if +// the caller needs to call the Init function. +// +U_COMMON_API UBool U_EXPORT2 +umtx_initImplPreInit(UInitOnce &uio) { + pthread_mutex_lock(&initMutex); + int32_t state = uio.fState; + if (state == 0) { + umtx_storeRelease(uio.fState, 1); + pthread_mutex_unlock(&initMutex); + return TRUE; // Caller will next call the init function. + } else { + while (uio.fState == 1) { + // Another thread is currently running the initialization. + // Wait until it completes. + pthread_cond_wait(&initCondition, &initMutex); + } + pthread_mutex_unlock(&initMutex); + U_ASSERT(uio.fState == 2); + return FALSE; + } } -/* 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; +// This function is called by the thread that ran an initialization function, +// just after completing the function. +// Some threads may be waiting on the condition, requiring the broadcast wakeup. +// Some threads may be racing to test the fState variable outside of the mutex, +// requiring the use of store/release when changing its value. + +U_COMMON_API void U_EXPORT2 +umtx_initImplPostInit(UInitOnce &uio) { + pthread_mutex_lock(&initMutex); + umtx_storeRelease(uio.fState, 2); + pthread_cond_broadcast(&initCondition); + pthread_mutex_unlock(&initMutex); } +U_NAMESPACE_END +// End of POSIX specific umutex implementation. -/*----------------------------------------------------------------- - * - * Atomic Increment and Decrement - * umtx_atomic_inc - * umtx_atomic_dec - * - *----------------------------------------------------------------*/ +#else // Platform #define chain. -/* 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; +#error Unknown Platform -#if defined (POSIX) && (U_HAVE_GCC_ATOMICS == 0) +#endif // Platform #define chain. + + +//------------------------------------------------------------------------------- +// +// Atomic Operations, out-of-line versions. +// These are conditional, only defined if better versions +// were not available for the platform. +// +// These versions are platform neutral. +// +//-------------------------------------------------------------------------------- + +#if defined U_NO_PLATFORM_ATOMICS static UMutex gIncDecMutex = U_MUTEX_INITIALIZER; -#endif -U_CAPI int32_t U_EXPORT2 -umtx_atomic_inc(int32_t *p) { +U_NAMESPACE_BEGIN + +U_COMMON_API int32_t U_EXPORT2 +umtx_atomic_inc(u_atomic_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 - } + umtx_lock(&gIncDecMutex); + retVal = ++(*p); + umtx_unlock(&gIncDecMutex); return retVal; } -U_CAPI int32_t U_EXPORT2 -umtx_atomic_dec(int32_t *p) { + +U_COMMON_API int32_t U_EXPORT2 +umtx_atomic_dec(u_atomic_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 - } + umtx_lock(&gIncDecMutex); + retVal = --(*p); + umtx_unlock(&gIncDecMutex); return retVal; } +U_COMMON_API int32_t U_EXPORT2 +umtx_loadAcquire(u_atomic_int32_t &var) { + umtx_lock(&gIncDecMutex); + int32_t val = var; + umtx_unlock(&gIncDecMutex); + return val; +} +U_COMMON_API void U_EXPORT2 +umtx_storeRelease(u_atomic_int32_t &var, int32_t val) { + umtx_lock(&gIncDecMutex); + var = val; + umtx_unlock(&gIncDecMutex); +} -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; - } +U_NAMESPACE_END +#endif - 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); +//-------------------------------------------------------------------------- +// +// Deprecated functions for setting user mutexes. +// +//-------------------------------------------------------------------------- + +U_DEPRECATED void U_EXPORT2 +u_setMutexFunctions(const void * /*context */, UMtxInitFn *, UMtxFn *, + UMtxFn *, UMtxFn *, UErrorCode *status) { + if (U_SUCCESS(*status)) { + *status = U_UNSUPPORTED_ERROR; } -#endif + return; } -/* - * 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; + +U_DEPRECATED void U_EXPORT2 +u_setAtomicIncDecFunctions(const void * /*context */, UMtxAtomicFn *, UMtxAtomicFn *, + UErrorCode *status) { + if (U_SUCCESS(*status)) { + *status = U_UNSUPPORTED_ERROR; + } + return; }