tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

unifiedcache.cpp (17005B)


      1 // © 2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html
      3 /*
      4 ******************************************************************************
      5 * Copyright (C) 2015, International Business Machines Corporation and
      6 * others. All Rights Reserved.
      7 ******************************************************************************
      8 *
      9 * File unifiedcache.cpp
     10 ******************************************************************************
     11 */
     12 
     13 #include "unifiedcache.h"
     14 
     15 #include <algorithm>      // For std::max()
     16 #ifndef __wasi__
     17 #include <mutex>
     18 #endif
     19 
     20 #include "uassert.h"
     21 #include "uhash.h"
     22 #include "ucln_cmn.h"
     23 
     24 static icu::UnifiedCache *gCache = nullptr;
     25 #ifndef __wasi__
     26 static std::mutex *gCacheMutex = nullptr;
     27 static std::condition_variable *gInProgressValueAddedCond;
     28 #endif
     29 static icu::UInitOnce gCacheInitOnce {};
     30 
     31 static const int32_t MAX_EVICT_ITERATIONS = 10;
     32 static const int32_t DEFAULT_MAX_UNUSED = 1000;
     33 static const int32_t DEFAULT_PERCENTAGE_OF_IN_USE = 100;
     34 
     35 
     36 U_CDECL_BEGIN
     37 static UBool U_CALLCONV unifiedcache_cleanup() {
     38    gCacheInitOnce.reset();
     39    delete gCache;
     40    gCache = nullptr;
     41 #ifndef __wasi__
     42    gCacheMutex->~mutex();
     43    gCacheMutex = nullptr;
     44    gInProgressValueAddedCond->~condition_variable();
     45    gInProgressValueAddedCond = nullptr;
     46 #endif
     47    return true;
     48 }
     49 U_CDECL_END
     50 
     51 
     52 U_NAMESPACE_BEGIN
     53 
     54 int32_t U_EXPORT2
     55 ucache_hashKeys(const UHashTok key) {
     56    const CacheKeyBase* ckey = static_cast<const CacheKeyBase*>(key.pointer);
     57    return ckey->hashCode();
     58 }
     59 
     60 UBool U_EXPORT2
     61 ucache_compareKeys(const UHashTok key1, const UHashTok key2) {
     62    const CacheKeyBase* p1 = static_cast<const CacheKeyBase*>(key1.pointer);
     63    const CacheKeyBase* p2 = static_cast<const CacheKeyBase*>(key2.pointer);
     64    return *p1 == *p2;
     65 }
     66 
     67 void U_EXPORT2
     68 ucache_deleteKey(void *obj) {
     69    CacheKeyBase* p = static_cast<CacheKeyBase*>(obj);
     70    delete p;
     71 }
     72 
     73 CacheKeyBase::~CacheKeyBase() {
     74 }
     75 
     76 static void U_CALLCONV cacheInit(UErrorCode &status) {
     77    U_ASSERT(gCache == nullptr);
     78    ucln_common_registerCleanup(
     79            UCLN_COMMON_UNIFIED_CACHE, unifiedcache_cleanup);
     80 
     81 #ifndef __wasi__
     82    gCacheMutex = STATIC_NEW(std::mutex);
     83    gInProgressValueAddedCond = STATIC_NEW(std::condition_variable);
     84 #endif
     85    gCache = new UnifiedCache(status);
     86    if (gCache == nullptr) {
     87        status = U_MEMORY_ALLOCATION_ERROR;
     88    }
     89    if (U_FAILURE(status)) {
     90        delete gCache;
     91        gCache = nullptr;
     92        return;
     93    }
     94 }
     95 
     96 UnifiedCache *UnifiedCache::getInstance(UErrorCode &status) {
     97    umtx_initOnce(gCacheInitOnce, &cacheInit, status);
     98    if (U_FAILURE(status)) {
     99        return nullptr;
    100    }
    101    U_ASSERT(gCache != nullptr);
    102    return gCache;
    103 }
    104 
    105 UnifiedCache::UnifiedCache(UErrorCode &status) :
    106        fHashtable(nullptr),
    107        fEvictPos(UHASH_FIRST),
    108        fNumValuesTotal(0),
    109        fNumValuesInUse(0),
    110        fMaxUnused(DEFAULT_MAX_UNUSED),
    111        fMaxPercentageOfInUse(DEFAULT_PERCENTAGE_OF_IN_USE),
    112        fAutoEvictedCount(0),
    113        fNoValue(nullptr) {
    114    if (U_FAILURE(status)) {
    115        return;
    116    }
    117    fNoValue = new SharedObject();
    118    if (fNoValue == nullptr) {
    119        status = U_MEMORY_ALLOCATION_ERROR;
    120        return;
    121    }
    122    fNoValue->softRefCount = 1;  // Add fake references to prevent fNoValue from being deleted
    123    fNoValue->hardRefCount = 1;  // when other references to it are removed.
    124    fNoValue->cachePtr = this;
    125 
    126    fHashtable = uhash_open(
    127            &ucache_hashKeys,
    128            &ucache_compareKeys,
    129            nullptr,
    130            &status);
    131    if (U_FAILURE(status)) {
    132        return;
    133    }
    134    uhash_setKeyDeleter(fHashtable, &ucache_deleteKey);
    135 }
    136 
    137 void UnifiedCache::setEvictionPolicy(
    138        int32_t count, int32_t percentageOfInUseItems, UErrorCode &status) {
    139    if (U_FAILURE(status)) {
    140        return;
    141    }
    142    if (count < 0 || percentageOfInUseItems < 0) {
    143        status = U_ILLEGAL_ARGUMENT_ERROR;
    144        return;
    145    }
    146 #ifndef __wasi__
    147    std::lock_guard<std::mutex> lock(*gCacheMutex);
    148 #endif
    149    fMaxUnused = count;
    150    fMaxPercentageOfInUse = percentageOfInUseItems;
    151 }
    152 
    153 int32_t UnifiedCache::unusedCount() const {
    154 #ifndef __wasi__
    155    std::lock_guard<std::mutex> lock(*gCacheMutex);
    156 #endif
    157    return uhash_count(fHashtable) - fNumValuesInUse;
    158 }
    159 
    160 int64_t UnifiedCache::autoEvictedCount() const {
    161 #ifndef __wasi__
    162    std::lock_guard<std::mutex> lock(*gCacheMutex);
    163 #endif
    164    return fAutoEvictedCount;
    165 }
    166 
    167 int32_t UnifiedCache::keyCount() const {
    168 #ifndef __wasi__
    169    std::lock_guard<std::mutex> lock(*gCacheMutex);
    170 #endif
    171    return uhash_count(fHashtable);
    172 }
    173 
    174 void UnifiedCache::flush() const {
    175 #ifndef __wasi__
    176    std::lock_guard<std::mutex> lock(*gCacheMutex);
    177 #endif
    178 
    179    // Use a loop in case cache items that are flushed held hard references to
    180    // other cache items making those additional cache items eligible for
    181    // flushing.
    182    while (_flush(false));
    183 }
    184 
    185 void UnifiedCache::handleUnreferencedObject() const {
    186 #ifndef __wasi__
    187    std::lock_guard<std::mutex> lock(*gCacheMutex);
    188 #endif
    189    --fNumValuesInUse;
    190    _runEvictionSlice();
    191 }
    192 
    193 #ifdef UNIFIED_CACHE_DEBUG
    194 #include <stdio.h>
    195 
    196 void UnifiedCache::dump() {
    197    UErrorCode status = U_ZERO_ERROR;
    198    const UnifiedCache *cache = getInstance(status);
    199    if (U_FAILURE(status)) {
    200        fprintf(stderr, "Unified Cache: Error fetching cache.\n");
    201        return;
    202    }
    203    cache->dumpContents();
    204 }
    205 
    206 void UnifiedCache::dumpContents() const {
    207 #ifndef __wasi__
    208    std::lock_guard<std::mutex> lock(*gCacheMutex);
    209 #endif
    210    _dumpContents();
    211 }
    212 
    213 // Dumps content of cache.
    214 // On entry, gCacheMutex must be held.
    215 // On exit, cache contents dumped to stderr.
    216 void UnifiedCache::_dumpContents() const {
    217    int32_t pos = UHASH_FIRST;
    218    const UHashElement *element = uhash_nextElement(fHashtable, &pos);
    219    char buffer[256];
    220    int32_t cnt = 0;
    221    for (; element != nullptr; element = uhash_nextElement(fHashtable, &pos)) {
    222        const SharedObject *sharedObject =
    223                (const SharedObject *) element->value.pointer;
    224        const CacheKeyBase *key =
    225                (const CacheKeyBase *) element->key.pointer;
    226        if (sharedObject->hasHardReferences()) {
    227            ++cnt;
    228            fprintf(
    229                    stderr,
    230                    "Unified Cache: Key '%s', error %d, value %p, total refcount %d, soft refcount %d\n",
    231                    key->writeDescription(buffer, 256),
    232                    key->creationStatus,
    233                    sharedObject == fNoValue ? nullptr :sharedObject,
    234                    sharedObject->getRefCount(),
    235                    sharedObject->getSoftRefCount());
    236        }
    237    }
    238    fprintf(stderr, "Unified Cache: %d out of a total of %d still have hard references\n", cnt, uhash_count(fHashtable));
    239 }
    240 #endif
    241 
    242 UnifiedCache::~UnifiedCache() {
    243    // Try our best to clean up first.
    244    flush();
    245    {
    246        // Now all that should be left in the cache are entries that refer to
    247        // each other and entries with hard references from outside the cache.
    248        // Nothing we can do about these so proceed to wipe out the cache.
    249 #ifndef __wasi__
    250        std::lock_guard<std::mutex> lock(*gCacheMutex);
    251 #endif
    252        _flush(true);
    253    }
    254    uhash_close(fHashtable);
    255    fHashtable = nullptr;
    256    delete fNoValue;
    257    fNoValue = nullptr;
    258 }
    259 
    260 const UHashElement *
    261 UnifiedCache::_nextElement() const {
    262    const UHashElement *element = uhash_nextElement(fHashtable, &fEvictPos);
    263    if (element == nullptr) {
    264        fEvictPos = UHASH_FIRST;
    265        return uhash_nextElement(fHashtable, &fEvictPos);
    266    }
    267    return element;
    268 }
    269 
    270 UBool UnifiedCache::_flush(UBool all) const {
    271    UBool result = false;
    272    int32_t origSize = uhash_count(fHashtable);
    273    for (int32_t i = 0; i < origSize; ++i) {
    274        const UHashElement *element = _nextElement();
    275        if (element == nullptr) {
    276            break;
    277        }
    278        if (all || _isEvictable(element)) {
    279            const SharedObject *sharedObject =
    280                    static_cast<const SharedObject*>(element->value.pointer);
    281            U_ASSERT(sharedObject->cachePtr == this);
    282            uhash_removeElement(fHashtable, element);
    283            removeSoftRef(sharedObject);    // Deletes the sharedObject when softRefCount goes to zero.
    284            result = true;
    285        }
    286    }
    287    return result;
    288 }
    289 
    290 int32_t UnifiedCache::_computeCountOfItemsToEvict() const {
    291    int32_t totalItems = uhash_count(fHashtable);
    292    int32_t evictableItems = totalItems - fNumValuesInUse;
    293 
    294    int32_t unusedLimitByPercentage = fNumValuesInUse * fMaxPercentageOfInUse / 100;
    295    int32_t unusedLimit = std::max(unusedLimitByPercentage, fMaxUnused);
    296    int32_t countOfItemsToEvict = std::max<int32_t>(0, evictableItems - unusedLimit);
    297    return countOfItemsToEvict;
    298 }
    299 
    300 void UnifiedCache::_runEvictionSlice() const {
    301    int32_t maxItemsToEvict = _computeCountOfItemsToEvict();
    302    if (maxItemsToEvict <= 0) {
    303        return;
    304    }
    305    for (int32_t i = 0; i < MAX_EVICT_ITERATIONS; ++i) {
    306        const UHashElement *element = _nextElement();
    307        if (element == nullptr) {
    308            break;
    309        }
    310        if (_isEvictable(element)) {
    311            const SharedObject *sharedObject =
    312                    static_cast<const SharedObject*>(element->value.pointer);
    313            uhash_removeElement(fHashtable, element);
    314            removeSoftRef(sharedObject);   // Deletes sharedObject when SoftRefCount goes to zero.
    315            ++fAutoEvictedCount;
    316            if (--maxItemsToEvict == 0) {
    317                break;
    318            }
    319        }
    320    }
    321 }
    322 
    323 void UnifiedCache::_putNew(
    324        const CacheKeyBase &key,
    325        const SharedObject *value,
    326        const UErrorCode creationStatus,
    327        UErrorCode &status) const {
    328    if (U_FAILURE(status)) {
    329        return;
    330    }
    331    CacheKeyBase *keyToAdopt = key.clone();
    332    if (keyToAdopt == nullptr) {
    333        status = U_MEMORY_ALLOCATION_ERROR;
    334        return;
    335    }
    336    keyToAdopt->fCreationStatus = creationStatus;
    337    if (value->softRefCount == 0) {
    338        _registerPrimary(keyToAdopt, value);
    339    }
    340    void *oldValue = uhash_put(fHashtable, keyToAdopt, (void *) value, &status);
    341    U_ASSERT(oldValue == nullptr);
    342    (void)oldValue;
    343    if (U_SUCCESS(status)) {
    344        value->softRefCount++;
    345    }
    346 }
    347 
    348 void UnifiedCache::_putIfAbsentAndGet(
    349        const CacheKeyBase &key,
    350        const SharedObject *&value,
    351        UErrorCode &status) const {
    352 #ifndef __wasi__
    353    std::lock_guard<std::mutex> lock(*gCacheMutex);
    354 #endif
    355    const UHashElement *element = uhash_find(fHashtable, &key);
    356    if (element != nullptr && !_inProgress(element)) {
    357        _fetch(element, value, status);
    358        return;
    359    }
    360    if (element == nullptr) {
    361        UErrorCode putError = U_ZERO_ERROR;
    362        // best-effort basis only.
    363        _putNew(key, value, status, putError);
    364    } else {
    365        _put(element, value, status);
    366    }
    367    // Run an eviction slice. This will run even if we added a primary entry
    368    // which doesn't increase the unused count, but that is still o.k
    369    _runEvictionSlice();
    370 }
    371 
    372 
    373 UBool UnifiedCache::_poll(
    374        const CacheKeyBase &key,
    375        const SharedObject *&value,
    376        UErrorCode &status) const {
    377    U_ASSERT(value == nullptr);
    378    U_ASSERT(status == U_ZERO_ERROR);
    379 #ifndef __wasi__
    380    std::unique_lock<std::mutex> lock(*gCacheMutex);
    381 #endif
    382    const UHashElement *element = uhash_find(fHashtable, &key);
    383 
    384    // If the hash table contains an inProgress placeholder entry for this key,
    385    // this means that another thread is currently constructing the value object.
    386    // Loop, waiting for that construction to complete.
    387     while (element != nullptr && _inProgress(element)) {
    388 #ifndef __wasi__
    389         gInProgressValueAddedCond->wait(lock);
    390 #endif
    391         element = uhash_find(fHashtable, &key);
    392    }
    393 
    394    // If the hash table contains an entry for the key,
    395    // fetch out the contents and return them.
    396    if (element != nullptr) {
    397         _fetch(element, value, status);
    398        return true;
    399    }
    400 
    401    // The hash table contained nothing for this key.
    402    // Insert an inProgress place holder value.
    403    // Our caller will create the final value and update the hash table.
    404    _putNew(key, fNoValue, U_ZERO_ERROR, status);
    405    return false;
    406 }
    407 
    408 void UnifiedCache::_get(
    409        const CacheKeyBase &key,
    410        const SharedObject *&value,
    411        const void *creationContext,
    412        UErrorCode &status) const {
    413    U_ASSERT(value == nullptr);
    414    U_ASSERT(status == U_ZERO_ERROR);
    415    if (_poll(key, value, status)) {
    416        if (value == fNoValue) {
    417            SharedObject::clearPtr(value);
    418        }
    419        return;
    420    }
    421    if (U_FAILURE(status)) {
    422        return;
    423    }
    424    value = key.createObject(creationContext, status);
    425    U_ASSERT(value == nullptr || value->hasHardReferences());
    426    U_ASSERT(value != nullptr || status != U_ZERO_ERROR);
    427    if (value == nullptr) {
    428        SharedObject::copyPtr(fNoValue, value);
    429    }
    430    _putIfAbsentAndGet(key, value, status);
    431    if (value == fNoValue) {
    432        SharedObject::clearPtr(value);
    433    }
    434 }
    435 
    436 void UnifiedCache::_registerPrimary(
    437            const CacheKeyBase *theKey, const SharedObject *value) const {
    438    theKey->fIsPrimary = true;
    439    value->cachePtr = this;
    440    ++fNumValuesTotal;
    441    ++fNumValuesInUse;
    442 }
    443 
    444 void UnifiedCache::_put(
    445        const UHashElement *element,
    446        const SharedObject *value,
    447        const UErrorCode status) const {
    448    U_ASSERT(_inProgress(element));
    449    const CacheKeyBase* theKey = static_cast<const CacheKeyBase*>(element->key.pointer);
    450    const SharedObject* oldValue = static_cast<const SharedObject*>(element->value.pointer);
    451    theKey->fCreationStatus = status;
    452    if (value->softRefCount == 0) {
    453        _registerPrimary(theKey, value);
    454    }
    455    value->softRefCount++;
    456    UHashElement *ptr = const_cast<UHashElement *>(element);
    457    ptr->value.pointer = (void *) value;
    458    U_ASSERT(oldValue == fNoValue);
    459    removeSoftRef(oldValue);
    460 
    461 #ifndef __wasi__
    462    // Tell waiting threads that we replace in-progress status with
    463    // an error.
    464    gInProgressValueAddedCond->notify_all();
    465 #endif
    466 }
    467 
    468 void UnifiedCache::_fetch(
    469        const UHashElement *element,
    470        const SharedObject *&value,
    471        UErrorCode &status) const {
    472    const CacheKeyBase* theKey = static_cast<const CacheKeyBase*>(element->key.pointer);
    473    status = theKey->fCreationStatus;
    474 
    475    // Since we have the cache lock, calling regular SharedObject add/removeRef
    476    // could cause us to deadlock on ourselves since they may need to lock
    477    // the cache mutex.
    478    removeHardRef(value);
    479    value = static_cast<const SharedObject *>(element->value.pointer);
    480    addHardRef(value);
    481 }
    482 
    483 
    484 UBool UnifiedCache::_inProgress(const UHashElement* element) const {
    485    UErrorCode status = U_ZERO_ERROR;
    486    const SharedObject * value = nullptr;
    487    _fetch(element, value, status);
    488    UBool result = _inProgress(value, status);
    489    removeHardRef(value);
    490    return result;
    491 }
    492 
    493 UBool UnifiedCache::_inProgress(
    494        const SharedObject* theValue, UErrorCode creationStatus) const {
    495    return (theValue == fNoValue && creationStatus == U_ZERO_ERROR);
    496 }
    497 
    498 UBool UnifiedCache::_isEvictable(const UHashElement *element) const
    499 {
    500    const CacheKeyBase* theKey = static_cast<const CacheKeyBase*>(element->key.pointer);
    501    const SharedObject* theValue = static_cast<const SharedObject*>(element->value.pointer);
    502 
    503    // Entries that are under construction are never evictable
    504    if (_inProgress(theValue, theKey->fCreationStatus)) {
    505        return false;
    506    }
    507 
    508    // We can evict entries that are either not a primary or have just
    509    // one reference (The one reference being from the cache itself).
    510    return (!theKey->fIsPrimary || (theValue->softRefCount == 1 && theValue->noHardReferences()));
    511 }
    512 
    513 void UnifiedCache::removeSoftRef(const SharedObject *value) const {
    514    U_ASSERT(value->cachePtr == this);
    515    U_ASSERT(value->softRefCount > 0);
    516    if (--value->softRefCount == 0) {
    517        --fNumValuesTotal;
    518        if (value->noHardReferences()) {
    519            delete value;
    520        } else {
    521            // This path only happens from flush(all). Which only happens from the
    522            // UnifiedCache destructor.  Nulling out value.cacheptr changes the behavior
    523            // of value.removeRef(), causing the deletion to be done there.
    524            value->cachePtr = nullptr;
    525        }
    526    }
    527 }
    528 
    529 int32_t UnifiedCache::removeHardRef(const SharedObject *value) const {
    530    int refCount = 0;
    531    if (value) {
    532        refCount = umtx_atomic_dec(&value->hardRefCount);
    533        U_ASSERT(refCount >= 0);
    534        if (refCount == 0) {
    535            --fNumValuesInUse;
    536        }
    537    }
    538    return refCount;
    539 }
    540 
    541 int32_t UnifiedCache::addHardRef(const SharedObject *value) const {
    542    int refCount = 0;
    543    if (value) {
    544        refCount = umtx_atomic_inc(&value->hardRefCount);
    545        U_ASSERT(refCount >= 1);
    546        if (refCount == 1) {
    547            fNumValuesInUse++;
    548        }
    549    }
    550    return refCount;
    551 }
    552 
    553 U_NAMESPACE_END