summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libadhocutil/cache.h83
-rw-r--r--libadhocutil/cache.impl.h127
-rw-r--r--libadhocutil/unittests/Jamfile.jam12
-rw-r--r--libadhocutil/unittests/testCache.cpp79
4 files changed, 301 insertions, 0 deletions
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 <time.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/thread/shared_mutex.hpp>
+#include <boost/variant.hpp>
+
+template <typename T, typename K>
+class Cacheable {
+ public:
+ Cacheable(const K & k, time_t validUntil);
+
+ const K key;
+ const time_t validUntil;
+
+ virtual const T & item() const = 0;
+};
+
+template <typename T, typename K>
+class ObjectCacheable : public Cacheable<T, K> {
+ public:
+ ObjectCacheable(const T & t, const K & k, time_t validUtil);
+
+ virtual const T & item() const override;
+
+ private:
+ const T value;
+};
+
+template <typename T, typename K>
+class CallCacheable : public Cacheable<T, K> {
+ public:
+ CallCacheable(const T & t, const K & k, time_t validUtil);
+ CallCacheable(const boost::function<T()> & t, const K & k, time_t validUtil);
+
+ virtual const T & item() const override;
+
+ private:
+ mutable boost::variant<T, boost::function<T()>> value;
+ mutable boost::shared_mutex lock;
+};
+
+struct byValidity {};
+struct byKey {};
+template <typename T, typename K>
+class Cache {
+ public:
+ typedef K Key;
+ typedef T Value;
+ typedef Cacheable<T, K> Item;
+ typedef boost::shared_ptr<Item> Element;
+
+ Cache();
+
+ void add(const K & k, const T & t, time_t validUntil);
+ void add(const K & k, const boost::function<T()> & 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<Element,
+ boost::multi_index::indexed_by<
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<byKey>, BOOST_MULTI_INDEX_MEMBER(Item, const K, key)>,
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<byValidity>, 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 <boost/lambda/lambda.hpp>
+#include "lockHelpers.h"
+
+template<typename T, typename K>
+Cacheable<T, K>::Cacheable(const K & k, time_t vu) :
+ key(k),
+ validUntil(vu)
+{
+}
+
+template<typename T, typename K>
+ObjectCacheable<T, K>::ObjectCacheable(const T & t, const K & k, time_t vu) :
+ Cacheable<T, K>(k, vu),
+ value(t)
+{
+}
+
+template<typename T, typename K>
+const T &
+ObjectCacheable<T, K>::item() const
+{
+ return value;
+}
+
+template<typename T, typename K>
+CallCacheable<T, K>::CallCacheable(const T & t, const K & k, time_t vu) :
+ Cacheable<T, K>(k, vu),
+ value(t)
+{
+}
+
+template<typename T, typename K>
+CallCacheable<T, K>::CallCacheable(const boost::function<T()> & t, const K & k, time_t vu) :
+ Cacheable<T, K>(k, vu),
+ value(t)
+{
+}
+
+template<typename T, typename K>
+const T &
+CallCacheable<T, K>::item() const
+{
+ Lock(lock);
+ const T * t = boost::get<T>(&value);
+ if (t) {
+ return *t;
+ }
+ const boost::function<T()> & f = boost::get<boost::function<T()>>(value);
+ value = f();
+ return boost::get<T>(value);
+}
+
+
+template<typename T, typename K>
+Cache<T, K>::Cache() :
+ lastPruneTime(time(NULL))
+{
+}
+
+template<typename T, typename K>
+void
+Cache<T, K>::add(const K & k, const T & t, time_t validUntil)
+{
+ Lock(lock);
+ cached.insert(Element(new ObjectCacheable<T, K>(t, k, validUntil)));
+}
+
+template<typename T, typename K>
+void
+Cache<T, K>::add(const K & k, const boost::function<T()> & tf, time_t validUntil)
+{
+ Lock(lock);
+ cached.insert(Element(new CallCacheable<T, K>(tf, k, validUntil)));
+}
+
+template<typename T, typename K>
+typename Cache<T, K>::Element
+Cache<T, K>::getItem(const K & k) const
+{
+ {
+ SharedLock(lock);
+ auto & collection = cached.template get<byKey>();
+ auto i = collection.find(k);
+ if (i == collection.end()) {
+ return Element();
+ }
+ if ((*i)->validUntil > time(NULL)) {
+ return (*i);
+ }
+ }
+ prune();
+ return Element();
+}
+
+template<typename T, typename K>
+const T *
+Cache<T, K>::get(const K & k) const
+{
+ auto i = getItem(k);
+ if (i) {
+ return &i->item();
+ }
+ return nullptr;
+}
+
+template<typename T, typename K>
+size_t
+Cache<T, K>::size() const
+{
+ return cached.size();
+}
+
+template<typename T, typename K>
+void
+Cache<T, K>::prune() const
+{
+ auto now = time(NULL);
+ if (lastPruneTime < now) {
+ Lock(lock);
+ auto & collection = cached.template get<byValidity>();
+ 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
+ : : :
+ <define>BOOST_TEST_DYN_LINK
+ <library>..//adhocutil
+ <library>boost_utf
+ <library>boost_system
+ <library>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 <boost/test/unit_test.hpp>
+
+#include <boost/bind.hpp>
+#include "cache.h"
+#include "cache.impl.h"
+
+typedef Cache<int, std::string> TestCache;
+template class Cache<int, std::string>;
+template class Cacheable<int, std::string>;
+template class ObjectCacheable<int, std::string>;
+template class CallCacheable<int, std::string>;
+
+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);
+}
+