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