diff options
| -rw-r--r-- | libadhocutil/cache.h | 83 | ||||
| -rw-r--r-- | libadhocutil/cache.impl.h | 127 | ||||
| -rw-r--r-- | libadhocutil/unittests/Jamfile.jam | 12 | ||||
| -rw-r--r-- | libadhocutil/unittests/testCache.cpp | 79 | 
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); +} + | 
