2 * Copyright 2001-2010 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 * Storage Service using memcache (pre memcache tags).
23 #if defined (_MSC_VER) || defined(__BORLANDC__)
24 # include "config_win32.h"
30 # define _CRT_NONSTDC_NO_DEPRECATE 1
31 # define _CRT_SECURE_NO_DEPRECATE 1
32 # define MCEXT_EXPORTS __declspec(dllexport)
34 # define MCEXT_EXPORTS
37 #include <xmltooling/base.h>
41 #include <libmemcached/memcached.h>
42 #include <xercesc/util/XMLUniDefs.hpp>
44 #include <xmltooling/logging.h>
45 #include <xmltooling/unicode.h>
46 #include <xmltooling/XMLToolingConfig.h>
47 #include <xmltooling/util/NDC.h>
48 #include <xmltooling/util/StorageService.h>
49 #include <xmltooling/util/Threads.h>
50 #include <xmltooling/util/XMLHelper.h>
52 using namespace xmltooling::logging;
53 using namespace xmltooling;
54 using namespace xercesc;
57 namespace xmltooling {
58 static const XMLCh Hosts[] = UNICODE_LITERAL_5(H,o,s,t,s);
59 static const XMLCh prefix[] = UNICODE_LITERAL_6(p,r,e,f,i,x);
60 static const XMLCh buildMap[] = UNICODE_LITERAL_8(b,u,i,l,d,M,a,p);
61 static const XMLCh sendTimeout[] = UNICODE_LITERAL_11(s,e,n,d,T,i,m,e,o,u,t);
62 static const XMLCh recvTimeout[] = UNICODE_LITERAL_11(r,e,c,v,T,i,m,e,o,u,t);
63 static const XMLCh pollTimeout[] = UNICODE_LITERAL_11(p,o,l,l,T,i,m,e,o,u,t);
64 static const XMLCh failLimit[] = UNICODE_LITERAL_9(f,a,i,l,L,i,m,i,t);
65 static const XMLCh retryTimeout[] = UNICODE_LITERAL_12(r,e,t,r,y,T,i,m,e,o,u,t);
66 static const XMLCh nonBlocking[] = UNICODE_LITERAL_11(n,o,n,B,l,o,c,k,i,n,g);
73 mc_record(string _v, time_t _e) :
74 value(_v), expiration(_e)
80 MemcacheBase(const DOMElement* e);
83 bool addMemcache(const char *key,
87 bool use_prefix = true);
88 bool setMemcache(const char *key,
92 bool use_prefix = true);
93 bool replaceMemcache(const char *key,
97 bool use_prefix = true);
98 bool getMemcache(const char *key,
101 bool use_prefix = true);
102 bool deleteMemcache(const char *key,
104 bool use_prefix = true);
106 void serialize(mc_record &source, string &dest);
107 void serialize(list<string> &source, string &dest);
108 void deserialize(string &source, mc_record &dest);
109 void deserialize(string &source, list<string> &dest);
111 bool addSessionToUser(string &key, string &user);
112 bool addLock(string what, bool use_prefix = true);
113 void deleteLock(string what, bool use_prefix = true);
116 const DOMElement* m_root; // can only use this during initialization
123 class MemcacheStorageService : public StorageService, public MemcacheBase {
126 MemcacheStorageService(const DOMElement* e);
127 ~MemcacheStorageService();
129 bool createString(const char* context, const char* key, const char* value, time_t expiration);
130 int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0);
131 int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0);
132 bool deleteString(const char* context, const char* key);
134 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
135 return createString(context, key, value, expiration);
137 int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
138 return readString(context, key, pvalue, pexpiration, version);
140 int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
141 return updateString(context, key, value, expiration, version);
143 bool deleteText(const char* context, const char* key) {
144 return deleteString(context, key);
147 void reap(const char* context) {}
149 void updateContext(const char* context, time_t expiration);
150 void deleteContext(const char* context);
160 StorageService* MemcacheStorageServiceFactory(const DOMElement* const & e) {
161 return new MemcacheStorageService(e);
166 bool MemcacheBase::addLock(string what, bool use_prefix) {
167 string lock_name = what + ":LOCK";
168 string set_val = "1";
170 while (!addMemcache(lock_name.c_str(), set_val, 5, 0, use_prefix)) {
172 log.debug("Unable to get lock %s... FAILED.", lock_name.c_str());
175 log.debug("Unable to get lock %s... Retrying.", lock_name.c_str());
181 struct timeval tv = { 0, 100000 };
182 select(0, 0, 0, 0, &tv);
188 void MemcacheBase::deleteLock(string what, bool use_prefix) {
190 string lock_name = what + ":LOCK";
191 deleteMemcache(lock_name.c_str(), 0, use_prefix);
196 void MemcacheBase::deserialize(string &source, mc_record &dest) {
197 istringstream is(source, stringstream::in | stringstream::out);
198 is >> dest.expiration;
199 is.ignore(1); // ignore delimiter
200 dest.value = is.str().c_str() + is.tellg();
203 void MemcacheBase::deserialize(string &source, list<string> &dest) {
204 istringstream is(source, stringstream::in | stringstream::out);
212 void MemcacheBase::serialize(mc_record &source, string &dest) {
213 ostringstream os(stringstream::in | stringstream::out);
214 os << source.expiration;
215 os << "-"; // delimiter
220 void MemcacheBase::serialize(list<string> &source, string &dest) {
221 ostringstream os(stringstream::in | stringstream::out);
222 for(list<string>::iterator iter = source.begin(); iter != source.end(); iter++) {
223 if (iter != source.begin()) {
231 bool MemcacheBase::addSessionToUser(string &key, string &user) {
233 if (! addLock(user, false)) {
239 string sessid = m_prefix + key; // add specific prefix to session
240 string delimiter = ";";
241 string user_key = "UDATA:";
245 bool result = getMemcache(user_key.c_str(), user_val, &flags, false);
248 bool already_there = false;
249 // skip delimiters at beginning.
250 string::size_type lastPos = user_val.find_first_not_of(delimiter, 0);
252 // find first "non-delimiter".
253 string::size_type pos = user_val.find_first_of(delimiter, lastPos);
255 while (string::npos != pos || string::npos != lastPos) {
256 // found a token, add it to the vector.
257 string session = user_val.substr(lastPos, pos - lastPos);
258 if (strcmp(session.c_str(), sessid.c_str()) == 0) {
259 already_there = true;
263 // skip delimiters. Note the "not_of"
264 lastPos = user_val.find_first_not_of(delimiter, pos);
266 // find next "non-delimiter"
267 pos = user_val.find_first_of(delimiter, lastPos);
270 if (!already_there) {
271 user_val += delimiter + sessid;
272 replaceMemcache(user_key.c_str(), user_val, 0, 0, false);
275 addMemcache(user_key.c_str(), sessid, 0, 0, false);
278 deleteLock(user, false);
283 bool MemcacheBase::deleteMemcache(const char *key,
291 final_key = m_prefix + key;
297 rv = memcached_delete(memc, (char *)final_key.c_str(), final_key.length(), timeout);
300 if (rv == MEMCACHED_SUCCESS) {
302 } else if (rv == MEMCACHED_NOTFOUND) {
303 // Key wasn't there... No biggie.
305 } else if (rv == MEMCACHED_ERRNO) {
307 string error = string("Memcache::deleteMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
309 throw IOException(error);
311 string error = string("Memcache::deleteMemcache() Problems: ") + memcached_strerror(memc, rv);
313 throw IOException(error);
319 bool MemcacheBase::getMemcache(const char *key,
330 final_key = m_prefix + key;
336 result = memcached_get(memc, (char *)final_key.c_str(), final_key.length(), &len, flags, &rv);
339 if (rv == MEMCACHED_SUCCESS) {
343 } else if (rv == MEMCACHED_NOTFOUND) {
344 log.debug("Key %s not found in memcache...", key);
346 } else if (rv == MEMCACHED_ERRNO) {
348 string error = string("Memcache::getMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
350 throw IOException(error);
352 string error = string("Memcache::getMemcache() Problems: ") + memcached_strerror(memc, rv);
354 throw IOException(error);
360 bool MemcacheBase::addMemcache(const char *key,
371 final_key = m_prefix + key;
377 rv = memcached_add(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
380 if (rv == MEMCACHED_SUCCESS) {
382 } else if (rv == MEMCACHED_NOTSTORED) {
385 } else if (rv == MEMCACHED_ERRNO) {
387 string error = string("Memcache::addMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
389 throw IOException(error);
391 string error = string("Memcache::addMemcache() Problems: ") + memcached_strerror(memc, rv);
393 throw IOException(error);
399 bool MemcacheBase::setMemcache(const char *key,
410 final_key = m_prefix + key;
416 rv = memcached_set(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
419 if (rv == MEMCACHED_SUCCESS) {
421 } else if (rv == MEMCACHED_ERRNO) {
423 string error = string("Memcache::setMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
425 throw IOException(error);
427 string error = string("Memcache::setMemcache() Problems: ") + memcached_strerror(memc, rv);
429 throw IOException(error);
435 bool MemcacheBase::replaceMemcache(const char *key,
446 final_key = m_prefix + key;
452 rv = memcached_replace(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
455 if (rv == MEMCACHED_SUCCESS) {
457 } else if (rv == MEMCACHED_NOTSTORED) {
460 } else if (rv == MEMCACHED_ERRNO) {
462 string error = string("Memcache::replaceMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
464 throw IOException(error);
466 string error = string("Memcache::replaceMemcache() Problems: ") + memcached_strerror(memc, rv);
468 throw IOException(error);
474 MemcacheBase::MemcacheBase(const DOMElement* e) : m_root(e), log(Category::getInstance("XMLTooling.MemcacheBase")), m_prefix("") {
476 auto_ptr_char p(e ? e->getAttributeNS(nullptr,prefix) : nullptr);
477 if (p.get() && *p.get()) {
478 log.debug("INIT: GOT key prefix: %s", p.get());
482 m_lock = Mutex::create();
483 log.debug("Lock created");
485 memc = memcached_create(nullptr);
486 if (memc == nullptr) {
487 throw XMLToolingException("MemcacheBase::Memcache(): memcached_create() failed");
490 log.debug("Memcache created");
492 unsigned int hash = MEMCACHED_HASH_CRC;
493 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, hash);
494 log.debug("CRC hash set");
496 int32_t send_timeout = 999999;
497 const XMLCh* tag = e ? e->getAttributeNS(nullptr, sendTimeout) : nullptr;
499 send_timeout = XMLString::parseInt(tag);
501 log.debug("MEMCACHED_BEHAVIOR_SND_TIMEOUT will be set to %d", send_timeout);
502 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SND_TIMEOUT, send_timeout);
504 int32_t recv_timeout = 999999;
505 tag = e ? e->getAttributeNS(nullptr, sendTimeout) : nullptr;
507 recv_timeout = XMLString::parseInt(tag);
509 log.debug("MEMCACHED_BEHAVIOR_RCV_TIMEOUT will be set to %d", recv_timeout);
510 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, recv_timeout);
512 int32_t poll_timeout = 1000;
513 tag = e ? e->getAttributeNS(nullptr, pollTimeout) : nullptr;
515 poll_timeout = XMLString::parseInt(tag);
517 log.debug("MEMCACHED_BEHAVIOR_POLL_TIMEOUT will be set to %d", poll_timeout);
518 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, poll_timeout);
520 int32_t fail_limit = 5;
521 tag = e ? e->getAttributeNS(nullptr, failLimit) : nullptr;
523 fail_limit = XMLString::parseInt(tag);
525 log.debug("MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT will be set to %d", fail_limit);
526 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, fail_limit);
528 int32_t retry_timeout = 30;
529 tag = e ? e->getAttributeNS(nullptr, retryTimeout) : nullptr;
531 retry_timeout = XMLString::parseInt(tag);
533 log.debug("MEMCACHED_BEHAVIOR_RETRY_TIMEOUT will be set to %d", retry_timeout);
534 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, retry_timeout);
536 int32_t nonblock_set = 1;
537 tag = e ? e->getAttributeNS(nullptr, nonBlocking) : nullptr;
539 nonblock_set = XMLString::parseInt(tag);
541 log.debug("MEMCACHED_BEHAVIOR_NO_BLOCK will be set to %d", nonblock_set);
542 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, nonblock_set);
544 // Grab hosts from the configuration.
545 e = e ? XMLHelper::getFirstChildElement(e,Hosts) : nullptr;
546 if (!e || !e->hasChildNodes()) {
547 throw XMLToolingException("Memcache StorageService requires Hosts element in configuration.");
549 auto_ptr_char h(e->getFirstChild()->getNodeValue());
550 log.debug("INIT: GOT Hosts: %s", h.get());
551 memcached_server_st *servers;
552 servers = memcached_servers_parse(const_cast<char*>(h.get()));
553 log.debug("Got %u hosts.", memcached_server_list_count(servers));
554 if (memcached_server_push(memc, servers) != MEMCACHED_SUCCESS) {
555 throw IOException("MemcacheBase::Memcache(): memcached_server_push() failed");
557 memcached_server_list_free(servers);
559 log.debug("Memcache object initialized");
562 MemcacheBase::~MemcacheBase() {
563 memcached_free(memc);
565 log.debug("Base object destroyed");
568 MemcacheStorageService::MemcacheStorageService(const DOMElement* e)
569 : MemcacheBase(e), m_log(Category::getInstance("XMLTooling.MemcacheStorageService")), m_buildMap(false) {
571 const XMLCh* tag=e ? e->getAttributeNS(nullptr,buildMap) : nullptr;
572 if (tag && *tag && XMLString::parseInt(tag) != 0) {
574 m_log.debug("Cache built with buildMap ON");
579 MemcacheStorageService::~MemcacheStorageService() {
584 bool MemcacheStorageService::createString(const char* context, const char* key, const char* value, time_t expiration) {
586 log.debug("createString ctx: %s - key: %s", context, key);
588 string final_key = string(context) + ":" + string(key);
590 mc_record rec(value, expiration);
592 serialize(rec, final_value);
594 bool result = addMemcache(final_key.c_str(), final_value, expiration, 1); // the flag will be the version
596 if (result && m_buildMap) {
597 log.debug("Got result, updating map");
599 string map_name = context;
600 // we need to update the context map
601 if (! addLock(map_name)) {
602 log.error("Unable to get lock for context %s!", context);
603 deleteMemcache(final_key.c_str(), 0);
609 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
611 list<string> contents;
613 log.debug("Match found. Parsing...");
615 deserialize(ser_arr, contents);
617 log.debug("Iterating retrieved session map...");
618 list<string>::iterator iter;
619 for(iter = contents.begin();
620 iter != contents.end();
622 log.debug("value = " + *iter);
626 log.debug("New context: %s", map_name.c_str());
630 contents.push_back(key);
631 serialize(contents, ser_arr);
632 setMemcache(map_name.c_str(), ser_arr, expiration, 0);
634 deleteLock(map_name);
641 int MemcacheStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version) {
643 log.debug("readString ctx: %s - key: %s", context, key);
645 string final_key = string(context) + ":" + string(key);
646 uint32_t rec_version;
650 log.debug("Checking context");
652 string map_name = context;
655 bool ctx_found = getMemcache(map_name.c_str(), ser_arr, &flags);
662 bool found = getMemcache(final_key.c_str(), value, &rec_version);
667 if (version && rec_version <= (uint32_t)version) {
671 if (pexpiration || pvalue) {
673 deserialize(value, rec);
676 *pexpiration = rec.expiration;
688 int MemcacheStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version) {
690 log.debug("updateString ctx: %s - key: %s", context, key);
692 time_t final_exp = expiration;
693 time_t *want_expiration = nullptr;
695 want_expiration = &final_exp;
698 int read_res = readString(context, key, nullptr, want_expiration, version);
705 if (version && version != read_res) {
710 // Proceding with update
711 string final_key = string(context) + ":" + string(key);
712 mc_record rec(value, final_exp);
714 serialize(rec, final_value);
716 replaceMemcache(final_key.c_str(), final_value, final_exp, ++version);
721 bool MemcacheStorageService::deleteString(const char* context, const char* key) {
723 log.debug("deleteString ctx: %s - key: %s", context, key);
725 string final_key = string(context) + ":" + string(key);
727 // Not updating context map, if there is one. There is no need.
729 return deleteMemcache(final_key.c_str(), 0);
733 void MemcacheStorageService::updateContext(const char* context, time_t expiration) {
735 log.debug("updateContext ctx: %s", context);
738 log.error("updateContext invoked on a Storage with no context map built!");
742 string map_name = context;
745 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
747 list<string> contents;
749 log.debug("Match found. Parsing...");
751 deserialize(ser_arr, contents);
753 log.debug("Iterating retrieved session map...");
754 list<string>::iterator iter;
755 for(iter = contents.begin();
756 iter != contents.end();
759 // Update expiration times
761 int read_res = readString(context, iter->c_str(), &value, nullptr, 0);
768 updateString(context, iter->c_str(), value.c_str(), expiration, read_res);
770 replaceMemcache(map_name.c_str(), ser_arr, expiration, flags);
775 void MemcacheStorageService::deleteContext(const char* context) {
777 log.debug("deleteContext ctx: %s", context);
780 log.error("deleteContext invoked on a Storage with no context map built!");
784 string map_name = context;
787 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
789 list<string> contents;
791 log.debug("Match found. Parsing...");
793 deserialize(ser_arr, contents);
795 log.debug("Iterating retrieved session map...");
796 list<string>::iterator iter;
797 for(iter = contents.begin();
798 iter != contents.end();
800 string final_key = map_name + *iter;
801 deleteMemcache(final_key.c_str(), 0);
804 deleteMemcache(map_name.c_str(), 0);
809 extern "C" int MCEXT_EXPORTS xmltooling_extension_init(void*) {
810 // Register this SS type
811 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("MEMCACHE", MemcacheStorageServiceFactory);
815 extern "C" void MCEXT_EXPORTS xmltooling_extension_term() {
816 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("MEMCACHE");