123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- // © 2016 and later: Unicode, Inc. and others.
- // License & terms of use: http://www.unicode.org/copyright.html
- /*
- ******************************************************************************
- * Copyright (C) 2015, International Business Machines Corporation and
- * others. All Rights Reserved.
- ******************************************************************************
- *
- * File unifiedcache.cpp
- ******************************************************************************
- */
- #include "unifiedcache.h"
- #include <algorithm> // For std::max()
- #include <mutex>
- #include "uassert.h"
- #include "uhash.h"
- #include "ucln_cmn.h"
- static icu::UnifiedCache *gCache = nullptr;
- static std::mutex *gCacheMutex = nullptr;
- static std::condition_variable *gInProgressValueAddedCond;
- static icu::UInitOnce gCacheInitOnce {};
- static const int32_t MAX_EVICT_ITERATIONS = 10;
- static const int32_t DEFAULT_MAX_UNUSED = 1000;
- static const int32_t DEFAULT_PERCENTAGE_OF_IN_USE = 100;
- U_CDECL_BEGIN
- static UBool U_CALLCONV unifiedcache_cleanup() {
- gCacheInitOnce.reset();
- delete gCache;
- gCache = nullptr;
- gCacheMutex->~mutex();
- gCacheMutex = nullptr;
- gInProgressValueAddedCond->~condition_variable();
- gInProgressValueAddedCond = nullptr;
- return true;
- }
- U_CDECL_END
- U_NAMESPACE_BEGIN
- int32_t U_EXPORT2
- ucache_hashKeys(const UHashTok key) {
- const CacheKeyBase* ckey = static_cast<const CacheKeyBase*>(key.pointer);
- return ckey->hashCode();
- }
- UBool U_EXPORT2
- ucache_compareKeys(const UHashTok key1, const UHashTok key2) {
- const CacheKeyBase* p1 = static_cast<const CacheKeyBase*>(key1.pointer);
- const CacheKeyBase* p2 = static_cast<const CacheKeyBase*>(key2.pointer);
- return *p1 == *p2;
- }
- void U_EXPORT2
- ucache_deleteKey(void *obj) {
- CacheKeyBase* p = static_cast<CacheKeyBase*>(obj);
- delete p;
- }
- CacheKeyBase::~CacheKeyBase() {
- }
- static void U_CALLCONV cacheInit(UErrorCode &status) {
- U_ASSERT(gCache == nullptr);
- ucln_common_registerCleanup(
- UCLN_COMMON_UNIFIED_CACHE, unifiedcache_cleanup);
- gCacheMutex = STATIC_NEW(std::mutex);
- gInProgressValueAddedCond = STATIC_NEW(std::condition_variable);
- gCache = new UnifiedCache(status);
- if (gCache == nullptr) {
- status = U_MEMORY_ALLOCATION_ERROR;
- }
- if (U_FAILURE(status)) {
- delete gCache;
- gCache = nullptr;
- return;
- }
- }
- UnifiedCache *UnifiedCache::getInstance(UErrorCode &status) {
- umtx_initOnce(gCacheInitOnce, &cacheInit, status);
- if (U_FAILURE(status)) {
- return nullptr;
- }
- U_ASSERT(gCache != nullptr);
- return gCache;
- }
- UnifiedCache::UnifiedCache(UErrorCode &status) :
- fHashtable(nullptr),
- fEvictPos(UHASH_FIRST),
- fNumValuesTotal(0),
- fNumValuesInUse(0),
- fMaxUnused(DEFAULT_MAX_UNUSED),
- fMaxPercentageOfInUse(DEFAULT_PERCENTAGE_OF_IN_USE),
- fAutoEvictedCount(0),
- fNoValue(nullptr) {
- if (U_FAILURE(status)) {
- return;
- }
- fNoValue = new SharedObject();
- if (fNoValue == nullptr) {
- status = U_MEMORY_ALLOCATION_ERROR;
- return;
- }
- fNoValue->softRefCount = 1; // Add fake references to prevent fNoValue from being deleted
- fNoValue->hardRefCount = 1; // when other references to it are removed.
- fNoValue->cachePtr = this;
- fHashtable = uhash_open(
- &ucache_hashKeys,
- &ucache_compareKeys,
- nullptr,
- &status);
- if (U_FAILURE(status)) {
- return;
- }
- uhash_setKeyDeleter(fHashtable, &ucache_deleteKey);
- }
- void UnifiedCache::setEvictionPolicy(
- int32_t count, int32_t percentageOfInUseItems, UErrorCode &status) {
- if (U_FAILURE(status)) {
- return;
- }
- if (count < 0 || percentageOfInUseItems < 0) {
- status = U_ILLEGAL_ARGUMENT_ERROR;
- return;
- }
- std::lock_guard<std::mutex> lock(*gCacheMutex);
- fMaxUnused = count;
- fMaxPercentageOfInUse = percentageOfInUseItems;
- }
- int32_t UnifiedCache::unusedCount() const {
- std::lock_guard<std::mutex> lock(*gCacheMutex);
- return uhash_count(fHashtable) - fNumValuesInUse;
- }
- int64_t UnifiedCache::autoEvictedCount() const {
- std::lock_guard<std::mutex> lock(*gCacheMutex);
- return fAutoEvictedCount;
- }
- int32_t UnifiedCache::keyCount() const {
- std::lock_guard<std::mutex> lock(*gCacheMutex);
- return uhash_count(fHashtable);
- }
- void UnifiedCache::flush() const {
- std::lock_guard<std::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));
- }
- void UnifiedCache::handleUnreferencedObject() const {
- std::lock_guard<std::mutex> lock(*gCacheMutex);
- --fNumValuesInUse;
- _runEvictionSlice();
- }
- #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 {
- std::lock_guard<std::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 != nullptr; element = uhash_nextElement(fHashtable, &pos)) {
- const SharedObject *sharedObject =
- (const SharedObject *) element->value.pointer;
- const CacheKeyBase *key =
- (const CacheKeyBase *) element->key.pointer;
- if (sharedObject->hasHardReferences()) {
- ++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 == fNoValue ? nullptr :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.
- std::lock_guard<std::mutex> lock(*gCacheMutex);
- _flush(true);
- }
- uhash_close(fHashtable);
- fHashtable = nullptr;
- delete fNoValue;
- fNoValue = nullptr;
- }
- const UHashElement *
- UnifiedCache::_nextElement() const {
- const UHashElement *element = uhash_nextElement(fHashtable, &fEvictPos);
- if (element == nullptr) {
- fEvictPos = UHASH_FIRST;
- return uhash_nextElement(fHashtable, &fEvictPos);
- }
- return element;
- }
- UBool UnifiedCache::_flush(UBool all) const {
- UBool result = false;
- int32_t origSize = uhash_count(fHashtable);
- for (int32_t i = 0; i < origSize; ++i) {
- const UHashElement *element = _nextElement();
- if (element == nullptr) {
- break;
- }
- if (all || _isEvictable(element)) {
- const SharedObject *sharedObject =
- static_cast<const SharedObject*>(element->value.pointer);
- U_ASSERT(sharedObject->cachePtr == this);
- uhash_removeElement(fHashtable, element);
- removeSoftRef(sharedObject); // Deletes the sharedObject when softRefCount goes to zero.
- result = true;
- }
- }
- return result;
- }
- int32_t UnifiedCache::_computeCountOfItemsToEvict() const {
- int32_t totalItems = uhash_count(fHashtable);
- int32_t evictableItems = totalItems - fNumValuesInUse;
- int32_t unusedLimitByPercentage = fNumValuesInUse * fMaxPercentageOfInUse / 100;
- int32_t unusedLimit = std::max(unusedLimitByPercentage, fMaxUnused);
- int32_t countOfItemsToEvict = std::max<int32_t>(0, evictableItems - unusedLimit);
- return countOfItemsToEvict;
- }
- void UnifiedCache::_runEvictionSlice() const {
- int32_t maxItemsToEvict = _computeCountOfItemsToEvict();
- if (maxItemsToEvict <= 0) {
- return;
- }
- for (int32_t i = 0; i < MAX_EVICT_ITERATIONS; ++i) {
- const UHashElement *element = _nextElement();
- if (element == nullptr) {
- break;
- }
- if (_isEvictable(element)) {
- const SharedObject *sharedObject =
- static_cast<const SharedObject*>(element->value.pointer);
- uhash_removeElement(fHashtable, element);
- removeSoftRef(sharedObject); // Deletes sharedObject when SoftRefCount goes to zero.
- ++fAutoEvictedCount;
- if (--maxItemsToEvict == 0) {
- break;
- }
- }
- }
- }
- 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 == nullptr) {
- status = U_MEMORY_ALLOCATION_ERROR;
- return;
- }
- keyToAdopt->fCreationStatus = creationStatus;
- if (value->softRefCount == 0) {
- _registerPrimary(keyToAdopt, value);
- }
- void *oldValue = uhash_put(fHashtable, keyToAdopt, (void *) value, &status);
- U_ASSERT(oldValue == nullptr);
- (void)oldValue;
- if (U_SUCCESS(status)) {
- value->softRefCount++;
- }
- }
- void UnifiedCache::_putIfAbsentAndGet(
- const CacheKeyBase &key,
- const SharedObject *&value,
- UErrorCode &status) const {
- std::lock_guard<std::mutex> lock(*gCacheMutex);
- const UHashElement *element = uhash_find(fHashtable, &key);
- if (element != nullptr && !_inProgress(element)) {
- _fetch(element, value, status);
- return;
- }
- if (element == nullptr) {
- UErrorCode putError = U_ZERO_ERROR;
- // best-effort basis only.
- _putNew(key, value, status, putError);
- } else {
- _put(element, value, status);
- }
- // Run an eviction slice. This will run even if we added a primary entry
- // which doesn't increase the unused count, but that is still o.k
- _runEvictionSlice();
- }
- UBool UnifiedCache::_poll(
- const CacheKeyBase &key,
- const SharedObject *&value,
- UErrorCode &status) const {
- U_ASSERT(value == nullptr);
- U_ASSERT(status == U_ZERO_ERROR);
- std::unique_lock<std::mutex> lock(*gCacheMutex);
- const UHashElement *element = uhash_find(fHashtable, &key);
- // If the hash table contains an inProgress placeholder entry for this key,
- // this means that another thread is currently constructing the value object.
- // Loop, waiting for that construction to complete.
- while (element != nullptr && _inProgress(element)) {
- gInProgressValueAddedCond->wait(lock);
- element = uhash_find(fHashtable, &key);
- }
- // If the hash table contains an entry for the key,
- // fetch out the contents and return them.
- if (element != nullptr) {
- _fetch(element, value, status);
- return true;
- }
- // The hash table contained nothing for this key.
- // Insert an inProgress place holder value.
- // Our caller will create the final value and update the hash table.
- _putNew(key, fNoValue, U_ZERO_ERROR, status);
- return false;
- }
- void UnifiedCache::_get(
- const CacheKeyBase &key,
- const SharedObject *&value,
- const void *creationContext,
- UErrorCode &status) const {
- U_ASSERT(value == nullptr);
- U_ASSERT(status == U_ZERO_ERROR);
- if (_poll(key, value, status)) {
- if (value == fNoValue) {
- SharedObject::clearPtr(value);
- }
- return;
- }
- if (U_FAILURE(status)) {
- return;
- }
- value = key.createObject(creationContext, status);
- U_ASSERT(value == nullptr || value->hasHardReferences());
- U_ASSERT(value != nullptr || status != U_ZERO_ERROR);
- if (value == nullptr) {
- SharedObject::copyPtr(fNoValue, value);
- }
- _putIfAbsentAndGet(key, value, status);
- if (value == fNoValue) {
- SharedObject::clearPtr(value);
- }
- }
- void UnifiedCache::_registerPrimary(
- const CacheKeyBase *theKey, const SharedObject *value) const {
- theKey->fIsPrimary = true;
- value->cachePtr = this;
- ++fNumValuesTotal;
- ++fNumValuesInUse;
- }
- void UnifiedCache::_put(
- const UHashElement *element,
- const SharedObject *value,
- const UErrorCode status) const {
- U_ASSERT(_inProgress(element));
- const CacheKeyBase* theKey = static_cast<const CacheKeyBase*>(element->key.pointer);
- const SharedObject* oldValue = static_cast<const SharedObject*>(element->value.pointer);
- theKey->fCreationStatus = status;
- if (value->softRefCount == 0) {
- _registerPrimary(theKey, value);
- }
- value->softRefCount++;
- UHashElement *ptr = const_cast<UHashElement *>(element);
- ptr->value.pointer = (void *) value;
- U_ASSERT(oldValue == fNoValue);
- removeSoftRef(oldValue);
- // Tell waiting threads that we replace in-progress status with
- // an error.
- gInProgressValueAddedCond->notify_all();
- }
- void UnifiedCache::_fetch(
- const UHashElement *element,
- const SharedObject *&value,
- UErrorCode &status) const {
- const CacheKeyBase* theKey = static_cast<const CacheKeyBase*>(element->key.pointer);
- status = theKey->fCreationStatus;
- // Since we have the cache lock, calling regular SharedObject add/removeRef
- // could cause us to deadlock on ourselves since they may need to lock
- // the cache mutex.
- removeHardRef(value);
- value = static_cast<const SharedObject *>(element->value.pointer);
- addHardRef(value);
- }
- UBool UnifiedCache::_inProgress(const UHashElement* element) const {
- UErrorCode status = U_ZERO_ERROR;
- const SharedObject * value = nullptr;
- _fetch(element, value, status);
- UBool result = _inProgress(value, status);
- removeHardRef(value);
- return result;
- }
- UBool UnifiedCache::_inProgress(
- const SharedObject* theValue, UErrorCode creationStatus) const {
- return (theValue == fNoValue && creationStatus == U_ZERO_ERROR);
- }
- UBool UnifiedCache::_isEvictable(const UHashElement *element) const
- {
- const CacheKeyBase* theKey = static_cast<const CacheKeyBase*>(element->key.pointer);
- const SharedObject* theValue = static_cast<const SharedObject*>(element->value.pointer);
- // Entries that are under construction are never evictable
- if (_inProgress(theValue, theKey->fCreationStatus)) {
- return false;
- }
- // We can evict entries that are either not a primary or have just
- // one reference (The one reference being from the cache itself).
- return (!theKey->fIsPrimary || (theValue->softRefCount == 1 && theValue->noHardReferences()));
- }
- void UnifiedCache::removeSoftRef(const SharedObject *value) const {
- U_ASSERT(value->cachePtr == this);
- U_ASSERT(value->softRefCount > 0);
- if (--value->softRefCount == 0) {
- --fNumValuesTotal;
- if (value->noHardReferences()) {
- delete value;
- } else {
- // This path only happens from flush(all). Which only happens from the
- // UnifiedCache destructor. Nulling out value.cacheptr changes the behavior
- // of value.removeRef(), causing the deletion to be done there.
- value->cachePtr = nullptr;
- }
- }
- }
- int32_t UnifiedCache::removeHardRef(const SharedObject *value) const {
- int refCount = 0;
- if (value) {
- refCount = umtx_atomic_dec(&value->hardRefCount);
- U_ASSERT(refCount >= 0);
- if (refCount == 0) {
- --fNumValuesInUse;
- }
- }
- return refCount;
- }
- int32_t UnifiedCache::addHardRef(const SharedObject *value) const {
- int refCount = 0;
- if (value) {
- refCount = umtx_atomic_inc(&value->hardRefCount);
- U_ASSERT(refCount >= 1);
- if (refCount == 1) {
- fNumValuesInUse++;
- }
- }
- return refCount;
- }
- U_NAMESPACE_END
|