summaryrefslogtreecommitdiff
path: root/intl/icu/source/common/umutex.cpp
blob: a7299a5cfd1603721fb58d7b3f5608d3d03131da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
/*
******************************************************************************
*
*   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;
}