From 1634821217afa6ee6751d075afdf23a53f9b73c8 Mon Sep 17 00:00:00 2001 From: Scott Cantor Date: Fri, 22 Sep 2006 04:19:31 +0000 Subject: [PATCH] Implement context storage handles. --- xmltooling/impl/MemoryStorageService.cpp | 186 ++++++++++++++++++++---------- xmltooling/util/StorageService.h | 64 ++++++++-- xmltoolingtest/MemoryStorageServiceTest.h | 17 +-- 3 files changed, 187 insertions(+), 80 deletions(-) diff --git a/xmltooling/impl/MemoryStorageService.cpp b/xmltooling/impl/MemoryStorageService.cpp index 3a041ea..4acd7c3 100644 --- a/xmltooling/impl/MemoryStorageService.cpp +++ b/xmltooling/impl/MemoryStorageService.cpp @@ -40,27 +40,29 @@ namespace xmltooling { MemoryStorageService(const DOMElement* e); virtual ~MemoryStorageService(); - void createString(const char* key, const char* value, time_t expiration); - bool readString(const char* key, string& value, time_t modifiedSince=0); - bool updateString(const char* key, const char* value=NULL, time_t expiration=0); - bool deleteString(const char* key); + StorageHandle* createHandle(); - void createText(const char* key, const char* value, time_t expiration) { - return createString(key, value, expiration); + void createString(StorageHandle* handle, const char* key, const char* value, time_t expiration); + bool readString(StorageHandle* handle, const char* key, string& value, time_t modifiedSince=0); + bool updateString(StorageHandle* handle, const char* key, const char* value=NULL, time_t expiration=0); + bool deleteString(StorageHandle* handle, const char* key); + + void createText(StorageHandle* handle, const char* key, const char* value, time_t expiration) { + return createString(handle, key, value, expiration); } - bool readText(const char* key, string& value, time_t modifiedSince=0) { - return readString(key, value, modifiedSince); + bool readText(StorageHandle* handle, const char* key, string& value, time_t modifiedSince=0) { + return readString(handle, key, value, modifiedSince); } - bool updateText(const char* key, const char* value=NULL, time_t expiration=0) { - return updateString(key, value, expiration); + bool updateText(StorageHandle* handle, const char* key, const char* value=NULL, time_t expiration=0) { + return updateString(handle, key, value, expiration); } - bool deleteText(const char* key) { - return deleteString(key); + bool deleteText(StorageHandle* handle, const char* key) { + return deleteString(handle, key); } - void reap() { - shutdown_wait->signal(); - } + void reap(StorageHandle* handle); + + void removeHandle(StorageHandle* handle); private: void cleanup(); @@ -72,9 +74,20 @@ namespace xmltooling { time_t modified, expiration; }; - map m_dataMap; - multimap m_expMap; - RWLock* m_lock; + struct XMLTOOL_DLLLOCAL MemoryHandle : public StorageHandle { + MemoryHandle(StorageService* storage) : StorageHandle(storage), m_lock(RWLock::create()) {} + virtual ~MemoryHandle() { + delete m_lock; + static_cast(m_storage)->removeHandle(this); + } + map m_dataMap; + multimap m_expMap; + RWLock* m_lock; + unsigned long reap(); + }; + + vector m_handles; + Mutex* mutex; CondWait* shutdown_wait; Thread* cleanup_thread; static void* cleanup_fn(void*); @@ -93,10 +106,10 @@ namespace xmltooling { 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) - : m_lock(NULL), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_cleanupInterval(0), + : mutex(NULL), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_cleanupInterval(0), m_log(Category::getInstance(XMLTOOLING_LOGCAT".StorageService")) { - m_lock = RWLock::create(); + mutex = Mutex::create(); shutdown_wait = CondWait::create(); cleanup_thread = Thread::create(&cleanup_fn, (void*)this); @@ -115,8 +128,28 @@ MemoryStorageService::~MemoryStorageService() shutdown_wait->signal(); cleanup_thread->join(NULL); - delete m_lock; delete shutdown_wait; + delete mutex; + for_each(m_handles.begin(), m_handles.end(), xmltooling::cleanup()); +} + +StorageService::StorageHandle* MemoryStorageService::createHandle() +{ + Lock wrapper(mutex); + MemoryHandle* ret = new MemoryHandle(this); + m_handles.push_back(ret); + return ret; +} + +void MemoryStorageService::removeHandle(StorageHandle* handle) +{ + Lock wrapper(mutex); + for (vector::iterator i=m_handles.begin(); i!=m_handles.end(); ++i) { + if (*i == handle) { + m_handles.erase(i); + return; + } + } } void* MemoryStorageService::cleanup_fn(void* cache_p) @@ -149,20 +182,10 @@ void MemoryStorageService::cleanup() shutdown_wait->timedwait(mutex, m_cleanupInterval); if (shutdown) break; - - // Lock the "database". - m_lock->wrlock(); - - // Garbage collect any expired entries. - unsigned int count=0; - time_t now=time(NULL)-XMLToolingConfig::getConfig().clock_skew_secs; - multimap::iterator stop=m_expMap.upper_bound(now); - for (multimap::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) { - m_dataMap.erase(i->second); - ++count; - } - m_lock->unlock(); + unsigned long count=0; + for (vector::iterator i=m_handles.begin(); i!=m_handles.end(); ++i) + count += (*i)->reap(); if (count) m_log.info("purged %d record(s) from storage", count); @@ -175,28 +198,61 @@ void MemoryStorageService::cleanup() Thread::exit(NULL); } -void MemoryStorageService::createString(const char* key, const char* value, time_t expiration) +void MemoryStorageService::reap(StorageHandle* handle) { - // Lock the maps. + if (!isValid(handle)) + throw IOException("Invalid storage handle."); + static_cast(handle)->reap(); +} + +unsigned long MemoryStorageService::MemoryHandle::reap() +{ + // Lock the "database". m_lock->wrlock(); SharedLock wrapper(m_lock, false); + // Garbage collect any expired entries. + unsigned long count=0; + time_t now=time(NULL)-XMLToolingConfig::getConfig().clock_skew_secs; + multimap::iterator stop=m_expMap.upper_bound(now); + for (multimap::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) { + m_dataMap.erase(i->second); + ++count; + } + + return count; +} + +void MemoryStorageService::createString(StorageHandle* handle, const char* key, const char* value, time_t expiration) +{ + if (!isValid(handle)) + throw IOException("Invalid storage handle."); + MemoryHandle* h = static_cast(handle); + + // Lock the maps. + h->m_lock->wrlock(); + SharedLock wrapper(h->m_lock, false); + // Check for a duplicate. - map::iterator i=m_dataMap.find(key); - if (i!=m_dataMap.end()) + map::iterator i=h->m_dataMap.find(key); + if (i!=h->m_dataMap.end()) throw IOException("attempted to insert a record with duplicate key ($1)", params(1,key)); - m_dataMap[key]=Record(value,time(NULL),expiration); - m_expMap.insert(multimap::value_type(expiration,key)); + h->m_dataMap[key]=Record(value,time(NULL),expiration); + h->m_expMap.insert(multimap::value_type(expiration,key)); m_log.debug("inserted record (%s)", key); } -bool MemoryStorageService::readString(const char* key, string& value, time_t modifiedSince) +bool MemoryStorageService::readString(StorageHandle* handle, const char* key, string& value, time_t modifiedSince) { - SharedLock wrapper(m_lock); - map::iterator i=m_dataMap.find(key); - if (i==m_dataMap.end()) + if (!isValid(handle)) + throw IOException("Invalid storage handle."); + MemoryHandle* h = static_cast(handle); + + SharedLock wrapper(h->m_lock); + map::iterator i=h->m_dataMap.find(key); + if (i==h->m_dataMap.end()) return false; else if (modifiedSince >= i->second.modified) return false; @@ -204,14 +260,18 @@ bool MemoryStorageService::readString(const char* key, string& value, time_t mod return true; } -bool MemoryStorageService::updateString(const char* key, const char* value, time_t expiration) +bool MemoryStorageService::updateString(StorageHandle* handle, const char* key, const char* value, time_t expiration) { + if (!isValid(handle)) + throw IOException("Invalid storage handle."); + MemoryHandle* h = static_cast(handle); + // Lock the maps. - m_lock->wrlock(); - SharedLock wrapper(m_lock, false); + h->m_lock->wrlock(); + SharedLock wrapper(h->m_lock, false); - map::iterator i=m_dataMap.find(key); - if (i==m_dataMap.end()) + map::iterator i=h->m_dataMap.find(key); + if (i==h->m_dataMap.end()) return false; if (value) @@ -219,15 +279,16 @@ bool MemoryStorageService::updateString(const char* key, const char* value, time if (expiration && expiration != i->second.expiration) { // Update secondary map. - pair::iterator,multimap::iterator> range=m_expMap.equal_range(i->second.expiration); + pair::iterator,multimap::iterator> range = + h->m_expMap.equal_range(i->second.expiration); for (; range.first != range.second; ++range.first) { if (range.first->second == i->first) { - m_expMap.erase(range.first); + h->m_expMap.erase(range.first); break; } } i->second.expiration = expiration; - m_expMap.insert(multimap::value_type(expiration,key)); + h->m_expMap.insert(multimap::value_type(expiration,key)); } i->second.modified = time(NULL); @@ -235,25 +296,30 @@ bool MemoryStorageService::updateString(const char* key, const char* value, time return true; } -bool MemoryStorageService::deleteString(const char* key) +bool MemoryStorageService::deleteString(StorageHandle* handle, const char* key) { + if (!isValid(handle)) + throw IOException("Invalid storage handle."); + MemoryHandle* h = static_cast(handle); + // Lock the maps. - m_lock->wrlock(); - SharedLock wrapper(m_lock, false); + h->m_lock->wrlock(); + SharedLock wrapper(h->m_lock, false); // Find the record. - map::iterator i=m_dataMap.find(key); - if (i!=m_dataMap.end()) { + map::iterator i=h->m_dataMap.find(key); + if (i!=h->m_dataMap.end()) { // Now find the reversed index of expiration to key, so we can clear it. - pair::iterator,multimap::iterator> range=m_expMap.equal_range(i->second.expiration); + pair::iterator,multimap::iterator> range = + h->m_expMap.equal_range(i->second.expiration); for (; range.first != range.second; ++range.first) { if (range.first->second == i->first) { - m_expMap.erase(range.first); + h->m_expMap.erase(range.first); break; } } // And finally delete the record itself. - m_dataMap.erase(i); + h->m_dataMap.erase(i); m_log.debug("deleted record (%s)", key); return true; } diff --git a/xmltooling/util/StorageService.h b/xmltooling/util/StorageService.h index 905884b..7a2a707 100644 --- a/xmltooling/util/StorageService.h +++ b/xmltooling/util/StorageService.h @@ -35,26 +35,51 @@ namespace xmltooling { class XMLTOOL_API StorageService { MAKE_NONCOPYABLE(StorageService); - protected: - StorageService() {} - public: virtual ~StorageService() {} /** + * A "context" for accessing a StorageService instance. + * Handles are created and freed using the StorageService interface, + * and can be kept for the life of the service, and shared by multiple + * threads. + */ + class XMLTOOL_API StorageHandle { + MAKE_NONCOPYABLE(StorageHandle); + friend class XMLTOOL_API StorageService; + public: + virtual ~StorageHandle() {} + protected: + StorageHandle(StorageService* storage) : m_storage(storage) {} + StorageService* m_storage; + }; + + + /** + * Returns a new handle for the storage service. + * The caller MUST delete the handle + * before freeing the StorageService itself. + * + * @return a new handle + */ + virtual StorageHandle* createHandle()=0; + + /** * Creates a new "short" record in the storage service. * + * @param handle a valid storage handle * @param key null-terminated unique key of up to 255 bytes * @param value null-terminated value of up to 255 bytes to store * @param expiration an expiration timestamp, after which the record can be purged * * @throws IOException raised if errors occur in the insertion process */ - virtual void createString(const char* key, const char* value, time_t expiration)=0; + virtual void createString(StorageHandle* handle, const char* key, const char* value, time_t expiration)=0; /** * Returns an existing "short" record from the storage service. * + * @param handle a valid storage handle * @param key null-terminated unique key of up to 255 bytes * @param value location in which to return the record value * @param modifiedSince the record should not be returned if unmodified since this time, @@ -63,11 +88,12 @@ namespace xmltooling { * * @throws IOException raised if errors occur in the read process */ - virtual bool readString(const char* key, std::string& value, time_t modifiedSince=0)=0; + virtual bool readString(StorageHandle* handle, const char* key, std::string& value, time_t modifiedSince=0)=0; /** * Updates an existing "short" record in the storage service. * + * @param handle a valid storage handle * @param key null-terminated unique key of up to 255 bytes * @param value null-terminated value of up to 255 bytes to store, or NULL to leave alone * @param expiration a new expiration timestamp, or 0 to leave alone @@ -75,32 +101,35 @@ namespace xmltooling { * * @throws IOException raised if errors occur in the update process */ - virtual bool updateString(const char* key, const char* value=NULL, time_t expiration=0)=0; + virtual bool updateString(StorageHandle* handle, const char* key, const char* value=NULL, time_t expiration=0)=0; /** * Deletes an existing "short" record from the storage service. * + * @param handle a valid storage handle * @param key null-terminated unique key of up to 255 bytes * @return true iff the record existed and was deleted * * @throws IOException raised if errors occur in the deletion process */ - virtual bool deleteString(const char* key)=0; + virtual bool deleteString(StorageHandle* handle, const char* key)=0; /** * Creates a new "long" record in the storage service. * + * @param handle a valid storage handle * @param key null-terminated unique key of up to 255 bytes * @param value null-terminated value of arbitrary length * @param expiration an expiration timestamp, after which the record can be purged * * @throws IOException raised if errors occur in the insertion process */ - virtual void createText(const char* key, const char* value, time_t expiration)=0; + virtual void createText(StorageHandle* handle, const char* key, const char* value, time_t expiration)=0; /** * Returns an existing "long" record from the storage service. * + * @param handle a valid storage handle * @param key null-terminated unique key of up to 255 bytes * @param value location in which to return the record value * @param modifiedSince the record should not be returned if unmodified since this time, @@ -109,11 +138,12 @@ namespace xmltooling { * * @throws IOException raised if errors occur in the read process */ - virtual bool readText(const char* key, std::string& value, time_t modifiedSince=0)=0; + virtual bool readText(StorageHandle* handle, const char* key, std::string& value, time_t modifiedSince=0)=0; /** * Updates an existing "long" record in the storage service. * + * @param handle a valid storage handle * @param key null-terminated unique key of up to 255 bytes * @param value null-terminated value of arbitrary length to store, or NULL to leave alone * @param expiration a new expiration timestamp, or 0 to leave alone @@ -121,24 +151,34 @@ namespace xmltooling { * * @throws IOException raised if errors occur in the update process */ - virtual bool updateText(const char* key, const char* value=NULL, time_t expiration=0)=0; + virtual bool updateText(StorageHandle* handle, const char* key, const char* value=NULL, time_t expiration=0)=0; /** * Deletes an existing "long" record from the storage service. * + * @param handle a valid storage handle * @param key null-terminated unique key of up to 255 bytes * @return true iff the record existed and was deleted * * @throws IOException raised if errors occur in the deletion process */ - virtual bool deleteText(const char* key)=0; + virtual bool deleteText(StorageHandle* handle, const char* key)=0; /** * Manually trigger a cleanup of expired records. * The method MAY return without guaranteeing that * cleanup has already occurred. + * + * @param handle a valid storage handle */ - virtual void reap()=0; + virtual void reap(StorageHandle* handle)=0; + + protected: + StorageService() {} + + virtual bool isValid(StorageHandle* handle) { + return this == (handle ? handle->m_storage : NULL); + } }; /** diff --git a/xmltoolingtest/MemoryStorageServiceTest.h b/xmltoolingtest/MemoryStorageServiceTest.h index 1d91950..99e2e0e 100644 --- a/xmltoolingtest/MemoryStorageServiceTest.h +++ b/xmltoolingtest/MemoryStorageServiceTest.h @@ -30,18 +30,19 @@ public: auto_ptr storage( XMLToolingConfig::getConfig().StorageServiceManager.newPlugin(MEMORY_STORAGE_SERVICE,NULL) ); + auto_ptr handle(storage->createHandle()); string data; - TSM_ASSERT("Record found in storage.", !storage->readString("foo1", data)); - storage->createString("foo1", "bar1", time(NULL) - 300); - storage->createString("foo2", "bar2", time(NULL)); - TSM_ASSERT("Record not found in storage.", storage->readString("foo1", data)); + TSM_ASSERT("Record found in storage.", !storage->readString(handle.get(), "foo1", data)); + storage->createString(handle.get(), "foo1", "bar1", time(NULL) - 300); + storage->createString(handle.get(), "foo2", "bar2", time(NULL)); + TSM_ASSERT("Record not found in storage.", storage->readString(handle.get(), "foo1", data)); TSM_ASSERT_EQUALS("Record value doesn't match.", data, "bar1"); - TSM_ASSERT("Update failed.", storage->updateString("foo2", "bar1")); - TSM_ASSERT("Record not found in storage.", storage->readString("foo2", data)); + TSM_ASSERT("Update failed.", storage->updateString(handle.get(), "foo2", "bar1")); + TSM_ASSERT("Record not found in storage.", storage->readString(handle.get(), "foo2", data)); TSM_ASSERT_EQUALS("Record value doesn't match.", data, "bar1"); - TSM_ASSERT("Delete failed.", storage->deleteString("foo2")); - storage->reap(); + TSM_ASSERT("Delete failed.", storage->deleteString(handle.get(), "foo2")); + storage->reap(handle.get()); Thread::sleep(1); } }; -- 2.1.4