2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
22 * MemoryStorageService.cpp
24 * In-memory "persistent" storage, suitable for simple applications.
30 #include "util/StorageService.h"
31 #include "util/Threads.h"
32 #include "util/XMLHelper.h"
35 #include <xercesc/util/XMLUniDefs.hpp>
37 using namespace xmltooling::logging;
38 using namespace xmltooling;
41 using xercesc::DOMElement;
44 // Reasonably extended sizes to avoid callers needing to shrink unduly.
45 static const XMLTOOL_DLLLOCAL StorageService::Capabilities g_memCaps(0x4000, 0x4000, 0x4000);
48 namespace xmltooling {
49 class XMLTOOL_DLLLOCAL MemoryStorageService : public StorageService
52 MemoryStorageService(const DOMElement* e);
53 virtual ~MemoryStorageService();
55 const Capabilities& getCapabilities() const {
59 bool createString(const char* context, const char* key, const char* value, time_t expiration);
60 int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0);
61 int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0);
62 bool deleteString(const char* context, const char* key);
64 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
65 return createString(context, key, value, expiration);
67 int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
68 return readString(context, key, pvalue, pexpiration, version);
70 int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
71 return updateString(context, key, value, expiration, version);
73 bool deleteText(const char* context, const char* key) {
74 return deleteString(context, key);
77 void reap(const char* context);
78 void updateContext(const char* context, time_t expiration);
79 void deleteContext(const char* context) {
81 m_contextMap.erase(context);
86 struct XMLTOOL_DLLLOCAL Record {
87 Record() : expiration(0), version(1) {}
88 Record(const string& s, time_t t) : data(s), expiration(t), version(1) {}
94 struct XMLTOOL_DLLLOCAL Context {
96 Context(const Context& src) {
97 m_dataMap = src.m_dataMap;
99 map<string,Record> m_dataMap;
100 unsigned long reap(time_t exp);
103 Context& readContext(const char* context) {
105 map<string,Context>::iterator i = m_contextMap.find(context);
106 if (i != m_contextMap.end())
110 return m_contextMap[context];
113 Context& writeContext(const char* context) {
115 return m_contextMap[context];
118 map<string,Context> m_contextMap;
119 auto_ptr<RWLock> m_lock;
120 auto_ptr<CondWait> shutdown_wait;
121 auto_ptr<Thread> cleanup_thread;
122 static void* cleanup_fn(void*);
124 int m_cleanupInterval;
128 StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)
130 return new MemoryStorageService(e);
134 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
136 MemoryStorageService::MemoryStorageService(const DOMElement* e)
137 : m_lock(RWLock::create()), shutdown_wait(CondWait::create()), shutdown(false),
138 m_cleanupInterval(XMLHelper::getAttrInt(e, 900, cleanupInterval)),
139 m_log(Category::getInstance(XMLTOOLING_LOGCAT".StorageService"))
141 cleanup_thread.reset(Thread::create(&cleanup_fn, (void*)this));
144 MemoryStorageService::~MemoryStorageService()
146 // Shut down the cleanup thread and let it know...
148 shutdown_wait->signal();
149 cleanup_thread->join(nullptr);
152 void* MemoryStorageService::cleanup_fn(void* pv)
154 MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(pv);
157 // First, let's block all signals
158 Thread::mask_all_signals();
165 auto_ptr<Mutex> mutex(Mutex::create());
168 cache->m_log.info("cleanup thread started...running every %d seconds", cache->m_cleanupInterval);
170 while (!cache->shutdown) {
171 cache->shutdown_wait->timedwait(mutex.get(), cache->m_cleanupInterval);
175 unsigned long count=0;
176 time_t now = time(nullptr);
177 cache->m_lock->wrlock();
178 SharedLock locker(cache->m_lock.get(), false);
179 for (map<string,Context>::iterator i=cache->m_contextMap.begin(); i!=cache->m_contextMap.end(); ++i)
180 count += i->second.reap(now);
183 cache->m_log.info("purged %d expired record(s) from storage", count);
186 cache->m_log.info("cleanup thread finished");
192 void MemoryStorageService::reap(const char* context)
194 Context& ctx = writeContext(context);
195 SharedLock locker(m_lock.get(), false);
196 ctx.reap(time(nullptr));
199 unsigned long MemoryStorageService::Context::reap(time_t exp)
201 // Garbage collect any expired entries.
202 unsigned long count=0;
203 map<string,Record>::iterator cur = m_dataMap.begin();
204 map<string,Record>::iterator stop = m_dataMap.end();
205 while (cur != stop) {
206 if (cur->second.expiration <= exp) {
207 map<string,Record>::iterator tmp = cur++;
208 m_dataMap.erase(tmp);
218 bool MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
220 Context& ctx = writeContext(context);
221 SharedLock locker(m_lock.get(), false);
223 // Check for a duplicate.
224 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
225 if (i!=ctx.m_dataMap.end()) {
227 if (time(nullptr) < i->second.expiration)
229 // It's dead, so we can just remove it now and create the new record.
230 ctx.m_dataMap.erase(i);
233 ctx.m_dataMap[key]=Record(value,expiration);
235 m_log.debug("inserted record (%s) in context (%s) with expiration (%lu)", key, context, expiration);
239 int MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
241 Context& ctx = readContext(context);
242 SharedLock locker(m_lock.get(), false);
244 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
245 if (i==ctx.m_dataMap.end())
247 else if (time(nullptr) >= i->second.expiration)
250 *pexpiration = i->second.expiration;
251 if (i->second.version == version)
252 return version; // nothing's changed, so just echo back the version
254 *pvalue = i->second.data;
255 return i->second.version;
258 int MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)
260 Context& ctx = writeContext(context);
261 SharedLock locker(m_lock.get(), false);
263 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
264 if (i==ctx.m_dataMap.end())
266 else if (time(nullptr) >= i->second.expiration)
269 if (version > 0 && version != i->second.version)
270 return -1; // caller's out of sync
273 i->second.data = value;
274 ++(i->second.version);
277 if (expiration && expiration != i->second.expiration)
278 i->second.expiration = expiration;
280 m_log.debug("updated record (%s) in context (%s) with expiration (%lu)", key, context, i->second.expiration);
281 return i->second.version;
284 bool MemoryStorageService::deleteString(const char* context, const char* key)
286 Context& ctx = writeContext(context);
287 SharedLock locker(m_lock.get(), false);
290 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
291 if (i!=ctx.m_dataMap.end()) {
292 ctx.m_dataMap.erase(i);
293 m_log.debug("deleted record (%s) in context (%s)", key, context);
297 m_log.debug("deleting record (%s) in context (%s)....not found", key, context);
301 void MemoryStorageService::updateContext(const char* context, time_t expiration)
303 Context& ctx = writeContext(context);
304 SharedLock locker(m_lock.get(), false);
306 time_t now = time(nullptr);
307 map<string,Record>::iterator stop=ctx.m_dataMap.end();
308 for (map<string,Record>::iterator i = ctx.m_dataMap.begin(); i!=stop; ++i) {
309 if (now < i->second.expiration)
310 i->second.expiration = expiration;
313 m_log.debug("updated expiration of valid records in context (%s) to (%lu)", context, expiration);