/*
- * Copyright 2001-2005 Internet2
- *
+ * Copyright 2001-2007 Internet2
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
/**
* MemoryStorageService.cpp
- *
+ *
* In-memory "persistent" storage, suitable for simple applications.
*/
#include "internal.h"
+#include "logging.h"
#include "util/NDC.h"
#include "util/StorageService.h"
#include "util/Threads.h"
#include "util/XMLHelper.h"
-#include <log4cpp/Category.hh>
+#include <memory>
#include <xercesc/util/XMLUniDefs.hpp>
+using namespace xmltooling::logging;
using namespace xmltooling;
-using namespace log4cpp;
using namespace std;
+using xercesc::DOMElement;
+
namespace xmltooling {
class XMLTOOL_DLLLOCAL MemoryStorageService : public StorageService
{
public:
MemoryStorageService(const DOMElement* e);
virtual ~MemoryStorageService();
-
- void createString(const char* context, const char* key, const char* value, time_t expiration);
- bool readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL);
- bool updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0);
+
+ bool createString(const char* context, const char* key, const char* value, time_t expiration);
+ int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0);
+ int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0);
bool deleteString(const char* context, const char* key);
-
- void createText(const char* context, const char* key, const char* value, time_t expiration) {
+
+ bool createText(const char* context, const char* key, const char* value, time_t expiration) {
return createString(context, key, value, expiration);
}
- bool readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL) {
- return readString(context, key, pvalue, pexpiration);
+ int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
+ return readString(context, key, pvalue, pexpiration, version);
}
- bool updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0) {
- return updateString(context, key, value, expiration);
+ int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
+ return updateString(context, key, value, expiration, version);
}
bool deleteText(const char* context, const char* key) {
return deleteString(context, key);
}
-
+
void reap(const char* context);
+ void updateContext(const char* context, time_t expiration);
void deleteContext(const char* context) {
- Lock wrapper(contextLock);
+ m_lock->wrlock();
m_contextMap.erase(context);
+ m_lock->unlock();
}
private:
void cleanup();
-
+
struct XMLTOOL_DLLLOCAL Record {
- Record() : expiration(0) {}
- Record(string s, time_t t) : data(s), expiration(t) {}
+ Record() : expiration(0), version(1) {}
+ Record(const string& s, time_t t) : data(s), expiration(t), version(1) {}
string data;
time_t expiration;
+ int version;
};
-
+
struct XMLTOOL_DLLLOCAL Context {
- Context() : m_lock(RWLock::create()) {}
+ Context() {}
Context(const Context& src) {
m_dataMap = src.m_dataMap;
- m_expMap = src.m_expMap;
- m_lock = RWLock::create();
}
- ~Context() { delete m_lock; }
map<string,Record> m_dataMap;
- multimap<time_t,string> m_expMap;
- RWLock* m_lock;
- unsigned long reap();
+ unsigned long reap(time_t exp);
};
- Context& getContext(const char* context) {
- Lock wrapper(contextLock);
+ Context& readContext(const char* context) {
+ m_lock->rdlock();
+ map<string,Context>::iterator i = m_contextMap.find(context);
+ if (i != m_contextMap.end())
+ return i->second;
+ m_lock->unlock();
+ m_lock->wrlock();
+ return m_contextMap[context];
+ }
+
+ Context& writeContext(const char* context) {
+ m_lock->wrlock();
return m_contextMap[context];
}
map<string,Context> m_contextMap;
- Mutex* contextLock;
+ RWLock* m_lock;
CondWait* shutdown_wait;
Thread* cleanup_thread;
static void* cleanup_fn(void*);
{
return new MemoryStorageService(e);
}
-
};
static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
MemoryStorageService::MemoryStorageService(const DOMElement* e)
- : contextLock(NULL), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_cleanupInterval(0),
+ : m_lock(NULL), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_cleanupInterval(0),
m_log(Category::getInstance(XMLTOOLING_LOGCAT".StorageService"))
{
const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
m_cleanupInterval = XMLString::parseInt(tag);
}
if (!m_cleanupInterval)
- m_cleanupInterval=300;
+ m_cleanupInterval=900;
- contextLock = Mutex::create();
+ m_lock = RWLock::create();
shutdown_wait = CondWait::create();
cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
}
shutdown_wait->signal();
cleanup_thread->join(NULL);
+ delete cleanup_thread;
delete shutdown_wait;
- delete contextLock;
+ delete m_lock;
}
void* MemoryStorageService::cleanup_fn(void* cache_p)
MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(cache_p);
#ifndef WIN32
- // First, let's block all signals
+ // First, let's block all signals
Thread::mask_all_signals();
#endif
#ifdef _DEBUG
NDC ndc("cleanup");
#endif
-
- Mutex* mutex = Mutex::create();
+ auto_ptr<Mutex> mutex(Mutex::create());
mutex->lock();
m_log.info("cleanup thread started...running every %d seconds", m_cleanupInterval);
while (!shutdown) {
- shutdown_wait->timedwait(mutex, m_cleanupInterval);
+ shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);
if (shutdown)
break;
-
+
unsigned long count=0;
- Lock wrapper(contextLock);
+ time_t now = time(NULL);
+ m_lock->wrlock();
+ SharedLock locker(m_lock, false);
for (map<string,Context>::iterator i=m_contextMap.begin(); i!=m_contextMap.end(); ++i)
- count += i->second.reap();
-
+ count += i->second.reap(now);
+
if (count)
- m_log.info("purged %d record(s) from storage", count);
+ m_log.info("purged %d expired record(s) from storage", count);
}
m_log.info("cleanup thread finished");
mutex->unlock();
- delete mutex;
Thread::exit(NULL);
}
void MemoryStorageService::reap(const char* context)
{
- getContext(context).reap();
+ Context& ctx = writeContext(context);
+ SharedLock locker(m_lock, false);
+ ctx.reap(time(NULL));
}
-unsigned long MemoryStorageService::Context::reap()
+unsigned long MemoryStorageService::Context::reap(time_t exp)
{
- // Lock the "database".
- m_lock->wrlock();
- SharedLock wrapper(m_lock, false);
-
// Garbage collect any expired entries.
unsigned long count=0;
- multimap<time_t,string>::iterator stop=m_expMap.upper_bound(time(NULL));
- for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {
- m_dataMap.erase(i->second);
- ++count;
+ map<string,Record>::iterator cur = m_dataMap.begin();
+ map<string,Record>::iterator stop = m_dataMap.end();
+ while (cur != stop) {
+ if (cur->second.expiration <= exp) {
+ map<string,Record>::iterator tmp = cur++;
+ m_dataMap.erase(tmp);
+ ++count;
+ }
+ else {
+ cur++;
+ }
}
-
return count;
}
-void MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
+bool MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
{
- Context& ctx = getContext(context);
+ Context& ctx = writeContext(context);
+ SharedLock locker(m_lock, false);
- // Lock the maps.
- ctx.m_lock->wrlock();
- SharedLock wrapper(ctx.m_lock, false);
-
// Check for a duplicate.
map<string,Record>::iterator i=ctx.m_dataMap.find(key);
if (i!=ctx.m_dataMap.end()) {
// Not yet expired?
if (time(NULL) < i->second.expiration)
- throw IOException("attempted to insert a record with duplicate key ($1)", params(1,key));
+ return false;
// It's dead, so we can just remove it now and create the new record.
- // Now find the reversed index of expiration to key, so we can clear it.
- pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
- ctx.m_expMap.equal_range(i->second.expiration);
- for (; range.first != range.second; ++range.first) {
- if (range.first->second == i->first) {
- ctx.m_expMap.erase(range.first);
- break;
- }
- }
- // And finally delete the record itself.
ctx.m_dataMap.erase(i);
}
-
+
ctx.m_dataMap[key]=Record(value,expiration);
- ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));
-
+
m_log.debug("inserted record (%s) in context (%s)", key, context);
+ return true;
}
-bool MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration)
+int MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
{
- Context& ctx = getContext(context);
+ Context& ctx = readContext(context);
+ SharedLock locker(m_lock, false);
- SharedLock wrapper(ctx.m_lock);
map<string,Record>::iterator i=ctx.m_dataMap.find(key);
if (i==ctx.m_dataMap.end())
- return false;
+ return 0;
else if (time(NULL) >= i->second.expiration)
- return false;
- if (pvalue)
- *pvalue = i->second.data;
+ return 0;
if (pexpiration)
*pexpiration = i->second.expiration;
- return true;
+ if (i->second.version == version)
+ return version; // nothing's changed, so just echo back the version
+ if (pvalue)
+ *pvalue = i->second.data;
+ return i->second.version;
}
-bool MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration)
+int MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)
{
- Context& ctx = getContext(context);
-
- // Lock the maps.
- ctx.m_lock->wrlock();
- SharedLock wrapper(ctx.m_lock, false);
+ Context& ctx = writeContext(context);
+ SharedLock locker(m_lock, false);
map<string,Record>::iterator i=ctx.m_dataMap.find(key);
if (i==ctx.m_dataMap.end())
- return false;
+ return 0;
else if (time(NULL) >= i->second.expiration)
- return false;
-
- if (value)
+ return 0;
+
+ if (version > 0 && version != i->second.version)
+ return -1; // caller's out of sync
+
+ if (value) {
i->second.data = value;
-
- if (expiration && expiration != i->second.expiration) {
- // Update secondary map.
- pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
- ctx.m_expMap.equal_range(i->second.expiration);
- for (; range.first != range.second; ++range.first) {
- if (range.first->second == i->first) {
- ctx.m_expMap.erase(range.first);
- break;
- }
- }
- i->second.expiration = expiration;
- ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));
+ ++(i->second.version);
}
+ if (expiration && expiration != i->second.expiration)
+ i->second.expiration = expiration;
+
m_log.debug("updated record (%s) in context (%s)", key, context);
- return true;
+ return i->second.version;
}
bool MemoryStorageService::deleteString(const char* context, const char* key)
{
- Context& ctx = getContext(context);
+ Context& ctx = writeContext(context);
+ SharedLock locker(m_lock, false);
- // Lock the maps.
- ctx.m_lock->wrlock();
- SharedLock wrapper(ctx.m_lock, false);
-
// Find the record.
map<string,Record>::iterator i=ctx.m_dataMap.find(key);
if (i!=ctx.m_dataMap.end()) {
- // Now find the reversed index of expiration to key, so we can clear it.
- pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
- ctx.m_expMap.equal_range(i->second.expiration);
- for (; range.first != range.second; ++range.first) {
- if (range.first->second == i->first) {
- ctx.m_expMap.erase(range.first);
- break;
- }
- }
- // And finally delete the record itself.
ctx.m_dataMap.erase(i);
m_log.debug("deleted record (%s) in context (%s)", key, context);
return true;
m_log.debug("deleting record (%s) in context (%s)....not found", key, context);
return false;
}
+
+void MemoryStorageService::updateContext(const char* context, time_t expiration)
+{
+ Context& ctx = writeContext(context);
+ SharedLock locker(m_lock, false);
+
+ time_t now = time(NULL);
+ map<string,Record>::iterator stop=ctx.m_dataMap.end();
+ for (map<string,Record>::iterator i = ctx.m_dataMap.begin(); i!=stop; ++i) {
+ if (now < i->second.expiration)
+ i->second.expiration = expiration;
+ }
+
+ m_log.debug("updated expiration of valid records in context (%s)", context);
+}