Index: source/common/unifiedcache.cpp |
diff --git a/source/common/unifiedcache.cpp b/source/common/unifiedcache.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ab9a3bbebb06ecfe7df99079cc54559f8d718c93 |
--- /dev/null |
+++ b/source/common/unifiedcache.cpp |
@@ -0,0 +1,379 @@ |
+/* |
+****************************************************************************** |
+* 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 |