From e5ecd73e063311e4a6145baaa3e358628b1caed4 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 31 Aug 2015 01:04:43 +0100 Subject: Migrate cache from libmisc and add covering tests, some minor renaming and refactoring --- libadhocutil/cache.h | 83 +++++++++++++++++++++++ libadhocutil/cache.impl.h | 127 +++++++++++++++++++++++++++++++++++ libadhocutil/unittests/Jamfile.jam | 12 ++++ libadhocutil/unittests/testCache.cpp | 79 ++++++++++++++++++++++ 4 files changed, 301 insertions(+) create mode 100644 libadhocutil/cache.h create mode 100644 libadhocutil/cache.impl.h create mode 100644 libadhocutil/unittests/testCache.cpp diff --git a/libadhocutil/cache.h b/libadhocutil/cache.h new file mode 100644 index 0000000..8cd7903 --- /dev/null +++ b/libadhocutil/cache.h @@ -0,0 +1,83 @@ +#ifndef NETFS_FUSE_CACHE_H +#define NETFS_FUSE_CACHE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +template +class Cacheable { + public: + Cacheable(const K & k, time_t validUntil); + + const K key; + const time_t validUntil; + + virtual const T & item() const = 0; +}; + +template +class ObjectCacheable : public Cacheable { + public: + ObjectCacheable(const T & t, const K & k, time_t validUtil); + + virtual const T & item() const override; + + private: + const T value; +}; + +template +class CallCacheable : public Cacheable { + public: + CallCacheable(const T & t, const K & k, time_t validUtil); + CallCacheable(const boost::function & t, const K & k, time_t validUtil); + + virtual const T & item() const override; + + private: + mutable boost::variant> value; + mutable boost::shared_mutex lock; +}; + +struct byValidity {}; +struct byKey {}; +template +class Cache { + public: + typedef K Key; + typedef T Value; + typedef Cacheable Item; + typedef boost::shared_ptr Element; + + Cache(); + + void add(const K & k, const T & t, time_t validUntil); + void add(const K & k, const boost::function & tf, time_t validUntil); + Element getItem(const K & k) const; + const T * get(const K & k) const; + size_t size() const; + + private: + void prune() const; + mutable time_t lastPruneTime; + + mutable boost::shared_mutex lock; + + typedef boost::multi_index::multi_index_container, BOOST_MULTI_INDEX_MEMBER(Item, const K, key)>, + boost::multi_index::ordered_non_unique< + boost::multi_index::tag, BOOST_MULTI_INDEX_MEMBER(Item, const time_t, validUntil)> + > > Cached; + mutable Cached cached; +}; + +#endif + diff --git a/libadhocutil/cache.impl.h b/libadhocutil/cache.impl.h new file mode 100644 index 0000000..43fcd1a --- /dev/null +++ b/libadhocutil/cache.impl.h @@ -0,0 +1,127 @@ +#include "cache.h" +#include +#include "lockHelpers.h" + +template +Cacheable::Cacheable(const K & k, time_t vu) : + key(k), + validUntil(vu) +{ +} + +template +ObjectCacheable::ObjectCacheable(const T & t, const K & k, time_t vu) : + Cacheable(k, vu), + value(t) +{ +} + +template +const T & +ObjectCacheable::item() const +{ + return value; +} + +template +CallCacheable::CallCacheable(const T & t, const K & k, time_t vu) : + Cacheable(k, vu), + value(t) +{ +} + +template +CallCacheable::CallCacheable(const boost::function & t, const K & k, time_t vu) : + Cacheable(k, vu), + value(t) +{ +} + +template +const T & +CallCacheable::item() const +{ + Lock(lock); + const T * t = boost::get(&value); + if (t) { + return *t; + } + const boost::function & f = boost::get>(value); + value = f(); + return boost::get(value); +} + + +template +Cache::Cache() : + lastPruneTime(time(NULL)) +{ +} + +template +void +Cache::add(const K & k, const T & t, time_t validUntil) +{ + Lock(lock); + cached.insert(Element(new ObjectCacheable(t, k, validUntil))); +} + +template +void +Cache::add(const K & k, const boost::function & tf, time_t validUntil) +{ + Lock(lock); + cached.insert(Element(new CallCacheable(tf, k, validUntil))); +} + +template +typename Cache::Element +Cache::getItem(const K & k) const +{ + { + SharedLock(lock); + auto & collection = cached.template get(); + auto i = collection.find(k); + if (i == collection.end()) { + return Element(); + } + if ((*i)->validUntil > time(NULL)) { + return (*i); + } + } + prune(); + return Element(); +} + +template +const T * +Cache::get(const K & k) const +{ + auto i = getItem(k); + if (i) { + return &i->item(); + } + return nullptr; +} + +template +size_t +Cache::size() const +{ + return cached.size(); +} + +template +void +Cache::prune() const +{ + auto now = time(NULL); + if (lastPruneTime < now) { + Lock(lock); + auto & collection = cached.template get(); + auto range = collection.range(boost::multi_index::unbounded, boost::lambda::_1 < now); + collection.erase(range.first, range.second); + lastPruneTime = now; + } +} + diff --git a/libadhocutil/unittests/Jamfile.jam b/libadhocutil/unittests/Jamfile.jam index 7b2720e..8275cbb 100644 --- a/libadhocutil/unittests/Jamfile.jam +++ b/libadhocutil/unittests/Jamfile.jam @@ -107,3 +107,15 @@ run testNvpParse ; +run + testCache.cpp + : : : + BOOST_TEST_DYN_LINK + ..//adhocutil + boost_utf + boost_system + boost_thread + : + testCache + ; + diff --git a/libadhocutil/unittests/testCache.cpp b/libadhocutil/unittests/testCache.cpp new file mode 100644 index 0000000..da6df69 --- /dev/null +++ b/libadhocutil/unittests/testCache.cpp @@ -0,0 +1,79 @@ +#define BOOST_TEST_MODULE Cache +#include + +#include +#include "cache.h" +#include "cache.impl.h" + +typedef Cache TestCache; +template class Cache; +template class Cacheable; +template class ObjectCacheable; +template class CallCacheable; + +namespace std { + std::ostream & operator<<(std::ostream & s, const std::nullptr_t &) + { + return s << "(nil)"; + } +} + +BOOST_AUTO_TEST_CASE( miss ) +{ + TestCache tc; + BOOST_REQUIRE_EQUAL(0, tc.size()); + tc.add("key", 3, time(NULL) + 5); + BOOST_REQUIRE_EQUAL(1, tc.size()); + BOOST_REQUIRE_EQUAL(nullptr, tc.get("anything")); + BOOST_REQUIRE_EQUAL(nullptr, tc.getItem("anything")); + BOOST_REQUIRE_EQUAL(1, tc.size()); +} + +BOOST_AUTO_TEST_CASE( hit ) +{ + TestCache tc; + auto vu = time(NULL) + 5; + tc.add("key", 3, vu); + BOOST_REQUIRE_EQUAL(1, tc.size()); + BOOST_REQUIRE_EQUAL(3, *tc.get("key")); + BOOST_REQUIRE_EQUAL(3, tc.getItem("key")->item()); + BOOST_REQUIRE_EQUAL(vu, tc.getItem("key")->validUntil); + BOOST_REQUIRE_EQUAL("key", tc.getItem("key")->key); + BOOST_REQUIRE_EQUAL(1, tc.size()); +} + +BOOST_AUTO_TEST_CASE( expired ) +{ + TestCache tc; + tc.add("miss", 3, time(NULL) - 5); + tc.add("hit", 3, time(NULL) + 5); + // We only prune once a second... so size() should stay at 1 + BOOST_REQUIRE_EQUAL(2, tc.size()); + BOOST_REQUIRE_EQUAL(nullptr, tc.get("miss")); + BOOST_REQUIRE_EQUAL(nullptr, tc.getItem("miss")); + BOOST_REQUIRE(tc.get("hit")); + BOOST_REQUIRE(tc.getItem("hit")); + BOOST_REQUIRE_EQUAL(2, tc.size()); + sleep(1); + // Should prune now... + BOOST_REQUIRE_EQUAL(2, tc.size()); + BOOST_REQUIRE(tc.get("hit")); + BOOST_REQUIRE_EQUAL(2, tc.size()); + BOOST_REQUIRE_EQUAL(nullptr, tc.get("miss")); + BOOST_REQUIRE_EQUAL(1, tc.size()); +} + +BOOST_AUTO_TEST_CASE( callcache ) +{ + TestCache tc; + int callCount = 0; + auto vu = time(NULL) + 5; + BOOST_REQUIRE_EQUAL(nullptr, tc.get("key")); + tc.add("key", [&callCount]{ callCount++; return 3; }, vu); + BOOST_REQUIRE_EQUAL(0, callCount); + BOOST_REQUIRE_EQUAL(3, *tc.get("key")); + BOOST_REQUIRE_EQUAL(1, callCount); + BOOST_REQUIRE_EQUAL(3, *tc.get("key")); + BOOST_REQUIRE_EQUAL(1, callCount); +} + -- cgit v1.2.3