2 ******************************************************************************
3 * Copyright (C) 2014, 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
;
25 static UBool U_CALLCONV
unifiedcache_cleanup() {
26 gCacheInitOnce
.reset();
42 U_CAPI
int32_t U_EXPORT2
43 ucache_hashKeys(const UHashTok key
) {
44 const CacheKeyBase
*ckey
= (const CacheKeyBase
*) key
.pointer
;
45 return ckey
->hashCode();
48 U_CAPI UBool U_EXPORT2
49 ucache_compareKeys(const UHashTok key1
, const UHashTok key2
) {
50 const CacheKeyBase
*p1
= (const CacheKeyBase
*) key1
.pointer
;
51 const CacheKeyBase
*p2
= (const CacheKeyBase
*) key2
.pointer
;
56 ucache_deleteKey(void *obj
) {
57 CacheKeyBase
*p
= (CacheKeyBase
*) obj
;
61 CacheKeyBase::~CacheKeyBase() {
64 static void U_CALLCONV
cacheInit(UErrorCode
&status
) {
65 U_ASSERT(gCache
== NULL
);
66 ucln_common_registerCleanup(
67 UCLN_COMMON_UNIFIED_CACHE
, unifiedcache_cleanup
);
69 // gNoValue must be created first to avoid assertion error in
71 gNoValue
= new SharedObject();
72 gCache
= new UnifiedCache(status
);
74 status
= U_MEMORY_ALLOCATION_ERROR
;
76 if (U_FAILURE(status
)) {
83 // We add a softref because we want hash elements with gNoValue to be
84 // elligible for purging but we don't ever want gNoValue to be deleted.
85 gNoValue
->addSoftRef();
88 const UnifiedCache
*UnifiedCache::getInstance(UErrorCode
&status
) {
89 umtx_initOnce(gCacheInitOnce
, &cacheInit
, status
);
90 if (U_FAILURE(status
)) {
93 U_ASSERT(gCache
!= NULL
);
97 UnifiedCache::UnifiedCache(UErrorCode
&status
) {
98 if (U_FAILURE(status
)) {
101 U_ASSERT(gNoValue
!= NULL
);
102 fHashtable
= uhash_open(
107 if (U_FAILURE(status
)) {
110 uhash_setKeyDeleter(fHashtable
, &ucache_deleteKey
);
113 int32_t UnifiedCache::keyCount() const {
114 Mutex
lock(&gCacheMutex
);
115 return uhash_count(fHashtable
);
118 void UnifiedCache::flush() const {
119 Mutex
lock(&gCacheMutex
);
121 // Use a loop in case cache items that are flushed held hard references to
122 // other cache items making those additional cache items eligible for
124 while (_flush(FALSE
));
125 umtx_condBroadcast(&gInProgressValueAddedCond
);
128 #ifdef UNIFIED_CACHE_DEBUG
131 void UnifiedCache::dump() {
132 UErrorCode status
= U_ZERO_ERROR
;
133 const UnifiedCache
*cache
= getInstance(status
);
134 if (U_FAILURE(status
)) {
135 fprintf(stderr
, "Unified Cache: Error fetching cache.\n");
138 cache
->dumpContents();
141 void UnifiedCache::dumpContents() const {
142 Mutex
lock(&gCacheMutex
);
146 // Dumps content of cache.
147 // On entry, gCacheMutex must be held.
148 // On exit, cache contents dumped to stderr.
149 void UnifiedCache::_dumpContents() const {
150 int32_t pos
= UHASH_FIRST
;
151 const UHashElement
*element
= uhash_nextElement(fHashtable
, &pos
);
154 for (; element
!= NULL
; element
= uhash_nextElement(fHashtable
, &pos
)) {
155 const SharedObject
*sharedObject
=
156 (const SharedObject
*) element
->value
.pointer
;
157 const CacheKeyBase
*key
=
158 (const CacheKeyBase
*) element
->key
.pointer
;
159 if (!sharedObject
->allSoftReferences()) {
163 "Unified Cache: Key '%s', error %d, value %p, total refcount %d, soft refcount %d\n",
164 key
->writeDescription(buffer
, 256),
166 sharedObject
== gNoValue
? NULL
:sharedObject
,
167 sharedObject
->getRefCount(),
168 sharedObject
->getSoftRefCount());
171 fprintf(stderr
, "Unified Cache: %d out of a total of %d still have hard references\n", cnt
, uhash_count(fHashtable
));
175 UnifiedCache::~UnifiedCache() {
176 // Try our best to clean up first.
179 // Now all that should be left in the cache are entries that refer to
180 // each other and entries with hard references from outside the cache.
181 // Nothing we can do about these so proceed to wipe out the cache.
182 Mutex
lock(&gCacheMutex
);
185 uhash_close(fHashtable
);
188 // Flushes the contents of the cache. If cache values hold references to other
189 // cache values then _flush should be called in a loop until it returns FALSE.
190 // On entry, gCacheMutex must be held.
191 // On exit, those values with only soft references are flushed. If all is true
192 // then every value is flushed even if hard references are held.
193 // Returns TRUE if any value in cache was flushed or FALSE otherwise.
194 UBool
UnifiedCache::_flush(UBool all
) const {
195 UBool result
= FALSE
;
196 int32_t pos
= UHASH_FIRST
;
197 const UHashElement
*element
= uhash_nextElement(fHashtable
, &pos
);
198 for (; element
!= NULL
; element
= uhash_nextElement(fHashtable
, &pos
)) {
199 const SharedObject
*sharedObject
=
200 (const SharedObject
*) element
->value
.pointer
;
201 if (all
|| sharedObject
->allSoftReferences()) {
202 uhash_removeElement(fHashtable
, element
);
203 sharedObject
->removeSoftRef();
210 // Places a new value and creationStatus in the cache for the given key.
211 // On entry, gCacheMutex must be held. key must not exist in the cache.
212 // On exit, value and creation status placed under key. Soft reference added
213 // to value on successful add. On error sets status.
214 void UnifiedCache::_putNew(
215 const CacheKeyBase
&key
,
216 const SharedObject
*value
,
217 const UErrorCode creationStatus
,
218 UErrorCode
&status
) const {
219 if (U_FAILURE(status
)) {
222 CacheKeyBase
*keyToAdopt
= key
.clone();
223 if (keyToAdopt
== NULL
) {
224 status
= U_MEMORY_ALLOCATION_ERROR
;
227 keyToAdopt
->creationStatus
= creationStatus
;
228 uhash_put(fHashtable
, keyToAdopt
, (void *) value
, &status
);
229 if (U_SUCCESS(status
)) {
234 // Places value and status at key if there is no value at key or if cache
235 // entry for key is in progress. Otherwise, it leaves the current value and
237 // On entry. gCacheMutex must not be held. value must be
238 // included in the reference count of the object to which it points.
239 // On exit, value and status are changed to what was already in the cache if
240 // something was there and not in progress. Otherwise, value and status are left
241 // unchanged in which case they are placed in the cache on a best-effort basis.
242 // Caller must call removeRef() on value.
243 void UnifiedCache::_putIfAbsentAndGet(
244 const CacheKeyBase
&key
,
245 const SharedObject
*&value
,
246 UErrorCode
&status
) const {
247 Mutex
lock(&gCacheMutex
);
248 const UHashElement
*element
= uhash_find(fHashtable
, &key
);
249 if (element
!= NULL
&& !_inProgress(element
)) {
250 _fetch(element
, value
, status
);
253 if (element
== NULL
) {
254 UErrorCode putError
= U_ZERO_ERROR
;
255 // best-effort basis only.
256 _putNew(key
, value
, status
, putError
);
259 _put(element
, value
, status
);
262 // Attempts to fetch value and status for key from cache.
263 // On entry, gCacheMutex must not be held value must be NULL and status must
265 // On exit, either returns FALSE (In this
266 // case caller should try to create the object) or returns TRUE with value
267 // pointing to the fetched value and status set to fetched status. When
268 // FALSE is returned status may be set to failure if an in progress hash
269 // entry could not be made but value will remain unchanged. When TRUE is
270 // returned, caler must call removeRef() on value.
271 UBool
UnifiedCache::_poll(
272 const CacheKeyBase
&key
,
273 const SharedObject
*&value
,
274 UErrorCode
&status
) const {
275 U_ASSERT(value
== NULL
);
276 U_ASSERT(status
== U_ZERO_ERROR
);
277 Mutex
lock(&gCacheMutex
);
278 const UHashElement
*element
= uhash_find(fHashtable
, &key
);
279 while (element
!= NULL
&& _inProgress(element
)) {
280 umtx_condWait(&gInProgressValueAddedCond
, &gCacheMutex
);
281 element
= uhash_find(fHashtable
, &key
);
283 if (element
!= NULL
) {
284 _fetch(element
, value
, status
);
287 _putNew(key
, gNoValue
, U_ZERO_ERROR
, status
);
291 // Gets value out of cache.
292 // On entry. gCacheMutex must not be held. value must be NULL. status
293 // must be U_ZERO_ERROR.
294 // On exit. value and status set to what is in cache at key or on cache
295 // miss the key's createObject() is called and value and status are set to
296 // the result of that. In this latter case, best effort is made to add the
297 // value and status to the cache. value will be set to NULL instead of
298 // gNoValue. Caller must call removeRef on value if non NULL.
299 void UnifiedCache::_get(
300 const CacheKeyBase
&key
,
301 const SharedObject
*&value
,
302 const void *creationContext
,
303 UErrorCode
&status
) const {
304 U_ASSERT(value
== NULL
);
305 U_ASSERT(status
== U_ZERO_ERROR
);
306 if (_poll(key
, value
, status
)) {
307 if (value
== gNoValue
) {
308 SharedObject::clearPtr(value
);
312 if (U_FAILURE(status
)) {
315 value
= key
.createObject(creationContext
, status
);
316 U_ASSERT(value
== NULL
|| !value
->allSoftReferences());
317 U_ASSERT(value
!= NULL
|| status
!= U_ZERO_ERROR
);
319 SharedObject::copyPtr(gNoValue
, value
);
321 _putIfAbsentAndGet(key
, value
, status
);
322 if (value
== gNoValue
) {
323 SharedObject::clearPtr(value
);
327 // Store a value and error in given hash entry.
328 // On entry, gCacheMutex must be held. Hash entry element must be in progress.
329 // value must be non NULL.
330 // On Exit, soft reference added to value. value and status stored in hash
331 // entry. Soft reference removed from previous stored value. Waiting
333 void UnifiedCache::_put(
334 const UHashElement
*element
,
335 const SharedObject
*value
,
336 const UErrorCode status
) {
337 U_ASSERT(_inProgress(element
));
338 const CacheKeyBase
*theKey
= (const CacheKeyBase
*) element
->key
.pointer
;
339 const SharedObject
*oldValue
= (const SharedObject
*) element
->value
.pointer
;
340 theKey
->creationStatus
= status
;
342 UHashElement
*ptr
= const_cast<UHashElement
*>(element
);
343 ptr
->value
.pointer
= (void *) value
;
344 oldValue
->removeSoftRef();
346 // Tell waiting threads that we replace in-progress status with
348 umtx_condBroadcast(&gInProgressValueAddedCond
);
351 // Fetch value and error code from a particular hash entry.
352 // On entry, gCacheMutex must be held. value must be either NULL or must be
353 // included in the ref count of the object to which it points.
354 // On exit, value and status set to what is in the hash entry. Caller must
355 // eventually call removeRef on value.
356 // If hash entry is in progress, value will be set to gNoValue and status will
357 // be set to U_ZERO_ERROR.
358 void UnifiedCache::_fetch(
359 const UHashElement
*element
,
360 const SharedObject
*&value
,
361 UErrorCode
&status
) {
362 const CacheKeyBase
*theKey
= (const CacheKeyBase
*) element
->key
.pointer
;
363 status
= theKey
->creationStatus
;
364 SharedObject::copyPtr(
365 (const SharedObject
*) element
->value
.pointer
, value
);
368 // Determine if given hash entry is in progress.
369 // On entry, gCacheMutex must be held.
370 UBool
UnifiedCache::_inProgress(const UHashElement
*element
) {
371 const SharedObject
*value
= NULL
;
372 UErrorCode status
= U_ZERO_ERROR
;
373 _fetch(element
, value
, status
);
374 UBool result
= (value
== gNoValue
&& status
== U_ZERO_ERROR
);
375 SharedObject::clearPtr(value
);