// ********************************************************************** // // Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved. // // This copy of Ice is licensed to you under the terms described in the // ICE_LICENSE file included in this distribution. // // ********************************************************************** #ifndef ICE_UTIL_CACHE_H #define ICE_UTIL_CACHE_H #include #include #include #include namespace IceUtil { // // An abstraction to efficiently populate a Cache, without holding // a lock while loading from a database. // template class Cache { public: // // Latch and CacheValue are implementation details; // application code should not use them. // struct Latch : public IceUtilInternal::CountDownLatch { Latch() : IceUtilInternal::CountDownLatch(1), useCount(0) { } int useCount; }; struct CacheValue { CacheValue(const Handle& o) : obj(o), latch(0) { } Handle obj; Latch* latch; }; typedef typename std::map::iterator Position; Handle getIfPinned(const Key&, bool = false) const; void unpin(Position); void clear(); size_t size() const; bool pin(const Key&, const Handle&); Handle pin(const Key&); Handle putIfAbsent(const Key&, const Handle&); protected: virtual Handle load(const Key&) = 0; virtual void pinned(const Handle&, Position) { } virtual ~Cache() { } private: Handle pinImpl(const Key&, const Handle&); typedef std::map CacheMap; Mutex _mutex; CacheMap _map; }; template Handle Cache::getIfPinned(const Key& key, bool wait) const { Mutex::Lock sync(_mutex); for(;;) { typename CacheMap::const_iterator p = _map.find(key); if(p != _map.end()) { Handle result = (*p).second.obj; if(result != 0 || wait == false) { return result; } // // The object is being loaded: we wait // if(p->second.latch == 0) { const_cast(p->second).latch = new Latch; } Latch* latch = p->second.latch; ++latch->useCount; sync.release(); latch->await(); sync.acquire(); if(--latch->useCount == 0) { delete latch; } // // Try again // } else { return 0; } } } template void Cache::unpin(typename Cache::Position p) { // // There is no risk to erase a 'being loaded' position, // since such position never got outside yet! // Mutex::Lock sync(_mutex); _map.erase(p); } template void Cache::clear() { // // Not safe during a pin! // Mutex::Lock sync(_mutex); _map.clear(); } template size_t Cache::size() const { Mutex::Lock sync(_mutex); return _map.size(); } template bool Cache::pin(const Key& key, const Handle& obj) { Mutex::Lock sync(_mutex); std::pair ir = #ifdef _MSC_VER _map.insert(CacheMap::value_type(key, CacheValue(obj))); #else _map.insert(typename CacheMap::value_type(key, CacheValue(obj))); #endif if(ir.second) { pinned(obj, ir.first); } return ir.second; } template Handle Cache::pin(const Key& key) { return pinImpl(key, 0); } template Handle Cache::putIfAbsent(const Key& key, const Handle& obj) { return pinImpl(key, obj); } template Handle Cache::pinImpl(const Key& key, const Handle& newObj) { Latch* latch = 0; Position p; do { { Mutex::Lock sync(_mutex); // // Clean up latch from previous loop // if(latch != 0) { if(--latch->useCount == 0) { delete latch; } latch = 0; } std::pair ir = #if defined(_MSC_VER) _map.insert(CacheMap::value_type(key, CacheValue(0))); #else _map.insert(typename CacheMap::value_type(key, CacheValue(0))); #endif if(ir.second == false) { CacheValue& val = ir.first->second; if(val.obj != 0) { return val.obj; } // // Otherwise wait // if(val.latch == 0) { // // The first queued thread creates the latch // val.latch = new Latch; } latch = val.latch; latch->useCount++; } p = ir.first; } if(latch != 0) { // // Note: only the threads owning a "useCount" wait; upon wake-up, // they loop back, release this useCount and possibly delete the latch // latch->await(); // // p could be stale now, e.g. some other thread pinned and unpinned the // object while we were waiting. // So start over. } } while(latch != 0); // // Load // Handle obj; try { obj = load(key); } catch(...) { Mutex::Lock sync(_mutex); latch = p->second.latch; p->second.latch = 0; _map.erase(p); if(latch != 0) { // // It is necessary to call countDown() within the sync // because countDown may not be atomic, and we don't // want the "await" thread to delete the latch while // this thread is still in countDown(). // assert(latch->getCount() == 1); latch->countDown(); } throw; } Mutex::Lock sync(_mutex); // // p is still valid here -- nobody knows about it. See also unpin(). // latch = p->second.latch; p->second.latch = 0; try { if(obj != 0) { p->second.obj = obj; pinned(obj, p); } else { if(newObj == 0) { // // pin() did not find the object // // // The waiting threads will have to call load() to see by themselves. // _map.erase(p); } else { // // putIfAbsent() inserts key/newObj // p->second.obj = newObj; pinned(newObj, p); } } } catch(...) { if(latch != 0) { // // Must be called within sync; see ->countDown() note above. // assert(latch->getCount() == 1); latch->countDown(); } throw; } if(latch != 0) { // // Must be called within sync; see ->countDown() note above. // assert(latch->getCount() == 1); latch->countDown(); } return obj; } } #endif