]>
Commit | Line | Data |
---|---|---|
b331163b A |
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 |