]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/common/unifiedcache.cpp
ICU-551.24.tar.gz
[apple/icu.git] / icuSources / common / unifiedcache.cpp
diff --git a/icuSources/common/unifiedcache.cpp b/icuSources/common/unifiedcache.cpp
new file mode 100644 (file)
index 0000000..32899af
--- /dev/null
@@ -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 = UHASH_FIRST;
+    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 = UHASH_FIRST;
+    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