#define CPPCMS_SOURCE
#include "config.h"
#include "cache_storage.h"
#ifdef CPPCMS_USE_EXTERNAL_BOOST
# include <boost/thread.hpp>
#else // Internal Boost
# include <cppcms_boost/thread.hpp>
namespace boost = cppcms_boost;
#endif
#ifndef CPPCMS_WIN32
# include "posix_util.h"
# include "shmem_allocator.h"
#endif
#include <map>
#include <list>
#include <limits>
#include <iostream>
#include "cstdint.h"
namespace cppcms {
namespace impl {
template<typename String>
struct copy_traits {
static String to_int(std::string const &other)
{
return String(other.c_str(),other.size());
}
static std::string to_std(String const &other)
{
return std::string(other.c_str(),other.size());
}
};
template<>
struct copy_traits<std::string>
{
static std::string to_int(std::string const &a) { return a; }
static std::string to_std(std::string const &a) { return a; }
};
#ifndef CPPCMS_WIN32
struct process_settings {
static shmem_control *process_memory;
typedef ::cppcms::impl::mutex mutex_type;
typedef ::cppcms::impl::shared_mutex shared_mutex_type;
typedef mutex_type::guard lock_guard;
typedef shared_mutex_type::shared_guard rdlock_guard;
typedef shared_mutex_type::unique_guard wrlock_guard;
typedef shmem_allocator<char,process_memory> allocator;
static bool not_enough_memory()
{
size_t mem_size = process_memory->size() / 10;
void *p=process_memory->malloc(mem_size);
if(!p) {
return true;
}
process_memory->free(p);
return false;
}
static size_t size_limit()
{
return process_memory->size() / 20;
}
static void init(size_t size)
{
if(process_memory)
return;
process_memory=new shmem_control(size);
}
static const bool process_shared = true;
};
shmem_control *process_settings::process_memory=0;
#endif
struct thread_settings {
typedef boost::mutex mutex_type;
typedef boost::shared_mutex shared_mutex_type;
typedef boost::lock_guard<boost::mutex> lock_guard;
typedef boost::shared_lock<boost::shared_mutex> rdlock_guard;
typedef boost::unique_lock<boost::shared_mutex> wrlock_guard;
typedef std::allocator<char> allocator;
static bool not_enough_memory() { return false; }
static size_t size_limit() { return std::numeric_limits<size_t>::max(); }
static const bool process_shared = false;
};
template<typename Setup>
class mem_cache : public base_cache {
typedef typename Setup::mutex_type mutex_type;
typedef typename Setup::shared_mutex_type shared_mutex_type;
std::auto_ptr<mutex_type> lru_mutex;
std::auto_ptr<shared_mutex_type> access_lock;
typedef typename Setup::allocator allocator;
typedef typename Setup::lock_guard lock_guard;
typedef typename Setup::rdlock_guard rdlock_guard;
typedef typename Setup::wrlock_guard wrlock_guard;
typedef typename allocator::template rebind<mem_cache>::other this_allocator;
bool not_enough_memory()
{
return Setup::not_enough_memory();
}
size_t size_limit()
{
return Setup::size_limit();
}
struct container;
typedef std::basic_string<char,std::char_traits<char>,allocator > string_type;
typedef std::map<
string_type,
container,
std::less<string_type>,
typename allocator::template rebind<std::pair<string_type,container> >::other
> map_type;
typedef typename map_type::iterator pointer;
typedef std::list<
pointer,
typename allocator::template rebind<std::pair<string_type,container> >::other
> lru_list_type;
typedef std::multimap<
string_type,
pointer,
std::less<string_type>,
typename allocator::template rebind<std::pair<string_type,pointer> >::other
> triggers_map_type;
typedef std::list<
typename triggers_map_type::iterator,
typename allocator::template rebind<typename triggers_map_type::iterator>::other
> triggers_list_type;
typedef std::multimap<
time_t,
pointer,
std::less<time_t>,
typename allocator::template rebind<std::pair<time_t,pointer> >::other
> timeout_mmap_type;
struct container {
string_type data;
typedef typename map_type::iterator pointer;
typename lru_list_type::iterator lru;
triggers_list_type triggers;
typename timeout_mmap_type::iterator timeout;
uint64_t generation;
};
map_type primary;
triggers_map_type triggers;
typedef typename triggers_map_type::iterator triggers_ptr;
timeout_mmap_type timeout;
typedef typename timeout_mmap_type::iterator timeout_ptr;
lru_list_type lru;
typedef typename lru_list_type::iterator lru_ptr;
unsigned limit;
size_t size;
int refs;
uint64_t generation;
string_type to_int(std::string const &other)
{
return copy_traits<string_type>::to_int(other);
}
std::string to_std(string_type const &other)
{
return copy_traits<string_type>::to_std(other);
}
void delete_node(pointer p)
{
lru.erase(p->second.lru);
timeout.erase(p->second.timeout);
typename triggers_list_type::iterator i;
for(i=p->second.triggers.begin();i!=p->second.triggers.end();i++) {
triggers.erase(*i);
}
primary.erase(p);
size--;
}
public:
mem_cache(unsigned pages=0) :
lru_mutex(new mutex_type()),
access_lock(new shared_mutex_type()),
limit(pages),
size(0),
refs(0),
generation(0)
{
}
~mem_cache()
{
}
void set_size(unsigned l) { limit=l; };
virtual bool fetch(std::string const &key,std::string *a,std::set<std::string> *triggers,time_t *timeout_out,uint64_t *gen)
{
rdlock_guard lock(*access_lock);
pointer p;
time_t now;
time(&now);
if((p=primary.find(to_int(key)))==primary.end() || p->second.timeout->first < now) {
return false;
}
{ // Update LRU
lock_guard lock(*lru_mutex);
lru.erase(p->second.lru);
lru.push_front(p);
p->second.lru=lru.begin();
}
if(a)
*a=to_std(p->second.data);
if(triggers) {
typename triggers_list_type::iterator tp;
for(tp=p->second.triggers.begin();tp!=p->second.triggers.end();tp++) {
triggers->insert(to_std((*tp)->first));
}
}
if(timeout_out) {
*timeout_out=p->second.timeout->first;
}
if(gen)
*gen=p->second.generation;
return true;
}
virtual void rise(std::string const &trigger)
{
wrlock_guard lock(*access_lock);
std::pair<triggers_ptr,triggers_ptr> range=triggers.equal_range(to_int(trigger));
triggers_ptr p;
std::list<pointer> kill_list;
for(p=range.first;p!=range.second;p++) {
kill_list.push_back(p->second);
}
typename std::list<pointer>::iterator lptr;
for(lptr=kill_list.begin();lptr!=kill_list.end();lptr++) {
delete_node(*lptr);
}
}
void nl_clear()
{
timeout.clear();
lru.clear();
primary.clear();
triggers.clear();
size = 0;
}
virtual void clear()
{
wrlock_guard lock(*access_lock);
nl_clear();
}
virtual void stats(unsigned &keys,unsigned &triggers)
{
rdlock_guard lock(*access_lock);
keys=size;
triggers=this->triggers.size();
}
void check_limits()
{
pointer main=primary.end();
time_t now;
time(&now);
while(size > 0 && (not_enough_memory() || (size>=limit && limit>0)))
{
if(!timeout.empty() && timeout.begin()->first<now) {
main=timeout.begin()->second;
}
else if(!lru.empty()){
main=lru.back();
}
else
break;
delete_node(main);
}
}
virtual void remove(std::string const &key)
{
wrlock_guard lock(*access_lock);
pointer p=primary.find(to_int(key));
if(p==primary.end())
return;
delete_node(p);
}
virtual void store(std::string const &key,std::string const &a,std::set<std::string> const &triggers_in,time_t timeout_in)
{
wrlock_guard lock(*access_lock);
try {
pointer main;
main=primary.find(to_int(key));
if(main!=primary.end())
delete_node(main);
if(size > size_limit())
return;
check_limits();
string_type int_key = to_int(key);
std::pair<pointer,bool> res=primary.insert(std::pair<string_type,container>(int_key,container()));
size ++;
main=res.first;
container &cont=main->second;
cont.data=to_int(a);
cont.generation=generation++;
lru.push_front(main);
cont.lru=lru.begin();
cont.timeout=timeout.insert(std::pair<time_t,pointer>(timeout_in,main));
if(triggers_in.find(key)==triggers_in.end()){
cont.triggers.push_back(triggers.insert(std::pair<string_type,pointer>(int_key,main)));
}
std::set<std::string>::const_iterator si;
for(si=triggers_in.begin();si!=triggers_in.end();si++) {
cont.triggers.push_back(triggers.insert(std::pair<string_type,pointer>(to_int(*si),main)));
}
}
catch(std::bad_alloc const &e)
{
nl_clear();
}
}
virtual void add_ref()
{
if(Setup::process_shared)
return;
wrlock_guard lock(*access_lock);
refs++;
}
virtual bool del_ref()
{
// This object should not be deleted because it is created only once
// and exists in memory
if(Setup::process_shared)
return false;
wrlock_guard lock(*access_lock);
refs--;
if(refs==0)
return true;
return false;
}
void *operator new(size_t /*n*/)
{
return this_allocator().allocate(1);
}
void operator delete(void *p)
{
this_allocator().deallocate(reinterpret_cast<mem_cache *>(p),1);
}
}; // mem cache
intrusive_ptr<base_cache> thread_cache_factory(unsigned items)
{
return new mem_cache<thread_settings>(items);
}
#ifndef CPPCMS_WIN32
intrusive_ptr<base_cache> process_cache_factory(size_t memory)
{
process_settings::init(memory);
return new mem_cache<process_settings>(0);
}
#endif
} // impl
} // cppcms