#include "process_cache.h"
#include <boost/format.hpp>
#include <unistd.h>
#ifdef HAVE_PTHREADS_PSHARED
# include "posix_mutex.h"
#else
# include "fcntl_mutex.h"
using namespace cppcms::fcntl;
#endif
#include <errno.h>
#include <iostream>
using boost::format;
using boost::str;
namespace cppcms {
shmem_control *process_cache_factory::mem(NULL);
::pid_t process_cache_factory::owner_pid(0);
process_cache_factory::process_cache_factory(size_t memsize,char const *file)
{
cache=NULL;
if(memsize<8*1024) {
throw cppcms_error("Cache size too small -- need at least 8K");
}
if(!mem) {
mem=new shmem_control(memsize,file);
owner_pid=getpid();
}
else {
throw cppcms_error("The memory initilized -- can't use more then once cache in same time");
}
cache=new process_cache(memsize);
};
process_cache_factory::~process_cache_factory()
{
// Only parent process can kill memory
// forked childs should never do it.
if(owner_pid==getpid()) {
delete cache;
delete mem;
mem=NULL;
}
}
base_cache *process_cache_factory::get() const
{
return cache;
};
void process_cache_factory::del(base_cache *p) const
{
};
process_cache::process_cache(size_t m) :
memsize(m)
{
#ifdef HAVE_PTHREADS_PSHARED
pthread_mutexattr_t a;
pthread_rwlockattr_t al;
if(
pthread_mutexattr_init(&a)
|| pthread_mutexattr_setpshared(&a,PTHREAD_PROCESS_SHARED)
|| pthread_mutex_init(&lru_mutex,&a)
|| pthread_mutexattr_destroy(&a)
|| pthread_rwlockattr_init(&al)
|| pthread_rwlockattr_setpshared(&al,PTHREAD_PROCESS_SHARED)
|| pthread_rwlock_init(&access_lock,&al)
|| pthread_rwlockattr_destroy(&al))
{
throw cppcms_error(errno,"Failed setup mutexes --- is this system "
"supports process shared mutex/rwlock?");
}
#else
if((lru_mutex=tmpfile())==NULL || (access_lock=tmpfile())==NULL) {
throw cppcms_error(errno,"Failed to create temporary file");
}
#endif
};
process_cache::~process_cache()
{
#ifdef HAVE_PTHREADS_PSHARED
pthread_mutex_destroy(&lru_mutex);
pthread_rwlock_destroy(&access_lock);
#else
fclose(lru_mutex);
fclose(access_lock);
#endif
}
process_cache::shr_string *process_cache::get(string const &key,set<string> *triggers)
{
pointer p;
time_t now;
time(&now);
if((p=primary.find(key.c_str()))==primary.end() || p->second.timeout->first < now) {
return NULL;
}
if(triggers) {
list<triggers_ptr>::iterator tp;
for(tp=p->second.triggers.begin();tp!=p->second.triggers.end();tp++) {
triggers->insert((*tp)->first.c_str());
}
}
{
mutex_lock lock(lru_mutex);
lru.erase(p->second.lru);
lru.push_front(p);
p->second.lru=lru.begin();
}
return &(p->second.data);
}
namespace {
template<typename T>
T unaligned(T const *p)
{
T tmp;
memcpy(&tmp,p,sizeof(T));
return tmp;
}
}
bool process_cache::fetch_page(string const &key,string &out,bool gzip)
{
rwlock_rdlock lock(access_lock);
shr_string *r=get(key,NULL);
if(!r) return false;
size_t size=r->size();
size_t s;
char const *ptr=r->c_str();
if(size<sizeof(size_t) || (s=unaligned((size_t const *)ptr))>size-sizeof(size_t))
return false;
if(!gzip){
out.assign(ptr+sizeof(size_t),s);
}
else {
ptr+=s+sizeof(size_t);
size-=s+sizeof(size_t);
if(size<sizeof(size_t) || (s=unaligned((size_t const *)ptr))!=size-sizeof(size_t))
return false;
out.assign(ptr+sizeof(size_t),s);
}
return true;
}
bool process_cache::fetch(string const &key,archive &a,set<string> &tags)
{
rwlock_rdlock lock(access_lock);
shr_string *r=get(key,&tags);
if(!r) return false;
a.set(r->c_str(),r->size());
return true;
}
void process_cache::clear()
{
rwlock_wrlock lock(access_lock);
timeout.clear();
lru.clear();
primary.clear();
triggers.clear();
}
void process_cache::stats(unsigned &keys,unsigned &triggers)
{
rwlock_rdlock lock(access_lock);
keys=primary.size();
triggers=this->triggers.size();
}
void process_cache::rise(string const &trigger)
{
rwlock_wrlock lock(access_lock);
pair<triggers_ptr,triggers_ptr> range=triggers.equal_range(trigger.c_str());
triggers_ptr p;
list<pointer> kill_list;
for(p=range.first;p!=range.second;p++) {
kill_list.push_back(p->second);
}
list<pointer>::iterator lptr;
for(lptr=kill_list.begin();lptr!=kill_list.end();lptr++) {
delete_node(*lptr);
}
}
void process_cache::store(string const &key,set<string> const &triggers_in,time_t timeout_in,archive const &a)
{
rwlock_wrlock lock(access_lock);
pointer main;
main=primary.find(key.c_str());
if(main!=primary.end())
delete_node(main);
if(a.get().size()>memsize/20) {
return;
}
time_t now;
time(&now);
// Make sure there is at least 10% avalible
// And there is a block that is big enough to allocate 5% of memory
for(;;) {
if(process_cache_factory::mem->available() > memsize / 10) {
void *p=process_cache_factory::mem->malloc(memsize/20);
if(p) {
process_cache_factory::mem->free(p);
break;
}
}
if(timeout.begin()->first<now) {
main=timeout.begin()->second;
}
else {
main=lru.back();
}
delete_node(main);
}
try {
pair<pointer,bool> res=primary.insert(pair<shr_string,container>(key.c_str(),container()));
main=res.first;
container &cont=main->second;
cont.data.assign(a.get().c_str(),a.get().size());
lru.push_front(main);
cont.lru=lru.begin();
cont.timeout=timeout.insert(pair<time_t,pointer>(timeout_in,main));
if(triggers_in.find(key)==triggers_in.end()){
cont.triggers.push_back(triggers.insert(
pair<shr_string,pointer>(key.c_str(),main)));
}
set<string>::const_iterator si;
for(si=triggers_in.begin();si!=triggers_in.end();si++) {
cont.triggers.push_back(triggers.insert(
pair<shr_string,pointer>(si->c_str(),main)));
}
}
catch(std::bad_alloc const &e) {
clear();
}
}
void process_cache::delete_node(pointer p)
{
lru.erase(p->second.lru);
timeout.erase(p->second.timeout);
list<triggers_ptr>::iterator i;
for(i=p->second.triggers.begin();i!=p->second.triggers.end();i++) {
triggers.erase(*i);
}
primary.erase(p);
}
void *process_cache::operator new(size_t n) {
void *p=process_cache_factory::mem->malloc(n);
if(!p)
throw std::bad_alloc();
return p;
}
void process_cache::operator delete (void *p) {
process_cache_factory::mem->free(p);
}
};