| /* |
| ****************************************************************************** |
| * Copyright (C) 2014, International Business Machines Corporation and |
| * others. All Rights Reserved. |
| ****************************************************************************** |
| * |
| * File UNIFIEDCACHE.CPP |
| ****************************************************************************** |
| */ |
| |
| #include "uhash.h" |
| #include "unifiedcache.h" |
| #include "umutex.h" |
| #include "mutex.h" |
| #include "uassert.h" |
| #include "ucln_cmn.h" |
| |
| static icu::UnifiedCache *gCache = NULL; |
| static icu::SharedObject *gNoValue = NULL; |
| static UMutex gCacheMutex = U_MUTEX_INITIALIZER; |
| static UConditionVar gInProgressValueAddedCond = U_CONDITION_INITIALIZER; |
| static icu::UInitOnce gCacheInitOnce = U_INITONCE_INITIALIZER; |
| |
| U_CDECL_BEGIN |
| static UBool U_CALLCONV unifiedcache_cleanup() { |
| gCacheInitOnce.reset(); |
| if (gCache) { |
| delete gCache; |
| gCache = NULL; |
| } |
| if (gNoValue) { |
| delete gNoValue; |
| gNoValue = NULL; |
| } |
| return TRUE; |
| } |
| U_CDECL_END |
| |
| |
| U_NAMESPACE_BEGIN |
| |
| U_CAPI int32_t U_EXPORT2 |
| ucache_hashKeys(const UHashTok key) { |
| const CacheKeyBase *ckey = (const CacheKeyBase *) key.pointer; |
| return ckey->hashCode(); |
| } |
| |
| U_CAPI UBool U_EXPORT2 |
| ucache_compareKeys(const UHashTok key1, const UHashTok key2) { |
| const CacheKeyBase *p1 = (const CacheKeyBase *) key1.pointer; |
| const CacheKeyBase *p2 = (const CacheKeyBase *) key2.pointer; |
| return *p1 == *p2; |
| } |
| |
| U_CAPI void U_EXPORT2 |
| ucache_deleteKey(void *obj) { |
| CacheKeyBase *p = (CacheKeyBase *) obj; |
| delete p; |
| } |
| |
| CacheKeyBase::~CacheKeyBase() { |
| } |
| |
| static void U_CALLCONV cacheInit(UErrorCode &status) { |
| U_ASSERT(gCache == NULL); |
| ucln_common_registerCleanup( |
| UCLN_COMMON_UNIFIED_CACHE, unifiedcache_cleanup); |
| |
| // gNoValue must be created first to avoid assertion error in |
| // cache constructor. |
| gNoValue = new SharedObject(); |
| gCache = new UnifiedCache(status); |
| if (gCache == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| } |
| if (U_FAILURE(status)) { |
| delete gCache; |
| delete gNoValue; |
| gCache = NULL; |
| gNoValue = NULL; |
| return; |
| } |
| // We add a softref because we want hash elements with gNoValue to be |
| // elligible for purging but we don't ever want gNoValue to be deleted. |
| gNoValue->addSoftRef(); |
| } |
| |
| const UnifiedCache *UnifiedCache::getInstance(UErrorCode &status) { |
| umtx_initOnce(gCacheInitOnce, &cacheInit, status); |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| U_ASSERT(gCache != NULL); |
| return gCache; |
| } |
| |
| UnifiedCache::UnifiedCache(UErrorCode &status) { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| U_ASSERT(gNoValue != NULL); |
| fHashtable = uhash_open( |
| &ucache_hashKeys, |
| &ucache_compareKeys, |
| NULL, |
| &status); |
| if (U_FAILURE(status)) { |
| return; |
| } |
| uhash_setKeyDeleter(fHashtable, &ucache_deleteKey); |
| } |
| |
| int32_t UnifiedCache::keyCount() const { |
| Mutex lock(&gCacheMutex); |
| return uhash_count(fHashtable); |
| } |
| |
| void UnifiedCache::flush() const { |
| Mutex lock(&gCacheMutex); |
| |
| // Use a loop in case cache items that are flushed held hard references to |
| // other cache items making those additional cache items eligible for |
| // flushing. |
| while (_flush(FALSE)); |
| umtx_condBroadcast(&gInProgressValueAddedCond); |
| } |
| |
| #ifdef UNIFIED_CACHE_DEBUG |
| #include <stdio.h> |
| |
| void UnifiedCache::dump() { |
| UErrorCode status = U_ZERO_ERROR; |
| const UnifiedCache *cache = getInstance(status); |
| if (U_FAILURE(status)) { |
| fprintf(stderr, "Unified Cache: Error fetching cache.\n"); |
| return; |
| } |
| cache->dumpContents(); |
| } |
| |
| void UnifiedCache::dumpContents() const { |
| Mutex lock(&gCacheMutex); |
| _dumpContents(); |
| } |
| |
| // Dumps content of cache. |
| // On entry, gCacheMutex must be held. |
| // On exit, cache contents dumped to stderr. |
| void UnifiedCache::_dumpContents() const { |
| int32_t pos = -1; |
| const UHashElement *element = uhash_nextElement(fHashtable, &pos); |
| char buffer[256]; |
| int32_t cnt = 0; |
| for (; element != NULL; element = uhash_nextElement(fHashtable, &pos)) { |
| const SharedObject *sharedObject = |
| (const SharedObject *) element->value.pointer; |
| const CacheKeyBase *key = |
| (const CacheKeyBase *) element->key.pointer; |
| if (!sharedObject->allSoftReferences()) { |
| ++cnt; |
| fprintf( |
| stderr, |
| "Unified Cache: Key '%s', error %d, value %p, total refcount %d, soft refcount %d\n", |
| key->writeDescription(buffer, 256), |
| key->creationStatus, |
| sharedObject == gNoValue ? NULL :sharedObject, |
| sharedObject->getRefCount(), |
| sharedObject->getSoftRefCount()); |
| } |
| } |
| fprintf(stderr, "Unified Cache: %d out of a total of %d still have hard references\n", cnt, uhash_count(fHashtable)); |
| } |
| #endif |
| |
| UnifiedCache::~UnifiedCache() { |
| // Try our best to clean up first. |
| flush(); |
| { |
| // Now all that should be left in the cache are entries that refer to |
| // each other and entries with hard references from outside the cache. |
| // Nothing we can do about these so proceed to wipe out the cache. |
| Mutex lock(&gCacheMutex); |
| _flush(TRUE); |
| } |
| uhash_close(fHashtable); |
| } |
| |
| // Flushes the contents of the cache. If cache values hold references to other |
| // cache values then _flush should be called in a loop until it returns FALSE. |
| // On entry, gCacheMutex must be held. |
| // On exit, those values with only soft references are flushed. If all is true |
| // then every value is flushed even if hard references are held. |
| // Returns TRUE if any value in cache was flushed or FALSE otherwise. |
| UBool UnifiedCache::_flush(UBool all) const { |
| UBool result = FALSE; |
| int32_t pos = -1; |
| const UHashElement *element = uhash_nextElement(fHashtable, &pos); |
| for (; element != NULL; element = uhash_nextElement(fHashtable, &pos)) { |
| const SharedObject *sharedObject = |
| (const SharedObject *) element->value.pointer; |
| if (all || sharedObject->allSoftReferences()) { |
| uhash_removeElement(fHashtable, element); |
| sharedObject->removeSoftRef(); |
| result = TRUE; |
| } |
| } |
| return result; |
| } |
| |
| // Places a new value and creationStatus in the cache for the given key. |
| // On entry, gCacheMutex must be held. key must not exist in the cache. |
| // On exit, value and creation status placed under key. Soft reference added |
| // to value on successful add. On error sets status. |
| void UnifiedCache::_putNew( |
| const CacheKeyBase &key, |
| const SharedObject *value, |
| const UErrorCode creationStatus, |
| UErrorCode &status) const { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| CacheKeyBase *keyToAdopt = key.clone(); |
| if (keyToAdopt == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| keyToAdopt->creationStatus = creationStatus; |
| uhash_put(fHashtable, keyToAdopt, (void *) value, &status); |
| if (U_SUCCESS(status)) { |
| value->addSoftRef(); |
| } |
| } |
| |
| // Places value and status at key if there is no value at key or if cache |
| // entry for key is in progress. Otherwise, it leaves the current value and |
| // status there. |
| // On entry. gCacheMutex must not be held. value must be |
| // included in the reference count of the object to which it points. |
| // On exit, value and status are changed to what was already in the cache if |
| // something was there and not in progress. Otherwise, value and status are left |
| // unchanged in which case they are placed in the cache on a best-effort basis. |
| // Caller must call removeRef() on value. |
| void UnifiedCache::_putIfAbsentAndGet( |
| const CacheKeyBase &key, |
| const SharedObject *&value, |
| UErrorCode &status) const { |
| Mutex lock(&gCacheMutex); |
| const UHashElement *element = uhash_find(fHashtable, &key); |
| if (element != NULL && !_inProgress(element)) { |
| _fetch(element, value, status); |
| return; |
| } |
| if (element == NULL) { |
| UErrorCode putError = U_ZERO_ERROR; |
| // best-effort basis only. |
| _putNew(key, value, status, putError); |
| return; |
| } |
| _put(element, value, status); |
| } |
| |
| // Attempts to fetch value and status for key from cache. |
| // On entry, gCacheMutex must not be held value must be NULL and status must |
| // be U_ZERO_ERROR. |
| // On exit, either returns FALSE (In this |
| // case caller should try to create the object) or returns TRUE with value |
| // pointing to the fetched value and status set to fetched status. When |
| // FALSE is returned status may be set to failure if an in progress hash |
| // entry could not be made but value will remain unchanged. When TRUE is |
| // returned, caler must call removeRef() on value. |
| UBool UnifiedCache::_poll( |
| const CacheKeyBase &key, |
| const SharedObject *&value, |
| UErrorCode &status) const { |
| U_ASSERT(value == NULL); |
| U_ASSERT(status == U_ZERO_ERROR); |
| Mutex lock(&gCacheMutex); |
| const UHashElement *element = uhash_find(fHashtable, &key); |
| while (element != NULL && _inProgress(element)) { |
| umtx_condWait(&gInProgressValueAddedCond, &gCacheMutex); |
| element = uhash_find(fHashtable, &key); |
| } |
| if (element != NULL) { |
| _fetch(element, value, status); |
| return TRUE; |
| } |
| _putNew(key, gNoValue, U_ZERO_ERROR, status); |
| return FALSE; |
| } |
| |
| // Gets value out of cache. |
| // On entry. gCacheMutex must not be held. value must be NULL. status |
| // must be U_ZERO_ERROR. |
| // On exit. value and status set to what is in cache at key or on cache |
| // miss the key's createObject() is called and value and status are set to |
| // the result of that. In this latter case, best effort is made to add the |
| // value and status to the cache. value will be set to NULL instead of |
| // gNoValue. Caller must call removeRef on value if non NULL. |
| void UnifiedCache::_get( |
| const CacheKeyBase &key, |
| const SharedObject *&value, |
| const void *creationContext, |
| UErrorCode &status) const { |
| U_ASSERT(value == NULL); |
| U_ASSERT(status == U_ZERO_ERROR); |
| if (_poll(key, value, status)) { |
| if (value == gNoValue) { |
| SharedObject::clearPtr(value); |
| } |
| return; |
| } |
| if (U_FAILURE(status)) { |
| return; |
| } |
| value = key.createObject(creationContext, status); |
| U_ASSERT(value == NULL || !value->allSoftReferences()); |
| U_ASSERT(value != NULL || status != U_ZERO_ERROR); |
| if (value == NULL) { |
| SharedObject::copyPtr(gNoValue, value); |
| } |
| _putIfAbsentAndGet(key, value, status); |
| if (value == gNoValue) { |
| SharedObject::clearPtr(value); |
| } |
| } |
| |
| // Store a value and error in given hash entry. |
| // On entry, gCacheMutex must be held. Hash entry element must be in progress. |
| // value must be non NULL. |
| // On Exit, soft reference added to value. value and status stored in hash |
| // entry. Soft reference removed from previous stored value. Waiting |
| // threads notified. |
| void UnifiedCache::_put( |
| const UHashElement *element, |
| const SharedObject *value, |
| const UErrorCode status) { |
| U_ASSERT(_inProgress(element)); |
| const CacheKeyBase *theKey = (const CacheKeyBase *) element->key.pointer; |
| const SharedObject *oldValue = (const SharedObject *) element->value.pointer; |
| theKey->creationStatus = status; |
| value->addSoftRef(); |
| UHashElement *ptr = const_cast<UHashElement *>(element); |
| ptr->value.pointer = (void *) value; |
| oldValue->removeSoftRef(); |
| |
| // Tell waiting threads that we replace in-progress status with |
| // an error. |
| umtx_condBroadcast(&gInProgressValueAddedCond); |
| } |
| |
| // Fetch value and error code from a particular hash entry. |
| // On entry, gCacheMutex must be held. value must be either NULL or must be |
| // included in the ref count of the object to which it points. |
| // On exit, value and status set to what is in the hash entry. Caller must |
| // eventually call removeRef on value. |
| // If hash entry is in progress, value will be set to gNoValue and status will |
| // be set to U_ZERO_ERROR. |
| void UnifiedCache::_fetch( |
| const UHashElement *element, |
| const SharedObject *&value, |
| UErrorCode &status) { |
| const CacheKeyBase *theKey = (const CacheKeyBase *) element->key.pointer; |
| status = theKey->creationStatus; |
| SharedObject::copyPtr( |
| (const SharedObject *) element->value.pointer, value); |
| } |
| |
| // Determine if given hash entry is in progress. |
| // On entry, gCacheMutex must be held. |
| UBool UnifiedCache::_inProgress(const UHashElement *element) { |
| const SharedObject *value = NULL; |
| UErrorCode status = U_ZERO_ERROR; |
| _fetch(element, value, status); |
| UBool result = (value == gNoValue && status == U_ZERO_ERROR); |
| SharedObject::clearPtr(value); |
| return result; |
| } |
| |
| U_NAMESPACE_END |