Coverage Report

Created: 2018-09-25 14:53

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