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.
24 * Storage Service using memcache (pre memcache tags).
27 #if defined (_MSC_VER) || defined(__BORLANDC__)
28 # include "config_win32.h"
34 # define _CRT_NONSTDC_NO_DEPRECATE 1
35 # define _CRT_SECURE_NO_DEPRECATE 1
36 # define MCEXT_EXPORTS __declspec(dllexport)
38 # define MCEXT_EXPORTS
41 #include <xmltooling/base.h>
45 #include <libmemcached/memcached.h>
46 #include <xercesc/util/XMLUniDefs.hpp>
48 #include <xmltooling/logging.h>
49 #include <xmltooling/unicode.h>
50 #include <xmltooling/XMLToolingConfig.h>
51 #include <xmltooling/util/NDC.h>
52 #include <xmltooling/util/StorageService.h>
53 #include <xmltooling/util/Threads.h>
54 #include <xmltooling/util/XMLHelper.h>
56 using namespace xmltooling::logging;
57 using namespace xmltooling;
58 using namespace xercesc;
61 namespace xmltooling {
62 static const XMLCh Hosts[] = UNICODE_LITERAL_5(H,o,s,t,s);
63 static const XMLCh prefix[] = UNICODE_LITERAL_6(p,r,e,f,i,x);
64 static const XMLCh buildMap[] = UNICODE_LITERAL_8(b,u,i,l,d,M,a,p);
65 static const XMLCh sendTimeout[] = UNICODE_LITERAL_11(s,e,n,d,T,i,m,e,o,u,t);
66 static const XMLCh recvTimeout[] = UNICODE_LITERAL_11(r,e,c,v,T,i,m,e,o,u,t);
67 static const XMLCh pollTimeout[] = UNICODE_LITERAL_11(p,o,l,l,T,i,m,e,o,u,t);
68 static const XMLCh failLimit[] = UNICODE_LITERAL_9(f,a,i,l,L,i,m,i,t);
69 static const XMLCh retryTimeout[] = UNICODE_LITERAL_12(r,e,t,r,y,T,i,m,e,o,u,t);
70 static const XMLCh nonBlocking[] = UNICODE_LITERAL_11(n,o,n,B,l,o,c,k,i,n,g);
77 mc_record(string _v, time_t _e) :
78 value(_v), expiration(_e)
84 MemcacheBase(const DOMElement* e);
87 bool addMemcache(const char *key,
91 bool use_prefix = true);
92 bool setMemcache(const char *key,
96 bool use_prefix = true);
97 bool replaceMemcache(const char *key,
101 bool use_prefix = true);
102 bool getMemcache(const char *key,
105 bool use_prefix = true);
106 bool deleteMemcache(const char *key,
108 bool use_prefix = true);
110 void serialize(mc_record &source, string &dest);
111 void serialize(list<string> &source, string &dest);
112 void deserialize(string &source, mc_record &dest);
113 void deserialize(string &source, list<string> &dest);
115 bool addLock(string what, bool use_prefix = true);
116 void deleteLock(string what, bool use_prefix = true);
119 const DOMElement* m_root; // can only use this during initialization
126 class MemcacheStorageService : public StorageService, public MemcacheBase {
129 MemcacheStorageService(const DOMElement* e);
130 ~MemcacheStorageService();
132 const Capabilities& getCapabilities() const {
136 bool createString(const char* context, const char* key, const char* value, time_t expiration);
137 int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0);
138 int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0);
139 bool deleteString(const char* context, const char* key);
141 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
142 return createString(context, key, value, expiration);
144 int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
145 return readString(context, key, pvalue, pexpiration, version);
147 int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
148 return updateString(context, key, value, expiration, version);
150 bool deleteText(const char* context, const char* key) {
151 return deleteString(context, key);
154 void reap(const char* context) {}
156 void updateContext(const char* context, time_t expiration);
157 void deleteContext(const char* context);
166 StorageService* MemcacheStorageServiceFactory(const DOMElement* const & e) {
167 return new MemcacheStorageService(e);
172 bool MemcacheBase::addLock(string what, bool use_prefix) {
173 string lock_name = what + ":LOCK";
174 string set_val = "1";
176 while (!addMemcache(lock_name.c_str(), set_val, 5, 0, use_prefix)) {
178 log.debug("Unable to get lock %s... FAILED.", lock_name.c_str());
181 log.debug("Unable to get lock %s... Retrying.", lock_name.c_str());
187 struct timeval tv = { 0, 100000 };
188 select(0, 0, 0, 0, &tv);
194 void MemcacheBase::deleteLock(string what, bool use_prefix) {
196 string lock_name = what + ":LOCK";
197 deleteMemcache(lock_name.c_str(), 0, use_prefix);
202 void MemcacheBase::deserialize(string &source, mc_record &dest) {
203 istringstream is(source, stringstream::in | stringstream::out);
204 is >> dest.expiration;
205 is.ignore(1); // ignore delimiter
206 dest.value = is.str().c_str() + is.tellg();
209 void MemcacheBase::deserialize(string &source, list<string> &dest) {
210 istringstream is(source, stringstream::in | stringstream::out);
218 void MemcacheBase::serialize(mc_record &source, string &dest) {
219 ostringstream os(stringstream::in | stringstream::out);
220 os << source.expiration;
221 os << "-"; // delimiter
226 void MemcacheBase::serialize(list<string> &source, string &dest) {
227 ostringstream os(stringstream::in | stringstream::out);
228 for(list<string>::iterator iter = source.begin(); iter != source.end(); iter++) {
229 if (iter != source.begin()) {
237 bool MemcacheBase::deleteMemcache(const char *key,
245 final_key = m_prefix + key;
251 rv = memcached_delete(memc, (char *)final_key.c_str(), final_key.length(), timeout);
254 if (rv == MEMCACHED_SUCCESS) {
256 } else if (rv == MEMCACHED_NOTFOUND) {
257 // Key wasn't there... No biggie.
259 } else if (rv == MEMCACHED_ERRNO) {
261 string error = string("Memcache::deleteMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
263 throw IOException(error);
265 string error = string("Memcache::deleteMemcache() Problems: ") + memcached_strerror(memc, rv);
267 throw IOException(error);
273 bool MemcacheBase::getMemcache(const char *key,
284 final_key = m_prefix + key;
290 result = memcached_get(memc, (char *)final_key.c_str(), final_key.length(), &len, flags, &rv);
293 if (rv == MEMCACHED_SUCCESS) {
297 } else if (rv == MEMCACHED_NOTFOUND) {
298 log.debug("Key %s not found in memcache...", key);
300 } else if (rv == MEMCACHED_ERRNO) {
302 string error = string("Memcache::getMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
304 throw IOException(error);
306 string error = string("Memcache::getMemcache() Problems: ") + memcached_strerror(memc, rv);
308 throw IOException(error);
314 bool MemcacheBase::addMemcache(const char *key,
325 final_key = m_prefix + key;
331 rv = memcached_add(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
334 if (rv == MEMCACHED_SUCCESS) {
336 } else if (rv == MEMCACHED_NOTSTORED) {
339 } else if (rv == MEMCACHED_ERRNO) {
341 string error = string("Memcache::addMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
343 throw IOException(error);
345 string error = string("Memcache::addMemcache() Problems: ") + memcached_strerror(memc, rv);
347 throw IOException(error);
353 bool MemcacheBase::setMemcache(const char *key,
364 final_key = m_prefix + key;
370 rv = memcached_set(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
373 if (rv == MEMCACHED_SUCCESS) {
375 } else if (rv == MEMCACHED_ERRNO) {
377 string error = string("Memcache::setMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
379 throw IOException(error);
381 string error = string("Memcache::setMemcache() Problems: ") + memcached_strerror(memc, rv);
383 throw IOException(error);
389 bool MemcacheBase::replaceMemcache(const char *key,
400 final_key = m_prefix + key;
406 rv = memcached_replace(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
409 if (rv == MEMCACHED_SUCCESS) {
411 } else if (rv == MEMCACHED_NOTSTORED) {
414 } else if (rv == MEMCACHED_ERRNO) {
416 string error = string("Memcache::replaceMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
418 throw IOException(error);
420 string error = string("Memcache::replaceMemcache() Problems: ") + memcached_strerror(memc, rv);
422 throw IOException(error);
428 MemcacheBase::MemcacheBase(const DOMElement* e) : m_root(e), log(Category::getInstance("XMLTooling.StorageService.MEMCACHE")), m_prefix("") {
430 auto_ptr_char p(e ? e->getAttributeNS(nullptr,prefix) : nullptr);
431 if (p.get() && *p.get()) {
432 log.debug("INIT: GOT key prefix: %s", p.get());
436 m_lock = Mutex::create();
437 log.debug("Lock created");
439 memc = memcached_create(nullptr);
440 if (memc == nullptr) {
441 throw XMLToolingException("MemcacheBase::Memcache(): memcached_create() failed");
444 log.debug("Memcache created");
446 unsigned int hash = MEMCACHED_HASH_CRC;
447 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, hash);
448 log.debug("CRC hash set");
450 int32_t send_timeout = 999999;
451 const XMLCh* tag = e ? e->getAttributeNS(nullptr, sendTimeout) : nullptr;
453 send_timeout = XMLString::parseInt(tag);
455 log.debug("MEMCACHED_BEHAVIOR_SND_TIMEOUT will be set to %d", send_timeout);
456 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SND_TIMEOUT, send_timeout);
458 int32_t recv_timeout = 999999;
459 tag = e ? e->getAttributeNS(nullptr, sendTimeout) : nullptr;
461 recv_timeout = XMLString::parseInt(tag);
463 log.debug("MEMCACHED_BEHAVIOR_RCV_TIMEOUT will be set to %d", recv_timeout);
464 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, recv_timeout);
466 int32_t poll_timeout = 1000;
467 tag = e ? e->getAttributeNS(nullptr, pollTimeout) : nullptr;
469 poll_timeout = XMLString::parseInt(tag);
471 log.debug("MEMCACHED_BEHAVIOR_POLL_TIMEOUT will be set to %d", poll_timeout);
472 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, poll_timeout);
474 int32_t fail_limit = 5;
475 tag = e ? e->getAttributeNS(nullptr, failLimit) : nullptr;
477 fail_limit = XMLString::parseInt(tag);
479 log.debug("MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT will be set to %d", fail_limit);
480 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, fail_limit);
482 int32_t retry_timeout = 30;
483 tag = e ? e->getAttributeNS(nullptr, retryTimeout) : nullptr;
485 retry_timeout = XMLString::parseInt(tag);
487 log.debug("MEMCACHED_BEHAVIOR_RETRY_TIMEOUT will be set to %d", retry_timeout);
488 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, retry_timeout);
490 int32_t nonblock_set = 1;
491 tag = e ? e->getAttributeNS(nullptr, nonBlocking) : nullptr;
493 nonblock_set = XMLString::parseInt(tag);
495 log.debug("MEMCACHED_BEHAVIOR_NO_BLOCK will be set to %d", nonblock_set);
496 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, nonblock_set);
498 // Grab hosts from the configuration.
499 e = e ? XMLHelper::getFirstChildElement(e,Hosts) : nullptr;
500 if (!e || !e->hasChildNodes()) {
501 throw XMLToolingException("Memcache StorageService requires Hosts element in configuration.");
503 auto_ptr_char h(e->getFirstChild()->getNodeValue());
504 log.debug("INIT: GOT Hosts: %s", h.get());
505 memcached_server_st *servers;
506 servers = memcached_servers_parse(const_cast<char*>(h.get()));
507 log.debug("Got %u hosts.", memcached_server_list_count(servers));
508 if (memcached_server_push(memc, servers) != MEMCACHED_SUCCESS) {
509 throw IOException("MemcacheBase::Memcache(): memcached_server_push() failed");
511 memcached_server_list_free(servers);
513 log.debug("Memcache object initialized");
516 MemcacheBase::~MemcacheBase() {
517 memcached_free(memc);
519 log.debug("Base object destroyed");
522 MemcacheStorageService::MemcacheStorageService(const DOMElement* e)
523 : MemcacheBase(e), m_log(Category::getInstance("XMLTooling.StorageService.MEMCACHE")),
524 m_caps(80, 250 - m_prefix.length() - 1 - 80, 255),
525 m_buildMap(XMLHelper::getAttrBool(e, false, buildMap)) {
527 m_log.debug("Cache built with buildMap ON");
530 MemcacheStorageService::~MemcacheStorageService() {
533 bool MemcacheStorageService::createString(const char* context, const char* key, const char* value, time_t expiration) {
535 log.debug("createString ctx: %s - key: %s", context, key);
537 string final_key = string(context) + ":" + string(key);
539 mc_record rec(value, expiration);
541 serialize(rec, final_value);
543 bool result = addMemcache(final_key.c_str(), final_value, expiration, 1); // the flag will be the version
545 if (result && m_buildMap) {
546 log.debug("Got result, updating map");
548 string map_name = context;
549 // we need to update the context map
550 if (! addLock(map_name)) {
551 log.error("Unable to get lock for context %s!", context);
552 deleteMemcache(final_key.c_str(), 0);
558 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
560 list<string> contents;
562 log.debug("Match found. Parsing...");
564 deserialize(ser_arr, contents);
566 log.debug("Iterating retrieved session map...");
567 list<string>::iterator iter;
568 for(iter = contents.begin();
569 iter != contents.end();
571 log.debug("value = " + *iter);
575 log.debug("New context: %s", map_name.c_str());
579 contents.push_back(key);
580 serialize(contents, ser_arr);
581 setMemcache(map_name.c_str(), ser_arr, expiration, 0);
583 deleteLock(map_name);
590 int MemcacheStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version) {
592 log.debug("readString ctx: %s - key: %s", context, key);
594 string final_key = string(context) + ":" + string(key);
595 uint32_t rec_version;
599 log.debug("Checking context");
601 string map_name = context;
604 bool ctx_found = getMemcache(map_name.c_str(), ser_arr, &flags);
611 bool found = getMemcache(final_key.c_str(), value, &rec_version);
616 if (version && rec_version <= (uint32_t)version) {
620 if (pexpiration || pvalue) {
622 deserialize(value, rec);
625 *pexpiration = rec.expiration;
637 int MemcacheStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version) {
639 log.debug("updateString ctx: %s - key: %s", context, key);
641 time_t final_exp = expiration;
642 time_t *want_expiration = nullptr;
644 want_expiration = &final_exp;
647 int read_res = readString(context, key, nullptr, want_expiration, version);
654 if (version && version != read_res) {
659 // Proceding with update
660 string final_key = string(context) + ":" + string(key);
661 mc_record rec(value, final_exp);
663 serialize(rec, final_value);
665 replaceMemcache(final_key.c_str(), final_value, final_exp, ++version);
670 bool MemcacheStorageService::deleteString(const char* context, const char* key) {
672 log.debug("deleteString ctx: %s - key: %s", context, key);
674 string final_key = string(context) + ":" + string(key);
676 // Not updating context map, if there is one. There is no need.
678 return deleteMemcache(final_key.c_str(), 0);
682 void MemcacheStorageService::updateContext(const char* context, time_t expiration) {
684 log.debug("updateContext ctx: %s", context);
687 log.error("updateContext invoked on a Storage with no context map built!");
691 string map_name = context;
694 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
696 list<string> contents;
698 log.debug("Match found. Parsing...");
700 deserialize(ser_arr, contents);
702 log.debug("Iterating retrieved session map...");
703 list<string>::iterator iter;
704 for(iter = contents.begin();
705 iter != contents.end();
708 // Update expiration times
710 int read_res = readString(context, iter->c_str(), &value, nullptr, 0);
717 updateString(context, iter->c_str(), value.c_str(), expiration, read_res);
719 replaceMemcache(map_name.c_str(), ser_arr, expiration, flags);
724 void MemcacheStorageService::deleteContext(const char* context) {
726 log.debug("deleteContext ctx: %s", context);
729 log.error("deleteContext invoked on a Storage with no context map built!");
733 string map_name = context;
736 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
738 list<string> contents;
740 log.debug("Match found. Parsing...");
742 deserialize(ser_arr, contents);
744 log.debug("Iterating retrieved session map...");
745 list<string>::iterator iter;
746 for(iter = contents.begin();
747 iter != contents.end();
749 string final_key = map_name + *iter;
750 deleteMemcache(final_key.c_str(), 0);
753 deleteMemcache(map_name.c_str(), 0);
758 extern "C" int MCEXT_EXPORTS xmltooling_extension_init(void*) {
759 // Register this SS type
760 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("MEMCACHE", MemcacheStorageServiceFactory);
764 extern "C" void MCEXT_EXPORTS xmltooling_extension_term() {
765 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("MEMCACHE");