]>
Commit | Line | Data |
---|---|---|
51004dcb A |
1 | /* |
2 | ****************************************************************************** | |
3 | * | |
4 | * Copyright (C) 1997-2012, International Business Machines | |
5 | * Corporation and others. All Rights Reserved. | |
6 | * | |
7 | ****************************************************************************** | |
8 | * | |
9 | * File umutex.cpp | |
10 | * | |
11 | * Modification History: | |
12 | * | |
13 | * Date Name Description | |
14 | * 04/02/97 aliu Creation. | |
15 | * 04/07/99 srl updated | |
16 | * 05/13/99 stephen Changed to umutex (from cmutex). | |
17 | * 11/22/99 aliu Make non-global mutex autoinitialize [j151] | |
18 | ****************************************************************************** | |
19 | */ | |
20 | ||
21 | #include "unicode/utypes.h" | |
22 | #include "uassert.h" | |
23 | #include "ucln_cmn.h" | |
24 | ||
25 | /* | |
26 | * ICU Mutex wrappers. Wrap operating system mutexes, giving the rest of ICU a | |
27 | * platform independent set of mutex operations. For internal ICU use only. | |
28 | */ | |
29 | ||
30 | #if U_PLATFORM_HAS_WIN32_API | |
31 | /* Prefer native Windows APIs even if POSIX is implemented (i.e., on Cygwin). */ | |
32 | # undef POSIX | |
33 | #elif U_PLATFORM_IMPLEMENTS_POSIX | |
34 | # define POSIX | |
35 | #else | |
36 | # undef POSIX | |
37 | #endif | |
38 | ||
39 | #if defined(POSIX) | |
40 | # include <pthread.h> /* must be first, so that we get the multithread versions of things. */ | |
41 | #endif /* POSIX */ | |
42 | ||
43 | #if U_PLATFORM_HAS_WIN32_API | |
44 | # define WIN32_LEAN_AND_MEAN | |
45 | # define VC_EXTRALEAN | |
46 | # define NOUSER | |
47 | # define NOSERVICE | |
48 | # define NOIME | |
49 | # define NOMCX | |
50 | # include <windows.h> | |
51 | #endif | |
52 | ||
53 | #include "umutex.h" | |
54 | #include "cmemory.h" | |
55 | ||
56 | #if U_PLATFORM_HAS_WIN32_API | |
57 | #define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ | |
58 | InterlockedCompareExchangePointer(dest, newval, oldval) | |
59 | ||
60 | #elif defined(POSIX) | |
61 | #if (U_HAVE_GCC_ATOMICS == 1) | |
62 | #define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ | |
63 | __sync_val_compare_and_swap(dest, oldval, newval) | |
64 | #else | |
65 | #define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ | |
66 | mutexed_compare_and_swap(dest, newval, oldval) | |
67 | #endif | |
68 | ||
69 | #else | |
70 | // Unknown platform. Note that user can still set mutex functions at run time. | |
71 | #define SYNC_COMPARE_AND_SWAP(dest, oldval, newval) \ | |
72 | mutexed_compare_and_swap(dest, newval, oldval) | |
73 | #endif | |
74 | ||
75 | static void *mutexed_compare_and_swap(void **dest, void *newval, void *oldval); | |
76 | ||
77 | // The ICU global mutex. Used when ICU implementation code passes NULL for the mutex pointer. | |
78 | static UMutex globalMutex = U_MUTEX_INITIALIZER; | |
79 | ||
80 | // Implementation mutex. Used for compare & swap when no intrinsic is available, and | |
81 | // for safe initialization of user defined mutexes. | |
82 | static UMutex implMutex = U_MUTEX_INITIALIZER; | |
83 | ||
84 | // List of all user mutexes that have been initialized. | |
85 | // Used to allow us to destroy them when cleaning up ICU. | |
86 | // Normal platform mutexes are not kept track of in this way - they survive until the process is shut down. | |
87 | // Normal platfrom mutexes don't allocate storage, so not cleaning them up won't trigger memory leak complaints. | |
88 | // | |
89 | // Note: putting this list in allocated memory would be awkward to arrange, because memory allocations | |
90 | // are used as a flag to indicate that ICU has been initialized, and setting other ICU | |
91 | // override functions will no longer work. | |
92 | // | |
93 | static const int MUTEX_LIST_LIMIT = 100; | |
94 | static UMutex *gMutexList[MUTEX_LIST_LIMIT]; | |
95 | static int gMutexListSize = 0; | |
96 | ||
97 | ||
98 | /* | |
99 | * User mutex implementation functions. If non-null, call back to these rather than | |
100 | * directly using the system (Posix or Windows) APIs. See u_setMutexFunctions(). | |
101 | * (declarations are in uclean.h) | |
102 | */ | |
103 | static UMtxInitFn *pMutexInitFn = NULL; | |
104 | static UMtxFn *pMutexDestroyFn = NULL; | |
105 | static UMtxFn *pMutexLockFn = NULL; | |
106 | static UMtxFn *pMutexUnlockFn = NULL; | |
107 | static const void *gMutexContext = NULL; | |
108 | ||
109 | ||
110 | // Clean up (undo) the effects of u_setMutexFunctions(). | |
111 | // | |
112 | static void usrMutexCleanup() { | |
113 | if (pMutexDestroyFn != NULL) { | |
114 | for (int i = 0; i < gMutexListSize; i++) { | |
115 | UMutex *m = gMutexList[i]; | |
116 | U_ASSERT(m->fInitialized); | |
117 | (*pMutexDestroyFn)(gMutexContext, &m->fUserMutex); | |
118 | m->fInitialized = FALSE; | |
119 | } | |
120 | (*pMutexDestroyFn)(gMutexContext, &globalMutex.fUserMutex); | |
121 | (*pMutexDestroyFn)(gMutexContext, &implMutex.fUserMutex); | |
122 | } | |
123 | gMutexListSize = 0; | |
124 | pMutexInitFn = NULL; | |
125 | pMutexDestroyFn = NULL; | |
126 | pMutexLockFn = NULL; | |
127 | pMutexUnlockFn = NULL; | |
128 | gMutexContext = NULL; | |
129 | } | |
130 | ||
131 | ||
132 | /* | |
133 | * User mutex lock. | |
134 | * | |
135 | * User mutexes need to be initialized before they can be used. We use the impl mutex | |
136 | * to synchronize the initialization check. This could be sped up on platforms that | |
137 | * support alternate ways to safely check the initialization flag. | |
138 | * | |
139 | */ | |
140 | static void usrMutexLock(UMutex *mutex) { | |
141 | UErrorCode status = U_ZERO_ERROR; | |
142 | if (!(mutex == &implMutex || mutex == &globalMutex)) { | |
143 | umtx_lock(&implMutex); | |
144 | if (!mutex->fInitialized) { | |
145 | (*pMutexInitFn)(gMutexContext, &mutex->fUserMutex, &status); | |
146 | U_ASSERT(U_SUCCESS(status)); | |
147 | mutex->fInitialized = TRUE; | |
148 | U_ASSERT(gMutexListSize < MUTEX_LIST_LIMIT); | |
149 | if (gMutexListSize < MUTEX_LIST_LIMIT) { | |
150 | gMutexList[gMutexListSize] = mutex; | |
151 | ++gMutexListSize; | |
152 | } | |
153 | } | |
154 | umtx_unlock(&implMutex); | |
155 | } | |
156 | (*pMutexLockFn)(gMutexContext, &mutex->fUserMutex); | |
157 | } | |
158 | ||
159 | ||
160 | ||
161 | #if defined(POSIX) | |
162 | ||
163 | // | |
164 | // POSIX implementation of UMutex. | |
165 | // | |
166 | // Each UMutex has a corresponding pthread_mutex_t. | |
167 | // All are statically initialized and ready for use. | |
168 | // There is no runtime mutex initialization code needed. | |
169 | ||
170 | U_CAPI void U_EXPORT2 | |
171 | umtx_lock(UMutex *mutex) { | |
172 | if (mutex == NULL) { | |
173 | mutex = &globalMutex; | |
174 | } | |
175 | if (pMutexLockFn) { | |
176 | usrMutexLock(mutex); | |
177 | } else { | |
178 | #if U_DEBUG | |
179 | // #if to avoid unused variable warnings in non-debug builds. | |
180 | int sysErr = pthread_mutex_lock(&mutex->fMutex); | |
181 | U_ASSERT(sysErr == 0); | |
182 | #else | |
183 | pthread_mutex_lock(&mutex->fMutex); | |
184 | #endif | |
185 | } | |
186 | } | |
187 | ||
188 | ||
189 | U_CAPI void U_EXPORT2 | |
190 | umtx_unlock(UMutex* mutex) | |
191 | { | |
192 | if (mutex == NULL) { | |
193 | mutex = &globalMutex; | |
194 | } | |
195 | if (pMutexUnlockFn) { | |
196 | (*pMutexUnlockFn)(gMutexContext, &mutex->fUserMutex); | |
197 | } else { | |
198 | #if U_DEBUG | |
199 | // #if to avoid unused variable warnings in non-debug builds. | |
200 | int sysErr = pthread_mutex_unlock(&mutex->fMutex); | |
201 | U_ASSERT(sysErr == 0); | |
202 | #else | |
203 | pthread_mutex_unlock(&mutex->fMutex); | |
204 | #endif | |
205 | } | |
206 | } | |
207 | ||
208 | #elif U_PLATFORM_HAS_WIN32_API | |
209 | // | |
210 | // Windows implementation of UMutex. | |
211 | // | |
212 | // Each UMutex has a corresponding Windows CRITICAL_SECTION. | |
213 | // CRITICAL_SECTIONS must be initialized before use. This is done | |
214 | // with a InitOnceExcuteOnce operation. | |
215 | // | |
216 | // InitOnceExecuteOnce was introduced with Windows Vista. For now ICU | |
217 | // must support Windows XP, so we roll our own. ICU will switch to the | |
218 | // native Windows InitOnceExecuteOnce when possible. | |
219 | ||
220 | typedef UBool (*U_PINIT_ONCE_FN) ( | |
221 | U_INIT_ONCE *initOnce, | |
222 | void *parameter, | |
223 | void **context | |
224 | ); | |
225 | ||
226 | UBool u_InitOnceExecuteOnce( | |
227 | U_INIT_ONCE *initOnce, | |
228 | U_PINIT_ONCE_FN initFn, | |
229 | void *parameter, | |
230 | void **context) { | |
231 | for (;;) { | |
232 | long previousState = InterlockedCompareExchange( | |
233 | &initOnce->fState, // Destination, | |
234 | 1, // Exchange Value | |
235 | 0); // Compare value | |
236 | if (previousState == 2) { | |
237 | // Initialization was already completed. | |
238 | if (context != NULL) { | |
239 | *context = initOnce->fContext; | |
240 | } | |
241 | return TRUE; | |
242 | } | |
243 | if (previousState == 1) { | |
244 | // Initialization is in progress in some other thread. | |
245 | // Loop until it completes. | |
246 | Sleep(1); | |
247 | continue; | |
248 | } | |
249 | ||
250 | // Initialization needed. Execute the callback function to do it. | |
251 | U_ASSERT(previousState == 0); | |
252 | U_ASSERT(initOnce->fState == 1); | |
253 | UBool success = (*initFn)(initOnce, parameter, &initOnce->fContext); | |
254 | U_ASSERT(success); // ICU is not supporting the failure case. | |
255 | ||
256 | // Assign the state indicating that initialization has completed. | |
257 | // Using InterlockedCompareExchange to do it ensures that all | |
258 | // threads will have a consistent view of memory. | |
259 | previousState = InterlockedCompareExchange(&initOnce->fState, 2, 1); | |
260 | U_ASSERT(previousState == 1); | |
261 | // Next loop iteration will see the initialization and return. | |
262 | } | |
263 | }; | |
264 | ||
265 | static UBool winMutexInit(U_INIT_ONCE *initOnce, void *param, void **context) { | |
266 | UMutex *mutex = static_cast<UMutex *>(param); | |
267 | U_ASSERT(sizeof(CRITICAL_SECTION) <= sizeof(mutex->fCS)); | |
268 | InitializeCriticalSection((CRITICAL_SECTION *)mutex->fCS); | |
269 | return TRUE; | |
270 | } | |
271 | ||
272 | /* | |
273 | * umtx_lock | |
274 | */ | |
275 | U_CAPI void U_EXPORT2 | |
276 | umtx_lock(UMutex *mutex) { | |
277 | if (mutex == NULL) { | |
278 | mutex = &globalMutex; | |
279 | } | |
280 | if (pMutexLockFn) { | |
281 | usrMutexLock(mutex); | |
282 | } else { | |
283 | u_InitOnceExecuteOnce(&mutex->fInitOnce, winMutexInit, mutex, NULL); | |
284 | EnterCriticalSection((CRITICAL_SECTION *)mutex->fCS); | |
285 | } | |
286 | } | |
287 | ||
288 | U_CAPI void U_EXPORT2 | |
289 | umtx_unlock(UMutex* mutex) | |
290 | { | |
291 | if (mutex == NULL) { | |
292 | mutex = &globalMutex; | |
293 | } | |
294 | if (pMutexUnlockFn) { | |
295 | (*pMutexUnlockFn)(gMutexContext, &mutex->fUserMutex); | |
296 | } else { | |
297 | LeaveCriticalSection((CRITICAL_SECTION *)mutex->fCS); | |
298 | } | |
299 | } | |
300 | ||
301 | #endif // Windows Implementation | |
302 | ||
303 | ||
304 | U_CAPI void U_EXPORT2 | |
305 | u_setMutexFunctions(const void *context, UMtxInitFn *i, UMtxFn *d, UMtxFn *l, UMtxFn *u, | |
306 | UErrorCode *status) { | |
307 | if (U_FAILURE(*status)) { | |
308 | return; | |
309 | } | |
310 | ||
311 | /* Can not set a mutex function to a NULL value */ | |
312 | if (i==NULL || d==NULL || l==NULL || u==NULL) { | |
313 | *status = U_ILLEGAL_ARGUMENT_ERROR; | |
314 | return; | |
315 | } | |
316 | ||
317 | /* If ICU is not in an initial state, disallow this operation. */ | |
318 | if (cmemory_inUse()) { | |
319 | *status = U_INVALID_STATE_ERROR; | |
320 | return; | |
321 | } | |
322 | ||
323 | // Clean up any previously set user mutex functions. | |
324 | // It's possible to call u_setMutexFunctions() more than once without without explicitly cleaning up, | |
325 | // and the last call should take. Kind of a corner case, but it worked once, there is a test for | |
326 | // it, so we keep it working. The global and impl mutexes will have been created by the | |
327 | // previous u_setMutexFunctions(), and now need to be destroyed. | |
328 | ||
329 | usrMutexCleanup(); | |
330 | ||
331 | /* Swap in the mutex function pointers. */ | |
332 | pMutexInitFn = i; | |
333 | pMutexDestroyFn = d; | |
334 | pMutexLockFn = l; | |
335 | pMutexUnlockFn = u; | |
336 | gMutexContext = context; | |
337 | gMutexListSize = 0; | |
338 | ||
339 | /* Initialize the global and impl mutexes. Safe to do at this point because | |
340 | * u_setMutexFunctions must be done in a single-threaded envioronment. Not thread safe. | |
341 | */ | |
342 | (*pMutexInitFn)(gMutexContext, &globalMutex.fUserMutex, status); | |
343 | globalMutex.fInitialized = TRUE; | |
344 | (*pMutexInitFn)(gMutexContext, &implMutex.fUserMutex, status); | |
345 | implMutex.fInitialized = TRUE; | |
346 | } | |
347 | ||
348 | ||
349 | ||
350 | /* synchronized compare and swap function, for use when OS or compiler built-in | |
351 | * equivalents aren't available. | |
352 | */ | |
353 | static void *mutexed_compare_and_swap(void **dest, void *newval, void *oldval) { | |
354 | umtx_lock(&implMutex); | |
355 | void *temp = *dest; | |
356 | if (temp == oldval) { | |
357 | *dest = newval; | |
358 | } | |
359 | umtx_unlock(&implMutex); | |
360 | ||
361 | return temp; | |
362 | } | |
363 | ||
364 | ||
365 | ||
366 | /*----------------------------------------------------------------- | |
367 | * | |
368 | * Atomic Increment and Decrement | |
369 | * umtx_atomic_inc | |
370 | * umtx_atomic_dec | |
371 | * | |
372 | *----------------------------------------------------------------*/ | |
373 | ||
374 | /* Pointers to user-supplied inc/dec functions. Null if no funcs have been set. */ | |
375 | static UMtxAtomicFn *pIncFn = NULL; | |
376 | static UMtxAtomicFn *pDecFn = NULL; | |
377 | static const void *gIncDecContext = NULL; | |
378 | ||
379 | #if defined (POSIX) && (U_HAVE_GCC_ATOMICS == 0) | |
380 | static UMutex gIncDecMutex = U_MUTEX_INITIALIZER; | |
381 | #endif | |
382 | ||
383 | U_CAPI int32_t U_EXPORT2 | |
384 | umtx_atomic_inc(int32_t *p) { | |
385 | int32_t retVal; | |
386 | if (pIncFn) { | |
387 | retVal = (*pIncFn)(gIncDecContext, p); | |
388 | } else { | |
389 | #if U_PLATFORM_HAS_WIN32_API | |
390 | retVal = InterlockedIncrement((LONG*)p); | |
391 | #elif defined(USE_MAC_OS_ATOMIC_INCREMENT) | |
392 | retVal = OSAtomicIncrement32Barrier(p); | |
393 | #elif (U_HAVE_GCC_ATOMICS == 1) | |
394 | retVal = __sync_add_and_fetch(p, 1); | |
395 | #elif defined (POSIX) | |
396 | umtx_lock(&gIncDecMutex); | |
397 | retVal = ++(*p); | |
398 | umtx_unlock(&gIncDecMutex); | |
399 | #else | |
400 | /* Unknown Platform. */ | |
401 | retVal = ++(*p); | |
402 | #endif | |
403 | } | |
404 | return retVal; | |
405 | } | |
406 | ||
407 | U_CAPI int32_t U_EXPORT2 | |
408 | umtx_atomic_dec(int32_t *p) { | |
409 | int32_t retVal; | |
410 | if (pDecFn) { | |
411 | retVal = (*pDecFn)(gIncDecContext, p); | |
412 | } else { | |
413 | #if U_PLATFORM_HAS_WIN32_API | |
414 | retVal = InterlockedDecrement((LONG*)p); | |
415 | #elif defined(USE_MAC_OS_ATOMIC_INCREMENT) | |
416 | retVal = OSAtomicDecrement32Barrier(p); | |
417 | #elif (U_HAVE_GCC_ATOMICS == 1) | |
418 | retVal = __sync_sub_and_fetch(p, 1); | |
419 | #elif defined (POSIX) | |
420 | umtx_lock(&gIncDecMutex); | |
421 | retVal = --(*p); | |
422 | umtx_unlock(&gIncDecMutex); | |
423 | #else | |
424 | /* Unknown Platform. */ | |
425 | retVal = --(*p); | |
426 | #endif | |
427 | } | |
428 | return retVal; | |
429 | } | |
430 | ||
431 | ||
432 | ||
433 | U_CAPI void U_EXPORT2 | |
434 | u_setAtomicIncDecFunctions(const void *context, UMtxAtomicFn *ip, UMtxAtomicFn *dp, | |
435 | UErrorCode *status) { | |
436 | if (U_FAILURE(*status)) { | |
437 | return; | |
438 | } | |
439 | /* Can not set a mutex function to a NULL value */ | |
440 | if (ip==NULL || dp==NULL) { | |
441 | *status = U_ILLEGAL_ARGUMENT_ERROR; | |
442 | return; | |
443 | } | |
444 | /* If ICU is not in an initial state, disallow this operation. */ | |
445 | if (cmemory_inUse()) { | |
446 | *status = U_INVALID_STATE_ERROR; | |
447 | return; | |
448 | } | |
449 | ||
450 | pIncFn = ip; | |
451 | pDecFn = dp; | |
452 | gIncDecContext = context; | |
453 | ||
454 | #if U_DEBUG | |
455 | { | |
456 | int32_t testInt = 0; | |
457 | U_ASSERT(umtx_atomic_inc(&testInt) == 1); /* Sanity Check. Do the functions work at all? */ | |
458 | U_ASSERT(testInt == 1); | |
459 | U_ASSERT(umtx_atomic_dec(&testInt) == 0); | |
460 | U_ASSERT(testInt == 0); | |
461 | } | |
462 | #endif | |
463 | } | |
464 | ||
465 | ||
466 | /* | |
467 | * Mutex Cleanup Function | |
468 | * Reset the mutex function callback pointers. | |
469 | * Called from the global ICU u_cleanup() function. | |
470 | */ | |
471 | U_CFUNC UBool umtx_cleanup(void) { | |
472 | /* Extra, do-nothing function call to suppress compiler warnings on platforms where | |
473 | * mutexed_compare_and_swap is not otherwise used. */ | |
474 | void *pv = &globalMutex; | |
475 | mutexed_compare_and_swap(&pv, NULL, NULL); | |
476 | usrMutexCleanup(); | |
477 | ||
478 | pIncFn = NULL; | |
479 | pDecFn = NULL; | |
480 | gIncDecContext = NULL; | |
481 | ||
482 | return TRUE; | |
483 | } |