OLD | NEW |
(Empty) | |
| 1 /* |
| 2 ****************************************************************************** |
| 3 * Copyright (C) 2014, International Business Machines Corporation and |
| 4 * others. All Rights Reserved. |
| 5 ****************************************************************************** |
| 6 * |
| 7 * File UNIFIEDCACHE.CPP |
| 8 ****************************************************************************** |
| 9 */ |
| 10 |
| 11 #include "uhash.h" |
| 12 #include "unifiedcache.h" |
| 13 #include "umutex.h" |
| 14 #include "mutex.h" |
| 15 #include "uassert.h" |
| 16 #include "ucln_cmn.h" |
| 17 |
| 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 |
| 24 U_CDECL_BEGIN |
| 25 static UBool U_CALLCONV unifiedcache_cleanup() { |
| 26 gCacheInitOnce.reset(); |
| 27 if (gCache) { |
| 28 delete gCache; |
| 29 gCache = NULL; |
| 30 } |
| 31 if (gNoValue) { |
| 32 delete gNoValue; |
| 33 gNoValue = NULL; |
| 34 } |
| 35 return TRUE; |
| 36 } |
| 37 U_CDECL_END |
| 38 |
| 39 |
| 40 U_NAMESPACE_BEGIN |
| 41 |
| 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(); |
| 46 } |
| 47 |
| 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; |
| 52 return *p1 == *p2; |
| 53 } |
| 54 |
| 55 U_CAPI void U_EXPORT2 |
| 56 ucache_deleteKey(void *obj) { |
| 57 CacheKeyBase *p = (CacheKeyBase *) obj; |
| 58 delete p; |
| 59 } |
| 60 |
| 61 CacheKeyBase::~CacheKeyBase() { |
| 62 } |
| 63 |
| 64 static void U_CALLCONV cacheInit(UErrorCode &status) { |
| 65 U_ASSERT(gCache == NULL); |
| 66 ucln_common_registerCleanup( |
| 67 UCLN_COMMON_UNIFIED_CACHE, unifiedcache_cleanup); |
| 68 |
| 69 // gNoValue must be created first to avoid assertion error in |
| 70 // cache constructor. |
| 71 gNoValue = new SharedObject(); |
| 72 gCache = new UnifiedCache(status); |
| 73 if (gCache == NULL) { |
| 74 status = U_MEMORY_ALLOCATION_ERROR; |
| 75 } |
| 76 if (U_FAILURE(status)) { |
| 77 delete gCache; |
| 78 delete gNoValue; |
| 79 gCache = NULL; |
| 80 gNoValue = NULL; |
| 81 return; |
| 82 } |
| 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(); |
| 86 } |
| 87 |
| 88 const UnifiedCache *UnifiedCache::getInstance(UErrorCode &status) { |
| 89 umtx_initOnce(gCacheInitOnce, &cacheInit, status); |
| 90 if (U_FAILURE(status)) { |
| 91 return NULL; |
| 92 } |
| 93 U_ASSERT(gCache != NULL); |
| 94 return gCache; |
| 95 } |
| 96 |
| 97 UnifiedCache::UnifiedCache(UErrorCode &status) { |
| 98 if (U_FAILURE(status)) { |
| 99 return; |
| 100 } |
| 101 U_ASSERT(gNoValue != NULL); |
| 102 fHashtable = uhash_open( |
| 103 &ucache_hashKeys, |
| 104 &ucache_compareKeys, |
| 105 NULL, |
| 106 &status); |
| 107 if (U_FAILURE(status)) { |
| 108 return; |
| 109 } |
| 110 uhash_setKeyDeleter(fHashtable, &ucache_deleteKey); |
| 111 } |
| 112 |
| 113 int32_t UnifiedCache::keyCount() const { |
| 114 Mutex lock(&gCacheMutex); |
| 115 return uhash_count(fHashtable); |
| 116 } |
| 117 |
| 118 void UnifiedCache::flush() const { |
| 119 Mutex lock(&gCacheMutex); |
| 120 |
| 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 |
| 123 // flushing. |
| 124 while (_flush(FALSE)); |
| 125 umtx_condBroadcast(&gInProgressValueAddedCond); |
| 126 } |
| 127 |
| 128 #ifdef UNIFIED_CACHE_DEBUG |
| 129 #include <stdio.h> |
| 130 |
| 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"); |
| 136 return; |
| 137 } |
| 138 cache->dumpContents(); |
| 139 } |
| 140 |
| 141 void UnifiedCache::dumpContents() const { |
| 142 Mutex lock(&gCacheMutex); |
| 143 _dumpContents(); |
| 144 } |
| 145 |
| 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 = -1; |
| 151 const UHashElement *element = uhash_nextElement(fHashtable, &pos); |
| 152 char buffer[256]; |
| 153 int32_t cnt = 0; |
| 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()) { |
| 160 ++cnt; |
| 161 fprintf( |
| 162 stderr, |
| 163 "Unified Cache: Key '%s', error %d, value %p, total refcount
%d, soft refcount %d\n", |
| 164 key->writeDescription(buffer, 256), |
| 165 key->creationStatus, |
| 166 sharedObject == gNoValue ? NULL :sharedObject, |
| 167 sharedObject->getRefCount(), |
| 168 sharedObject->getSoftRefCount()); |
| 169 } |
| 170 } |
| 171 fprintf(stderr, "Unified Cache: %d out of a total of %d still have hard refe
rences\n", cnt, uhash_count(fHashtable)); |
| 172 } |
| 173 #endif |
| 174 |
| 175 UnifiedCache::~UnifiedCache() { |
| 176 // Try our best to clean up first. |
| 177 flush(); |
| 178 { |
| 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); |
| 183 _flush(TRUE); |
| 184 } |
| 185 uhash_close(fHashtable); |
| 186 } |
| 187 |
| 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 = -1; |
| 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(); |
| 204 result = TRUE; |
| 205 } |
| 206 } |
| 207 return result; |
| 208 } |
| 209 |
| 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)) { |
| 220 return; |
| 221 } |
| 222 CacheKeyBase *keyToAdopt = key.clone(); |
| 223 if (keyToAdopt == NULL) { |
| 224 status = U_MEMORY_ALLOCATION_ERROR; |
| 225 return; |
| 226 } |
| 227 keyToAdopt->creationStatus = creationStatus; |
| 228 uhash_put(fHashtable, keyToAdopt, (void *) value, &status); |
| 229 if (U_SUCCESS(status)) { |
| 230 value->addSoftRef(); |
| 231 } |
| 232 } |
| 233 |
| 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 |
| 236 // status there. |
| 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); |
| 251 return; |
| 252 } |
| 253 if (element == NULL) { |
| 254 UErrorCode putError = U_ZERO_ERROR; |
| 255 // best-effort basis only. |
| 256 _putNew(key, value, status, putError); |
| 257 return; |
| 258 } |
| 259 _put(element, value, status); |
| 260 } |
| 261 |
| 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 |
| 264 // be U_ZERO_ERROR. |
| 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); |
| 282 } |
| 283 if (element != NULL) { |
| 284 _fetch(element, value, status); |
| 285 return TRUE; |
| 286 } |
| 287 _putNew(key, gNoValue, U_ZERO_ERROR, status); |
| 288 return FALSE; |
| 289 } |
| 290 |
| 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); |
| 309 } |
| 310 return; |
| 311 } |
| 312 if (U_FAILURE(status)) { |
| 313 return; |
| 314 } |
| 315 value = key.createObject(creationContext, status); |
| 316 U_ASSERT(value == NULL || !value->allSoftReferences()); |
| 317 U_ASSERT(value != NULL || status != U_ZERO_ERROR); |
| 318 if (value == NULL) { |
| 319 SharedObject::copyPtr(gNoValue, value); |
| 320 } |
| 321 _putIfAbsentAndGet(key, value, status); |
| 322 if (value == gNoValue) { |
| 323 SharedObject::clearPtr(value); |
| 324 } |
| 325 } |
| 326 |
| 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 |
| 332 // threads notified. |
| 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; |
| 341 value->addSoftRef(); |
| 342 UHashElement *ptr = const_cast<UHashElement *>(element); |
| 343 ptr->value.pointer = (void *) value; |
| 344 oldValue->removeSoftRef(); |
| 345 |
| 346 // Tell waiting threads that we replace in-progress status with |
| 347 // an error. |
| 348 umtx_condBroadcast(&gInProgressValueAddedCond); |
| 349 } |
| 350 |
| 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); |
| 366 } |
| 367 |
| 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); |
| 376 return result; |
| 377 } |
| 378 |
| 379 U_NAMESPACE_END |
OLD | NEW |