]> git.saurik.com Git - apple/icu.git/blame - icuSources/common/umutex.c
ICU-6.2.22.tar.gz
[apple/icu.git] / icuSources / common / umutex.c
CommitLineData
b75a7d8f
A
1/*
2******************************************************************************
3*
374ca955 4* Copyright (C) 1997-2004, International Business Machines
b75a7d8f
A
5* Corporation and others. All Rights Reserved.
6*
7******************************************************************************
8*
9* File CMUTEX.C
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/* Assume POSIX, and modify as necessary below */
22#define POSIX
23
24#if defined(_WIN32)
25#undef POSIX
26#endif
27#if defined(macintosh)
28#undef POSIX
29#endif
30#if defined(OS2)
31#undef POSIX
32#endif
33
34
b75a7d8f
A
35#include "unicode/utypes.h"
36#include "uassert.h"
374ca955 37#include "ucln_cmn.h"
b75a7d8f
A
38
39
40#if defined(POSIX) && (ICU_USE_THREADS==1)
b75a7d8f
A
41# include <pthread.h> /* must be first, so that we get the multithread versions of things. */
42
b75a7d8f
A
43#endif /* POSIX && (ICU_USE_THREADS==1) */
44
45#ifdef WIN32
46# define WIN32_LEAN_AND_MEAN
374ca955 47# define VC_EXTRALEAN
b75a7d8f
A
48# define NOUSER
49# define NOSERVICE
50# define NOIME
51# define NOMCX
52# include <windows.h>
53#endif
54
55#include "umutex.h"
56#include "cmemory.h"
57
374ca955
A
58/*
59 * A note on ICU Mutex Initialization and ICU startup:
60 *
61 * ICU mutexes, as used through the rest of the ICU code, are self-initializing.
62 * To make this work, ICU uses the _ICU GLobal Mutex_ to synchronize the lazy init
63 * of other ICU mutexes. For the global mutex itself, we need some other mechanism
64 * to safely initialize it on first use. This becomes important if two or more
65 * threads were more or less simultaenously the first to use ICU in a process, and
66 * were racing into the mutex initialization code.
67 *
68 * The solution for the global mutex init is platform dependent.
69 * On POSIX systems, C-style init can be used on a mutex, with the
70 * macro PTHREAD_MUTEX_INITIALIZER. The mutex is then ready for use, without
71 * first calling pthread_mutex_init().
72 *
73 * Windows has no equivalent statically initialized mutex or CRITICAL SECION.
74 * InitializeCriticalSection() must be called. If the global mutex does not
75 * appear to be initialized, a thread will create and initialize a new
76 * CRITICAL_SECTION, then use a Windows InterlockedCompareAndExchange to
77 * avoid problems with race conditions.
78 *
79 * If an application has overridden the ICU mutex implementation
80 * by calling u_setMutexFunctions(), the user supplied init function must
81 * be safe in the event that multiple threads concurrently attempt to init
82 * the same mutex. The first thread should do the init, and the others should
83 * have no effect.
84 *
85 */
86
87#define MAX_MUTEXES 30
88static UMTX gGlobalMutex = NULL;
89static UMTX gIncDecMutex = NULL;
b75a7d8f 90#if (ICU_USE_THREADS == 1)
374ca955
A
91static UBool gMutexPoolInitialized = FALSE;
92static char gMutexesInUse[MAX_MUTEXES];
93
94#if defined(WIN32)
95/*-------------------------------------------------------------
96 *
97 * WINDOWS platform variable declarations
98 *
99 *-------------------------------------------------------------*/
100static CRITICAL_SECTION gMutexes[MAX_MUTEXES];
101static CRITICAL_SECTION gGlobalWinMutex;
102
103
104/* On WIN32 mutexes are reentrant. This makes it difficult to debug
105 * deadlocking problems that show up on POSIXy platforms, where
106 * mutexes deadlock upon reentry. ICU contains checking code for
107 * the global mutex as well as for other mutexes in the pool.
108 *
109 * This is for debugging purposes.
110 *
111 * This has no effect on non-WIN32 platforms, non-DEBUG builds, and
112 * non-ICU_USE_THREADS builds.
113 *
114 * Note: The CRITICAL_SECTION structure already has a RecursionCount
115 * member that can be used for this purpose, but portability to
116 * Win98/NT/2K needs to be tested before use. Works fine on XP.
117 * After portability is confirmed, the built-in RecursionCount can be
118 * used, and the gRecursionCountPool can be removed.
119 *
120 * Note: Non-global mutex checking only happens if there is no custom
121 * pMutexLockFn defined. Use one function, not two (don't use
122 * pMutexLockFn and pMutexUnlockFn) so the increment and decrement of
123 * the recursion count don't get out of sync. Users might set just
124 * one function, e.g., to perform a custom action, followed by a
125 * standard call to EnterCriticalSection.
126 */
127#if defined(U_DEBUG) && (ICU_USE_THREADS==1)
128static int32_t gRecursionCount = 0; /* detect global mutex locking */
129static int32_t gRecursionCountPool[MAX_MUTEXES]; /* ditto for non-global */
130#endif
b75a7d8f 131
b75a7d8f 132
374ca955
A
133#elif defined(POSIX)
134/*-------------------------------------------------------------
135 *
136 * POSIX platform variable declarations
137 *
138 *-------------------------------------------------------------*/
139static pthread_mutex_t gMutexes[MAX_MUTEXES] = {
140 PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
141 PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
142 PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
143 PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
144 PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
145 PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
146 PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER
147};
b75a7d8f 148
374ca955
A
149#else
150/*-------------------------------------------------------------
151 *
152 * UNKNOWN platform declarations
153 *
154 *-------------------------------------------------------------*/
155static void *gMutexes[MAX_MUTEXES] = {
156 NULL, NULL, NULL,
157 NULL, NULL, NULL,
158 NULL, NULL, NULL,
159 NULL, NULL, NULL,
160 NULL, NULL, NULL,
161 NULL, NULL, NULL,
162 NULL, NULL };
163
164/* Unknown platform. OK so long as ICU_USE_THREAD is not set.
165 Note that user can still set mutex functions at run time,
166 and that the global mutex variable is still needed in that case. */
167#if (ICU_USE_THREADS == 1)
168#error no ICU mutex implementation for this platform
169#endif
b75a7d8f
A
170#endif
171#endif /* ICU_USE_THREADS==1 */
172
173
174
b75a7d8f 175
374ca955
A
176/*
177 * User mutex implementation functions. If non-null, call back to these rather than
178 * directly using the system (Posix or Windows) APIs.
179 * (declarations are in uclean.h)
180 */
181static UMtxInitFn *pMutexInitFn = NULL;
182static UMtxFn *pMutexDestroyFn = NULL;
183static UMtxFn *pMutexLockFn = NULL;
184static UMtxFn *pMutexUnlockFn = NULL;
185static const void *gMutexContext = NULL;
186
187
188
189/*
190 * umtx_lock
191 */
b75a7d8f
A
192U_CAPI void U_EXPORT2
193umtx_lock(UMTX *mutex)
194{
374ca955 195 if (mutex == NULL) {
b75a7d8f
A
196 mutex = &gGlobalMutex;
197 }
198
374ca955
A
199 if (*mutex == NULL) {
200 /* Lock of an uninitialized mutex. Initialize it before proceeding. */
201 umtx_init(mutex);
b75a7d8f
A
202 }
203
374ca955
A
204 if (pMutexLockFn != NULL) {
205 (*pMutexLockFn)(gMutexContext, mutex);
206 } else {
b75a7d8f 207
374ca955
A
208#if (ICU_USE_THREADS == 1)
209#if defined(WIN32)
210 EnterCriticalSection((CRITICAL_SECTION*) *mutex);
b75a7d8f 211#elif defined(POSIX)
374ca955
A
212 pthread_mutex_lock((pthread_mutex_t*) *mutex);
213#endif /* cascade of platforms */
214#endif /* ICU_USE_THREADS==1 */
215 }
b75a7d8f 216
374ca955
A
217#if defined(WIN32) && defined(U_DEBUG) && (ICU_USE_THREADS==1)
218 if (mutex == &gGlobalMutex) { /* Detect Reentrant locking of the global mutex. */
219 gRecursionCount++; /* Recursion causes deadlocks on Unixes. */
220 U_ASSERT(gRecursionCount == 1); /* Detection works on Windows. Debug problems there. */
b75a7d8f 221 }
374ca955
A
222 /* This handles gGlobalMutex too, but only if there is no pMutexLockFn */
223 else if (pMutexLockFn == NULL) { /* see comments above */
224 int i = ((CRITICAL_SECTION*)*mutex) - &gMutexes[0];
225 U_ASSERT(i >= 0 && i < MAX_MUTEXES);
226 ++gRecursionCountPool[i];
227
228 U_ASSERT(gRecursionCountPool[i] == 1); /* !Detect Deadlock! */
229
230 /* This works and is fast, but needs testing on Win98/NT/2K.
231 See comments above. [alan]
232 U_ASSERT((CRITICAL_SECTION*)*mutex >= &gMutexes[0] &&
233 (CRITICAL_SECTION*)*mutex <= &gMutexes[MAX_MUTEXES]);
234 U_ASSERT(((CRITICAL_SECTION*)*mutex)->RecursionCount == 1);
235 */
236 }
237#endif /*U_DEBUG*/
b75a7d8f
A
238}
239
374ca955
A
240
241
242/*
243 * umtx_unlock
244 */
b75a7d8f
A
245U_CAPI void U_EXPORT2
246umtx_unlock(UMTX* mutex)
247{
374ca955 248 if(mutex == NULL) {
b75a7d8f
A
249 mutex = &gGlobalMutex;
250 }
251
374ca955
A
252 if(*mutex == NULL) {
253#if (ICU_USE_THREADS == 1)
254 U_ASSERT(FALSE); /* This mutex is not initialized. */
255#endif
256 return;
b75a7d8f
A
257 }
258
374ca955 259#if defined (WIN32) && defined (U_DEBUG) && (ICU_USE_THREADS==1)
b75a7d8f
A
260 if (mutex == &gGlobalMutex) {
261 gRecursionCount--;
374ca955 262 U_ASSERT(gRecursionCount == 0); /* Detect unlock of an already unlocked mutex */
b75a7d8f 263 }
374ca955
A
264 /* This handles gGlobalMutex too, but only if there is no pMutexLockFn */
265 else if (pMutexLockFn == NULL) { /* see comments above */
266 int i = ((CRITICAL_SECTION*)*mutex) - &gMutexes[0];
267 U_ASSERT(i >= 0 && i < MAX_MUTEXES);
268 --gRecursionCountPool[i];
269
270 U_ASSERT(gRecursionCountPool[i] == 0); /* !Detect Deadlock! */
271
272 /* This works and is fast, but needs testing on Win98/NT/2K.
273 Note that RecursionCount will be 1, not 0, since we haven't
274 left the CRITICAL_SECTION yet. See comments above. [alan]
275 U_ASSERT((CRITICAL_SECTION*)*mutex >= &gMutexes[0] &&
276 (CRITICAL_SECTION*)*mutex <= &gMutexes[MAX_MUTEXES]);
277 U_ASSERT(((CRITICAL_SECTION*)*mutex)->RecursionCount == 1);
278 */
b75a7d8f
A
279 }
280#endif
281
374ca955
A
282 if (pMutexUnlockFn) {
283 (*pMutexUnlockFn)(gMutexContext, mutex);
284 } else {
285#if (ICU_USE_THREADS==1)
286#if defined (WIN32)
287 LeaveCriticalSection((CRITICAL_SECTION*)*mutex);
288#elif defined (POSIX)
289 pthread_mutex_unlock((pthread_mutex_t*)*mutex);
290#endif /* cascade of platforms */
b75a7d8f 291#endif /* ICU_USE_THREADS == 1 */
374ca955 292 }
b75a7d8f
A
293}
294
295
296
374ca955 297
b75a7d8f 298/*
374ca955
A
299 * initGlobalMutex Do the platform specific initialization of the ICU global mutex.
300 * Separated out from the other mutexes because it is different:
301 * Mutex storage is static for POSIX, init must be thread safe
302 * without the use of another mutex.
b75a7d8f 303 */
374ca955
A
304static void initGlobalMutex() {
305 /*
306 * If User Supplied mutex functions are in use
307 * init the icu global mutex using them.
308 */
309 if (pMutexInitFn != NULL) {
310 if (gGlobalMutex==NULL) {
311 UErrorCode status = U_ZERO_ERROR;
312 (*pMutexInitFn)(gMutexContext, &gGlobalMutex, &status);
313 if (U_FAILURE(status)) {
314 /* TODO: how should errors here be handled? */
315 return;
316 }
317 }
318 return;
319 }
320
321 /* No user override of mutex functions.
322 * Use default ICU mutex implementations.
323 */
b75a7d8f 324#if (ICU_USE_THREADS == 1)
374ca955
A
325 /*
326 * for Windows, init the pool of critical sections that we
327 * will use as needed for ICU mutexes.
328 */
329#if defined (WIN32)
330 if (gMutexPoolInitialized == FALSE) {
331 int i;
332 for (i=0; i<MAX_MUTEXES; i++) {
333 InitializeCriticalSection(&gMutexes[i]);
334#if defined (U_DEBUG)
335 gRecursionCountPool[i] = 0; /* see comments above */
336#endif
b75a7d8f 337 }
374ca955
A
338 gMutexPoolInitialized = TRUE;
339 }
340#elif defined (POSIX)
341 /* TODO: experimental code. Shouldn't need to explicitly init the mutexes. */
342 if (gMutexPoolInitialized == FALSE) {
343 int i;
344 for (i=0; i<MAX_MUTEXES; i++) {
345 pthread_mutex_init(&gMutexes[i], NULL);
b75a7d8f 346 }
374ca955
A
347 gMutexPoolInitialized = TRUE;
348 }
349#endif
350
351 /*
352 * for both Windows & POSIX, the first mutex in the array is used
353 * for the ICU global mutex.
354 */
355 gGlobalMutex = &gMutexes[0];
356 gMutexesInUse[0] = 1;
357
358#else /* ICU_USE_THREADS */
359 gGlobalMutex = &gGlobalMutex; /* With no threads, we must still set the mutex to
360 * some non-null value to make the rest of the
361 * (not ifdefed) mutex code think that it is initialized.
362 */
363#endif /* ICU_USE_THREADS */
b75a7d8f 364}
b75a7d8f
A
365
366
b75a7d8f 367
b75a7d8f 368
b75a7d8f 369
374ca955
A
370U_CAPI void U_EXPORT2
371umtx_init(UMTX *mutex)
372{
373 if (mutex == NULL || mutex == &gGlobalMutex) {
374 initGlobalMutex();
b75a7d8f 375 } else {
b75a7d8f 376 umtx_lock(NULL);
374ca955
A
377 if (*mutex != NULL) {
378 /* Another thread initialized this mutex first. */
379 umtx_unlock(NULL);
b75a7d8f
A
380 return;
381 }
382
374ca955
A
383 if (pMutexInitFn != NULL) {
384 UErrorCode status = U_ZERO_ERROR;
385 (*pMutexInitFn)(gMutexContext, mutex, &status);
386 /* TODO: how to report failure on init? */
387 umtx_unlock(NULL);
388 return;
389 }
390 else {
391#if (ICU_USE_THREADS == 1)
392 /* Search through our pool of pre-allocated mutexes for one that is not
393 * already in use. */
394 int i;
395 for (i=0; i<MAX_MUTEXES; i++) {
396 if (gMutexesInUse[i] == 0) {
397 gMutexesInUse[i] = 1;
398 *mutex = &gMutexes[i];
399 break;
400 }
401 }
402#endif
b75a7d8f
A
403 }
404 umtx_unlock(NULL);
374ca955
A
405
406#if (ICU_USE_THREADS == 1)
407 /* No more mutexes were available from our pre-allocated pool. */
408 /* TODO: how best to deal with this? */
409 U_ASSERT(*mutex != NULL);
410#endif
b75a7d8f 411 }
b75a7d8f
A
412}
413
374ca955
A
414
415/*
416 * umtx_destroy. Un-initialize a mutex, releasing any underlying resources
417 * that it may be holding. Destroying an already destroyed
418 * mutex has no effect. Unlike umtx_init(), this function
419 * is not thread safe; two threads must not concurrently try to
420 * destroy the same mutex.
421 */
b75a7d8f
A
422U_CAPI void U_EXPORT2
423umtx_destroy(UMTX *mutex) {
374ca955 424 if (mutex == NULL) { /* destroy the global mutex */
b75a7d8f
A
425 mutex = &gGlobalMutex;
426 }
374ca955
A
427
428 if (*mutex == NULL) { /* someone already did it. */
b75a7d8f 429 return;
374ca955 430 }
b75a7d8f 431
374ca955
A
432 /* The life of the inc/dec mutex is tied to that of the global mutex. */
433 if (mutex == &gGlobalMutex) {
434 umtx_destroy(&gIncDecMutex);
435 }
b75a7d8f 436
374ca955
A
437 if (pMutexDestroyFn != NULL) {
438 /* Mutexes are being managed by the app. Call back to it for the destroy. */
439 (*pMutexDestroyFn)(gMutexContext, mutex);
440 }
441 else {
442#if (ICU_USE_THREADS == 1)
443 /* Return this mutex to the pool of available mutexes, if it came from the
444 * pool in the first place.
445 */
446 /* TODO use pointer math here, instead of iterating! */
447 int i;
448 for (i=0; i<MAX_MUTEXES; i++) {
449 if (*mutex == &gMutexes[i]) {
450 gMutexesInUse[i] = 0;
451 break;
452 }
453 }
b75a7d8f 454#endif
b75a7d8f
A
455 }
456
457 *mutex = NULL;
b75a7d8f
A
458}
459
460
b75a7d8f 461
374ca955
A
462U_CAPI void U_EXPORT2
463u_setMutexFunctions(const void *context, UMtxInitFn *i, UMtxFn *d, UMtxFn *l, UMtxFn *u,
464 UErrorCode *status) {
465 if (U_FAILURE(*status)) {
466 return;
467 }
b75a7d8f 468
374ca955
A
469 /* Can not set a mutex function to a NULL value */
470 if (i==NULL || d==NULL || l==NULL || u==NULL) {
471 *status = U_ILLEGAL_ARGUMENT_ERROR;
472 return;
473 }
b75a7d8f 474
374ca955
A
475 /* If ICU is not in an initial state, disallow this operation. */
476 if (cmemory_inUse()) {
477 *status = U_INVALID_STATE_ERROR;
478 return;
479 }
b75a7d8f 480
374ca955
A
481 /* Swap in the mutex function pointers. */
482 pMutexInitFn = i;
483 pMutexDestroyFn = d;
484 pMutexLockFn = l;
485 pMutexUnlockFn = u;
486 gMutexContext = context;
487 gGlobalMutex = NULL; /* For POSIX, the global mutex will be pre-initialized */
488 /* Undo that, force re-initialization when u_init() */
489 /* happens. */
b75a7d8f
A
490}
491
b75a7d8f 492
b75a7d8f 493
374ca955
A
494/*-----------------------------------------------------------------
495 *
496 * Atomic Increment and Decrement
497 * umtx_atomic_inc
498 * umtx_atomic_dec
499 *
500 *----------------------------------------------------------------*/
501
502/* Pointers to user-supplied inc/dec functions. Null if no funcs have been set. */
503static UMtxAtomicFn *pIncFn = NULL;
504static UMtxAtomicFn *pDecFn = NULL;
505static void *gIncDecContext = NULL;
b75a7d8f
A
506
507
508U_CAPI int32_t U_EXPORT2
374ca955
A
509umtx_atomic_inc(int32_t *p) {
510 int32_t retVal;
511 if (pIncFn) {
512 retVal = (*pIncFn)(gIncDecContext, p);
513 } else {
514 #if defined (WIN32) && ICU_USE_THREADS == 1
515 retVal = InterlockedIncrement((LONG*)p);
516 #elif defined (POSIX) && ICU_USE_THREADS == 1
517 umtx_lock(&gIncDecMutex);
518 retVal = ++(*p);
519 umtx_unlock(&gIncDecMutex);
520 #else
521 /* Unknown Platform, or ICU thread support compiled out. */
522 retVal = ++(*p);
523 #endif
524 }
525 return retVal;
526}
b75a7d8f 527
374ca955
A
528U_CAPI int32_t U_EXPORT2
529umtx_atomic_dec(int32_t *p) {
530 int32_t retVal;
531 if (pDecFn) {
532 retVal = (*pDecFn)(gIncDecContext, p);
533 } else {
534 #if defined (WIN32) && ICU_USE_THREADS == 1
535 retVal = InterlockedDecrement((LONG*)p);
536 #elif defined (POSIX) && ICU_USE_THREADS == 1
537 umtx_lock(&gIncDecMutex);
538 retVal = --(*p);
539 umtx_unlock(&gIncDecMutex);
540 #else
541 /* Unknown Platform, or ICU thread support compiled out. */
542 retVal = --(*p);
543 #endif
544 }
b75a7d8f
A
545 return retVal;
546}
547
374ca955 548/* TODO: Some POSIXy platforms have atomic inc/dec functions available. Use them. */
b75a7d8f 549
b75a7d8f 550
b75a7d8f
A
551
552
b75a7d8f 553
374ca955
A
554U_CAPI void U_EXPORT2
555u_setAtomicIncDecFunctions(const void *context, UMtxAtomicFn *ip, UMtxAtomicFn *dp,
556 UErrorCode *status) {
557 int32_t testInt;
558 if (U_FAILURE(*status)) {
559 return;
560 }
561 /* Can not set a mutex function to a NULL value */
562 if (ip==NULL || dp==NULL) {
563 *status = U_ILLEGAL_ARGUMENT_ERROR;
564 return;
565 }
566 /* If ICU is not in an initial state, disallow this operation. */
567 if (cmemory_inUse()) {
568 *status = U_INVALID_STATE_ERROR;
569 return;
570 }
571
572 pIncFn = ip;
573 pDecFn = dp;
b75a7d8f 574
374ca955
A
575 testInt = 0;
576 U_ASSERT(umtx_atomic_inc(&testInt) == 1); /* Sanity Check. Do the functions work at all? */
577 U_ASSERT(testInt == 1);
578 U_ASSERT(umtx_atomic_dec(&testInt) == 0);
579 U_ASSERT(testInt == 0);
b75a7d8f
A
580}
581
b75a7d8f 582
b75a7d8f 583
374ca955
A
584/*
585 * Mutex Cleanup Function
586 *
587 * Destroy the global mutex(es), and reset the mutex function callback pointers.
588 */
589U_CFUNC UBool umtx_cleanup(void) {
590 umtx_destroy(NULL);
591 pMutexInitFn = NULL;
592 pMutexDestroyFn = NULL;
593 pMutexLockFn = NULL;
594 pMutexUnlockFn = NULL;
595 gMutexContext = NULL;
596 gGlobalMutex = NULL;
597 pIncFn = NULL;
598 pDecFn = NULL;
599 gIncDecMutex = NULL;
600
601#if (ICU_USE_THREADS == 1)
602 if (gMutexPoolInitialized) {
603 int i;
604 for (i=0; i<MAX_MUTEXES; i++) {
605 if (gMutexesInUse[i]) {
606#if defined (WIN32)
607 DeleteCriticalSection(&gMutexes[i]);
608#elif defined (POSIX)
609 pthread_mutex_destroy(&gMutexes[i]);
610#endif
611 gMutexesInUse[i] = 0;
612 }
613 }
614 }
615 gMutexPoolInitialized = FALSE;
616#endif
b75a7d8f 617
374ca955
A
618 return TRUE;
619}
b75a7d8f
A
620
621