forked from lix-project/lix
24b739817f
It was holding on to a Value* (i.e. a std::shared_ptr<ValidPathInfo>*) outside of the pathInfoCache lock, so the std::shared_ptr could be destroyed between the release of the lock and the decrement of the std::shared_ptr refcount. This can happen if more than 'path-info-cache-size' paths are added in the meantime, *or* if clearPathInfoCache() is called. The hydra-queue-runner queue monitor thread periodically calls the later, so is likely to trigger a crash. Fixes https://github.com/NixOS/hydra/issues/542.
93 lines
1.9 KiB
C++
93 lines
1.9 KiB
C++
#pragma once
|
|
|
|
#include <map>
|
|
#include <list>
|
|
#include <experimental/optional>
|
|
|
|
namespace nix {
|
|
|
|
/* A simple least-recently used cache. Not thread-safe. */
|
|
template<typename Key, typename Value>
|
|
class LRUCache
|
|
{
|
|
private:
|
|
|
|
size_t capacity;
|
|
|
|
// Stupid wrapper to get around circular dependency between Data
|
|
// and LRU.
|
|
struct LRUIterator;
|
|
|
|
using Data = std::map<Key, std::pair<LRUIterator, Value>>;
|
|
using LRU = std::list<typename Data::iterator>;
|
|
|
|
struct LRUIterator { typename LRU::iterator it; };
|
|
|
|
Data data;
|
|
LRU lru;
|
|
|
|
public:
|
|
|
|
LRUCache(size_t capacity) : capacity(capacity) { }
|
|
|
|
/* Insert or upsert an item in the cache. */
|
|
void upsert(const Key & key, const Value & value)
|
|
{
|
|
if (capacity == 0) return;
|
|
|
|
erase(key);
|
|
|
|
if (data.size() >= capacity) {
|
|
/* Retire the oldest item. */
|
|
auto oldest = lru.begin();
|
|
data.erase(*oldest);
|
|
lru.erase(oldest);
|
|
}
|
|
|
|
auto res = data.emplace(key, std::make_pair(LRUIterator(), value));
|
|
assert(res.second);
|
|
auto & i(res.first);
|
|
|
|
auto j = lru.insert(lru.end(), i);
|
|
|
|
i->second.first.it = j;
|
|
}
|
|
|
|
bool erase(const Key & key)
|
|
{
|
|
auto i = data.find(key);
|
|
if (i == data.end()) return false;
|
|
lru.erase(i->second.first.it);
|
|
data.erase(i);
|
|
return true;
|
|
}
|
|
|
|
/* Look up an item in the cache. If it exists, it becomes the most
|
|
recently used item. */
|
|
std::experimental::optional<Value> get(const Key & key)
|
|
{
|
|
auto i = data.find(key);
|
|
if (i == data.end()) return {};
|
|
|
|
/* Move this item to the back of the LRU list. */
|
|
lru.erase(i->second.first.it);
|
|
auto j = lru.insert(lru.end(), i);
|
|
i->second.first.it = j;
|
|
|
|
return i->second.second;
|
|
}
|
|
|
|
size_t size()
|
|
{
|
|
return data.size();
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
data.clear();
|
|
lru.clear();
|
|
}
|
|
};
|
|
|
|
}
|