Home | History | Annotate | Download | only in common
      1 /*
      2 ******************************************************************************
      3 * Copyright (C) 2014, International Business Machines Corporation and
      4 * others. All Rights Reserved.
      5 ******************************************************************************
      6 *
      7 * File UNIFIEDCACHE.CPP
      8 ******************************************************************************
      9 */
     10 
     11 #include "uhash.h"
     12 #include "unifiedcache.h"
     13 #include "umutex.h"
     14 #include "mutex.h"
     15 #include "uassert.h"
     16 #include "ucln_cmn.h"
     17 
     18 static icu::UnifiedCache *gCache = NULL;
     19 static icu::SharedObject *gNoValue = NULL;
     20 static UMutex gCacheMutex = U_MUTEX_INITIALIZER;
     21 static UConditionVar gInProgressValueAddedCond = U_CONDITION_INITIALIZER;
     22 static icu::UInitOnce gCacheInitOnce = U_INITONCE_INITIALIZER;
     23 
     24 U_CDECL_BEGIN
     25 static UBool U_CALLCONV unifiedcache_cleanup() {
     26     gCacheInitOnce.reset();
     27     if (gCache) {
     28         delete gCache;
     29         gCache = NULL;
     30     }
     31     if (gNoValue) {
     32         delete gNoValue;
     33         gNoValue = NULL;
     34     }
     35     return TRUE;
     36 }
     37 U_CDECL_END
     38 
     39 
     40 U_NAMESPACE_BEGIN
     41 
     42 U_CAPI int32_t U_EXPORT2
     43 ucache_hashKeys(const UHashTok key) {
     44     const CacheKeyBase *ckey = (const CacheKeyBase *) key.pointer;
     45     return ckey->hashCode();
     46 }
     47 
     48 U_CAPI UBool U_EXPORT2
     49 ucache_compareKeys(const UHashTok key1, const UHashTok key2) {
     50     const CacheKeyBase *p1 = (const CacheKeyBase *) key1.pointer;
     51     const CacheKeyBase *p2 = (const CacheKeyBase *) key2.pointer;
     52     return *p1 == *p2;
     53 }
     54 
     55 U_CAPI void U_EXPORT2
     56 ucache_deleteKey(void *obj) {
     57     CacheKeyBase *p = (CacheKeyBase *) obj;
     58     delete p;
     59 }
     60 
     61 CacheKeyBase::~CacheKeyBase() {
     62 }
     63 
     64 static void U_CALLCONV cacheInit(UErrorCode &status) {
     65     U_ASSERT(gCache == NULL);
     66     ucln_common_registerCleanup(
     67             UCLN_COMMON_UNIFIED_CACHE, unifiedcache_cleanup);
     68 
     69     // gNoValue must be created first to avoid assertion error in
     70     // cache constructor.
     71     gNoValue = new SharedObject();
     72     gCache = new UnifiedCache(status);
     73     if (gCache == NULL) {
     74         status = U_MEMORY_ALLOCATION_ERROR;
     75     }
     76     if (U_FAILURE(status)) {
     77         delete gCache;
     78         delete gNoValue;
     79         gCache = NULL;
     80         gNoValue = NULL;
     81         return;
     82     }
     83     // We add a softref because we want hash elements with gNoValue to be
     84     // elligible for purging but we don't ever want gNoValue to be deleted.
     85     gNoValue->addSoftRef();
     86 }
     87 
     88 const UnifiedCache *UnifiedCache::getInstance(UErrorCode &status) {
     89     umtx_initOnce(gCacheInitOnce, &cacheInit, status);
     90     if (U_FAILURE(status)) {
     91         return NULL;
     92     }
     93     U_ASSERT(gCache != NULL);
     94     return gCache;
     95 }
     96 
     97 UnifiedCache::UnifiedCache(UErrorCode &status) {
     98     if (U_FAILURE(status)) {
     99         return;
    100     }
    101     U_ASSERT(gNoValue != NULL);
    102     fHashtable = uhash_open(
    103             &ucache_hashKeys,
    104             &ucache_compareKeys,
    105             NULL,
    106             &status);
    107     if (U_FAILURE(status)) {
    108         return;
    109     }
    110     uhash_setKeyDeleter(fHashtable, &ucache_deleteKey);
    111 }
    112 
    113 int32_t UnifiedCache::keyCount() const {
    114     Mutex lock(&gCacheMutex);
    115     return uhash_count(fHashtable);
    116 }
    117 
    118 void UnifiedCache::flush() const {
    119     Mutex lock(&gCacheMutex);
    120 
    121     // Use a loop in case cache items that are flushed held hard references to
    122     // other cache items making those additional cache items eligible for
    123     // flushing.
    124     while (_flush(FALSE));
    125     umtx_condBroadcast(&gInProgressValueAddedCond);
    126 }
    127 
    128 #ifdef UNIFIED_CACHE_DEBUG
    129 #include <stdio.h>
    130 
    131 void UnifiedCache::dump() {
    132     UErrorCode status = U_ZERO_ERROR;
    133     const UnifiedCache *cache = getInstance(status);
    134     if (U_FAILURE(status)) {
    135         fprintf(stderr, "Unified Cache: Error fetching cache.\n");
    136         return;
    137     }
    138     cache->dumpContents();
    139 }
    140 
    141 void UnifiedCache::dumpContents() const {
    142     Mutex lock(&gCacheMutex);
    143     _dumpContents();
    144 }
    145 
    146 // Dumps content of cache.
    147 // On entry, gCacheMutex must be held.
    148 // On exit, cache contents dumped to stderr.
    149 void UnifiedCache::_dumpContents() const {
    150     int32_t pos = UHASH_FIRST;
    151     const UHashElement *element = uhash_nextElement(fHashtable, &pos);
    152     char buffer[256];
    153     int32_t cnt = 0;
    154     for (; element != NULL; element = uhash_nextElement(fHashtable, &pos)) {
    155         const SharedObject *sharedObject =
    156                 (const SharedObject *) element->value.pointer;
    157         const CacheKeyBase *key =
    158                 (const CacheKeyBase *) element->key.pointer;
    159         if (!sharedObject->allSoftReferences()) {
    160             ++cnt;
    161             fprintf(
    162                     stderr,
    163                     "Unified Cache: Key '%s', error %d, value %p, total refcount %d, soft refcount %d\n",
    164                     key->writeDescription(buffer, 256),
    165                     key->creationStatus,
    166                     sharedObject == gNoValue ? NULL :sharedObject,
    167                     sharedObject->getRefCount(),
    168                     sharedObject->getSoftRefCount());
    169         }
    170     }
    171     fprintf(stderr, "Unified Cache: %d out of a total of %d still have hard references\n", cnt, uhash_count(fHashtable));
    172 }
    173 #endif
    174 
    175 UnifiedCache::~UnifiedCache() {
    176     // Try our best to clean up first.
    177     flush();
    178     {
    179         // Now all that should be left in the cache are entries that refer to
    180         // each other and entries with hard references from outside the cache.
    181         // Nothing we can do about these so proceed to wipe out the cache.
    182         Mutex lock(&gCacheMutex);
    183         _flush(TRUE);
    184     }
    185     uhash_close(fHashtable);
    186 }
    187 
    188 // Flushes the contents of the cache. If cache values hold references to other
    189 // cache values then _flush should be called in a loop until it returns FALSE.
    190 // On entry, gCacheMutex must be held.
    191 // On exit, those values with only soft references are flushed. If all is true
    192 // then every value is flushed even if hard references are held.
    193 // Returns TRUE if any value in cache was flushed or FALSE otherwise.
    194 UBool UnifiedCache::_flush(UBool all) const {
    195     UBool result = FALSE;
    196     int32_t pos = UHASH_FIRST;
    197     const UHashElement *element = uhash_nextElement(fHashtable, &pos);
    198     for (; element != NULL; element = uhash_nextElement(fHashtable, &pos)) {
    199         const SharedObject *sharedObject =
    200                 (const SharedObject *) element->value.pointer;
    201         if (all || sharedObject->allSoftReferences()) {
    202             uhash_removeElement(fHashtable, element);
    203             sharedObject->removeSoftRef();
    204             result = TRUE;
    205         }
    206     }
    207     return result;
    208 }
    209 
    210 // Places a new value and creationStatus in the cache for the given key.
    211 // On entry, gCacheMutex must be held. key must not exist in the cache.
    212 // On exit, value and creation status placed under key. Soft reference added
    213 // to value on successful add. On error sets status.
    214 void UnifiedCache::_putNew(
    215         const CacheKeyBase &key,
    216         const SharedObject *value,
    217         const UErrorCode creationStatus,
    218         UErrorCode &status) const {
    219     if (U_FAILURE(status)) {
    220         return;
    221     }
    222     CacheKeyBase *keyToAdopt = key.clone();
    223     if (keyToAdopt == NULL) {
    224         status = U_MEMORY_ALLOCATION_ERROR;
    225         return;
    226     }
    227     keyToAdopt->creationStatus = creationStatus;
    228     uhash_put(fHashtable, keyToAdopt, (void *) value, &status);
    229     if (U_SUCCESS(status)) {
    230         value->addSoftRef();
    231     }
    232 }
    233 
    234 // Places value and status at key if there is no value at key or if cache
    235 // entry for key is in progress. Otherwise, it leaves the current value and
    236 // status there.
    237 // On entry. gCacheMutex must not be held. value must be
    238 // included in the reference count of the object to which it points.
    239 // On exit, value and status are changed to what was already in the cache if
    240 // something was there and not in progress. Otherwise, value and status are left
    241 // unchanged in which case they are placed in the cache on a best-effort basis.
    242 // Caller must call removeRef() on value.
    243 void UnifiedCache::_putIfAbsentAndGet(
    244         const CacheKeyBase &key,
    245         const SharedObject *&value,
    246         UErrorCode &status) const {
    247     Mutex lock(&gCacheMutex);
    248     const UHashElement *element = uhash_find(fHashtable, &key);
    249     if (element != NULL && !_inProgress(element)) {
    250         _fetch(element, value, status);
    251         return;
    252     }
    253     if (element == NULL) {
    254         UErrorCode putError = U_ZERO_ERROR;
    255         // best-effort basis only.
    256         _putNew(key, value, status, putError);
    257         return;
    258     }
    259     _put(element, value, status);
    260 }
    261 
    262 // Attempts to fetch value and status for key from cache.
    263 // On entry, gCacheMutex must not be held value must be NULL and status must
    264 // be U_ZERO_ERROR.
    265 // On exit, either returns FALSE (In this
    266 // case caller should try to create the object) or returns TRUE with value
    267 // pointing to the fetched value and status set to fetched status. When
    268 // FALSE is returned status may be set to failure if an in progress hash
    269 // entry could not be made but value will remain unchanged. When TRUE is
    270 // returned, caler must call removeRef() on value.
    271 UBool UnifiedCache::_poll(
    272         const CacheKeyBase &key,
    273         const SharedObject *&value,
    274         UErrorCode &status) const {
    275     U_ASSERT(value == NULL);
    276     U_ASSERT(status == U_ZERO_ERROR);
    277     Mutex lock(&gCacheMutex);
    278     const UHashElement *element = uhash_find(fHashtable, &key);
    279     while (element != NULL && _inProgress(element)) {
    280         umtx_condWait(&gInProgressValueAddedCond, &gCacheMutex);
    281         element = uhash_find(fHashtable, &key);
    282     }
    283     if (element != NULL) {
    284         _fetch(element, value, status);
    285         return TRUE;
    286     }
    287     _putNew(key, gNoValue, U_ZERO_ERROR, status);
    288     return FALSE;
    289 }
    290 
    291 // Gets value out of cache.
    292 // On entry. gCacheMutex must not be held. value must be NULL. status
    293 // must be U_ZERO_ERROR.
    294 // On exit. value and status set to what is in cache at key or on cache
    295 // miss the key's createObject() is called and value and status are set to
    296 // the result of that. In this latter case, best effort is made to add the
    297 // value and status to the cache. value will be set to NULL instead of
    298 // gNoValue. Caller must call removeRef on value if non NULL.
    299 void UnifiedCache::_get(
    300         const CacheKeyBase &key,
    301         const SharedObject *&value,
    302         const void *creationContext,
    303         UErrorCode &status) const {
    304     U_ASSERT(value == NULL);
    305     U_ASSERT(status == U_ZERO_ERROR);
    306     if (_poll(key, value, status)) {
    307         if (value == gNoValue) {
    308             SharedObject::clearPtr(value);
    309         }
    310         return;
    311     }
    312     if (U_FAILURE(status)) {
    313         return;
    314     }
    315     value = key.createObject(creationContext, status);
    316     U_ASSERT(value == NULL || !value->allSoftReferences());
    317     U_ASSERT(value != NULL || status != U_ZERO_ERROR);
    318     if (value == NULL) {
    319         SharedObject::copyPtr(gNoValue, value);
    320     }
    321     _putIfAbsentAndGet(key, value, status);
    322     if (value == gNoValue) {
    323         SharedObject::clearPtr(value);
    324     }
    325 }
    326 
    327 // Store a value and error in given hash entry.
    328 // On entry, gCacheMutex must be held. Hash entry element must be in progress.
    329 // value must be non NULL.
    330 // On Exit, soft reference added to value. value and status stored in hash
    331 // entry. Soft reference removed from previous stored value. Waiting
    332 // threads notified.
    333 void UnifiedCache::_put(
    334         const UHashElement *element,
    335         const SharedObject *value,
    336         const UErrorCode status) {
    337     U_ASSERT(_inProgress(element));
    338     const CacheKeyBase *theKey = (const CacheKeyBase *) element->key.pointer;
    339     const SharedObject *oldValue = (const SharedObject *) element->value.pointer;
    340     theKey->creationStatus = status;
    341     value->addSoftRef();
    342     UHashElement *ptr = const_cast<UHashElement *>(element);
    343     ptr->value.pointer = (void *) value;
    344     oldValue->removeSoftRef();
    345 
    346     // Tell waiting threads that we replace in-progress status with
    347     // an error.
    348     umtx_condBroadcast(&gInProgressValueAddedCond);
    349 }
    350 
    351 // Fetch value and error code from a particular hash entry.
    352 // On entry, gCacheMutex must be held. value must be either NULL or must be
    353 // included in the ref count of the object to which it points.
    354 // On exit, value and status set to what is in the hash entry. Caller must
    355 // eventually call removeRef on value.
    356 // If hash entry is in progress, value will be set to gNoValue and status will
    357 // be set to U_ZERO_ERROR.
    358 void UnifiedCache::_fetch(
    359         const UHashElement *element,
    360         const SharedObject *&value,
    361         UErrorCode &status) {
    362     const CacheKeyBase *theKey = (const CacheKeyBase *) element->key.pointer;
    363     status = theKey->creationStatus;
    364     SharedObject::copyPtr(
    365             (const SharedObject *) element->value.pointer, value);
    366 }
    367 
    368 // Determine if given hash entry is in progress.
    369 // On entry, gCacheMutex must be held.
    370 UBool UnifiedCache::_inProgress(const UHashElement *element) {
    371     const SharedObject *value = NULL;
    372     UErrorCode status = U_ZERO_ERROR;
    373     _fetch(element, value, status);
    374     UBool result = (value == gNoValue && status == U_ZERO_ERROR);
    375     SharedObject::clearPtr(value);
    376     return result;
    377 }
    378 
    379 U_NAMESPACE_END
    380