2 * Copyright 2001-2009 Internet2
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
18 * memcache-store.cpp
\r
20 * Storage Service using memcache (pre memcache tags)
\r
23 #if defined (_MSC_VER) || defined(__BORLANDC__)
\r
24 # include "config_win32.h"
\r
26 # include "config.h"
\r
30 # define _CRT_NONSTDC_NO_DEPRECATE 1
\r
31 # define _CRT_SECURE_NO_DEPRECATE 1
\r
32 # define MCEXT_EXPORTS __declspec(dllexport)
\r
34 # define MCEXT_EXPORTS
\r
37 #include <xmltooling/base.h>
\r
40 #include <iostream>
\r
41 #include <libmemcached/memcached.h>
\r
42 #include <xercesc/util/XMLUniDefs.hpp>
\r
44 #include <xmltooling/logging.h>
\r
45 #include <xmltooling/unicode.h>
\r
46 #include <xmltooling/XMLToolingConfig.h>
\r
47 #include <xmltooling/util/NDC.h>
\r
48 #include <xmltooling/util/StorageService.h>
\r
49 #include <xmltooling/util/Threads.h>
\r
50 #include <xmltooling/util/XMLHelper.h>
\r
52 using namespace xmltooling::logging;
\r
53 using namespace xmltooling;
\r
54 using namespace xercesc;
\r
55 using namespace std;
\r
57 namespace xmltooling {
\r
58 static const XMLCh Hosts[] = UNICODE_LITERAL_5(H,o,s,t,s);
\r
59 static const XMLCh prefix[] = UNICODE_LITERAL_6(p,r,e,f,i,x);
\r
60 static const XMLCh buildMap[] = UNICODE_LITERAL_8(b,u,i,l,d,M,a,p);
\r
61 static const XMLCh sendTimeout[] = UNICODE_LITERAL_11(s,e,n,d,T,i,m,e,o,u,t);
\r
62 static const XMLCh recvTimeout[] = UNICODE_LITERAL_11(r,e,c,v,T,i,m,e,o,u,t);
\r
63 static const XMLCh pollTimeout[] = UNICODE_LITERAL_11(p,o,l,l,T,i,m,e,o,u,t);
\r
64 static const XMLCh failLimit[] = UNICODE_LITERAL_9(f,a,i,l,L,i,m,i,t);
\r
65 static const XMLCh retryTimeout[] = UNICODE_LITERAL_12(r,e,t,r,y,T,i,m,e,o,u,t);
\r
72 mc_record(string _v, time_t _e) :
\r
73 value(_v), expiration(_e)
\r
77 class MemcacheBase {
\r
79 MemcacheBase(const DOMElement* e);
\r
82 bool addMemcache(const char *key,
\r
86 bool use_prefix = true);
\r
87 bool setMemcache(const char *key,
\r
91 bool use_prefix = true);
\r
92 bool replaceMemcache(const char *key,
\r
96 bool use_prefix = true);
\r
97 bool getMemcache(const char *key,
\r
100 bool use_prefix = true);
\r
101 bool deleteMemcache(const char *key,
\r
103 bool use_prefix = true);
\r
105 void serialize(mc_record &source, string &dest);
\r
106 void serialize(list<string> &source, string &dest);
\r
107 void deserialize(string &source, mc_record &dest);
\r
108 void deserialize(string &source, list<string> &dest);
\r
110 bool addSessionToUser(string &key, string &user);
\r
111 bool addLock(string what, bool use_prefix = true);
\r
112 void deleteLock(string what, bool use_prefix = true);
\r
115 const DOMElement* m_root; // can only use this during initialization
\r
117 memcached_st *memc;
\r
122 class MemcacheStorageService : public StorageService, public MemcacheBase {
\r
125 MemcacheStorageService(const DOMElement* e);
\r
126 ~MemcacheStorageService();
\r
128 bool createString(const char* context, const char* key, const char* value, time_t expiration);
\r
129 int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0);
\r
130 int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0);
\r
131 bool deleteString(const char* context, const char* key);
\r
133 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
\r
134 return createString(context, key, value, expiration);
\r
136 int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
\r
137 return readString(context, key, pvalue, pexpiration, version);
\r
139 int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
\r
140 return updateString(context, key, value, expiration, version);
\r
142 bool deleteText(const char* context, const char* key) {
\r
143 return deleteString(context, key);
\r
146 void reap(const char* context) {}
\r
148 void updateContext(const char* context, time_t expiration);
\r
149 void deleteContext(const char* context);
\r
159 StorageService* MemcacheStorageServiceFactory(const DOMElement* const & e) {
\r
160 return new MemcacheStorageService(e);
\r
165 bool MemcacheBase::addLock(string what, bool use_prefix) {
\r
166 string lock_name = what + ":LOCK";
\r
167 string set_val = "1";
\r
168 unsigned tries = 5;
\r
169 while (!addMemcache(lock_name.c_str(), set_val, 5, 0, use_prefix)) {
\r
170 if (tries-- == 0) {
\r
171 log.debug("Unable to get lock %s... FAILED.", lock_name.c_str());
\r
174 log.debug("Unable to get lock %s... Retrying.", lock_name.c_str());
\r
180 struct timeval tv = { 0, 100000 };
\r
181 select(0, 0, 0, 0, &tv);
\r
187 void MemcacheBase::deleteLock(string what, bool use_prefix) {
\r
189 string lock_name = what + ":LOCK";
\r
190 deleteMemcache(lock_name.c_str(), 0, use_prefix);
\r
195 void MemcacheBase::deserialize(string &source, mc_record &dest) {
\r
196 istringstream is(source, stringstream::in | stringstream::out);
\r
197 is >> dest.expiration;
\r
198 is.ignore(1); // ignore delimiter
\r
199 dest.value = is.str().c_str() + is.tellg();
\r
202 void MemcacheBase::deserialize(string &source, list<string> &dest) {
\r
203 istringstream is(source, stringstream::in | stringstream::out);
\r
204 while (!is.eof()) {
\r
211 void MemcacheBase::serialize(mc_record &source, string &dest) {
\r
212 ostringstream os(stringstream::in | stringstream::out);
\r
213 os << source.expiration;
\r
214 os << "-"; // delimiter
\r
215 os << source.value;
\r
219 void MemcacheBase::serialize(list<string> &source, string &dest) {
\r
220 ostringstream os(stringstream::in | stringstream::out);
\r
221 for(list<string>::iterator iter = source.begin(); iter != source.end(); iter++) {
\r
222 if (iter != source.begin()) {
\r
230 bool MemcacheBase::addSessionToUser(string &key, string &user) {
\r
232 if (! addLock(user, false)) {
\r
238 string sessid = m_prefix + key; // add specific prefix to session
\r
239 string delimiter = ";";
\r
240 string user_key = "UDATA:";
\r
244 bool result = getMemcache(user_key.c_str(), user_val, &flags, false);
\r
247 bool already_there = false;
\r
248 // skip delimiters at beginning.
\r
249 string::size_type lastPos = user_val.find_first_not_of(delimiter, 0);
\r
251 // find first "non-delimiter".
\r
252 string::size_type pos = user_val.find_first_of(delimiter, lastPos);
\r
254 while (string::npos != pos || string::npos != lastPos) {
\r
255 // found a token, add it to the vector.
\r
256 string session = user_val.substr(lastPos, pos - lastPos);
\r
257 if (strcmp(session.c_str(), sessid.c_str()) == 0) {
\r
258 already_there = true;
\r
262 // skip delimiters. Note the "not_of"
\r
263 lastPos = user_val.find_first_not_of(delimiter, pos);
\r
265 // find next "non-delimiter"
\r
266 pos = user_val.find_first_of(delimiter, lastPos);
\r
269 if (!already_there) {
\r
270 user_val += delimiter + sessid;
\r
271 replaceMemcache(user_key.c_str(), user_val, 0, 0, false);
\r
274 addMemcache(user_key.c_str(), sessid, 0, 0, false);
\r
277 deleteLock(user, false);
\r
282 bool MemcacheBase::deleteMemcache(const char *key,
\r
285 memcached_return rv;
\r
290 final_key = m_prefix + key;
\r
296 rv = memcached_delete(memc, (char *)final_key.c_str(), final_key.length(), timeout);
\r
299 if (rv == MEMCACHED_SUCCESS) {
\r
301 } else if (rv == MEMCACHED_NOTFOUND) {
\r
302 // Key wasn't there... No biggie.
\r
304 } else if (rv == MEMCACHED_ERRNO) {
\r
306 string error = string("Memcache::deleteMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
\r
308 throw IOException(error);
\r
310 string error = string("Memcache::deleteMemcache() Problems: ") + memcached_strerror(memc, rv);
\r
312 throw IOException(error);
\r
318 bool MemcacheBase::getMemcache(const char *key,
\r
322 memcached_return rv;
\r
329 final_key = m_prefix + key;
\r
335 result = memcached_get(memc, (char *)final_key.c_str(), final_key.length(), &len, flags, &rv);
\r
338 if (rv == MEMCACHED_SUCCESS) {
\r
342 } else if (rv == MEMCACHED_NOTFOUND) {
\r
343 log.debug("Key %s not found in memcache...", key);
\r
345 } else if (rv == MEMCACHED_ERRNO) {
\r
347 string error = string("Memcache::getMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
\r
349 throw IOException(error);
\r
351 string error = string("Memcache::getMemcache() Problems: ") + memcached_strerror(memc, rv);
\r
353 throw IOException(error);
\r
359 bool MemcacheBase::addMemcache(const char *key,
\r
365 memcached_return rv;
\r
370 final_key = m_prefix + key;
\r
376 rv = memcached_add(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
\r
379 if (rv == MEMCACHED_SUCCESS) {
\r
381 } else if (rv == MEMCACHED_NOTSTORED) {
\r
384 } else if (rv == MEMCACHED_ERRNO) {
\r
386 string error = string("Memcache::addMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
\r
388 throw IOException(error);
\r
390 string error = string("Memcache::addMemcache() Problems: ") + memcached_strerror(memc, rv);
\r
392 throw IOException(error);
\r
398 bool MemcacheBase::setMemcache(const char *key,
\r
404 memcached_return rv;
\r
409 final_key = m_prefix + key;
\r
415 rv = memcached_set(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
\r
418 if (rv == MEMCACHED_SUCCESS) {
\r
420 } else if (rv == MEMCACHED_ERRNO) {
\r
422 string error = string("Memcache::setMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
\r
424 throw IOException(error);
\r
426 string error = string("Memcache::setMemcache() Problems: ") + memcached_strerror(memc, rv);
\r
428 throw IOException(error);
\r
434 bool MemcacheBase::replaceMemcache(const char *key,
\r
440 memcached_return rv;
\r
445 final_key = m_prefix + key;
\r
451 rv = memcached_replace(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
\r
454 if (rv == MEMCACHED_SUCCESS) {
\r
456 } else if (rv == MEMCACHED_NOTSTORED) {
\r
459 } else if (rv == MEMCACHED_ERRNO) {
\r
461 string error = string("Memcache::replaceMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
\r
463 throw IOException(error);
\r
465 string error = string("Memcache::replaceMemcache() Problems: ") + memcached_strerror(memc, rv);
\r
467 throw IOException(error);
\r
473 MemcacheBase::MemcacheBase(const DOMElement* e) : m_root(e), log(Category::getInstance("XMLTooling.MemcacheBase")), m_prefix("") {
\r
475 auto_ptr_char p(e ? e->getAttributeNS(NULL,prefix) : NULL);
\r
476 if (p.get() && *p.get()) {
\r
477 log.debug("INIT: GOT key prefix: %s", p.get());
\r
478 m_prefix = p.get();
\r
481 m_lock = Mutex::create();
\r
482 log.debug("Lock created");
\r
484 memc = memcached_create(NULL);
\r
485 if (memc == NULL) {
\r
486 throw XMLToolingException("MemcacheBase::Memcache(): memcached_create() failed");
\r
489 log.debug("Memcache created");
\r
491 unsigned int hash = MEMCACHED_HASH_CRC;
\r
492 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, hash);
\r
493 log.debug("CRC hash set");
\r
495 int32_t send_timeout = 1000000;
\r
496 const XMLCh* tag = e ? e->getAttributeNS(NULL, sendTimeout) : NULL;
\r
498 send_timeout = XMLString::parseInt(tag);
\r
500 log.debug("MEMCACHED_BEHAVIOR_SND_TIMEOUT will be set to %d", send_timeout);
\r
501 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SND_TIMEOUT, send_timeout);
\r
503 int32_t recv_timeout = 1000000;
\r
504 tag = e ? e->getAttributeNS(NULL, sendTimeout) : NULL;
\r
506 recv_timeout = XMLString::parseInt(tag);
\r
508 log.debug("MEMCACHED_BEHAVIOR_RCV_TIMEOUT will be set to %d", recv_timeout);
\r
509 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, recv_timeout);
\r
511 int32_t poll_timeout = 1000;
\r
512 tag = e ? e->getAttributeNS(NULL, pollTimeout) : NULL;
\r
514 poll_timeout = XMLString::parseInt(tag);
\r
516 log.debug("MEMCACHED_BEHAVIOR_POLL_TIMEOUT will be set to %d", poll_timeout);
\r
517 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, poll_timeout);
\r
519 int32_t fail_limit = 5;
\r
520 tag = e ? e->getAttributeNS(NULL, failLimit) : NULL;
\r
522 fail_limit = XMLString::parseInt(tag);
\r
524 log.debug("MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT will be set to %d", fail_limit);
\r
525 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, fail_limit);
\r
527 int32_t retry_timeout = 30;
\r
528 tag = e ? e->getAttributeNS(NULL, retryTimeout) : NULL;
\r
530 retry_timeout = XMLString::parseInt(tag);
\r
532 log.debug("MEMCACHED_BEHAVIOR_RETRY_TIMEOUT will be set to %d", retry_timeout);
\r
533 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, retry_timeout);
\r
535 // Grab hosts from the configuration.
\r
536 e = e ? XMLHelper::getFirstChildElement(e,Hosts) : NULL;
\r
537 if (!e || !e->hasChildNodes()) {
\r
538 throw XMLToolingException("Memcache StorageService requires Hosts element in configuration.");
\r
540 auto_ptr_char h(e->getFirstChild()->getNodeValue());
\r
541 log.debug("INIT: GOT Hosts: %s", h.get());
\r
542 memcached_server_st *servers;
\r
543 servers = memcached_servers_parse(const_cast<char*>(h.get()));
\r
544 log.debug("Got %u hosts.", memcached_server_list_count(servers));
\r
545 if (memcached_server_push(memc, servers) != MEMCACHED_SUCCESS) {
\r
546 throw IOException("MemcacheBase::Memcache(): memcached_server_push() failed");
\r
548 memcached_server_list_free(servers);
\r
550 log.debug("Memcache object initialized");
\r
553 MemcacheBase::~MemcacheBase() {
\r
554 memcached_free(memc);
\r
556 log.debug("Base object destroyed");
\r
559 MemcacheStorageService::MemcacheStorageService(const DOMElement* e)
\r
560 : MemcacheBase(e), m_log(Category::getInstance("XMLTooling.MemcacheStorageService")), m_buildMap(false) {
\r
562 const XMLCh* tag=e ? e->getAttributeNS(NULL,buildMap) : NULL;
\r
563 if (tag && *tag && XMLString::parseInt(tag) != 0) {
\r
565 m_log.debug("Cache built with buildMap ON");
\r
570 MemcacheStorageService::~MemcacheStorageService() {
\r
575 bool MemcacheStorageService::createString(const char* context, const char* key, const char* value, time_t expiration) {
\r
577 log.debug("createString ctx: %s - key: %s", context, key);
\r
579 string final_key = string(context) + ":" + string(key);
\r
581 mc_record rec(value, expiration);
\r
582 string final_value;
\r
583 serialize(rec, final_value);
\r
585 bool result = addMemcache(final_key.c_str(), final_value, expiration, 1); // the flag will be the version
\r
587 if (result && m_buildMap) {
\r
588 log.debug("Got result, updating map");
\r
590 string map_name = context;
\r
591 // we need to update the context map
\r
592 if (! addLock(map_name)) {
\r
593 log.error("Unable to get lock for context %s!", context);
\r
594 deleteMemcache(final_key.c_str(), 0);
\r
600 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
\r
602 list<string> contents;
\r
604 log.debug("Match found. Parsing...");
\r
606 deserialize(ser_arr, contents);
\r
608 log.debug("Iterating retrieved session map...");
\r
609 list<string>::iterator iter;
\r
610 for(iter = contents.begin();
\r
611 iter != contents.end();
\r
613 log.debug("value = " + *iter);
\r
617 log.debug("New context: %s", map_name.c_str());
\r
621 contents.push_back(key);
\r
622 serialize(contents, ser_arr);
\r
623 setMemcache(map_name.c_str(), ser_arr, expiration, 0);
\r
625 deleteLock(map_name);
\r
632 int MemcacheStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version) {
\r
634 log.debug("readString ctx: %s - key: %s", context, key);
\r
636 string final_key = string(context) + ":" + string(key);
\r
637 uint32_t rec_version;
\r
641 log.debug("Checking context");
\r
643 string map_name = context;
\r
646 bool ctx_found = getMemcache(map_name.c_str(), ser_arr, &flags);
\r
653 bool found = getMemcache(final_key.c_str(), value, &rec_version);
\r
658 if (version && rec_version <= (uint32_t)version) {
\r
662 if (pexpiration || pvalue) {
\r
664 deserialize(value, rec);
\r
667 *pexpiration = rec.expiration;
\r
671 *pvalue = rec.value;
\r
675 return rec_version;
\r
679 int MemcacheStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version) {
\r
681 log.debug("updateString ctx: %s - key: %s", context, key);
\r
683 time_t final_exp = expiration;
\r
684 time_t *want_expiration = NULL;
\r
686 want_expiration = &final_exp;
\r
689 int read_res = readString(context, key, NULL, want_expiration, version);
\r
696 if (version && version != read_res) {
\r
697 // version incorrect
\r
701 // Proceding with update
\r
702 string final_key = string(context) + ":" + string(key);
\r
703 mc_record rec(value, final_exp);
\r
704 string final_value;
\r
705 serialize(rec, final_value);
\r
707 replaceMemcache(final_key.c_str(), final_value, final_exp, ++version);
\r
712 bool MemcacheStorageService::deleteString(const char* context, const char* key) {
\r
714 log.debug("deleteString ctx: %s - key: %s", context, key);
\r
716 string final_key = string(context) + ":" + string(key);
\r
718 // Not updating context map, if there is one. There is no need.
\r
720 return deleteMemcache(final_key.c_str(), 0);
\r
724 void MemcacheStorageService::updateContext(const char* context, time_t expiration) {
\r
726 log.debug("updateContext ctx: %s", context);
\r
729 log.error("updateContext invoked on a Storage with no context map built!");
\r
733 string map_name = context;
\r
736 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
\r
738 list<string> contents;
\r
740 log.debug("Match found. Parsing...");
\r
742 deserialize(ser_arr, contents);
\r
744 log.debug("Iterating retrieved session map...");
\r
745 list<string>::iterator iter;
\r
746 for(iter = contents.begin();
\r
747 iter != contents.end();
\r
750 // Update expiration times
\r
752 int read_res = readString(context, iter->c_str(), &value, NULL, 0);
\r
759 updateString(context, iter->c_str(), value.c_str(), expiration, read_res);
\r
761 replaceMemcache(map_name.c_str(), ser_arr, expiration, flags);
\r
766 void MemcacheStorageService::deleteContext(const char* context) {
\r
768 log.debug("deleteContext ctx: %s", context);
\r
771 log.error("deleteContext invoked on a Storage with no context map built!");
\r
775 string map_name = context;
\r
778 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
\r
780 list<string> contents;
\r
782 log.debug("Match found. Parsing...");
\r
784 deserialize(ser_arr, contents);
\r
786 log.debug("Iterating retrieved session map...");
\r
787 list<string>::iterator iter;
\r
788 for(iter = contents.begin();
\r
789 iter != contents.end();
\r
791 string final_key = map_name + *iter;
\r
792 deleteMemcache(final_key.c_str(), 0);
\r
795 deleteMemcache(map_name.c_str(), 0);
\r
800 extern "C" int MCEXT_EXPORTS xmltooling_extension_init(void*) {
\r
801 // Register this SS type
\r
802 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("MEMCACHE", MemcacheStorageServiceFactory);
\r
806 extern "C" void MCEXT_EXPORTS xmltooling_extension_term() {
\r
807 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("MEMCACHE");
\r