2 ******************************************************************************
3 * Copyright (C) 2015, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 ******************************************************************************
7 * File UNIFIEDCACHE.CPP
8 ******************************************************************************
12 #include "unifiedcache.h"
18 static icu::UnifiedCache
*gCache
= NULL
;
19 static icu::SharedObject
*gNoValue
= NULL
;
20 static UMutex gCacheMutex
= U_MUTEX_INITIALIZER
;
21 static UConditionVar gInProgressValueAddedCond
= U_CONDITION_INITIALIZER
;
22 static icu::UInitOnce gCacheInitOnce
= U_INITONCE_INITIALIZER
;
23 static const int32_t MAX_EVICT_ITERATIONS
= 10;
25 static int32_t DEFAULT_MAX_UNUSED
= 1000;
26 static int32_t DEFAULT_PERCENTAGE_OF_IN_USE
= 100;
30 static UBool U_CALLCONV
unifiedcache_cleanup() {
31 gCacheInitOnce
.reset();
47 U_CAPI
int32_t U_EXPORT2
48 ucache_hashKeys(const UHashTok key
) {
49 const CacheKeyBase
*ckey
= (const CacheKeyBase
*) key
.pointer
;
50 return ckey
->hashCode();
53 U_CAPI UBool U_EXPORT2
54 ucache_compareKeys(const UHashTok key1
, const UHashTok key2
) {
55 const CacheKeyBase
*p1
= (const CacheKeyBase
*) key1
.pointer
;
56 const CacheKeyBase
*p2
= (const CacheKeyBase
*) key2
.pointer
;
61 ucache_deleteKey(void *obj
) {
62 CacheKeyBase
*p
= (CacheKeyBase
*) obj
;
66 CacheKeyBase::~CacheKeyBase() {
69 static void U_CALLCONV
cacheInit(UErrorCode
&status
) {
70 U_ASSERT(gCache
== NULL
);
71 ucln_common_registerCleanup(
72 UCLN_COMMON_UNIFIED_CACHE
, unifiedcache_cleanup
);
74 // gNoValue must be created first to avoid assertion error in
76 gNoValue
= new SharedObject();
77 gCache
= new UnifiedCache(status
);
79 status
= U_MEMORY_ALLOCATION_ERROR
;
81 if (U_FAILURE(status
)) {
88 // We add a softref because we want hash elements with gNoValue to be
89 // elligible for purging but we don't ever want gNoValue to be deleted.
90 gNoValue
->addSoftRef();
93 UnifiedCache
*UnifiedCache::getInstance(UErrorCode
&status
) {
94 umtx_initOnce(gCacheInitOnce
, &cacheInit
, status
);
95 if (U_FAILURE(status
)) {
98 U_ASSERT(gCache
!= NULL
);
102 UnifiedCache::UnifiedCache(UErrorCode
&status
) :
104 fEvictPos(UHASH_FIRST
),
106 fMaxUnused(DEFAULT_MAX_UNUSED
),
107 fMaxPercentageOfInUse(DEFAULT_PERCENTAGE_OF_IN_USE
),
108 fAutoEvictedCount(0) {
109 if (U_FAILURE(status
)) {
112 U_ASSERT(gNoValue
!= NULL
);
113 fHashtable
= uhash_open(
118 if (U_FAILURE(status
)) {
121 uhash_setKeyDeleter(fHashtable
, &ucache_deleteKey
);
124 void UnifiedCache::setEvictionPolicy(
125 int32_t count
, int32_t percentageOfInUseItems
, UErrorCode
&status
) {
126 if (U_FAILURE(status
)) {
129 if (count
< 0 || percentageOfInUseItems
< 0) {
130 status
= U_ILLEGAL_ARGUMENT_ERROR
;
133 Mutex
lock(&gCacheMutex
);
135 fMaxPercentageOfInUse
= percentageOfInUseItems
;
138 int32_t UnifiedCache::unusedCount() const {
139 Mutex
lock(&gCacheMutex
);
140 return uhash_count(fHashtable
) - fItemsInUseCount
;
143 int64_t UnifiedCache::autoEvictedCount() const {
144 Mutex
lock(&gCacheMutex
);
145 return fAutoEvictedCount
;
148 int32_t UnifiedCache::keyCount() const {
149 Mutex
lock(&gCacheMutex
);
150 return uhash_count(fHashtable
);
153 void UnifiedCache::flush() const {
154 Mutex
lock(&gCacheMutex
);
156 // Use a loop in case cache items that are flushed held hard references to
157 // other cache items making those additional cache items eligible for
159 while (_flush(FALSE
));
162 #ifdef UNIFIED_CACHE_DEBUG
165 void UnifiedCache::dump() {
166 UErrorCode status
= U_ZERO_ERROR
;
167 const UnifiedCache
*cache
= getInstance(status
);
168 if (U_FAILURE(status
)) {
169 fprintf(stderr
, "Unified Cache: Error fetching cache.\n");
172 cache
->dumpContents();
175 void UnifiedCache::dumpContents() const {
176 Mutex
lock(&gCacheMutex
);
180 // Dumps content of cache.
181 // On entry, gCacheMutex must be held.
182 // On exit, cache contents dumped to stderr.
183 void UnifiedCache::_dumpContents() const {
184 int32_t pos
= UHASH_FIRST
;
185 const UHashElement
*element
= uhash_nextElement(fHashtable
, &pos
);
188 for (; element
!= NULL
; element
= uhash_nextElement(fHashtable
, &pos
)) {
189 const SharedObject
*sharedObject
=
190 (const SharedObject
*) element
->value
.pointer
;
191 const CacheKeyBase
*key
=
192 (const CacheKeyBase
*) element
->key
.pointer
;
193 if (sharedObject
->hasHardReferences()) {
197 "Unified Cache: Key '%s', error %d, value %p, total refcount %d, soft refcount %d\n",
198 key
->writeDescription(buffer
, 256),
200 sharedObject
== gNoValue
? NULL
:sharedObject
,
201 sharedObject
->getRefCount(),
202 sharedObject
->getSoftRefCount());
205 fprintf(stderr
, "Unified Cache: %d out of a total of %d still have hard references\n", cnt
, uhash_count(fHashtable
));
209 UnifiedCache::~UnifiedCache() {
210 // Try our best to clean up first.
213 // Now all that should be left in the cache are entries that refer to
214 // each other and entries with hard references from outside the cache.
215 // Nothing we can do about these so proceed to wipe out the cache.
216 Mutex
lock(&gCacheMutex
);
219 uhash_close(fHashtable
);
222 // Returns the next element in the cache round robin style.
223 // On entry, gCacheMutex must be held.
225 UnifiedCache::_nextElement() const {
226 const UHashElement
*element
= uhash_nextElement(fHashtable
, &fEvictPos
);
227 if (element
== NULL
) {
228 fEvictPos
= UHASH_FIRST
;
229 return uhash_nextElement(fHashtable
, &fEvictPos
);
234 // Flushes the contents of the cache. If cache values hold references to other
235 // cache values then _flush should be called in a loop until it returns FALSE.
236 // On entry, gCacheMutex must be held.
237 // On exit, those values with are evictable are flushed. If all is true
238 // then every value is flushed even if it is not evictable.
239 // Returns TRUE if any value in cache was flushed or FALSE otherwise.
240 UBool
UnifiedCache::_flush(UBool all
) const {
241 UBool result
= FALSE
;
242 int32_t origSize
= uhash_count(fHashtable
);
243 for (int32_t i
= 0; i
< origSize
; ++i
) {
244 const UHashElement
*element
= _nextElement();
245 if (all
|| _isEvictable(element
)) {
246 const SharedObject
*sharedObject
=
247 (const SharedObject
*) element
->value
.pointer
;
248 uhash_removeElement(fHashtable
, element
);
249 sharedObject
->removeSoftRef();
256 // Computes how many items should be evicted.
257 // On entry, gCacheMutex must be held.
258 // Returns number of items that should be evicted or a value <= 0 if no
259 // items need to be evicted.
260 int32_t UnifiedCache::_computeCountOfItemsToEvict() const {
261 int32_t maxPercentageOfInUseCount
=
262 fItemsInUseCount
* fMaxPercentageOfInUse
/ 100;
263 int32_t maxUnusedCount
= fMaxUnused
;
264 if (maxUnusedCount
< maxPercentageOfInUseCount
) {
265 maxUnusedCount
= maxPercentageOfInUseCount
;
267 return uhash_count(fHashtable
) - fItemsInUseCount
- maxUnusedCount
;
270 // Run an eviction slice.
271 // On entry, gCacheMutex must be held.
272 // _runEvictionSlice runs a slice of the evict pipeline by examining the next
273 // 10 entries in the cache round robin style evicting them if they are eligible.
274 void UnifiedCache::_runEvictionSlice() const {
275 int32_t maxItemsToEvict
= _computeCountOfItemsToEvict();
276 if (maxItemsToEvict
<= 0) {
279 for (int32_t i
= 0; i
< MAX_EVICT_ITERATIONS
; ++i
) {
280 const UHashElement
*element
= _nextElement();
281 if (_isEvictable(element
)) {
282 const SharedObject
*sharedObject
=
283 (const SharedObject
*) element
->value
.pointer
;
284 uhash_removeElement(fHashtable
, element
);
285 sharedObject
->removeSoftRef();
287 if (--maxItemsToEvict
== 0) {
295 // Places a new value and creationStatus in the cache for the given key.
296 // On entry, gCacheMutex must be held. key must not exist in the cache.
297 // On exit, value and creation status placed under key. Soft reference added
298 // to value on successful add. On error sets status.
299 void UnifiedCache::_putNew(
300 const CacheKeyBase
&key
,
301 const SharedObject
*value
,
302 const UErrorCode creationStatus
,
303 UErrorCode
&status
) const {
304 if (U_FAILURE(status
)) {
307 CacheKeyBase
*keyToAdopt
= key
.clone();
308 if (keyToAdopt
== NULL
) {
309 status
= U_MEMORY_ALLOCATION_ERROR
;
312 keyToAdopt
->fCreationStatus
= creationStatus
;
313 if (value
->noSoftReferences()) {
314 _registerMaster(keyToAdopt
, value
);
316 uhash_put(fHashtable
, keyToAdopt
, (void *) value
, &status
);
317 if (U_SUCCESS(status
)) {
322 // Places value and status at key if there is no value at key or if cache
323 // entry for key is in progress. Otherwise, it leaves the current value and
325 // On entry. gCacheMutex must not be held. value must be
326 // included in the reference count of the object to which it points.
327 // On exit, value and status are changed to what was already in the cache if
328 // something was there and not in progress. Otherwise, value and status are left
329 // unchanged in which case they are placed in the cache on a best-effort basis.
330 // Caller must call removeRef() on value.
331 void UnifiedCache::_putIfAbsentAndGet(
332 const CacheKeyBase
&key
,
333 const SharedObject
*&value
,
334 UErrorCode
&status
) const {
335 Mutex
lock(&gCacheMutex
);
336 const UHashElement
*element
= uhash_find(fHashtable
, &key
);
337 if (element
!= NULL
&& !_inProgress(element
)) {
338 _fetch(element
, value
, status
);
341 if (element
== NULL
) {
342 UErrorCode putError
= U_ZERO_ERROR
;
343 // best-effort basis only.
344 _putNew(key
, value
, status
, putError
);
346 _put(element
, value
, status
);
348 // Run an eviction slice. This will run even if we added a master entry
349 // which doesn't increase the unused count, but that is still o.k
353 // Attempts to fetch value and status for key from cache.
354 // On entry, gCacheMutex must not be held value must be NULL and status must
356 // On exit, either returns FALSE (In this
357 // case caller should try to create the object) or returns TRUE with value
358 // pointing to the fetched value and status set to fetched status. When
359 // FALSE is returned status may be set to failure if an in progress hash
360 // entry could not be made but value will remain unchanged. When TRUE is
361 // returned, caler must call removeRef() on value.
362 UBool
UnifiedCache::_poll(
363 const CacheKeyBase
&key
,
364 const SharedObject
*&value
,
365 UErrorCode
&status
) const {
366 U_ASSERT(value
== NULL
);
367 U_ASSERT(status
== U_ZERO_ERROR
);
368 Mutex
lock(&gCacheMutex
);
369 const UHashElement
*element
= uhash_find(fHashtable
, &key
);
370 while (element
!= NULL
&& _inProgress(element
)) {
371 umtx_condWait(&gInProgressValueAddedCond
, &gCacheMutex
);
372 element
= uhash_find(fHashtable
, &key
);
374 if (element
!= NULL
) {
375 _fetch(element
, value
, status
);
378 _putNew(key
, gNoValue
, U_ZERO_ERROR
, status
);
382 // Gets value out of cache.
383 // On entry. gCacheMutex must not be held. value must be NULL. status
384 // must be U_ZERO_ERROR.
385 // On exit. value and status set to what is in cache at key or on cache
386 // miss the key's createObject() is called and value and status are set to
387 // the result of that. In this latter case, best effort is made to add the
388 // value and status to the cache. If createObject() fails to create a value,
389 // gNoValue is stored in cache, and value is set to NULL. Caller must call
390 // removeRef on value if non NULL.
391 void UnifiedCache::_get(
392 const CacheKeyBase
&key
,
393 const SharedObject
*&value
,
394 const void *creationContext
,
395 UErrorCode
&status
) const {
396 U_ASSERT(value
== NULL
);
397 U_ASSERT(status
== U_ZERO_ERROR
);
398 if (_poll(key
, value
, status
)) {
399 if (value
== gNoValue
) {
400 SharedObject::clearPtr(value
);
404 if (U_FAILURE(status
)) {
407 value
= key
.createObject(creationContext
, status
);
408 U_ASSERT(value
== NULL
|| value
->hasHardReferences());
409 U_ASSERT(value
!= NULL
|| status
!= U_ZERO_ERROR
);
411 SharedObject::copyPtr(gNoValue
, value
);
413 _putIfAbsentAndGet(key
, value
, status
);
414 if (value
== gNoValue
) {
415 SharedObject::clearPtr(value
);
419 void UnifiedCache::decrementItemsInUseWithLockingAndEviction() const {
420 Mutex
mutex(&gCacheMutex
);
421 decrementItemsInUse();
425 void UnifiedCache::incrementItemsInUse() const {
429 void UnifiedCache::decrementItemsInUse() const {
433 // Register a master cache entry.
434 // On entry, gCacheMutex must be held.
435 // On exit, items in use count incremented, entry is marked as a master
436 // entry, and value registered with cache so that subsequent calls to
437 // addRef() and removeRef() on it correctly updates items in use count
438 void UnifiedCache::_registerMaster(
439 const CacheKeyBase
*theKey
, const SharedObject
*value
) const {
440 theKey
->fIsMaster
= TRUE
;
442 value
->registerWithCache(this);
445 // Store a value and error in given hash entry.
446 // On entry, gCacheMutex must be held. Hash entry element must be in progress.
447 // value must be non NULL.
448 // On Exit, soft reference added to value. value and status stored in hash
449 // entry. Soft reference removed from previous stored value. Waiting
451 void UnifiedCache::_put(
452 const UHashElement
*element
,
453 const SharedObject
*value
,
454 const UErrorCode status
) const {
455 U_ASSERT(_inProgress(element
));
456 const CacheKeyBase
*theKey
= (const CacheKeyBase
*) element
->key
.pointer
;
457 const SharedObject
*oldValue
= (const SharedObject
*) element
->value
.pointer
;
458 theKey
->fCreationStatus
= status
;
459 if (value
->noSoftReferences()) {
460 _registerMaster(theKey
, value
);
463 UHashElement
*ptr
= const_cast<UHashElement
*>(element
);
464 ptr
->value
.pointer
= (void *) value
;
465 oldValue
->removeSoftRef();
467 // Tell waiting threads that we replace in-progress status with
469 umtx_condBroadcast(&gInProgressValueAddedCond
);
473 UnifiedCache::copyPtr(const SharedObject
*src
, const SharedObject
*&dest
) {
476 dest
->removeRefWhileHoldingCacheLock();
480 src
->addRefWhileHoldingCacheLock();
486 UnifiedCache::clearPtr(const SharedObject
*&ptr
) {
488 ptr
->removeRefWhileHoldingCacheLock();
494 // Fetch value and error code from a particular hash entry.
495 // On entry, gCacheMutex must be held. value must be either NULL or must be
496 // included in the ref count of the object to which it points.
497 // On exit, value and status set to what is in the hash entry. Caller must
498 // eventually call removeRef on value.
499 // If hash entry is in progress, value will be set to gNoValue and status will
500 // be set to U_ZERO_ERROR.
501 void UnifiedCache::_fetch(
502 const UHashElement
*element
,
503 const SharedObject
*&value
,
504 UErrorCode
&status
) {
505 const CacheKeyBase
*theKey
= (const CacheKeyBase
*) element
->key
.pointer
;
506 status
= theKey
->fCreationStatus
;
508 // Since we have the cache lock, calling regular SharedObject methods
509 // could cause us to deadlock on ourselves since they may need to lock
511 UnifiedCache::copyPtr((const SharedObject
*) element
->value
.pointer
, value
);
514 // Determine if given hash entry is in progress.
515 // On entry, gCacheMutex must be held.
516 UBool
UnifiedCache::_inProgress(const UHashElement
*element
) {
517 const SharedObject
*value
= NULL
;
518 UErrorCode status
= U_ZERO_ERROR
;
519 _fetch(element
, value
, status
);
520 UBool result
= _inProgress(value
, status
);
522 // Since we have the cache lock, calling regular SharedObject methods
523 // could cause us to deadlock on ourselves since they may need to lock
525 UnifiedCache::clearPtr(value
);
529 // Determine if given hash entry is in progress.
530 // On entry, gCacheMutex must be held.
531 UBool
UnifiedCache::_inProgress(
532 const SharedObject
*theValue
, UErrorCode creationStatus
) {
533 return (theValue
== gNoValue
&& creationStatus
== U_ZERO_ERROR
);
536 // Determine if given hash entry is eligible for eviction.
537 // On entry, gCacheMutex must be held.
538 UBool
UnifiedCache::_isEvictable(const UHashElement
*element
) {
539 const CacheKeyBase
*theKey
= (const CacheKeyBase
*) element
->key
.pointer
;
540 const SharedObject
*theValue
=
541 (const SharedObject
*) element
->value
.pointer
;
543 // Entries that are under construction are never evictable
544 if (_inProgress(theValue
, theKey
->fCreationStatus
)) {
548 // We can evict entries that are either not a master or have just
549 // one reference (The one reference being from the cache itself).
550 return (!theKey
->fIsMaster
|| (theValue
->getSoftRefCount() == 1 && theValue
->noHardReferences()));