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 = UHASH_FIRST; 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 references\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 = 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(); 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 380