diff options
author | Kim van der Riet <kpvdr@apache.org> | 2013-02-28 16:14:30 +0000 |
---|---|---|
committer | Kim van der Riet <kpvdr@apache.org> | 2013-02-28 16:14:30 +0000 |
commit | 9c73ef7a5ac10acd6a50d5d52bd721fc2faa5919 (patch) | |
tree | 2a890e1df09e5b896a9b4168a7b22648f559a1f2 /cpp/src/qpid | |
parent | 172d9b2a16cfb817bbe632d050acba7e31401cd2 (diff) | |
download | qpid-python-asyncstore.tar.gz |
Update from trunk r1375509 through r1450773asyncstore
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/asyncstore@1451244 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp/src/qpid')
422 files changed, 36978 insertions, 6038 deletions
diff --git a/cpp/src/qpid/Modules.cpp b/cpp/src/qpid/Modules.cpp index 727e05d212..049ededaa7 100644 --- a/cpp/src/qpid/Modules.cpp +++ b/cpp/src/qpid/Modules.cpp @@ -24,11 +24,7 @@ #include "qpid/Exception.h" #include "qpid/log/Statement.h" #include "qpid/sys/Shlib.h" - -#include <boost/filesystem/operations.hpp> -#include <boost/filesystem/path.hpp> - -namespace fs=boost::filesystem; +#include "qpid/sys/FileSysDir.h" namespace { @@ -43,7 +39,7 @@ inline std::string& suffix() { } bool isShlibName(const std::string& name) { - return name.find (suffix()) == name.length() - suffix().length(); + return name.substr(name.size()-suffix().size()) == suffix(); } } @@ -59,39 +55,40 @@ ModuleOptions::ModuleOptions(const std::string& defaultModuleDir) ("no-module-dir", optValue(noLoad), "Don't load modules from module directory"); } -void tryShlib(const char* libname_, bool noThrow) { - std::string libname(libname_); - if (!isShlibName(libname)) libname += suffix(); +void tryShlib(const std::string& libname) { + sys::Shlib shlib( isShlibName(libname) ? libname : (libname + suffix())); +} + +namespace { + +void tryOnlyShlib(const std::string& libname) throw() { try { - sys::Shlib shlib(libname); + if (isShlibName(libname)) sys::Shlib shlib( libname ); } catch (const std::exception& /*e*/) { - if (!noThrow) - throw; } } +} + void loadModuleDir (std::string dirname, bool isDefault) { - fs::path dirPath (dirname, fs::native); - if (!fs::exists (dirPath)) + sys::FileSysDir dirPath (dirname); + + bool exists; + try { - if (isDefault) - return; - throw Exception ("Directory not found: " + dirname); + exists = dirPath.exists(); + } catch (Exception& e) { + throw Exception ("Invalid value for module-dir: " + e.getMessage()); } - if (!fs::is_directory(dirPath)) - { - throw Exception ("Invalid value for module-dir: " + dirname + " is not a directory"); + if (!exists) { + if (isDefault) return; + throw Exception ("Directory not found: " + dirname); } - fs::directory_iterator endItr; - for (fs::directory_iterator itr (dirPath); itr != endItr; ++itr) - { - if (!fs::is_directory(*itr) && isShlibName(itr->string())) - tryShlib (itr->string().data(), true); - } + dirPath.forEachFile(&tryOnlyShlib); } } // namespace qpid diff --git a/cpp/src/qpid/Modules.h b/cpp/src/qpid/Modules.h index 159dd156c1..9fb91d60eb 100644 --- a/cpp/src/qpid/Modules.h +++ b/cpp/src/qpid/Modules.h @@ -36,7 +36,7 @@ struct ModuleOptions : public qpid::Options { QPID_COMMON_EXTERN ModuleOptions(const std::string& defaultModuleDir); }; -QPID_COMMON_EXTERN void tryShlib(const char* libname, bool noThrow); +QPID_COMMON_EXTERN void tryShlib(const std::string& libname); QPID_COMMON_EXTERN void loadModuleDir (std::string dirname, bool isDefault); } // namespace qpid diff --git a/cpp/src/qpid/NullSaslServer.cpp b/cpp/src/qpid/NullSaslServer.cpp new file mode 100644 index 0000000000..40bd9ebbc6 --- /dev/null +++ b/cpp/src/qpid/NullSaslServer.cpp @@ -0,0 +1,86 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "NullSaslServer.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/SecurityLayer.h" +#include <assert.h> +#include <boost/format.hpp> + +namespace qpid { +NullSaslServer::NullSaslServer(const std::string& r) : realm(r) {} +NullSaslServer::Status NullSaslServer::start(const std::string& mechanism, const std::string* response, std::string& /*challenge*/) +{ + if (mechanism == "PLAIN") { + if (response) { + std::string uid; + std::string::size_type i = response->find((char)0); + if (i == 0 && response->size() > 1) { + //no authorization id; use authentication id + i = response->find((char)0, 1); + if (i != std::string::npos) uid = response->substr(1, i-1); + } else if (i != std::string::npos) { + //authorization id is first null delimited field + uid = response->substr(0, i); + } else { + QPID_LOG(error, "Invalid PLAIN request, null delimiter not found in response data"); + return FAIL; + } + if (!uid.empty()) { + //append realm if it has not already been added + i = uid.find(realm); + if (i == std::string::npos || realm.size() + i < uid.size()) { + uid = boost::str(boost::format("%1%@%2%") % uid % realm); + } + userid = uid; + } + return OK; + } else { + QPID_LOG(error, "Invalid PLAIN request, expected response containing user credentials"); + return FAIL; + } + } else if (mechanism == "ANONYMOUS") { + userid = "anonymous"; + return OK; + } else { + return FAIL; + } +} + +NullSaslServer::Status NullSaslServer::step(const std::string* /*response*/, std::string& /*challenge*/) +{ + assert(false); + return FAIL; +} +std::string NullSaslServer::getMechanisms() +{ + return std::string("ANONYMOUS PLAIN"); +} +std::string NullSaslServer::getUserid() +{ + return userid; +} + +std::auto_ptr<qpid::sys::SecurityLayer> NullSaslServer::getSecurityLayer(size_t) +{ + return std::auto_ptr<qpid::sys::SecurityLayer>(); +} + +} // namespace qpid diff --git a/cpp/src/qpid/NullSaslServer.h b/cpp/src/qpid/NullSaslServer.h new file mode 100644 index 0000000000..810defe574 --- /dev/null +++ b/cpp/src/qpid/NullSaslServer.h @@ -0,0 +1,49 @@ +#ifndef QPID_NULLSASLSERVER_H +#define QPID_NULLSASLSERVER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/SaslServer.h" + +namespace qpid { + +/** + * Dummy implementation of the SASL server role. This will advertise + * ANONYMOUS and PLAIN, and parse the reponse data for those + * accordingly, but will make no attempt to actually authenticate + * users. + */ +class NullSaslServer : public SaslServer +{ + public: + NullSaslServer(const std::string& realm); + Status start(const std::string& mechanism, const std::string* response, std::string& challenge); + Status step(const std::string* response, std::string& challenge); + std::string getMechanisms(); + std::string getUserid(); + std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(size_t); + private: + std::string realm; + std::string userid; +}; +} // namespace qpid + +#endif /*!QPID_NULLSASLSERVER_H*/ diff --git a/cpp/src/qpid/Options.cpp b/cpp/src/qpid/Options.cpp index 35787aa8f3..c0e955e2b3 100644 --- a/cpp/src/qpid/Options.cpp +++ b/cpp/src/qpid/Options.cpp @@ -41,13 +41,13 @@ struct EnvOptMapper { return desc->long_name().size() == env.size() && std::equal(env.begin(), env.end(), desc->long_name().begin(), &matchChar); } - + static bool matchCase(const string& env, boost::shared_ptr<po::option_description> desc) { return env == desc->long_name(); } - + EnvOptMapper(const Options& o) : opts(o) {} - + string operator()(const string& envVar) { static const std::string prefix("QPID_"); if (envVar.substr(0, prefix.size()) == prefix) { @@ -74,31 +74,60 @@ struct EnvOptMapper { } - string configFileLine (string& line) { - - if ( isComment ( line ) ) - return string(); + void badArg ( string& line ) { + ostringstream msg; + msg << "Bad argument: |" << line << "|\n"; + throw Exception(msg.str()); + } + + + string configFileLine (string& line, bool allowUnknowns=true) { - size_t pos = line.find ('='); - if (pos == string::npos) + if ( isComment ( line ) ) { return string(); + } + + size_t pos = line.find ('='); + if (pos == string::npos) { + if ( allowUnknowns ) { + return string(); + } + else { + badArg ( line ); + } + } string key = line.substr (0, pos); #if (BOOST_VERSION >= 103300) typedef const std::vector< boost::shared_ptr<po::option_description> > OptDescs; - OptDescs::const_iterator i = + OptDescs::const_iterator i = find_if(opts.options().begin(), opts.options().end(), boost::bind(matchCase, key, _1)); if (i != opts.options().end()) return string (line) + "\n"; - else - return string(); + else { + if ( allowUnknowns ) { + return string(); + } + else { + badArg ( line ); + } + } #else - // Use 'count' to see if this option exists. Using 'find' will SEGV or hang - // if the option has not been defined yet. + // Use 'count' to see if this option exists. Using 'find' will + // SEGV or hang if the option has not been defined yet. if ( opts.count(key.c_str()) > 0 ) return string ( line ) + "\n"; - else - return string ( ); + else { + if ( allowUnknowns ) { + return string ( ); + } + else { + badArg ( line ); + } + } #endif + // Control will not arrive here, but the compiler things it could. + // Calls to badArg(), that I used above, throw. + return string(); } const Options& opts; @@ -109,8 +138,8 @@ std::string prettyArg(const std::string& name, const std::string& value) { return value.empty() ? name+" " : name+" ("+value+") "; } -Options::Options(const string& name) : - po::options_description(name) +Options::Options(const string& name) : + po::options_description(name) { } @@ -150,6 +179,7 @@ void Options::parse(int argc, char const* const* argv, const std::string& config if (!configFile.empty()) { parsing="configuration file "+configFile; ifstream conf(configFile.c_str()); + conf.peek(); if (conf.good()) { // Remove this hack when we get a stable version of boost that // can allow unregistered options in config files. @@ -159,7 +189,7 @@ void Options::parse(int argc, char const* const* argv, const std::string& config while (!conf.eof()) { string line; getline (conf, line); - filtered << mapper.configFileLine (line); + filtered << mapper.configFileLine (line, allowUnknown); } po::store(po::parse_config_file(filtered, *this), vm); @@ -197,5 +227,33 @@ CommonOptions::CommonOptions(const string& name, const string& configfile, const } + +bool Options::findArg(int argc, char const* const* argv, const std::string& theArg) +{ + const string parsing("command line options"); + bool result(false); + try { + if (argc > 0 && argv != 0) { + po::command_line_parser clp = po::command_line_parser(argc, const_cast<char**>(argv)). + options(*this).allow_unregistered(); + po::parsed_options opts = clp.run(); + + for (std::vector< po::basic_option<char> >::iterator + i = opts.options.begin(); i != opts.options.end(); i++) { + if (theArg.compare(i->string_key) == 0) { + result = true; + break; + } + } + } + return result; + } + catch (const std::exception& e) { + ostringstream msg; + msg << "Error in " << parsing << ": " << e.what() << endl; + throw Exception(msg.str()); + } +} + } // namespace qpid diff --git a/cpp/src/qpid/Sasl.h b/cpp/src/qpid/Sasl.h index 4d579fa051..efda92d718 100644 --- a/cpp/src/qpid/Sasl.h +++ b/cpp/src/qpid/Sasl.h @@ -34,7 +34,7 @@ struct SecuritySettings; } /** - * Interface to SASL support. This class is implemented by platform-specific + * Interface to support for the SASL client role. This class is implemented by platform-specific * SASL providers. */ class Sasl diff --git a/cpp/src/qpid/SaslFactory.cpp b/cpp/src/qpid/SaslFactory.cpp index a8d1f94c1e..4d81a793ee 100644 --- a/cpp/src/qpid/SaslFactory.cpp +++ b/cpp/src/qpid/SaslFactory.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -18,7 +18,9 @@ * under the License. * */ -#include "qpid//SaslFactory.h" +#include "qpid/SaslFactory.h" +#include "qpid/SaslServer.h" +#include "qpid/NullSaslServer.h" #include <map> #include <string.h> @@ -32,6 +34,7 @@ namespace qpid { //Null implementation + SaslFactory::SaslFactory() {} SaslFactory::~SaslFactory() {} @@ -50,6 +53,12 @@ std::auto_ptr<Sasl> SaslFactory::create( const std::string &, const std::string return std::auto_ptr<Sasl>(); } +std::auto_ptr<SaslServer> SaslFactory::createServer(const std::string& realm, bool /*encryptionRequired*/, const qpid::sys::SecuritySettings&) +{ + std::auto_ptr<SaslServer> server(new NullSaslServer(realm)); + return server; +} + qpid::sys::Mutex SaslFactory::lock; std::auto_ptr<SaslFactory> SaslFactory::instance; @@ -140,6 +149,22 @@ typedef int CallbackProc(); qpid::sys::Mutex SaslFactory::lock; std::auto_ptr<SaslFactory> SaslFactory::instance; +class CyrusSaslServer : public SaslServer +{ + public: + CyrusSaslServer(const std::string& realm, bool encryptionRequired, const qpid::sys::SecuritySettings& external); + ~CyrusSaslServer(); + Status start(const std::string& mechanism, const std::string* response, std::string& challenge); + Status step(const std::string* response, std::string& challenge); + std::string getMechanisms(); + std::string getUserid(); + std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(size_t); + private: + std::string realm; + std::string userid; + sasl_conn_t *sasl_conn; +}; + SaslFactory::SaslFactory() { sasl_callback_t* callbacks = 0; @@ -169,6 +194,12 @@ std::auto_ptr<Sasl> SaslFactory::create(const std::string & username, const std: return sasl; } +std::auto_ptr<SaslServer> SaslFactory::createServer(const std::string& realm, bool encryptionRequired, const qpid::sys::SecuritySettings& external) +{ + std::auto_ptr<SaslServer> server(new CyrusSaslServer(realm, encryptionRequired, external)); + return server; +} + CyrusSasl::CyrusSasl(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction) : conn(0), settings(username, password, serviceName, hostName, minSsf, maxSsf), allowInteraction(allowInteraction) { @@ -377,7 +408,180 @@ std::auto_ptr<SecurityLayer> CyrusSasl::getSecurityLayer(uint16_t maxFrameSize) std::auto_ptr<SecurityLayer> securityLayer; if (ssf) { QPID_LOG(info, "Installing security layer, SSF: "<< ssf); - securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(conn, maxFrameSize)); + securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(conn, maxFrameSize, ssf)); + } + return securityLayer; +} + +CyrusSaslServer::CyrusSaslServer(const std::string& r, bool encryptionRequired, const qpid::sys::SecuritySettings& external) : realm(r), sasl_conn(0) +{ + int code = sasl_server_new(BROKER_SASL_NAME, /* Service name */ + NULL, /* Server FQDN, gethostname() */ + realm.c_str(), /* Authentication realm */ + NULL, /* Local IP, needed for some mechanism */ + NULL, /* Remote IP, needed for some mechanism */ + NULL, /* Callbacks */ + 0, /* Connection flags */ + &sasl_conn); + + if (SASL_OK != code) { + QPID_LOG(error, "SASL: Connection creation failed: [" << code << "] " << sasl_errdetail(sasl_conn)); + + // TODO: Change this to an exception signaling + // server error, when one is available + throw qpid::framing::ConnectionForcedException("Unable to perform authentication"); + } + + sasl_security_properties_t secprops; + + //TODO: should the actual SSF values be configurable here? + secprops.min_ssf = encryptionRequired ? 10: 0; + secprops.max_ssf = 256; + + // If the transport provides encryption, notify the SASL library of + // the key length and set the ssf range to prevent double encryption. + QPID_LOG(debug, "External ssf=" << external.ssf << " and auth=" << external.authid); + sasl_ssf_t external_ssf = (sasl_ssf_t) external.ssf; + if (external_ssf) { + int result = sasl_setprop(sasl_conn, SASL_SSF_EXTERNAL, &external_ssf); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result)); + } + + secprops.max_ssf = secprops.min_ssf = 0; + } + + QPID_LOG(debug, "min_ssf: " << secprops.min_ssf << + ", max_ssf: " << secprops.max_ssf << + ", external_ssf: " << external_ssf ); + + if (!external.authid.empty()) { + const char* external_authid = external.authid.c_str(); + int result = sasl_setprop(sasl_conn, SASL_AUTH_EXTERNAL, external_authid); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external auth: " << result)); + } + + QPID_LOG(debug, "external auth detected and set to " << external_authid); + } + secprops.maxbufsize = 65535; + secprops.property_names = 0; + secprops.property_values = 0; + secprops.security_flags = 0; /* or SASL_SEC_NOANONYMOUS etc as appropriate */ + /* + * The nodict flag restricts SASL authentication mechanisms + * to those that are not susceptible to dictionary attacks. + * They are: + * SRP + * PASSDSS-3DES-1 + * EXTERNAL + */ + if (external.nodict) secprops.security_flags |= SASL_SEC_NODICTIONARY; + int result = sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << result)); + } +} + +CyrusSaslServer::~CyrusSaslServer() +{ + if (sasl_conn) { + sasl_dispose(&sasl_conn); + sasl_conn = 0; + } +} + +CyrusSaslServer::Status CyrusSaslServer::start(const std::string& mechanism, const std::string* response, std::string& chllng) +{ + const char *challenge; + unsigned int challenge_len; + + // This should be at same debug level as mech list in getMechanisms(). + QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); + int code = sasl_server_start(sasl_conn, + mechanism.c_str(), + (response ? response->c_str() : 0), (response ? response->size() : 0), + &challenge, &challenge_len); + switch (code) { + case SASL_OK: + return SaslServer::OK; + case SASL_CONTINUE: + chllng = std::string(challenge, challenge_len); + return SaslServer::CHALLENGE; + case SASL_NOMECH: + QPID_LOG(info, "Unsupported mechanism: " << mechanism); + default: + return SaslServer::FAIL; + } +} + +CyrusSaslServer::Status CyrusSaslServer::step(const std::string* response, std::string& chllng) +{ + const char *challenge; + unsigned int challenge_len; + + int code = sasl_server_step(sasl_conn, + (response ? response->c_str() : 0), (response ? response->size() : 0), + &challenge, &challenge_len); + + switch (code) { + case SASL_OK: + return SaslServer::OK; + case SASL_CONTINUE: + chllng = std::string(challenge, challenge_len); + return SaslServer::CHALLENGE; + default: + return SaslServer::FAIL; + } + +} +std::string CyrusSaslServer::getMechanisms() +{ + const char *separator = " "; + const char *list; + unsigned int list_len; + int count; + + int code = sasl_listmech(sasl_conn, NULL, + "", separator, "", + &list, &list_len, + &count); + + if (SASL_OK != code) { + QPID_LOG(info, "SASL: Mechanism listing failed: " << sasl_errdetail(sasl_conn)); + + // TODO: Change this to an exception signaling + // server error, when one is available + throw qpid::framing::ConnectionForcedException("Mechanism listing failed"); + } else { + std::string mechanisms(list, list_len); + QPID_LOG(info, "SASL: Mechanism list: " << mechanisms); + return mechanisms; + } +} +std::string CyrusSaslServer::getUserid() +{ + const void* ptr; + int code = sasl_getprop(sasl_conn, SASL_USERNAME, &ptr); + if (SASL_OK == code) { + userid = static_cast<const char*>(ptr); + } else { + QPID_LOG(warning, "Failed to retrieve sasl username"); + } + return userid; +} + +std::auto_ptr<SecurityLayer> CyrusSaslServer::getSecurityLayer(size_t maxFrameSize) +{ + const void* value(0); + int result = sasl_getprop(sasl_conn, SASL_SSF, &value); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(sasl_conn))); + } + uint ssf = *(reinterpret_cast<const unsigned*>(value)); + std::auto_ptr<SecurityLayer> securityLayer; + if (ssf) { + securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize, ssf)); } return securityLayer; } @@ -428,7 +632,6 @@ int getPasswordFromSettings(sasl_conn_t* conn, void* context, int /*id*/, sasl_s return SASL_FAIL; } } - } // namespace qpid #endif diff --git a/cpp/src/qpid/SaslFactory.h b/cpp/src/qpid/SaslFactory.h index 8554597147..da7e892f1d 100644 --- a/cpp/src/qpid/SaslFactory.h +++ b/cpp/src/qpid/SaslFactory.h @@ -26,7 +26,7 @@ #include <memory> namespace qpid { - +class SaslServer; /** * Factory for instances of the Sasl interface through which Sasl * support is provided to a ConnectionHandler. @@ -35,6 +35,7 @@ class SaslFactory { public: QPID_COMMON_EXTERN std::auto_ptr<Sasl> create(const std::string & userName, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction=true ); + QPID_COMMON_EXTERN std::auto_ptr<SaslServer> createServer(const std::string& realm, bool encryptionRequired, const qpid::sys::SecuritySettings&); QPID_COMMON_EXTERN static SaslFactory& getInstance(); QPID_COMMON_EXTERN ~SaslFactory(); private: diff --git a/cpp/src/qpid/SaslServer.h b/cpp/src/qpid/SaslServer.h new file mode 100644 index 0000000000..a707a468eb --- /dev/null +++ b/cpp/src/qpid/SaslServer.h @@ -0,0 +1,48 @@ +#ifndef QPID_SASLSERVER_H +#define QPID_SASLSERVER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <string> +#include <memory> + +namespace qpid { +namespace sys { +class SecurityLayer; +} +/** + * + */ +class SaslServer +{ + public: + typedef enum {OK, FAIL, CHALLENGE} Status; + virtual ~SaslServer() {} + virtual Status start(const std::string& mechanism, const std::string* response, std::string& challenge) = 0; + virtual Status step(const std::string* response, std::string& challenge) = 0; + virtual std::string getMechanisms() = 0; + virtual std::string getUserid() = 0; + virtual std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(size_t) = 0; + private: +}; +} // namespace qpid + +#endif /*!QPID_SASLSERVER_H*/ diff --git a/cpp/src/qpid/Url.cpp b/cpp/src/qpid/Url.cpp index 840f46e928..21de32aaa3 100644 --- a/cpp/src/qpid/Url.cpp +++ b/cpp/src/qpid/Url.cpp @@ -64,19 +64,6 @@ class ProtocolTags { Url::Invalid::Invalid(const string& s) : Exception(s) {} -Url Url::getHostNameUrl(uint16_t port) { - Address address("tcp", std::string(), port); - if (!sys::SystemInfo::getLocalHostname(address)) - throw Url::Invalid(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno))); - return Url(address); -} - -Url Url::getIpAddressesUrl(uint16_t port) { - Url url; - sys::SystemInfo::getLocalIpAddresses(port, url); - return url; -} - string Url::str() const { if (cache.empty() && !this->empty()) { ostringstream os; diff --git a/cpp/src/qpid/UrlArray.h b/cpp/src/qpid/UrlArray.h index ce9e42f248..f0065f0f0c 100644 --- a/cpp/src/qpid/UrlArray.h +++ b/cpp/src/qpid/UrlArray.h @@ -1,5 +1,5 @@ -#ifndef QPID_CLUSTER_URLARRAY_H -#define QPID_CLUSTER_URLARRAY_H +#ifndef QPID_URLARRAY_H +#define QPID_URLARRAY_H /* * @@ -33,4 +33,4 @@ QPID_COMMON_EXTERN std::vector<Url> urlArrayToVector(const framing::Array& array QPID_COMMON_EXTERN framing::Array vectorToUrlArray(const std::vector<Url>& urls); } // namespace qpid -#endif /* !QPID_CLUSTER_URLARRAY_H */ +#endif /* !QPID_URLARRAY_H */ diff --git a/cpp/src/qpid/acl/Acl.cpp b/cpp/src/qpid/acl/Acl.cpp index 89c4b3402a..31ad9a38ac 100644 --- a/cpp/src/qpid/acl/Acl.cpp +++ b/cpp/src/qpid/acl/Acl.cpp @@ -18,11 +18,13 @@ #include "qpid/acl/Acl.h" #include "qpid/acl/AclConnectionCounter.h" +#include "qpid/acl/AclResourceCounter.h" #include "qpid/acl/AclData.h" #include "qpid/acl/AclValidator.h" #include "qpid/sys/Mutex.h" #include "qpid/broker/Broker.h" +#include "qpid/broker/Connection.h" #include "qpid/Plugin.h" #include "qpid/Options.h" #include "qpid/log/Logger.h" @@ -32,6 +34,7 @@ #include "qmf/org/apache/qpid/acl/Package.h" #include "qmf/org/apache/qpid/acl/EventAllow.h" #include "qmf/org/apache/qpid/acl/EventConnectionDeny.h" +#include "qmf/org/apache/qpid/acl/EventQueueQuotaDeny.h" #include "qmf/org/apache/qpid/acl/EventDeny.h" #include "qmf/org/apache/qpid/acl/EventFileLoaded.h" #include "qmf/org/apache/qpid/acl/EventFileLoadFailed.h" @@ -50,19 +53,29 @@ using qpid::management::Manageable; using qpid::management::Args; namespace _qmf = qmf::org::apache::qpid::acl; -Acl::Acl (AclValues& av, Broker& b): aclValues(av), broker(&b), transferAcl(false), mgmtObject(0), - connectionCounter(new ConnectionCounter(*this, aclValues.aclMaxConnectPerUser, aclValues.aclMaxConnectPerIp, aclValues.aclMaxConnectTotal)) -{ +Acl::Acl (AclValues& av, Broker& b): aclValues(av), broker(&b), transferAcl(false), + connectionCounter(new ConnectionCounter(*this, aclValues.aclMaxConnectPerUser, aclValues.aclMaxConnectPerIp, aclValues.aclMaxConnectTotal)), + resourceCounter(new ResourceCounter(*this, aclValues.aclMaxQueuesPerUser)){ + + if (aclValues.aclMaxConnectPerUser > AclData::getConnectMaxSpec()) + throw Exception("--connection-limit-per-user switch cannot be larger than " + AclData::getMaxConnectSpecStr()); + if (aclValues.aclMaxConnectPerIp > AclData::getConnectMaxSpec()) + throw Exception("--connection-limit-per-ip switch cannot be larger than " + AclData::getMaxConnectSpecStr()); + if (aclValues.aclMaxConnectTotal > AclData::getConnectMaxSpec()) + throw Exception("--max-connections switch cannot be larger than " + AclData::getMaxConnectSpecStr()); + if (aclValues.aclMaxQueuesPerUser > AclData::getConnectMaxSpec()) + throw Exception("--max-queues-per-user switch cannot be larger than " + AclData::getMaxConnectSpecStr()); agent = broker->getManagementAgent(); if (agent != 0){ _qmf::Package packageInit(agent); - mgmtObject = new _qmf::Acl (agent, this, broker); + mgmtObject = _qmf::Acl::shared_ptr(new _qmf::Acl (agent, this, broker)); agent->addObject (mgmtObject); mgmtObject->set_maxConnections(aclValues.aclMaxConnectTotal); mgmtObject->set_maxConnectionsPerIp(aclValues.aclMaxConnectPerIp); mgmtObject->set_maxConnectionsPerUser(aclValues.aclMaxConnectPerUser); + mgmtObject->set_maxQueuesPerUser(aclValues.aclMaxQueuesPerUser); } std::string errorString; if (!readAclFile(errorString)){ @@ -84,6 +97,15 @@ void Acl::reportConnectLimit(const std::string user, const std::string addr) } +void Acl::reportQueueLimit(const std::string user, const std::string queueName) +{ + if (mgmtObject!=0) + mgmtObject->inc_queueQuotaDenyCount(); + + agent->raiseEvent(_qmf::EventQueueQuotaDeny(user, queueName)); +} + + bool Acl::authorise( const std::string& id, const Action& action, @@ -126,13 +148,29 @@ bool Acl::authorise( bool Acl::approveConnection(const qpid::broker::Connection& conn) { - return connectionCounter->approveConnection(conn); + const std::string& userName(conn.getUserId()); + uint16_t connectionLimit(0); + + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + + bool enforcingConnQuotas = dataLocal->getConnQuotaForUser(userName, &connectionLimit); + + return connectionCounter->approveConnection(conn, enforcingConnQuotas, connectionLimit); +} + +bool Acl::approveCreateQueue(const std::string& userId, const std::string& queueName) +{ + return resourceCounter->approveCreateQueue(userId, queueName); } -void Acl::setUserId(const qpid::broker::Connection& connection, const std::string& username) +void Acl::recordDestroyQueue(const std::string& queueName) { - connectionCounter->setUserId(connection, username); + resourceCounter->recordDestroyQueue(queueName); } @@ -190,7 +228,7 @@ bool Acl::readAclFile(std::string& errorText) bool Acl::readAclFile(std::string& aclFile, std::string& errorText) { boost::shared_ptr<AclData> d(new AclData); - AclReader ar; + AclReader ar(aclValues.aclMaxConnectPerUser); if (ar.read(aclFile, d)){ agent->raiseEvent(_qmf::EventFileLoadFailed("", ar.getError())); errorText = ar.getError(); @@ -211,6 +249,10 @@ bool Acl::readAclFile(std::string& aclFile, std::string& errorText) { QPID_LOG(debug,"ACL: Transfer ACL is Enabled!"); } + if (data->enforcingConnectionQuotas()){ + QPID_LOG(debug, "ACL: Connection quotas are Enabled."); + } + data->aclSource = aclFile; if (mgmtObject!=0){ mgmtObject->set_transferAcl(transferAcl?1:0); @@ -300,9 +342,9 @@ Acl::~Acl(){ broker->getConnectionObservers().remove(connectionCounter); } -ManagementObject* Acl::GetManagementObject(void) const +ManagementObject::shared_ptr Acl::GetManagementObject(void) const { - return (ManagementObject*) mgmtObject; + return mgmtObject; } Manageable::status_t Acl::ManagementMethod (uint32_t methodId, Args& args, string& text) diff --git a/cpp/src/qpid/acl/Acl.h b/cpp/src/qpid/acl/Acl.h index 4787934275..ea3c6586a3 100644 --- a/cpp/src/qpid/acl/Acl.h +++ b/cpp/src/qpid/acl/Acl.h @@ -43,12 +43,14 @@ class Connection; namespace acl { class ConnectionCounter; +class ResourceCounter; struct AclValues { std::string aclFile; uint16_t aclMaxConnectPerUser; uint16_t aclMaxConnectPerIp; uint16_t aclMaxConnectTotal; + uint16_t aclMaxQueuesPerUser; }; @@ -60,10 +62,11 @@ private: broker::Broker* broker; bool transferAcl; boost::shared_ptr<AclData> data; - qmf::org::apache::qpid::acl::Acl* mgmtObject; // mgnt owns lifecycle + qmf::org::apache::qpid::acl::Acl::shared_ptr mgmtObject; qpid::management::ManagementAgent* agent; mutable qpid::sys::Mutex dataLock; boost::shared_ptr<ConnectionCounter> connectionCounter; + boost::shared_ptr<ResourceCounter> resourceCounter; public: Acl (AclValues& av, broker::Broker& b); @@ -72,11 +75,16 @@ public: * issue management counts and alerts for denied connections */ void reportConnectLimit(const std::string user, const std::string addr); + void reportQueueLimit(const std::string user, const std::string queueName); inline virtual bool doTransferAcl() { return transferAcl; }; + inline virtual uint16_t getMaxConnectTotal() { + return aclValues.aclMaxConnectTotal; + }; + // create specilied authorise methods for cases that need faster matching as needed. virtual bool authorise( const std::string& id, @@ -92,9 +100,10 @@ public: const std::string& ExchangeName, const std::string& RoutingKey); + // Resource quota tracking virtual bool approveConnection(const broker::Connection& connection); - - virtual void setUserId(const broker::Connection& connection, const std::string& username); + virtual bool approveCreateQueue(const std::string& userId, const std::string& queueName); + virtual void recordDestroyQueue(const std::string& queueName); virtual ~Acl(); private: @@ -108,7 +117,7 @@ private: bool readAclFile(std::string& aclFile, std::string& errorText); Manageable::status_t lookup (management::Args& args, std::string& text); Manageable::status_t lookupPublish(management::Args& args, std::string& text); - virtual qpid::management::ManagementObject* GetManagementObject(void) const; + virtual qpid::management::ManagementObject::shared_ptr GetManagementObject(void) const; virtual management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); }; diff --git a/cpp/src/qpid/acl/AclConnectionCounter.cpp b/cpp/src/qpid/acl/AclConnectionCounter.cpp index 8c6e3eef6e..875137bf55 100644 --- a/cpp/src/qpid/acl/AclConnectionCounter.cpp +++ b/cpp/src/qpid/acl/AclConnectionCounter.cpp @@ -85,32 +85,32 @@ bool ConnectionCounter::limitApproveLH( // // countConnectionLH // -// Increment the name's count in map and return a comparison against the limit. -// called with dataLock already taken +// Increment the name's count in map and return an optional comparison +// against a connection limit. +// Called with dataLock already taken. // bool ConnectionCounter::countConnectionLH( connectCountsMap_t& theMap, const std::string& theName, uint16_t theLimit, - bool emitLog) { + bool emitLog, + bool enforceLimit) { bool result(true); uint16_t count(0); - if (theLimit > 0) { - connectCountsMap_t::iterator eRef = theMap.find(theName); - if (eRef != theMap.end()) { - count = (uint16_t)(*eRef).second + 1; - (*eRef).second = count; - result = count <= theLimit; - } else { - theMap[theName] = count = 1; - } - if (emitLog) { - QPID_LOG(trace, "ACL ConnectionApprover user=" << theName - << " limit=" << theLimit - << " curValue=" << count - << " result=" << (result ? "allow" : "deny")); - } + connectCountsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + count = (uint16_t)(*eRef).second + 1; + (*eRef).second = count; + result = (enforceLimit ? count <= theLimit : true); + } else { + theMap[theName] = count = 1; + } + if (emitLog) { + QPID_LOG(trace, "ACL ConnectionApprover user=" << theName + << " limit=" << theLimit + << " curValue=" << count + << " result=" << (result ? "allow" : "deny")); } return result; } @@ -123,23 +123,21 @@ bool ConnectionCounter::countConnectionLH( // called with dataLock already taken // void ConnectionCounter::releaseLH( - connectCountsMap_t& theMap, const std::string& theName, uint16_t theLimit) { - - if (theLimit > 0) { - connectCountsMap_t::iterator eRef = theMap.find(theName); - if (eRef != theMap.end()) { - uint16_t count = (uint16_t) (*eRef).second; - assert (count > 0); - if (1 == count) { - theMap.erase (eRef); - } else { - (*eRef).second = count - 1; - } + connectCountsMap_t& theMap, const std::string& theName) { + + connectCountsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + uint16_t count = (uint16_t) (*eRef).second; + assert (count > 0); + if (1 == count) { + theMap.erase (eRef); } else { - // User had no connections. - QPID_LOG(notice, "ACL ConnectionCounter Connection for '" << theName - << "' not found in connection count pool"); + (*eRef).second = count - 1; } + } else { + // User had no connections. + QPID_LOG(notice, "ACL ConnectionCounter Connection for '" << theName + << "' not found in connection count pool"); } } @@ -161,7 +159,7 @@ void ConnectionCounter::connection(broker::Connection& connection) { connectProgressMap[connection.getMgmtId()] = C_CREATED; // Count the connection from this host. - (void) countConnectionLH(connectByHostMap, hostName, hostLimit, false); + (void) countConnectionLH(connectByHostMap, hostName, hostLimit, false, false); } @@ -180,8 +178,7 @@ void ConnectionCounter::closed(broker::Connection& connection) { // Normal case: connection was created and opened. // Decrement user in-use counts releaseLH(connectByNameMap, - connection.getUserId(), - nameLimit); + connection.getUserId()); } else { // Connection was created but not opened. // Don't decrement user count. @@ -189,8 +186,7 @@ void ConnectionCounter::closed(broker::Connection& connection) { // Decrement host in-use count. releaseLH(connectByHostMap, - getClientHost(connection.getMgmtId()), - hostLimit); + getClientHost(connection.getMgmtId())); // destroy connection progress indicator connectProgressMap.erase(eRef); @@ -211,7 +207,10 @@ void ConnectionCounter::closed(broker::Connection& connection) { // check total connections, connections from IP, connections by user and // disallow if over any limit // -bool ConnectionCounter::approveConnection(const broker::Connection& connection) +bool ConnectionCounter::approveConnection( + const broker::Connection& connection, + bool enforcingConnectionQuotas, + uint16_t connectionUserQuota ) { const std::string& hostName(getClientHost(connection.getMgmtId())); const std::string& userName( connection.getUserId()); @@ -220,122 +219,53 @@ bool ConnectionCounter::approveConnection(const broker::Connection& connection) // Bump state from CREATED to OPENED (void) countConnectionLH(connectProgressMap, connection.getMgmtId(), - C_OPENED, false); + C_OPENED, false, false); // Approve total connections bool okTotal = true; if (totalLimit > 0) { okTotal = totalCurrentConnections <= totalLimit; - if (!connection.isShadow()) { - QPID_LOG(trace, "ACL ConnectionApprover totalLimit=" << totalLimit - << " curValue=" << totalCurrentConnections - << " result=" << (okTotal ? "allow" : "deny")); - } + QPID_LOG(trace, "ACL ConnectionApprover totalLimit=" << totalLimit + << " curValue=" << totalCurrentConnections + << " result=" << (okTotal ? "allow" : "deny")); } // Approve by IP host connections - bool okByIP = limitApproveLH(connectByHostMap, hostName, hostLimit, !connection.isShadow()); + bool okByIP = limitApproveLH(connectByHostMap, hostName, hostLimit, true); // Count and Approve the connection by the user - bool okByUser = countConnectionLH(connectByNameMap, userName, nameLimit, !connection.isShadow()); - - if (!connection.isShadow()) { - // Emit separate log for each disapproval - if (!okTotal) { - QPID_LOG(error, "Client max total connection count limit of " << totalLimit - << " exceeded by '" - << connection.getMgmtId() << "', user: '" - << userName << "'. Connection refused"); - } - if (!okByIP) { - QPID_LOG(error, "Client max per-host connection count limit of " - << hostLimit << " exceeded by '" - << connection.getMgmtId() << "', user: '" - << userName << "'. Connection refused."); - } - if (!okByUser) { - QPID_LOG(error, "Client max per-user connection count limit of " - << nameLimit << " exceeded by '" - << connection.getMgmtId() << "', user: '" - << userName << "'. Connection refused."); - } - - // Count/Event once for each disapproval - bool result = okTotal && okByIP && okByUser; - if (!result) { - acl.reportConnectLimit(userName, hostName); - } - - return result; - } else { - // Always allow shadow connections - if (!okTotal) { - QPID_LOG(warning, "Client max total connection count limit of " << totalLimit - << " exceeded by '" - << connection.getMgmtId() << "', user: '" - << userName << "' but still within tolerance. Cluster connection allowed"); - } - if (!okByIP) { - QPID_LOG(warning, "Client max per-host connection count limit of " - << hostLimit << " exceeded by '" - << connection.getMgmtId() << "', user: '" - << userName << "' but still within tolerance. Cluster connection allowed"); - } - if (!okByUser) { - QPID_LOG(warning, "Client max per-user connection count limit of " - << nameLimit << " exceeded by '" - << connection.getMgmtId() << "', user: '" - << userName << "' but still within tolerance. Cluster connection allowed"); - } - if (okTotal && okByIP && okByUser) { - QPID_LOG(debug, "Cluster client connection: '" - << connection.getMgmtId() << "', user '" - << userName << "' allowed"); - } - return true; + bool okByUser = countConnectionLH(connectByNameMap, userName, + connectionUserQuota, true, + enforcingConnectionQuotas); + + // Emit separate log for each disapproval + if (!okTotal) { + QPID_LOG(error, "Client max total connection count limit of " << totalLimit + << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "'. Connection refused"); + } + if (!okByIP) { + QPID_LOG(error, "Client max per-host connection count limit of " + << hostLimit << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "'. Connection refused."); + } + if (!okByUser) { + QPID_LOG(error, "Client max per-user connection count limit of " + << connectionUserQuota << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "'. Connection refused."); } -} - - -// -// setUserId -// On cluster shadow connections, track a new user id for this connection. -// -void ConnectionCounter::setUserId(const broker::Connection& connection, - const std::string& username) -{ - Mutex::ScopedLock locker(dataLock); - connectCountsMap_t::iterator eRef = connectProgressMap.find(connection.getMgmtId()); - if (eRef != connectProgressMap.end()) { - if ((*eRef).second == C_OPENED){ - // Connection has been opened so that current user has been counted - if (connection.isShadow()) { - // This is a shadow connection and therefore receives userId changes - QPID_LOG(debug, "Changing User ID for cluster connection: " - << connection.getMgmtId() << ", old user:'" << connection.getUserId() - << "', new user:'" << username << "'"); - - // Decrement user in-use count for old userId - releaseLH(connectByNameMap, - connection.getUserId(), - nameLimit); - // Increment user in-use count for new userId - (void) countConnectionLH(connectByNameMap, username, nameLimit, false); - } else { - QPID_LOG(warning, "Changing User ID for non-cluster connections is not supported: " - << connection.getMgmtId() << ", old user " << connection.getUserId() - << ", new user " << username); - } - } else { - // connection exists but has not been opened. - // setUserId is called in normal course. The user gets counted when connection is opened. - } - } else { - // Connection does not exist. + // Count/Event once for each disapproval + bool result = okTotal && okByIP && okByUser; + if (!result) { + acl.reportConnectLimit(userName, hostName); } -} + return result; +} // // getClientIp - given a connection's mgmtId return the client host part. diff --git a/cpp/src/qpid/acl/AclConnectionCounter.h b/cpp/src/qpid/acl/AclConnectionCounter.h index 54fa6933ff..e8ef35c1ba 100644 --- a/cpp/src/qpid/acl/AclConnectionCounter.h +++ b/cpp/src/qpid/acl/AclConnectionCounter.h @@ -77,12 +77,12 @@ private: bool countConnectionLH(connectCountsMap_t& theMap, const std::string& theName, uint16_t theLimit, - bool emitLog); + bool emitLog, + bool enforceLimit); /** Release a connection */ void releaseLH(connectCountsMap_t& theMap, - const std::string& theName, - uint16_t theLimit); + const std::string& theName); public: ConnectionCounter(Acl& acl, uint16_t nl, uint16_t hl, uint16_t tl); @@ -93,8 +93,9 @@ public: void closed(broker::Connection& connection); // Connection counting - bool approveConnection(const broker::Connection& conn); - void setUserId(const broker::Connection& connection, const std::string& username); + bool approveConnection(const broker::Connection& conn, + bool enforcingConnectionQuotas, + uint16_t connectionLimit ); }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/acl/AclData.cpp b/cpp/src/qpid/acl/AclData.cpp index 7c14d0985d..ca866ab7d3 100644 --- a/cpp/src/qpid/acl/AclData.cpp +++ b/cpp/src/qpid/acl/AclData.cpp @@ -25,11 +25,19 @@ namespace qpid { namespace acl { // - // Instantiate the substitution keyword string + // Instantiate the keyword strings // - const std::string AclData::USER_SUBSTITUTION_KEYWORD = "${user}"; - const std::string AclData::DOMAIN_SUBSTITUTION_KEYWORD = "${domain}"; - const std::string AclData::USERDOMAIN_SUBSTITUTION_KEYWORD = "${userdomain}"; + const std::string AclData::ACL_KEYWORD_USER_SUBST = "${user}"; + const std::string AclData::ACL_KEYWORD_DOMAIN_SUBST = "${domain}"; + const std::string AclData::ACL_KEYWORD_USERDOMAIN_SUBST = "${userdomain}"; + const std::string AclData::ACL_KEYWORD_ALL = "all"; + const std::string AclData::ACL_KEYWORD_ACL = "acl"; + const std::string AclData::ACL_KEYWORD_GROUP = "group"; + const std::string AclData::ACL_KEYWORD_QUOTA = "quota"; + const std::string AclData::ACL_KEYWORD_QUOTA_CONNECTIONS = "connections"; + const char AclData::ACL_SYMBOL_WILDCARD = '*'; + const std::string AclData::ACL_KEYWORD_WILDCARD = "*"; + const char AclData::ACL_SYMBOL_LINE_CONTINUATION = '\\'; // // constructor @@ -37,7 +45,9 @@ namespace acl { AclData::AclData(): decisionMode(qpid::acl::DENY), transferAcl(false), - aclSource("UNKNOWN") + aclSource("UNKNOWN"), + connQuotaRulesExist(false), + connQuotaRuleSettings(new quotaRuleSet) { for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++) { @@ -60,6 +70,9 @@ namespace acl { } delete[] actionList[cnt]; } + transferAcl = false; + connQuotaRulesExist = false; + connQuotaRuleSettings->clear(); } @@ -73,7 +86,7 @@ namespace acl { const std::string& lookupStr) { // allow wildcard on the end of rule strings... - if (ruleStr.data()[ruleStr.size()-1]=='*') + if (ruleStr.data()[ruleStr.size()-1]==ACL_SYMBOL_WILDCARD) { return ruleStr.compare(0, ruleStr.size()-1, @@ -124,7 +137,7 @@ namespace acl { // If individual actorId not found then find a rule set for '*'. if (itrRule == actionList[action][objType]->end()) - itrRule = actionList[action][objType]->find("*"); + itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD); if (itrRule != actionList[action][objType]->end()) { @@ -199,6 +212,16 @@ namespace acl { lookupParamItr = params->find(PROP_MAXQUEUESIZE); break; + case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT: + case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT: + lookupParamItr = params->find(PROP_MAXFILECOUNT); + break; + + case acl::SPECPROP_MAXFILESIZEUPPERLIMIT: + case acl::SPECPROP_MAXFILESIZELOWERLIMIT: + lookupParamItr = params->find(PROP_MAXFILESIZE); + break; + default: lookupParamItr = params->find((Property)rulePropMapItr->first); break; @@ -222,6 +245,8 @@ namespace acl { { case acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT: case acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT: + case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT: + case acl::SPECPROP_MAXFILESIZEUPPERLIMIT: limitChecked &= compareIntMax( rulePropMapItr->first, @@ -231,6 +256,8 @@ namespace acl { case acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT: case acl::SPECPROP_MAXQUEUESIZELOWERLIMIT: + case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT: + case acl::SPECPROP_MAXFILESIZELOWERLIMIT: limitChecked &= compareIntMin( rulePropMapItr->first, @@ -241,14 +268,31 @@ namespace acl { default: bool result; if ((SPECPROP_ALTERNATE == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_ALTERNATE]) || - (SPECPROP_ROUTINGKEY == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_ROUTINGKEY]) || (SPECPROP_QUEUENAME == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_QUEUENAME])) { // These properties are allowed to have username substitution std::string sName(rulePropMapItr->second); substituteUserId(sName, id); result = matchProp(sName, lookupParamItr->second); - } else { + } + else if (SPECPROP_ROUTINGKEY == rulePropMapItr->first) + { + // Routing key is allowed to have username substitution + // and it gets topic exchange matching + if (rsItr->ruleHasUserSub[PROP_ROUTINGKEY]) + { + std::string sKey(lookupParamItr->second); + substituteKeywords(sKey, id); + result = rsItr->matchRoutingKey(sKey); + } + else + { + result = rsItr->matchRoutingKey(lookupParamItr->second); + } + } + else + { + // Rules without substitution result = matchProp(rulePropMapItr->second, lookupParamItr->second); } @@ -359,7 +403,7 @@ namespace acl { AclData::actObjItr itrRule = actionList[action][objType]->find(id); if (itrRule == actionList[action][objType]->end()) - itrRule = actionList[action][objType]->find("*"); + itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD); if (itrRule != actionList[action][objType]->end() ) { @@ -405,9 +449,9 @@ namespace acl { if (match && rsItr->pubRoutingKeyInRule) { - if ((routingKey.find(USER_SUBSTITUTION_KEYWORD, 0) != std::string::npos) || - (routingKey.find(DOMAIN_SUBSTITUTION_KEYWORD, 0) != std::string::npos) || - (routingKey.find(USERDOMAIN_SUBSTITUTION_KEYWORD, 0) != std::string::npos)) + if ((routingKey.find(ACL_KEYWORD_USER_SUBST, 0) != std::string::npos) || + (routingKey.find(ACL_KEYWORD_DOMAIN_SUBST, 0) != std::string::npos) || + (routingKey.find(ACL_KEYWORD_USERDOMAIN_SUBST, 0) != std::string::npos)) { // The user is not allowed to present a routing key with the substitution key in it QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << @@ -458,6 +502,62 @@ namespace acl { } + + // + // + // + void AclData::setConnQuotaRuleSettings ( + bool rulesExist, boost::shared_ptr<quotaRuleSet> quotaPtr) + { + connQuotaRulesExist = rulesExist; + connQuotaRuleSettings = quotaPtr; + } + + + // + // getConnQuotaForUser + // + // Return the true or false value of connQuotaRulesExist, + // indicating whether any kind of lookup was done or not. + // + // When lookups are performed return the result value of + // 1. The user's setting else + // 2. The 'all' user setting else + // 3. Zero + // When lookups are not performed then return a result value of Zero. + // + bool AclData::getConnQuotaForUser(const std::string& theUserName, + uint16_t* theResult) const { + if (connQuotaRulesExist) { + // look for this user explicitly + quotaRuleSetItr nameItr = (*connQuotaRuleSettings).find(theUserName); + if (nameItr != (*connQuotaRuleSettings).end()) { + QPID_LOG(trace, "ACL: Connection quota for user " << theUserName + << " explicitly set to : " << (*nameItr).second); + *theResult = (*nameItr).second; + } else { + // Look for the 'all' user + nameItr = (*connQuotaRuleSettings).find(ACL_KEYWORD_ALL); + if (nameItr != (*connQuotaRuleSettings).end()) { + QPID_LOG(trace, "ACL: Connection quota for user " << theUserName + << " chosen through value for 'all' : " << (*nameItr).second); + *theResult = (*nameItr).second; + } else { + // Neither userName nor "all" found. + QPID_LOG(trace, "ACL: Connection quota for user " << theUserName + << " absent in quota settings. Return value : 0"); + *theResult = 0; + } + } + } else { + // Rules do not exist + QPID_LOG(trace, "ACL: Connection quota for user " << theUserName + << " unavailable; quota settings are not specified. Return value : 0"); + *theResult = 0; + } + return connQuotaRulesExist; + } + // // // @@ -607,8 +707,8 @@ namespace acl { // Given an Acl rule and an authenticated userId // do the keyword substitutions on the rule. // - void AclData::AclData::substituteUserId(std::string& ruleString, - const std::string& userId) + void AclData::substituteUserId(std::string& ruleString, + const std::string& userId) { size_t locDomSeparator(0); std::string user(""); @@ -625,9 +725,9 @@ namespace acl { domain = normalizeUserId(userId.substr(locDomSeparator+1)); } - substituteString(ruleString, USER_SUBSTITUTION_KEYWORD, user); - substituteString(ruleString, DOMAIN_SUBSTITUTION_KEYWORD, domain); - substituteString(ruleString, USERDOMAIN_SUBSTITUTION_KEYWORD, userdomain); + substituteString(ruleString, ACL_KEYWORD_USER_SUBST, user); + substituteString(ruleString, ACL_KEYWORD_DOMAIN_SUBST, domain); + substituteString(ruleString, ACL_KEYWORD_USERDOMAIN_SUBST, userdomain); } @@ -640,8 +740,8 @@ namespace acl { // topic key lookups where the keyword string proper is in the // topic key search tree. // - void AclData::AclData::substituteKeywords(std::string& ruleString, - const std::string& userId) + void AclData::substituteKeywords(std::string& ruleString, + const std::string& userId) { size_t locDomSeparator(0); std::string user(""); @@ -658,8 +758,8 @@ namespace acl { domain = normalizeUserId(userId.substr(locDomSeparator+1)); } std::string oRule(ruleString); - substituteString(ruleString, userdomain, USERDOMAIN_SUBSTITUTION_KEYWORD); - substituteString(ruleString, user, USER_SUBSTITUTION_KEYWORD); - substituteString(ruleString, domain, DOMAIN_SUBSTITUTION_KEYWORD); + substituteString(ruleString, userdomain, ACL_KEYWORD_USERDOMAIN_SUBST); + substituteString(ruleString, user, ACL_KEYWORD_USER_SUBST); + substituteString(ruleString, domain, ACL_KEYWORD_DOMAIN_SUBST); } }} diff --git a/cpp/src/qpid/acl/AclData.h b/cpp/src/qpid/acl/AclData.h index b4b13c44b6..43cb5193f5 100644 --- a/cpp/src/qpid/acl/AclData.h +++ b/cpp/src/qpid/acl/AclData.h @@ -111,6 +111,8 @@ public: typedef std::map<std::string, ruleSet > actionObject; // user typedef actionObject::iterator actObjItr; typedef actionObject* aclAction; + typedef std::map<std::string, uint16_t> quotaRuleSet; // <username, N> + typedef quotaRuleSet::const_iterator quotaRuleSetItr; // Action*[] -> Object*[] -> map<user -> set<Rule> > aclAction* actionList[qpid::acl::ACTIONSIZE]; @@ -134,9 +136,18 @@ public: bool matchProp(const std::string & src, const std::string& src1); void clear (); - static const std::string USER_SUBSTITUTION_KEYWORD; - static const std::string DOMAIN_SUBSTITUTION_KEYWORD; - static const std::string USERDOMAIN_SUBSTITUTION_KEYWORD; + static const std::string ACL_KEYWORD_USER_SUBST; + static const std::string ACL_KEYWORD_DOMAIN_SUBST; + static const std::string ACL_KEYWORD_USERDOMAIN_SUBST; + static const std::string ACL_KEYWORD_ALL; + static const std::string ACL_KEYWORD_ACL; + static const std::string ACL_KEYWORD_GROUP; + static const std::string ACL_KEYWORD_QUOTA; + static const std::string ACL_KEYWORD_QUOTA_CONNECTIONS; + static const char ACL_SYMBOL_WILDCARD; + static const std::string ACL_KEYWORD_WILDCARD; + static const char ACL_SYMBOL_LINE_CONTINUATION; + void substituteString(std::string& targetString, const std::string& placeholder, const std::string& replacement); @@ -146,6 +157,31 @@ public: void substituteKeywords(std::string& ruleString, const std::string& userId); + // Per user connection quotas extracted from acl rule file + // Set by reader + void setConnQuotaRuleSettings (bool, boost::shared_ptr<quotaRuleSet>); + // Get by connection approvers + bool enforcingConnectionQuotas() { return connQuotaRulesExist; } + bool getConnQuotaForUser(const std::string&, uint16_t*) const; + + /** getConnectMaxSpec + * Connection quotas are held in uint16_t variables. + * This function specifies the largest value that a user is allowed + * to declare for a connection quota. The upper limit serves two + * purposes: 1. It leaves room for magic numbers that may be declared + * by keyword names in Acl files and not have those numbers conflict + * with innocent user declared values, and 2. It makes the unsigned + * math very close to _MAX work reliably with no risk of accidental + * wrapping back to zero. + */ + static uint16_t getConnectMaxSpec() { + return 65530; + } + static std::string getMaxConnectSpecStr() { + return "65530"; + } + + AclData(); virtual ~AclData(); @@ -157,6 +193,10 @@ private: bool compareIntMin(const qpid::acl::SpecProperty theProperty, const std::string theAclValue, const std::string theLookupValue); + + // Per-user connection quota + bool connQuotaRulesExist; + boost::shared_ptr<quotaRuleSet> connQuotaRuleSettings; // Map of user-to-N values from rule file }; }} // namespace qpid::acl diff --git a/cpp/src/qpid/acl/AclPlugin.cpp b/cpp/src/qpid/acl/AclPlugin.cpp index ebf5e90afe..c666eb5420 100644 --- a/cpp/src/qpid/acl/AclPlugin.cpp +++ b/cpp/src/qpid/acl/AclPlugin.cpp @@ -42,9 +42,10 @@ struct AclOptions : public Options { values.aclMaxConnectTotal = 500; addOptions() ("acl-file", optValue(values.aclFile, "FILE"), "The policy file to load from, loaded from data dir") + ("connection-limit-per-user", optValue(values.aclMaxConnectPerUser, "N"), "The maximum number of connections allowed per user. 0 implies no limit.") ("max-connections" , optValue(values.aclMaxConnectTotal, "N"), "The maximum combined number of connections allowed. 0 implies no limit.") - ("max-connections-per-user", optValue(values.aclMaxConnectPerUser, "N"), "The maximum number of connections allowed per user. 0 implies no limit.") - ("max-connections-per-ip" , optValue(values.aclMaxConnectPerIp, "N"), "The maximum number of connections allowed per host IP address. 0 implies no limit.") + ("connection-limit-per-ip" , optValue(values.aclMaxConnectPerIp, "N"), "The maximum number of connections allowed per host IP address. 0 implies no limit.") + ("max-queues-per-user", optValue(values.aclMaxQueuesPerUser, "N"), "The maximum number of queues allowed per user. 0 implies no limit.") ; } }; diff --git a/cpp/src/qpid/acl/AclReader.cpp b/cpp/src/qpid/acl/AclReader.cpp index fae67d0325..7eb9b82c64 100644 --- a/cpp/src/qpid/acl/AclReader.cpp +++ b/cpp/src/qpid/acl/AclReader.cpp @@ -24,6 +24,7 @@ #include <sstream> #include "qpid/log/Statement.h" #include "qpid/Exception.h" +#include <boost/lexical_cast.hpp> #include <iomanip> // degug #include <iostream> // debug @@ -95,7 +96,7 @@ namespace acl { << cnt << " " << (*i)->toString()); if (!foundmode && (*i)->actionAll && (*i)->names.size() == 1 - && (*((*i)->names.begin())).compare("*") == 0) { + && (*((*i)->names.begin())).compare(AclData::ACL_KEYWORD_WILDCARD) == 0) { d->decisionMode = (*i)->res; QPID_LOG(debug, "ACL: FoundMode " << AclHelper::getAclResultStr(d->decisionMode)); @@ -105,13 +106,23 @@ namespace acl { // Record which properties have the user substitution string for (pmCitr pItr=rule.props.begin(); pItr!=rule.props.end(); pItr++) { - if ((pItr->second.find(AclData::USER_SUBSTITUTION_KEYWORD, 0) != std::string::npos) || - (pItr->second.find(AclData::DOMAIN_SUBSTITUTION_KEYWORD, 0) != std::string::npos) || - (pItr->second.find(AclData::USERDOMAIN_SUBSTITUTION_KEYWORD, 0) != std::string::npos)) { + if ((pItr->second.find(AclData::ACL_KEYWORD_USER_SUBST, 0) != std::string::npos) || + (pItr->second.find(AclData::ACL_KEYWORD_DOMAIN_SUBST, 0) != std::string::npos) || + (pItr->second.find(AclData::ACL_KEYWORD_USERDOMAIN_SUBST, 0) != std::string::npos)) { rule.ruleHasUserSub[pItr->first] = true; } } + // Find possible routingkey property and cache its pattern + for (pmCitr pItr=rule.props.begin(); pItr!=rule.props.end(); pItr++) { + if (acl::SPECPROP_ROUTINGKEY == pItr->first) + { + rule.pubRoutingKeyInRule = true; + rule.pubRoutingKey = (std::string)pItr->second; + rule.addTopicTest(rule.pubRoutingKey); + } + } + // Action -> Object -> map<user -> set<Rule> > std::ostringstream actionstr; for (int acnt = ((*i)->actionAll ? 0 : (*i)->action); @@ -126,13 +137,6 @@ namespace acl { // Go through the rule properties and find the name and the key. // If found then place them specially for the lookup engine. for (pmCitr pItr=(*i)->props.begin(); pItr!=(*i)->props.end(); pItr++) { - if (acl::SPECPROP_ROUTINGKEY == pItr->first) - { - rule.pubRoutingKeyInRule = true; - rule.pubRoutingKey = (std::string)pItr->second; - rule.addTopicTest(rule.pubRoutingKey); - break; - } if (acl::SPECPROP_NAME == pItr->first) { rule.pubExchNameInRule = true; @@ -164,7 +168,7 @@ namespace acl { // add users and Rule to object set bool allNames = false; // check to see if names.begin is '*' - if ((*(*i)->names.begin()).compare("*") == 0) + if ((*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0) allNames = true; for (nsCitr itr = (allNames ? names.begin() : (*i)->names.begin()); @@ -196,7 +200,7 @@ namespace acl { objstr << AclHelper::getObjectTypeStr((ObjectType) ocnt) << ","; } - bool allNames = ((*(*i)->names.begin()).compare("*") == 0); + bool allNames = ((*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0); std::ostringstream userstr; for (nsCitr itr = (allNames ? names.begin() : (*i)->names.begin()); itr != (allNames ? names.end() : (*i)->names.end()); @@ -215,12 +219,15 @@ namespace acl { << "}" ); } } + + // connection quota + d->setConnQuotaRuleSettings(connQuotaRulesExist, connQuota); } void AclReader::aclRule::processName(const std::string& name, const groupMap& groups) { - if (name.compare("all") == 0) { - names.insert("*"); + if (name.compare(AclData::ACL_KEYWORD_ALL) == 0) { + names.insert(AclData::ACL_KEYWORD_WILDCARD); } else { gmCitr itr = groups.find(name); if (itr == groups.end()) { @@ -231,9 +238,13 @@ namespace acl { } } - AclReader::AclReader() : lineNumber(0), contFlag(false), validationMap(new AclHelper::objectMap) { + AclReader::AclReader(uint16_t theCliMaxConnPerUser) : lineNumber(0), contFlag(false), + validationMap(new AclHelper::objectMap), + cliMaxConnPerUser (theCliMaxConnPerUser), + connQuotaRulesExist(false), + connQuota(new AclData::quotaRuleSet) { AclHelper::loadValidationMap(validationMap); - names.insert("*"); + names.insert(AclData::ACL_KEYWORD_WILDCARD); } AclReader::~AclReader() {} @@ -251,6 +262,11 @@ namespace acl { errorStream << "Unable to open ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F"); return -1; } + // Propagate nonzero per-user max connection setting from CLI + if (cliMaxConnPerUser > 0) { + (*connQuota)[AclData::ACL_KEYWORD_ACL] = cliMaxConnPerUser; + } + // Loop to process the Acl file try { bool err = false; while (ifs.good()) { @@ -279,6 +295,7 @@ namespace acl { } printNames(); printRules(); + printConnectionQuotas(); loadDecisionData(d); return 0; @@ -289,7 +306,7 @@ namespace acl { std::vector<std::string> toks; // Check for continuation - char* contCharPtr = std::strrchr(line, '\\'); + char* contCharPtr = std::strrchr(line, AclData::ACL_SYMBOL_LINE_CONTINUATION); bool cont = contCharPtr != 0; if (cont) *contCharPtr = 0; @@ -300,10 +317,12 @@ namespace acl { return false; } - if (numToks && (toks[0].compare("group") == 0 || contFlag)) { + if (numToks && (toks[0].compare(AclData::ACL_KEYWORD_GROUP) == 0 || contFlag)) { ret = processGroupLine(toks, cont); - } else if (numToks && toks[0].compare("acl") == 0) { + } else if (numToks && toks[0].compare(AclData::ACL_KEYWORD_ACL) == 0) { ret = processAclLine(toks); + } else if (numToks && toks[0].compare(AclData::ACL_KEYWORD_QUOTA) == 0) { + ret = processQuotaLine(toks); } else { // Check for whitespace only line, ignore these bool ws = true; @@ -314,7 +333,10 @@ namespace acl { ret = true; } else { errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber - << ", Non-continuation line must start with \"group\" or \"acl\"."; + << ", Non-continuation line must start with \"" + << AclData::ACL_KEYWORD_GROUP << "\", \"" + << AclData::ACL_KEYWORD_ACL << "\". or \"" + << AclData::ACL_KEYWORD_QUOTA << "\"."; ret = false; } } @@ -334,6 +356,102 @@ namespace acl { return cnt; } + + // Process 'quota' rule lines + // Return true if the line is successfully processed without errors + bool AclReader::processQuotaLine(tokList& toks) { + const unsigned toksSize = toks.size(); + const unsigned minimumSize = 3; + if (toksSize < minimumSize) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Insufficient tokens for quota definition."; + return false; + } + + if (toks[1].compare(AclData::ACL_KEYWORD_QUOTA_CONNECTIONS) == 0) { + return processQuotaConnLine(toks); + } else { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Quota type \"" << toks[1] << "\" unrecognized."; + return false; + } + } + + + // Process 'quota connections' rule lines + // Return true if the line is successfully processed without errors + bool AclReader::processQuotaConnLine(tokList& toks) { + const unsigned toksSize = toks.size(); + + uint16_t nConns(0); + try { + nConns = boost::lexical_cast<uint16_t>(toks[2]); + } catch(const boost::bad_lexical_cast&) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Connection quota value \"" << toks[2] + << "\" cannot be converted to a 16-bit unsigned integer."; + return false; + } + + // limit check the connection setting + if (nConns > AclData::getConnectMaxSpec()) + { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Connection quota value \"" << toks[2] + << "\" exceeds maximum configuration setting of " + << AclData::getConnectMaxSpec(); + return false; + } + + // Apply the connection count to all names in rule + for (unsigned idx = 3; idx < toksSize; idx++) { + if (groups.find(toks[idx]) == groups.end()) { + // This is the name of an individual, not a group + (*connQuota)[toks[idx]] = nConns; + } else { + if (!processQuotaConnGroup(toks[idx], nConns)) + return false; + } + } + return true; + } + + + // Process 'quota connections' group expansion + // Return true if the quota is applied to all members of the group + bool AclReader::processQuotaConnGroup(const std::string& theGroup, uint16_t theQuota) { + gmCitr citr = groups.find(theGroup); + + if (citr == groups.end()) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Failed to expand group \"" << theGroup << "\"."; + return false; + } + + for (nsCitr gni=citr->second->begin(); gni!=citr->second->end(); gni++) { + if (groups.find(*gni) == groups.end()) { + (*connQuota)[*gni] = theQuota; + } else { + if (!processQuotaConnGroup(*gni, theQuota)) + return false; + } + } + return true; + } + + + void AclReader::printConnectionQuotas() const { + QPID_LOG(debug, "ACL: connection quota: " << (*connQuota).size() << " rules found:"); + int cnt = 1; + for (AclData::quotaRuleSetItr itr=(*connQuota).begin(); + itr != (*connQuota).end(); + ++itr,++cnt) { + QPID_LOG(debug, "ACL: quota " << cnt << " : " << (*itr).second + << " connections for " << (*itr).first) + } + } + + // Return true if the line is successfully processed without errors // If cont is true, then groupName must be set to the continuation group name bool AclReader::processGroupLine(tokList& toks, const bool cont) { @@ -459,8 +577,8 @@ namespace acl { return false; } - bool actionAllFlag = toks[3].compare("all") == 0; - bool userAllFlag = toks[2].compare("all") == 0; + bool actionAllFlag = toks[3].compare(AclData::ACL_KEYWORD_ALL) == 0; + bool userAllFlag = toks[2].compare(AclData::ACL_KEYWORD_ALL) == 0; Action action; if (actionAllFlag) { @@ -489,7 +607,7 @@ namespace acl { } if (toksSize >= 5) { // object name-value pair - if (toks[4].compare("all") == 0) { + if (toks[4].compare(AclData::ACL_KEYWORD_ALL) == 0) { rule->setObjectTypeAll(); } else { try { @@ -523,7 +641,7 @@ namespace acl { } } // Check if name (toks[2]) is group; if not, add as name of individual - if (toks[2].compare("all") != 0) { + if (toks[2].compare(AclData::ACL_KEYWORD_ALL) != 0) { if (groups.find(toks[2]) == groups.end()) { addName(toks[2]); } diff --git a/cpp/src/qpid/acl/AclReader.h b/cpp/src/qpid/acl/AclReader.h index 6351c1e509..1fa374c59c 100644 --- a/cpp/src/qpid/acl/AclReader.h +++ b/cpp/src/qpid/acl/AclReader.h @@ -28,6 +28,7 @@ #include <sstream> #include <memory> #include "qpid/acl/AclData.h" +#include "qpid/acl/Acl.h" #include "qpid/broker/AclModule.h" namespace qpid { @@ -96,7 +97,7 @@ class AclReader { std::ostringstream errorStream; public: - AclReader(); + AclReader(uint16_t cliMaxConnPerUser); virtual ~AclReader(); int read(const std::string& fn, boost::shared_ptr<AclData> d); // return=0 for success std::string getError(); @@ -116,8 +117,17 @@ class AclReader { void printRules() const; // debug aid bool isValidUserName(const std::string& name); + bool processQuotaLine(tokList& toks); + bool processQuotaConnLine(tokList& toks); + bool processQuotaConnGroup(const std::string&, uint16_t); + void printConnectionQuotas() const; + static bool isValidGroupName(const std::string& name); static nvPair splitNameValuePair(const std::string& nvpString); + + const uint16_t cliMaxConnPerUser; + bool connQuotaRulesExist; + boost::shared_ptr<AclData::quotaRuleSet> connQuota; }; }} // namespace qpid::acl diff --git a/cpp/src/qpid/acl/AclResourceCounter.cpp b/cpp/src/qpid/acl/AclResourceCounter.cpp new file mode 100644 index 0000000000..66dfd0777e --- /dev/null +++ b/cpp/src/qpid/acl/AclResourceCounter.cpp @@ -0,0 +1,165 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "AclResourceCounter.h" +#include "Acl.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Mutex.h" +#include <assert.h> +#include <sstream> + +using namespace qpid::sys; + +namespace qpid { +namespace acl { + +// +// This module approves various resource creation requests: +// Queues +// + + +// +// +// +ResourceCounter::ResourceCounter(Acl& a, uint16_t ql) : + acl(a), queueLimit(ql) {} + +ResourceCounter::~ResourceCounter() {} + + +// +// limitApproveLH +// +// Resource creation approver. +// If user is under limit increment count and return true. +// Called with lock held. +// +bool ResourceCounter::limitApproveLH( + const std::string& theTitle, + countsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog) { + + bool result(true); + if (theLimit > 0) { + uint16_t count; + countsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + count = (uint16_t)(*eRef).second; + result = count < theLimit; + if (result) { + count += 1; + (*eRef).second = count; + } + } else { + // Not found + theMap[theName] = count = 1; + } + if (emitLog) { + QPID_LOG(trace, theTitle << theName + << " limit=" << theLimit + << " curValue=" << count + << " result=" << (result ? "allow" : "deny")); + } + } + return result; +} + + +// +// releaseLH +// +// Decrement the name's count in map. +// called with dataLock already taken +// +void ResourceCounter::releaseLH( + const std::string& theTitle, countsMap_t& theMap, const std::string& theName, uint16_t theLimit) { + + if (theLimit > 0) { + countsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + uint16_t count = (uint16_t) (*eRef).second; + assert (count > 0); + if (1 == count) { + theMap.erase (eRef); + } else { + (*eRef).second = count - 1; + } + } else { + // User had no connections. + QPID_LOG(notice, theTitle << theName + << "' not found in resource count pool"); + } + } +} + + +// +// approveCreateQueue +// Count an attempted queue creation by this user. +// Disapprove if over limit. +// +bool ResourceCounter::approveCreateQueue(const std::string& userId, const std::string& queueName) +{ + Mutex::ScopedLock locker(dataLock); + + bool okByQ = limitApproveLH("ACL Queue creation approver. userId:", queuePerUserMap, userId, queueLimit, true); + + if (okByQ) { + // Queue is owned by this userId + queueOwnerMap[queueName] = userId; + + QPID_LOG(trace, "ACL create queue approved for user '" << userId + << "' queue '" << queueName << "'"); + } else { + + QPID_LOG(error, "Client max queue count limit of " << queueLimit + << " exceeded by '" << userId << "' creating queue '" + << queueName << "'. Queue creation denied."); + + acl.reportQueueLimit(userId, queueName); + } + return okByQ; +} + + +// +// recordDestroyQueue +// Return a destroyed queue to a user's quota +// +void ResourceCounter::recordDestroyQueue(const std::string& queueName) +{ + Mutex::ScopedLock locker(dataLock); + + queueOwnerMap_t::iterator eRef = queueOwnerMap.find(queueName); + if (eRef != queueOwnerMap.end()) { + releaseLH("ACL resource counter: Queue owner for queue '", queuePerUserMap, (*eRef).second, queueLimit); + + queueOwnerMap.erase(eRef); + } else { + QPID_LOG(notice, "ACL resource counter: Queue '" << queueName + << "' not found in queue owner map"); + } +} + +}} // namespace qpid::acl diff --git a/cpp/src/qpid/acl/AclResourceCounter.h b/cpp/src/qpid/acl/AclResourceCounter.h new file mode 100644 index 0000000000..f5995eb961 --- /dev/null +++ b/cpp/src/qpid/acl/AclResourceCounter.h @@ -0,0 +1,78 @@ +#ifndef QPID_ACL_RESOURCECOUNTER_H +#define QPID_ACL_RESOURCECOUNTER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/sys/Mutex.h" +#include <boost/iterator/iterator_concepts.hpp> + +#include <map> + +namespace qpid { + +namespace acl { +class Acl; + + /** + * Approve or disapprove resource creation requests + */ +class ResourceCounter +{ +private: + typedef std::map<std::string, uint32_t> countsMap_t; + typedef std::map<std::string, std::string> queueOwnerMap_t; + + Acl& acl; + uint16_t queueLimit; + qpid::sys::Mutex dataLock; + + /** Records queueName-queueUserId */ + queueOwnerMap_t queueOwnerMap; + + /** Records queue-by-owner counts */ + countsMap_t queuePerUserMap; + + /** Return approval for proposed resource creation */ + bool limitApproveLH(const std::string& theTitle, + countsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog); + + /** Release a connection */ + void releaseLH(const std::string& theTitle, + countsMap_t& theMap, + const std::string& theName, + uint16_t theLimit); + +public: + ResourceCounter(Acl& acl, uint16_t ql); + ~ResourceCounter(); + + // Queue counting + bool approveCreateQueue(const std::string& userId, const std::string& queueName); + void recordDestroyQueue(const std::string& queueName); +}; + +}} // namespace qpid::acl + +#endif /*!QPID_ACL_RESOURCECOUNTER_H*/ diff --git a/cpp/src/qpid/acl/AclTopicMatch.h b/cpp/src/qpid/acl/AclTopicMatch.h index 486c229ad5..654d1d63d4 100644 --- a/cpp/src/qpid/acl/AclTopicMatch.h +++ b/cpp/src/qpid/acl/AclTopicMatch.h @@ -30,7 +30,7 @@ namespace qpid { namespace broker { // Class for executing topic exchange routing key matching rules in -// Acl code the allows or denies users publishing to an exchange. +// Acl code. Allows or denies users publishing to an exchange. class TopicExchange::TopicExchangeTester { class boundNode; diff --git a/cpp/src/qpid/acl/AclValidator.cpp b/cpp/src/qpid/acl/AclValidator.cpp index 85f0f7c240..73b49b2959 100644 --- a/cpp/src/qpid/acl/AclValidator.cpp +++ b/cpp/src/qpid/acl/AclValidator.cpp @@ -94,6 +94,22 @@ namespace acl { boost::shared_ptr<PropertyType>( new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + validators.insert(Validator(acl::SPECPROP_MAXFILESIZELOWERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXFILESIZEUPPERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXFILECOUNTLOWERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXFILECOUNTUPPERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + std::string policyTypes[] = {"ring", "ring_strict", "flow_to_disk", "reject"}; std::vector<std::string> v(policyTypes, policyTypes + sizeof(policyTypes) / sizeof(std::string)); validators.insert(Validator(acl::SPECPROP_POLICYTYPE, diff --git a/cpp/src/qpid/acl/management-schema.xml b/cpp/src/qpid/acl/management-schema.xml index f52c251bed..2ac20bb324 100644 --- a/cpp/src/qpid/acl/management-schema.xml +++ b/cpp/src/qpid/acl/management-schema.xml @@ -25,8 +25,10 @@ <property name="maxConnections" type="uint16" access="RO" desc="Maximum allowed connections"/> <property name="maxConnectionsPerIp" type="uint16" access="RO" desc="Maximum allowed connections"/> <property name="maxConnectionsPerUser" type="uint16" access="RO" desc="Maximum allowed connections"/> + <property name="maxQueuesPerUser" type="uint16" access="RO" desc="Maximum allowed queues"/> <statistic name="aclDenyCount" type="count64" unit="request" desc="Number of ACL requests denied"/> <statistic name="connectionDenyCount" type="count64" unit="connection" desc="Number of connections denied"/> + <statistic name="queueQuotaDenyCount" type="count64" unit="queue" desc="Number of queue creations denied"/> <method name="reloadACLFile" desc="Reload the ACL file"/> @@ -70,11 +72,13 @@ <arg name="reason" type="lstr"/> <arg name="userId" type="sstr"/> <arg name="clientAddr" type="sstr"/> + <arg name="queueName" type="sstr"/> </eventArguments> <event name="allow" sev="inform" args="userId, action, objectType, objectName, arguments"/> <event name="deny" sev="notice" args="userId, action, objectType, objectName, arguments"/> <event name="connectionDeny" sev="notice" args="userId, clientAddr"/> + <event name="queueQuotaDeny" sev="notice" args="userId, queueName"/> <event name="fileLoaded" sev="inform" args="userId"/> <event name="fileLoadFailed" sev="error" args="userId, reason"/> diff --git a/cpp/src/qpid/agent/ManagementAgentImpl.cpp b/cpp/src/qpid/agent/ManagementAgentImpl.cpp index 09b7fa58e9..a48789973a 100644 --- a/cpp/src/qpid/agent/ManagementAgentImpl.cpp +++ b/cpp/src/qpid/agent/ManagementAgentImpl.cpp @@ -654,7 +654,10 @@ void ManagementAgentImpl::invokeMethodRequest(const string& body, const string& void ManagementAgentImpl::handleGetQuery(const string& body, const string& cid, const string& rte, const string& rtk) { - moveNewObjectsLH(); + { + sys::Mutex::ScopedLock lock(agentLock); + moveNewObjectsLH(lock); + } Variant::Map inMap; Variant::Map::const_iterator i; @@ -985,14 +988,37 @@ ManagementAgentImpl::PackageMap::iterator ManagementAgentImpl::findOrAddPackage( return result.first; } -void ManagementAgentImpl::moveNewObjectsLH() +// note well: caller must hold agentLock when calling this! +void ManagementAgentImpl::moveNewObjectsLH(const sys::Mutex::ScopedLock& /*agentLock*/) { sys::Mutex::ScopedLock lock(addLock); - for (ObjectMap::iterator iter = newManagementObjects.begin(); - iter != newManagementObjects.end(); - iter++) - managementObjects[iter->first] = iter->second; - newManagementObjects.clear(); + ObjectMap::iterator newObj = newManagementObjects.begin(); + while (newObj != newManagementObjects.end()) { + // before adding a new mgmt object, check for duplicates: + ObjectMap::iterator oldObj = managementObjects.find(newObj->first); + if (oldObj == managementObjects.end()) { + managementObjects[newObj->first] = newObj->second; + newManagementObjects.erase(newObj++); // post inc iterator safe! + } else { + // object exists with same object id. This may be legit, for example, when a + // recently deleted object is re-added before the mgmt poll runs. + if (newObj->second->isDeleted()) { + // @TODO fixme: we missed an add-delete for the new object + QPID_LOG(warning, "Mgmt Object deleted before update sent, oid=" << newObj->first); + newManagementObjects.erase(newObj++); // post inc iterator safe! + } else if (oldObj->second->isDeleted()) { + // skip adding newObj, try again later once oldObj has been cleaned up by poll + ++newObj; + } else { + // real bad - two objects exist with same OID. This is a bug in the application + QPID_LOG(error, "Detected two Mgmt Objects using the same object id! oid=" << newObj->first + << ", this is bad!"); + // what to do here? Can't erase an active obj - owner has a pointer to it. + // for now I punt. Maybe the flood of log messages will get someone's attention :P + ++newObj; + } + } + } } void ManagementAgentImpl::addClassLocal(uint8_t classKind, @@ -1060,7 +1086,7 @@ void ManagementAgentImpl::periodicProcessing() if (!connected) return; - moveNewObjectsLH(); + moveNewObjectsLH(lock); // // Clear the been-here flag on all objects in the map. diff --git a/cpp/src/qpid/agent/ManagementAgentImpl.h b/cpp/src/qpid/agent/ManagementAgentImpl.h index 53f3c13a91..4c97bc89da 100644 --- a/cpp/src/qpid/agent/ManagementAgentImpl.h +++ b/cpp/src/qpid/agent/ManagementAgentImpl.h @@ -92,10 +92,6 @@ class ManagementAgentImpl : public ManagementAgent, public client::MessageListen uint16_t getInterval() { return interval; } void periodicProcessing(); - // these next are here to support the hot-wiring of state between clustered brokers - uint64_t getNextObjectId(void) { return nextObjectId; } - void setNextObjectId(uint64_t o) { nextObjectId = o; } - uint16_t getBootSequence(void) { return bootSequence; } void setBootSequence(uint16_t b) { bootSequence = b; } @@ -261,7 +257,7 @@ class ManagementAgentImpl : public ManagementAgent, public client::MessageListen void storeData(bool requested=false); void retrieveData(std::string& vendor, std::string& product, std::string& inst); PackageMap::iterator findOrAddPackage(const std::string& name); - void moveNewObjectsLH(); + void moveNewObjectsLH(const sys::Mutex::ScopedLock& agentLock); void addClassLocal (uint8_t classKind, PackageMap::iterator pIter, const std::string& className, diff --git a/cpp/src/qpid/amqp/CharSequence.cpp b/cpp/src/qpid/amqp/CharSequence.cpp new file mode 100644 index 0000000000..857ec7e587 --- /dev/null +++ b/cpp/src/qpid/amqp/CharSequence.cpp @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "CharSequence.h" + +namespace qpid { +namespace amqp { + +void CharSequence::init() +{ + data = 0; + size = 0; +} + +CharSequence::operator bool() const +{ + return data && size; +} +std::string CharSequence::str() const +{ + return std::string(data, size); +} + +CharSequence CharSequence::create(const char* data, size_t size) +{ + CharSequence c = {data, size}; + return c; +} + +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/CharSequence.h b/cpp/src/qpid/amqp/CharSequence.h new file mode 100644 index 0000000000..307a4b1537 --- /dev/null +++ b/cpp/src/qpid/amqp/CharSequence.h @@ -0,0 +1,49 @@ +#ifndef QPID_AMQP_CHARSEQUENCE_H +#define QPID_AMQP_CHARSEQUENCE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <stddef.h> +#include <string> +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace amqp { + +/** + * Simple record of a particular sequence of chars/bytes. The memroy + * referenced is assumed to be owned by some other entity, this is + * merely a pointer into a (segment of) it. + */ +struct CharSequence +{ + const char* data; + size_t size; + + QPID_COMMON_EXTERN operator bool() const; + QPID_COMMON_EXTERN std::string str() const; + QPID_COMMON_EXTERN void init(); + + QPID_COMMON_EXTERN static CharSequence create(const char* data, size_t size); +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_CHARSEQUENCE_H*/ diff --git a/cpp/src/qpid/amqp/Codec.h b/cpp/src/qpid/amqp/Codec.h new file mode 100644 index 0000000000..c91cd0a96b --- /dev/null +++ b/cpp/src/qpid/amqp/Codec.h @@ -0,0 +1,83 @@ +#ifndef QPID_AMQP_CODEC_H +#define QPID_AMQP_CODEC_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +namespace qpid { +namespace amqp { + +/** + * + */ +class Codec +{ + public: + + + + private: + + struct Constructor + { + uint8_t code; + Descriptor descriptor; + bool isDescribed; + }; + + Constructor readConstructor(Decoder decoder, Reader reader) + { + Constructor result; + result.code = decoder.readCode(); + if (code == DESCRIPTOR) { + result.isDescribed = true; + result.descriptor = decoder.readDescriptor(); + result.code = decoder.readCode(); + } else { + result.isDescribed = false; + } + return result; + } +}; + +Codec::Descriptor Codec::Decoder::readDescriptor() +{ + uint8_t code = decoder.readCode(); + switch(code) { + case SYMBOL8: + return Descriptor(readSequence8()); + case SYMBOL32: + return Descriptor(readSequence32()); + case ULONG: + return Descriptor(readULong()); + case ULONG_SMALL: + return Descriptor((uint64_t) readUByte()); + case ULONG_ZERO: + return Descriptor((uint64_t) 0); + default: + throw qpid::Exception("Expected descriptor of type ulong or symbol; found " << code); + } +} + +Codec::Descriptor::Descriptor(uint64_t id) : value.id(id), type(NUMERIC) {} +Codec::Descriptor::Descriptor(const CharSequence& symbol) : value.symbol(symbol), type(SYMBOLIC) {} +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_CODEC_H*/ diff --git a/cpp/src/qpid/amqp/Constructor.h b/cpp/src/qpid/amqp/Constructor.h new file mode 100644 index 0000000000..444e455670 --- /dev/null +++ b/cpp/src/qpid/amqp/Constructor.h @@ -0,0 +1,42 @@ +#ifndef QPID_AMQP_CONSTRUCTOR_H +#define QPID_AMQP_CONSTRUCTOR_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/Descriptor.h" +namespace qpid { +namespace amqp { + +/** + * Representation of an AMQP 1.0 type 'constructor' (i.e. a type code + * with an optional descriptor) + */ +struct Constructor +{ + uint8_t code; + Descriptor descriptor; + bool isDescribed; + + Constructor(uint8_t c) : code(c), descriptor(0), isDescribed(false) {} +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_CONSTRUCTOR_H*/ diff --git a/cpp/src/qpid/amqp/Decoder.cpp b/cpp/src/qpid/amqp/Decoder.cpp new file mode 100644 index 0000000000..9c577e6c92 --- /dev/null +++ b/cpp/src/qpid/amqp/Decoder.cpp @@ -0,0 +1,545 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/Decoder.h" +#include "qpid/amqp/CharSequence.h" +#include "qpid/amqp/Constructor.h" +#include "qpid/amqp/Descriptor.h" +#include "qpid/amqp/Reader.h" +#include "qpid/amqp/typecodes.h" +#include "qpid/types/Uuid.h" +#include "qpid/types/Variant.h" +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" + +namespace qpid { +namespace amqp { + +using namespace qpid::amqp::typecodes; + +Decoder::Decoder(const char* d, size_t s) : start(d), size(s), position(0) {} + +namespace { +class MapBuilder : public Reader +{ + public: + void onNull(const Descriptor*) + { + qpid::types::Variant v; + handle(v, NULL_NAME); + } + void onBoolean(bool v, const Descriptor*) + { + handle(v, BOOLEAN_NAME); + } + void onUByte(uint8_t v, const Descriptor*) + { + handle(v, UBYTE_NAME); + } + void onUShort(uint16_t v, const Descriptor*) + { + handle(v, USHORT_NAME); + } + void onUInt(uint32_t v, const Descriptor*) + { + handle(v, UINT_NAME); + } + void onULong(uint64_t v, const Descriptor*) + { + handle(v, ULONG_NAME); + } + void onByte(int8_t v, const Descriptor*) + { + handle(v, BYTE_NAME); + } + void onShort(int16_t v, const Descriptor*) + { + handle(v, SHORT_NAME); + } + void onInt(int32_t v, const Descriptor*) + { + handle(v, INT_NAME); + } + void onLong(int64_t v, const Descriptor*) + { + handle(v, LONG_NAME); + } + void onFloat(float v, const Descriptor*) + { + handle(v, FLOAT_NAME); + } + void onDouble(double v, const Descriptor*) + { + handle(v, DOUBLE_NAME); + } + void onUuid(const CharSequence& v, const Descriptor*) + { + handle(v, UUID_NAME); + } + void onTimestamp(int64_t v, const Descriptor*) + { + handle(v, TIMESTAMP_NAME); + } + void onBinary(const CharSequence& v, const Descriptor*) + { + handle(v); + } + void onString(const CharSequence& v, const Descriptor*) + { + handle(v); + } + void onSymbol(const CharSequence& v, const Descriptor*) + { + handle(v); + } + MapBuilder(qpid::types::Variant::Map& m) : map(m), state(KEY) {} + private: + qpid::types::Variant::Map& map; + enum {KEY, SKIP, VALUE} state; + std::string key; + + template <typename T> void handle(T value, const std::string& name) + { + switch (state) { + case KEY: + QPID_LOG(warning, "Ignoring key of type " << name); + state = SKIP; + break; + case VALUE: + map[key] = value; + case SKIP: + state = KEY; + break; + } + } + void handle(const CharSequence& value) + { + switch (state) { + case KEY: + key = value.str(); + state = VALUE; + break; + case VALUE: + map[key] = value.str(); + case SKIP: + state = KEY; + break; + } + } +}; +} +void Decoder::readMap(qpid::types::Variant::Map& map) +{ + MapBuilder builder(map); + read(builder); +} + +qpid::types::Variant::Map Decoder::readMap() +{ + qpid::types::Variant::Map map; + readMap(map); + return map; +} + +void Decoder::read(Reader& reader) +{ + while (available() && reader.proceed()) { + readOne(reader); + } +} + +void Decoder::readOne(Reader& reader) +{ + const char* temp = start + position; + Constructor c = readConstructor(); + if (c.isDescribed) reader.onDescriptor(c.descriptor, temp); + readValue(reader, c.code, c.isDescribed ? &c.descriptor : 0); +} + +void Decoder::readValue(Reader& reader, uint8_t code, const Descriptor* descriptor) +{ + switch(code) { + case NULL_VALUE: + reader.onNull(descriptor); + break; + case BOOLEAN: + reader.onBoolean(readBoolean(), descriptor); + break; + case BOOLEAN_TRUE: + reader.onBoolean(true, descriptor); + break; + case BOOLEAN_FALSE: + reader.onBoolean(false, descriptor); + break; + case UBYTE: + reader.onUByte(readUByte(), descriptor); + break; + case USHORT: + reader.onUShort(readUShort(), descriptor); + break; + case UINT: + reader.onUInt(readUInt(), descriptor); + break; + case UINT_SMALL: + reader.onUInt(readUByte(), descriptor); + break; + case UINT_ZERO: + reader.onUInt(0, descriptor); + break; + case ULONG: + reader.onULong(readULong(), descriptor); + break; + case ULONG_SMALL: + reader.onULong(readUByte(), descriptor); + break; + case ULONG_ZERO: + reader.onULong(0, descriptor); + break; + case BYTE: + reader.onByte(readByte(), descriptor); + break; + case SHORT: + reader.onShort(readShort(), descriptor); + break; + case INT: + reader.onInt(readInt(), descriptor); + break; + case INT_SMALL: + reader.onInt(readByte(), descriptor); + break; + case LONG: + reader.onLong(readLong(), descriptor); + break; + case LONG_SMALL: + reader.onLong(readByte(), descriptor); + break; + case FLOAT: + reader.onFloat(readFloat(), descriptor); + break; + case DOUBLE: + reader.onDouble(readDouble(), descriptor); + break; + case UUID: + reader.onUuid(readRawUuid(), descriptor); + break; + case TIMESTAMP: + reader.onTimestamp(readLong(), descriptor); + break; + + case BINARY8: + reader.onBinary(readSequence8(), descriptor); + break; + case BINARY32: + reader.onBinary(readSequence32(), descriptor); + break; + case STRING8: + reader.onString(readSequence8(), descriptor); + break; + case STRING32: + reader.onString(readSequence32(), descriptor); + break; + case SYMBOL8: + reader.onSymbol(readSequence8(), descriptor); + break; + case SYMBOL32: + reader.onSymbol(readSequence32(), descriptor); + break; + + case LIST0: + reader.onStartList(0, CharSequence::create(0, 0), descriptor); + reader.onEndList(0, descriptor); + break; + case LIST8: + readList8(reader, descriptor); + break; + case LIST32: + readList32(reader, descriptor); + break; + case MAP8: + readMap8(reader, descriptor); + break; + case MAP32: + readMap32(reader, descriptor); + break; + case ARRAY8: + readArray8(reader, descriptor); + break; + case ARRAY32: + readArray32(reader, descriptor); + break; + default: + break; + } +} + +void Decoder::readList8(Reader& reader, const Descriptor* descriptor) +{ + uint8_t size = readUByte(); + uint8_t count = readUByte(); + readList(reader, size-sizeof(size), count, descriptor); +} + +void Decoder::readList32(Reader& reader, const Descriptor* descriptor) +{ + uint32_t size = readUInt(); + uint32_t count = readUInt(); + readList(reader, size-sizeof(size), count, descriptor); +} + +void Decoder::readMap8(Reader& reader, const Descriptor* descriptor) +{ + uint8_t size = readUByte(); + uint8_t count = readUByte(); + readMap(reader, size-sizeof(size), count, descriptor); +} + +void Decoder::readMap32(Reader& reader, const Descriptor* descriptor) +{ + uint32_t size = readUInt(); + uint32_t count = readUInt(); + readMap(reader, size-sizeof(size), count, descriptor); +} + +void Decoder::readArray8(Reader& reader, const Descriptor* descriptor) +{ + uint8_t size = readUByte(); + uint8_t count = readUByte(); + readArray(reader, size-sizeof(size), count, descriptor); +} + +void Decoder::readArray32(Reader& reader, const Descriptor* descriptor) +{ + uint32_t size = readUInt(); + uint32_t count = readUInt(); + readArray(reader, size-sizeof(size), count, descriptor); +} + +void Decoder::readList(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor) +{ + if (reader.onStartList(count, CharSequence::create(data(), size), descriptor)) { + for (uint32_t i = 0; i < count; ++i) { + readOne(reader); + } + reader.onEndList(count, descriptor); + } else { + //skip + advance(size); + } +} +void Decoder::readMap(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor) +{ + if (reader.onStartMap(count, CharSequence::create(data(), size), descriptor)) { + for (uint32_t i = 0; i < count; ++i) { + readOne(reader); + } + reader.onEndMap(count, descriptor); + } else { + //skip + advance(size); + } +} + +void Decoder::readArray(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor) +{ + size_t temp = position; + Constructor constructor = readConstructor(); + CharSequence raw = CharSequence::create(data(), size-(position-temp)); + if (reader.onStartArray(count, raw, constructor, descriptor)) { + for (uint32_t i = 0; i < count; ++i) { + readValue(reader, constructor.code, constructor.isDescribed ? &constructor.descriptor : 0); + } + reader.onEndArray(count, descriptor); + } else { + //skip + advance(raw.size); + } +} + + +Constructor Decoder::readConstructor() +{ + Constructor result(readCode()); + if (result.code == DESCRIPTOR) { + result.isDescribed = true; + result.descriptor = readDescriptor(); + result.code = readCode(); + } else { + result.isDescribed = false; + } + return result; +} + +Descriptor Decoder::readDescriptor() +{ + uint8_t code = readCode(); + switch(code) { + case SYMBOL8: + return Descriptor(readSequence8()); + case SYMBOL32: + return Descriptor(readSequence32()); + case ULONG: + return Descriptor(readULong()); + case ULONG_SMALL: + return Descriptor((uint64_t) readUByte()); + case ULONG_ZERO: + return Descriptor((uint64_t) 0); + default: + throw qpid::Exception(QPID_MSG("Expected descriptor of type ulong or symbol; found " << code)); + } +} + +void Decoder::advance(size_t n) +{ + if (n > available()) throw qpid::Exception(QPID_MSG("Out of Bounds")); + position += n; +} + +const char* Decoder::data() +{ + return start + position; +} + +size_t Decoder::available() +{ + return size - position; +} + +uint8_t Decoder::readCode() +{ + return readUByte(); +} + +bool Decoder::readBoolean() +{ + return readUByte(); +} + +uint8_t Decoder::readUByte() +{ + return static_cast<uint8_t>(start[position++]); +} + +uint16_t Decoder::readUShort() +{ + uint16_t hi = (unsigned char) start[position++]; + hi = hi << 8; + hi |= (unsigned char) start[position++]; + return hi; +} + +uint32_t Decoder::readUInt() +{ + uint32_t a = (unsigned char) start[position++]; + uint32_t b = (unsigned char) start[position++]; + uint32_t c = (unsigned char) start[position++]; + uint32_t d = (unsigned char) start[position++]; + a = a << 24; + a |= b << 16; + a |= c << 8; + a |= d; + return a; +} + +uint64_t Decoder::readULong() +{ + uint64_t hi =readUInt(); + uint64_t lo = readUInt(); + hi = hi << 32; + return hi | lo; +} + +int8_t Decoder::readByte() +{ + return (int8_t) readUByte(); +} + +int16_t Decoder::readShort() +{ + return (int16_t) readUShort(); +} + +int32_t Decoder::readInt() +{ + return (int32_t) readUInt(); +} + +int64_t Decoder::readLong() +{ + return (int64_t) readULong(); +} + +float Decoder::readFloat() +{ + union { + uint32_t i; + float f; + } val; + val.i = readUInt(); + return val.f; +} + +double Decoder::readDouble() +{ + union { + uint64_t i; + double f; + } val; + val.i = readULong(); + return val.f; +} + +CharSequence Decoder::readSequence8() +{ + CharSequence s; + s.size = readUByte(); + s.data = start + position; + advance(s.size); + return s; +} + +CharSequence Decoder::readSequence32() +{ + CharSequence s; + s.size = readUInt(); + s.data = start + position; + advance(s.size); + return s; +} + +qpid::types::Uuid Decoder::readUuid() +{ + qpid::types::Uuid uuid(start + position); + advance(16); + return uuid; +} + +CharSequence Decoder::readRawUuid() +{ + CharSequence s; + s.data = start + position; + s.size = 16; + advance(s.size); + return s; +} + +size_t Decoder::getPosition() const { return position; } +size_t Decoder::getSize() const { return size; } +void Decoder::resetSize(size_t s) { size = s; } +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/Decoder.h b/cpp/src/qpid/amqp/Decoder.h new file mode 100644 index 0000000000..7ddfe0f17f --- /dev/null +++ b/cpp/src/qpid/amqp/Decoder.h @@ -0,0 +1,99 @@ +#ifndef QPID_AMQP_DECODER_H +#define QPID_AMQP_DECODER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/sys/IntegerTypes.h" +#include "qpid/CommonImportExport.h" +#include <map> +#include <string> +#include <stddef.h> + +namespace qpid { +namespace types { +class Uuid; +class Variant; +} +namespace amqp { +struct CharSequence; +struct Constructor; +struct Descriptor; +class Reader; + +/** + * Class to assist in decoding an AMQP encoded data-stream. + */ +class Decoder +{ + public: + QPID_COMMON_EXTERN Decoder(const char*, size_t); + + QPID_COMMON_EXTERN size_t available(); + QPID_COMMON_EXTERN uint8_t readCode(); + + QPID_COMMON_EXTERN bool readBoolean(); + QPID_COMMON_EXTERN uint8_t readUByte(); + QPID_COMMON_EXTERN uint16_t readUShort(); + QPID_COMMON_EXTERN uint32_t readUInt(); + QPID_COMMON_EXTERN uint64_t readULong(); + QPID_COMMON_EXTERN int8_t readByte(); + QPID_COMMON_EXTERN int16_t readShort(); + QPID_COMMON_EXTERN int32_t readInt(); + QPID_COMMON_EXTERN int64_t readLong(); + QPID_COMMON_EXTERN float readFloat(); + QPID_COMMON_EXTERN double readDouble(); + QPID_COMMON_EXTERN qpid::types::Uuid readUuid(); + QPID_COMMON_EXTERN CharSequence readSequence8(); + QPID_COMMON_EXTERN CharSequence readSequence32(); + QPID_COMMON_EXTERN Descriptor readDescriptor(); + QPID_COMMON_EXTERN void read(Reader& reader); + + QPID_COMMON_EXTERN void readMap(std::map<std::string, qpid::types::Variant>&); + QPID_COMMON_EXTERN std::map<std::string, qpid::types::Variant> readMap(); + QPID_COMMON_EXTERN void advance(size_t); + QPID_COMMON_EXTERN size_t getPosition() const; + QPID_COMMON_EXTERN void resetSize(size_t size); + QPID_COMMON_EXTERN size_t getSize() const; + + private: + const char* const start; + size_t size; + size_t position; + + void readOne(Reader& reader); + void readValue(Reader& reader, uint8_t code, const Descriptor* descriptor); + void readList(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor); + void readMap(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor); + void readArray(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor); + void readList8(Reader& reader, const Descriptor* descriptor); + void readList32(Reader& reader, const Descriptor* descriptor); + void readMap8(Reader& reader, const Descriptor* descriptor); + void readMap32(Reader& reader, const Descriptor* descriptor); + void readArray8(Reader& reader, const Descriptor* descriptor); + void readArray32(Reader& reader, const Descriptor* descriptor); + CharSequence readRawUuid(); + Constructor readConstructor(); + const char* data(); + +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_DECODER_H*/ diff --git a/cpp/src/qpid/amqp/Descriptor.cpp b/cpp/src/qpid/amqp/Descriptor.cpp new file mode 100644 index 0000000000..087e87c5e6 --- /dev/null +++ b/cpp/src/qpid/amqp/Descriptor.cpp @@ -0,0 +1,52 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Descriptor.h" + +namespace qpid { +namespace amqp { +Descriptor::Descriptor(uint64_t code) : type(NUMERIC) { value.code = code; } +Descriptor::Descriptor(const CharSequence& symbol) : type(SYMBOLIC) { value.symbol = symbol; } +bool Descriptor::match(const std::string& symbol, uint64_t code) const +{ + switch (type) { + case SYMBOLIC: + return symbol.compare(0, symbol.size(), value.symbol.data, value.symbol.size) == 0; + case NUMERIC: + return code == value.code; + } + return false; +} + + +std::ostream& operator<<(std::ostream& os, const Descriptor& d) +{ + switch (d.type) { + case Descriptor::SYMBOLIC: + if (d.value.symbol.data && d.value.symbol.size) os << std::string(d.value.symbol.data, d.value.symbol.size); + else os << "null"; + break; + case Descriptor::NUMERIC: + os << d.value.code; + break; + } + return os; +} +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/Descriptor.h b/cpp/src/qpid/amqp/Descriptor.h new file mode 100644 index 0000000000..c36aa38ee3 --- /dev/null +++ b/cpp/src/qpid/amqp/Descriptor.h @@ -0,0 +1,54 @@ +#ifndef QPID_AMQP_DESCRIPTOR_H +#define QPID_AMQP_DESCRIPTOR_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/CharSequence.h" +#include "qpid/sys/IntegerTypes.h" +#include <ostream> + +namespace qpid { +namespace amqp { + +/** + * Representation of an AMQP 1.0 type descriptor. + */ +struct Descriptor +{ + union { + CharSequence symbol; + uint64_t code; + } value; + enum { + NUMERIC, + SYMBOLIC + } type; + + Descriptor(uint64_t code); + Descriptor(const CharSequence& symbol); + bool match(const std::string&, uint64_t) const; +}; + +std::ostream& operator<<(std::ostream& os, const Descriptor& d); + +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_DESCRIPTOR_H*/ diff --git a/cpp/src/qpid/amqp/Encoder.cpp b/cpp/src/qpid/amqp/Encoder.cpp new file mode 100644 index 0000000000..6599f70811 --- /dev/null +++ b/cpp/src/qpid/amqp/Encoder.cpp @@ -0,0 +1,402 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/Encoder.h" +#include "qpid/amqp/CharSequence.h" +#include "qpid/amqp/Descriptor.h" +#include "qpid/amqp/typecodes.h" +#include "qpid/types/Uuid.h" +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" +#include <assert.h> +#include <string.h> + +namespace qpid { +namespace amqp { + +namespace { +template <typename T> size_t encode(char* data, T i); +template <> size_t encode<uint8_t>(char* data, uint8_t i) +{ + *data = i; + return 1; +} +template <> size_t encode<uint16_t>(char* data, uint16_t i) +{ + uint16_t b = i; + size_t position(0); + data[position++] = (uint8_t) (0xFF & (b >> 8)); + data[position++] = (uint8_t) (0xFF & b); + return position; +} +template <> size_t encode<uint32_t>(char* data, uint32_t i) +{ + uint32_t b = i; + size_t position(0); + data[position++] = (uint8_t) (0xFF & (b >> 24)); + data[position++] = (uint8_t) (0xFF & (b >> 16)); + data[position++] = (uint8_t) (0xFF & (b >> 8)); + data[position++] = (uint8_t) (0xFF & b); + return position; +} +template <> size_t encode<uint64_t>(char* data, uint64_t i) +{ + uint32_t hi = i >> 32; + uint32_t lo = i; + size_t r(0); + r += encode(data, hi); + r += encode(data + r, lo); + return r; +} +template<typename T> struct Backfill +{ + T size; + T count; + char* location; +}; + +template<typename T> void end(T count, void* token, char* current) +{ + Backfill<T> b; + b.location = (char*) token; + b.size = (T) (current - b.location) - sizeof(b.size); + b.count = count; + b.location += encode<T>(b.location, b.size); + encode<T>(b.location, b.count); +} +} +char* Encoder::skip(size_t n) +{ + char* current = data + position; + check(n); + position += n; + return current; +} + +void Encoder::write(bool b) +{ + check(sizeof(b)); + position += encode<uint8_t>(data+position, b ? 1u : 0u); +} +void Encoder::write(uint8_t i) +{ + check(sizeof(i)); + position += encode<uint8_t>(data+position, i); +} +void Encoder::write(uint16_t i) +{ + check(sizeof(i)); + position += encode<uint16_t>(data+position, i); +} +void Encoder::write(uint32_t i) +{ + check(sizeof(i)); + position += encode<uint32_t>(data+position, i); +} +void Encoder::write(uint64_t i) +{ + check(sizeof(i)); + position += encode<uint64_t>(data+position, i); +} +void Encoder::write(int8_t i) +{ + check(sizeof(i)); + position += encode(data+position, (uint8_t) i); +} +void Encoder::write(int16_t i) +{ + check(sizeof(i)); + position += encode(data+position, (uint16_t) i); +} +void Encoder::write(int32_t i) +{ + check(sizeof(i)); + position += encode(data+position, (uint32_t) i); +} +void Encoder::write(int64_t i) +{ + check(sizeof(i)); + position += encode(data+position, (uint64_t) i); +} +void Encoder::write(float f) +{ + check(sizeof(f)); + union { + uint32_t i; + float f; + } val; + + val.f = f; + write(val.i); +} +void Encoder::write(double d) +{ + check(sizeof(d)); + union { + uint64_t i; + double d; + } val; + + val.d = d; + write(val.i); +} +void Encoder::write(const qpid::types::Uuid& uuid) +{ + writeBytes((const char*) uuid.data(), uuid.size()); +} + +void Encoder::writeBytes(const char* bytes, size_t count) +{ + check(count); + ::memcpy(data + position, bytes, count); + position += count; +} + +void Encoder::writeCode(uint8_t code) +{ + write(code); +} + +void Encoder::writeNull(const Descriptor* d) +{ + if (d) writeDescriptor(*d); + writeCode(typecodes::NULL_VALUE); +} +void Encoder::writeBoolean(bool b, const Descriptor* d) +{ + if (d) writeDescriptor(*d); + writeCode(b ? typecodes::BOOLEAN_TRUE : typecodes::BOOLEAN_FALSE); +} +void Encoder::writeUByte(uint8_t i, const Descriptor* d) +{ + write(i, typecodes::UBYTE, d); +} + +void Encoder::writeUShort(uint16_t i, const Descriptor* d) +{ + write(i, typecodes::USHORT, d); +} + +void Encoder::writeUInt(uint32_t i, const Descriptor* d) +{ + if (i == 0) { + if (d) writeDescriptor(*d); + writeCode(typecodes::UINT_ZERO); + } else { + if (i < 256) { + write((uint8_t) i, typecodes::UINT_SMALL, d); + } else { + write(i, typecodes::UINT, d); + } + } +} + +void Encoder::writeULong(uint64_t i, const Descriptor* d) +{ + if (i == 0) { + if (d) writeDescriptor(*d); + writeCode(typecodes::ULONG_ZERO); + } else { + if (i < 256) { + write((uint8_t) i, typecodes::ULONG_SMALL, d); + } else { + write(i, typecodes::ULONG, d); + } + } +} + +void Encoder::writeByte(int8_t i, const Descriptor* d) +{ + write((uint8_t) i, typecodes::LONG, d); +} + +void Encoder::writeShort(int16_t i, const Descriptor* d) +{ + write((uint16_t) i, typecodes::SHORT, d); +} + +void Encoder::writeInt(int32_t i, const Descriptor* d) +{ + write((uint32_t) i, typecodes::INT, d); +} + +void Encoder::writeLong(int64_t i, const Descriptor* d) +{ + write((uint64_t) i, typecodes::LONG, d); +} + +void Encoder::writeFloat(float f, const Descriptor* d) +{ + write(f, typecodes::FLOAT, d); +} + +void Encoder::writeDouble(double f, const Descriptor* d) +{ + write(f, typecodes::DOUBLE, d); +} + +void Encoder::writeUuid(const qpid::types::Uuid& uuid, const Descriptor* d) +{ + write(uuid, typecodes::UUID, d); +} + +void Encoder::write(const CharSequence& v, std::pair<uint8_t, uint8_t> codes, const Descriptor* d) +{ + if (d) writeDescriptor(*d); + if (v.size < 256) { + writeCode(codes.first); + write((uint8_t) v.size); + } else { + writeCode(codes.second); + write((uint32_t) v.size); + } + writeBytes(v.data, v.size); +} + +void Encoder::write(const std::string& v, std::pair<uint8_t, uint8_t> codes, const Descriptor* d) +{ + if (d) writeDescriptor(*d); + if (v.size() < 256) { + writeCode(codes.first); + write((uint8_t) v.size()); + } else { + writeCode(codes.second); + write((uint32_t) v.size()); + } + writeBytes(v.data(), v.size()); +} + +void Encoder::writeSymbol(const CharSequence& v, const Descriptor* d) +{ + write(v, typecodes::SYMBOL, d); +} + +void Encoder::writeSymbol(const std::string& v, const Descriptor* d) +{ + write(v, typecodes::SYMBOL, d); +} + +void Encoder::writeString(const CharSequence& v, const Descriptor* d) +{ + write(v, typecodes::STRING, d); +} + +void Encoder::writeString(const std::string& v, const Descriptor* d) +{ + write(v, typecodes::STRING, d); +} + +void Encoder::writeBinary(const CharSequence& v, const Descriptor* d) +{ + write(v, typecodes::BINARY, d); +} + +void Encoder::writeBinary(const std::string& v, const Descriptor* d) +{ + write(v, typecodes::BINARY, d); +} + +void* Encoder::startList8(const Descriptor* d) +{ + return start<uint8_t>(typecodes::LIST8, d); +} + +void* Encoder::startList32(const Descriptor* d) +{ + return start<uint32_t>(typecodes::LIST32, d); +} + +void Encoder::endList8(uint8_t count, void* token) +{ + end<uint8_t>(count, token, data+position); +} + +void Encoder::endList32(uint32_t count, void* token) +{ + end<uint32_t>(count, token, data+position); +} + +void* Encoder::startMap8(const Descriptor* d) +{ + return start<uint8_t>(typecodes::MAP8, d); +} + +void* Encoder::startMap32(const Descriptor* d) +{ + return start<uint32_t>(typecodes::MAP32, d); +} + +void Encoder::endMap8(uint8_t count, void* token) +{ + end<uint8_t>(count, token, data+position); +} + +void Encoder::endMap32(uint32_t count, void* token) +{ + end<uint32_t>(count, token, data+position); +} + + +void* Encoder::startArray8(const Constructor& c, const Descriptor* d) +{ + return startArray<uint8_t>(typecodes::ARRAY8, d, c); +} + +void* Encoder::startArray32(const Constructor& c, const Descriptor* d) +{ + return startArray<uint8_t>(typecodes::ARRAY32, d, c); +} + +void Encoder::endArray8(size_t count, void* token) +{ + end<uint8_t>(count, token, data+position); +} + +void Encoder::endArray32(size_t count, void* token) +{ + end<uint32_t>(count, token, data+position); +} + + +void Encoder::writeDescriptor(const Descriptor& d) +{ + writeCode(typecodes::DESCRIPTOR); + switch (d.type) { + case Descriptor::NUMERIC: + writeULong(d.value.code, 0); + break; + case Descriptor::SYMBOLIC: + writeSymbol(d.value.symbol, 0); + break; + } +} +void Encoder::check(size_t s) +{ + if (position + s > size) { + QPID_LOG(notice, "Buffer overflow for write of size " << s << " to buffer of size " << size << " at position " << position); + assert(false); + throw qpid::Exception("Buffer overflow in encoder!"); + } +} +Encoder::Encoder(char* d, size_t s) : data(d), size(s), position(0) {} +size_t Encoder::getPosition() { return position; } +void Encoder::resetPosition(size_t p) { assert(p <= size); position = p; } + +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/Encoder.h b/cpp/src/qpid/amqp/Encoder.h new file mode 100644 index 0000000000..e2938a002a --- /dev/null +++ b/cpp/src/qpid/amqp/Encoder.h @@ -0,0 +1,149 @@ +#ifndef QPID_AMQP_ENCODER_H +#define QPID_AMQP_ENCODER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/sys/IntegerTypes.h" +#include "qpid/amqp/Constructor.h" +#include <stddef.h> +#include <string> + +namespace qpid { +namespace types { +class Uuid; +} +namespace amqp { +struct CharSequence; +struct Descriptor; + +/** + * Class to help create AMQP encoded data. + */ +class Encoder +{ + public: + void writeCode(uint8_t); + + void write(bool); + void write(uint8_t); + void write(uint16_t); + void write(uint32_t); + void write(uint64_t); + void write(int8_t); + void write(int16_t); + void write(int32_t); + void write(int64_t); + void write(float); + void write(double); + void write(const qpid::types::Uuid&); + + void writeNull(const Descriptor* d=0); + void writeBoolean(bool, const Descriptor* d=0); + void writeUByte(uint8_t, const Descriptor* d=0); + void writeUShort(uint16_t, const Descriptor* d=0); + void writeUInt(uint32_t, const Descriptor* d=0); + void writeULong(uint64_t, const Descriptor* d=0); + void writeByte(int8_t, const Descriptor* d=0); + void writeShort(int16_t, const Descriptor* d=0); + void writeInt(int32_t, const Descriptor* d=0); + void writeLong(int64_t, const Descriptor* d=0); + void writeFloat(float, const Descriptor* d=0); + void writeDouble(double, const Descriptor* d=0); + void writeUuid(const qpid::types::Uuid&, const Descriptor* d=0); + + void writeSymbol(const CharSequence&, const Descriptor* d=0); + void writeSymbol(const std::string&, const Descriptor* d=0); + void writeString(const CharSequence&, const Descriptor* d=0); + void writeString(const std::string&, const Descriptor* d=0); + void writeBinary(const CharSequence&, const Descriptor* d=0); + void writeBinary(const std::string&, const Descriptor* d=0); + + void* startList8(const Descriptor* d=0); + void* startList32(const Descriptor* d=0); + void endList8(uint8_t count, void*); + void endList32(uint32_t count, void*); + + void* startMap8(const Descriptor* d=0); + void* startMap32(const Descriptor* d=0); + void endMap8(uint8_t count, void*); + void endMap32(uint32_t count, void*); + + void* startArray8(const Constructor&, const Descriptor* d=0); + void* startArray32(const Constructor&, const Descriptor* d=0); + void endArray8(size_t count, void*); + void endArray32(size_t count, void*); + + void writeDescriptor(const Descriptor&); + Encoder(char* data, size_t size); + size_t getPosition(); + void resetPosition(size_t p); + char* skip(size_t); + void writeBytes(const char* bytes, size_t count); + virtual ~Encoder() {} + private: + char* data; + size_t size; + size_t position; + + void write(const CharSequence& v, std::pair<uint8_t, uint8_t> codes, const Descriptor* d); + void write(const std::string& v, std::pair<uint8_t, uint8_t> codes, const Descriptor* d); + void check(size_t); + + template<typename T> void write(T value, uint8_t code, const Descriptor* d) + { + if (d) writeDescriptor(*d); + writeCode(code); + write(value); + } + + template<typename T> void write(T value, std::pair<uint8_t, uint8_t> codes, const Descriptor* d) + { + if (value < 256) { + write((uint8_t) value, codes.first, d); + } else { + write(value, codes.second, d); + } + } + + template<typename T> void* start(uint8_t code, const Descriptor* d) + { + if (d) writeDescriptor(*d); + writeCode(code); + //skip size and count, will backfill on end + return skip(sizeof(T)/*size*/ + sizeof(T)/*count*/); + } + + template<typename T> void* startArray(uint8_t code, const Descriptor* d, const Constructor& c) + { + void* token = start<T>(code, d); + if (c.isDescribed) { + writeDescriptor(c.descriptor); + } + check(1); + writeCode(c.code); + return token; + } + +}; + +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_ENCODER_H*/ diff --git a/cpp/src/qpid/amqp/ListReader.h b/cpp/src/qpid/amqp/ListReader.h new file mode 100644 index 0000000000..dce874bf2f --- /dev/null +++ b/cpp/src/qpid/amqp/ListReader.h @@ -0,0 +1,103 @@ +#ifndef QPID_AMQP_LISTREADER_H +#define QPID_AMQP_LISTREADER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Reader.h" + +namespace qpid { +namespace amqp { + +/** + * Utility to assist in reading AMQP encoded lists + */ +class ListReader : public Reader +{ + public: + ListReader() : index(0), level(0) {} + virtual ~ListReader() {} + virtual void onNull(const Descriptor* descriptor) { getReader().onNull(descriptor); } + virtual void onBoolean(bool v, const Descriptor* descriptor) { getReader().onBoolean(v, descriptor); } + virtual void onUByte(uint8_t v, const Descriptor* descriptor) { getReader().onUByte(v, descriptor); } + virtual void onUShort(uint16_t v, const Descriptor* descriptor) { getReader().onUShort(v, descriptor); } + virtual void onUInt(uint32_t v, const Descriptor* descriptor) { getReader().onUInt(v, descriptor); } + virtual void onULong(uint64_t v, const Descriptor* descriptor) { getReader().onULong(v, descriptor); } + virtual void onByte(int8_t v, const Descriptor* descriptor) { getReader().onByte(v, descriptor); } + virtual void onShort(int16_t v, const Descriptor* descriptor) { getReader().onShort(v, descriptor); } + virtual void onInt(int32_t v, const Descriptor* descriptor) { getReader().onInt(v, descriptor); } + virtual void onLong(int64_t v, const Descriptor* descriptor) { getReader().onLong(v, descriptor); } + virtual void onFloat(float v, const Descriptor* descriptor) { getReader().onFloat(v, descriptor); } + virtual void onDouble(double v, const Descriptor* descriptor) { getReader().onDouble(v, descriptor); } + virtual void onUuid(const CharSequence& v, const Descriptor* descriptor) { getReader().onUuid(v, descriptor); } + virtual void onTimestamp(int64_t v, const Descriptor* descriptor) { getReader().onTimestamp(v, descriptor); } + + virtual void onBinary(const CharSequence& v, const Descriptor* descriptor) { getReader().onBinary(v, descriptor); } + virtual void onString(const CharSequence& v, const Descriptor* descriptor) { getReader().onString(v, descriptor); } + virtual void onSymbol(const CharSequence& v, const Descriptor* descriptor) { getReader().onSymbol(v, descriptor); } + + virtual bool onStartList(uint32_t count, const CharSequence& v, const Descriptor* descriptor) + { + ++level; + getReader().onStartList(count, v, descriptor); + return false; + } + virtual void onEndList(uint32_t count, const Descriptor* descriptor) + { + --level; + getReader().onEndList(count, descriptor); + } + virtual bool onStartMap(uint32_t count, const CharSequence& v, const Descriptor* descriptor) + { + ++level; + getReader().onStartMap(count, v, descriptor); + return false; + } + virtual void onEndMap(uint32_t count, const Descriptor* descriptor) + { + --level; + getReader().onEndList(count, descriptor); + } + virtual bool onStartArray(uint32_t count, const CharSequence& v, const Constructor& c, const Descriptor* descriptor) + { + ++level; + getReader().onStartArray(count, v, c, descriptor); + return false; + } + virtual void onEndArray(uint32_t count, const Descriptor* descriptor) + { + --level; + getReader().onEndList(count, descriptor); + } + private: + size_t index; + size_t level; + Reader& getReader() + { + Reader& r = getReader(index); + if (level == 0) ++index; + return r; + } + protected: + virtual Reader& getReader(size_t i) = 0; +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_LISTREADER_H*/ diff --git a/cpp/src/qpid/amqp/LoggingReader.h b/cpp/src/qpid/amqp/LoggingReader.h new file mode 100644 index 0000000000..ed5cab1cbd --- /dev/null +++ b/cpp/src/qpid/amqp/LoggingReader.h @@ -0,0 +1,64 @@ +#ifndef QPID_AMQP_LOGGINGREADER_H +#define QPID_AMQP_LOGGINGREADER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Reader.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace amqp { + +class LoggingReader : public Reader +{ + public: + virtual ~LoggingReader() {} + virtual void onNull(const Descriptor*) { if (!ignoreNull()) QPID_LOG(warning, prefix() << "null" << suffix()); } + virtual void onBoolean(bool, const Descriptor*) { QPID_LOG(warning, prefix() << "boolean" << suffix()); } + virtual void onUByte(uint8_t, const Descriptor*) { QPID_LOG(warning, prefix() << "ubyte" << suffix()); } + virtual void onUShort(uint16_t, const Descriptor*) { QPID_LOG(warning, prefix() << "ushort" << suffix()); } + virtual void onUInt(uint32_t, const Descriptor*) { QPID_LOG(warning, prefix() << "uint" << suffix()); } + virtual void onULong(uint64_t, const Descriptor*) { QPID_LOG(warning, prefix() << "ulong" << suffix()); } + virtual void onByte(int8_t, const Descriptor*) { QPID_LOG(warning, prefix() << "byte" << suffix()); } + virtual void onShort(int16_t, const Descriptor*) { QPID_LOG(warning, prefix() << "short" << suffix()); } + virtual void onInt(int32_t, const Descriptor*) { QPID_LOG(warning, prefix() << "int" << suffix()); } + virtual void onLong(int64_t, const Descriptor*) { QPID_LOG(warning, prefix() << "long" << suffix()); } + virtual void onFloat(float, const Descriptor*) { QPID_LOG(warning, prefix() << "float" << suffix()); } + virtual void onDouble(double, const Descriptor*) { QPID_LOG(warning, prefix() << "double" << suffix()); } + virtual void onUuid(const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "uuid" << suffix()); } + virtual void onTimestamp(int64_t, const Descriptor*) { QPID_LOG(warning, prefix() << "timestamp" << suffix()); } + + virtual void onBinary(const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "binary" << suffix()); } + virtual void onString(const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "string" << suffix()); } + virtual void onSymbol(const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "symbol" << suffix()); } + + virtual bool onStartList(uint32_t, const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "list" << suffix()); return recursive; } + virtual bool onStartMap(uint32_t, const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "map" << suffix()); return recursive; } + virtual bool onStartArray(uint32_t, const CharSequence&, const Constructor&, const Descriptor*) { QPID_LOG(warning, prefix() << "array" << suffix()); return recursive; } + protected: + virtual bool recursive() { return true; } + virtual bool ignoreNull() { return true; } + virtual std::string prefix() = 0; + virtual std::string suffix() = 0; +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_LOGGINGREADER_H*/ diff --git a/cpp/src/qpid/amqp/MapReader.cpp b/cpp/src/qpid/amqp/MapReader.cpp new file mode 100644 index 0000000000..2bace74d34 --- /dev/null +++ b/cpp/src/qpid/amqp/MapReader.cpp @@ -0,0 +1,289 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/MapReader.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace amqp { + +void MapReader::onNull(const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onNullValue(key, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} +void MapReader::onBoolean(bool v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onBooleanValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onUByte(uint8_t v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onUByteValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onUShort(uint16_t v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onUShortValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onUInt(uint32_t v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onUIntValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onULong(uint64_t v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onULongValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onByte(int8_t v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onByteValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onShort(int16_t v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onShortValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onInt(int32_t v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onIntValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onLong(int64_t v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onLongValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onFloat(float v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onFloatValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onDouble(double v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onDoubleValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onUuid(const CharSequence& v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onUuidValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onTimestamp(int64_t v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onTimestampValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onBinary(const CharSequence& v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onBinaryValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onString(const CharSequence& v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onStringValue(key, v, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key, got string " << v.str())); + } +} + +void MapReader::onSymbol(const CharSequence& v, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onSymbolValue(key, v, d); + key.data = 0; key.size = 0; + } else { + key = v; + } +} + +bool MapReader::onStartList(uint32_t count, const CharSequence&, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + bool step = onStartListValue(key, count, d); + key.data = 0; key.size = 0; + return step; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } + return true; +} + +bool MapReader::onStartMap(uint32_t count, const CharSequence&, const Descriptor* d) +{ + if (level++) { + if (key) { + bool step = onStartMapValue(key, count, d); + key.data = 0; key.size = 0; + return step; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } + } + return true; +} + +bool MapReader::onStartArray(uint32_t count, const CharSequence&, const Constructor& c, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + bool step = onStartArrayValue(key, count, c, d); + key.data = 0; key.size = 0; + return step; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } + return true; +} + +void MapReader::onEndList(uint32_t count, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onEndListValue(key, count, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +void MapReader::onEndMap(uint32_t count, const Descriptor* d) +{ + if (--level) { + onEndMapValue(key, count, d); + key.data = 0; key.size = 0; + } +} + +void MapReader::onEndArray(uint32_t count, const Descriptor* d) +{ + if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum")); + if (key) { + onEndArrayValue(key, count, d); + key.data = 0; key.size = 0; + } else { + throw qpid::Exception(QPID_MSG("Expecting symbol as key")); + } +} + +MapReader::MapReader() : level(0) +{ + key.data = 0; key.size = 0; +} + +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/MapReader.h b/cpp/src/qpid/amqp/MapReader.h new file mode 100644 index 0000000000..fe8f65b30c --- /dev/null +++ b/cpp/src/qpid/amqp/MapReader.h @@ -0,0 +1,104 @@ +#ifndef QPID_AMQP_MAPREADER_H +#define QPID_AMQP_MAPREADER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Reader.h" +#include "CharSequence.h" +#include <string> + +namespace qpid { +namespace amqp { + +/** + * Reading AMQP 1.0 encoded data which is constrained to be a symbol + * keyeed map. The keys are assumed never to be described, the values + * may be. + */ +class MapReader : public Reader +{ + public: + virtual void onNullValue(const CharSequence& /*key*/, const Descriptor*) {} + virtual void onBooleanValue(const CharSequence& /*key*/, bool, const Descriptor*) {} + virtual void onUByteValue(const CharSequence& /*key*/, uint8_t, const Descriptor*) {} + virtual void onUShortValue(const CharSequence& /*key*/, uint16_t, const Descriptor*) {} + virtual void onUIntValue(const CharSequence& /*key*/, uint32_t, const Descriptor*) {} + virtual void onULongValue(const CharSequence& /*key*/, uint64_t, const Descriptor*) {} + virtual void onByteValue(const CharSequence& /*key*/, int8_t, const Descriptor*) {} + virtual void onShortValue(const CharSequence& /*key*/, int16_t, const Descriptor*) {} + virtual void onIntValue(const CharSequence& /*key*/, int32_t, const Descriptor*) {} + virtual void onLongValue(const CharSequence& /*key*/, int64_t, const Descriptor*) {} + virtual void onFloatValue(const CharSequence& /*key*/, float, const Descriptor*) {} + virtual void onDoubleValue(const CharSequence& /*key*/, double, const Descriptor*) {} + virtual void onUuidValue(const CharSequence& /*key*/, const CharSequence&, const Descriptor*) {} + virtual void onTimestampValue(const CharSequence& /*key*/, int64_t, const Descriptor*) {} + + virtual void onBinaryValue(const CharSequence& /*key*/, const CharSequence&, const Descriptor*) {} + virtual void onStringValue(const CharSequence& /*key*/, const CharSequence&, const Descriptor*) {} + virtual void onSymbolValue(const CharSequence& /*key*/, const CharSequence&, const Descriptor*) {} + + /** + * @return true to step into elements of the compound value, false + * to skip over it + */ + virtual bool onStartListValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) { return true; } + virtual bool onStartMapValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) { return true; } + virtual bool onStartArrayValue(const CharSequence& /*key*/, uint32_t /*count*/, const Constructor&, const Descriptor*) { return true; } + virtual void onEndListValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) {} + virtual void onEndMapValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) {} + virtual void onEndArrayValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) {} + + + //this class implements the Reader interface, thus acting as a transformer into a more map oriented scheme + void onNull(const Descriptor*); + void onBoolean(bool, const Descriptor*); + void onUByte(uint8_t, const Descriptor*); + void onUShort(uint16_t, const Descriptor*); + void onUInt(uint32_t, const Descriptor*); + void onULong(uint64_t, const Descriptor*); + void onByte(int8_t, const Descriptor*); + void onShort(int16_t, const Descriptor*); + void onInt(int32_t, const Descriptor*); + void onLong(int64_t, const Descriptor*); + void onFloat(float, const Descriptor*); + void onDouble(double, const Descriptor*); + void onUuid(const CharSequence&, const Descriptor*); + void onTimestamp(int64_t, const Descriptor*); + + void onBinary(const CharSequence&, const Descriptor*); + void onString(const CharSequence&, const Descriptor*); + void onSymbol(const CharSequence&, const Descriptor*); + + bool onStartList(uint32_t /*count*/, const CharSequence&, const Descriptor*); + bool onStartMap(uint32_t /*count*/, const CharSequence&, const Descriptor*); + bool onStartArray(uint32_t /*count*/, const CharSequence&, const Constructor&, const Descriptor*); + void onEndList(uint32_t /*count*/, const Descriptor*); + void onEndMap(uint32_t /*count*/, const Descriptor*); + void onEndArray(uint32_t /*count*/, const Descriptor*); + + MapReader(); + private: + CharSequence key; + size_t level; +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_MAPREADER_H*/ diff --git a/cpp/src/qpid/amqp/MessageEncoder.cpp b/cpp/src/qpid/amqp/MessageEncoder.cpp new file mode 100644 index 0000000000..852ad29635 --- /dev/null +++ b/cpp/src/qpid/amqp/MessageEncoder.cpp @@ -0,0 +1,313 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/MessageEncoder.h" +#include "qpid/amqp/descriptors.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace amqp { + +namespace { +size_t optimisable(const MessageEncoder::Header& msg) +{ + if (msg.getDeliveryCount()) return 5; + else if (msg.isFirstAcquirer()) return 4; + else if (msg.hasTtl()) return 3; + else if (msg.getPriority() != 4) return 2; + else if (msg.isDurable()) return 1; + else return 0; +} + +size_t optimisable(const MessageEncoder::Properties& msg) +{ + if (msg.hasReplyToGroupId()) return 13; + else if (msg.hasGroupSequence()) return 12; + else if (msg.hasGroupId()) return 11; + else if (msg.hasCreationTime()) return 10; + else if (msg.hasAbsoluteExpiryTime()) return 9; + else if (msg.hasContentEncoding()) return 8; + else if (msg.hasContentType()) return 7; + else if (msg.hasCorrelationId()) return 6; + else if (msg.hasReplyTo()) return 5; + else if (msg.hasSubject()) return 4; + else if (msg.hasTo()) return 3; + else if (msg.hasUserId()) return 2; + else if (msg.hasMessageId()) return 1; + else return 0; +} +size_t encodedSize(const std::string& s) +{ + size_t total = s.size(); + if (total > 255) total += 4; + else total += 1; + return total; +} +const std::string BINARY("binary"); +} + +void MessageEncoder::writeHeader(const Header& msg) +{ + size_t fields(optimise ? optimisable(msg) : 5); + if (fields) { + void* token = startList8(&qpid::amqp::message::HEADER); + writeBoolean(msg.isDurable()); + if (fields > 1) writeUByte(msg.getPriority()); + + if (msg.getTtl()) writeUInt(msg.getTtl()); + else if (fields > 2) writeNull(); + + if (msg.isFirstAcquirer()) writeBoolean(true); + else if (fields > 3) writeNull(); + + if (msg.getDeliveryCount()) writeUInt(msg.getDeliveryCount()); + else if (fields > 4) writeNull(); + endList8(fields, token); + } +} + + +void MessageEncoder::writeProperties(const Properties& msg) +{ + size_t fields(optimise ? optimisable(msg) : 13); + if (fields) { + void* token = startList32(&qpid::amqp::message::PROPERTIES); + if (msg.hasMessageId()) writeString(msg.getMessageId()); + else writeNull(); + + if (msg.hasUserId()) writeBinary(msg.getUserId()); + else if (fields > 1) writeNull(); + + if (msg.hasTo()) writeString(msg.getTo()); + else if (fields > 2) writeNull(); + + if (msg.hasSubject()) writeString(msg.getSubject()); + else if (fields > 3) writeNull(); + + if (msg.hasReplyTo()) writeString(msg.getReplyTo()); + else if (fields > 4) writeNull(); + + if (msg.hasCorrelationId()) writeString(msg.getCorrelationId()); + else if (fields > 5) writeNull(); + + if (msg.hasContentType()) writeSymbol(msg.getContentType()); + else if (fields > 6) writeNull(); + + if (msg.hasContentEncoding()) writeSymbol(msg.getContentEncoding()); + else if (fields > 7) writeNull(); + + if (msg.hasAbsoluteExpiryTime()) writeLong(msg.getAbsoluteExpiryTime()); + else if (fields > 8) writeNull(); + + if (msg.hasCreationTime()) writeLong(msg.getCreationTime()); + else if (fields > 9) writeNull(); + + if (msg.hasGroupId()) writeString(msg.getGroupId()); + else if (fields > 10) writeNull(); + + if (msg.hasGroupSequence()) writeUInt(msg.getGroupSequence()); + else if (fields > 11) writeNull(); + + if (msg.hasReplyToGroupId()) writeString(msg.getReplyToGroupId()); + else if (fields > 12) writeNull(); + + endList32(fields, token); + } +} + +void MessageEncoder::writeApplicationProperties(const qpid::types::Variant::Map& properties) +{ + writeApplicationProperties(properties, !optimise || properties.size()*2 > 255 || getEncodedSizeForElements(properties) > 255); +} + +void MessageEncoder::writeApplicationProperties(const qpid::types::Variant::Map& properties, bool large) +{ + writeMap(properties, &qpid::amqp::message::APPLICATION_PROPERTIES, large); +} + +void MessageEncoder::writeMap(const qpid::types::Variant::Map& properties, const Descriptor* d, bool large) +{ + void* token = large ? startMap32(d) : startMap8(d); + for (qpid::types::Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + writeString(i->first); + switch (i->second.getType()) { + case qpid::types::VAR_MAP: + case qpid::types::VAR_LIST: + //not allowed (TODO: revise, only strictly true for application-properties) whereas this is now a more general method) + QPID_LOG(warning, "Ignoring nested map/list; not allowed in application-properties for AMQP 1.0"); + case qpid::types::VAR_VOID: + writeNull(); + break; + case qpid::types::VAR_BOOL: + writeBoolean(i->second); + break; + case qpid::types::VAR_UINT8: + writeUByte(i->second); + break; + case qpid::types::VAR_UINT16: + writeUShort(i->second); + break; + case qpid::types::VAR_UINT32: + writeUInt(i->second); + break; + case qpid::types::VAR_UINT64: + writeULong(i->second); + break; + case qpid::types::VAR_INT8: + writeByte(i->second); + break; + case qpid::types::VAR_INT16: + writeShort(i->second); + break; + case qpid::types::VAR_INT32: + writeInt(i->second); + break; + case qpid::types::VAR_INT64: + writeULong(i->second); + break; + case qpid::types::VAR_FLOAT: + writeFloat(i->second); + break; + case qpid::types::VAR_DOUBLE: + writeDouble(i->second); + break; + case qpid::types::VAR_STRING: + if (i->second.getEncoding() == BINARY) { + writeBinary(i->second); + } else { + writeString(i->second); + } + break; + case qpid::types::VAR_UUID: + writeUuid(i->second); + break; + } + } + if (large) endMap32(properties.size()*2, token); + else endMap8(properties.size()*2, token); +} + +size_t MessageEncoder::getEncodedSize(const Header& h, const Properties& p, const qpid::types::Variant::Map& ap, const std::string& d) +{ + //NOTE: this does not take optional optimisation into account, + //i.e. it is a 'worst case' estimate for required buffer space + size_t total(0); + + //header: + total += 3/*descriptor*/ + 1/*code*/ + 1/*size*/ + 1/*count*/ + 5/*codes for each field*/; + if (h.getPriority() != 4) total += 1; + if (h.getDeliveryCount()) total += 4; + if (h.hasTtl()) total += 4; + return total + getEncodedSize(p, ap, d); +} + +size_t MessageEncoder::getEncodedSize(const Properties& p, const qpid::types::Variant::Map& ap, const std::string& d) +{ + //NOTE: this does not take optional optimisation into account, + //i.e. it is a 'worst case' estimate for required buffer space + size_t total(0); + + //properties: + total += 3/*descriptor*/ + 1/*code*/ + 4/*size*/ + 4/*count*/ + 13/*codes for each field*/; + if (p.hasMessageId()) total += encodedSize(p.getMessageId()); + if (p.hasUserId()) total += encodedSize(p.getUserId()); + if (p.hasTo()) total += encodedSize(p.getTo()); + if (p.hasSubject()) total += encodedSize(p.getSubject()); + if (p.hasReplyTo()) total += encodedSize(p.getReplyTo()); + if (p.hasCorrelationId()) total += encodedSize(p.getCorrelationId()); + if (p.hasContentType()) total += encodedSize(p.getContentType()); + if (p.hasContentEncoding()) total += encodedSize(p.getContentEncoding()); + if (p.hasAbsoluteExpiryTime()) total += 8; + if (p.hasCreationTime()) total += 8; + if (p.hasGroupId()) total += encodedSize(p.getGroupId()); + if (p.hasGroupSequence()) total += 4; + if (p.hasReplyToGroupId()) total += encodedSize(p.getReplyToGroupId()); + + + //application-properties: + total += 3/*descriptor*/ + getEncodedSize(ap, true); + //body: + if (d.size()) total += 3/*descriptor*/ + 1/*code*/ + encodedSize(d); + + return total; +} + +size_t MessageEncoder::getEncodedSizeForElements(const qpid::types::Variant::Map& map) +{ + size_t total = 0; + for (qpid::types::Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) { + total += 1/*code*/ + encodedSize(i->first); + + switch (i->second.getType()) { + case qpid::types::VAR_MAP: + case qpid::types::VAR_LIST: + case qpid::types::VAR_VOID: + case qpid::types::VAR_BOOL: + total += 1; + break; + + case qpid::types::VAR_UINT8: + case qpid::types::VAR_INT8: + total += 2; + break; + + case qpid::types::VAR_UINT16: + case qpid::types::VAR_INT16: + total += 3; + break; + + case qpid::types::VAR_UINT32: + case qpid::types::VAR_INT32: + case qpid::types::VAR_FLOAT: + total += 5; + break; + + case qpid::types::VAR_UINT64: + case qpid::types::VAR_INT64: + case qpid::types::VAR_DOUBLE: + total += 9; + break; + + case qpid::types::VAR_UUID: + total += 17; + break; + + case qpid::types::VAR_STRING: + total += 1/*code*/ + encodedSize(i->second); + break; + } + } + return total; +} + + +size_t MessageEncoder::getEncodedSize(const qpid::types::Variant::Map& map, bool alwaysUseLargeMap) +{ + size_t total = getEncodedSizeForElements(map); + + //its not just the count that determines whether we can use a small map, but the aggregate size: + if (alwaysUseLargeMap || map.size()*2 > 255 || total > 255) total += 4/*size*/ + 4/*count*/; + else total += 1/*size*/ + 1/*count*/; + + total += 1 /*code for map itself*/; + + return total; +} +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/MessageEncoder.h b/cpp/src/qpid/amqp/MessageEncoder.h new file mode 100644 index 0000000000..3db0763e8a --- /dev/null +++ b/cpp/src/qpid/amqp/MessageEncoder.h @@ -0,0 +1,100 @@ +#ifndef QPID_AMQP_MESSAGEENCODER_H +#define QPID_AMQP_MESSAGEENCODER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/Encoder.h" +#include "qpid/types/Variant.h" + +namespace qpid { +namespace amqp { + +/** + * + */ +class MessageEncoder : public Encoder +{ + public: + class Header + { + public: + virtual ~Header() {} + virtual bool isDurable() const = 0; + virtual uint8_t getPriority() const = 0; + virtual bool hasTtl() const = 0; + virtual uint32_t getTtl() const = 0; + virtual bool isFirstAcquirer() const = 0; + virtual uint32_t getDeliveryCount() const = 0; + }; + + class Properties + { + public: + virtual ~Properties() {} + virtual bool hasMessageId() const = 0; + virtual std::string getMessageId() const = 0; + virtual bool hasUserId() const = 0; + virtual std::string getUserId() const = 0; + virtual bool hasTo() const = 0; + virtual std::string getTo() const = 0; + virtual bool hasSubject() const = 0; + virtual std::string getSubject() const = 0; + virtual bool hasReplyTo() const = 0; + virtual std::string getReplyTo() const = 0; + virtual bool hasCorrelationId() const = 0; + virtual std::string getCorrelationId() const = 0; + virtual bool hasContentType() const = 0; + virtual std::string getContentType() const = 0; + virtual bool hasContentEncoding() const = 0; + virtual std::string getContentEncoding() const = 0; + virtual bool hasAbsoluteExpiryTime() const = 0; + virtual int64_t getAbsoluteExpiryTime() const = 0; + virtual bool hasCreationTime() const = 0; + virtual int64_t getCreationTime() const = 0; + virtual bool hasGroupId() const = 0; + virtual std::string getGroupId() const = 0; + virtual bool hasGroupSequence() const = 0; + virtual uint32_t getGroupSequence() const = 0; + virtual bool hasReplyToGroupId() const = 0; + virtual std::string getReplyToGroupId() const = 0; + }; + + MessageEncoder(char* d, size_t s, bool o=false) : Encoder(d, s), optimise(o) {} + void writeHeader(const Header&); + void writeProperties(const Properties&); + void writeApplicationProperties(const qpid::types::Variant::Map& properties); + void writeApplicationProperties(const qpid::types::Variant::Map& properties, bool useLargeMap); + + void writeMap(const qpid::types::Variant::Map& map, const Descriptor*, bool useLargeMap); + + static size_t getEncodedSize(const Header&, const Properties&, const qpid::types::Variant::Map&, const std::string&); + static size_t getEncodedSize(const Properties&, const qpid::types::Variant::Map&, const std::string&); + static size_t getEncodedSize(const qpid::types::Variant::Map&, bool useLargeMap); + static size_t getEncodedSize(const qpid::types::Variant::Map&); + + private: + bool optimise; + + static size_t getEncodedSizeForElements(const qpid::types::Variant::Map&); +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_MESSAGEENCODER_H*/ diff --git a/cpp/src/qpid/amqp/MessageId.cpp b/cpp/src/qpid/amqp/MessageId.cpp new file mode 100644 index 0000000000..e6f6f4a231 --- /dev/null +++ b/cpp/src/qpid/amqp/MessageId.cpp @@ -0,0 +1,69 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/MessageId.h" +#include <assert.h> +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace amqp { + +MessageId::MessageId() : type(BYTES) +{ + value.bytes.data = 0; + value.bytes.size = 0; +} +void MessageId::assign(std::string& s) const +{ + switch (type) { + case BYTES: + if (value.bytes) s.assign(value.bytes.data, value.bytes.size); + break; + case UUID: + s = qpid::types::Uuid(value.bytes).str(); + break; + case ULONG: + s = boost::lexical_cast<std::string>(value.ulong); + break; + } +} + +void MessageId::set(qpid::amqp::CharSequence bytes, qpid::types::VariantType t) +{ + switch (t) { + case qpid::types::VAR_STRING: + type = BYTES; + break; + case qpid::types::VAR_UUID: + type = UUID; + assert(bytes.size == 16); + break; + default: + assert(false); + } + value.bytes = bytes; +} +void MessageId::set(uint64_t ulong) +{ + type = ULONG; + value.ulong = ulong; +} + +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/MessageId.h b/cpp/src/qpid/amqp/MessageId.h new file mode 100644 index 0000000000..ee440f3011 --- /dev/null +++ b/cpp/src/qpid/amqp/MessageId.h @@ -0,0 +1,54 @@ +#ifndef QPID_AMQP_MESSAGEID_H +#define QPID_AMQP_MESSAGEID_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/CharSequence.h" +#include "qpid/types/Variant.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace amqp { + +struct MessageId +{ + union + { + qpid::amqp::CharSequence bytes; + uint64_t ulong; + } value; + enum + { + BYTES, + UUID, + ULONG + } type; + + QPID_COMMON_EXTERN MessageId(); + QPID_COMMON_EXTERN void assign(std::string&) const; + QPID_COMMON_EXTERN void set(qpid::amqp::CharSequence bytes, qpid::types::VariantType t); + QPID_COMMON_EXTERN void set(uint64_t ulong); + +}; + +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_MESSAGEID_H*/ diff --git a/cpp/src/qpid/amqp/MessageReader.cpp b/cpp/src/qpid/amqp/MessageReader.cpp new file mode 100644 index 0000000000..1550fa1977 --- /dev/null +++ b/cpp/src/qpid/amqp/MessageReader.cpp @@ -0,0 +1,759 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/MessageReader.h" +#include "qpid/amqp/Descriptor.h" +#include "qpid/amqp/descriptors.h" +#include "qpid/types/Uuid.h" +#include "qpid/types/Variant.h" +#include "qpid/log/Statement.h" + +using namespace qpid::amqp::message; + +namespace qpid { +namespace amqp { +namespace { + +//header fields: +const size_t DURABLE(0); +const size_t PRIORITY(1); +const size_t TTL(2); +const size_t FIRST_ACQUIRER(3); +const size_t DELIVERY_COUNT(4); + +//properties fields: +const size_t MESSAGE_ID(0); +const size_t USER_ID(1); +const size_t TO(2); +const size_t SUBJECT(3); +const size_t REPLY_TO(4); +const size_t CORRELATION_ID(5); +const size_t CONTENT_TYPE(6); +const size_t CONTENT_ENCODING(7); +const size_t ABSOLUTE_EXPIRY_TIME(8); +const size_t CREATION_TIME(9); +const size_t GROUP_ID(10); +const size_t GROUP_SEQUENCE(11); +const size_t REPLY_TO_GROUP_ID(12); + +} + +/* +Reader& MessageReader::HeaderReader::getReader(size_t index) +{ + switch (index) { + case DURABLE: return durableReader; + case PRIORITY: return priorityReader; + case TTL: return ttlReader; + case FIRST_ACQUIRER: return firstAcquirerReader; + case DELIVERY_COUNT: return deliveryCountReader; + default: return noSuchFieldReader; + } +} + +Reader& MessageReader::PropertiesReader::getReader(size_t index) +{ + switch (index) { + case MESSAGE_ID: return messageIdReader; + case USER_ID: return userIdReader; + case TO: return toReader; + case SUBJECT: return subjectReader; + case REPLY_TO: return replyToReader; + case CORRELATION_ID: return correlationIdReader; + case CONTENT_TYPE: return contentTypeReader; + case CONTENT_ENCODING: return contentEncodingReader; + case ABSOLUTE_EXPIRY_TIME: return absoluteExpiryTimeReader; + case CREATION_TIME: return creationTimeReader; + case GROUP_ID: return groupIdReader; + case GROUP_SEQUENCE: return groupSequenceReader; + case REPLY_TO_GROUP_ID: return replyToGroupIdReader; + default: return noSuchFieldReader; + } +} +*/ + +MessageReader::HeaderReader::HeaderReader(MessageReader& p) : parent(p), index(0) {} +void MessageReader::HeaderReader::onBoolean(bool v, const Descriptor*) // durable, first-acquirer +{ + if (index == DURABLE) { + parent.onDurable(v); + } else if (index == FIRST_ACQUIRER) { + parent.onFirstAcquirer(v); + } else { + QPID_LOG(warning, "Unexpected message format, got boolean at index " << index << " of headers"); + } + ++index; +} +void MessageReader::HeaderReader::onUByte(uint8_t v, const Descriptor*) // priority +{ + if (index == PRIORITY) { + parent.onPriority(v); + } else { + QPID_LOG(warning, "Unexpected message format, got ubyte at index " << index << " of headers"); + } + ++index; +} +void MessageReader::HeaderReader::onUInt(uint32_t v, const Descriptor*) // ttl, delivery-count +{ + if (index == TTL) { + parent.onTtl(v); + } else if (index == DELIVERY_COUNT) { + parent.onDeliveryCount(v); + } else { + QPID_LOG(warning, "Unexpected message format, got uint at index " << index << " of headers"); + } + ++index; +} +void MessageReader::HeaderReader::onNull(const Descriptor*) +{ + ++index; +} + +MessageReader::PropertiesReader::PropertiesReader(MessageReader& p) : parent(p), index(0) {} +void MessageReader::PropertiesReader::onUuid(const CharSequence& v, const Descriptor*) // message-id, correlation-id +{ + if (index == MESSAGE_ID) { + parent.onMessageId(v, qpid::types::VAR_UUID); + } else if (index == CORRELATION_ID) { + parent.onCorrelationId(v); + } else { + QPID_LOG(warning, "Unexpected message format, got uuid at index " << index << " of properties"); + } + ++index; +} +void MessageReader::PropertiesReader::onULong(uint64_t v, const Descriptor*) // message-id, correlation-id +{ + if (index == MESSAGE_ID) { + parent.onMessageId(v); + } else if (index == CORRELATION_ID) { + parent.onCorrelationId(v); + } else { + QPID_LOG(warning, "Unexpected message format, got long at index " << index << " of properties"); + } + ++index; +} +void MessageReader::PropertiesReader::onBinary(const CharSequence& v, const Descriptor*) // message-id, correlation-id, user-id +{ + if (index == MESSAGE_ID) { + parent.onMessageId(v, qpid::types::VAR_STRING); + } else if (index == CORRELATION_ID) { + parent.onCorrelationId(v); + } else if (index == USER_ID) { + parent.onUserId(v); + } else { + QPID_LOG(warning, "Unexpected message format, got binary at index " << index << " of properties"); + } + ++index; +} +void MessageReader::PropertiesReader::onString(const CharSequence& v, const Descriptor*) // message-id, correlation-id, group-id, reply-to-group-id, subject, to, reply-to +{ + if (index == MESSAGE_ID) { + parent.onMessageId(v); + } else if (index == CORRELATION_ID) { + parent.onCorrelationId(v); + } else if (index == GROUP_ID) { + parent.onGroupId(v); + } else if (index == REPLY_TO_GROUP_ID) { + parent.onReplyToGroupId(v); + } else if (index == SUBJECT) { + parent.onSubject(v); + } else if (index == TO) { + parent.onTo(v); + } else if (index == REPLY_TO) { + parent.onReplyTo(v); + } else { + QPID_LOG(warning, "Unexpected message format, got string at index " << index << " of properties"); + } + ++index; +} +void MessageReader::PropertiesReader::onSymbol(const CharSequence& v, const Descriptor*) // content-type, content-encoding +{ + if (index == CONTENT_TYPE) { + parent.onContentType(v); + } else if (index == CONTENT_ENCODING) { + parent.onContentEncoding(v); + } else { + QPID_LOG(warning, "Unexpected message format, got symbol at index " << index << " of properties"); + } + ++index; +} +void MessageReader::PropertiesReader::onTimestamp(int64_t v, const Descriptor*) // absolute-expiry-time, creation-time +{ + if (index == ABSOLUTE_EXPIRY_TIME) { + parent.onAbsoluteExpiryTime(v); + } else if (index == CREATION_TIME) { + parent.onCreationTime(v); + } else { + QPID_LOG(warning, "Unexpected message format, got timestamp at index " << index << " of properties"); + } + ++index; +} +void MessageReader::PropertiesReader::onUInt(uint32_t v, const Descriptor*) // group-sequence +{ + if (index == GROUP_SEQUENCE) { + parent.onGroupSequence(v); + } else { + QPID_LOG(warning, "Unexpected message format, got uint at index " << index << " of properties"); + } + ++index; +} +void MessageReader::PropertiesReader::onNull(const Descriptor*) +{ + ++index; +} + +/* +MessageReader::DurableReader::DurableReader(MessageReader& p) : parent(p) {} +void MessageReader::DurableReader::onBoolean(bool v, const Descriptor*) +{ + parent.onDurable(v); +} +MessageReader::PriorityReader::PriorityReader(MessageReader& p) : parent(p) {} +void MessageReader::PriorityReader::onUByte(uint8_t v, const Descriptor*) +{ + parent.onPriority(v); +} +MessageReader::TtlReader::TtlReader(MessageReader& p) : parent(p) {} +void MessageReader::TtlReader::onUInt(uint32_t v, const Descriptor*) +{ + parent.onTtl(v); +} +MessageReader::FirstAcquirerReader::FirstAcquirerReader(MessageReader& p) : parent(p) {} +void MessageReader::FirstAcquirerReader::onBoolean(bool v, const Descriptor*) +{ + parent.onFirstAcquirer(v); +} +MessageReader::DeliveryCountReader::DeliveryCountReader(MessageReader& p) : parent(p) {} +void MessageReader::DeliveryCountReader::onUInt(uint32_t v, const Descriptor*) +{ + parent.onDeliveryCount(v); +} +MessageReader::MessageIdReader::MessageIdReader(MessageReader& p) : parent(p) {} +void MessageReader::MessageIdReader::onUuid(const qpid::types::Uuid& v, const Descriptor*) +{ + parent.onMessageId(v); +} +void MessageReader::MessageIdReader::onULong(uint64_t v, const Descriptor*) +{ + parent.onMessageId(v); +} +void MessageReader::MessageIdReader::onString(const CharSequence& v, const Descriptor*) +{ + parent.onMessageId(v); +} +void MessageReader::MessageIdReader::onBinary(const CharSequence& v, const Descriptor*) +{ + parent.onMessageId(v); +} +MessageReader::UserIdReader::UserIdReader(MessageReader& p) : parent(p) {} +void MessageReader::UserIdReader::onBinary(const CharSequence& v, const Descriptor*) +{ + parent.onUserId(v); +} +MessageReader::ToReader::ToReader(MessageReader& p) : parent(p) {} +void MessageReader::ToReader::onString(const CharSequence& v, const Descriptor*) +{ + parent.onTo(v); +} +MessageReader::SubjectReader::SubjectReader(MessageReader& p) : parent(p) {} +void MessageReader::SubjectReader::onString(const CharSequence& v, const Descriptor*) +{ + parent.onSubject(v); +} +MessageReader::ReplyToReader::ReplyToReader(MessageReader& p) : parent(p) {} +void MessageReader::ReplyToReader::onString(const CharSequence& v, const Descriptor*) +{ + parent.onReplyTo(v); +} +MessageReader::CorrelationIdReader::CorrelationIdReader(MessageReader& p) : parent(p) {} +void MessageReader::CorrelationIdReader::onUuid(const qpid::types::Uuid& v, const Descriptor*) +{ + parent.onCorrelationId(v); +} +void MessageReader::CorrelationIdReader::onULong(uint64_t v, const Descriptor*) +{ + parent.onCorrelationId(v); +} +void MessageReader::CorrelationIdReader::onString(const CharSequence& v, const Descriptor*) +{ + parent.onCorrelationId(v); +} +void MessageReader::CorrelationIdReader::onBinary(const CharSequence& v, const Descriptor*) +{ + parent.onCorrelationId(v); +} +MessageReader::ContentTypeReader::ContentTypeReader(MessageReader& p) : parent(p) {} +void MessageReader::ContentTypeReader::onString(const CharSequence& v, const Descriptor*) +{ + parent.onContentType(v); +} +MessageReader::ContentEncodingReader::ContentEncodingReader(MessageReader& p) : parent(p) {} +void MessageReader::ContentEncodingReader::onString(const CharSequence& v, const Descriptor*) +{ + parent.onContentEncoding(v); +} +MessageReader::AbsoluteExpiryTimeReader::AbsoluteExpiryTimeReader(MessageReader& p) : parent(p) {} +void MessageReader::AbsoluteExpiryTimeReader::onTimestamp(int64_t v, const Descriptor*) +{ + parent.onAbsoluteExpiryTime(v); +} +MessageReader::CreationTimeReader::CreationTimeReader(MessageReader& p) : parent(p) {} +void MessageReader::CreationTimeReader::onTimestamp(int64_t v, const Descriptor*) +{ + parent.onCreationTime(v); +} +MessageReader::GroupIdReader::GroupIdReader(MessageReader& p) : parent(p) {} +void MessageReader::GroupIdReader::onString(const CharSequence& v, const Descriptor*) +{ + parent.onGroupId(v); +} +MessageReader::GroupSequenceReader::GroupSequenceReader(MessageReader& p) : parent(p) {} +void MessageReader::GroupSequenceReader::onUInt(uint32_t v, const Descriptor*) +{ + parent.onGroupSequence(v); +} +MessageReader::ReplyToGroupIdReader::ReplyToGroupIdReader(MessageReader& p) : parent(p) {} +void MessageReader::ReplyToGroupIdReader::onString(const CharSequence& v, const Descriptor*) +{ + parent.onReplyToGroupId(v); +} +*/ + +//header, properties, amqp-sequence, amqp-value +bool MessageReader::onStartList(uint32_t count, const CharSequence& raw, const Descriptor* descriptor) +{ + if (delegate) { + return delegate->onStartList(count, raw, descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got no descriptor for list."); + return false; + } else if (descriptor->match(HEADER_SYMBOL, HEADER_CODE)) { + delegate = &headerReader; + return true; + } else if (descriptor->match(PROPERTIES_SYMBOL, PROPERTIES_CODE)) { + delegate = &propertiesReader; + return true; + } else if (descriptor->match(AMQP_SEQUENCE_SYMBOL, AMQP_SEQUENCE_CODE) || descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + onBody(raw, *descriptor); + return false; + } else { + QPID_LOG(warning, "Unexpected described list: " << *descriptor); + return false; + } + } +} +void MessageReader::onEndList(uint32_t count, const Descriptor* descriptor) +{ + if (delegate) { + if (descriptor && (descriptor->match(HEADER_SYMBOL, HEADER_CODE) || descriptor->match(PROPERTIES_SYMBOL, PROPERTIES_CODE))) { + delegate = 0; + } else { + delegate->onEndList(count, descriptor); + } + } +} + +//delivery-annotations, message-annotations, application-properties, amqp-value +bool MessageReader::onStartMap(uint32_t count, const CharSequence& raw, const Descriptor* descriptor) +{ + if (delegate) { + return delegate->onStartMap(count, raw, descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got no descriptor for map."); + return false; + } else if (descriptor->match(DELIVERY_ANNOTATIONS_SYMBOL, DELIVERY_ANNOTATIONS_CODE)) { + onDeliveryAnnotations(raw); + return false; + } else if (descriptor->match(MESSAGE_ANNOTATIONS_SYMBOL, MESSAGE_ANNOTATIONS_CODE)) { + onMessageAnnotations(raw); + return false; + } else if (descriptor->match(FOOTER_SYMBOL, FOOTER_CODE)) { + onFooter(raw); + return false; + } else if (descriptor->match(APPLICATION_PROPERTIES_SYMBOL, APPLICATION_PROPERTIES_CODE)) { + onApplicationProperties(raw); + return false; + } else if (descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + onBody(raw, *descriptor); + return false; + } else { + QPID_LOG(warning, "Unexpected described map: " << *descriptor); + return false; + } + } +} + +void MessageReader::onEndMap(uint32_t count, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onEndMap(count, descriptor); + } +} + +//data, amqp-value +void MessageReader::onBinary(const CharSequence& bytes, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onBinary(bytes, descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got binary value with no descriptor."); + } else if (descriptor->match(DATA_SYMBOL, DATA_CODE) || descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + onBody(bytes, *descriptor); + } else { + QPID_LOG(warning, "Unexpected binary value with descriptor: " << *descriptor); + } + } + +} + +//amqp-value +void MessageReader::onNull(const Descriptor* descriptor) +{ + if (delegate) { + delegate->onNull(descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant v; + onBody(v, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got null value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected null value with descriptor: " << *descriptor); + } + } + } +} +void MessageReader::onString(const CharSequence& v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onString(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + onBody(v, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got string value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected string value with descriptor: " << *descriptor); + } + } + } +} +void MessageReader::onSymbol(const CharSequence& v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onSymbol(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + onBody(v, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got symbol value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected symbol value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onBoolean(bool v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onBoolean(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got boolean value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected boolean value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onUByte(uint8_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onUByte(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got ubyte value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected ubyte value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onUShort(uint16_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onUShort(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got ushort value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected ushort value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onUInt(uint32_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onUInt(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got uint value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected uint value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onULong(uint64_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onULong(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got ulong value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected ulong value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onByte(int8_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onByte(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got byte value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected byte value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onShort(int16_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onShort(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got short value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected short value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onInt(int32_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onInt(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got int value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected int value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onLong(int64_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onLong(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got long value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected long value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onFloat(float v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onFloat(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got float value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected float value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onDouble(double v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onDouble(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got double value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected double value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onUuid(const CharSequence& v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onUuid(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + onBody(v, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got uuid value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected uuid value with descriptor: " << *descriptor); + } + } + } +} + +void MessageReader::onTimestamp(int64_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onTimestamp(v, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + qpid::types::Variant body = v; + onBody(body, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got timestamp value with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected timestamp value with descriptor: " << *descriptor); + } + } + } +} + +bool MessageReader::onStartArray(uint32_t count, const CharSequence& raw, const Constructor& constructor, const Descriptor* descriptor) +{ + if (delegate) { + return delegate->onStartArray(count, raw, constructor, descriptor); + } else { + if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) { + onBody(raw, *descriptor); + } else { + if (!descriptor) { + QPID_LOG(warning, "Expected described type but got array with no descriptor."); + } else { + QPID_LOG(warning, "Unexpected array with descriptor: " << *descriptor); + } + } + return false; + } +} + +void MessageReader::onEndArray(uint32_t v, const Descriptor* descriptor) +{ + if (delegate) { + delegate->onEndArray(v, descriptor); + } +} + +MessageReader::MessageReader() : headerReader(*this), propertiesReader(*this), delegate(0) +{ + bare.init(); +} + +void MessageReader::onDescriptor(const Descriptor& descriptor, const char* position) +{ + if (bare.data) { + if (descriptor.match(FOOTER_SYMBOL, FOOTER_CODE)) { + bare.size = position - bare.data; + } + } else { + if (descriptor.match(PROPERTIES_SYMBOL, PROPERTIES_CODE) || descriptor.match(APPLICATION_PROPERTIES_SYMBOL, APPLICATION_PROPERTIES_CODE) + || descriptor.match(AMQP_SEQUENCE_SYMBOL, AMQP_SEQUENCE_CODE) || descriptor.match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE) || descriptor.match(DATA_SYMBOL, DATA_CODE)) { + bare.data = position; + } + } +} + +CharSequence MessageReader::getBareMessage() const { return bare; } + +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/MessageReader.h b/cpp/src/qpid/amqp/MessageReader.h new file mode 100644 index 0000000000..5d26b288f5 --- /dev/null +++ b/cpp/src/qpid/amqp/MessageReader.h @@ -0,0 +1,301 @@ +#ifndef QPID_AMQP_MESSAGEREADER_H +#define QPID_AMQP_MESSAGEREADER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/amqp/CharSequence.h" +#include "qpid/amqp/Reader.h" +#include "qpid/amqp/ListReader.h" +#include "qpid/types/Variant.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace amqp { + +/** + * Reader for an AMQP 1.0 message + */ +class MessageReader : public Reader +{ + public: + QPID_COMMON_EXTERN MessageReader(); + + //header, properties, amqp-sequence, amqp-value + QPID_COMMON_EXTERN bool onStartList(uint32_t, const CharSequence&, const Descriptor*); + QPID_COMMON_EXTERN void onEndList(uint32_t, const Descriptor*); + + //delivery-annotations, message-annotations, application-headers, amqp-value + QPID_COMMON_EXTERN bool onStartMap(uint32_t, const CharSequence&, const Descriptor*); + QPID_COMMON_EXTERN void onEndMap(uint32_t, const Descriptor*); + + //data, amqp-value + QPID_COMMON_EXTERN void onBinary(const CharSequence&, const Descriptor*); + + //amqp-value + QPID_COMMON_EXTERN void onNull(const Descriptor*); + QPID_COMMON_EXTERN void onString(const CharSequence&, const Descriptor*); + QPID_COMMON_EXTERN void onSymbol(const CharSequence&, const Descriptor*); + QPID_COMMON_EXTERN void onBoolean(bool, const Descriptor*); + QPID_COMMON_EXTERN void onUByte(uint8_t, const Descriptor*); + QPID_COMMON_EXTERN void onUShort(uint16_t, const Descriptor*); + QPID_COMMON_EXTERN void onUInt(uint32_t, const Descriptor*); + QPID_COMMON_EXTERN void onULong(uint64_t, const Descriptor*); + QPID_COMMON_EXTERN void onByte(int8_t, const Descriptor*); + QPID_COMMON_EXTERN void onShort(int16_t, const Descriptor*); + QPID_COMMON_EXTERN void onInt(int32_t, const Descriptor*); + QPID_COMMON_EXTERN void onLong(int64_t, const Descriptor*); + QPID_COMMON_EXTERN void onFloat(float, const Descriptor*); + QPID_COMMON_EXTERN void onDouble(double, const Descriptor*); + QPID_COMMON_EXTERN void onUuid(const CharSequence&, const Descriptor*); + QPID_COMMON_EXTERN void onTimestamp(int64_t, const Descriptor*); + QPID_COMMON_EXTERN bool onStartArray(uint32_t, const CharSequence&, const Constructor&, const Descriptor*); + QPID_COMMON_EXTERN void onEndArray(uint32_t, const Descriptor*); + QPID_COMMON_EXTERN void onDescriptor(const Descriptor&, const char*); + + //header: + virtual void onDurable(bool) = 0; + virtual void onPriority(uint8_t) = 0; + virtual void onTtl(uint32_t) = 0; + virtual void onFirstAcquirer(bool) = 0; + virtual void onDeliveryCount(uint32_t) = 0; + + //properties: + virtual void onMessageId(uint64_t) = 0; + virtual void onMessageId(const CharSequence&, qpid::types::VariantType) = 0; + virtual void onUserId(const CharSequence&) = 0; + virtual void onTo(const CharSequence&) = 0; + virtual void onSubject(const CharSequence&) = 0; + virtual void onReplyTo(const CharSequence&) = 0; + virtual void onCorrelationId(uint64_t) = 0; + virtual void onCorrelationId(const CharSequence&, qpid::types::VariantType) = 0; + virtual void onContentType(const CharSequence&) = 0; + virtual void onContentEncoding(const CharSequence&) = 0; + virtual void onAbsoluteExpiryTime(int64_t) = 0; + virtual void onCreationTime(int64_t) = 0; + virtual void onGroupId(const CharSequence&) = 0; + virtual void onGroupSequence(uint32_t) = 0; + virtual void onReplyToGroupId(const CharSequence&) = 0; + + virtual void onApplicationProperties(const CharSequence&) = 0; + virtual void onDeliveryAnnotations(const CharSequence&) = 0; + virtual void onMessageAnnotations(const CharSequence&) = 0; + virtual void onBody(const CharSequence&, const Descriptor&) = 0; + virtual void onBody(const qpid::types::Variant&, const Descriptor&) = 0; + virtual void onFooter(const CharSequence&) = 0; + + QPID_COMMON_EXTERN CharSequence getBareMessage() const; + + private: + /* + class DurableReader : public Reader + { + public: + DurableReader(MessageReader&); + void onBoolean(bool v, const Descriptor*); + private: + MessageReader& parent; + }; + class PriorityReader : public Reader + { + public: + PriorityReader(MessageReader&); + void onUByte(uint8_t v, const Descriptor*); + private: + MessageReader& parent; + }; + class TtlReader : public Reader + { + public: + TtlReader(MessageReader&); + void onUInt(uint32_t v, const Descriptor*); + private: + MessageReader& parent; + }; + class FirstAcquirerReader : public Reader + { + public: + FirstAcquirerReader(MessageReader&); + void onBoolean(bool v, const Descriptor*); + private: + MessageReader& parent; + }; + class DeliveryCountReader : public Reader + { + public: + DeliveryCountReader(MessageReader&); + void onUInt(uint32_t v, const Descriptor*); + private: + MessageReader& parent; + }; + + class MessageIdReader : public Reader + { + public: + MessageIdReader(MessageReader&); + void onUuid(const qpid::types::Uuid& v, const Descriptor*); + void onULong(uint64_t v, const Descriptor*); + void onString(const CharSequence& v, const Descriptor*); + void onBinary(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + class UserIdReader : public Reader + { + public: + UserIdReader(MessageReader&); + void onBinary(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + class ToReader : public Reader + { + public: + ToReader(MessageReader&); + void onString(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + class SubjectReader : public Reader + { + public: + SubjectReader(MessageReader&); + void onString(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + class ReplyToReader : public Reader + { + public: + ReplyToReader(MessageReader&); + void onString(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + class CorrelationIdReader : public Reader + { + public: + CorrelationIdReader(MessageReader&); + void onUuid(const qpid::types::Uuid& v, const Descriptor*); + void onULong(uint64_t v, const Descriptor*); + void onString(const CharSequence& v, const Descriptor*); + void onBinary(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + class ContentTypeReader : public Reader + { + public: + ContentTypeReader(MessageReader&); + void onString(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + class ContentEncodingReader : public Reader + { + public: + ContentEncodingReader(MessageReader&); + void onString(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + class AbsoluteExpiryTimeReader : public Reader + { + public: + AbsoluteExpiryTimeReader(MessageReader&); + void onTimestamp(int64_t v, const Descriptor*); + private: + MessageReader& parent; + }; + class CreationTimeReader : public Reader + { + public: + CreationTimeReader(MessageReader&); + void onTimestamp(int64_t v, const Descriptor*); + private: + MessageReader& parent; + }; + class GroupIdReader : public Reader + { + public: + GroupIdReader(MessageReader&); + void onString(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + class GroupSequenceReader : public Reader + { + public: + GroupSequenceReader(MessageReader&); + void onUInt(uint32_t v, const Descriptor*); + private: + MessageReader& parent; + }; + class ReplyToGroupIdReader : public Reader + { + public: + ReplyToGroupIdReader(MessageReader&); + void onString(const CharSequence& v, const Descriptor*); + private: + MessageReader& parent; + }; + */ + + class HeaderReader : public Reader //public ListReader + { + public: + //Reader& getReader(size_t index); + + HeaderReader(MessageReader&); + void onBoolean(bool v, const Descriptor*); // durable, first-acquirer + void onUByte(uint8_t v, const Descriptor*); // priority + void onUInt(uint32_t v, const Descriptor*); // ttl, delivery-count + void onNull(const Descriptor*); + private: + MessageReader& parent; + size_t index; + }; + class PropertiesReader : public Reader //public ListReader + { + public: + //Reader& getReader(size_t index); + + PropertiesReader(MessageReader&); + void onUuid(const CharSequence& v, const Descriptor*); // message-id, correlation-id + void onULong(uint64_t v, const Descriptor*); // message-id, correlation-id + void onBinary(const CharSequence& v, const Descriptor*); // message-id, correlation-id, user-id + void onString(const CharSequence& v, const Descriptor*); // message-id, correlation-id, group-id, reply-to-group-id, subject, to, reply-to + void onSymbol(const CharSequence& v, const Descriptor*); // content-type, content-encoding + void onTimestamp(int64_t v, const Descriptor*); // absolute-expiry-time, creation-time + void onUInt(uint32_t v, const Descriptor*); // group-sequence + void onNull(const Descriptor*); + private: + MessageReader& parent; + size_t index; + }; + HeaderReader headerReader; + PropertiesReader propertiesReader; + Reader* delegate; + CharSequence bare; +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_MESSAGEREADER_H*/ diff --git a/cpp/src/qpid/amqp/Reader.h b/cpp/src/qpid/amqp/Reader.h new file mode 100644 index 0000000000..64019d1521 --- /dev/null +++ b/cpp/src/qpid/amqp/Reader.h @@ -0,0 +1,80 @@ +#ifndef QPID_AMQP_READER_H +#define QPID_AMQP_READER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/sys/IntegerTypes.h" +#include <stddef.h> + +namespace qpid { +namespace amqp { +struct CharSequence; +struct Constructor; +struct Descriptor; + +/** + * Allows an event-driven, callback-based approach to processing an + * AMQP encoded data stream. By sublassing and implementing the + * methods of interest, readers can be constructed for different + * contexts. + */ +class Reader +{ + public: + virtual ~Reader() {} + virtual void onNull(const Descriptor*) {} + virtual void onBoolean(bool, const Descriptor*) {} + virtual void onUByte(uint8_t, const Descriptor*) {} + virtual void onUShort(uint16_t, const Descriptor*) {} + virtual void onUInt(uint32_t, const Descriptor*) {} + virtual void onULong(uint64_t, const Descriptor*) {} + virtual void onByte(int8_t, const Descriptor*) {} + virtual void onShort(int16_t, const Descriptor*) {} + virtual void onInt(int32_t, const Descriptor*) {} + virtual void onLong(int64_t, const Descriptor*) {} + virtual void onFloat(float, const Descriptor*) {} + virtual void onDouble(double, const Descriptor*) {} + virtual void onUuid(const CharSequence&, const Descriptor*) {} + virtual void onTimestamp(int64_t, const Descriptor*) {} + + virtual void onBinary(const CharSequence&, const Descriptor*) {} + virtual void onString(const CharSequence&, const Descriptor*) {} + virtual void onSymbol(const CharSequence&, const Descriptor*) {} + + /** + * @return true to get elements of the compound value, false + * to skip over it + */ + virtual bool onStartList(uint32_t /*count*/, const CharSequence&, const Descriptor*) { return true; } + virtual bool onStartMap(uint32_t /*count*/, const CharSequence&, const Descriptor*) { return true; } + virtual bool onStartArray(uint32_t /*count*/, const CharSequence&, const Constructor&, const Descriptor*) { return true; } + virtual void onEndList(uint32_t /*count*/, const Descriptor*) {} + virtual void onEndMap(uint32_t /*count*/, const Descriptor*) {} + virtual void onEndArray(uint32_t /*count*/, const Descriptor*) {} + + virtual void onDescriptor(const Descriptor&, const char*) {} + + virtual bool proceed() { return true; } + private: +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_READER_H*/ diff --git a/cpp/src/qpid/amqp/Sasl.cpp b/cpp/src/qpid/amqp/Sasl.cpp new file mode 100644 index 0000000000..7b0779fe94 --- /dev/null +++ b/cpp/src/qpid/amqp/Sasl.cpp @@ -0,0 +1,136 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/Sasl.h" +#include "qpid/amqp/Decoder.h" +#include "qpid/amqp/Descriptor.h" +#include "qpid/amqp/Encoder.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/framing/ProtocolInitiation.h" +#include <string.h> + +namespace qpid { +namespace amqp { + +Sasl::Sasl(const std::string& i) : id(i), buffer(2*512/*AMQP 1.0's MAX_MIN_FRAME_SIZE - is this enough though?*/), encoder(&buffer[0], buffer.size()) {} +Sasl::~Sasl() {} + +void* Sasl::startFrame() +{ + //write sasl frame header, leaving 4 bytes for total size + char* start = encoder.skip(4); + encoder.write((uint8_t) 0x02);//data offset + encoder.write((uint8_t) 0x01);//frame type + encoder.write((uint16_t) 0x0000);//ignored + return start; +} + +void Sasl::endFrame(void* frame) +{ + //now backfill the frame size + char* start = (char*) frame; + char* current = &buffer[encoder.getPosition()]; + uint32_t frameSize = current - start; + Encoder backfill(start, 4); + backfill.write(frameSize); + QPID_LOG(trace, "Completed encoding of frame of " << frameSize << " bytes"); +} + + +std::size_t Sasl::read(const char* data, size_t available) +{ + size_t consumed = 0; + while (available - consumed > 4/*framesize*/) { + Decoder decoder(data+consumed, available-consumed); + //read frame-header + uint32_t frameSize = decoder.readUInt(); + if (frameSize > decoder.getSize()) break;//don't have all the data for this frame yet + + QPID_LOG(trace, "Reading SASL frame of size " << frameSize); + decoder.resetSize(frameSize); + uint8_t dataOffset = decoder.readUByte(); + uint8_t frameType = decoder.readUByte(); + if (frameType != 0x01) { + QPID_LOG(error, "Expected SASL frame; got type " << frameType); + } + uint16_t ignored = decoder.readUShort(); + if (ignored) { + QPID_LOG(info, "Got non null bytes at end of SASL frame header"); + } + + //body is at offset 4*dataOffset from the start + size_t skip = dataOffset*4 - 8; + if (skip) { + QPID_LOG(info, "Offset for sasl frame was not as expected"); + decoder.advance(skip); + } + decoder.read(*this); + consumed += decoder.getPosition(); + } + return consumed; +} + +std::size_t Sasl::write(char* data, size_t size) +{ + size_t available = encoder.getPosition(); + if (available) { + size_t encoded = available > size ? size : available; + ::memcpy(data, &buffer[0], encoded); + size_t remainder = encoder.getPosition() - encoded; + if (remainder) { + //shuffle + ::memcpy(&buffer[0], &buffer[size], remainder); + } + encoder.resetPosition(remainder); + return encoded; + } else { + return 0; + } +} + +std::size_t Sasl::readProtocolHeader(const char* buffer, std::size_t size) +{ + framing::ProtocolInitiation pi(qpid::framing::ProtocolVersion(1,0,qpid::framing::ProtocolVersion::SASL)); + if (size >= pi.encodedSize()) { + qpid::framing::Buffer out(const_cast<char*>(buffer), size); + pi.decode(out); + QPID_LOG_CAT(debug, protocol, id << " read protocol header: " << pi); + return pi.encodedSize(); + } else { + return 0; + } +} +std::size_t Sasl::writeProtocolHeader(char* buffer, std::size_t size) +{ + framing::ProtocolInitiation pi(qpid::framing::ProtocolVersion(1,0,qpid::framing::ProtocolVersion::SASL)); + if (size >= pi.encodedSize()) { + QPID_LOG_CAT(debug, protocol, id << " writing protocol header: " << pi); + qpid::framing::Buffer out(buffer, size); + pi.encode(out); + return pi.encodedSize(); + } else { + QPID_LOG_CAT(warning, protocol, id << " insufficient buffer for protocol header: " << size) + return 0; + } +} + +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/Sasl.h b/cpp/src/qpid/amqp/Sasl.h new file mode 100644 index 0000000000..558f6071fc --- /dev/null +++ b/cpp/src/qpid/amqp/Sasl.h @@ -0,0 +1,54 @@ +#ifndef QPID_AMQP_SASL_H +#define QPID_AMQP_SASL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/Encoder.h" +#include "qpid/amqp/Reader.h" +#include <string> +#include <vector> + +namespace qpid { +namespace amqp { + +/** + * Base for SASL client and server utilities + */ +class Sasl : protected Reader +{ + public: + Sasl(const std::string& id); + virtual ~Sasl(); + std::size_t read(const char* data, size_t available); + std::size_t write(char* data, size_t available); + std::size_t readProtocolHeader(const char* buffer, std::size_t size); + std::size_t writeProtocolHeader(char* buffer, std::size_t size); + protected: + const std::string id; + std::vector<char> buffer; + Encoder encoder; + + void* startFrame(); + void endFrame(void*); +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_SASL_H*/ diff --git a/cpp/src/qpid/amqp/SaslClient.cpp b/cpp/src/qpid/amqp/SaslClient.cpp new file mode 100644 index 0000000000..69660e9294 --- /dev/null +++ b/cpp/src/qpid/amqp/SaslClient.cpp @@ -0,0 +1,154 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/SaslClient.h" +#include "qpid/amqp/Decoder.h" +#include "qpid/amqp/Descriptor.h" +#include "qpid/amqp/descriptors.h" +#include "qpid/amqp/Encoder.h" +#include "qpid/log/Statement.h" + +using namespace qpid::amqp::sasl; + +namespace qpid { +namespace amqp { + +SaslClient::SaslClient(const std::string& id) : Sasl(id) {} +SaslClient::~SaslClient() {} +void SaslClient::init(const std::string& mechanism, const std::string* response, const std::string* hostname) +{ + void* frame = startFrame(); + + void* token = encoder.startList32(&SASL_INIT); + encoder.writeSymbol(mechanism); + if (response) encoder.writeBinary(*response); + else encoder.writeNull(); + if (hostname) encoder.writeString(*hostname); + else encoder.writeNull(); + encoder.endList32(3, token); + + endFrame(frame); + QPID_LOG_CAT(debug, protocol, id << " Sent SASL-INIT(" << mechanism << ", " << (response ? *response : "null") << ", " << (hostname ? *hostname : "null") << ")"); +} +void SaslClient::response(const std::string* r) +{ + void* frame = startFrame(); + + void* token = encoder.startList32(&SASL_RESPONSE); + if (r) encoder.writeBinary(*r); + else encoder.writeNull(); + encoder.endList32(1, token); + + endFrame(frame); + QPID_LOG_CAT(debug, protocol, id << " Sent SASL-RESPONSE(" << (r ? *r : "null") << ")"); +} + + +namespace { +const std::string SPACE(" "); +class SaslMechanismsReader : public Reader +{ + public: + SaslMechanismsReader(SaslClient& c) : client(c), expected(0) {} + void onSymbol(const CharSequence& mechanism, const Descriptor*) + { + if (expected) { + mechanisms << mechanism.str() << SPACE; + } else { + client.mechanisms(mechanism.str()); + } + } + bool onStartArray(uint32_t count, const CharSequence&, const Constructor&, const Descriptor*) + { + expected = count; + return true; + } + void onEndArray(uint32_t, const Descriptor*) + { + client.mechanisms(mechanisms.str()); + } + private: + SaslClient& client; + uint32_t expected; + std::stringstream mechanisms; +}; +class SaslChallengeReader : public Reader +{ + public: + SaslChallengeReader(SaslClient& c) : client(c) {} + void onNull(const Descriptor*) { client.challenge(); } + void onBinary(const CharSequence& c, const Descriptor*) { client.challenge(c.str()); } + private: + SaslClient& client; +}; +class SaslOutcomeReader : public Reader +{ + public: + SaslOutcomeReader(SaslClient& c, bool e) : client(c), expectExtraData(e) {} + void onUByte(uint8_t c, const Descriptor*) + { + if (expectExtraData) code = c; + else client.outcome(c); + } + void onBinary(const CharSequence& extra, const Descriptor*) { client.outcome(code, extra.str()); } + void onNull(const Descriptor*) { client.outcome(code); } + private: + SaslClient& client; + bool expectExtraData; + uint8_t code; +}; +} + +bool SaslClient::onStartList(uint32_t count, const CharSequence& arguments, const Descriptor* descriptor) +{ + if (!descriptor) { + QPID_LOG(error, "Expected described type in SASL negotiation but got no descriptor"); + } else if (descriptor->match(SASL_MECHANISMS_SYMBOL, SASL_MECHANISMS_CODE)) { + QPID_LOG(trace, "Reading SASL-MECHANISMS"); + Decoder decoder(arguments.data, arguments.size); + if (count != 1) QPID_LOG(error, "Invalid SASL-MECHANISMS frame; exactly one field expected, got " << count); + SaslMechanismsReader reader(*this); + decoder.read(reader); + } else if (descriptor->match(SASL_CHALLENGE_SYMBOL, SASL_CHALLENGE_CODE)) { + QPID_LOG(trace, "Reading SASL-CHALLENGE"); + Decoder decoder(arguments.data, arguments.size); + if (count != 1) QPID_LOG(error, "Invalid SASL-CHALLENGE frame; exactly one field expected, got " << count); + SaslChallengeReader reader(*this); + decoder.read(reader); + } else if (descriptor->match(SASL_OUTCOME_SYMBOL, SASL_OUTCOME_CODE)) { + QPID_LOG(trace, "Reading SASL-OUTCOME"); + Decoder decoder(arguments.data, arguments.size); + if (count == 1) { + SaslOutcomeReader reader(*this, false); + decoder.read(reader); + } else if (count == 2) { + SaslOutcomeReader reader(*this, true); + decoder.read(reader); + } else { + QPID_LOG(error, "Invalid SASL-OUTCOME frame; got " << count << " fields"); + } + } else { + QPID_LOG(error, "Unexpected descriptor in SASL negotiation: " << *descriptor); + } + return false; +} + + +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/SaslClient.h b/cpp/src/qpid/amqp/SaslClient.h new file mode 100644 index 0000000000..9f3eefadc2 --- /dev/null +++ b/cpp/src/qpid/amqp/SaslClient.h @@ -0,0 +1,54 @@ +#ifndef QPID_AMQP_SASLCLIENT_H +#define QPID_AMQP_SASLCLIENT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/Sasl.h" + +namespace qpid { +namespace amqp { + +/** + * Utility for decoding and encoding SASL frames by the peer acting as + * the SASL client. + */ +class SaslClient : public Sasl +{ + public: + SaslClient(const std::string& id); + virtual ~SaslClient(); + virtual void mechanisms(const std::string&) = 0; + virtual void challenge(const std::string&) = 0; + virtual void challenge() = 0; //null != empty string + virtual void outcome(uint8_t result, const std::string&) = 0; + virtual void outcome(uint8_t result) = 0; + + void init(const std::string& mechanism, const std::string* response, const std::string* hostname); + void response(const std::string*); + + private: + bool onStartList(uint32_t count, const CharSequence& arguments, const Descriptor* descriptor); + +}; + +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_SASLCLIENT_H*/ diff --git a/cpp/src/qpid/amqp/SaslServer.cpp b/cpp/src/qpid/amqp/SaslServer.cpp new file mode 100644 index 0000000000..403730ad69 --- /dev/null +++ b/cpp/src/qpid/amqp/SaslServer.cpp @@ -0,0 +1,183 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/SaslServer.h" +#include "qpid/amqp/Constructor.h" +#include "qpid/amqp/Decoder.h" +#include "qpid/amqp/Descriptor.h" +#include "qpid/amqp/descriptors.h" +#include "qpid/amqp/Encoder.h" +#include "qpid/amqp/typecodes.h" +#include "qpid/log/Statement.h" +#include "qpid/StringUtils.h" +#include <limits> +#include <vector> + +using namespace qpid::amqp::sasl; +using namespace qpid::amqp::typecodes; + +namespace qpid { +namespace amqp { +namespace { +const std::string SPACE(" "); +const std::string NULL_("NULL"); +} + +SaslServer::SaslServer(const std::string& id) : Sasl(id) {} +SaslServer::~SaslServer() {} + +void SaslServer::mechanisms(const std::string& mechanisms) +{ + void* frameToken = startFrame(); + + std::vector<std::string> parts = split(mechanisms, SPACE); + void* listToken = encoder.startList32(&SASL_MECHANISMS); + if (parts.size() > 1) { + void* arrayToken = encoder.startArray8(Constructor(SYMBOL8)); + for (std::vector<std::string>::const_iterator i = parts.begin();i != parts.end(); ++i) { + uint8_t size = i->size() > std::numeric_limits<uint8_t>::max() ? std::numeric_limits<uint8_t>::max() : i->size(); + encoder.write(size); + encoder.writeBytes(i->data(), size); + } + encoder.endArray8(parts.size(), arrayToken); + } else { + encoder.writeSymbol(mechanisms); + } + encoder.endList32(1, listToken); + + endFrame(frameToken); + QPID_LOG_CAT(debug, protocol, id << " Sent SASL-MECHANISMS(" << mechanisms << ") " << encoder.getPosition()); +} +void SaslServer::challenge(const std::string* c) +{ + void* frameToken = startFrame(); + + void* listToken = encoder.startList32(&SASL_CHALLENGE); + if (c) encoder.writeBinary(*c); + else encoder.writeNull(); + encoder.endList32(1, listToken); + + endFrame(frameToken); + QPID_LOG_CAT(debug, protocol, id << " Sent SASL-CHALLENGE(" << (c ? *c : NULL_) << ") " << encoder.getPosition()); +} +void SaslServer::completed(bool succeeded) +{ + void* frameToken = startFrame(); + + void* listToken = encoder.startList8(&SASL_OUTCOME); + encoder.writeUByte(succeeded ? 0 : 1); + encoder.endList8(1, listToken); + + endFrame(frameToken); + QPID_LOG_CAT(debug, protocol, id << " Sent SASL-OUTCOME(" << (succeeded ? 0 : 1) << ") " << encoder.getPosition()); +} + +namespace { +class SaslInitReader : public Reader +{ + public: + SaslInitReader(SaslServer& s, uint32_t e) : server(s), expected(e), hasResponse(false), index(0) {} + void onNull(const Descriptor*) + { + ++index; + if (index == 2) { + if (--expected == 0) { + server.init(mechanism, 0, 0); + } + } else if (index == 3) { + server.init(mechanism, hasResponse ? &response : 0, 0); + } else { + QPID_LOG(warning, "Unexpected sequence of fields for SASL-INIT: got null for field " << index); + } + } + void onBinary(const CharSequence& r, const Descriptor*) + { + if (++index != 2) QPID_LOG(warning, "Unexpected sequence of fields for SASL-INIT: got binary for field " << index); + response = r.str(); + hasResponse = true; + if (--expected == 0) { + server.init(mechanism, &response, 0); + } + } + void onString(const CharSequence& h, const Descriptor*) + { + if (--expected || ++index != 3) { + QPID_LOG(warning, "Unexpected sequence of fields for SASL-INIT: got string for field " << index); + } else { + std::string hostname = h.str(); + server.init(mechanism, hasResponse ? &response : 0, &hostname); + } + } + void onSymbol(const CharSequence& m, const Descriptor*) + { + if (++index != 1) QPID_LOG(warning, "Unexpected sequence of fields for SASL-INIT: got symbol for field " << index); + if (--expected) { + mechanism = m.str(); + } else { + server.init(m.str(), 0, 0); + } + } + private: + SaslServer& server; + uint32_t expected; + std::string mechanism; + std::string response; + bool hasResponse; + uint32_t index; +}; + +class SaslResponseReader : public Reader +{ + public: + SaslResponseReader(SaslServer& s) : server(s) {} + void onNull(const Descriptor*) { server.response(0); } + void onBinary(const CharSequence& r, const Descriptor*) + { + std::string s = r.str(); + server.response(&s); + } + private: + SaslServer& server; +}; +} + +bool SaslServer::onStartList(uint32_t count, const CharSequence& arguments, const Descriptor* descriptor) +{ + if (!descriptor) { + QPID_LOG(error, "Expected described type in SASL negotiation but got no descriptor"); + } else if (descriptor->match(SASL_INIT_SYMBOL, SASL_INIT_CODE)) { + QPID_LOG(trace, "Reading SASL-INIT"); + Decoder decoder(arguments.data, arguments.size); + if (count < 1 || count > 3) QPID_LOG(error, "Invalid SASL-INIT frame; got " << count << " fields"); + SaslInitReader reader(*this, count); + decoder.read(reader); + } else if (descriptor->match(SASL_RESPONSE_SYMBOL, SASL_RESPONSE_CODE)) { + QPID_LOG(trace, "Reading SASL-RESPONSE (" << std::string(arguments.data, arguments.size) << ") " << count << " elements"); + Decoder decoder(arguments.data, arguments.size); + if (count != 1) QPID_LOG(error, "Invalid SASL-RESPONSE frame; exactly one field expected, got " << count); + SaslResponseReader reader(*this); + decoder.read(reader); + } else { + QPID_LOG(error, "Unexpected descriptor in SASL negotiation: " << *descriptor); + } + return false; +} + +}} // namespace qpid::amqp diff --git a/cpp/src/qpid/amqp/SaslServer.h b/cpp/src/qpid/amqp/SaslServer.h new file mode 100644 index 0000000000..43b960454f --- /dev/null +++ b/cpp/src/qpid/amqp/SaslServer.h @@ -0,0 +1,50 @@ +#ifndef QPID_AMQP_SASLSERVER_H +#define QPID_AMQP_SASLSERVER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/Sasl.h" + +namespace qpid { +namespace amqp { + +/** + * Utility for decoding and encoding SASL frames by the peer acting as + * the SASL server. + */ +class SaslServer : public Sasl +{ + public: + SaslServer(const std::string& id); + virtual ~SaslServer(); + virtual void init(const std::string& mechanism, const std::string* response, const std::string* hostname) = 0; + virtual void response(const std::string*) = 0; + + void mechanisms(const std::string& mechanisms); + void challenge(const std::string*); + void completed(bool succeeded); + + private: + bool onStartList(uint32_t count, const CharSequence& arguments, const Descriptor* descriptor); +}; +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_SASLSERVER_H*/ diff --git a/cpp/src/qpid/amqp/descriptors.h b/cpp/src/qpid/amqp/descriptors.h new file mode 100644 index 0000000000..19a8985433 --- /dev/null +++ b/cpp/src/qpid/amqp/descriptors.h @@ -0,0 +1,88 @@ +#ifndef QPID_AMQP_DESCRIPTORS_H +#define QPID_AMQP_DESCRIPTORS_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Descriptor.h" + +namespace qpid { +namespace amqp { + +namespace message { +const std::string HEADER_SYMBOL("amqp:header:list"); +const std::string PROPERTIES_SYMBOL("amqp:properties:list"); +const std::string DELIVERY_ANNOTATIONS_SYMBOL("amqp:delivery-annotations:map"); +const std::string MESSAGE_ANNOTATIONS_SYMBOL("amqp:message-annotations:map"); +const std::string APPLICATION_PROPERTIES_SYMBOL("amqp:application-properties:map"); +const std::string AMQP_SEQUENCE_SYMBOL("amqp:amqp-sequence:list"); +const std::string AMQP_VALUE_SYMBOL("amqp:amqp-sequence:*"); +const std::string DATA_SYMBOL("amqp:data:binary"); +const std::string FOOTER_SYMBOL("amqp:footer:map"); + +const uint64_t HEADER_CODE(0x70); +const uint64_t DELIVERY_ANNOTATIONS_CODE(0x71); +const uint64_t MESSAGE_ANNOTATIONS_CODE(0x72); +const uint64_t PROPERTIES_CODE(0x73); +const uint64_t APPLICATION_PROPERTIES_CODE(0x74); +const uint64_t DATA_CODE(0x75); +const uint64_t AMQP_SEQUENCE_CODE(0x76); +const uint64_t AMQP_VALUE_CODE(0x77); +const uint64_t FOOTER_CODE(0x78); + +const Descriptor HEADER(HEADER_CODE); +const Descriptor DELIVERY_ANNOTATIONS(DELIVERY_ANNOTATIONS_CODE); +const Descriptor MESSAGE_ANNOTATIONS(MESSAGE_ANNOTATIONS_CODE); +const Descriptor PROPERTIES(PROPERTIES_CODE); +const Descriptor APPLICATION_PROPERTIES(APPLICATION_PROPERTIES_CODE); +const Descriptor DATA(DATA_CODE); +} + +namespace sasl { +const std::string SASL_MECHANISMS_SYMBOL("amqp:sasl-mechanisms:list"); +const std::string SASL_INIT_SYMBOL("amqp:sasl-init:list"); +const std::string SASL_CHALLENGE_SYMBOL("amqp:sasl-challenge:list"); +const std::string SASL_RESPONSE_SYMBOL("amqp:sasl-response:list"); +const std::string SASL_OUTCOME_SYMBOL("amqp:sasl-outcome:list"); + +const uint64_t SASL_MECHANISMS_CODE(0x40); +const uint64_t SASL_INIT_CODE(0x41); +const uint64_t SASL_CHALLENGE_CODE(0x42); +const uint64_t SASL_RESPONSE_CODE(0x43); +const uint64_t SASL_OUTCOME_CODE(0x44); + +const Descriptor SASL_MECHANISMS(SASL_MECHANISMS_CODE); +const Descriptor SASL_INIT(SASL_INIT_CODE); +const Descriptor SASL_CHALLENGE(SASL_CHALLENGE_CODE); +const Descriptor SASL_RESPONSE(SASL_RESPONSE_CODE); +const Descriptor SASL_OUTCOME(SASL_OUTCOME_CODE); +} + +namespace filters { +const std::string LEGACY_DIRECT_FILTER_SYMBOL("apache.org:legacy-amqp-direct-binding:string"); +const std::string LEGACY_TOPIC_FILTER_SYMBOL("apache.org:legacy-amqp-direct-binding:string"); + +const uint64_t LEGACY_DIRECT_FILTER_CODE(0x0000468C00000000ULL); +const uint64_t LEGACY_TOPIC_FILTER_CODE(0x0000468C00000001ULL); +} + +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_DESCRIPTORS_H*/ diff --git a/cpp/src/qpid/amqp/typecodes.h b/cpp/src/qpid/amqp/typecodes.h new file mode 100644 index 0000000000..3c6bd17b97 --- /dev/null +++ b/cpp/src/qpid/amqp/typecodes.h @@ -0,0 +1,115 @@ +#ifndef QPID_AMQP_TYPECODES_H +#define QPID_AMQP_TYPECODES_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +namespace qpid { +namespace amqp { + +namespace typecodes +{ +const uint8_t DESCRIPTOR(0x0); + +const uint8_t NULL_VALUE(0x40); + +const uint8_t BOOLEAN(0x56); +const uint8_t BOOLEAN_TRUE(0x41); +const uint8_t BOOLEAN_FALSE(0x42); + +const uint8_t UBYTE(0x50); +const uint8_t USHORT(0x60); +const uint8_t UINT(0x70); +const uint8_t UINT_SMALL(0x52); +const uint8_t UINT_ZERO(0x43); +const uint8_t ULONG(0x80); +const uint8_t ULONG_SMALL(0x53); +const uint8_t ULONG_ZERO(0x44); + +const uint8_t BYTE(0x51); +const uint8_t SHORT(0x61); +const uint8_t INT(0x71); +const uint8_t INT_SMALL(0x54); +const uint8_t LONG(0x81); +const uint8_t LONG_SMALL(0x55); + +const uint8_t FLOAT(0x72); +const uint8_t DOUBLE(0x82); + +const uint8_t DECIMAL32(0x74); +const uint8_t DECIMAL64(0x84); +const uint8_t DECIMAL128(0x94); + +const uint8_t CHAR_UTF32(0x73); +const uint8_t TIMESTAMP(0x83); +const uint8_t UUID(0x98); + +const uint8_t BINARY8(0xa0); +const uint8_t BINARY32(0xb0); +const uint8_t STRING8(0xa1); +const uint8_t STRING32(0xb1); +const uint8_t SYMBOL8(0xa3); +const uint8_t SYMBOL32(0xb3); + +typedef std::pair<uint8_t, uint8_t> CodePair; +const CodePair SYMBOL(SYMBOL8, SYMBOL32); +const CodePair STRING(STRING8, STRING32); +const CodePair BINARY(BINARY8, BINARY32); + +const uint8_t LIST0(0x45); +const uint8_t LIST8(0xc0); +const uint8_t LIST32(0xd0); +const uint8_t MAP8(0xc1); +const uint8_t MAP32(0xd1); +const uint8_t ARRAY8(0xe0); +const uint8_t ARRAY32(0xf0); + + +const std::string NULL_NAME("null"); +const std::string BOOLEAN_NAME("name"); + +const std::string UBYTE_NAME("ubyte"); +const std::string USHORT_NAME("ushort"); +const std::string UINT_NAME("uint"); +const std::string ULONG_NAME("ulong"); + +const std::string BYTE_NAME("byte"); +const std::string SHORT_NAME("short"); +const std::string INT_NAME("int"); +const std::string LONG_NAME("long"); + +const std::string FLOAT_NAME("float"); +const std::string DOUBLE_NAME("double"); + +const std::string TIMESTAMP_NAME("timestamp"); +const std::string UUID_NAME("uuid"); + +const std::string BINARY_NAME("binary"); +const std::string STRING_NAME("string"); +const std::string SYMBOL_NAME("symbol"); + +const std::string LIST_NAME("list"); +const std::string MAP_NAME("map"); +const std::string ARRAY_NAME("array"); +} + +}} // namespace qpid::amqp + +#endif /*!QPID_AMQP_TYPECODES_H*/ diff --git a/cpp/src/qpid/amqp_0_10/Connection.cpp b/cpp/src/qpid/amqp_0_10/Connection.cpp index ceeaadf70c..5312c40f2a 100644 --- a/cpp/src/qpid/amqp_0_10/Connection.cpp +++ b/cpp/src/qpid/amqp_0_10/Connection.cpp @@ -74,14 +74,14 @@ bool Connection::isClosed() const { return pushClosed && popClosed; } -size_t Connection::encode(const char* buffer, size_t size) { +size_t Connection::encode(char* buffer, size_t size) { { // Swap frameQueue data into workQueue to avoid holding lock while we encode. Mutex::ScopedLock l(frameQueueLock); if (popClosed) return 0; // Can't pop any more frames. assert(workQueue.empty()); workQueue.swap(frameQueue); } - framing::Buffer out(const_cast<char*>(buffer), size); + framing::Buffer out(buffer, size); if (!isClient && !initialized) { framing::ProtocolInitiation pi(getVersion()); pi.encode(out); @@ -119,7 +119,6 @@ size_t Connection::encode(const char* buffer, size_t size) { void Connection::abort() { output.abort(); } void Connection::activateOutput() { output.activateOutput(); } -void Connection::giveReadCredit(int32_t credit) { output.giveReadCredit(credit); } void Connection::close() { // No more frames can be pushed onto the queue. @@ -146,10 +145,6 @@ framing::ProtocolVersion Connection::getVersion() const { return version; } -void Connection::setVersion(const framing::ProtocolVersion& v) { - version = v; -} - size_t Connection::getBuffered() const { Mutex::ScopedLock l(frameQueueLock); return buffered; diff --git a/cpp/src/qpid/amqp_0_10/Connection.h b/cpp/src/qpid/amqp_0_10/Connection.h index 995d824796..92ae5a3dd3 100644 --- a/cpp/src/qpid/amqp_0_10/Connection.h +++ b/cpp/src/qpid/amqp_0_10/Connection.h @@ -61,21 +61,16 @@ class Connection : public sys::ConnectionCodec, QPID_BROKER_EXTERN Connection(sys::OutputControl&, const std::string& id, bool isClient); QPID_BROKER_EXTERN void setInputHandler(std::auto_ptr<sys::ConnectionInputHandler> c); size_t decode(const char* buffer, size_t size); - size_t encode(const char* buffer, size_t size); + size_t encode(char* buffer, size_t size); bool isClosed() const; bool canEncode(); void abort(); void activateOutput(); - void giveReadCredit(int32_t); void closed(); // connection closed by peer. void close(); // closing from this end. void send(framing::AMQFrame&); framing::ProtocolVersion getVersion() const; size_t getBuffered() const; - - /** Used by cluster code to set a special version on "update" connections. */ - // FIXME aconway 2009-07-30: find a cleaner mechanism for this. - void setVersion(const framing::ProtocolVersion&); }; }} // namespace qpid::amqp_0_10 diff --git a/cpp/src/qpid/asyncStore/OperationQueue.cpp b/cpp/src/qpid/asyncStore/OperationQueue.cpp index dff5387827..9dea055223 100644 --- a/cpp/src/qpid/asyncStore/OperationQueue.cpp +++ b/cpp/src/qpid/asyncStore/OperationQueue.cpp @@ -51,7 +51,7 @@ OperationQueue::handle(const OperationQueue::OpQueue::Batch& e) { try { for (OpQueue::Batch::const_iterator i = e.begin(); i != e.end(); ++i) { // DEBUG: kpvdr -std::cout << "#### OperationQueue::handle(): op=" << (*i)->getOpStr() << std::endl << std::flush; +//std::cout << "#### OperationQueue::handle(): op=" << (*i)->getOpStr() << std::endl << std::flush; (*i)->executeOp(); // Do store work here } } catch (const std::exception& e) { diff --git a/cpp/src/qpid/asyncStore/Plugin.cpp b/cpp/src/qpid/asyncStore/Plugin.cpp index 7395fc9b87..666db2120f 100644 --- a/cpp/src/qpid/asyncStore/Plugin.cpp +++ b/cpp/src/qpid/asyncStore/Plugin.cpp @@ -54,13 +54,8 @@ Plugin::initialize(Target& target) { Broker* broker = dynamic_cast<Broker*>(&target); if (!broker || !m_store) return; - // Not done in earlyInitialize as the Broker::isInCluster test won't work there. - if (broker->isInCluster()) { - QPID_LOG(info, "asyncStore: Part of cluster: Disabling management instrumentation"); - } else { - QPID_LOG(info, "asyncStore: Enabling management instrumentation"); - m_store->initManagement(broker); - } + QPID_LOG(info, "asyncStore: Enabling management instrumentation"); + m_store->initManagement(broker); } void diff --git a/cpp/src/qpid/broker/AclModule.h b/cpp/src/qpid/broker/AclModule.h index 4caf8ed3ce..c01697ace9 100644 --- a/cpp/src/qpid/broker/AclModule.h +++ b/cpp/src/qpid/broker/AclModule.h @@ -79,6 +79,8 @@ namespace acl { PROP_POLICYTYPE, PROP_MAXQUEUESIZE, PROP_MAXQUEUECOUNT, + PROP_MAXFILESIZE, + PROP_MAXFILECOUNT, PROPERTYSIZE // PROPERTYSIZE must be last in list }; @@ -102,7 +104,11 @@ namespace acl { SPECPROP_MAXQUEUESIZELOWERLIMIT, SPECPROP_MAXQUEUESIZEUPPERLIMIT, SPECPROP_MAXQUEUECOUNTLOWERLIMIT, - SPECPROP_MAXQUEUECOUNTUPPERLIMIT }; + SPECPROP_MAXQUEUECOUNTUPPERLIMIT, + SPECPROP_MAXFILESIZELOWERLIMIT, + SPECPROP_MAXFILESIZEUPPERLIMIT, + SPECPROP_MAXFILECOUNTLOWERLIMIT, + SPECPROP_MAXFILECOUNTUPPERLIMIT }; // AclResult shared between ACL spec and ACL authorise interface enum AclResult { @@ -126,6 +132,8 @@ namespace broker { // doTransferAcl pervents time consuming ACL calls on a per-message basis. virtual bool doTransferAcl()=0; + virtual uint16_t getMaxConnectTotal()=0; + virtual bool authorise( const std::string& id, const acl::Action& action, @@ -147,9 +155,10 @@ namespace broker { */ virtual bool approveConnection (const Connection& connection)=0; - /** Change connection's counted userId + /** Approve queue creation by counting per-user. */ - virtual void setUserId(const Connection& connection, const std::string& username)=0; + virtual bool approveCreateQueue(const std::string& userId, const std::string& queueName)=0; + virtual void recordDestroyQueue(const std::string& queueName)=0; virtual ~AclModule() {}; }; @@ -222,6 +231,8 @@ namespace acl { if (str.compare("policytype") == 0) return PROP_POLICYTYPE; if (str.compare("maxqueuesize") == 0) return PROP_MAXQUEUESIZE; if (str.compare("maxqueuecount") == 0) return PROP_MAXQUEUECOUNT; + if (str.compare("maxfilesize") == 0) return PROP_MAXFILESIZE; + if (str.compare("maxfilecount") == 0) return PROP_MAXFILECOUNT; throw qpid::Exception(str); } static inline std::string getPropertyStr(const Property p) { @@ -240,6 +251,8 @@ namespace acl { case PROP_POLICYTYPE: return "policytype"; case PROP_MAXQUEUESIZE: return "maxqueuesize"; case PROP_MAXQUEUECOUNT: return "maxqueuecount"; + case PROP_MAXFILESIZE: return "maxfilesize"; + case PROP_MAXFILECOUNT: return "maxfilecount"; default: assert(false); // should never get here } return ""; @@ -261,6 +274,10 @@ namespace acl { if (str.compare("queuemaxsizeupperlimit") == 0) return SPECPROP_MAXQUEUESIZEUPPERLIMIT; if (str.compare("queuemaxcountlowerlimit") == 0) return SPECPROP_MAXQUEUECOUNTLOWERLIMIT; if (str.compare("queuemaxcountupperlimit") == 0) return SPECPROP_MAXQUEUECOUNTUPPERLIMIT; + if (str.compare("filemaxsizelowerlimit") == 0) return SPECPROP_MAXFILESIZELOWERLIMIT; + if (str.compare("filemaxsizeupperlimit") == 0) return SPECPROP_MAXFILESIZEUPPERLIMIT; + if (str.compare("filemaxcountlowerlimit") == 0) return SPECPROP_MAXFILECOUNTLOWERLIMIT; + if (str.compare("filemaxcountupperlimit") == 0) return SPECPROP_MAXFILECOUNTUPPERLIMIT; // Allow old names in ACL file as aliases for newly-named properties if (str.compare("maxqueuesize") == 0) return SPECPROP_MAXQUEUESIZEUPPERLIMIT; if (str.compare("maxqueuecount") == 0) return SPECPROP_MAXQUEUECOUNTUPPERLIMIT; @@ -284,6 +301,10 @@ namespace acl { case SPECPROP_MAXQUEUESIZEUPPERLIMIT: return "queuemaxsizeupperlimit"; case SPECPROP_MAXQUEUECOUNTLOWERLIMIT: return "queuemaxcountlowerlimit"; case SPECPROP_MAXQUEUECOUNTUPPERLIMIT: return "queuemaxcountupperlimit"; + case SPECPROP_MAXFILESIZELOWERLIMIT: return "filemaxsizelowerlimit"; + case SPECPROP_MAXFILESIZEUPPERLIMIT: return "filemaxsizeupperlimit"; + case SPECPROP_MAXFILECOUNTLOWERLIMIT: return "filemaxcountlowerlimit"; + case SPECPROP_MAXFILECOUNTUPPERLIMIT: return "filemaxcountupperlimit"; default: assert(false); // should never get here } return ""; diff --git a/cpp/src/qpid/broker/Bridge.cpp b/cpp/src/qpid/broker/Bridge.cpp index 4604ac643f..75aba70ae8 100644 --- a/cpp/src/qpid/broker/Bridge.cpp +++ b/cpp/src/qpid/broker/Bridge.cpp @@ -19,6 +19,8 @@ * */ #include "qpid/broker/Bridge.h" + +#include "qpid/broker/Broker.h" #include "qpid/broker/FedOps.h" #include "qpid/broker/ConnectionState.h" #include "qpid/broker/Connection.h" @@ -49,6 +51,11 @@ using qpid::management::ManagementAgent; using std::string; namespace _qmf = qmf::org::apache::qpid::broker; +namespace { +const std::string QPID_REPLICATE("qpid.replicate"); +const std::string NONE("none"); +} + namespace qpid { namespace broker { @@ -60,7 +67,7 @@ void Bridge::PushHandler::handle(framing::AMQFrame& frame) Bridge::Bridge(const std::string& _name, Link* _link, framing::ChannelId _id, CancellationListener l, const _qmf::ArgsLinkBridge& _args, InitializeCallback init, const std::string& _queueName, const string& ae) : - link(_link), channel(_id), args(_args), mgmtObject(0), + link(_link), channel(_id), args(_args), listener(l), name(_name), queueName(_queueName.empty() ? "qpid.bridge_queue_" + name + "_" + link->getBroker()->getFederationTag() : _queueName), @@ -71,10 +78,10 @@ Bridge::Bridge(const std::string& _name, Link* _link, framing::ChannelId _id, { ManagementAgent* agent = link->getBroker()->getManagementAgent(); if (agent != 0) { - mgmtObject = new _qmf::Bridge + mgmtObject = _qmf::Bridge::shared_ptr(new _qmf::Bridge (agent, this, link, name, args.i_durable, args.i_src, args.i_dest, args.i_key, args.i_srcIsQueue, args.i_srcIsLocal, - args.i_tag, args.i_excludes, args.i_dynamic, args.i_sync); + args.i_tag, args.i_excludes, args.i_dynamic, args.i_sync)); mgmtObject->set_channelId(channel); agent->addObject(mgmtObject); } @@ -121,28 +128,30 @@ void Bridge::create(Connection& c, AsyncStore* const store) peer->getMessage().flow(args.i_dest, 1, 0xFFFFFFFF); QPID_LOG(debug, "Activated bridge " << name << " for route from queue " << args.i_src << " to " << args.i_dest); } else { - FieldTable queueSettings; - - if (args.i_tag.size()) { - queueSettings.setString("qpid.trace.id", args.i_tag); - } else { - const string& peerTag = c.getFederationPeerTag(); - if (peerTag.size()) - queueSettings.setString("qpid.trace.id", peerTag); + if (!useExistingQueue) { + FieldTable queueSettings; + + if (args.i_tag.size()) { + queueSettings.setString("qpid.trace.id", args.i_tag); + } else { + const string& peerTag = c.getFederationPeerTag(); + if (peerTag.size()) + queueSettings.setString("qpid.trace.id", peerTag); + } + + if (args.i_excludes.size()) { + queueSettings.setString("qpid.trace.exclude", args.i_excludes); + } else { + const string& localTag = link->getBroker()->getFederationTag(); + if (localTag.size()) + queueSettings.setString("qpid.trace.exclude", localTag); + } + + bool durable = false;//should this be an arg, or would we use srcIsQueue for durable queues? + bool exclusive = true; // only exclusive if the queue is owned by the bridge + bool autoDelete = exclusive && !durable;//auto delete transient queues? + peer->getQueue().declare(queueName, altEx, false, durable, exclusive, autoDelete, queueSettings); } - - if (args.i_excludes.size()) { - queueSettings.setString("qpid.trace.exclude", args.i_excludes); - } else { - const string& localTag = link->getBroker()->getFederationTag(); - if (localTag.size()) - queueSettings.setString("qpid.trace.exclude", localTag); - } - - bool durable = false;//should this be an arg, or would we use srcIsQueue for durable queues? - bool exclusive = !useExistingQueue; // only exclusive if the queue is owned by the bridge - bool autoDelete = exclusive && !durable;//auto delete transient queues? - peer->getQueue().declare(queueName, altEx, false, durable, exclusive, autoDelete, queueSettings); if (!args.i_dynamic) peer->getExchange().bind(queueName, args.i_src, args.i_key, FieldTable()); peer->getMessage().subscribe(queueName, args.i_dest, (useExistingQueue && args.i_sync) ? 0 : 1, 0, false, "", 0, options); @@ -296,9 +305,9 @@ uint32_t Bridge::encodedSize() const + 2; // sync } -management::ManagementObject* Bridge::GetManagementObject (void) const +management::ManagementObject::shared_ptr Bridge::GetManagementObject(void) const { - return (management::ManagementObject*) mgmtObject; + return mgmtObject; } management::Manageable::status_t Bridge::ManagementMethod(uint32_t methodId, @@ -331,6 +340,7 @@ void Bridge::propagateBinding(const string& key, const string& tagList, } string newTagList(tagList + string(tagList.empty() ? "" : ",") + localTag); + bindArgs.setString(QPID_REPLICATE, NONE); bindArgs.setString(qpidFedOp, op); bindArgs.setString(qpidFedTags, newTagList); if (origin.empty()) @@ -366,8 +376,7 @@ void Bridge::ioThreadPropagateBinding(const string& queue, const string& exchang if (resetProxy()) { peer->getExchange().bind(queue, exchange, key, args); } else { - QPID_LOG(error, "Cannot propagate binding for dynamic bridge as session has been detached, deleting dynamic bridge"); - close(); + // link's periodic maintenance visit will attempt to recover } } diff --git a/cpp/src/qpid/broker/Bridge.h b/cpp/src/qpid/broker/Bridge.h index 04ac585d80..893febdf92 100644 --- a/cpp/src/qpid/broker/Bridge.h +++ b/cpp/src/qpid/broker/Bridge.h @@ -65,6 +65,7 @@ class Bridge : public PersistableConfig, QPID_BROKER_EXTERN void close(); bool isDurable() { return args.i_durable; } + framing::ChannelId getChannel() const { return channel; } Link *getLink() const { return link; } const std::string getSrc() const { return args.i_src; } const std::string getDest() const { return args.i_dest; } @@ -72,7 +73,7 @@ class Bridge : public PersistableConfig, bool isDetached() const { return detached; } - management::ManagementObject* GetManagementObject() const; + management::ManagementObject::shared_ptr GetManagementObject() const; management::Manageable::status_t ManagementMethod(uint32_t methodId, management::Args& args, std::string& text); @@ -128,7 +129,7 @@ class Bridge : public PersistableConfig, Link* const link; const framing::ChannelId channel; qmf::org::apache::qpid::broker::ArgsLinkBridge args; - qmf::org::apache::qpid::broker::Bridge* mgmtObject; + qmf::org::apache::qpid::broker::Bridge::shared_ptr mgmtObject; CancellationListener listener; std::string name; std::string queueName; diff --git a/cpp/src/qpid/broker/Broker.cpp b/cpp/src/qpid/broker/Broker.cpp index 08606516d4..49e80689fc 100644 --- a/cpp/src/qpid/broker/Broker.cpp +++ b/cpp/src/qpid/broker/Broker.cpp @@ -20,6 +20,8 @@ */ #include "qpid/broker/Broker.h" + +#include "qpid/broker/AclModule.h" #include "qpid/broker/AsyncResultHandle.h" #include "qpid/broker/ConfigAsyncContext.h" #include "qpid/broker/ConfigHandle.h" @@ -27,9 +29,7 @@ #include "qpid/broker/DirectExchange.h" #include "qpid/broker/FanOutExchange.h" #include "qpid/broker/HeadersExchange.h" -//#include "qpid/broker/MessageStoreModule.h" -//#include "qpid/broker/NullMessageStore.h" -//#include "qpid/broker/RecoveryAsyncContext.h" +#include "qpid/broker/NameGenerator.h" #include "qpid/broker/RecoveryManagerImpl.h" #include "qpid/broker/SaslAuthenticator.h" #include "qpid/broker/SecureConnectionFactory.h" @@ -43,6 +43,7 @@ #include "qpid/broker/MessageGroupManager.h" #include "qmf/org/apache/qpid/broker/Package.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerConnect.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerCreate.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerDelete.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerQuery.h" @@ -50,12 +51,12 @@ #include "qmf/org/apache/qpid/broker/ArgsBrokerGetLogLevel.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerQueueMoveMessages.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerSetLogLevel.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerGetLogHiresTimestamp.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerSetLogHiresTimestamp.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerSetTimestampConfig.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerGetTimestampConfig.h" #include "qmf/org/apache/qpid/broker/EventExchangeDeclare.h" #include "qmf/org/apache/qpid/broker/EventExchangeDelete.h" -#include "qmf/org/apache/qpid/broker/EventQueueDeclare.h" -#include "qmf/org/apache/qpid/broker/EventQueueDelete.h" #include "qmf/org/apache/qpid/broker/EventBind.h" #include "qmf/org/apache/qpid/broker/EventUnbind.h" #include "qpid/amqp_0_10/Codecs.h" @@ -75,6 +76,7 @@ #include "qpid/sys/Dispatcher.h" #include "qpid/sys/Thread.h" #include "qpid/sys/Time.h" +#include "qpid/sys/Timer.h" #include "qpid/sys/ConnectionInputHandler.h" #include "qpid/sys/ConnectionInputHandlerFactory.h" #include "qpid/sys/TimeoutHandler.h" @@ -110,6 +112,17 @@ namespace _qmf = qmf::org::apache::qpid::broker; namespace qpid { namespace broker { +const std::string empty; +const std::string amq_direct("amq.direct"); +const std::string amq_topic("amq.topic"); +const std::string amq_fanout("amq.fanout"); +const std::string amq_match("amq.match"); +const std::string qpid_management("qpid.management"); +const std::string knownHostsNone("none"); + +// static +Broker* Broker::thisBroker; + Broker::Options::Options(const std::string& name) : qpid::Options(name), noDataDir(0), @@ -127,6 +140,7 @@ Broker::Options::Options(const std::string& name) : queueLimit(100*1048576/*100M default limit*/), tcpNoDelay(false), requireEncrypted(false), + knownHosts(knownHostsNone), qmf2Support(true), qmf1Support(true), queueFlowStopRatio(80), @@ -136,7 +150,7 @@ Broker::Options::Options(const std::string& name) : timestampRcvMsgs(false), // set the 0.10 timestamp delivery property linkMaintenanceInterval(2), linkHeartbeatInterval(120), - maxNegotiateTime(2000) // 2s + maxNegotiateTime(10000) // 10s { int c = sys::SystemInfo::concurrency(); workerThreads=c+1; @@ -152,6 +166,7 @@ Broker::Options::Options(const std::string& name) : ("data-dir", optValue(dataDir,"DIR"), "Directory to contain persistent data generated by the broker") ("no-data-dir", optValue(noDataDir), "Don't use a data directory. No persistent configuration will be loaded or stored") ("port,p", optValue(port,"PORT"), "Tells the broker to listen on PORT") + ("interface", optValue(listenInterfaces, "<interface name>|<interface address>"), "Which network interfaces to use to listen for incoming connections") ("worker-threads", optValue(workerThreads, "N"), "Sets the broker thread pool size") ("connection-backlog", optValue(connectionBacklog, "N"), "Sets the connection backlog limit for the server socket") ("mgmt-enable,m", optValue(enableMgmt,"yes|no"), "Enable Management") @@ -175,23 +190,27 @@ Broker::Options::Options(const std::string& name) : ("default-event-threshold-ratio", optValue(queueThresholdEventRatio, "%age of limit"), "The ratio of any specified queue limit at which an event will be raised") ("default-message-group", optValue(defaultMsgGroup, "GROUP-IDENTIFER"), "Group identifier to assign to messages delivered to a message group queue that do not contain an identifier.") ("enable-timestamp", optValue(timestampRcvMsgs, "yes|no"), "Add current time to each received message.") - ("link-maintenace-interval", optValue(linkMaintenanceInterval, "SECONDS")) - ("link-heartbeat-interval", optValue(linkHeartbeatInterval, "SECONDS")) - ("max-negotiate-time", optValue(maxNegotiateTime, "MilliSeconds"), "Maximum time a connection can take to send the initial protocol negotiation") + ("link-maintenance-interval", optValue(linkMaintenanceInterval, "SECONDS"), + "Interval to check link health and re-connect if need be") + ("link-heartbeat-interval", optValue(linkHeartbeatInterval, "SECONDS"), + "Heartbeat interval for a federation link") + ("max-negotiate-time", optValue(maxNegotiateTime, "MILLISECONDS"), "Maximum time a connection can take to send the initial protocol negotiation") ("federation-tag", optValue(fedTag, "NAME"), "Override the federation tag") ; } -const std::string empty; -const std::string amq_direct("amq.direct"); -const std::string amq_topic("amq.topic"); -const std::string amq_fanout("amq.fanout"); -const std::string amq_match("amq.match"); -const std::string qpid_management("qpid.management"); -const std::string knownHostsNone("none"); +namespace { +// Arguments to declare a non-replicated exchange. +framing::FieldTable noReplicateArgs() { + framing::FieldTable args; + args.setString("qpid.replicate", "none"); + return args; +} +} Broker::Broker(const Broker::Options& conf) : poller(new Poller), + timer(new qpid::sys::Timer), config(conf), managementAgent(conf.enableMgmt ? new ManagementAgent(conf.qmf1Support, conf.qmf2Support) @@ -205,21 +224,18 @@ Broker::Broker(const Broker::Options& conf) : exchanges(this), links(this), factory(new SecureConnectionFactory(*this)), - dtxManager(timer), + dtxManager(*timer.get()), sessionManager( qpid::SessionState::Configuration( conf.replayFlushLimit*1024, // convert kb to bytes. conf.replayHardLimit*1024), *this), - mgmtObject(0), - queueCleaner(queues, &timer), - recovery(true), - inCluster(false), - clusterUpdatee(false), + queueCleaner(queues, timer.get()), + recoveryInProgress(false), expiryPolicy(new ExpiryPolicy), - getKnownBrokers(boost::bind(&Broker::getKnownBrokersImpl, this)), - deferDelivery(boost::bind(&Broker::deferDeliveryImpl, this, _1, _2)) + getKnownBrokers(boost::bind(&Broker::getKnownBrokersImpl, this)) { + thisBroker = this; try { if (conf.enableMgmt) { QPID_LOG(info, "Management enabled"); @@ -231,7 +247,7 @@ Broker::Broker(const Broker::Options& conf) : System* system = new System (dataDir.isEnabled() ? dataDir.getPath() : string(), this); systemObject = System::shared_ptr(system); - mgmtObject = new _qmf::Broker(managementAgent.get(), this, system, "amqp-broker"); + mgmtObject = _qmf::Broker::shared_ptr(new _qmf::Broker(managementAgent.get(), this, system, "amqp-broker")); mgmtObject->set_systemRef(system->GetManagementObject()->getObjectId()); mgmtObject->set_port(conf.port); mgmtObject->set_workerThreads(conf.workerThreads); @@ -281,28 +297,26 @@ Broker::Broker(const Broker::Options& conf) : // if (NullMessageStore::isNullStore(store.get())) // setStore(); - exchanges.declare(empty, DirectExchange::typeName); // Default exchange. + framing::FieldTable args; + + // Default exchnge is not replicated. + exchanges.declare(empty, DirectExchange::typeName, false, noReplicateArgs()); // if (store.get() != 0) { if (asyncStore.get() != 0) { - // The cluster plug-in will setRecovery(false) on all but the first - // broker to join a cluster. - if (getRecovery()) { QPID_LOG(info, "Store recovery starting") - RecoveryManagerImpl recoverer(queues, exchanges, links, dtxManager); + RecoveryManagerImpl recoverer(queues, exchanges, links, dtxManager, protocolRegistry); RecoveryHandle rh = asyncStore->createRecoveryHandle(); - boost::shared_ptr<RecoveryAsyncContext> rac(new RecoveryAsyncContext(recoverer, &recoverComplete, &asyncResultQueue)); + boost::shared_ptr<RecoveryAsyncContext> rac(new RecoveryAsyncContext(recoverer, &recoverCompleteCb, &asyncResultQueue)); asyncStore->submitRecover(rh, rac); -// store->recover(recoverer); - } - else { - QPID_LOG(notice, "Cluster recovery: recovered journal data discarded and journal files pushed down"); -// store->truncateInit(true); // save old files in subdir - asyncStore->initialize(true, true); - } +// RecoveryManagerImpl recoverer( +// queues, exchanges, links, dtxManager, protocolRegistry); +// recoveryInProgress = true; +// store->recover(recoverer); +// recoveryInProgress = false; } // debug - else QPID_LOG(info, ">>>> No store!!!!") +// else QPID_LOG(info, ">>>> No store!!!!") //ensure standard exchanges exist (done after recovery from store) declareStandardExchange(amq_direct, DirectExchange::typeName); @@ -311,7 +325,7 @@ Broker::Broker(const Broker::Options& conf) : declareStandardExchange(amq_match, HeadersExchange::typeName); if(conf.enableMgmt) { - exchanges.declare(qpid_management, ManagementTopicExchange::typeName); + exchanges.declare(qpid_management, ManagementTopicExchange::typeName, false, noReplicateArgs()); Exchange::shared_ptr mExchange = exchanges.get(qpid_management); Exchange::shared_ptr dExchange = exchanges.get(amq_direct); managementAgent->setExchange(mExchange, dExchange); @@ -320,8 +334,10 @@ Broker::Broker(const Broker::Options& conf) : std::string qmfTopic("qmf.default.topic"); std::string qmfDirect("qmf.default.direct"); - std::pair<Exchange::shared_ptr, bool> topicPair(exchanges.declare(qmfTopic, ManagementTopicExchange::typeName)); - std::pair<Exchange::shared_ptr, bool> directPair(exchanges.declare(qmfDirect, ManagementDirectExchange::typeName)); + std::pair<Exchange::shared_ptr, bool> topicPair( + exchanges.declare(qmfTopic, ManagementTopicExchange::typeName, false, noReplicateArgs())); + std::pair<Exchange::shared_ptr, bool> directPair( + exchanges.declare(qmfDirect, ManagementDirectExchange::typeName, false, noReplicateArgs())); boost::dynamic_pointer_cast<ManagementDirectExchange>(directPair.first)->setManagmentAgent(managementAgent.get(), 2); boost::dynamic_pointer_cast<ManagementTopicExchange>(topicPair.first)->setManagmentAgent(managementAgent.get(), 2); @@ -349,19 +365,17 @@ Broker::Broker(const Broker::Options& conf) : // Initialize plugins Plugin::initializeAll(*this); - if (managementAgent.get()) managementAgent->pluginsInitialized(); + if(conf.enableMgmt) { + if (getAcl()) { + mgmtObject->set_maxConns(getAcl()->getMaxConnectTotal()); + } + } if (conf.queueCleanInterval) { queueCleaner.start(conf.queueCleanInterval * qpid::sys::TIME_SEC); } - //initialize known broker urls (TODO: add support for urls for other transports (SSL, RDMA)): - if (conf.knownHosts.empty()) { - boost::shared_ptr<ProtocolFactory> factory = getProtocolFactory(TCP_TRANSPORT); - if (factory) { - knownBrokers.push_back ( qpid::Url::getIpAddressesUrl ( factory->getPort() ) ); - } - } else if (conf.knownHosts != knownHostsNone) { + if (!conf.knownHosts.empty() && conf.knownHosts != knownHostsNone) { knownBrokers.push_back(Url(conf.knownHosts)); } @@ -375,11 +389,14 @@ void Broker::declareStandardExchange(const std::string& name, const std::string& { // bool storeEnabled = store.get() != NULL; bool storeEnabled = asyncStore.get() != NULL; - std::pair<Exchange::shared_ptr, bool> status = exchanges.declare(name, type, storeEnabled); + framing::FieldTable args; + // Standard exchanges are not replicated. + std::pair<Exchange::shared_ptr, bool> status = + exchanges.declare(name, type, storeEnabled, noReplicateArgs()); if (status.second && storeEnabled) { // store->create(*status.first, framing::FieldTable ()); ConfigHandle ch = asyncStore->createConfigHandle(); - boost::shared_ptr<BrokerAsyncContext> bc(new ConfigAsyncContext(&configureComplete, &asyncResultQueue)); + boost::shared_ptr<BrokerAsyncContext> bc(new ConfigAsyncContext(&configureCompleteCb, &asyncResultQueue)); asyncStore->submitCreate(ch, status.first.get(), bc); } } @@ -420,11 +437,20 @@ void Broker::setStore () { } // static +void Broker::recoverCompleteCb(const AsyncResultHandle* const arh) { + thisBroker->recoverComplete(arh); +} + void Broker::recoverComplete(const AsyncResultHandle* const arh) { + recoveryInProgress = false; std::cout << "@@@@ Broker: Recover complete: err=" << arh->getErrNo() << "; msg=\"" << arh->getErrMsg() << "\"" << std::endl; } // static +void Broker::configureCompleteCb(const AsyncResultHandle* const arh) { + thisBroker->configureComplete(arh); +} + void Broker::configureComplete(const AsyncResultHandle* const arh) { std::cout << "@@@@ Broker: Configure complete: err=" << arh->getErrNo() << "; msg=\"" << arh->getErrMsg() << "\"" << std::endl; } @@ -464,13 +490,13 @@ Broker::~Broker() { finalize(); // Finalize any plugins. if (config.auth) SaslAuthenticator::fini(); - timer.stop(); + timer->stop(); QPID_LOG(notice, "Shut down"); } -ManagementObject* Broker::GetManagementObject(void) const +ManagementObject::shared_ptr Broker::GetManagementObject(void) const { - return (ManagementObject*) mgmtObject; + return mgmtObject; } Manageable* Broker::GetVhostObject(void) const @@ -536,7 +562,7 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, _qmf::ArgsBrokerQueueMoveMessages& moveArgs= dynamic_cast<_qmf::ArgsBrokerQueueMoveMessages&>(args); QPID_LOG (debug, "Broker::queueMoveMessages()"); - if (queueMoveMessages(moveArgs.i_srcQueue, moveArgs.i_destQueue, moveArgs.i_qty, moveArgs.i_filter)) + if (queueMoveMessages(moveArgs.i_srcQueue, moveArgs.i_destQueue, moveArgs.i_qty, moveArgs.i_filter) >= 0) status = Manageable::STATUS_OK; else return Manageable::STATUS_PARAMETER_INVALID; @@ -584,7 +610,22 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, status = setTimestampConfig(a.i_receive, getManagementExecutionContext()); break; } - default: + + case _qmf::Broker::METHOD_GETLOGHIRESTIMESTAMP: + { + dynamic_cast<_qmf::ArgsBrokerGetLogHiresTimestamp&>(args).o_logHires = getLogHiresTimestamp(); + QPID_LOG (debug, "Broker::getLogHiresTimestamp()"); + status = Manageable::STATUS_OK; + break; + } + case _qmf::Broker::METHOD_SETLOGHIRESTIMESTAMP: + { + setLogHiresTimestamp(dynamic_cast<_qmf::ArgsBrokerSetLogHiresTimestamp&>(args).i_logHires); + QPID_LOG (debug, "Broker::setLogHiresTimestamp()"); + status = Manageable::STATUS_OK; + break; + } + default: QPID_LOG (debug, "Broker ManagementMethod not implemented: id=" << methodId << "]"); status = Manageable::STATUS_NOT_IMPLEMENTED; break; @@ -750,7 +791,7 @@ void Broker::createObject(const std::string& type, const std::string& name, else extensions[i->first] = i->second; } framing::FieldTable arguments; - amqp_0_10::translate(extensions, arguments); + qpid::amqp_0_10::translate(extensions, arguments); try { std::pair<boost::shared_ptr<Exchange>, bool> result = @@ -772,7 +813,7 @@ void Broker::createObject(const std::string& type, const std::string& name, else extensions[i->first] = i->second; } framing::FieldTable arguments; - amqp_0_10::translate(extensions, arguments); + qpid::amqp_0_10::translate(extensions, arguments); bind(binding.queue, binding.exchange, binding.key, arguments, userId, connectionId); @@ -1008,6 +1049,18 @@ std::string Broker::getLogLevel() return level; } +void Broker::setLogHiresTimestamp(bool enabled) +{ + QPID_LOG(notice, "Changing log hires timestamp to " << enabled); + qpid::log::Logger::instance().setHiresTimestamp(enabled); +} + +bool Broker::getLogHiresTimestamp() +{ + return qpid::log::Logger::instance().getHiresTimestamp(); +} + + boost::shared_ptr<ProtocolFactory> Broker::getProtocolFactory(const std::string& name) const { ProtocolFactoryMap::const_iterator i = name.empty() ? protocolFactories.begin() : protocolFactories.find(name); @@ -1036,39 +1089,29 @@ void Broker::accept() { } void Broker::connect( + const std::string& name, const std::string& host, const std::string& port, const std::string& transport, - boost::function2<void, int, std::string> failed, - sys::ConnectionCodec::Factory* f) + boost::function2<void, int, std::string> failed) { boost::shared_ptr<ProtocolFactory> pf = getProtocolFactory(transport); - if (pf) pf->connect(poller, host, port, f ? f : factory.get(), failed); + if (pf) pf->connect(poller, name, host, port, factory.get(), failed); else throw NoSuchTransportException(QPID_MSG("Unsupported transport type: " << transport)); } -void Broker::connect( - const Url& url, - boost::function2<void, int, std::string> failed, - sys::ConnectionCodec::Factory* f) -{ - url.throwIfEmpty(); - const Address& addr=url[0]; - connect(addr.host, boost::lexical_cast<std::string>(addr.port), addr.protocol, failed, f); -} - -uint32_t Broker::queueMoveMessages( +int32_t Broker::queueMoveMessages( const std::string& srcQueue, const std::string& destQueue, uint32_t qty, const Variant::Map& filter) { - Queue::shared_ptr src_queue = queues.find(srcQueue); - if (!src_queue) - return 0; - Queue::shared_ptr dest_queue = queues.find(destQueue); - if (!dest_queue) - return 0; - - return src_queue->move(dest_queue, qty, &filter); + Queue::shared_ptr src_queue = queues.find(srcQueue); + if (!src_queue) + return -1; + Queue::shared_ptr dest_queue = queues.find(destQueue); + if (!dest_queue) + return -1; + + return (int32_t) src_queue->move(dest_queue, qty, &filter); } @@ -1083,12 +1126,6 @@ Broker::getKnownBrokersImpl() bool Broker::deferDeliveryImpl(const std::string&, const Message&) { return false; } -void Broker::setClusterTimer(std::auto_ptr<sys::Timer> t) { - clusterTimer = t; - queueCleaner.setTimer(clusterTimer.get()); - dtxManager.setTimer(*clusterTimer.get()); -} - const std::string Broker::TCP_TRANSPORT("tcp"); @@ -1109,9 +1146,14 @@ std::pair<boost::shared_ptr<Queue>, bool> Broker::createQueue( params.insert(make_pair(acl::PROP_POLICYTYPE, settings.dropMessagesAtLimit ? "ring" : "reject")); params.insert(make_pair(acl::PROP_MAXQUEUECOUNT, boost::lexical_cast<string>(settings.maxDepth.getCount()))); params.insert(make_pair(acl::PROP_MAXQUEUESIZE, boost::lexical_cast<string>(settings.maxDepth.getSize()))); + params.insert(make_pair(acl::PROP_MAXFILECOUNT, boost::lexical_cast<string>(settings.maxFileCount))); + params.insert(make_pair(acl::PROP_MAXFILESIZE, boost::lexical_cast<string>(settings.maxFileSize))); if (!acl->authorise(userId,acl::ACT_CREATE,acl::OBJ_QUEUE,name,¶ms) ) throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << userId)); + + if (!acl->approveCreateQueue(userId,name) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << userId)); } Exchange::shared_ptr alternate; @@ -1120,21 +1162,12 @@ std::pair<boost::shared_ptr<Queue>, bool> Broker::createQueue( if (!alternate) throw framing::NotFoundException(QPID_MSG("Alternate exchange does not exist: " << alternateExchange)); } - std::pair<Queue::shared_ptr, bool> result = queues.declare(name, settings, alternate); + std::pair<Queue::shared_ptr, bool> result = + queues.declare(name, settings, alternate, false/*recovering*/, + owner, connectionId, userId); if (result.second) { //add default binding: result.first->bind(exchanges.getDefault(), name, qpid::framing::FieldTable()); - - if (managementAgent.get()) { - //TODO: debatable whether we should raise an event here for - //create when this is a 'declare' event; ideally add a create - //event instead? - managementAgent->raiseEvent( - _qmf::EventQueueDeclare(connectionId, userId, name, - settings.durable, owner, settings.autodelete, alternateExchange, - settings.asMap(), - "created")); - } QPID_LOG_CAT(debug, model, "Create queue. name:" << name << " user:" << userId << " rhost:" << connectionId @@ -1149,6 +1182,10 @@ std::pair<boost::shared_ptr<Queue>, bool> Broker::createQueue( void Broker::deleteQueue(const std::string& name, const std::string& userId, const std::string& connectionId, QueueFunctor check) { + QPID_LOG_CAT(debug, model, "Deleting queue. name:" << name + << " user:" << userId + << " rhost:" << connectionId + ); if (acl && !acl->authorise(userId,acl::ACT_DELETE,acl::OBJ_QUEUE,name,NULL)) { throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue delete request from " << userId)); } @@ -1156,19 +1193,13 @@ void Broker::deleteQueue(const std::string& name, const std::string& userId, Queue::shared_ptr queue = queues.find(name); if (queue) { if (check) check(queue); - queues.destroy(name); + if (acl) + acl->recordDestroyQueue(name); + queues.destroy(name, connectionId, userId); queue->destroyed(); } else { throw framing::NotFoundException(QPID_MSG("Delete failed. No such queue: " << name)); } - - if (managementAgent.get()) - managementAgent->raiseEvent(_qmf::EventQueueDelete(connectionId, userId, name)); - QPID_LOG_CAT(debug, model, "Delete queue. name:" << name - << " user:" << userId - << " rhost:" << connectionId - ); - } std::pair<Exchange::shared_ptr, bool> Broker::createExchange( @@ -1196,33 +1227,16 @@ std::pair<Exchange::shared_ptr, bool> Broker::createExchange( } std::pair<Exchange::shared_ptr, bool> result; - result = exchanges.declare(name, type, durable, arguments); + result = exchanges.declare( + name, type, durable, arguments, alternate, connectionId, userId); if (result.second) { - if (alternate) { - result.first->setAlternate(alternate); - alternate->incAlternateUsers(); - } if (durable) { // store->create(*result.first, arguments); ConfigHandle ch = asyncStore->createConfigHandle(); result.first->setHandle(ch); - boost::shared_ptr<BrokerAsyncContext> bc(new ConfigAsyncContext(&configureComplete, &asyncResultQueue)); + boost::shared_ptr<BrokerAsyncContext> bc(new ConfigAsyncContext(&configureCompleteCb, &asyncResultQueue)); asyncStore->submitCreate(ch, result.first.get(), bc); } - if (managementAgent.get()) { - //TODO: debatable whether we should raise an event here for - //create when this is a 'declare' event; ideally add a create - //event instead? - managementAgent->raiseEvent(_qmf::EventExchangeDeclare(connectionId, - userId, - name, - type, - alternateExchange, - durable, - false, - ManagementAgent::toMap(arguments), - "created")); - } QPID_LOG_CAT(debug, model, "Create exchange. name:" << name << " user:" << userId << " rhost:" << connectionId @@ -1236,6 +1250,9 @@ std::pair<Exchange::shared_ptr, bool> Broker::createExchange( void Broker::deleteExchange(const std::string& name, const std::string& userId, const std::string& connectionId) { + QPID_LOG_CAT(debug, model, "Deleting exchange. name:" << name + << " user:" << userId + << " rhost:" << connectionId); if (acl) { if (!acl->authorise(userId,acl::ACT_DELETE,acl::OBJ_EXCHANGE,name,NULL) ) throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange delete request from " << userId)); @@ -1246,21 +1263,15 @@ void Broker::deleteExchange(const std::string& name, const std::string& userId, } Exchange::shared_ptr exchange(exchanges.get(name)); if (!exchange) throw framing::NotFoundException(QPID_MSG("Delete failed. No such exchange: " << name)); - if (exchange->inUseAsAlternate()) throw framing::NotAllowedException(QPID_MSG("Exchange in use as alternate-exchange.")); + if (exchange->inUseAsAlternate()) throw framing::NotAllowedException(QPID_MSG("Cannot delete " << name <<", in use as alternate-exchange.")); // if (exchange->isDurable()) store->destroy(*exchange); if (exchange->isDurable()) { - boost::shared_ptr<BrokerAsyncContext> bc(new ConfigAsyncContext(&configureComplete, &asyncResultQueue)); + boost::shared_ptr<BrokerAsyncContext> bc(new ConfigAsyncContext(&configureCompleteCb, &asyncResultQueue)); asyncStore->submitDestroy(exchange->getHandle(), bc); exchange->resetHandle(); } if (exchange->getAlternate()) exchange->getAlternate()->decAlternateUsers(); - exchanges.destroy(name); - - if (managementAgent.get()) - managementAgent->raiseEvent(_qmf::EventExchangeDelete(connectionId, userId, name)); - QPID_LOG_CAT(debug, model, "Delete exchange. name:" << name - << " user:" << userId - << " rhost:" << connectionId); + exchanges.destroy(name, connectionId, userId); } void Broker::bind(const std::string& queueName, @@ -1298,6 +1309,7 @@ void Broker::bind(const std::string& queueName, QPID_LOG_CAT(debug, model, "Create binding. exchange:" << exchangeName << " queue:" << queueName << " key:" << key + << " arguments:" << arguments << " user:" << userId << " rhost:" << connectionId); } diff --git a/cpp/src/qpid/broker/Broker.h b/cpp/src/qpid/broker/Broker.h index 698d446bca..468da8983a 100644 --- a/cpp/src/qpid/broker/Broker.h +++ b/cpp/src/qpid/broker/Broker.h @@ -25,40 +25,30 @@ #include "qpid/broker/AsyncResultQueueImpl.h" #include "qpid/broker/AsyncStore.h" #include "qpid/broker/BrokerImportExport.h" -#include "qpid/broker/ConnectionFactory.h" -#include "qpid/broker/ConnectionToken.h" -#include "qpid/broker/DirectExchange.h" + +#include "qpid/DataDir.h" +#include "qpid/Options.h" +#include "qpid/Plugin.h" #include "qpid/broker/DtxManager.h" #include "qpid/broker/ExchangeRegistry.h" //#include "qpid/broker/MessageStore.h" +#include "qpid/broker/Protocol.h" #include "qpid/broker/QueueRegistry.h" #include "qpid/broker/LinkRegistry.h" #include "qpid/broker/SessionManager.h" #include "qpid/broker/QueueCleaner.h" #include "qpid/broker/Vhost.h" #include "qpid/broker/System.h" -#include "qpid/broker/ExpiryPolicy.h" #include "qpid/broker/ConsumerFactory.h" #include "qpid/broker/ConnectionObservers.h" #include "qpid/broker/ConfigurationObservers.h" #include "qpid/management/Manageable.h" -#include "qpid/management/ManagementAgent.h" -#include "qmf/org/apache/qpid/broker/Broker.h" -#include "qmf/org/apache/qpid/broker/ArgsBrokerConnect.h" -#include "qpid/Options.h" -#include "qpid/Plugin.h" -#include "qpid/DataDir.h" -#include "qpid/framing/FrameHandler.h" -#include "qpid/framing/OutputHandler.h" -#include "qpid/framing/ProtocolInitiation.h" -#include "qpid/sys/Runnable.h" -#include "qpid/sys/Timer.h" -#include "qpid/types/Variant.h" -#include "qpid/RefCounted.h" -#include "qpid/broker/AclModule.h" +#include "qpid/sys/ConnectionCodec.h" #include "qpid/sys/Mutex.h" +#include "qpid/sys/Runnable.h" #include <boost/intrusive_ptr.hpp> + #include <string> #include <vector> @@ -67,12 +57,14 @@ namespace qpid { namespace sys { class ProtocolFactory; class Poller; +class Timer; } struct Url; namespace broker { +class AclModule; class ConnectionState; class ExpiryPolicy; class Message; @@ -103,6 +95,7 @@ class Broker : public sys::Runnable, public Plugin::Target, bool noDataDir; std::string dataDir; uint16_t port; + std::vector<std::string> listenInterfaces; int workerThreads; int connectionBacklog; bool enableMgmt; @@ -139,10 +132,15 @@ class Broker : public sys::Runnable, public Plugin::Target, void declareStandardExchange(const std::string& name, const std::string& type); void setStore (); - static void recoverComplete(const AsyncResultHandle* const); - static void configureComplete(const AsyncResultHandle* const); + static void recoverCompleteCb(const AsyncResultHandle* const); + static void configureCompleteCb(const AsyncResultHandle* const); + static Broker* thisBroker; + void recoverComplete(const AsyncResultHandle* const arh); + void configureComplete(const AsyncResultHandle* const arh); void setLogLevel(const std::string& level); std::string getLogLevel(); + void setLogHiresTimestamp(bool enabled); + bool getLogHiresTimestamp(); void createObject(const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties, bool strict, const ConnectionState* context); void deleteObject(const std::string& type, const std::string& name, @@ -158,8 +156,7 @@ class Broker : public sys::Runnable, public Plugin::Target, Manageable::status_t setTimestampConfig(const bool receive, const ConnectionState* context); boost::shared_ptr<sys::Poller> poller; - sys::Timer timer; - std::auto_ptr<sys::Timer> clusterTimer; + std::auto_ptr<sys::Timer> timer; Options config; std::auto_ptr<management::ManagementAgent> managementAgent; ProtocolFactoryMap protocolFactories; @@ -178,7 +175,7 @@ class Broker : public sys::Runnable, public Plugin::Target, boost::shared_ptr<sys::ConnectionCodec::Factory> factory; DtxManager dtxManager; SessionManager sessionManager; - qmf::org::apache::qpid::broker::Broker* mgmtObject; + qmf::org::apache::qpid::broker::Broker::shared_ptr mgmtObject; Vhost::shared_ptr vhostObject; System::shared_ptr systemObject; QueueCleaner queueCleaner; @@ -187,10 +184,10 @@ class Broker : public sys::Runnable, public Plugin::Target, bool deferDeliveryImpl(const std::string& queue, const Message& msg); std::string federationTag; - bool recovery; - bool inCluster, clusterUpdatee; + bool recoveryInProgress; boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; ConsumerFactories consumerFactories; + ProtocolRegistry protocolRegistry; mutable sys::Mutex linkClientPropertiesLock; framing::FieldTable linkClientProperties; @@ -233,6 +230,7 @@ class Broker : public sys::Runnable, public Plugin::Target, DataDir& getDataDir() { return dataDir; } Options& getOptions() { return config; } AsyncResultQueueImpl& getAsyncResultQueue() { return asyncResultQueue; } + ProtocolRegistry& getProtocolRegistry() { return protocolRegistry; } void setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e) { expiryPolicy = e; } boost::intrusive_ptr<ExpiryPolicy> getExpiryPolicy() { return expiryPolicy; } @@ -240,7 +238,7 @@ class Broker : public sys::Runnable, public Plugin::Target, SessionManager& getSessionManager() { return sessionManager; } const std::string& getFederationTag() const { return federationTag; } - QPID_BROKER_EXTERN management::ManagementObject* GetManagementObject() const; + QPID_BROKER_EXTERN management::ManagementObject::shared_ptr GetManagementObject() const; QPID_BROKER_EXTERN management::Manageable* GetVhostObject() const; QPID_BROKER_EXTERN management::Manageable::status_t ManagementMethod( uint32_t methodId, management::Args& args, std::string& text); @@ -253,19 +251,17 @@ class Broker : public sys::Runnable, public Plugin::Target, QPID_BROKER_EXTERN void accept(); /** Create a connection to another broker. */ - void connect(const std::string& host, const std::string& port, + void connect(const std::string& name, + const std::string& host, const std::string& port, const std::string& transport, - boost::function2<void, int, std::string> failed, - sys::ConnectionCodec::Factory* =0); - /** Create a connection to another broker. */ - void connect(const Url& url, - boost::function2<void, int, std::string> failed, - sys::ConnectionCodec::Factory* =0); + boost::function2<void, int, std::string> failed); /** Move messages from one queue to another. A zero quantity means to move all messages + Return -1 if one of the queues does not exist, otherwise + the number of messages moved. */ - QPID_BROKER_EXTERN uint32_t queueMoveMessages( + QPID_BROKER_EXTERN int32_t queueMoveMessages( const std::string& srcQueue, const std::string& destQueue, uint32_t qty, @@ -277,46 +273,17 @@ class Broker : public sys::Runnable, public Plugin::Target, /** Expose poller so plugins can register their descriptors. */ QPID_BROKER_EXTERN boost::shared_ptr<sys::Poller> getPoller(); - boost::shared_ptr<sys::ConnectionCodec::Factory> getConnectionFactory() { return factory; } - void setConnectionFactory(boost::shared_ptr<sys::ConnectionCodec::Factory> f) { factory = f; } - /** Timer for local tasks affecting only this broker */ - sys::Timer& getTimer() { return timer; } - - /** Timer for tasks that must be synchronized if we are in a cluster */ - sys::Timer& getClusterTimer() { return clusterTimer.get() ? *clusterTimer : timer; } - QPID_BROKER_EXTERN void setClusterTimer(std::auto_ptr<sys::Timer>); + sys::Timer& getTimer() { return *timer; } boost::function<std::vector<Url> ()> getKnownBrokers; static QPID_BROKER_EXTERN const std::string TCP_TRANSPORT; - void setRecovery(bool set) { recovery = set; } - bool getRecovery() const { return recovery; } - - /** True of this broker is part of a cluster. - * Only valid after early initialization of plugins is complete. - */ - bool isInCluster() const { return inCluster; } - void setInCluster(bool set) { inCluster = set; } - - /** True if this broker is joining a cluster and in the process of - * receiving a state update. - */ - bool isClusterUpdatee() const { return clusterUpdatee; } - void setClusterUpdatee(bool set) { clusterUpdatee = set; } + bool inRecovery() const { return recoveryInProgress; } management::ManagementAgent* getManagementAgent() { return managementAgent.get(); } - /** - * Never true in a stand-alone broker. In a cluster, return true - * to defer delivery of messages deliveredg in a cluster-unsafe - * context. - *@return true if delivery of a message should be deferred. - */ - boost::function<bool (const std::string& queue, - const Message& msg)> deferDelivery; - bool isAuthenticating ( ) { return config.auth; } bool isTimestamping() { return config.timestampRcvMsgs; } @@ -371,8 +338,10 @@ class Broker : public sys::Runnable, public Plugin::Target, QPID_BROKER_EXTERN framing::FieldTable getLinkClientProperties() const; QPID_BROKER_EXTERN void setLinkClientProperties(const framing::FieldTable&); + QPID_BROKER_EXTERN uint16_t getLinkHearbeatInterval() { return config.linkHeartbeatInterval; } /** Information identifying this system */ boost::shared_ptr<const System> getSystem() const { return systemObject; } + friend class StatusCheckThread; }; }} diff --git a/cpp/src/qpid/broker/ConfigurationObserver.h b/cpp/src/qpid/broker/ConfigurationObserver.h index 701043db40..789490e08c 100644 --- a/cpp/src/qpid/broker/ConfigurationObserver.h +++ b/cpp/src/qpid/broker/ConfigurationObserver.h @@ -38,6 +38,10 @@ class Exchange; /** * Observer for changes to configuration (aka wiring) + * + * NOTE: create and destroy functions are called with + * the registry lock held. This is necessary to ensure + * they are called in the correct sequence. */ class ConfigurationObserver { diff --git a/cpp/src/qpid/broker/Connection.cpp b/cpp/src/qpid/broker/Connection.cpp index e68c906cc2..df1a23f882 100644 --- a/cpp/src/qpid/broker/Connection.cpp +++ b/cpp/src/qpid/broker/Connection.cpp @@ -25,9 +25,9 @@ #include "qpid/broker/Bridge.h" #include "qpid/broker/Broker.h" #include "qpid/broker/Queue.h" -#include "qpid/broker/AclModule.h" +#include "qpid/management/ManagementAgent.h" #include "qpid/sys/SecuritySettings.h" -#include "qpid/sys/ClusterSafe.h" +#include "qpid/sys/Timer.h" #include "qpid/log/Statement.h" #include "qpid/ptr_map.h" @@ -86,20 +86,14 @@ Connection::Connection(ConnectionOutputHandler* out_, std::string& mgmtId_, const qpid::sys::SecuritySettings& external, bool link_, - uint64_t objectId_, - bool shadow_, - bool delayManagement, - bool authenticated_ + uint64_t objectId_ ) : ConnectionState(out_, broker_), securitySettings(external), - shadow(shadow_), - authenticated(authenticated_), adapter(*this, link_), link(link_), mgmtClosing(false), mgmtId(mgmtId_), - mgmtObject(0), links(broker_.getLinks()), agent(0), timer(broker_.getTimer()), @@ -109,11 +103,6 @@ Connection::Connection(ConnectionOutputHandler* out_, { outboundTracker.wrap(out); broker.getConnectionObservers().connection(*this); - // In a cluster, allow adding the management object to be delayed. - if (!delayManagement) addManagementObject(); -} - -void Connection::addManagementObject() { assert(agent == 0); assert(mgmtObject == 0); Manageable* parent = broker.GetVhostObject(); @@ -121,8 +110,7 @@ void Connection::addManagementObject() { agent = broker.getManagementAgent(); if (agent != 0) { // TODO set last bool true if system connection - mgmtObject = new _qmf::Connection(agent, this, parent, mgmtId, !link, false); - mgmtObject->set_shadow(shadow); + mgmtObject = _qmf::Connection::shared_ptr(new _qmf::Connection(agent, this, parent, mgmtId, !link, false, "AMQP 0-10")); agent->addObject(mgmtObject, objectId); } ConnectionState::setUrl(mgmtId); @@ -139,13 +127,11 @@ void Connection::requestIOProcessing(boost::function0<void> callback) Connection::~Connection() { if (mgmtObject != 0) { - mgmtObject->resourceDestroy(); - // In a cluster, Connections destroyed during shutdown are in - // a cluster-unsafe context. Don't raise an event in that case. - if (!link && isClusterSafe()) - agent->raiseEvent(_qmf::EventClientDisconnect(mgmtId, ConnectionState::getUserId())); + if (!link) + agent->raiseEvent(_qmf::EventClientDisconnect(mgmtId, ConnectionState::getUserId(), mgmtObject->get_remoteProperties())); QPID_LOG_CAT(debug, model, "Delete connection. user:" << ConnectionState::getUserId() << " rhost:" << mgmtId ); + mgmtObject->resourceDestroy(); } broker.getConnectionObservers().closed(*this); @@ -188,8 +174,7 @@ bool isMessage(const AMQMethodBody* method) void Connection::recordFromServer(const framing::AMQFrame& frame) { - // Don't record management stats in cluster-unsafe contexts - if (mgmtObject != 0 && isClusterSafe()) + if (mgmtObject != 0) { qmf::org::apache::qpid::broker::Connection::PerThreadStats *cStats = mgmtObject->getStatistics(); cStats->framesToClient += 1; @@ -203,8 +188,7 @@ void Connection::recordFromServer(const framing::AMQFrame& frame) void Connection::recordFromClient(const framing::AMQFrame& frame) { - // Don't record management stats in cluster-unsafe contexts - if (mgmtObject != 0 && isClusterSafe()) + if (mgmtObject != 0) { qmf::org::apache::qpid::broker::Connection::PerThreadStats *cStats = mgmtObject->getStatistics(); cStats->framesFromClient += 1; @@ -279,28 +263,7 @@ void Connection::notifyConnectionForced(const string& text) void Connection::setUserId(const string& userId) { - // Account for changing userId - AclModule* acl = broker.getAcl(); - if (acl) - { - acl->setUserId(*this, userId); - } - ConnectionState::setUserId(userId); - // In a cluster, the cluster code will raise the connect event - // when the connection is replicated to the cluster. - if (!broker.isInCluster()) raiseConnectEvent(); -} - -void Connection::raiseConnectEvent() { - if (mgmtObject != 0) { - mgmtObject->set_authIdentity(userId); - agent->raiseEvent(_qmf::EventClientConnect(mgmtId, userId)); - } - - QPID_LOG_CAT(debug, model, "Create connection. user:" << userId - << " rhost:" << mgmtId ); - } void Connection::setUserProxyAuth(bool b) @@ -327,19 +290,6 @@ void Connection::close(connection::CloseCode code, const string& text) getOutput().close(); } -// Send a close to the client but keep the channels. Used by cluster. -void Connection::sendClose() { - if (heartbeatTimer) - heartbeatTimer->cancel(); - if (timeoutTimer) - timeoutTimer->cancel(); - if (linkHeartbeatTimer) { - linkHeartbeatTimer->cancel(); - } - adapter.close(connection::CLOSE_CODE_NORMAL, "OK"); - getOutput().close(); -} - void Connection::idleOut(){} void Connection::idleIn(){} @@ -364,9 +314,6 @@ void Connection::closed(){ // Physically closed, suspend open sessions. void Connection::doIoCallbacks() { if (!isOpen()) return; // Don't process IO callbacks until we are open. ScopedLock<Mutex> l(ioCallbackLock); - // Although IO callbacks execute in the connection thread context, they are - // not cluster safe because they are queued for execution in non-IO threads. - ClusterUnsafeScope cus; while (!ioCallbacks.empty()) { boost::function0<void> cb = ioCallbacks.front(); ioCallbacks.pop(); @@ -413,9 +360,9 @@ SessionHandler& Connection::getChannel(ChannelId id) { return *ptr_map_ptr(i); } -ManagementObject* Connection::GetManagementObject(void) const +ManagementObject::shared_ptr Connection::GetManagementObject(void) const { - return (ManagementObject*) mgmtObject; + return mgmtObject; } Manageable::status_t Connection::ManagementMethod(uint32_t methodId, Args&, string&) @@ -499,7 +446,7 @@ void Connection::abort() void Connection::setHeartbeatInterval(uint16_t heartbeat) { setHeartbeat(heartbeat); - if (heartbeat > 0 && !isShadow()) { + if (heartbeat > 0) { if (!heartbeatTimer) { heartbeatTimer = new ConnectionHeartbeatTask(heartbeat, timer, *this); timer.add(heartbeatTimer); @@ -535,7 +482,6 @@ void Connection::OutboundFrameTracker::close() { next->close(); } size_t Connection::OutboundFrameTracker::getBuffered() const { return next->getBuffered(); } void Connection::OutboundFrameTracker::abort() { next->abort(); } void Connection::OutboundFrameTracker::activateOutput() { next->activateOutput(); } -void Connection::OutboundFrameTracker::giveReadCredit(int32_t credit) { next->giveReadCredit(credit); } void Connection::OutboundFrameTracker::send(framing::AMQFrame& f) { next->send(f); diff --git a/cpp/src/qpid/broker/Connection.h b/cpp/src/qpid/broker/Connection.h index d01599ce54..5c4d7132be 100644 --- a/cpp/src/qpid/broker/Connection.h +++ b/cpp/src/qpid/broker/Connection.h @@ -30,24 +30,13 @@ #include "qpid/broker/BrokerImportExport.h" #include "qpid/broker/ConnectionHandler.h" #include "qpid/broker/ConnectionState.h" -#include "qpid/broker/SessionHandler.h" -#include "qmf/org/apache/qpid/broker/Connection.h" -#include "qpid/Exception.h" -#include "qpid/RefCounted.h" -#include "qpid/framing/AMQFrame.h" -#include "qpid/framing/AMQP_ClientProxy.h" -#include "qpid/framing/AMQP_ServerOperations.h" -#include "qpid/framing/ProtocolVersion.h" -#include "qpid/management/ManagementAgent.h" -#include "qpid/management/Manageable.h" -#include "qpid/ptr_map.h" -#include "qpid/sys/AggregateOutput.h" #include "qpid/sys/ConnectionInputHandler.h" -#include "qpid/sys/ConnectionOutputHandler.h" #include "qpid/sys/SecuritySettings.h" -#include "qpid/sys/Socket.h" -#include "qpid/sys/TimeoutHandler.h" #include "qpid/sys/Mutex.h" +#include "qpid/RefCounted.h" +#include "qpid/ptr_map.h" + +#include "qmf/org/apache/qpid/broker/Connection.h" #include <boost/ptr_container/ptr_map.hpp> #include <boost/bind.hpp> @@ -55,11 +44,17 @@ #include <algorithm> namespace qpid { +namespace sys { +class Timer; +class TimerTask; +} namespace broker { class Broker; class LinkRegistry; +class Queue; class SecureConnection; +class SessionHandler; struct ConnectionTimeoutTask; class Connection : public sys::ConnectionInputHandler, @@ -83,10 +78,7 @@ class Connection : public sys::ConnectionInputHandler, const std::string& mgmtId, const qpid::sys::SecuritySettings&, bool isLink = false, - uint64_t objectId = 0, - bool shadow=false, - bool delayManagement = false, - bool authenticated=true); + uint64_t objectId = 0); ~Connection (); @@ -112,7 +104,7 @@ class Connection : public sys::ConnectionInputHandler, void closeChannel(framing::ChannelId channel); // Manageable entry points - management::ManagementObject* GetManagementObject (void) const; + management::ManagementObject::shared_ptr GetManagementObject(void) const; management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string&); @@ -130,7 +122,6 @@ class Connection : public sys::ConnectionInputHandler, void notifyConnectionForced(const std::string& text); void setUserId(const std::string& uid); - void raiseConnectEvent(); // credentials for connected client const std::string& getUserId() const { return ConnectionState::getUserId(); } @@ -144,27 +135,14 @@ class Connection : public sys::ConnectionInputHandler, void setHeartbeatInterval(uint16_t heartbeat); void sendHeartbeat(); void restartTimeout(); - + template <class F> void eachSessionHandler(F f) { for (ChannelMap::iterator i = channels.begin(); i != channels.end(); ++i) f(*ptr_map_ptr(i)); } - void sendClose(); void setSecureConnection(SecureConnection* secured); - /** True if this is a shadow connection in a cluster. */ - bool isShadow() const { return shadow; } - - /** True if this connection is authenticated */ - bool isAuthenticated() const { return authenticated; } - - // Used by cluster to update connection status - sys::AggregateOutput& getOutputTasks() { return outputTasks; } - - /** Cluster delays adding management object in the constructor then calls this. */ - void addManagementObject(); - const qpid::sys::SecuritySettings& getExternalSecuritySettings() const { return securitySettings; @@ -176,9 +154,6 @@ class Connection : public sys::ConnectionInputHandler, bool isLink() { return link; } void startLinkHeartbeatTimeoutTask(); - // Used by cluster during catch-up, see cluster::OutputInterceptor - void doIoCallbacks(); - void setClientProperties(const framing::FieldTable& cp) { clientProperties = cp; } const framing::FieldTable& getClientProperties() const { return clientProperties; } @@ -188,15 +163,13 @@ class Connection : public sys::ConnectionInputHandler, ChannelMap channels; qpid::sys::SecuritySettings securitySettings; - bool shadow; - bool authenticated; ConnectionHandler adapter; const bool link; bool mgmtClosing; const std::string mgmtId; sys::Mutex ioCallbackLock; std::queue<boost::function0<void> > ioCallbacks; - qmf::org::apache::qpid::broker::Connection* mgmtObject; + qmf::org::apache::qpid::broker::Connection::shared_ptr mgmtObject; LinkRegistry& links; management::ManagementAgent* agent; sys::Timer& timer; @@ -218,7 +191,6 @@ class Connection : public sys::ConnectionInputHandler, size_t getBuffered() const; void abort(); void activateOutput(); - void giveReadCredit(int32_t credit); void send(framing::AMQFrame&); void wrap(sys::ConnectionOutputHandlerPtr&); private: @@ -228,10 +200,11 @@ class Connection : public sys::ConnectionInputHandler, OutboundFrameTracker outboundTracker; void sent(const framing::AMQFrame& f); + void doIoCallbacks(); public: - qmf::org::apache::qpid::broker::Connection* getMgmtObject() { return mgmtObject; } + qmf::org::apache::qpid::broker::Connection::shared_ptr getMgmtObject() { return mgmtObject; } }; }} diff --git a/cpp/src/qpid/broker/ConnectionFactory.cpp b/cpp/src/qpid/broker/ConnectionFactory.cpp deleted file mode 100644 index d5d24ca629..0000000000 --- a/cpp/src/qpid/broker/ConnectionFactory.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -#include "qpid/broker/ConnectionFactory.h" -#include "qpid/framing/ProtocolVersion.h" -#include "qpid/amqp_0_10/Connection.h" -#include "qpid/broker/Connection.h" -#include "qpid/sys/SecuritySettings.h" -#include "qpid/log/Statement.h" - -namespace qpid { -namespace broker { - -using framing::ProtocolVersion; -using qpid::sys::SecuritySettings; -typedef std::auto_ptr<amqp_0_10::Connection> ConnectionPtr; -typedef std::auto_ptr<sys::ConnectionInputHandler> InputPtr; - -ConnectionFactory::ConnectionFactory(Broker& b) : broker(b) {} - -ConnectionFactory::~ConnectionFactory() {} - -sys::ConnectionCodec* -ConnectionFactory::create(ProtocolVersion v, sys::OutputControl& out, const std::string& id, - const SecuritySettings& external) { - if (v == ProtocolVersion(0, 10)) { - ConnectionPtr c(new amqp_0_10::Connection(out, id, false)); - c->setInputHandler(InputPtr(new broker::Connection(c.get(), broker, id, external, false))); - return c.release(); - } - return 0; -} - -sys::ConnectionCodec* -ConnectionFactory::create(sys::OutputControl& out, const std::string& id, - const SecuritySettings& external) { - // used to create connections from one broker to another - ConnectionPtr c(new amqp_0_10::Connection(out, id, true)); - c->setInputHandler(InputPtr(new broker::Connection(c.get(), broker, id, external, true))); - return c.release(); -} - - -}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/ConnectionHandler.cpp b/cpp/src/qpid/broker/ConnectionHandler.cpp index 06f442a47f..977c706ebd 100644 --- a/cpp/src/qpid/broker/ConnectionHandler.cpp +++ b/cpp/src/qpid/broker/ConnectionHandler.cpp @@ -20,8 +20,10 @@ * */ -#include "qpid/SaslFactory.h" #include "qpid/broker/ConnectionHandler.h" + +#include "qpid/SaslFactory.h" +#include "qpid/broker/Broker.h" #include "qpid/broker/Connection.h" #include "qpid/broker/SecureConnection.h" #include "qpid/Url.h" @@ -30,8 +32,10 @@ #include "qpid/framing/enum.h" #include "qpid/framing/FieldValue.h" #include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" #include "qpid/sys/SecurityLayer.h" #include "qpid/broker/AclModule.h" +#include "qpid/amqp_0_10/Codecs.h" #include "qmf/org/apache/qpid/broker/EventClientConnectFail.h" using namespace qpid; @@ -148,6 +152,24 @@ void ConnectionHandler::Handler::startOk(const framing::FieldTable& /*clientProp void ConnectionHandler::Handler::startOk(const ConnectionStartOkBody& body) { + const framing::FieldTable& clientProperties = body.getClientProperties(); + qmf::org::apache::qpid::broker::Connection::shared_ptr mgmtObject = connection.getMgmtObject(); + + if (mgmtObject != 0) { + string procName = clientProperties.getAsString(CLIENT_PROCESS_NAME); + uint32_t pid = clientProperties.getAsInt(CLIENT_PID); + uint32_t ppid = clientProperties.getAsInt(CLIENT_PPID); + + types::Variant::Map properties; + qpid::amqp_0_10::translate(clientProperties, properties); + mgmtObject->set_remoteProperties(properties); + if (!procName.empty()) + mgmtObject->set_remoteProcessName(procName); + if (pid != 0) + mgmtObject->set_remotePid(pid); + if (ppid != 0) + mgmtObject->set_remoteParentPid(ppid); + } try { authenticator->start(body.getMechanism(), body.hasResponse() ? &body.getResponse() : 0); } catch (std::exception& /*e*/) { @@ -160,8 +182,9 @@ void ConnectionHandler::Handler::startOk(const ConnectionStartOkBody& body) string uid; authenticator->getError(error); authenticator->getUid(uid); - if (agent) { - agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + if (agent && mgmtObject) { + agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error, + mgmtObject->get_remoteProperties())); } QPID_LOG_CAT(debug, model, "Failed connection. rhost:" << connection.getMgmtId() << " user:" << uid @@ -169,9 +192,8 @@ void ConnectionHandler::Handler::startOk(const ConnectionStartOkBody& body) } throw; } - const framing::FieldTable& clientProperties = body.getClientProperties(); - connection.setClientProperties(clientProperties); + connection.setClientProperties(clientProperties); connection.setFederationLink(clientProperties.get(QPID_FED_LINK)); if (clientProperties.isSet(QPID_FED_TAG)) { connection.setFederationPeerTag(clientProperties.getAsString(QPID_FED_TAG)); @@ -187,19 +209,6 @@ void ConnectionHandler::Handler::startOk(const ConnectionStartOkBody& body) } QPID_LOG(info, "Connection is a federation link"); } - - if (connection.getMgmtObject() != 0) { - string procName = clientProperties.getAsString(CLIENT_PROCESS_NAME); - uint32_t pid = clientProperties.getAsInt(CLIENT_PID); - uint32_t ppid = clientProperties.getAsInt(CLIENT_PPID); - - if (!procName.empty()) - connection.getMgmtObject()->set_remoteProcessName(procName); - if (pid != 0) - connection.getMgmtObject()->set_remotePid(pid); - if (ppid != 0) - connection.getMgmtObject()->set_remoteParentPid(ppid); - } } void ConnectionHandler::Handler::secureOk(const string& response) @@ -216,8 +225,9 @@ void ConnectionHandler::Handler::secureOk(const string& response) string uid; authenticator->getError(error); authenticator->getUid(uid); - if (agent) { - agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + if (agent && connection.getMgmtObject()) { + agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error, + connection.getMgmtObject()->get_remoteProperties())); } QPID_LOG_CAT(debug, model, "Failed connection. rhost:" << connection.getMgmtId() << " user:" << uid diff --git a/cpp/src/qpid/sys/alloca.h b/cpp/src/qpid/broker/ConnectionState.cpp index b3f59b7c3f..c6a8317c2b 100644 --- a/cpp/src/qpid/sys/alloca.h +++ b/cpp/src/qpid/broker/ConnectionState.cpp @@ -1,7 +1,5 @@ -#ifndef QPID_SYS_ALLOCA_H -#define QPID_SYS_ALLOCA_H - /* + * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -21,22 +19,20 @@ * */ -#if (defined(_WINDOWS) || defined (WIN32)) -# include <malloc.h> +#include "qpid/broker/ConnectionState.h" + +#include "qpid/broker/Broker.h" -# if defined(_MSC_VER) -# ifdef alloc -# undef alloc -# endif -# define alloc _alloc -# ifdef alloca -# undef alloca -# endif -# define alloca _alloca -# endif -#endif -#if !defined _WINDOWS && !defined WIN32 -# include <alloca.h> -#endif +namespace qpid { +namespace broker { -#endif /*!QPID_SYS_ALLOCA_H*/ +void ConnectionState::setUserId(const std::string& uid) { + userId = uid; + size_t at = userId.find('@'); + userName = userId.substr(0, at); + isDefaultRealm = ( + at!= std::string::npos && + getBroker().getOptions().realm == userId.substr(at+1,userId.size())); +} + +}} diff --git a/cpp/src/qpid/broker/ConnectionState.h b/cpp/src/qpid/broker/ConnectionState.h index 4dfd86fd8e..a8d6e82210 100644 --- a/cpp/src/qpid/broker/ConnectionState.h +++ b/cpp/src/qpid/broker/ConnectionState.h @@ -21,18 +21,22 @@ #ifndef _ConnectionState_ #define _ConnectionState_ -#include <vector> - +#include "qpid/broker/ConnectionToken.h" #include "qpid/sys/AggregateOutput.h" #include "qpid/sys/ConnectionOutputHandlerPtr.h" #include "qpid/framing/ProtocolVersion.h" #include "qpid/management/Manageable.h" #include "qpid/Url.h" -#include "qpid/broker/Broker.h" + +#include <boost/function.hpp> +#include <vector> + namespace qpid { namespace broker { +class Broker; + class ConnectionState : public ConnectionToken, public management::Manageable { protected: @@ -46,9 +50,8 @@ class ConnectionState : public ConnectionToken, public management::Manageable framemax(65535), heartbeat(0), heartbeatmax(120), - userProxyAuth(false), // Can proxy msgs with non-matching auth ids when true (used by federation links & clustering) + userProxyAuth(false), // Can proxy msgs with non-matching auth ids when true (used by federation links) federationLink(true), - clusterOrderOut(0), isDefaultRealm(false) {} @@ -62,14 +65,7 @@ class ConnectionState : public ConnectionToken, public management::Manageable void setHeartbeat(uint16_t hb) { heartbeat = hb; } void setHeartbeatMax(uint16_t hbm) { heartbeatmax = hbm; } - virtual void setUserId(const std::string& uid) { - userId = uid; - size_t at = userId.find('@'); - userName = userId.substr(0, at); - isDefaultRealm = ( - at!= std::string::npos && - getBroker().getOptions().realm == userId.substr(at+1,userId.size())); - } + virtual void setUserId(const std::string& uid); const std::string& getUserId() const { return userId; } @@ -102,15 +98,6 @@ class ConnectionState : public ConnectionToken, public management::Manageable framing::ProtocolVersion getVersion() const { return version; } void setOutputHandler(qpid::sys::ConnectionOutputHandler* o) { out.set(o); } - /** - * If the broker is part of a cluster, this is a handler provided - * by cluster code. It ensures consistent ordering of commands - * that are sent based on criteria that are not predictably - * ordered cluster-wide, e.g. a timer firing. - */ - framing::FrameHandler* getClusterOrderOutput() { return clusterOrderOut; } - void setClusterOrderOutput(framing::FrameHandler& fh) { clusterOrderOut = &fh; } - virtual void requestIOProcessing (boost::function0<void>) = 0; protected: @@ -124,7 +111,6 @@ class ConnectionState : public ConnectionToken, public management::Manageable bool federationLink; std::string federationPeerTag; std::vector<Url> knownHosts; - framing::FrameHandler* clusterOrderOut; std::string userName; bool isDefaultRealm; }; diff --git a/cpp/src/qpid/broker/Consumer.h b/cpp/src/qpid/broker/Consumer.h index 64fc4288af..662b0f937d 100644 --- a/cpp/src/qpid/broker/Consumer.h +++ b/cpp/src/qpid/broker/Consumer.h @@ -79,6 +79,15 @@ class Consumer : public QueueCursor { */ virtual bool hideDeletedError() { return false; } + /** If false, the consumer is not counted for purposes of auto-deletion or + * immediate messages. This is used for "system" consumers that are created + * by the broker for internal purposes as opposed to consumers that are + * created by normal clients. + */ + virtual bool isCounted() { return true; } + + QueueCursor getCursor() const { return *this; } + void setCursor(const QueueCursor& qc) { static_cast<QueueCursor&>(*this) = qc; } protected: //framing::SequenceNumber position; diff --git a/cpp/src/qpid/broker/ConsumerFactory.h b/cpp/src/qpid/broker/ConsumerFactory.h index abd39fb3f8..1c0f2571e2 100644 --- a/cpp/src/qpid/broker/ConsumerFactory.h +++ b/cpp/src/qpid/broker/ConsumerFactory.h @@ -25,11 +25,14 @@ // TODO aconway 2011-11-25: it's ugly exposing SemanticState::ConsumerImpl in public. // Refactor to use a more abstract interface. -#include "qpid/broker/SemanticState.h" +#include <boost/shared_ptr.hpp> namespace qpid { namespace broker { +class SemanticState; +class SemanticStateConsumerImpl; + /** * Base class for consumer factoires. Plugins can register a * ConsumerFactory via Broker:: getConsumerFactories() Each time a @@ -41,7 +44,7 @@ class ConsumerFactory public: virtual ~ConsumerFactory() {} - virtual boost::shared_ptr<SemanticState::ConsumerImpl> create( + virtual boost::shared_ptr<SemanticStateConsumerImpl> create( SemanticState* parent, const std::string& name, boost::shared_ptr<Queue> queue, bool ack, bool acquire, bool exclusive, const std::string& tag, diff --git a/cpp/src/qpid/broker/DeliverableMessage.cpp b/cpp/src/qpid/broker/DeliverableMessage.cpp index be4b7f0796..31823709ce 100644 --- a/cpp/src/qpid/broker/DeliverableMessage.cpp +++ b/cpp/src/qpid/broker/DeliverableMessage.cpp @@ -36,8 +36,3 @@ Message& DeliverableMessage::getMessage() { return msg; } - -uint64_t DeliverableMessage::contentSize() -{ - return msg.getContentSize(); -} diff --git a/cpp/src/qpid/broker/DeliverableMessage.h b/cpp/src/qpid/broker/DeliverableMessage.h index d6d6bf5265..6e8275770d 100644 --- a/cpp/src/qpid/broker/DeliverableMessage.h +++ b/cpp/src/qpid/broker/DeliverableMessage.h @@ -36,7 +36,6 @@ namespace qpid { QPID_BROKER_EXTERN DeliverableMessage(const Message& msg, TxBuffer* txn); QPID_BROKER_EXTERN virtual void deliverTo(const boost::shared_ptr<Queue>& queue); QPID_BROKER_EXTERN Message& getMessage(); - QPID_BROKER_EXTERN uint64_t contentSize(); virtual ~DeliverableMessage(){} }; } diff --git a/cpp/src/qpid/broker/DeliveryAdapter.h b/cpp/src/qpid/broker/DeliveryAdapter.h deleted file mode 100644 index e69de29bb2..0000000000 --- a/cpp/src/qpid/broker/DeliveryAdapter.h +++ /dev/null diff --git a/cpp/src/qpid/broker/DirectExchange.cpp b/cpp/src/qpid/broker/DirectExchange.cpp index b1130c3ec0..6aef1c2168 100644 --- a/cpp/src/qpid/broker/DirectExchange.cpp +++ b/cpp/src/qpid/broker/DirectExchange.cpp @@ -70,7 +70,7 @@ bool DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, con if (args == 0 || fedOp.empty() || fedOp == fedOpBind) { Mutex::ScopedLock l(lock); - Binding::shared_ptr b(new Binding(routingKey, queue, this, FieldTable(), fedOrigin)); + Binding::shared_ptr b(new Binding(routingKey, queue, this, args ? *args : FieldTable(), fedOrigin)); BoundKey& bk = bindings[routingKey]; if (exclusiveBinding) bk.queues.clear(); diff --git a/cpp/src/qpid/broker/DtxManager.cpp b/cpp/src/qpid/broker/DtxManager.cpp index c55771c4e6..3b23ea2900 100644 --- a/cpp/src/qpid/broker/DtxManager.cpp +++ b/cpp/src/qpid/broker/DtxManager.cpp @@ -27,6 +27,9 @@ #include "qpid/ptr_map.h" #include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/function.hpp> + #include <iostream> using boost::intrusive_ptr; @@ -35,6 +38,30 @@ using qpid::ptr_map_ptr; using namespace qpid::broker; using namespace qpid::framing; +namespace { + typedef boost::function0<void> FireFunction; + struct DtxCleanup : public qpid::sys::TimerTask + { + FireFunction fireFunction; + + DtxCleanup(uint32_t timeout, FireFunction f); + void fire(); + }; + + DtxCleanup::DtxCleanup(uint32_t _timeout, FireFunction f) + : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxCleanup"), fireFunction(f){} + + void DtxCleanup::fire() + { + try { + fireFunction(); + } catch (qpid::ConnectionException& /*e*/) { + //assume it was explicitly cleaned up after a call to prepare, commit or rollback + } + } + +} + //DtxManager::DtxManager(qpid::sys::Timer& t) : store(0), timer(&t) {} DtxManager::DtxManager(qpid::sys::Timer& t) : asyncTxnStore(0), timer(&t) {} @@ -158,19 +185,7 @@ void DtxManager::timedout(const std::string& xid) } else { ptr_map_ptr(i)->timedout(); //TODO: do we want to have a timed task to cleanup, or can we rely on an explicit completion? - //timer.add(intrusive_ptr<TimerTask>(new DtxCleanup(60*30/*30 mins*/, *this, xid))); - } -} - -DtxManager::DtxCleanup::DtxCleanup(uint32_t _timeout, DtxManager& _mgr, const std::string& _xid) - : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxCleanup"), mgr(_mgr), xid(_xid) {} - -void DtxManager::DtxCleanup::fire() -{ - try { - mgr.remove(xid); - } catch (ConnectionException& /*e*/) { - //assume it was explicitly cleaned up after a call to prepare, commit or rollback + //timer->add(new DtxCleanup(60*30/*30 mins*/, boost::bind(&DtxManager::remove, this, xid))); } } diff --git a/cpp/src/qpid/broker/DtxManager.h b/cpp/src/qpid/broker/DtxManager.h index cbc66d6391..82f7ebb9ac 100644 --- a/cpp/src/qpid/broker/DtxManager.h +++ b/cpp/src/qpid/broker/DtxManager.h @@ -32,20 +32,15 @@ #include "qpid/ptr_map.h" namespace qpid { +namespace sys { +class Timer; +} + namespace broker { class DtxManager{ typedef boost::ptr_map<std::string, DtxWorkRecord> WorkMap; - struct DtxCleanup : public sys::TimerTask - { - DtxManager& mgr; - const std::string& xid; - - DtxCleanup(uint32_t timeout, DtxManager& mgr, const std::string& xid); - void fire(); - }; - WorkMap work; // TransactionalStore* store; AsyncTransactionalStore* asyncTxnStore; @@ -71,11 +66,6 @@ public: void setStore(AsyncTransactionalStore* const ats); void setTimer(sys::Timer& t) { timer = &t; } - // Used by cluster for replication. - template<class F> void each(F f) const { - for (WorkMap::const_iterator i = work.begin(); i != work.end(); ++i) - f(*ptr_map_ptr(i)); - } DtxWorkRecord* getWork(const std::string& xid); bool exists(const std::string& xid); static std::string convert(const framing::Xid& xid); diff --git a/cpp/src/qpid/broker/DtxWorkRecord.cpp b/cpp/src/qpid/broker/DtxWorkRecord.cpp index 924f953eb8..13eaf4e568 100644 --- a/cpp/src/qpid/broker/DtxWorkRecord.cpp +++ b/cpp/src/qpid/broker/DtxWorkRecord.cpp @@ -20,7 +20,10 @@ */ #include "qpid/broker/DtxWorkRecord.h" #include "qpid/broker/DtxManager.h" +#include "qpid/broker/DtxTimeout.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/Timer.h" + #include <boost/format.hpp> #include <boost/mem_fn.hpp> using boost::mem_fn; @@ -39,6 +42,12 @@ DtxWorkRecord::~DtxWorkRecord() } } +void DtxWorkRecord::setTimeout(boost::intrusive_ptr<DtxTimeout> t) +{ timeout = t; } + +boost::intrusive_ptr<DtxTimeout> DtxWorkRecord::getTimeout() +{ return timeout; } + bool DtxWorkRecord::prepare() { Mutex::ScopedLock locker(lock); @@ -182,17 +191,3 @@ void DtxWorkRecord::timedout() } abort(); } - -size_t DtxWorkRecord::indexOf(const DtxBuffer::shared_ptr& buf) { - Work::iterator i = std::find(work.begin(), work.end(), buf); - if (i == work.end()) throw NotFoundException( - QPID_MSG("Can't find DTX buffer for xid: " << buf->getXid())); - return i - work.begin(); -} - -DtxBuffer::shared_ptr DtxWorkRecord::operator[](size_t i) const { - if (i > work.size()) - throw NotFoundException( - QPID_MSG("Can't find DTX buffer " << i << " for xid: " << xid)); - return work[i]; -} diff --git a/cpp/src/qpid/broker/DtxWorkRecord.h b/cpp/src/qpid/broker/DtxWorkRecord.h index 579579df2d..84344533d9 100644 --- a/cpp/src/qpid/broker/DtxWorkRecord.h +++ b/cpp/src/qpid/broker/DtxWorkRecord.h @@ -24,7 +24,6 @@ #include "qpid/broker/AsyncStore.h" #include "qpid/broker/BrokerImportExport.h" #include "qpid/broker/DtxBuffer.h" -#include "qpid/broker/DtxTimeout.h" #include "qpid/broker/TransactionalStore.h" #include "qpid/framing/amqp_types.h" @@ -39,6 +38,8 @@ namespace qpid { namespace broker { +struct DtxTimeout; + /** * Represents the work done under a particular distributed transaction * across potentially multiple channels. Identified by a xid. Allows @@ -74,19 +75,13 @@ public: QPID_BROKER_EXTERN void add(DtxBuffer::shared_ptr ops); void recover(std::auto_ptr<TPCTransactionContext> txn, DtxBuffer::shared_ptr ops); void timedout(); - void setTimeout(boost::intrusive_ptr<DtxTimeout> t) { timeout = t; } - boost::intrusive_ptr<DtxTimeout> getTimeout() { return timeout; } + void setTimeout(boost::intrusive_ptr<DtxTimeout> t); + boost::intrusive_ptr<DtxTimeout> getTimeout(); std::string getXid() const { return xid; } bool isCompleted() const { return completed; } bool isRolledback() const { return rolledback; } bool isPrepared() const { return prepared; } bool isExpired() const { return expired; } - - // Used by cluster update; - size_t size() const { return work.size(); } - DtxBuffer::shared_ptr operator[](size_t i) const; - uint32_t getTimeout() const { return timeout? timeout->timeout : 0; } - size_t indexOf(const DtxBuffer::shared_ptr&); }; }} // qpid::broker diff --git a/cpp/src/qpid/broker/Exchange.cpp b/cpp/src/qpid/broker/Exchange.cpp index 2414981481..855a69af98 100644 --- a/cpp/src/qpid/broker/Exchange.cpp +++ b/cpp/src/qpid/broker/Exchange.cpp @@ -167,19 +167,19 @@ void Exchange::routeIVE(){ Exchange::Exchange (const string& _name, Manageable* parent, Broker* b) : name(_name), durable(false), alternateUsers(0), persistenceId(0), sequence(false), - sequenceNo(0), ive(false), mgmtExchange(0), brokerMgmtObject(0), broker(b), destroyed(false) + sequenceNo(0), ive(false), broker(b), destroyed(false) { if (parent != 0 && broker != 0) { ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - mgmtExchange = new _qmf::Exchange (agent, this, parent, _name); + mgmtExchange = _qmf::Exchange::shared_ptr(new _qmf::Exchange (agent, this, parent, _name)); mgmtExchange->set_durable(durable); mgmtExchange->set_autoDelete(false); agent->addObject(mgmtExchange, 0, durable); if (broker) - brokerMgmtObject = (qmf::org::apache::qpid::broker::Broker*) broker->GetManagementObject(); + brokerMgmtObject = boost::dynamic_pointer_cast<qmf::org::apache::qpid::broker::Broker>(broker->GetManagementObject()); } } } @@ -187,20 +187,20 @@ Exchange::Exchange (const string& _name, Manageable* parent, Broker* b) : Exchange::Exchange(const string& _name, bool _durable, const qpid::framing::FieldTable& _args, Manageable* parent, Broker* b) : name(_name), durable(_durable), alternateUsers(0), persistenceId(0), - args(_args), sequence(false), sequenceNo(0), ive(false), mgmtExchange(0), brokerMgmtObject(0), broker(b), destroyed(false) + args(_args), sequence(false), sequenceNo(0), ive(false), broker(b), destroyed(false) { if (parent != 0 && broker != 0) { ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - mgmtExchange = new _qmf::Exchange (agent, this, parent, _name); + mgmtExchange = _qmf::Exchange::shared_ptr(new _qmf::Exchange (agent, this, parent, _name)); mgmtExchange->set_durable(durable); mgmtExchange->set_autoDelete(false); mgmtExchange->set_arguments(ManagementAgent::toMap(args)); agent->addObject(mgmtExchange, 0, durable); if (broker) - brokerMgmtObject = (qmf::org::apache::qpid::broker::Broker*) broker->GetManagementObject(); + brokerMgmtObject = boost::dynamic_pointer_cast<qmf::org::apache::qpid::broker::Broker>(broker->GetManagementObject()); } } @@ -212,8 +212,6 @@ Exchange::Exchange(const string& _name, bool _durable, const qpid::framing::Fiel ive = _args.get(qpidIVE); if (ive) { - if (broker && broker->isInCluster()) - throw framing::NotImplementedException("Cannot use Initial Value Exchanges in a cluster"); QPID_LOG(debug, "Configured exchange " << _name << " with Initial Value"); } } @@ -227,6 +225,7 @@ Exchange::~Exchange () void Exchange::setAlternate(Exchange::shared_ptr _alternate) { alternate = _alternate; + alternate->incAlternateUsers(); if (mgmtExchange != 0) { if (alternate.get() != 0) mgmtExchange->set_altExchange(alternate->GetManagementObject()->getObjectId()); @@ -296,9 +295,9 @@ void Exchange::recoveryComplete(ExchangeRegistry& exchanges) } } -ManagementObject* Exchange::GetManagementObject (void) const +ManagementObject::shared_ptr Exchange::GetManagementObject (void) const { - return (ManagementObject*) mgmtExchange; + return mgmtExchange; } void Exchange::registerDynamicBridge(DynamicBridge* db, AsyncStore* const store) @@ -347,16 +346,16 @@ void Exchange::propagateFedOp(const string& routingKey, const string& tags, cons Exchange::Binding::Binding(const string& _key, Queue::shared_ptr _queue, Exchange* _parent, FieldTable _args, const string& _origin, ConfigHandle _cfgHandle) - : parent(_parent), queue(_queue), key(_key), args(_args), origin(_origin), cfgHandle(_cfgHandle), mgmtBinding(0) + : parent(_parent), queue(_queue), key(_key), args(_args), origin(_origin), cfgHandle(_cfgHandle) { } Exchange::Binding::~Binding () { if (mgmtBinding != 0) { - ManagementObject* mo = queue->GetManagementObject(); + _qmf::Queue::shared_ptr mo = boost::dynamic_pointer_cast<_qmf::Queue>(queue->GetManagementObject()); if (mo != 0) - static_cast<_qmf::Queue*>(mo)->dec_bindingCount(); + mo->dec_bindingCount(); mgmtBinding->resourceDestroy (); } } @@ -369,25 +368,25 @@ void Exchange::Binding::startManagement() if (broker != 0) { ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - ManagementObject* mo = queue->GetManagementObject(); + _qmf::Queue::shared_ptr mo = boost::dynamic_pointer_cast<_qmf::Queue>(queue->GetManagementObject()); if (mo != 0) { management::ObjectId queueId = mo->getObjectId(); - mgmtBinding = new _qmf::Binding - (agent, this, (Manageable*) parent, queueId, key, ManagementAgent::toMap(args)); + mgmtBinding = _qmf::Binding::shared_ptr(new _qmf::Binding + (agent, this, (Manageable*) parent, queueId, key, ManagementAgent::toMap(args))); if (!origin.empty()) mgmtBinding->set_origin(origin); agent->addObject(mgmtBinding); - static_cast<_qmf::Queue*>(mo)->inc_bindingCount(); + mo->inc_bindingCount(); } } } } } -ManagementObject* Exchange::Binding::GetManagementObject () const +ManagementObject::shared_ptr Exchange::Binding::GetManagementObject () const { - return (ManagementObject*) mgmtBinding; + return mgmtBinding; } uint64_t Exchange::Binding::getSize() { return 0; } // TODO: kpvdr: implement persistence @@ -434,5 +433,10 @@ bool Exchange::routeWithAlternate(Deliverable& msg) return msg.delivered; } +void Exchange::setArgs(const framing::FieldTable& newArgs) { + args = newArgs; + if (mgmtExchange) mgmtExchange->set_arguments(ManagementAgent::toMap(args)); +} + }} diff --git a/cpp/src/qpid/broker/Exchange.h b/cpp/src/qpid/broker/Exchange.h index df6d5d05a4..2c85e826ab 100644 --- a/cpp/src/qpid/broker/Exchange.h +++ b/cpp/src/qpid/broker/Exchange.h @@ -55,14 +55,14 @@ public: const framing::FieldTable args; std::string origin; ConfigHandle cfgHandle; - qmf::org::apache::qpid::broker::Binding* mgmtBinding; + qmf::org::apache::qpid::broker::Binding::shared_ptr mgmtBinding; Binding(const std::string& key, boost::shared_ptr<Queue> queue, Exchange* parent = 0, framing::FieldTable args = framing::FieldTable(), const std::string& origin = std::string(), ConfigHandle cfgHandle = ConfigHandle()); ~Binding(); void startManagement(); - management::ManagementObject* GetManagementObject() const; + management::ManagementObject::shared_ptr GetManagementObject() const; // DataSource implementation - allows for persistence uint64_t getSize(); @@ -170,8 +170,8 @@ protected: } }; - qmf::org::apache::qpid::broker::Exchange* mgmtExchange; - qmf::org::apache::qpid::broker::Broker* brokerMgmtObject; + qmf::org::apache::qpid::broker::Exchange::shared_ptr mgmtExchange; + qmf::org::apache::qpid::broker::Broker::shared_ptr brokerMgmtObject; public: typedef boost::shared_ptr<Exchange> shared_ptr; @@ -184,7 +184,8 @@ public: const std::string& getName() const { return name; } bool isDurable() { return durable; } - qpid::framing::FieldTable& getArgs() { return args; } + QPID_BROKER_EXTERN const qpid::framing::FieldTable& getArgs() const { return args; } + QPID_BROKER_EXTERN void setArgs(const framing::FieldTable&); QPID_BROKER_EXTERN Exchange::shared_ptr getAlternate() { return alternate; } QPID_BROKER_EXTERN void setAlternate(Exchange::shared_ptr _alternate); @@ -221,7 +222,7 @@ public: static QPID_BROKER_EXTERN Exchange::shared_ptr decode(ExchangeRegistry& exchanges, framing::Buffer& buffer); // Manageable entry points - QPID_BROKER_EXTERN management::ManagementObject* GetManagementObject(void) const; + QPID_BROKER_EXTERN management::ManagementObject::shared_ptr GetManagementObject(void) const; // Federation hooks class DynamicBridge { diff --git a/cpp/src/qpid/broker/ExchangeRegistry.cpp b/cpp/src/qpid/broker/ExchangeRegistry.cpp index b31c7bd7b8..645918d526 100644 --- a/cpp/src/qpid/broker/ExchangeRegistry.cpp +++ b/cpp/src/qpid/broker/ExchangeRegistry.cpp @@ -29,20 +29,26 @@ #include "qpid/management/ManagementDirectExchange.h" #include "qpid/management/ManagementTopicExchange.h" #include "qpid/framing/reply_exceptions.h" +#include "qmf/org/apache/qpid/broker/EventExchangeDeclare.h" +#include "qmf/org/apache/qpid/broker/EventExchangeDelete.h" using namespace qpid::broker; using namespace qpid::sys; using std::pair; using std::string; using qpid::framing::FieldTable; +using qpid::management::ManagementAgent; +namespace _qmf = qmf::org::apache::qpid::broker; pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, const string& type){ return declare(name, type, false, FieldTable()); } -pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, const string& type, - bool durable, const FieldTable& args){ +pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare( + const string& name, const string& type, bool durable, const FieldTable& args, + Exchange::shared_ptr alternate, const string& connectionId, const string& userId) +{ Exchange::shared_ptr exchange; std::pair<Exchange::shared_ptr, bool> result; { @@ -73,31 +79,55 @@ pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, c } exchanges[name] = exchange; result = std::pair<Exchange::shared_ptr, bool>(exchange, true); + if (alternate) exchange->setAlternate(alternate); + // Call exchangeCreate inside the lock to ensure correct ordering. + if (broker) broker->getConfigurationObservers().exchangeCreate(exchange); } else { result = std::pair<Exchange::shared_ptr, bool>(i->second, false); } + if (broker && broker->getManagementAgent()) { + // Call raiseEvent inside the lock to ensure correct ordering. + broker->getManagementAgent()->raiseEvent( + _qmf::EventExchangeDeclare( + connectionId, + userId, + name, + type, + alternate ? alternate->getName() : string(), + durable, + false, + ManagementAgent::toMap(result.first->getArgs()), + result.second ? "created" : "existing")); + } } - if (broker && exchange) broker->getConfigurationObservers().exchangeCreate(exchange); return result; } -void ExchangeRegistry::destroy(const string& name){ +void ExchangeRegistry::destroy( + const string& name, const string& connectionId, const string& userId) +{ if (name.empty() || (name.find("amq.") == 0 && (name == "amq.direct" || name == "amq.fanout" || name == "amq.topic" || name == "amq.match")) || name == "qpid.management") throw framing::NotAllowedException(QPID_MSG("Cannot delete default exchange: '" << name << "'")); - Exchange::shared_ptr exchange; { RWlock::ScopedWlock locker(lock); ExchangeMap::iterator i = exchanges.find(name); if (i != exchanges.end()) { - exchange = i->second; + if (broker) { + // Call exchangeDestroy and raiseEvent inside the lock to ensure + // correct ordering. + broker->getConfigurationObservers().exchangeDestroy(i->second); + if (broker->getManagementAgent()) + broker->getManagementAgent()->raiseEvent( + _qmf::EventExchangeDelete(connectionId, userId, name)); + } i->second->destroy(); exchanges.erase(i); + } } - if (broker && exchange) broker->getConfigurationObservers().exchangeDestroy(exchange); } Exchange::shared_ptr ExchangeRegistry::find(const string& name){ diff --git a/cpp/src/qpid/broker/ExchangeRegistry.h b/cpp/src/qpid/broker/ExchangeRegistry.h index c5d2483a23..df4c24da39 100644 --- a/cpp/src/qpid/broker/ExchangeRegistry.h +++ b/cpp/src/qpid/broker/ExchangeRegistry.h @@ -46,14 +46,23 @@ class ExchangeRegistry{ bool, const qpid::framing::FieldTable&, qpid::management::Manageable*, qpid::broker::Broker*> FactoryFunction; ExchangeRegistry (Broker* b = 0) : parent(0), broker(b) {} - QPID_BROKER_EXTERN std::pair<Exchange::shared_ptr, bool> declare - (const std::string& name, const std::string& type); - QPID_BROKER_EXTERN std::pair<Exchange::shared_ptr, bool> declare - (const std::string& name, - const std::string& type, - bool durable, - const qpid::framing::FieldTable& args = framing::FieldTable()); - QPID_BROKER_EXTERN void destroy(const std::string& name); + QPID_BROKER_EXTERN std::pair<Exchange::shared_ptr, bool> declare( + const std::string& name, const std::string& type); + + QPID_BROKER_EXTERN std::pair<Exchange::shared_ptr, bool> declare( + const std::string& name, + const std::string& type, + bool durable, + const qpid::framing::FieldTable& args = framing::FieldTable(), + Exchange::shared_ptr alternate = Exchange::shared_ptr(), + const std::string& connectionId = std::string(), + const std::string& userId = std::string()); + + QPID_BROKER_EXTERN void destroy( + const std::string& name, + const std::string& connectionId = std::string(), + const std::string& userId = std::string()); + QPID_BROKER_EXTERN Exchange::shared_ptr getDefault(); /** diff --git a/cpp/src/qpid/broker/FanOutExchange.cpp b/cpp/src/qpid/broker/FanOutExchange.cpp index 941d909778..4ccb44ce53 100644 --- a/cpp/src/qpid/broker/FanOutExchange.cpp +++ b/cpp/src/qpid/broker/FanOutExchange.cpp @@ -54,7 +54,7 @@ bool FanOutExchange::bind(Queue::shared_ptr queue, const string& /*key*/, const bool propagate = false; if (args == 0 || fedOp.empty() || fedOp == fedOpBind) { - Binding::shared_ptr binding (new Binding ("", queue, this, FieldTable(), fedOrigin)); + Binding::shared_ptr binding (new Binding ("", queue, this, args ? *args : FieldTable(), fedOrigin)); if (bindings.add_unless(binding, MatchQueue(queue))) { binding->startManagement(); propagate = fedBinding.addOrigin(queue->getName(), fedOrigin); diff --git a/cpp/src/qpid/broker/HeadersExchange.cpp b/cpp/src/qpid/broker/HeadersExchange.cpp index 76ffa7a922..7edd54bc24 100644 --- a/cpp/src/qpid/broker/HeadersExchange.cpp +++ b/cpp/src/qpid/broker/HeadersExchange.cpp @@ -48,6 +48,7 @@ namespace { const std::string empty; // federation related args and values + const std::string QPID_RESERVED("qpid."); const std::string qpidFedOp("qpid.fed.op"); const std::string qpidFedTags("qpid.fed.tags"); const std::string qpidFedOrigin("qpid.fed.origin"); @@ -200,8 +201,8 @@ bool HeadersExchange::bind(Queue::shared_ptr queue, const string& bindingKey, co //matching (they are internally added properties //controlling binding propagation but not relevant to //actual routing) - Binding::shared_ptr binding (new Binding (bindingKey, queue, this, extra_args)); - BoundKey bk(binding); + Binding::shared_ptr binding (new Binding (bindingKey, queue, this, args ? *args : FieldTable())); + BoundKey bk(binding, extra_args); if (bindings.add_unless(bk, MatchArgs(queue, &extra_args))) { binding->startManagement(); propagate = bk.fedBinding.addOrigin(queue->getName(), fedOrigin); @@ -282,7 +283,7 @@ void HeadersExchange::route(Deliverable& msg) Bindings::ConstPtr p = bindings.snapshot(); if (p.get()) { for (std::vector<BoundKey>::const_iterator i = p->begin(); i != p->end(); ++i) { - Matcher matcher(i->binding->args); + Matcher matcher(i->args); msg.getMessage().processProperties(matcher); if (matcher.matches()) { b->push_back(i->binding); @@ -298,7 +299,7 @@ bool HeadersExchange::isBound(Queue::shared_ptr queue, const string* const, cons Bindings::ConstPtr p = bindings.snapshot(); if (p.get()){ for (std::vector<BoundKey>::const_iterator i = p->begin(); i != p->end(); ++i) { - if ( (!args || equal((*i).binding->args, *args)) && (!queue || (*i).binding->queue == queue)) { + if ( (!args || equal((*i).args, *args)) && (!queue || (*i).binding->queue == queue)) { return true; } } @@ -315,10 +316,7 @@ void HeadersExchange::getNonFedArgs(const FieldTable* args, FieldTable& nonFedAr for (qpid::framing::FieldTable::ValueMap::const_iterator i=args->begin(); i != args->end(); ++i) { - const string & name(i->first); - if (name == qpidFedOp || - name == qpidFedTags || - name == qpidFedOrigin) + if (i->first.find(QPID_RESERVED) == 0) { continue; } diff --git a/cpp/src/qpid/broker/HeadersExchange.h b/cpp/src/qpid/broker/HeadersExchange.h index 10bf9c8c0b..93e3929563 100644 --- a/cpp/src/qpid/broker/HeadersExchange.h +++ b/cpp/src/qpid/broker/HeadersExchange.h @@ -38,8 +38,9 @@ class HeadersExchange : public virtual Exchange { struct BoundKey { Binding::shared_ptr binding; + qpid::framing::FieldTable args; FedBinding fedBinding; - BoundKey(Binding::shared_ptr binding_) : binding(binding_) {} + BoundKey(Binding::shared_ptr binding_, const qpid::framing::FieldTable& args_) : binding(binding_), args(args_) {} }; struct MatchArgs diff --git a/cpp/src/qpid/broker/LegacyLVQ.cpp b/cpp/src/qpid/broker/LegacyLVQ.cpp deleted file mode 100644 index e69de29bb2..0000000000 --- a/cpp/src/qpid/broker/LegacyLVQ.cpp +++ /dev/null diff --git a/cpp/src/qpid/broker/LegacyLVQ.h b/cpp/src/qpid/broker/LegacyLVQ.h deleted file mode 100644 index e69de29bb2..0000000000 --- a/cpp/src/qpid/broker/LegacyLVQ.h +++ /dev/null diff --git a/cpp/src/qpid/broker/Link.cpp b/cpp/src/qpid/broker/Link.cpp index 9727040c9b..bb5d5cb678 100644 --- a/cpp/src/qpid/broker/Link.cpp +++ b/cpp/src/qpid/broker/Link.cpp @@ -30,8 +30,10 @@ #include "qpid/log/Statement.h" #include "qpid/framing/enum.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/amqp_types.h" #include "qpid/broker/AclModule.h" #include "qpid/broker/Exchange.h" +#include "qpid/broker/NameGenerator.h" #include "qpid/UrlArray.h" namespace qpid { @@ -148,12 +150,12 @@ Link::Link(const string& _name, host(_host), port(_port), transport(_transport), durable(_durable), authMechanism(_authMechanism), username(_username), password(_password), - persistenceId(0), mgmtObject(0), broker(_broker), state(0), + persistenceId(0), broker(_broker), state(0), visitCount(0), currentInterval(1), - closing(false), reconnectNext(0), // Index of next address for reconnecting in url. - channelCounter(1), + nextFreeChannel(1), + freeChannels(1, framing::CHANNEL_MAX), connection(0), agent(0), listener(l), @@ -166,19 +168,15 @@ Link::Link(const string& _name, agent = broker->getManagementAgent(); if (agent != 0) { - mgmtObject = new _qmf::Link(agent, this, parent, name, durable); + mgmtObject = _qmf::Link::shared_ptr(new _qmf::Link(agent, this, parent, name, durable)); mgmtObject->set_host(host); mgmtObject->set_port(port); mgmtObject->set_transport(transport); agent->addObject(mgmtObject, 0, durable); } } - if (links->isPassive()) { - setStateLH(STATE_PASSIVE); - } else { - setStateLH(STATE_WAITING); - startConnectionLH(); - } + setStateLH(STATE_WAITING); + startConnectionLH(); broker->getTimer().add(timerTask); if (failover) { @@ -212,9 +210,6 @@ void Link::setStateLH (int newState) state = newState; - if (hideManagement()) - return; - switch (state) { case STATE_WAITING : mgmtObject->set_state("Waiting"); break; @@ -222,7 +217,7 @@ void Link::setStateLH (int newState) case STATE_OPERATIONAL : mgmtObject->set_state("Operational"); break; case STATE_FAILED : mgmtObject->set_state("Failed"); break; case STATE_CLOSED : mgmtObject->set_state("Closed"); break; - case STATE_PASSIVE : mgmtObject->set_state("Passive"); break; + case STATE_CLOSING : mgmtObject->set_state("Closing"); break; } } @@ -233,40 +228,39 @@ void Link::startConnectionLH () // Set the state before calling connect. It is possible that connect // will fail synchronously and call Link::closed before returning. setStateLH(STATE_CONNECTING); - broker->connect (host, boost::lexical_cast<std::string>(port), transport, + broker->connect (name, host, boost::lexical_cast<std::string>(port), transport, boost::bind (&Link::closed, this, _1, _2)); - QPID_LOG (debug, "Inter-broker link connecting to " << host << ":" << port); + QPID_LOG (info, "Inter-broker link connecting to " << host << ":" << port); } catch(const std::exception& e) { QPID_LOG(error, "Link connection to " << host << ":" << port << " failed: " << e.what()); setStateLH(STATE_WAITING); - if (!hideManagement()) - mgmtObject->set_lastError (e.what()); + mgmtObject->set_lastError (e.what()); } } void Link::established(Connection* c) { - if (state == STATE_PASSIVE) return; stringstream addr; addr << host << ":" << port; QPID_LOG (info, "Inter-broker link established to " << addr.str()); - if (!hideManagement() && agent) + if (agent) agent->raiseEvent(_qmf::EventBrokerLinkUp(addr.str())); - bool isClosing = false; + bool isClosing = true; { Mutex::ScopedLock mutex(lock); - setStateLH(STATE_OPERATIONAL); - currentInterval = 1; - visitCount = 0; - connection = c; - isClosing = closing; + if (state != STATE_CLOSING) { + isClosing = false; + setStateLH(STATE_OPERATIONAL); + currentInterval = 1; + visitCount = 0; + connection = c; + c->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); + } } if (isClosing) destroy(); - else // Process any IO tasks bridges added before established. - c->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); } @@ -291,11 +285,12 @@ class DetachedCallback : public SessionHandler::ErrorListener { }; } -void Link::opened() { +void Link::opened() +{ Mutex::ScopedLock mutex(lock); - if (!connection) return; + if (!connection || state != STATE_OPERATIONAL) return; - if (!hideManagement() && connection->GetManagementObject()) { + if (connection->GetManagementObject()) { mgmtObject->set_connectionRef(connection->GetManagementObject()->getObjectId()); } @@ -350,37 +345,43 @@ void Link::opened() { } } + +// called when connection attempt fails (see startConnectionLH) void Link::closed(int, std::string text) { - Mutex::ScopedLock mutex(lock); QPID_LOG (info, "Inter-broker link disconnected from " << host << ":" << port << " " << text); - connection = 0; + bool isClosing = false; + { + Mutex::ScopedLock mutex(lock); + + connection = 0; - if (!hideManagement()) { mgmtObject->set_connectionRef(qpid::management::ObjectId()); if (state == STATE_OPERATIONAL && agent) { stringstream addr; addr << host << ":" << port; agent->raiseEvent(_qmf::EventBrokerLinkDown(addr.str())); } - } - for (Bridges::iterator i = active.begin(); i != active.end(); i++) { - (*i)->closed(); - created.push_back(*i); - } - active.clear(); + for (Bridges::iterator i = active.begin(); i != active.end(); i++) { + (*i)->closed(); + created.push_back(*i); + } + active.clear(); - if (state != STATE_FAILED && state != STATE_PASSIVE) - { - setStateLH(STATE_WAITING); - if (!hideManagement()) + if (state == STATE_CLOSING) { + isClosing = true; + } else if (state != STATE_FAILED) { + setStateLH(STATE_WAITING); mgmtObject->set_lastError (text); + } } + if (isClosing) destroy(); } -// Called in connection IO thread, cleans up the connection before destroying Link +// Cleans up the connection before destroying Link. Must be called in connection thread +// if the connection is active. Caller Note well: may call "delete this"! void Link::destroy () { Bridges toDelete; @@ -410,7 +411,9 @@ void Link::destroy () for (Bridges::iterator i = toDelete.begin(); i != toDelete.end(); i++) (*i)->close(); toDelete.clear(); - listener(this); // notify LinkRegistry that this Link has been destroyed + // notify LinkRegistry that this Link has been destroyed. Will result in "delete + // this" if LinkRegistry is holding the last shared pointer to *this + listener(this); } void Link::add(Bridge::shared_ptr bridge) @@ -452,7 +455,7 @@ void Link::ioThreadProcessing() { Mutex::ScopedLock mutex(lock); - if (state != STATE_OPERATIONAL || closing) + if (state != STATE_OPERATIONAL) return; // check for bridge session errors and recover @@ -489,9 +492,9 @@ void Link::ioThreadProcessing() void Link::maintenanceVisit () { Mutex::ScopedLock mutex(lock); - if (closing) return; - if (state == STATE_WAITING) - { + + switch (state) { + case STATE_WAITING: visitCount++; if (visitCount >= currentInterval) { @@ -504,11 +507,17 @@ void Link::maintenanceVisit () startConnectionLH(); } } + break; + + case STATE_OPERATIONAL: + if ((!active.empty() || !created.empty() || !cancellations.empty()) && + connection && connection->isOpen()) + connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); + break; + + default: // no-op for all other states + break; } - else if (state == STATE_OPERATIONAL && - (!active.empty() || !created.empty() || !cancellations.empty()) && - connection && connection->isOpen()) - connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); } void Link::reconnectLH(const Address& a) @@ -517,14 +526,13 @@ void Link::reconnectLH(const Address& a) port = a.port; transport = a.protocol; - if (!hideManagement()) { - stringstream errorString; - errorString << "Failing over to " << a; - mgmtObject->set_lastError(errorString.str()); - mgmtObject->set_host(host); - mgmtObject->set_port(port); - mgmtObject->set_transport(transport); - } + stringstream errorString; + errorString << "Failing over to " << a; + mgmtObject->set_lastError(errorString.str()); + mgmtObject->set_host(host); + mgmtObject->set_port(port); + mgmtObject->set_transport(transport); + startConnectionLH(); } @@ -541,26 +549,56 @@ bool Link::tryFailoverLH() { return false; } -// Management updates for a link are inconsistent in a cluster, so they are -// suppressed. -bool Link::hideManagement() const { - return !mgmtObject || ( broker && broker->isInCluster()); +// Allocate channel from link free pool +framing::ChannelId Link::nextChannel() +{ + Mutex::ScopedLock mutex(lock); + if (!freeChannels.empty()) { + // A free channel exists. + for (framing::ChannelId i = 1; i <= framing::CHANNEL_MAX; i++) + { + // extract proposed free channel + framing::ChannelId c = nextFreeChannel; + // calculate next free channel + if (framing::CHANNEL_MAX == nextFreeChannel) + nextFreeChannel = 1; + else + nextFreeChannel += 1; + // if proposed channel is free, use it + if (freeChannels.contains(c)) + { + freeChannels -= c; + QPID_LOG(debug, "Link " << name << " allocates channel: " << c); + return c; + } + } + assert (false); + } + + throw Exception(Msg() << "Link " << name << " channel pool is empty"); } -uint Link::nextChannel() +// Return channel to link free pool +void Link::returnChannel(framing::ChannelId c) { Mutex::ScopedLock mutex(lock); - if (channelCounter >= framing::CHANNEL_MAX) - channelCounter = 1; - return channelCounter++; + QPID_LOG(debug, "Link " << name << " frees channel: " << c); + freeChannels += c; } void Link::notifyConnectionForced(const string text) { - Mutex::ScopedLock mutex(lock); - setStateLH(STATE_FAILED); - if (!hideManagement()) - mgmtObject->set_lastError(text); + bool isClosing = false; + { + Mutex::ScopedLock mutex(lock); + if (state == STATE_CLOSING) { + isClosing = true; + } else { + setStateLH(STATE_FAILED); + mgmtObject->set_lastError(text); + } + } + if (isClosing) destroy(); } void Link::setPersistenceId(uint64_t id) const @@ -643,21 +681,32 @@ uint32_t Link::encodedSize() const + password.size() + 1; } -ManagementObject* Link::GetManagementObject (void) const +ManagementObject::shared_ptr Link::GetManagementObject(void) const { - return (ManagementObject*) mgmtObject; + return mgmtObject; } void Link::close() { QPID_LOG(debug, "Link::close(), link=" << name ); - Mutex::ScopedLock mutex(lock); - if (!closing) { - closing = true; - if (state != STATE_CONNECTING && connection) { - //connection can only be closed on the connections own IO processing thread - connection->requestIOProcessing(boost::bind(&Link::destroy, this)); + bool destroy_now = false; + { + Mutex::ScopedLock mutex(lock); + if (state != STATE_CLOSING) { + int old_state = state; + setStateLH(STATE_CLOSING); + if (connection) { + //connection can only be closed on the connections own IO processing thread + connection->requestIOProcessing(boost::bind(&Link::destroy, this)); + } else if (old_state == STATE_CONNECTING) { + // cannot destroy Link now since a connection request is outstanding. + // destroy the link after we get a response (see Link::established, + // Link::closed, Link::notifyConnectionForced, etc). + } else { + destroy_now = true; + } } } + if (destroy_now) destroy(); } @@ -701,22 +750,6 @@ Manageable::status_t Link::ManagementMethod (uint32_t op, Args& args, string& te return Manageable::STATUS_UNKNOWN_METHOD; } -void Link::setPassive(bool passive) -{ - Mutex::ScopedLock mutex(lock); - if (passive) { - setStateLH(STATE_PASSIVE); - } else { - if (state == STATE_PASSIVE) { - setStateLH(STATE_WAITING); - } else { - QPID_LOG(warning, "Ignoring attempt to activate non-passive link " - << host << ":" << port); - } - } -} - - /** utility to clean up connection resources correctly */ void Link::closeConnection( const std::string& reason) { @@ -752,28 +785,6 @@ namespace { const std::string FAILOVER_INDEX("failover-index"); } -void Link::getState(framing::FieldTable& state) const -{ - state.clear(); - Mutex::ScopedLock mutex(lock); - if (!url.empty()) { - state.setString(FAILOVER_ADDRESSES, url.str()); - state.setInt(FAILOVER_INDEX, reconnectNext); - } -} - -void Link::setState(const framing::FieldTable& state) -{ - Mutex::ScopedLock mutex(lock); - if (state.isSet(FAILOVER_ADDRESSES)) { - Url failovers(state.getAsString(FAILOVER_ADDRESSES)); - setUrl(failovers); - } - if (state.isSet(FAILOVER_INDEX)) { - reconnectNext = state.getAsInt(FAILOVER_INDEX); - } -} - std::string Link::createName(const std::string& transport, const std::string& host, uint16_t port) @@ -784,14 +795,6 @@ std::string Link::createName(const std::string& transport, return linkName.str(); } - -bool Link::pendingConnection(const std::string& _host, uint16_t _port) const -{ - Mutex::ScopedLock mutex(lock); - return (isConnecting() && _port == port && _host == host); -} - - const std::string Link::exchangeTypeName("qpid.LinkExchange"); }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Link.h b/cpp/src/qpid/broker/Link.h index f0cb90e73b..01ddc68d97 100644 --- a/cpp/src/qpid/broker/Link.h +++ b/cpp/src/qpid/broker/Link.h @@ -69,12 +69,11 @@ class Link : public PersistableConfig, public management::Manageable { std::string username; std::string password; mutable uint64_t persistenceId; - qmf::org::apache::qpid::broker::Link* mgmtObject; + qmf::org::apache::qpid::broker::Link::shared_ptr mgmtObject; Broker* broker; int state; uint32_t visitCount; uint32_t currentInterval; - bool closing; Url url; // URL can contain many addresses. size_t reconnectNext; // Index for next re-connect attempt @@ -82,7 +81,8 @@ class Link : public PersistableConfig, public management::Manageable { Bridges created; // Bridges pending creation Bridges active; // Bridges active Bridges cancellations; // Bridges pending cancellation - uint channelCounter; + framing::ChannelId nextFreeChannel; + RangeSet<framing::ChannelId> freeChannels; Connection* connection; management::ManagementAgent* agent; boost::function<void(Link*)> listener; @@ -97,7 +97,7 @@ class Link : public PersistableConfig, public management::Manageable { static const int STATE_OPERATIONAL = 3; static const int STATE_FAILED = 4; static const int STATE_CLOSED = 5; - static const int STATE_PASSIVE = 6; + static const int STATE_CLOSING = 6; // Waiting for outstanding connect to complete first static const uint32_t MAX_INTERVAL = 32; @@ -106,7 +106,6 @@ class Link : public PersistableConfig, public management::Manageable { void destroy(); // Cleanup connection before link goes away void ioThreadProcessing(); // Called on connection's IO thread by request bool tryFailoverLH(); // Called during maintenance visit - bool hideManagement() const; void reconnectLH(const Address&); //called by LinkRegistry // connection management (called by LinkRegistry) @@ -115,7 +114,6 @@ class Link : public PersistableConfig, public management::Manageable { void closed(int, std::string); // Called when connection goes away void notifyConnectionForced(const std::string text); void closeConnection(const std::string& reason); - bool pendingConnection(const std::string& host, uint16_t port) const; // is Link trying to connect to this remote? friend class LinkRegistry; // to call established, opened, closed @@ -151,7 +149,8 @@ class Link : public PersistableConfig, public management::Manageable { bool isDurable() { return durable; } void maintenanceVisit (); - uint nextChannel(); + framing::ChannelId nextChannel(); // allocate channel from link free pool + void returnChannel(framing::ChannelId); // return channel to link free pool void add(Bridge::shared_ptr); void cancel(Bridge::shared_ptr); @@ -165,7 +164,6 @@ class Link : public PersistableConfig, public management::Manageable { std::string getPassword() { return password; } Broker* getBroker() { return broker; } - void setPassive(bool p); bool isConnecting() const { return state == STATE_CONNECTING; } // PersistableConfig: @@ -181,17 +179,13 @@ class Link : public PersistableConfig, public management::Manageable { static bool isEncodedLink(const std::string& key); // Manageable entry points - management::ManagementObject* GetManagementObject(void) const; + management::ManagementObject::shared_ptr GetManagementObject(void) const; management::Manageable::status_t ManagementMethod(uint32_t, management::Args&, std::string&); // manage the exchange owned by this link static const std::string exchangeTypeName; static boost::shared_ptr<Exchange> linkExchangeFactory(const std::string& name); - // replicate internal state of this Link for clustering - void getState(framing::FieldTable& state) const; - void setState(const framing::FieldTable& state); - /** create a name for a link (if none supplied by user config) */ static std::string createName(const std::string& transport, const std::string& host, diff --git a/cpp/src/qpid/broker/LinkRegistry.cpp b/cpp/src/qpid/broker/LinkRegistry.cpp index a79081b8ed..d048b9c05f 100644 --- a/cpp/src/qpid/broker/LinkRegistry.cpp +++ b/cpp/src/qpid/broker/LinkRegistry.cpp @@ -19,8 +19,10 @@ * */ #include "qpid/broker/LinkRegistry.h" -#include "qpid/broker/Link.h" + +#include "qpid/broker/Broker.h" #include "qpid/broker/Connection.h" +#include "qpid/broker/Link.h" #include "qpid/log/Statement.h" #include <iostream> #include <boost/format.hpp> @@ -42,8 +44,8 @@ namespace _qmf = qmf::org::apache::qpid::broker; // factored: The persistence element should be factored separately LinkRegistry::LinkRegistry () : broker(0), -// parent(0), store(0), passive(false), - parent(0), asyncStore(0), passive(false), +// parent(0), store(0), + parent(0), asyncStore(0), realm("") { } @@ -60,7 +62,8 @@ class LinkRegistryConnectionObserver : public ConnectionObserver { LinkRegistry::LinkRegistry (Broker* _broker) : broker(_broker), - parent(0), asyncStore(0), passive(false), +// parent(0), store(0), + parent(0), asyncStore(0), realm(broker->getOptions().realm) { broker->getConnectionObservers().add( @@ -118,10 +121,9 @@ pair<Link::shared_ptr, bool> LinkRegistry::declare(const string& name, boost::bind(&LinkRegistry::linkDestroyed, this, _1), durable, authMechanism, username, password, broker, parent, failover)); -// if (durable && store) store->create(*link); - if (durable && asyncStore) { -// store->create(*link); - // TODO: kpvdr: async create config (link) + if (durable && asyncStore && !broker->inRecovery()) { + //store->create(*link); + // TODO: kpvdr: async create config (link) } links[name] = link; pendingLinks[name] = link; @@ -218,9 +220,8 @@ pair<Bridge::shared_ptr, bool> LinkRegistry::declare(const std::string& name, args, init, queueName, altExchange)); bridges[name] = bridge; link.add(bridge); -// if (durable && store) - if (durable && asyncStore) { -// store->create(*bridge); + if (durable && asyncStore && !broker->inRecovery()) { + //store->create(*bridge); // TODO: kpvdr: Async create config (bridge) } @@ -264,6 +265,7 @@ void LinkRegistry::destroyBridge(Bridge *bridge) Link *link = b->second->getLink(); if (link) { link->cancel(b->second); + link->returnChannel( bridge->getChannel() ); } // if (b->second->isDurable()) if (b->second->isDurable()) { @@ -283,38 +285,6 @@ AsyncStore* LinkRegistry::getStore() const { return asyncStore; } -namespace { - void extractHostPort(const std::string& connId, std::string *host, uint16_t *port) - { - // Extract host and port of remote broker from connection id string. - // - // TODO aconway 2011-02-01: centralize code that constructs/parses connection - // management IDs. Currently sys:: protocol factories and IO plugins construct the - // IDs and LinkRegistry parses them. - // KAG: current connection id format assumed: - // "localhost:port-remotehost:port". In the case of IpV6, the host addresses are - // contained within brackets "[...]", example: - // connId="[::1]:36859-[::1]:48603". Liberal use of "asserts" provided to alert us - // if this assumption changes! - size_t separator = connId.find('-'); - assert(separator != std::string::npos); - std::string remote = connId.substr(separator+1, std::string::npos); - separator = remote.rfind(":"); - assert(separator != std::string::npos); - *host = remote.substr(0, separator); - // IPv6 - host is bracketed by "[]", strip them - if ((*host)[0] == '[' && (*host)[host->length() - 1] == ']') { - *host = host->substr(1, host->length() - 2); - } - try { - *port = boost::lexical_cast<uint16_t>(remote.substr(separator+1, std::string::npos)); - } catch (const boost::bad_lexical_cast&) { - QPID_LOG(error, "Invalid format for connection identifier! '" << connId << "'"); - assert(false); - } - } -} - /** find the Link that corresponds to the given connection */ Link::shared_ptr LinkRegistry::findLink(const std::string& connId) { @@ -334,19 +304,15 @@ void LinkRegistry::notifyConnection(const std::string& key, Connection* c) // create a mapping from connection id to link QPID_LOG(debug, "LinkRegistry::notifyConnection(); key=" << key ); std::string host; - uint16_t port = 0; - extractHostPort( key, &host, &port ); Link::shared_ptr link; { Mutex::ScopedLock locker(lock); - for (LinkMap::iterator l = pendingLinks.begin(); l != pendingLinks.end(); ++l) { - if (l->second->pendingConnection(host, port)) { - link = l->second; - pendingLinks.erase(l); - connections[key] = link->getName(); - QPID_LOG(debug, "LinkRegistry:: found pending =" << link->getName()); - break; - } + LinkMap::iterator l = pendingLinks.find(key); + if (l != pendingLinks.end()) { + link = l->second; + pendingLinks.erase(l); + connections[key] = link->getName(); + QPID_LOG(debug, "LinkRegistry:: found pending =" << link->getName()); } } @@ -461,26 +427,4 @@ std::string LinkRegistry::getAuthIdentity(const std::string& key) return link->getUsername(); } - -void LinkRegistry::setPassive(bool p) -{ - Mutex::ScopedLock locker(lock); - passive = p; - if (passive) { QPID_LOG(info, "Passivating links"); } - else { QPID_LOG(info, "Activating links"); } - for (LinkMap::iterator i = links.begin(); i != links.end(); i++) { - i->second->setPassive(passive); - } -} - -void LinkRegistry::eachLink(boost::function<void(boost::shared_ptr<Link>)> f) { - Mutex::ScopedLock locker(lock); - for (LinkMap::iterator i = links.begin(); i != links.end(); ++i) f(i->second); -} - -void LinkRegistry::eachBridge(boost::function<void(boost::shared_ptr<Bridge>)> f) { - Mutex::ScopedLock locker(lock); - for (BridgeMap::iterator i = bridges.begin(); i != bridges.end(); ++i) f(i->second); -} - }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/LinkRegistry.h b/cpp/src/qpid/broker/LinkRegistry.h index 17f45a60c8..6689ceda0f 100644 --- a/cpp/src/qpid/broker/LinkRegistry.h +++ b/cpp/src/qpid/broker/LinkRegistry.h @@ -54,7 +54,6 @@ namespace broker { management::Manageable* parent; // MessageStore* store; AsyncStore* asyncStore; - bool passive; std::string realm; boost::shared_ptr<Link> findLink(const std::string& key); @@ -147,20 +146,6 @@ namespace broker { QPID_BROKER_EXTERN std::string getPassword (const std::string& key); QPID_BROKER_EXTERN std::string getHost (const std::string& key); QPID_BROKER_EXTERN uint16_t getPort (const std::string& key); - - /** - * Called to alter passive state. In passive state the links - * and bridges managed by a link registry will be recorded and - * updated but links won't actually establish connections and - * bridges won't therefore pull or push any messages. - */ - QPID_BROKER_EXTERN void setPassive(bool); - QPID_BROKER_EXTERN bool isPassive() { return passive; } - - /** Iterate over each link in the registry. Used for cluster updates. */ - QPID_BROKER_EXTERN void eachLink(boost::function<void(boost::shared_ptr<Link>)> f); - /** Iterate over each bridge in the registry. Used for cluster updates. */ - QPID_BROKER_EXTERN void eachBridge(boost::function<void(boost::shared_ptr< Bridge>)> f); }; } } diff --git a/cpp/src/qpid/broker/Lvq.cpp b/cpp/src/qpid/broker/Lvq.cpp index f5e66c8a74..9d6923e560 100644 --- a/cpp/src/qpid/broker/Lvq.cpp +++ b/cpp/src/qpid/broker/Lvq.cpp @@ -20,7 +20,6 @@ */ #include "Lvq.h" #include "MessageMap.h" -#include "qpid/sys/ClusterSafe.h" #include "qpid/sys/Monitor.h" namespace qpid { @@ -35,7 +34,6 @@ Lvq::Lvq(const std::string& n, std::auto_ptr<MessageMap> m, const QueueSettings& void Lvq::push(Message& message, bool isRecovery) { - qpid::sys::assertClusterSafe(); QueueListeners::NotificationSet copy; Message old; bool removed; diff --git a/cpp/src/qpid/broker/Message.cpp b/cpp/src/qpid/broker/Message.cpp index c48e9bcfa4..431f4291ff 100644 --- a/cpp/src/qpid/broker/Message.cpp +++ b/cpp/src/qpid/broker/Message.cpp @@ -131,6 +131,18 @@ uint64_t Message::getTtl() const } } +bool Message::getTtl(uint64_t ttl) const +{ + if (encoding->getTtl(ttl) && expiration < FAR_FUTURE) { + sys::Duration remaining(sys::AbsTime::now(), getExpiration()); + // convert from ns to ms; set to 0 if expired + ttl = (int64_t(remaining) >= 1000000 ? int64_t(remaining)/1000000 : 0); + return true; + } else { + return false; + } +} + void Message::computeExpiration(const boost::intrusive_ptr<ExpiryPolicy>& e) { //TODO: this is still quite 0-10 specific... diff --git a/cpp/src/qpid/broker/Message.h b/cpp/src/qpid/broker/Message.h index 599819d7b6..b999a7d5a3 100644 --- a/cpp/src/qpid/broker/Message.h +++ b/cpp/src/qpid/broker/Message.h @@ -61,7 +61,6 @@ public: virtual std::string getPropertyAsString(const std::string& key) const = 0; virtual std::string getAnnotationAsString(const std::string& key) const = 0; virtual bool getTtl(uint64_t&) const = 0; - virtual bool hasExpiration() const = 0; virtual std::string getContent() const = 0; virtual void processProperties(MapHandler&) const = 0; virtual std::string getUserId() const = 0; @@ -92,6 +91,7 @@ public: sys::AbsTime getExpiration() const { return expiration; } void setExpiration(sys::AbsTime exp) { expiration = exp; } uint64_t getTtl() const; + bool getTtl(uint64_t) const; /** set the timestamp delivery property to the current time-of-day */ QPID_BROKER_EXTERN void setTimestamp(); @@ -125,7 +125,7 @@ public: QPID_BROKER_EXTERN const qpid::types::Variant::Map& getAnnotations() const; std::string getUserId() const; - QPID_BROKER_EXTERN std::string getContent() const;//TODO: may be better to get rid of this... + QPID_BROKER_EXTERN std::string getContent() const;//Used for ha, management, when content needs to be decoded QPID_BROKER_EXTERN boost::intrusive_ptr<AsyncCompletion> getIngressCompletion() const; QPID_BROKER_EXTERN boost::intrusive_ptr<PersistableMessage> getPersistentContext() const; diff --git a/cpp/src/qpid/broker/MessageGroupManager.cpp b/cpp/src/qpid/broker/MessageGroupManager.cpp index 47e40a4794..c083e4ee0f 100644 --- a/cpp/src/qpid/broker/MessageGroupManager.cpp +++ b/cpp/src/qpid/broker/MessageGroupManager.cpp @@ -302,19 +302,6 @@ void MessageGroupManager::setDefaults(const std::string& groupId) // static defaultGroupId = groupId; } -/** Cluster replication: - - state map format: - - { "group-state": [ {"name": <group-name>, - "owner": <consumer-name>-or-empty, - "acquired-ct": <acquired count>, - "positions": [Seqnumbers, ... ]}, - {...} - ] - } -*/ - namespace { const std::string GROUP_NAME("name"); const std::string GROUP_OWNER("owner"); @@ -324,100 +311,3 @@ namespace { const std::string GROUP_STATE("group-state"); } - -/** Runs on UPDATER to snapshot current state */ -void MessageGroupManager::getState(qpid::framing::FieldTable& state ) const -{ - using namespace qpid::framing; - state.clear(); - framing::Array groupState(TYPE_CODE_MAP); - for (GroupMap::const_iterator g = messageGroups.begin(); - g != messageGroups.end(); ++g) { - - framing::FieldTable group; - group.setString(GROUP_NAME, g->first); - group.setString(GROUP_OWNER, g->second.owner); - group.setInt(GROUP_ACQUIRED_CT, g->second.acquired); - framing::Array positions(TYPE_CODE_UINT32); - framing::Array acquiredMsgs(TYPE_CODE_BOOLEAN); - for (GroupState::MessageFifo::const_iterator p = g->second.members.begin(); - p != g->second.members.end(); ++p) { - positions.push_back(framing::Array::ValuePtr(new IntegerValue( p->position ))); - acquiredMsgs.push_back(framing::Array::ValuePtr(new BoolValue( p->acquired ))); - } - group.setArray(GROUP_POSITIONS, positions); - group.setArray(GROUP_ACQUIRED_MSGS, acquiredMsgs); - groupState.push_back(framing::Array::ValuePtr(new FieldTableValue(group))); - } - state.setArray(GROUP_STATE, groupState); - - QPID_LOG(debug, "Queue \"" << qName << "\": replicating message group state, key=" << groupIdHeader); -} - - -/** called on UPDATEE to set state from snapshot */ -void MessageGroupManager::setState(const qpid::framing::FieldTable& state) -{ - using namespace qpid::framing; - messageGroups.clear(); - freeGroups.clear(); - cachedGroup = 0; - - framing::Array groupState(TYPE_CODE_MAP); - - bool ok = state.getArray(GROUP_STATE, groupState); - if (!ok) { - QPID_LOG(error, "Unable to find message group state information for queue \"" << - qName << "\": cluster inconsistency error!"); - return; - } - - for (framing::Array::const_iterator g = groupState.begin(); - g != groupState.end(); ++g) { - framing::FieldTable group; - ok = framing::getEncodedValue<FieldTable>(*g, group); - if (!ok) { - QPID_LOG(error, "Invalid message group state information for queue \"" << - qName << "\": table encoding error!"); - return; - } - MessageGroupManager::GroupState state; - if (!group.isSet(GROUP_NAME) || !group.isSet(GROUP_OWNER) || !group.isSet(GROUP_ACQUIRED_CT)) { - QPID_LOG(error, "Invalid message group state information for queue \"" << - qName << "\": fields missing error!"); - return; - } - state.group = group.getAsString(GROUP_NAME); - state.owner = group.getAsString(GROUP_OWNER); - state.acquired = group.getAsInt(GROUP_ACQUIRED_CT); - framing::Array positions(TYPE_CODE_UINT32); - ok = group.getArray(GROUP_POSITIONS, positions); - if (!ok) { - QPID_LOG(error, "Invalid message group state information for queue \"" << - qName << "\": position encoding error!"); - return; - } - framing::Array acquiredMsgs(TYPE_CODE_BOOLEAN); - ok = group.getArray(GROUP_ACQUIRED_MSGS, acquiredMsgs); - if (!ok || positions.count() != acquiredMsgs.count()) { - QPID_LOG(error, "Invalid message group state information for queue \"" << - qName << "\": acquired flag encoding error!"); - return; - } - - Array::const_iterator a = acquiredMsgs.begin(); - for (Array::const_iterator p = positions.begin(); p != positions.end(); ++p) { - GroupState::MessageState mState((*p)->getIntegerValue<uint32_t, 4>()); - mState.acquired = (*a++)->getIntegerValue<bool>(); - state.members.push_back(mState); - } - - messageGroups[state.group] = state; - if (!state.owned()) { - assert(state.members.size()); - freeGroups[state.members.front().position] = &messageGroups[state.group]; - } - } - - QPID_LOG(debug, "Queue \"" << qName << "\": message group state replicated, key =" << groupIdHeader) -} diff --git a/cpp/src/qpid/broker/MessageGroupManager.h b/cpp/src/qpid/broker/MessageGroupManager.h index fe39e007b5..bf45e776c8 100644 --- a/cpp/src/qpid/broker/MessageGroupManager.h +++ b/cpp/src/qpid/broker/MessageGroupManager.h @@ -25,11 +25,12 @@ /* for managing message grouping on Queues */ #include "qpid/broker/BrokerImportExport.h" -#include "qpid/broker/StatefulQueueObserver.h" +#include "qpid/broker/QueueObserver.h" #include "qpid/broker/MessageDistributor.h" #include "qpid/framing/SequenceNumber.h" #include "qpid/sys/unordered_map.h" +#include "boost/shared_ptr.hpp" #include <deque> namespace qpid { @@ -39,8 +40,9 @@ class QueueObserver; struct QueueSettings; class MessageDistributor; class Messages; +class Consumer; -class MessageGroupManager : public StatefulQueueObserver, public MessageDistributor +class MessageGroupManager : public QueueObserver, public MessageDistributor { static std::string defaultGroupId; // assigned if no group id header present @@ -101,10 +103,10 @@ class MessageGroupManager : public StatefulQueueObserver, public MessageDistribu MessageGroupManager(const std::string& header, const std::string& _qName, Messages& container, unsigned int _timestamp=0 ) - : StatefulQueueObserver(std::string("MessageGroupManager:") + header), - groupIdHeader( header ), timestamp(_timestamp), messages(container), qName(_qName), - hits(0), misses(0), - lastMsg(0), cachedGroup(0) {} + : groupIdHeader( header ), timestamp(_timestamp), messages(container), + qName(_qName), + hits(0), misses(0), + lastMsg(0), cachedGroup(0) {} virtual ~MessageGroupManager(); // QueueObserver iface @@ -114,8 +116,6 @@ class MessageGroupManager : public StatefulQueueObserver, public MessageDistribu void dequeued( const Message& qm ); void consumerAdded( const Consumer& ) {}; void consumerRemoved( const Consumer& ) {}; - void getState(qpid::framing::FieldTable& state ) const; - void setState(const qpid::framing::FieldTable&); // MessageDistributor iface bool acquire(const std::string& c, Message& ); diff --git a/cpp/src/qpid/broker/MessageStore.h b/cpp/src/qpid/broker/MessageStore.h index 5e339574fd..d6a7cea18b 100644 --- a/cpp/src/qpid/broker/MessageStore.h +++ b/cpp/src/qpid/broker/MessageStore.h @@ -49,20 +49,6 @@ class MessageStore : public TransactionalStore, public Recoverable { public: /** - * If called after initialization but before recovery, will discard the database - * content and reinitialize as though it were a new installation. If the parameter - * saveStoreContent is true, the content of the store will be saved in such a way - * that the truncate can be reversed. This is used when cluster nodes recover and - * must get their content from a cluster sync rather than directly from the store. - * - * @param saveStoreContent If true, will move content of the store to a backup - * location where they may be restored later if needed. It is - * not necessary to save more than one prior version of the - * store. - */ - virtual void truncateInit(const bool saveStoreContent = false) = 0; - - /** * Record the existence of a durable queue */ virtual void create(PersistableQueue& queue, diff --git a/cpp/src/qpid/broker/MessageStoreModule.cpp b/cpp/src/qpid/broker/MessageStoreModule.cpp index 4309ee8524..9214f5a732 100644 --- a/cpp/src/qpid/broker/MessageStoreModule.cpp +++ b/cpp/src/qpid/broker/MessageStoreModule.cpp @@ -44,11 +44,6 @@ MessageStoreModule::~MessageStoreModule() bool MessageStoreModule::init(const Options*) { return true; } -void MessageStoreModule::truncateInit(const bool pushDownStoreFiles) -{ - TRANSFER_EXCEPTION(store->truncateInit(pushDownStoreFiles)); -} - void MessageStoreModule::create(PersistableQueue& queue, const FieldTable& args) { TRANSFER_EXCEPTION(store->create(queue, args)); diff --git a/cpp/src/qpid/broker/MessageStoreModule.h b/cpp/src/qpid/broker/MessageStoreModule.h index e5c271f4fa..89d6ebdecb 100644 --- a/cpp/src/qpid/broker/MessageStoreModule.h +++ b/cpp/src/qpid/broker/MessageStoreModule.h @@ -44,7 +44,6 @@ class MessageStoreModule : public MessageStore MessageStoreModule(boost::shared_ptr<MessageStore>& store); bool init(const Options* options); - void truncateInit(const bool pushDownStoreFiles = false); std::auto_ptr<TransactionContext> begin(); std::auto_ptr<TPCTransactionContext> begin(const std::string& xid); void prepare(TPCTransactionContext& txn); diff --git a/cpp/src/qpid/broker/Messages.h b/cpp/src/qpid/broker/Messages.h index a94ac7e0bf..cd846a4973 100644 --- a/cpp/src/qpid/broker/Messages.h +++ b/cpp/src/qpid/broker/Messages.h @@ -91,13 +91,6 @@ class Messages virtual Message* find(const QueueCursor&) = 0; /** - * Add an already acquired message to the queue. - * Used by a cluster updatee to replicate acquired messages from the updater. - * Only need be implemented by subclasses that keep track of - * acquired messages. - */ - //virtual void updateAcquired(const QueuedMessage&) { } - /** * Apply, the functor to each message held */ virtual void foreach(Functor) = 0; diff --git a/cpp/src/qpid/broker/NullMessageStore.cpp b/cpp/src/qpid/broker/NullMessageStore.cpp index 209941875a..a038c4db63 100644 --- a/cpp/src/qpid/broker/NullMessageStore.cpp +++ b/cpp/src/qpid/broker/NullMessageStore.cpp @@ -54,8 +54,6 @@ NullMessageStore::NullMessageStore() : nextPersistenceId(1) {} bool NullMessageStore::init(const Options* /*options*/) {return true;} -void NullMessageStore::truncateInit(const bool /*pushDownStoreFiles*/) {} - void NullMessageStore::create(PersistableQueue& queue, const framing::FieldTable& /*args*/) { queue.setPersistenceId(nextPersistenceId++); diff --git a/cpp/src/qpid/broker/NullMessageStore.h b/cpp/src/qpid/broker/NullMessageStore.h index 799bf6b368..4b108e0331 100644 --- a/cpp/src/qpid/broker/NullMessageStore.h +++ b/cpp/src/qpid/broker/NullMessageStore.h @@ -47,7 +47,6 @@ class QPID_BROKER_CLASS_EXTERN NullMessageStore : public MessageStore QPID_BROKER_EXTERN NullMessageStore(); QPID_BROKER_EXTERN virtual bool init(const Options* options); - QPID_BROKER_EXTERN virtual void truncateInit(const bool pushDownStoreFiles = false); QPID_BROKER_EXTERN virtual std::auto_ptr<TransactionContext> begin(); QPID_BROKER_EXTERN virtual std::auto_ptr<TPCTransactionContext> begin(const std::string& xid); QPID_BROKER_EXTERN virtual void prepare(TPCTransactionContext& txn); diff --git a/cpp/src/qpid/broker/Protocol.cpp b/cpp/src/qpid/broker/Protocol.cpp new file mode 100644 index 0000000000..e236698142 --- /dev/null +++ b/cpp/src/qpid/broker/Protocol.cpp @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Protocol.h" +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/broker/amqp_0_10/MessageTransfer.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace broker { + +qpid::sys::ConnectionCodec* ProtocolRegistry::create(const qpid::framing::ProtocolVersion& v, qpid::sys::OutputControl& o, const std::string& id, const qpid::sys::SecuritySettings& s) +{ + qpid::sys::ConnectionCodec* codec = 0; + for (Protocols::const_iterator i = protocols.begin(); !codec && i != protocols.end(); ++i) { + codec = i->second->create(v, o, id, s); + } + return codec; +} +boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> ProtocolRegistry::translate(const Message& m) +{ + boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> transfer; + const qpid::broker::amqp_0_10::MessageTransfer* ptr = dynamic_cast<const qpid::broker::amqp_0_10::MessageTransfer*>(&m.getEncoding()); + if (ptr) transfer = boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer>(ptr); + for (Protocols::const_iterator i = protocols.begin(); !transfer && i != protocols.end(); ++i) { + transfer = i->second->translate(m); + } + if (!transfer) throw new Exception("Could not convert message into 0-10"); + return transfer; +} +boost::shared_ptr<RecoverableMessage> ProtocolRegistry::recover(qpid::framing::Buffer& b) +{ + boost::shared_ptr<RecoverableMessage> msg; + for (Protocols::const_iterator i = protocols.begin(); !msg && i != protocols.end(); ++i) { + msg = i->second->recover(b); + } + return msg; +} + +ProtocolRegistry::~ProtocolRegistry() +{ + for (Protocols::const_iterator i = protocols.begin(); i != protocols.end(); ++i) { + delete i->second; + } + protocols.clear(); +} +void ProtocolRegistry::add(const std::string& key, Protocol* protocol) +{ + protocols[key] = protocol; + QPID_LOG(info, "Loaded protocol " << key); +} + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Protocol.h b/cpp/src/qpid/broker/Protocol.h new file mode 100644 index 0000000000..2f268748fb --- /dev/null +++ b/cpp/src/qpid/broker/Protocol.h @@ -0,0 +1,82 @@ +#ifndef QPID_BROKER_PROTOCOL_H +#define QPID_BROKER_PROTOCOL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <map> +#include <string> +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace sys { +class ConnectionCodec; +class OutputControl; +struct SecuritySettings; +} +namespace framing { +class Buffer; +class ProtocolVersion; +} +namespace broker { +class Message; +class RecoverableMessage; +namespace amqp_0_10 { +class MessageTransfer; +} + +/** + * A simple abstraction allowing pluggable protocol(s) + * (versions). AMQP 0-10 is considered the default. Alternatives must + * provide a ConnectionCodec for encoding/decoding the protocol in + * full, a means of translating the native message format of that + * protocol into AMQP 0-10 and a means of recovering durable messages + * from disk. + */ +class Protocol +{ + public: + virtual ~Protocol() {} + virtual qpid::sys::ConnectionCodec* create(const qpid::framing::ProtocolVersion&, qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&) = 0; + virtual boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> translate(const Message&) = 0; + virtual boost::shared_ptr<RecoverableMessage> recover(qpid::framing::Buffer&) = 0; + + private: +}; + +class ProtocolRegistry : public Protocol +{ + public: + qpid::sys::ConnectionCodec* create(const qpid::framing::ProtocolVersion&, qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&); + boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> translate(const Message&); + boost::shared_ptr<RecoverableMessage> recover(qpid::framing::Buffer&); + + ~ProtocolRegistry(); + void add(const std::string&, Protocol*); + private: + //name may be useful for descriptive purposes or even for some + //limited manipulation of ordering + typedef std::map<std::string, Protocol*> Protocols; + Protocols protocols; +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_PROTOCOL_H*/ diff --git a/cpp/src/qpid/broker/Queue.cpp b/cpp/src/qpid/broker/Queue.cpp index f595b81724..bab6f2ea55 100644 --- a/cpp/src/qpid/broker/Queue.cpp +++ b/cpp/src/qpid/broker/Queue.cpp @@ -44,9 +44,9 @@ #include "qpid/framing/reply_exceptions.h" #include "qpid/framing/FieldTable.h" #include "qpid/framing/FieldValue.h" -#include "qpid/sys/ClusterSafe.h" #include "qpid/sys/Monitor.h" #include "qpid/sys/Time.h" +#include "qpid/sys/Timer.h" #include "qpid/types/Variant.h" #include "qmf/org/apache/qpid/broker/ArgsQueuePurge.h" #include "qmf/org/apache/qpid/broker/ArgsQueueReroute.h" @@ -79,8 +79,8 @@ namespace { inline void mgntEnqStats(const Message& msg, - _qmf::Queue* mgmtObject, - _qmf::Broker* brokerMgmtObject) + _qmf::Queue::shared_ptr mgmtObject, + _qmf::Broker::shared_ptr brokerMgmtObject) { if (mgmtObject != 0) { _qmf::Queue::PerThreadStats *qStats = mgmtObject->getStatistics(); @@ -103,8 +103,8 @@ inline void mgntEnqStats(const Message& msg, } inline void mgntDeqStats(const Message& msg, - _qmf::Queue* mgmtObject, - _qmf::Broker* brokerMgmtObject) + _qmf::Queue::shared_ptr mgmtObject, + _qmf::Broker::shared_ptr brokerMgmtObject) { if (mgmtObject != 0){ _qmf::Queue::PerThreadStats *qStats = mgmtObject->getStatistics(); @@ -166,13 +166,11 @@ void Queue::TxPublish::rollback() throw() } Queue::Queue(const string& _name, const QueueSettings& _settings, -// MessageStore* const _store, AsyncStore* const _asyncStore, Manageable* parent, Broker* b) : name(_name), -// store(_store), asyncStore(_asyncStore), owner(0), consumerCount(0), @@ -183,8 +181,6 @@ Queue::Queue(const string& _name, const QueueSettings& _settings, messages(new MessageDeque()), persistenceId(0), settings(b ? merge(_settings, b->getOptions()) : _settings), - mgmtObject(0), - brokerMgmtObject(0), eventMode(0), broker(b), deleted(false), @@ -199,27 +195,24 @@ Queue::Queue(const string& _name, const QueueSettings& _settings, qpid::amqp_0_10::translate(settings.asMap(), encodableSettings); if (parent != 0 && broker != 0) { ManagementAgent* agent = broker->getManagementAgent(); - if (agent != 0) { -// mgmtObject = new _qmf::Queue(agent, this, parent, _name, _store != 0, settings.autodelete); - mgmtObject = new _qmf::Queue(agent, this, parent, _name, _asyncStore != 0, settings.autodelete); + mgmtObject = _qmf::Queue::shared_ptr( + new _qmf::Queue(agent, this, parent, _name, _asyncStore != 0, settings.autodelete)); mgmtObject->set_arguments(settings.asMap()); -// agent->addObject(mgmtObject, 0, store != 0); agent->addObject(mgmtObject, 0, asyncStore != 0); - brokerMgmtObject = (qmf::org::apache::qpid::broker::Broker*) broker->GetManagementObject(); + brokerMgmtObject = boost::dynamic_pointer_cast<_qmf::Broker>(broker->GetManagementObject()); if (brokerMgmtObject) brokerMgmtObject->inc_queueCount(); } } + + if ( settings.isBrowseOnly ) { + QPID_LOG ( info, "Queue " << name << " is browse-only." ); + } } Queue::~Queue() { - if (mgmtObject != 0) { - mgmtObject->resourceDestroy(); - if (brokerMgmtObject) - brokerMgmtObject->dec_queueCount(); - } } bool isLocalTo(const OwnershipToken* token, const Message& msg) @@ -246,9 +239,6 @@ void Queue::deliver(Message msg, TxBuffer* txn){ //'link' for whatever protocol is used; that would let protocol //specific stuff be kept out the queue - // Check for deferred delivery in a cluster. - if (broker && broker->deferDelivery(name, msg)) - return; if (broker::amqp_0_10::MessageTransfer::isImmediateDeliveryRequired(msg) && getConsumerCount() == 0) { if (alternateExchange) { DeliverableMessage deliverable(msg, 0); @@ -307,7 +297,6 @@ void Queue::process(Message& msg) void Queue::release(const QueueCursor& position, bool markRedelivered) { - assertClusterSafe(); QueueListeners::NotificationSet copy; { Mutex::ScopedLock locker(messageLock); @@ -333,7 +322,6 @@ bool Queue::dequeueMessageAt(const SequenceNumber& position) boost::intrusive_ptr<PersistableMessage> pmsg; { Mutex::ScopedLock locker(messageLock); - assertClusterSafe(); QPID_LOG(debug, "Attempting to dequeue message at " << position); QueueCursor cursor; Message* msg = messages->find(position, &cursor); @@ -353,7 +341,6 @@ bool Queue::dequeueMessageAt(const SequenceNumber& position) bool Queue::acquire(const QueueCursor& position, const std::string& consumer) { Mutex::ScopedLock locker(messageLock); - assertClusterSafe(); Message* msg; msg = messages->find(position); @@ -375,12 +362,13 @@ bool Queue::acquire(const QueueCursor& position, const std::string& consumer) bool Queue::getNextMessage(Message& m, Consumer::shared_ptr& c) { - checkNotDeleted(c); + if (!checkNotDeleted(c)) return false; QueueListeners::NotificationSet set; while (true) { //TODO: reduce lock scope Mutex::ScopedLock locker(messageLock); - Message* msg = messages->next(*c); + QueueCursor cursor = c->getCursor(); // Save current position. + Message* msg = messages->next(*c); // Advances c. if (msg) { if (msg->hasExpired()) { QPID_LOG(debug, "Message expired from queue '" << name << "'"); @@ -419,6 +407,7 @@ bool Queue::getNextMessage(Message& m, Consumer::shared_ptr& c) } else { //message(s) are available but consumer hasn't got enough credit QPID_LOG(debug, "Consumer can't currently accept message from '" << name << "'"); + c->setCursor(cursor); // Restore cursor, will try again with credit if (c->preAcquires()) { //let someone else try listeners.populate(set); @@ -480,7 +469,6 @@ bool Queue::find(SequenceNumber pos, Message& msg) const void Queue::consume(Consumer::shared_ptr c, bool requestExclusive) { - assertClusterSafe(); { Mutex::ScopedLock locker(messageLock); // NOTE: consumerCount is actually a count of all @@ -488,6 +476,11 @@ void Queue::consume(Consumer::shared_ptr c, bool requestExclusive) // Check for exclusivity of acquiring consumers. size_t acquiringConsumers = consumerCount - browserCount; if (c->preAcquires()) { + if(settings.isBrowseOnly) { + throw NotAllowedException( + QPID_MSG("Queue " << name << " is browse only. Refusing acquiring consumer.")); + } + if(exclusive) { throw ResourceLockedException( QPID_MSG("Queue " << getName() @@ -502,22 +495,29 @@ void Queue::consume(Consumer::shared_ptr c, bool requestExclusive) } } } - else + else if(c->isCounted()) { browserCount++; - consumerCount++; - //reset auto deletion timer if necessary - if (settings.autoDeleteDelay && autoDeleteTask) { - autoDeleteTask->cancel(); } - observeConsumerAdd(*c, locker); + if(c->isCounted()) { + consumerCount++; + + //reset auto deletion timer if necessary + if (settings.autoDeleteDelay && autoDeleteTask) { + autoDeleteTask->cancel(); + } + + observeConsumerAdd(*c, locker); + } + } + if (mgmtObject != 0 && c->isCounted()) { + mgmtObject->inc_consumerCount(); } - if (mgmtObject != 0) - mgmtObject->inc_consumerCount (); } void Queue::cancel(Consumer::shared_ptr c) { removeListener(c); + if(c->isCounted()) { Mutex::ScopedLock locker(messageLock); consumerCount--; @@ -525,8 +525,9 @@ void Queue::cancel(Consumer::shared_ptr c) if(exclusive) exclusive = 0; observeConsumerRemove(*c, locker); } - if (mgmtObject != 0) - mgmtObject->dec_consumerCount (); + if (mgmtObject != 0 && c->isCounted()) { + mgmtObject->dec_consumerCount(); + } } /** @@ -733,7 +734,6 @@ uint32_t Queue::move(const Queue::shared_ptr destq, uint32_t qty, void Queue::push(Message& message, bool /*isRecovery*/) { - assertClusterSafe(); QueueListeners::NotificationSet copy; { Mutex::ScopedLock locker(messageLock); @@ -1101,8 +1101,16 @@ void Queue::destroyed() notifyDeleted(); { Mutex::ScopedLock lock(messageLock); + for_each(observers.begin(), observers.end(), + boost::bind(&QueueObserver::destroy, _1)); observers.clear(); } + + if (mgmtObject != 0) { + mgmtObject->resourceDestroy(); + if (brokerMgmtObject) + brokerMgmtObject->dec_queueCount(); + } } void Queue::notifyDeleted() @@ -1136,7 +1144,7 @@ void Queue::setPersistenceId(uint64_t _persistenceId) const { if (mgmtObject != 0 && persistenceId == 0 && externalQueueStore) { - ManagementObject* childObj = externalQueueStore->GetManagementObject(); + ManagementObject::shared_ptr childObj = externalQueueStore->GetManagementObject(); if (childObj != 0) childObj->setReference(mgmtObject->getObjectId()); } @@ -1180,6 +1188,7 @@ Queue::shared_ptr Queue::restore( QueueRegistry& queues, Buffer& buffer ) void Queue::setAlternateExchange(boost::shared_ptr<Exchange> exchange) { alternateExchange = exchange; + alternateExchange->incAlternateUsers(); if (mgmtObject) { if (exchange.get() != 0) mgmtObject->set_altExchange(exchange->GetManagementObject()->getObjectId()); @@ -1197,14 +1206,10 @@ void tryAutoDeleteImpl(Broker& broker, Queue::shared_ptr queue, const std::strin { if (broker.getQueues().destroyIf(queue->getName(), boost::bind(boost::mem_fn(&Queue::canAutoDelete), queue))) { - QPID_LOG(debug, "Auto-deleting " << queue->getName()); - queue->destroyed(); - - if (broker.getManagementAgent()) - broker.getManagementAgent()->raiseEvent(_qmf::EventQueueDelete(connectionId, userId, queue->getName())); - QPID_LOG_CAT(debug, model, "Delete queue. name:" << queue->getName() + QPID_LOG_CAT(debug, model, "Auto-delete queue: " << queue->getName() << " user:" << userId << " rhost:" << connectionId ); + queue->destroyed(); } } @@ -1233,7 +1238,7 @@ void Queue::tryAutoDelete(Broker& broker, Queue::shared_ptr queue, const std::st if (queue->settings.autoDeleteDelay && queue->canAutoDelete()) { AbsTime time(now(), Duration(queue->settings.autoDeleteDelay * TIME_SEC)); queue->autoDeleteTask = boost::intrusive_ptr<qpid::sys::TimerTask>(new AutoDeleteTask(broker, queue, connectionId, userId, time)); - broker.getClusterTimer().add(queue->autoDeleteTask); + broker.getTimer().add(queue->autoDeleteTask); QPID_LOG(debug, "Timed auto-delete for " << queue->getName() << " initiated"); } else { tryAutoDeleteImpl(broker, queue, connectionId, userId); @@ -1290,7 +1295,7 @@ void Queue::setExternalQueueStore(ExternalQueueStore* inst) { externalQueueStore = inst; if (inst) { - ManagementObject* childObj = inst->GetManagementObject(); + ManagementObject::shared_ptr childObj = inst->GetManagementObject(); if (childObj != 0 && mgmtObject != 0) childObj->setReference(mgmtObject->getObjectId()); } @@ -1378,9 +1383,9 @@ void Queue::countLoadedFromDisk(uint64_t size) const } -ManagementObject* Queue::GetManagementObject (void) const +ManagementObject::shared_ptr Queue::GetManagementObject(void) const { - return (ManagementObject*) mgmtObject; + return mgmtObject; } Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, string& etext) @@ -1459,6 +1464,16 @@ SequenceNumber Queue::getPosition() { return sequence; } +void Queue::getRange(framing::SequenceNumber& front, framing::SequenceNumber& back, + SubscriptionType type) +{ + Mutex::ScopedLock locker(messageLock); + QueueCursor cursor(type); + back = sequence; + Message* message = messages->next(cursor); + front = message ? message->getSequence() : back+1; +} + int Queue::getEventMode() { return eventMode; } void Queue::recoveryComplete(ExchangeRegistry& exchanges) @@ -1493,20 +1508,11 @@ void Queue::observeEnqueue(const Message& m, const Mutex::ScopedLock&) mgntEnqStats(m, mgmtObject, brokerMgmtObject); } -// Note: accessing listeners outside of lock is dangerous. Caller must ensure the queue's -// state is not changed while listeners is referenced. -QueueListeners& Queue::getListeners() { return listeners; } - -// Note: accessing messages outside of lock is dangerous. Caller must ensure the queue's -// state is not changed while messages is referenced. -Messages& Queue::getMessages() { return *messages; } -const Messages& Queue::getMessages() const { return *messages; } - -void Queue::checkNotDeleted(const Consumer::shared_ptr& c) +bool Queue::checkNotDeleted(const Consumer::shared_ptr& c) { - if (deleted && !c->hideDeletedError()) { + if (deleted && !c->hideDeletedError()) throw ResourceDeletedException(QPID_MSG("Queue " << getName() << " has been deleted.")); - } + return !deleted; } void Queue::addObserver(boost::shared_ptr<QueueObserver> observer) @@ -1641,7 +1647,7 @@ Queue::UsageBarrier::UsageBarrier(Queue& q) : parent(q), count(0) {} bool Queue::UsageBarrier::acquire() { - Monitor::ScopedLock l(parent.messageLock); /** @todo: use a dedicated lock instead of messageLock */ + Monitor::ScopedLock l(usageLock); if (parent.deleted) { return false; } else { @@ -1652,15 +1658,20 @@ bool Queue::UsageBarrier::acquire() void Queue::UsageBarrier::release() { - Monitor::ScopedLock l(parent.messageLock); - if (--count == 0) parent.messageLock.notifyAll(); + Monitor::ScopedLock l(usageLock); + if (--count == 0) usageLock.notifyAll(); } void Queue::UsageBarrier::destroy() { - Monitor::ScopedLock l(parent.messageLock); + Monitor::ScopedLock l(usageLock); parent.deleted = true; - while (count) parent.messageLock.wait(); + while (count) usageLock.wait(); +} + +void Queue::addArgument(const string& key, const types::Variant& value) { + settings.original.insert(types::Variant::Map::value_type(key, value)); + if (mgmtObject != 0) mgmtObject->set_arguments(settings.asMap()); } }} diff --git a/cpp/src/qpid/broker/Queue.h b/cpp/src/qpid/broker/Queue.h index 1294f813aa..bb713eba2b 100644 --- a/cpp/src/qpid/broker/Queue.h +++ b/cpp/src/qpid/broker/Queue.h @@ -38,7 +38,6 @@ #include "qpid/framing/SequenceNumber.h" #include "qpid/sys/AtomicValue.h" #include "qpid/sys/Monitor.h" -#include "qpid/sys/Timer.h" #include "qpid/management/Manageable.h" #include "qmf/org/apache/qpid/broker/Queue.h" #include "qmf/org/apache/qpid/broker/Broker.h" @@ -56,6 +55,9 @@ #include <algorithm> namespace qpid { +namespace sys { +class TimerTask; +} namespace broker { class Broker; class Exchange; @@ -83,6 +85,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, { Queue& parent; uint count; + qpid::sys::Monitor usageLock; UsageBarrier(Queue&); bool acquire(); @@ -142,18 +145,18 @@ class Queue : public boost::enable_shared_from_this<Queue>, * o consumerCount (TBD: move under separate lock) * o Queue::UsageBarrier (TBD: move under separate lock) */ - mutable qpid::sys::Monitor messageLock; + mutable qpid::sys::Mutex messageLock; mutable qpid::sys::Mutex ownershipLock; mutable uint64_t persistenceId; - const QueueSettings settings; + QueueSettings settings; qpid::framing::FieldTable encodableSettings; QueueDepth current; QueueBindings bindings; std::string alternateExchangeName; boost::shared_ptr<Exchange> alternateExchange; framing::SequenceNumber sequence; - qmf::org::apache::qpid::broker::Queue* mgmtObject; - qmf::org::apache::qpid::broker::Broker* brokerMgmtObject; + qmf::org::apache::qpid::broker::Queue::shared_ptr mgmtObject; + qmf::org::apache::qpid::broker::Broker::shared_ptr brokerMgmtObject; sys::AtomicValue<uint32_t> dequeueSincePurge; // Count dequeues since last purge. int eventMode; Observers observers; @@ -189,7 +192,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, int getEventMode(); void dequeueFromStore(boost::intrusive_ptr<PersistableMessage>); void abandoned(const Message& message); - void checkNotDeleted(const Consumer::shared_ptr&); + bool checkNotDeleted(const Consumer::shared_ptr&); void notifyDeleted(); uint32_t remove(uint32_t maxCount, MessagePredicate, MessageFunctor, SubscriptionType); virtual bool checkDepth(const QueueDepth& increment, const Message&); @@ -338,7 +341,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, * exclusive owner */ static Queue::shared_ptr restore(QueueRegistry& queues, framing::Buffer& buffer); - static void tryAutoDelete(Broker& broker, Queue::shared_ptr, const std::string& connectionId, const std::string& userId); + QPID_BROKER_EXTERN static void tryAutoDelete(Broker& broker, Queue::shared_ptr, const std::string& connectionId, const std::string& userId); virtual void setExternalQueueStore(ExternalQueueStore* inst); @@ -352,7 +355,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, QPID_BROKER_EXTERN void countLoadedFromDisk(uint64_t size) const; // Manageable entry points - QPID_BROKER_EXTERN management::ManagementObject* GetManagementObject (void) const; + QPID_BROKER_EXTERN management::ManagementObject::shared_ptr GetManagementObject(void) const; management::Manageable::status_t QPID_BROKER_EXTERN ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); QPID_BROKER_EXTERN void query(::qpid::types::Variant::Map&) const; @@ -382,15 +385,30 @@ class Queue : public boost::enable_shared_from_this<Queue>, * * The _caller_ must ensure that any messages after pos have been dequeued. * - * Used by HA/cluster code for queue replication. + * Used by HA code for queue replication. */ QPID_BROKER_EXTERN void setPosition(framing::SequenceNumber pos); /** *@return sequence number for the back of the queue. The next message pushed - * will be at getPosition+1 + * will be at getPosition()+1 */ QPID_BROKER_EXTERN framing::SequenceNumber getPosition(); + + /** + * Set front and back. + * If the queue is empty then front = back+1 (the first message to + * consume will be the next message pushed.) + * + *@param front = Position of first message to consume. + *@param back = getPosition(), next message pushed will be getPosition()+1 + *@param type Subscription type to use to determine the front. + */ + QPID_BROKER_EXTERN void getRange( + framing::SequenceNumber& front, framing::SequenceNumber& back, + SubscriptionType type=CONSUMER + ); + QPID_BROKER_EXTERN void addObserver(boost::shared_ptr<QueueObserver>); QPID_BROKER_EXTERN void removeObserver(boost::shared_ptr<QueueObserver>); QPID_BROKER_EXTERN void insertSequenceNumbers(const std::string& key); @@ -399,11 +417,6 @@ class Queue : public boost::enable_shared_from_this<Queue>, */ QPID_BROKER_EXTERN void recoveryComplete(ExchangeRegistry& exchanges); - // For cluster update - QPID_BROKER_EXTERN QueueListeners& getListeners(); - QPID_BROKER_EXTERN Messages& getMessages(); - QPID_BROKER_EXTERN const Messages& getMessages() const; - /** * Reserve space in policy for an enqueued message that * has been recovered in the prepared state (dtx only) @@ -420,6 +433,10 @@ class Queue : public boost::enable_shared_from_this<Queue>, uint32_t getDequeueSincePurge() { return dequeueSincePurge.get(); } QPID_BROKER_EXTERN void setDequeueSincePurge(uint32_t value); + + /** Add an argument to be included in management messages about this queue. */ + QPID_BROKER_EXTERN void addArgument(const std::string& key, const types::Variant& value); + friend class QueueFactory; }; } diff --git a/cpp/src/qpid/broker/QueueCleaner.cpp b/cpp/src/qpid/broker/QueueCleaner.cpp index 838bc28be8..8d9e3f43dd 100644 --- a/cpp/src/qpid/broker/QueueCleaner.cpp +++ b/cpp/src/qpid/broker/QueueCleaner.cpp @@ -18,15 +18,36 @@ * under the License. * */ -#include "qpid/broker/Queue.h" #include "qpid/broker/QueueCleaner.h" #include "qpid/broker/Broker.h" +#include "qpid/broker/Queue.h" +#include "qpid/sys/Timer.h" + +#include <boost/function.hpp> #include <boost/bind.hpp> namespace qpid { namespace broker { +namespace { + typedef boost::function0<void> FireFunction; + class Task : public sys::TimerTask + { + public: + Task(FireFunction f, sys::Duration duration); + void fire(); + private: + FireFunction fireFunction; + }; + + Task::Task(FireFunction f, qpid::sys::Duration d) : sys::TimerTask(d,"QueueCleaner"), fireFunction(f) {} + + void Task::fire() + { + fireFunction(); + } +} QueueCleaner::QueueCleaner(QueueRegistry& q, sys::Timer* t) : queues(q), timer(t) {} QueueCleaner::~QueueCleaner() @@ -37,7 +58,7 @@ QueueCleaner::~QueueCleaner() void QueueCleaner::start(qpid::sys::Duration p) { period = p; - task = new Task(*this, p); + task = new Task(boost::bind(&QueueCleaner::fired, this), p); timer->add(task); } @@ -45,14 +66,6 @@ void QueueCleaner::setTimer(qpid::sys::Timer* timer) { this->timer = timer; } - -QueueCleaner::Task::Task(QueueCleaner& p, qpid::sys::Duration d) : sys::TimerTask(d,"QueueCleaner"), parent(p) {} - -void QueueCleaner::Task::fire() -{ - parent.fired(); -} - namespace { struct CollectQueues { diff --git a/cpp/src/qpid/broker/QueueCleaner.h b/cpp/src/qpid/broker/QueueCleaner.h index ffebfe3e1b..896af1dcd5 100644 --- a/cpp/src/qpid/broker/QueueCleaner.h +++ b/cpp/src/qpid/broker/QueueCleaner.h @@ -23,9 +23,17 @@ */ #include "qpid/broker/BrokerImportExport.h" -#include "qpid/sys/Timer.h" +#include "qpid/sys/Time.h" + +#include <boost/intrusive_ptr.hpp> namespace qpid { + +namespace sys { + class Timer; + class TimerTask; +} + namespace broker { class QueueRegistry; @@ -39,16 +47,8 @@ class QueueCleaner QPID_BROKER_EXTERN ~QueueCleaner(); QPID_BROKER_EXTERN void start(sys::Duration period); QPID_BROKER_EXTERN void setTimer(sys::Timer* timer); - private: - class Task : public sys::TimerTask - { - public: - Task(QueueCleaner& parent, sys::Duration duration); - void fire(); - private: - QueueCleaner& parent; - }; + private: boost::intrusive_ptr<sys::TimerTask> task; QueueRegistry& queues; sys::Timer* timer; diff --git a/cpp/src/qpid/broker/QueueFlowLimit.cpp b/cpp/src/qpid/broker/QueueFlowLimit.cpp index 11b9cbae63..9b2e31c925 100644 --- a/cpp/src/qpid/broker/QueueFlowLimit.cpp +++ b/cpp/src/qpid/broker/QueueFlowLimit.cpp @@ -29,7 +29,6 @@ #include "qpid/log/Statement.h" #include "qpid/sys/Mutex.h" #include "qpid/broker/SessionState.h" -#include "qpid/sys/ClusterSafe.h" #include "qmf/org/apache/qpid/broker/Queue.h" @@ -66,10 +65,10 @@ namespace { QueueFlowLimit::QueueFlowLimit(Queue *_queue, uint32_t _flowStopCount, uint32_t _flowResumeCount, uint64_t _flowStopSize, uint64_t _flowResumeSize) - : StatefulQueueObserver(std::string("QueueFlowLimit")), queue(_queue), queueName("<unknown>"), + : queue(_queue), queueName("<unknown>"), flowStopCount(_flowStopCount), flowResumeCount(_flowResumeCount), flowStopSize(_flowStopSize), flowResumeSize(_flowResumeSize), - flowStopped(false), count(0), size(0), queueMgmtObj(0), broker(0) + flowStopped(false), count(0), size(0), broker(0) { uint32_t maxCount(0); uint64_t maxSize(0); @@ -79,7 +78,7 @@ QueueFlowLimit::QueueFlowLimit(Queue *_queue, if (queue->getSettings().maxDepth.hasCount()) maxCount = queue->getSettings().maxDepth.getCount(); if (queue->getSettings().maxDepth.hasCount()) maxSize = queue->getSettings().maxDepth.getSize(); broker = queue->getBroker(); - queueMgmtObj = dynamic_cast<_qmfBroker::Queue*> (queue->GetManagementObject()); + queueMgmtObj = boost::dynamic_pointer_cast<_qmfBroker::Queue> (queue->GetManagementObject()); if (queueMgmtObj) { queueMgmtObj->set_flowStopped(isFlowControlActive()); } @@ -130,11 +129,6 @@ void QueueFlowLimit::enqueued(const Message& msg) } if (flowStopped || !index.empty()) { - // ignore flow control if we are populating the queue due to cluster replication: - if (broker && broker->isClusterUpdatee()) { - QPID_LOG(trace, "Queue \"" << queueName << "\": ignoring flow control for msg pos=" << msg.getSequence()); - return; - } QPID_LOG(trace, "Queue \"" << queueName << "\": setting flow control for msg pos=" << msg.getSequence()); msg.getPersistentContext()->getIngressCompletion().startCompleter(); // don't complete until flow resumes bool unique; @@ -297,79 +291,8 @@ QueueFlowLimit *QueueFlowLimit::createLimit(Queue *queue, const QueueSettings& s return 0; } -/* Cluster replication */ - -namespace { - /** pack a set of sequence number ranges into a framing::Array */ - void buildSeqRangeArray(qpid::framing::Array *seqs, - const qpid::framing::SequenceNumber& first, - const qpid::framing::SequenceNumber& last) - { - seqs->push_back(qpid::framing::Array::ValuePtr(new Unsigned32Value(first))); - seqs->push_back(qpid::framing::Array::ValuePtr(new Unsigned32Value(last))); - } -} - -/** Runs on UPDATER to snapshot current state */ -void QueueFlowLimit::getState(qpid::framing::FieldTable& state ) const -{ - sys::Mutex::ScopedLock l(indexLock); - state.clear(); - - framing::SequenceSet ss; - if (!index.empty()) { - /* replicate the set of messages pending flow control */ - for (std::map<framing::SequenceNumber, Message >::const_iterator itr = index.begin(); - itr != index.end(); ++itr) { - ss.add(itr->first); - } - framing::Array seqs(TYPE_CODE_UINT32); - typedef boost::function<void(framing::SequenceNumber, framing::SequenceNumber)> arrayBuilder; - ss.for_each((arrayBuilder)boost::bind(&buildSeqRangeArray, &seqs, _1, _2)); - state.setArray("pendingMsgSeqs", seqs); - } - QPID_LOG(debug, "Queue \"" << queueName << "\": flow limit replicating pending msgs, range=" << ss); -} - - -/** called on UPDATEE to set state from snapshot */ -void QueueFlowLimit::setState(const qpid::framing::FieldTable& state) -{ - sys::Mutex::ScopedLock l(indexLock); - index.clear(); - - framing::SequenceSet fcmsg; - framing::Array seqArray(TYPE_CODE_UINT32); - if (state.getArray("pendingMsgSeqs", seqArray)) { - assert((seqArray.count() & 0x01) == 0); // must be even since they are sequence ranges - framing::Array::const_iterator i = seqArray.begin(); - while (i != seqArray.end()) { - framing::SequenceNumber first((*i)->getIntegerValue<uint32_t, 4>()); - ++i; - framing::SequenceNumber last((*i)->getIntegerValue<uint32_t, 4>()); - ++i; - fcmsg.add(first, last); - for (SequenceNumber seq = first; seq <= last; ++seq) { - Message msg; - queue->find(seq, msg); // fyi: may not be found if msg is acquired & unacked - bool unique; - unique = index.insert(std::pair<framing::SequenceNumber, Message >(seq, msg)).second; - // Like this to avoid tripping up unused variable warning when NDEBUG set - if (!unique) assert(unique); - } - } - } - - flowStopped = index.size() != 0; - if (queueMgmtObj) { - queueMgmtObj->set_flowStopped(isFlowControlActive()); - } - QPID_LOG(debug, "Queue \"" << queueName << "\": flow limit replicated the pending msgs, range=" << fcmsg) -} - - namespace qpid { - namespace broker { +namespace broker { std::ostream& operator<<(std::ostream& out, const QueueFlowLimit& f) { @@ -378,6 +301,6 @@ std::ostream& operator<<(std::ostream& out, const QueueFlowLimit& f) return out; } - } +} } diff --git a/cpp/src/qpid/broker/QueueFlowLimit.h b/cpp/src/qpid/broker/QueueFlowLimit.h index 1bcc388ceb..b9aa09ec3a 100644 --- a/cpp/src/qpid/broker/QueueFlowLimit.h +++ b/cpp/src/qpid/broker/QueueFlowLimit.h @@ -26,19 +26,13 @@ #include <iostream> #include <memory> #include "qpid/broker/BrokerImportExport.h" -#include "qpid/broker/StatefulQueueObserver.h" +#include "qpid/broker/QueueObserver.h" #include "qpid/framing/FieldTable.h" #include "qpid/framing/SequenceNumber.h" #include "qpid/sys/AtomicValue.h" #include "qpid/sys/Mutex.h" +#include "qmf/org/apache/qpid/broker/Queue.h" -namespace qmf { -namespace org { -namespace apache { -namespace qpid { -namespace broker { - class Queue; -}}}}} namespace _qmfBroker = qmf::org::apache::qpid::broker; namespace qpid { @@ -46,6 +40,7 @@ namespace broker { class Broker; class Queue; +class Message; struct QueueSettings; /** @@ -55,7 +50,7 @@ struct QueueSettings; * passing _either_ level may turn flow control ON, but _both_ must be * below level before flow control will be turned OFF. */ - class QueueFlowLimit : public StatefulQueueObserver + class QueueFlowLimit : public QueueObserver { static uint64_t defaultMaxSize; static uint defaultFlowStopRatio; @@ -90,10 +85,6 @@ struct QueueSettings; QPID_BROKER_EXTERN void acquired(const Message&) {}; QPID_BROKER_EXTERN void requeued(const Message&) {}; - /** for clustering: */ - QPID_BROKER_EXTERN void getState(qpid::framing::FieldTable&) const; - QPID_BROKER_EXTERN void setState(const qpid::framing::FieldTable&); - uint32_t getFlowStopCount() const { return flowStopCount; } uint32_t getFlowResumeCount() const { return flowResumeCount; } uint64_t getFlowStopSize() const { return flowStopSize; } @@ -118,7 +109,7 @@ struct QueueSettings; std::map<framing::SequenceNumber, Message > index; mutable qpid::sys::Mutex indexLock; - _qmfBroker::Queue *queueMgmtObj; + _qmfBroker::Queue::shared_ptr queueMgmtObj; const Broker *broker; diff --git a/cpp/src/qpid/broker/QueueObserver.h b/cpp/src/qpid/broker/QueueObserver.h index 29e867253e..2ba98f6945 100644 --- a/cpp/src/qpid/broker/QueueObserver.h +++ b/cpp/src/qpid/broker/QueueObserver.h @@ -69,6 +69,7 @@ class QueueObserver virtual void requeued(const Message&) = 0; virtual void consumerAdded( const Consumer& ) {}; virtual void consumerRemoved( const Consumer& ) {}; + virtual void destroy() {}; private: }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/QueueRegistry.cpp b/cpp/src/qpid/broker/QueueRegistry.cpp index eb525b6727..2fdcf3b8e6 100644 --- a/cpp/src/qpid/broker/QueueRegistry.cpp +++ b/cpp/src/qpid/broker/QueueRegistry.cpp @@ -23,10 +23,14 @@ #include "qpid/broker/QueueRegistry.h" #include "qpid/broker/Exchange.h" #include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" #include "qpid/framing/reply_exceptions.h" +#include "qmf/org/apache/qpid/broker/EventQueueDeclare.h" +#include "qmf/org/apache/qpid/broker/EventQueueDelete.h" #include <sstream> #include <assert.h> +namespace _qmf = qmf::org::apache::qpid::broker; using namespace qpid::broker; using namespace qpid::sys; using std::string; @@ -44,7 +48,10 @@ QueueRegistry::declare(const string& name, const QueueSettings& settings, bool recovering/*true if this declare is a result of recovering queue definition from persistent - record*/) + record*/, + const OwnershipToken* owner, + std::string connectionId, + std::string userId) { std::pair<Queue::shared_ptr, bool> result; { @@ -53,25 +60,38 @@ QueueRegistry::declare(const string& name, const QueueSettings& settings, if (i == queues.end()) { Queue::shared_ptr queue = create(name, settings); //Move this to factory also? - if (alternate) { + if (alternate) queue->setAlternateExchange(alternate);//need to do this *before* create - alternate->incAlternateUsers(); - } if (!recovering) { //create persistent record if required queue->create(); } queues[name] = queue; + // NOTE: raiseEvent and queueCreate must be called with the lock held in + // order to ensure events are generated in the correct order. + // Call queueCreate before raiseEvents so it can add arguments that + // will be included in the management event. + if (getBroker()) getBroker()->getConfigurationObservers().queueCreate(queue); result = std::pair<Queue::shared_ptr, bool>(queue, true); } else { result = std::pair<Queue::shared_ptr, bool>(i->second, false); } + if (getBroker() && getBroker()->getManagementAgent()) { + getBroker()->getManagementAgent()->raiseEvent( + _qmf::EventQueueDeclare( + connectionId, userId, name, + settings.durable, owner, settings.autodelete, + alternate ? alternate->getName() : string(), + result.first->getSettings().asMap(), + result.second ? "created" : "existing")); + } } - if (getBroker() && result.second) getBroker()->getConfigurationObservers().queueCreate(result.first); return result; } -void QueueRegistry::destroy(const string& name) { +void QueueRegistry::destroy( + const string& name, const string& connectionId, const string& userId) +{ Queue::shared_ptr q; { qpid::sys::RWlock::ScopedWlock locker(lock); @@ -79,9 +99,17 @@ void QueueRegistry::destroy(const string& name) { if (i != queues.end()) { q = i->second; queues.erase(i); + if (getBroker()) { + // NOTE: queueDestroy and raiseEvent must be called with the + // lock held in order to ensure events are generated + // in the correct order. + getBroker()->getConfigurationObservers().queueDestroy(q); + if (getBroker()->getManagementAgent()) + getBroker()->getManagementAgent()->raiseEvent( + _qmf::EventQueueDelete(connectionId, userId, name)); + } } } - if (getBroker() && q) getBroker()->getConfigurationObservers().queueDestroy(q); } Queue::shared_ptr QueueRegistry::find(const string& name){ diff --git a/cpp/src/qpid/broker/QueueRegistry.h b/cpp/src/qpid/broker/QueueRegistry.h index ada76f9cca..0170c441b3 100644 --- a/cpp/src/qpid/broker/QueueRegistry.h +++ b/cpp/src/qpid/broker/QueueRegistry.h @@ -59,7 +59,9 @@ class QueueRegistry : QueueFactory { const std::string& name, const QueueSettings& settings, boost::shared_ptr<Exchange> alternateExchange = boost::shared_ptr<Exchange>(), - bool recovering = false); + bool recovering = false, + const OwnershipToken* owner = 0, + std::string connectionId=std::string(), std::string userId=std::string()); /** * Destroy the named queue. @@ -73,7 +75,11 @@ class QueueRegistry : QueueFactory { * subsequent calls to find or declare with the same name. * */ - QPID_BROKER_EXTERN void destroy(const std::string& name); + QPID_BROKER_EXTERN void destroy( + const std::string& name, + const std::string& connectionId=std::string(), + const std::string& userId=std::string()); + template <class Test> bool destroyIf(const std::string& name, Test test) { if (test()) { diff --git a/cpp/src/qpid/broker/QueueSettings.cpp b/cpp/src/qpid/broker/QueueSettings.cpp index 91616636f1..b92a81bcf3 100644 --- a/cpp/src/qpid/broker/QueueSettings.cpp +++ b/cpp/src/qpid/broker/QueueSettings.cpp @@ -33,10 +33,13 @@ namespace broker { namespace { const std::string MAX_COUNT("qpid.max_count"); const std::string MAX_SIZE("qpid.max_size"); +const std::string MAX_FILE_COUNT("qpid.file_count"); +const std::string MAX_FILE_SIZE("qpid.file_size"); const std::string POLICY_TYPE("qpid.policy_type"); const std::string POLICY_TYPE_REJECT("reject"); const std::string POLICY_TYPE_RING("ring"); const std::string NO_LOCAL("no-local"); +const std::string BROWSE_ONLY("qpid.browse-only"); const std::string TRACE_ID("qpid.trace.id"); const std::string TRACE_EXCLUDES("qpid.trace.exclude"); const std::string LVQ_KEY("qpid.last_value_queue_key"); @@ -74,12 +77,14 @@ const QueueSettings::Aliases QueueSettings::aliases; QueueSettings::QueueSettings(bool d, bool a) : durable(d), autodelete(a), + isTemporary(false), priorities(0), defaultFairshare(0), shareGroups(false), addTimestamp(false), dropMessagesAtLimit(false), noLocal(false), + isBrowseOnly(false), autoDeleteDelay(0), alertRepeatInterval(60) {} @@ -106,6 +111,9 @@ bool QueueSettings::handle(const std::string& key, const qpid::types::Variant& v } else if (key == NO_LOCAL) { noLocal = value; return true; + } else if (key == BROWSE_ONLY) { + isBrowseOnly = value; + return true; } else if (key == TRACE_ID) { traceId = value.asString(); return true; @@ -163,6 +171,12 @@ bool QueueSettings::handle(const std::string& key, const qpid::types::Variant& v } else if (key == ALERT_SIZE) { alertThreshold.setSize(value); return true; + } else if (key == MAX_FILE_COUNT && value.asUint64() > 0) { + maxFileCount = value.asUint64(); + return false; // 'handle' here and also pass to store + } else if (key == MAX_FILE_SIZE && value.asUint64() > 0) { + maxFileSize = value.asUint64(); + return false; // 'handle' here and also pass to store } else { return false; } diff --git a/cpp/src/qpid/broker/QueueSettings.h b/cpp/src/qpid/broker/QueueSettings.h index 2443624615..62d34db5cb 100644 --- a/cpp/src/qpid/broker/QueueSettings.h +++ b/cpp/src/qpid/broker/QueueSettings.h @@ -43,6 +43,7 @@ struct QueueSettings bool durable; bool autodelete; + bool isTemporary; //basic queue types: std::string lvqKey; @@ -59,6 +60,7 @@ struct QueueSettings bool dropMessagesAtLimit;//aka ring queue policy bool noLocal; + bool isBrowseOnly; std::string traceId; std::string traceExcludes; uint64_t autoDeleteDelay;//queueTtl? @@ -71,6 +73,10 @@ struct QueueSettings QueueDepth alertThreshold; int64_t alertRepeatInterval; + //file limits checked by Acl and shared with storeSettings + uint64_t maxFileSize; + uint64_t maxFileCount; + //yuck, yuck qpid::framing::FieldTable storeSettings; std::map<std::string, qpid::types::Variant> original; diff --git a/cpp/src/qpid/broker/RecoverableExchange.h b/cpp/src/qpid/broker/RecoverableExchange.h index 6bda1e2617..2302a7d925 100644 --- a/cpp/src/qpid/broker/RecoverableExchange.h +++ b/cpp/src/qpid/broker/RecoverableExchange.h @@ -45,6 +45,9 @@ public: const std::string& routingKey, qpid::framing::FieldTable&, AsyncStore* const store) = 0; + + virtual std::string getName() const = 0; + virtual ~RecoverableExchange() {}; }; diff --git a/cpp/src/qpid/broker/RecoverableMessage.h b/cpp/src/qpid/broker/RecoverableMessage.h index c98857ceb0..aafcd756d5 100644 --- a/cpp/src/qpid/broker/RecoverableMessage.h +++ b/cpp/src/qpid/broker/RecoverableMessage.h @@ -22,12 +22,14 @@ * */ +#include <boost/intrusive_ptr.hpp> #include <boost/shared_ptr.hpp> #include "qpid/framing/amqp_types.h" #include "qpid/framing/Buffer.h" namespace qpid { namespace broker { +class ExpiryPolicy; /** * The interface through which messages are reloaded on recovery. @@ -38,6 +40,7 @@ public: typedef boost::shared_ptr<RecoverableMessage> shared_ptr; virtual void setPersistenceId(uint64_t id) = 0; virtual void setRedelivered() = 0; + virtual void computeExpiration(const boost::intrusive_ptr<ExpiryPolicy>& e) = 0; /** * Used by store to determine whether to load content on recovery * or let message load its own content as and when it requires it. diff --git a/cpp/src/qpid/broker/RecoverableMessageImpl.h b/cpp/src/qpid/broker/RecoverableMessageImpl.h new file mode 100644 index 0000000000..a46f5a3676 --- /dev/null +++ b/cpp/src/qpid/broker/RecoverableMessageImpl.h @@ -0,0 +1,49 @@ +#ifndef QPID_BROKER_RECOVERABLEMESSAGEIMPL_H +#define QPID_BROKER_RECOVERABLEMESSAGEIMPL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "RecoverableMessage.h" + +namespace qpid { +namespace broker { +class DtxBuffer; +class Message; +class Queue; + +class RecoverableMessageImpl : public RecoverableMessage +{ + Message msg; +public: + RecoverableMessageImpl(const Message& _msg); + ~RecoverableMessageImpl() {}; + void setPersistenceId(uint64_t id); + void setRedelivered(); + void computeExpiration(const boost::intrusive_ptr<ExpiryPolicy>& ep); + bool loadContent(uint64_t available); + void decodeContent(framing::Buffer& buffer); + void recover(boost::shared_ptr<Queue> queue); + void enqueue(boost::shared_ptr<DtxBuffer> buffer, boost::shared_ptr<Queue> queue); + void dequeue(boost::shared_ptr<DtxBuffer> buffer, boost::shared_ptr<Queue> queue); +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_RECOVERABLEMESSAGEIMPL_H*/ diff --git a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp index f3e1639ca5..901b3ad15a 100644 --- a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp +++ b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp @@ -25,6 +25,8 @@ #include "qpid/broker/Queue.h" #include "qpid/broker/Link.h" #include "qpid/broker/Bridge.h" +#include "qpid/broker/Protocol.h" +#include "qpid/broker/RecoverableMessageImpl.h" #include "qpid/broker/RecoveredEnqueue.h" #include "qpid/broker/RecoveredDequeue.h" #include "qpid/broker/amqp_0_10/MessageTransfer.h" @@ -38,26 +40,11 @@ namespace qpid { namespace broker { RecoveryManagerImpl::RecoveryManagerImpl(QueueRegistry& _queues, ExchangeRegistry& _exchanges, LinkRegistry& _links, - DtxManager& _dtxMgr) - : queues(_queues), exchanges(_exchanges), links(_links), dtxMgr(_dtxMgr) {} + DtxManager& _dtxMgr, ProtocolRegistry& p) + : queues(_queues), exchanges(_exchanges), links(_links), dtxMgr(_dtxMgr), protocols(p) {} RecoveryManagerImpl::~RecoveryManagerImpl() {} -class RecoverableMessageImpl : public RecoverableMessage -{ - Message msg; -public: - RecoverableMessageImpl(const Message& _msg); - ~RecoverableMessageImpl() {}; - void setPersistenceId(uint64_t id); - void setRedelivered(); - bool loadContent(uint64_t available); - void decodeContent(framing::Buffer& buffer); - void recover(Queue::shared_ptr queue); - void enqueue(DtxBuffer::shared_ptr buffer, Queue::shared_ptr queue); - void dequeue(DtxBuffer::shared_ptr buffer, Queue::shared_ptr queue); -}; - class RecoverableQueueImpl : public RecoverableQueue { Queue::shared_ptr queue; @@ -82,6 +69,7 @@ public: RecoverableExchangeImpl(Exchange::shared_ptr _exchange, QueueRegistry& _queues) : exchange(_exchange), queues(_queues) {} void setPersistenceId(uint64_t id); void bind(const std::string& queue, const std::string& routingKey, qpid::framing::FieldTable& args, AsyncStore* const store); + string getName() const { return exchange->getName(); } }; class RecoverableConfigImpl : public RecoverableConfig @@ -130,10 +118,15 @@ RecoverableQueue::shared_ptr RecoveryManagerImpl::recoverQueue(framing::Buffer& RecoverableMessage::shared_ptr RecoveryManagerImpl::recoverMessage(framing::Buffer& buffer) { - //TODO: determine encoding/version actually used - boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> transfer(new qpid::broker::amqp_0_10::MessageTransfer()); - transfer->decodeHeader(buffer); - return RecoverableMessage::shared_ptr(new RecoverableMessageImpl(Message(transfer, transfer))); + framing::Buffer sniffer(buffer.getPointer(), buffer.available()); + RecoverableMessage::shared_ptr m = protocols.recover(sniffer); + if (m) { + return m; + } else { + boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> transfer(new qpid::broker::amqp_0_10::MessageTransfer()); + transfer->decodeHeader(buffer); + return RecoverableMessage::shared_ptr(new RecoverableMessageImpl(Message(transfer, transfer))); + } } RecoverableTransaction::shared_ptr RecoveryManagerImpl::recoverTransaction(const std::string& xid, @@ -193,6 +186,11 @@ void RecoverableMessageImpl::setRedelivered() msg.deliver();//increment delivery count (but at present that isn't recorded durably) } +void RecoverableMessageImpl::computeExpiration(const boost::intrusive_ptr<ExpiryPolicy>& ep) +{ + msg.computeExpiration(ep); +} + void RecoverableQueueImpl::recover(RecoverableMessage::shared_ptr msg) { dynamic_pointer_cast<RecoverableMessageImpl>(msg)->recover(queue); diff --git a/cpp/src/qpid/broker/RecoveryManagerImpl.h b/cpp/src/qpid/broker/RecoveryManagerImpl.h index 7fca0be194..f824f4a540 100644 --- a/cpp/src/qpid/broker/RecoveryManagerImpl.h +++ b/cpp/src/qpid/broker/RecoveryManagerImpl.h @@ -30,15 +30,17 @@ namespace qpid { namespace broker { +class ProtocolRegistry; class RecoveryManagerImpl : public RecoveryManager{ QueueRegistry& queues; ExchangeRegistry& exchanges; LinkRegistry& links; DtxManager& dtxMgr; + ProtocolRegistry& protocols; public: RecoveryManagerImpl(QueueRegistry& queues, ExchangeRegistry& exchanges, LinkRegistry& links, - DtxManager& dtxMgr); + DtxManager& dtxMgr, ProtocolRegistry&); ~RecoveryManagerImpl(); RecoverableExchange::shared_ptr recoverExchange(framing::Buffer& buffer); diff --git a/cpp/src/qpid/broker/SaslAuthenticator.cpp b/cpp/src/qpid/broker/SaslAuthenticator.cpp index 2d7c820b63..8211657e04 100644 --- a/cpp/src/qpid/broker/SaslAuthenticator.cpp +++ b/cpp/src/qpid/broker/SaslAuthenticator.cpp @@ -24,6 +24,7 @@ #endif #include "qpid/broker/AclModule.h" +#include "qpid/broker/Broker.h" #include "qpid/broker/Connection.h" #include "qpid/log/Statement.h" #include "qpid/framing/reply_exceptions.h" @@ -169,14 +170,8 @@ void SaslAuthenticator::fini(void) std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c ) { if (c.getBroker().getOptions().auth) { - // The cluster creates non-authenticated connections for internal shadow connections - // that are never connected to an external client. - if ( !c.isAuthenticated() ) - return std::auto_ptr<SaslAuthenticator>( - new NullAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); - else - return std::auto_ptr<SaslAuthenticator>( - new CyrusAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); + return std::auto_ptr<SaslAuthenticator>( + new CyrusAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); } else { QPID_LOG(debug, "SASL: No Authentication Performed"); return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); @@ -424,7 +419,7 @@ void CyrusAuthenticator::start(const string& mechanism, const string* response) &challenge, &challenge_len); processAuthenticationStep(code, challenge, challenge_len); - qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); + qmf::org::apache::qpid::broker::Connection::shared_ptr cnxMgmt = connection.getMgmtObject(); if ( cnxMgmt ) cnxMgmt->set_saslMechanism(mechanism); } @@ -505,9 +500,9 @@ std::auto_ptr<SecurityLayer> CyrusAuthenticator::getSecurityLayer(uint16_t maxFr uint ssf = *(reinterpret_cast<const unsigned*>(value)); std::auto_ptr<SecurityLayer> securityLayer; if (ssf) { - securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize)); + securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize, ssf)); } - qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); + qmf::org::apache::qpid::broker::Connection::shared_ptr cnxMgmt = connection.getMgmtObject(); if ( cnxMgmt ) cnxMgmt->set_saslSsf(ssf); return securityLayer; diff --git a/cpp/src/qpid/broker/SecureConnection.cpp b/cpp/src/qpid/broker/SecureConnection.cpp index 5c1ebf3e8b..59ac9ef132 100644 --- a/cpp/src/qpid/broker/SecureConnection.cpp +++ b/cpp/src/qpid/broker/SecureConnection.cpp @@ -43,7 +43,7 @@ size_t SecureConnection::decode(const char* buffer, size_t size) } } -size_t SecureConnection::encode(const char* buffer, size_t size) +size_t SecureConnection::encode(char* buffer, size_t size) { if (secured) { return securityLayer->encode(buffer, size); diff --git a/cpp/src/qpid/broker/SecureConnection.h b/cpp/src/qpid/broker/SecureConnection.h index 1547faae1e..632087350e 100644 --- a/cpp/src/qpid/broker/SecureConnection.h +++ b/cpp/src/qpid/broker/SecureConnection.h @@ -43,7 +43,7 @@ class SecureConnection : public qpid::sys::ConnectionCodec public: SecureConnection(); size_t decode(const char* buffer, size_t size); - size_t encode(const char* buffer, size_t size); + size_t encode(char* buffer, size_t size); bool canEncode(); void closed(); bool isClosed() const; diff --git a/cpp/src/qpid/broker/SecureConnectionFactory.cpp b/cpp/src/qpid/broker/SecureConnectionFactory.cpp index 757f6efc59..7bc2c94d1c 100644 --- a/cpp/src/qpid/broker/SecureConnectionFactory.cpp +++ b/cpp/src/qpid/broker/SecureConnectionFactory.cpp @@ -19,19 +19,21 @@ * */ #include "qpid/broker/SecureConnectionFactory.h" -#include "qpid/framing/ProtocolVersion.h" + #include "qpid/amqp_0_10/Connection.h" +#include "qpid/broker/Broker.h" #include "qpid/broker/Connection.h" #include "qpid/broker/SecureConnection.h" -#include "qpid/sys/SecuritySettings.h" +#include "qpid/framing/ProtocolVersion.h" #include "qpid/log/Statement.h" +#include "qpid/sys/SecuritySettings.h" namespace qpid { namespace broker { using framing::ProtocolVersion; using qpid::sys::SecuritySettings; -typedef std::auto_ptr<amqp_0_10::Connection> CodecPtr; +typedef std::auto_ptr<qpid::amqp_0_10::Connection> CodecPtr; typedef std::auto_ptr<SecureConnection> SecureConnectionPtr; typedef std::auto_ptr<Connection> ConnectionPtr; typedef std::auto_ptr<sys::ConnectionInputHandler> InputPtr; @@ -43,12 +45,14 @@ SecureConnectionFactory::create(ProtocolVersion v, sys::OutputControl& out, cons const SecuritySettings& external) { if (v == ProtocolVersion(0, 10)) { SecureConnectionPtr sc(new SecureConnection()); - CodecPtr c(new amqp_0_10::Connection(out, id, false)); + CodecPtr c(new qpid::amqp_0_10::Connection(out, id, false)); ConnectionPtr i(new broker::Connection(c.get(), broker, id, external, false)); i->setSecureConnection(sc.get()); c->setInputHandler(InputPtr(i.release())); sc->setCodec(std::auto_ptr<sys::ConnectionCodec>(c)); return sc.release(); + } else { + return broker.getProtocolRegistry().create(v, out, id, external); } return 0; } @@ -58,7 +62,7 @@ SecureConnectionFactory::create(sys::OutputControl& out, const std::string& id, const SecuritySettings& external) { // used to create connections from one broker to another SecureConnectionPtr sc(new SecureConnection()); - CodecPtr c(new amqp_0_10::Connection(out, id, true)); + CodecPtr c(new qpid::amqp_0_10::Connection(out, id, true)); ConnectionPtr i(new broker::Connection(c.get(), broker, id, external, true )); i->setSecureConnection(sc.get()); c->setInputHandler(InputPtr(i.release())); diff --git a/cpp/src/qpid/broker/SemanticState.cpp b/cpp/src/qpid/broker/SemanticState.cpp index 5fc9a1a932..751b3ff709 100644 --- a/cpp/src/qpid/broker/SemanticState.cpp +++ b/cpp/src/qpid/broker/SemanticState.cpp @@ -20,6 +20,8 @@ */ #include "qpid/broker/SessionState.h" + +#include "qpid/broker/Broker.h" #include "qpid/broker/Connection.h" #include "qpid/broker/DeliverableMessage.h" #include "qpid/broker/DtxAck.h" @@ -35,12 +37,14 @@ #include "qpid/framing/SequenceSet.h" #include "qpid/framing/IsInSequenceSet.h" #include "qpid/log/Statement.h" -#include "qpid/sys/ClusterSafe.h" +#include "qpid/management/ManagementAgent.h" #include "qpid/ptr_map.h" #include "qpid/broker/AclModule.h" +#include "qpid/broker/FedOps.h" #include <boost/bind.hpp> #include <boost/format.hpp> +#include <boost/tuple/tuple_comparison.hpp> #include <iostream> #include <sstream> @@ -49,6 +53,11 @@ #include <assert.h> +namespace { +const std::string X_SCOPE("x-scope"); +const std::string SESSION("session"); +} + namespace qpid { namespace broker { @@ -88,6 +97,7 @@ void SemanticState::closed() { if (dtxBuffer.get()) { dtxBuffer->fail(); } + unbindSessionBindings(); requeue(); //now unsubscribe, which may trigger queue deletion and thus @@ -277,7 +287,7 @@ void SemanticState::record(const DeliveryRecord& delivery) const std::string QPID_SYNC_FREQUENCY("qpid.sync_frequency"); -SemanticState::ConsumerImpl::ConsumerImpl(SemanticState* _parent, +SemanticStateConsumerImpl::SemanticStateConsumerImpl(SemanticState* _parent, const string& _name, Queue::shared_ptr _queue, bool ack, @@ -303,7 +313,7 @@ Consumer(_name, type), notifyEnabled(true), syncFrequency(_arguments.getAsInt(QPID_SYNC_FREQUENCY)), deliveryCount(0), - mgmtObject(0) + protocols(parent->getSession().getBroker().getProtocolRegistry()) { if (parent != 0 && queue.get() != 0 && queue->GetManagementObject() !=0) { @@ -312,20 +322,20 @@ Consumer(_name, type), if (agent != 0) { - mgmtObject = new _qmf::Subscription(agent, this, ms , queue->GetManagementObject()->getObjectId(), getTag(), - !acquire, ackExpected, exclusive, ManagementAgent::toMap(arguments)); + mgmtObject = _qmf::Subscription::shared_ptr(new _qmf::Subscription(agent, this, ms , queue->GetManagementObject()->getObjectId(), getTag(), + !acquire, ackExpected, exclusive, ManagementAgent::toMap(arguments))); agent->addObject (mgmtObject); mgmtObject->set_creditMode("WINDOW"); } } } -ManagementObject* SemanticState::ConsumerImpl::GetManagementObject (void) const +ManagementObject::shared_ptr SemanticStateConsumerImpl::GetManagementObject (void) const { - return (ManagementObject*) mgmtObject; + return mgmtObject; } -Manageable::status_t SemanticState::ConsumerImpl::ManagementMethod (uint32_t methodId, Args&, string&) +Manageable::status_t SemanticStateConsumerImpl::ManagementMethod (uint32_t methodId, Args&, string&) { Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; @@ -335,24 +345,23 @@ Manageable::status_t SemanticState::ConsumerImpl::ManagementMethod (uint32_t met } -OwnershipToken* SemanticState::ConsumerImpl::getSession() +OwnershipToken* SemanticStateConsumerImpl::getSession() { return &(parent->session); } -bool SemanticState::ConsumerImpl::deliver(const QueueCursor& cursor, const Message& msg) +bool SemanticStateConsumerImpl::deliver(const QueueCursor& cursor, const Message& msg) { return deliver(cursor, msg, shared_from_this()); } -bool SemanticState::ConsumerImpl::deliver(const QueueCursor& cursor, const Message& msg, boost::shared_ptr<Consumer> consumer) +bool SemanticStateConsumerImpl::deliver(const QueueCursor& cursor, const Message& msg, boost::shared_ptr<Consumer> consumer) { - assertClusterSafe(); allocateCredit(msg); + boost::intrusive_ptr<const amqp_0_10::MessageTransfer> transfer = protocols.translate(msg); DeliveryRecord record(cursor, msg.getSequence(), queue, getTag(), - consumer, acquire, !ackExpected, credit.isWindowMode(), amqp_0_10::MessageTransfer::getRequiredCredit(msg)); + consumer, acquire, !ackExpected, credit.isWindowMode(), transfer->getRequiredCredit()); bool sync = syncFrequency && ++deliveryCount >= syncFrequency; if (sync) deliveryCount = 0;//reset - const amqp_0_10::MessageTransfer* transfer = dynamic_cast<const amqp_0_10::MessageTransfer*>(&msg.getEncoding()); record.setId(parent->session.deliver(*transfer, getTag(), msg.isRedelivered(), msg.getTtl(), msg.getTimestamp(), ackExpected ? message::ACCEPT_MODE_EXPLICIT : message::ACCEPT_MODE_NONE, @@ -370,27 +379,26 @@ bool SemanticState::ConsumerImpl::deliver(const QueueCursor& cursor, const Messa return true; } -bool SemanticState::ConsumerImpl::filter(const Message&) +bool SemanticStateConsumerImpl::filter(const Message&) { return true; } -bool SemanticState::ConsumerImpl::accept(const Message& msg) +bool SemanticStateConsumerImpl::accept(const Message& msg) { - assertClusterSafe(); // TODO aconway 2009-06-08: if we have byte & message credit but // checkCredit fails because the message is to big, we should // remain on queue's listener list for possible smaller messages // in future. // - blocked = !(filter(msg) && checkCredit(msg)); + blocked = !checkCredit(msg); return !blocked; } namespace { struct ConsumerName { - const SemanticState::ConsumerImpl& consumer; - ConsumerName(const SemanticState::ConsumerImpl& ci) : consumer(ci) {} + const SemanticStateConsumerImpl& consumer; + ConsumerName(const SemanticStateConsumerImpl& ci) : consumer(ci) {} }; ostream& operator<<(ostream& o, const ConsumerName& pc) { @@ -399,26 +407,27 @@ ostream& operator<<(ostream& o, const ConsumerName& pc) { } } -void SemanticState::ConsumerImpl::allocateCredit(const Message& msg) +void SemanticStateConsumerImpl::allocateCredit(const Message& msg) { - assertClusterSafe(); Credit original = credit; - credit.consume(1, qpid::broker::amqp_0_10::MessageTransfer::getRequiredCredit(msg)); + boost::intrusive_ptr<const amqp_0_10::MessageTransfer> transfer = protocols.translate(msg); + credit.consume(1, transfer->getRequiredCredit()); QPID_LOG(debug, "Credit allocated for " << ConsumerName(*this) << ", was " << original << " now " << credit); } -bool SemanticState::ConsumerImpl::checkCredit(const Message& msg) +bool SemanticStateConsumerImpl::checkCredit(const Message& msg) { - bool enoughCredit = credit.check(1, qpid::broker::amqp_0_10::MessageTransfer::getRequiredCredit(msg)); + boost::intrusive_ptr<const amqp_0_10::MessageTransfer> transfer = protocols.translate(msg); + bool enoughCredit = credit.check(1, transfer->getRequiredCredit()); QPID_LOG(debug, "Subscription " << ConsumerName(*this) << " has " << (enoughCredit ? "sufficient " : "insufficient") - << " credit for message of " << qpid::broker::amqp_0_10::MessageTransfer::getRequiredCredit(msg) << " bytes: " + << " credit for message of " << transfer->getRequiredCredit() << " bytes: " << credit); return enoughCredit; } -SemanticState::ConsumerImpl::~ConsumerImpl() +SemanticStateConsumerImpl::~SemanticStateConsumerImpl() { if (mgmtObject != 0) mgmtObject->resourceDestroy (); @@ -437,9 +446,9 @@ void SemanticState::cancel(ConsumerImpl::shared_ptr c) Queue::shared_ptr queue = c->getQueue(); if(queue) { queue->cancel(c); - if (queue->canAutoDelete() && !queue->hasExclusiveOwner()) { + // Only run auto-delete for counted consumers. + if (c->isCounted() && queue->canAutoDelete() && !queue->hasExclusiveOwner()) Queue::tryAutoDelete(session.getBroker(), queue, connectionId, userID); - } } c->cancel(); } @@ -491,9 +500,8 @@ void SemanticState::requestDispatch() i->second->requestDispatch(); } -void SemanticState::ConsumerImpl::requestDispatch() +void SemanticStateConsumerImpl::requestDispatch() { - assertClusterSafe(); if (blocked) { parent->session.getConnection().outputTasks.addOutputTask(this); parent->session.getConnection().outputTasks.activateOutput(); @@ -510,7 +518,7 @@ bool SemanticState::complete(DeliveryRecord& delivery) return delivery.isRedundant(); } -void SemanticState::ConsumerImpl::complete(DeliveryRecord& delivery) +void SemanticStateConsumerImpl::complete(DeliveryRecord& delivery) { if (!delivery.isComplete()) { delivery.complete(); @@ -535,7 +543,7 @@ SessionContext& SemanticState::getSession() { return session; } const SessionContext& SemanticState::getSession() const { return session; } -const SemanticState::ConsumerImpl::shared_ptr SemanticState::find(const std::string& destination) const +const SemanticStateConsumerImpl::shared_ptr SemanticState::find(const std::string& destination) const { ConsumerImpl::shared_ptr consumer; if (!find(destination, consumer)) { @@ -592,37 +600,33 @@ void SemanticState::stop(const std::string& destination) find(destination)->stop(); } -void SemanticState::ConsumerImpl::setWindowMode() +void SemanticStateConsumerImpl::setWindowMode() { - assertClusterSafe(); credit.setWindowMode(true); if (mgmtObject){ mgmtObject->set_creditMode("WINDOW"); } } -void SemanticState::ConsumerImpl::setCreditMode() +void SemanticStateConsumerImpl::setCreditMode() { - assertClusterSafe(); credit.setWindowMode(false); if (mgmtObject){ mgmtObject->set_creditMode("CREDIT"); } } -void SemanticState::ConsumerImpl::addByteCredit(uint32_t value) +void SemanticStateConsumerImpl::addByteCredit(uint32_t value) { - assertClusterSafe(); credit.addByteCredit(value); } -void SemanticState::ConsumerImpl::addMessageCredit(uint32_t value) +void SemanticStateConsumerImpl::addMessageCredit(uint32_t value) { - assertClusterSafe(); credit.addMessageCredit(value); } -bool SemanticState::ConsumerImpl::haveCredit() +bool SemanticStateConsumerImpl::haveCredit() { if (credit) { return true; @@ -632,21 +636,20 @@ bool SemanticState::ConsumerImpl::haveCredit() } } -bool SemanticState::ConsumerImpl::doDispatch() +bool SemanticStateConsumerImpl::doDispatch() { return queue->dispatch(shared_from_this()); } -void SemanticState::ConsumerImpl::flush() +void SemanticStateConsumerImpl::flush() { while(haveCredit() && doDispatch()) ; credit.cancel(); } -void SemanticState::ConsumerImpl::stop() +void SemanticStateConsumerImpl::stop() { - assertClusterSafe(); credit.cancel(); } @@ -700,7 +703,7 @@ void SemanticState::reject(DeliveryId first, DeliveryId last) getSession().setUnackedCount(unacked.size()); } -bool SemanticState::ConsumerImpl::doOutput() +bool SemanticStateConsumerImpl::doOutput() { try { return haveCredit() && doDispatch(); @@ -709,28 +712,26 @@ bool SemanticState::ConsumerImpl::doOutput() } } -void SemanticState::ConsumerImpl::enableNotify() +void SemanticStateConsumerImpl::enableNotify() { Mutex::ScopedLock l(lock); - assertClusterSafe(); notifyEnabled = true; } -void SemanticState::ConsumerImpl::disableNotify() +void SemanticStateConsumerImpl::disableNotify() { Mutex::ScopedLock l(lock); notifyEnabled = false; } -bool SemanticState::ConsumerImpl::isNotifyEnabled() const { +bool SemanticStateConsumerImpl::isNotifyEnabled() const { Mutex::ScopedLock l(lock); return notifyEnabled; } -void SemanticState::ConsumerImpl::notify() +void SemanticStateConsumerImpl::notify() { Mutex::ScopedLock l(lock); - assertClusterSafe(); if (notifyEnabled) { parent->session.getConnection().outputTasks.addOutputTask(this); parent->session.getConnection().outputTasks.activateOutput(); @@ -755,7 +756,6 @@ isInSequenceSetAnd(const SequenceSet& s, Predicate p) { } void SemanticState::accepted(const SequenceSet& commands) { - assertClusterSafe(); if (txBuffer.get()) { //in transactional mode, don't dequeue or remove, just //maintain set of acknowledged messages: @@ -815,4 +815,63 @@ void SemanticState::detached() } } +void SemanticState::addBinding(const string& queueName, const string& exchangeName, + const string& routingKey, const framing::FieldTable& arguments) +{ + QPID_LOG (debug, "SemanticState::addBinding [" + << "queue=" << queueName << ", " + << "exchange=" << exchangeName << ", " + << "key=" << routingKey << ", " + << "args=" << arguments << "]"); + std::string fedOp = arguments.getAsString(qpidFedOp); + if ((arguments.isSet(qpidFedOp)) && (fedOp.empty())) { + fedOp = fedOpBind; + } + std::string fedOrigin = arguments.getAsString(qpidFedOrigin); + if ((arguments.getAsString(X_SCOPE) == SESSION) || (fedOp == fedOpBind)) { + bindings.insert(boost::make_tuple(queueName, exchangeName, routingKey, fedOrigin)); + } + else if (fedOp == fedOpUnbind) { + bindings.erase(boost::make_tuple(queueName, exchangeName, routingKey, fedOrigin)); + } +} + +void SemanticState::removeBinding(const string& queueName, const string& exchangeName, + const string& routingKey) +{ + QPID_LOG (debug, "SemanticState::removeBinding [" + << "queue=" << queueName << ", " + << "exchange=" << exchangeName << ", " + << "key=" << routingKey) + bindings.erase(boost::make_tuple(queueName, exchangeName, routingKey, "")); +} + +void SemanticState::unbindSessionBindings() +{ + //unbind session-scoped bindings + for (Bindings::iterator i = bindings.begin(); i != bindings.end(); i++) { + QPID_LOG (debug, "SemanticState::unbindSessionBindings [" + << "queue=" << i->get<0>() << ", " + << "exchange=" << i->get<1>()<< ", " + << "key=" << i->get<2>() << ", " + << "fedOrigin=" << i->get<3>() << "]"); + try { + std::string fedOrigin = i->get<3>(); + if (!fedOrigin.empty()) { + framing::FieldTable fedArguments; + fedArguments.setString(qpidFedOp, fedOpUnbind); + fedArguments.setString(qpidFedOrigin, fedOrigin); + session.getBroker().bind(i->get<0>(), i->get<1>(), i->get<2>(), fedArguments, + userID, connectionId); + } else { + session.getBroker().unbind(i->get<0>(), i->get<1>(), i->get<2>(), + userID, connectionId); + } + } + catch (...) { + } + } + bindings.clear(); +} + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SemanticState.h b/cpp/src/qpid/broker/SemanticState.h index 9add663e24..ec48ca4753 100644 --- a/cpp/src/qpid/broker/SemanticState.h +++ b/cpp/src/qpid/broker/SemanticState.h @@ -46,10 +46,12 @@ #include <list> #include <map> +#include <set> #include <vector> #include <boost/enable_shared_from_this.hpp> #include <boost/cast.hpp> +#include <boost/tuple/tuple.hpp> namespace qpid { namespace broker { @@ -57,6 +59,7 @@ namespace broker { class Exchange; //class MessageStore; class AsyncStore; +class ProtocolRegistry; class SessionContext; class SessionState; @@ -74,104 +77,18 @@ class SessionState; * called when a client's socket is ready to write data. * */ +class SemanticStateConsumerImpl; class SemanticState : private boost::noncopyable { - public: - class ConsumerImpl : public Consumer, public sys::OutputTask, - public boost::enable_shared_from_this<ConsumerImpl>, - public management::Manageable - { - protected: - mutable qpid::sys::Mutex lock; - SemanticState* const parent; - private: - const boost::shared_ptr<Queue> queue; - const bool ackExpected; - const bool acquire; - bool blocked; - bool exclusive; - std::string resumeId; - const std::string tag; // <destination> from AMQP 0-10 Message.subscribe command - uint64_t resumeTtl; - framing::FieldTable arguments; - Credit credit; - bool notifyEnabled; - const int syncFrequency; - int deliveryCount; - qmf::org::apache::qpid::broker::Subscription* mgmtObject; - - bool checkCredit(const Message& msg); - void allocateCredit(const Message& msg); - bool haveCredit(); - - protected: - QPID_BROKER_EXTERN virtual bool doDispatch(); - size_t unacked() { return parent->unacked.size(); } - QPID_BROKER_EXTERN bool deliver(const QueueCursor&, const Message&, boost::shared_ptr<Consumer>); - - public: - typedef boost::shared_ptr<ConsumerImpl> shared_ptr; - - QPID_BROKER_EXTERN ConsumerImpl(SemanticState* parent, - const std::string& name, boost::shared_ptr<Queue> queue, - bool ack, SubscriptionType type, bool exclusive, - const std::string& tag, const std::string& resumeId, - uint64_t resumeTtl, const framing::FieldTable& arguments); - QPID_BROKER_EXTERN ~ConsumerImpl(); - QPID_BROKER_EXTERN OwnershipToken* getSession(); - QPID_BROKER_EXTERN bool deliver(const QueueCursor&, const Message&); - QPID_BROKER_EXTERN bool filter(const Message&); - QPID_BROKER_EXTERN bool accept(const Message&); - QPID_BROKER_EXTERN void cancel() {} - - QPID_BROKER_EXTERN void disableNotify(); - QPID_BROKER_EXTERN void enableNotify(); - QPID_BROKER_EXTERN void notify(); - QPID_BROKER_EXTERN bool isNotifyEnabled() const; - - QPID_BROKER_EXTERN void requestDispatch(); - - QPID_BROKER_EXTERN void setWindowMode(); - QPID_BROKER_EXTERN void setCreditMode(); - QPID_BROKER_EXTERN void addByteCredit(uint32_t value); - QPID_BROKER_EXTERN void addMessageCredit(uint32_t value); - QPID_BROKER_EXTERN void flush(); - QPID_BROKER_EXTERN void stop(); - QPID_BROKER_EXTERN void complete(DeliveryRecord&); - boost::shared_ptr<Queue> getQueue() const { return queue; } - bool isBlocked() const { return blocked; } - bool setBlocked(bool set) { std::swap(set, blocked); return set; } - - QPID_BROKER_EXTERN bool doOutput(); - - Credit& getCredit() { return credit; } - const Credit& getCredit() const { return credit; } - bool isAckExpected() const { return ackExpected; } - bool isAcquire() const { return acquire; } - bool isExclusive() const { return exclusive; } - std::string getResumeId() const { return resumeId; }; - const std::string& getTag() const { return tag; } - uint64_t getResumeTtl() const { return resumeTtl; } - uint32_t getDeliveryCount() const { return deliveryCount; } - void setDeliveryCount(uint32_t _deliveryCount) { deliveryCount = _deliveryCount; } - const framing::FieldTable& getArguments() const { return arguments; } - - SemanticState& getParent() { return *parent; } - const SemanticState& getParent() const { return *parent; } - - void acknowledged(const DeliveryRecord&) {} - - // manageable entry points - QPID_BROKER_EXTERN management::ManagementObject* - GetManagementObject(void) const; - - QPID_BROKER_EXTERN management::Manageable::status_t - ManagementMethod(uint32_t methodId, management::Args& args, std::string& text); - }; + friend class SemanticStateConsumerImpl; + public: + typedef SemanticStateConsumerImpl ConsumerImpl; typedef std::map<std::string, DtxBuffer::shared_ptr> DtxBufferMap; private: - typedef std::map<std::string, ConsumerImpl::shared_ptr> ConsumerImplMap; + typedef std::map<std::string, boost::shared_ptr<ConsumerImpl> > ConsumerImplMap; + typedef boost::tuple<std::string, std::string, std::string, std::string> Binding; + typedef std::set<Binding> Bindings; SessionState& session; ConsumerImplMap consumers; @@ -189,13 +106,16 @@ class SemanticState : private boost::noncopyable { //needed for queue delete events in auto-delete: const std::string connectionId; + Bindings bindings; + void checkDtxTimeout(); bool complete(DeliveryRecord&); AckRange findRange(DeliveryId first, DeliveryId last); void requestDispatch(); - void cancel(ConsumerImpl::shared_ptr); - void disable(ConsumerImpl::shared_ptr); + void cancel(boost::shared_ptr<ConsumerImpl>); + void disable(boost::shared_ptr<ConsumerImpl>); + void unbindSessionBindings(); public: @@ -205,8 +125,8 @@ class SemanticState : private boost::noncopyable { SessionContext& getSession(); const SessionContext& getSession() const; - const ConsumerImpl::shared_ptr find(const std::string& destination) const; - bool find(const std::string& destination, ConsumerImpl::shared_ptr&) const; + const boost::shared_ptr<ConsumerImpl> find(const std::string& destination) const; + bool find(const std::string& destination, boost::shared_ptr<ConsumerImpl>&) const; /** * Get named queue, never returns 0. @@ -257,11 +177,6 @@ class SemanticState : private boost::noncopyable { void detached(); void closed(); - // Used by cluster to re-create sessions - template <class F> void eachConsumer(F f) { - for(ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); ++i) - f(i->second); - } DeliveryRecords& getUnacked() { return unacked; } framing::SequenceSet getAccumulatedAck() const { return accumulatedAck; } TxBuffer::shared_ptr getTxBuffer() const { return txBuffer; } @@ -271,6 +186,104 @@ class SemanticState : private boost::noncopyable { void setAccumulatedAck(const framing::SequenceSet& s) { accumulatedAck = s; } void record(const DeliveryRecord& delivery); DtxBufferMap& getSuspendedXids() { return suspendedXids; } + + void addBinding(const std::string& queueName, const std::string& exchangeName, + const std::string& routingKey, const framing::FieldTable& arguments); + void removeBinding(const std::string& queueName, const std::string& exchangeName, + const std::string& routingKey); +}; + +class SemanticStateConsumerImpl : public Consumer, public sys::OutputTask, + public boost::enable_shared_from_this<SemanticStateConsumerImpl>, + public management::Manageable +{ + protected: + mutable qpid::sys::Mutex lock; + SemanticState* const parent; + private: + const boost::shared_ptr<Queue> queue; + const bool ackExpected; + const bool acquire; + bool blocked; + bool exclusive; + std::string resumeId; + const std::string tag; // <destination> from AMQP 0-10 Message.subscribe command + uint64_t resumeTtl; + framing::FieldTable arguments; + Credit credit; + bool notifyEnabled; + const int syncFrequency; + int deliveryCount; + qmf::org::apache::qpid::broker::Subscription::shared_ptr mgmtObject; + ProtocolRegistry& protocols; + + bool checkCredit(const Message& msg); + void allocateCredit(const Message& msg); + bool haveCredit(); + + protected: + QPID_BROKER_EXTERN virtual bool doDispatch(); + size_t unacked() { return parent->unacked.size(); } + QPID_BROKER_EXTERN bool deliver(const QueueCursor&, const Message&, boost::shared_ptr<Consumer>); + + public: + typedef boost::shared_ptr<SemanticStateConsumerImpl> shared_ptr; + + QPID_BROKER_EXTERN SemanticStateConsumerImpl(SemanticState* parent, + const std::string& name, boost::shared_ptr<Queue> queue, + bool ack, SubscriptionType type, bool exclusive, + const std::string& tag, const std::string& resumeId, + uint64_t resumeTtl, const framing::FieldTable& arguments); + QPID_BROKER_EXTERN ~SemanticStateConsumerImpl(); + QPID_BROKER_EXTERN OwnershipToken* getSession(); + QPID_BROKER_EXTERN bool deliver(const QueueCursor&, const Message&); + QPID_BROKER_EXTERN bool filter(const Message&); + QPID_BROKER_EXTERN bool accept(const Message&); + QPID_BROKER_EXTERN void cancel() {} + + QPID_BROKER_EXTERN void disableNotify(); + QPID_BROKER_EXTERN void enableNotify(); + QPID_BROKER_EXTERN void notify(); + QPID_BROKER_EXTERN bool isNotifyEnabled() const; + + QPID_BROKER_EXTERN void requestDispatch(); + + QPID_BROKER_EXTERN void setWindowMode(); + QPID_BROKER_EXTERN void setCreditMode(); + QPID_BROKER_EXTERN void addByteCredit(uint32_t value); + QPID_BROKER_EXTERN void addMessageCredit(uint32_t value); + QPID_BROKER_EXTERN void flush(); + QPID_BROKER_EXTERN void stop(); + QPID_BROKER_EXTERN void complete(DeliveryRecord&); + boost::shared_ptr<Queue> getQueue() const { return queue; } + bool isBlocked() const { return blocked; } + bool setBlocked(bool set) { std::swap(set, blocked); return set; } + + QPID_BROKER_EXTERN bool doOutput(); + + Credit& getCredit() { return credit; } + const Credit& getCredit() const { return credit; } + bool isAckExpected() const { return ackExpected; } + bool isAcquire() const { return acquire; } + bool isExclusive() const { return exclusive; } + std::string getResumeId() const { return resumeId; }; + const std::string& getTag() const { return tag; } + uint64_t getResumeTtl() const { return resumeTtl; } + uint32_t getDeliveryCount() const { return deliveryCount; } + void setDeliveryCount(uint32_t _deliveryCount) { deliveryCount = _deliveryCount; } + const framing::FieldTable& getArguments() const { return arguments; } + + SemanticState& getParent() { return *parent; } + const SemanticState& getParent() const { return *parent; } + + void acknowledged(const DeliveryRecord&) {} + + // manageable entry points + QPID_BROKER_EXTERN management::ManagementObject::shared_ptr + GetManagementObject(void) const; + + QPID_BROKER_EXTERN management::Manageable::status_t + ManagementMethod(uint32_t methodId, management::Args& args, std::string& text); }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionAdapter.cpp b/cpp/src/qpid/broker/SessionAdapter.cpp index cb2fe15b58..1ea18ea472 100644 --- a/cpp/src/qpid/broker/SessionAdapter.cpp +++ b/cpp/src/qpid/broker/SessionAdapter.cpp @@ -16,7 +16,10 @@ * */ #include "qpid/broker/SessionAdapter.h" + +#include "qpid/broker/Broker.h" #include "qpid/broker/Connection.h" +#include "qpid/broker/DtxTimeout.h" #include "qpid/broker/Queue.h" #include "qpid/Exception.h" #include "qpid/framing/reply_exceptions.h" @@ -98,17 +101,6 @@ void SessionAdapter::ExchangeHandlerImpl::declare(const string& exchange, const //exchange already there, not created checkType(response.first, type); checkAlternate(response.first, alternate); - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventExchangeDeclare(getConnection().getUrl(), - getConnection().getUserId(), - exchange, - type, - alternateExchange, - durable, - false, - ManagementAgent::toMap(args), - "existing")); QPID_LOG_CAT(debug, model, "Create exchange. name:" << exchange << " user:" << getConnection().getUserId() << " rhost:" << getConnection().getUrl() @@ -165,12 +157,14 @@ void SessionAdapter::ExchangeHandlerImpl::bind(const string& queueName, { getBroker().bind(queueName, exchangeName, routingKey, arguments, getConnection().getUserId(), getConnection().getUrl()); + state.addBinding(queueName, exchangeName, routingKey, arguments); } void SessionAdapter::ExchangeHandlerImpl::unbind(const string& queueName, const string& exchangeName, const string& routingKey) { + state.removeBinding(queueName, exchangeName, routingKey); getBroker().unbind(queueName, exchangeName, routingKey, getConnection().getUserId(), getConnection().getUrl()); } @@ -300,6 +294,8 @@ void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& } catch (const qpid::types::Exception& e) { throw InvalidArgumentException(e.what()); } + // Identify queues that won't survive a failover. + settings.isTemporary = exclusive && autoDelete && !settings.autoDeleteDelay; std::pair<Queue::shared_ptr, bool> queue_created = getBroker().createQueue(name, settings, @@ -318,11 +314,6 @@ void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& if (exclusive && queue->setExclusiveOwner(&session)) { exclusiveQueues.push_back(queue); } - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventQueueDeclare(getConnection().getUrl(), getConnection().getUserId(), - name, durable, exclusive, autoDelete, alternateExchange, ManagementAgent::toMap(arguments), - "existing")); QPID_LOG_CAT(debug, model, "Create queue. name:" << name << " user:" << getConnection().getUserId() << " rhost:" << getConnection().getUrl() @@ -422,6 +413,11 @@ SessionAdapter::MessageHandlerImpl::subscribe(const string& queueName, if(!destination.empty() && state.exists(destination)) throw NotAllowedException(QPID_MSG("Consumer tags must be unique")); + if (queue->getSettings().isBrowseOnly && acquireMode == 0) { + QPID_LOG(info, "Overriding request to consume from browse-only queue " << queue->getName()); + acquireMode = 1; + } + // We allow browsing (acquireMode == 1) of exclusive queues, this is required by HA. if (queue->hasExclusiveOwner() && !queue->isExclusiveOwner(&session) && acquireMode == 0) throw ResourceLockedException(QPID_MSG("Cannot subscribe to exclusive queue " diff --git a/cpp/src/qpid/broker/SessionHandler.cpp b/cpp/src/qpid/broker/SessionHandler.cpp index 9888d12be2..a6b008647f 100644 --- a/cpp/src/qpid/broker/SessionHandler.cpp +++ b/cpp/src/qpid/broker/SessionHandler.cpp @@ -19,8 +19,9 @@ */ #include "qpid/broker/SessionHandler.h" -#include "qpid/broker/SessionState.h" +#include "qpid/broker/Broker.h" #include "qpid/broker/Connection.h" +#include "qpid/broker/SessionState.h" #include "qpid/log/Statement.h" #include <boost/bind.hpp> @@ -34,9 +35,7 @@ using namespace qpid::sys; SessionHandler::SessionHandler(Connection& c, ChannelId ch) : qpid::amqp_0_10::SessionHandler(&c.getOutput(), ch), connection(c), - proxy(out), - clusterOrderProxy(c.getClusterOrderOutput() ? - new SetChannelProxy(ch, c.getClusterOrderOutput()) : 0) + proxy(out) {} SessionHandler::~SessionHandler() {} @@ -110,10 +109,7 @@ void SessionHandler::attachAs(const std::string& name) { SessionId id(connection.getUserId(), name); SessionState::Configuration config = connection.broker.getSessionManager().getSessionConfig(); - // Delay creating management object till attached(). In a cluster, - // only the active link broker calls attachAs but all brokers - // receive the subsequent attached() call. - session.reset(new SessionState(connection.getBroker(), *this, id, config, true)); + session.reset(new SessionState(connection.getBroker(), *this, id, config)); sendAttach(false); } diff --git a/cpp/src/qpid/broker/SessionHandler.h b/cpp/src/qpid/broker/SessionHandler.h index 21c736fa37..d42b7838bb 100644 --- a/cpp/src/qpid/broker/SessionHandler.h +++ b/cpp/src/qpid/broker/SessionHandler.h @@ -71,17 +71,6 @@ class SessionHandler : public qpid::amqp_0_10::SessionHandler { framing::AMQP_ClientProxy& getProxy() { return proxy; } const framing::AMQP_ClientProxy& getProxy() const { return proxy; } - /** - * If commands are sent based on the local time (e.g. in timers), they don't have - * a well-defined ordering across cluster nodes. - * This proxy is for sending such commands. In a clustered broker it will take steps - * to synchronize command order across the cluster. In a stand-alone broker - * it is just a synonym for getProxy() - */ - framing::AMQP_ClientProxy& getClusterOrderProxy() { - return clusterOrderProxy.get() ? *clusterOrderProxy : proxy; - } - virtual void handleDetach(); void attached(const std::string& name);//used by 'pushing' inter-broker bridges void attachAs(const std::string& name);//used by 'pulling' inter-broker bridges @@ -108,7 +97,6 @@ class SessionHandler : public qpid::amqp_0_10::SessionHandler { Connection& connection; framing::AMQP_ClientProxy proxy; std::auto_ptr<SessionState> session; - std::auto_ptr<SetChannelProxy> clusterOrderProxy; boost::shared_ptr<ErrorListener> errorListener; }; diff --git a/cpp/src/qpid/broker/SessionState.cpp b/cpp/src/qpid/broker/SessionState.cpp index 944cbad0aa..d71134548a 100644 --- a/cpp/src/qpid/broker/SessionState.cpp +++ b/cpp/src/qpid/broker/SessionState.cpp @@ -25,7 +25,6 @@ #include "qpid/broker/DeliveryRecord.h" #include "qpid/broker/SessionManager.h" #include "qpid/broker/SessionHandler.h" -#include "qpid/sys/ClusterSafe.h" #include "qpid/framing/AMQContentBody.h" #include "qpid/framing/AMQHeaderBody.h" #include "qpid/framing/AMQMethodBody.h" @@ -54,15 +53,14 @@ namespace _qmf = qmf::org::apache::qpid::broker; SessionState::SessionState( Broker& b, SessionHandler& h, const SessionId& id, - const SessionState::Configuration& config, bool delayManagement) + const SessionState::Configuration& config) : qpid::SessionState(id, config), broker(b), handler(&h), semanticState(*this), adapter(semanticState), - mgmtObject(0), asyncCommandCompleter(new AsyncCommandCompleter(this)) { - if (!delayManagement) addManagementObject(); + addManagementObject(); attach(h); } @@ -72,8 +70,8 @@ void SessionState::addManagementObject() { if (parent != 0) { ManagementAgent* agent = getBroker().getManagementAgent(); if (agent != 0) { - mgmtObject = new _qmf::Session - (agent, this, parent, getId().getName()); + mgmtObject = _qmf::Session::shared_ptr(new _qmf::Session + (agent, this, parent, getId().getName())); mgmtObject->set_attached (0); mgmtObject->set_detachedLifespan (0); mgmtObject->clr_expireTime(); @@ -145,14 +143,9 @@ void SessionState::activateOutput() { getConnection().outputTasks.activateOutput(); } -void SessionState::giveReadCredit(int32_t credit) { - if (isAttached()) - getConnection().outputTasks.giveReadCredit(credit); -} - -ManagementObject* SessionState::GetManagementObject (void) const +ManagementObject::shared_ptr SessionState::GetManagementObject(void) const { - return (ManagementObject*) mgmtObject; + return mgmtObject; } Manageable::status_t SessionState::ManagementMethod (uint32_t methodId, @@ -251,11 +244,6 @@ void SessionState::completeRcvMsg(SequenceNumber id, bool requiresAccept, bool requiresSync) { - // Mark this as a cluster-unsafe scope since it can be called in - // journal threads or connection threads as part of asynchronous - // command completion. - sys::ClusterUnsafeScope cus; - bool callSendCompletion = false; receiverCompleted(id); if (requiresAccept) @@ -340,15 +328,9 @@ void SessionState::readyToSend() { Broker& SessionState::getBroker() { return broker; } // Session resume is not fully implemented so it is useless to set a -// non-0 timeout. Moreover it creates problems in a cluster because -// dead sessions are kept and interfere with failover. +// non-0 timeout. void SessionState::setTimeout(uint32_t) { } -framing::AMQP_ClientProxy& SessionState::getClusterOrderProxy() { - return handler->getClusterOrderProxy(); -} - - // Current received command is an execution.sync command. // Complete this command only when all preceding commands have completed. // (called via the invoker() in handleCommand() above) diff --git a/cpp/src/qpid/broker/SessionState.h b/cpp/src/qpid/broker/SessionState.h index 5e3a77d7ed..a531ec9fc6 100644 --- a/cpp/src/qpid/broker/SessionState.h +++ b/cpp/src/qpid/broker/SessionState.h @@ -41,6 +41,7 @@ #include <boost/scoped_ptr.hpp> #include <boost/intrusive_ptr.hpp> +#include <queue> #include <set> #include <vector> #include <ostream> @@ -73,7 +74,7 @@ class SessionState : public qpid::SessionState, { public: SessionState(Broker&, SessionHandler&, const SessionId&, - const SessionState::Configuration&, bool delayManagement=false); + const SessionState::Configuration&); ~SessionState(); bool isAttached() const { return handler; } @@ -98,7 +99,6 @@ class SessionState : public qpid::SessionState, /** OutputControl **/ void abort(); void activateOutput(); - void giveReadCredit(int32_t); void senderCompleted(const framing::SequenceSet& ranges); @@ -110,17 +110,12 @@ class SessionState : public qpid::SessionState, const qpid::types::Variant::Map& annotations, bool sync); // Manageable entry points - management::ManagementObject* GetManagementObject (void) const; + management::ManagementObject::shared_ptr GetManagementObject(void) const; management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string&); void readyToSend(); - // Used by cluster to create replica sessions. - SemanticState& getSemanticState() { return semanticState; } - boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> getMessageInProgress() { return msgBuilder.getMessage(); } - SessionAdapter& getSessionAdapter() { return adapter; } - const SessionId& getSessionId() const { return getId(); } // Used by ExecutionHandler sync command processing. Notifies @@ -153,22 +148,13 @@ class SessionState : public qpid::SessionState, void sendAcceptAndCompletion(); - /** - * If commands are sent based on the local time (e.g. in timers), they don't have - * a well-defined ordering across cluster nodes. - * This proxy is for sending such commands. In a clustered broker it will take steps - * to synchronize command order across the cluster. In a stand-alone broker - * it is just a synonym for getProxy() - */ - framing::AMQP_ClientProxy& getClusterOrderProxy(); - Broker& broker; SessionHandler* handler; sys::AbsTime expiry; // Used by SessionManager. SemanticState semanticState; SessionAdapter adapter; MessageBuilder msgBuilder; - qmf::org::apache::qpid::broker::Session* mgmtObject; + qmf::org::apache::qpid::broker::Session::shared_ptr mgmtObject; qpid::framing::SequenceSet accepted; // sequence numbers for pending received Execution.Sync commands diff --git a/cpp/src/qpid/broker/StatefulQueueObserver.h b/cpp/src/qpid/broker/StatefulQueueObserver.h deleted file mode 100644 index c682d460b7..0000000000 --- a/cpp/src/qpid/broker/StatefulQueueObserver.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef QPID_BROKER_STATEFULQUEUEOBSERVER_H -#define QPID_BROKER_STATEFULQUEUEOBSERVER_H - -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -#include "qpid/broker/QueueObserver.h" -#include "qpid/framing/FieldTable.h" - -namespace qpid { -namespace broker { - -/** - * Specialized type of QueueObserver that maintains internal state that has to - * be replicated across clustered brokers. - */ -class StatefulQueueObserver : public QueueObserver -{ - public: - StatefulQueueObserver(std::string _id) : id(_id) {} - virtual ~StatefulQueueObserver() {} - - /** This identifier must uniquely identify this particular observer amoung - * all observers on a queue. For cluster replication, this id will be used - * to identify the peer queue observer for synchronization across - * brokers. - */ - const std::string& getId() const { return id; } - - /** This method should return the observer's internal state as an opaque - * map. - */ - virtual void getState(qpid::framing::FieldTable& state ) const = 0; - - /** The input map represents the internal state of the peer observer that - * this observer should synchonize to. - */ - virtual void setState(const qpid::framing::FieldTable&) = 0; - - - private: - std::string id; -}; -}} // namespace qpid::broker - -#endif /*!QPID_BROKER_STATEFULQUEUEOBSERVER_H*/ diff --git a/cpp/src/qpid/broker/System.cpp b/cpp/src/qpid/broker/System.cpp index fa8df6406b..8d54427fdc 100644 --- a/cpp/src/qpid/broker/System.cpp +++ b/cpp/src/qpid/broker/System.cpp @@ -31,7 +31,7 @@ using namespace qpid::broker; using namespace std; namespace _qmf = qmf::org::apache::qpid::broker; -System::System (string _dataDir, Broker* broker) : mgmtObject(0) +System::System (string _dataDir, Broker* broker) { ManagementAgent* agent = broker ? broker->getManagementAgent() : 0; @@ -64,7 +64,7 @@ System::System (string _dataDir, Broker* broker) : mgmtObject(0) } } - mgmtObject = new _qmf::System(agent, this, types::Uuid(systemId.c_array())); + mgmtObject = _qmf::System::shared_ptr(new _qmf::System(agent, this, types::Uuid(systemId.c_array()))); qpid::sys::SystemInfo::getSystemId (osName, nodeName, release, diff --git a/cpp/src/qpid/broker/System.h b/cpp/src/qpid/broker/System.h index 6847c662ae..52643fb2d5 100644 --- a/cpp/src/qpid/broker/System.h +++ b/cpp/src/qpid/broker/System.h @@ -35,7 +35,7 @@ class System : public management::Manageable { private: - qmf::org::apache::qpid::broker::System* mgmtObject; + qmf::org::apache::qpid::broker::System::shared_ptr mgmtObject; framing::Uuid systemId; std::string osName, nodeName, release, version, machine; @@ -45,7 +45,7 @@ class System : public management::Manageable System (std::string _dataDir, Broker* broker = 0); - management::ManagementObject* GetManagementObject (void) const + management::ManagementObject::shared_ptr GetManagementObject(void) const { return mgmtObject; } diff --git a/cpp/src/qpid/broker/TopicExchange.cpp b/cpp/src/qpid/broker/TopicExchange.cpp index 38d8f255ac..0badd1621c 100644 --- a/cpp/src/qpid/broker/TopicExchange.cpp +++ b/cpp/src/qpid/broker/TopicExchange.cpp @@ -179,7 +179,7 @@ bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, cons } } - Binding::shared_ptr binding (new Binding (routingPattern, queue, this, FieldTable(), fedOrigin)); + Binding::shared_ptr binding (new Binding (routingPattern, queue, this, args ? *args : FieldTable(), fedOrigin)); binding->startManagement(); bk->bindingVector.push_back(binding); nBindings++; diff --git a/cpp/src/qpid/broker/TxAccept.h b/cpp/src/qpid/broker/TxAccept.h index a59e69a85f..daf192285a 100644 --- a/cpp/src/qpid/broker/TxAccept.h +++ b/cpp/src/qpid/broker/TxAccept.h @@ -71,9 +71,6 @@ namespace qpid { virtual void commit() throw(); virtual void rollback() throw(); virtual ~TxAccept(){} - - // Used by cluster replication. - const framing::SequenceSet& getAcked() const { return acked; } }; } } diff --git a/cpp/src/qpid/broker/TxOpVisitor.h b/cpp/src/qpid/broker/TxOpVisitor.h deleted file mode 100644 index e69de29bb2..0000000000 --- a/cpp/src/qpid/broker/TxOpVisitor.h +++ /dev/null diff --git a/cpp/src/qpid/broker/TxPublish.cpp b/cpp/src/qpid/broker/TxPublish.cpp deleted file mode 100644 index e69de29bb2..0000000000 --- a/cpp/src/qpid/broker/TxPublish.cpp +++ /dev/null diff --git a/cpp/src/qpid/broker/TxPublish.h b/cpp/src/qpid/broker/TxPublish.h deleted file mode 100644 index e69de29bb2..0000000000 --- a/cpp/src/qpid/broker/TxPublish.h +++ /dev/null diff --git a/cpp/src/qpid/broker/Vhost.cpp b/cpp/src/qpid/broker/Vhost.cpp index a9ca3b42ab..e72118b570 100644 --- a/cpp/src/qpid/broker/Vhost.cpp +++ b/cpp/src/qpid/broker/Vhost.cpp @@ -29,7 +29,7 @@ namespace qpid { namespace management { class Manageable; }} -Vhost::Vhost (qpid::management::Manageable* parentBroker, Broker* broker) : mgmtObject(0) +Vhost::Vhost (qpid::management::Manageable* parentBroker, Broker* broker) { if (parentBroker != 0 && broker != 0) { @@ -37,7 +37,7 @@ Vhost::Vhost (qpid::management::Manageable* parentBroker, Broker* broker) : mgmt if (agent != 0) { - mgmtObject = new _qmf::Vhost(agent, this, parentBroker, "/"); + mgmtObject = _qmf::Vhost::shared_ptr(new _qmf::Vhost(agent, this, parentBroker, "/")); agent->addObject(mgmtObject, 0, true); } } diff --git a/cpp/src/qpid/broker/Vhost.h b/cpp/src/qpid/broker/Vhost.h index 9554d641c2..599b821870 100644 --- a/cpp/src/qpid/broker/Vhost.h +++ b/cpp/src/qpid/broker/Vhost.h @@ -32,7 +32,7 @@ class Vhost : public management::Manageable { private: - qmf::org::apache::qpid::broker::Vhost* mgmtObject; + qmf::org::apache::qpid::broker::Vhost::shared_ptr mgmtObject; public: @@ -40,7 +40,7 @@ class Vhost : public management::Manageable Vhost (management::Manageable* parentBroker, Broker* broker = 0); - management::ManagementObject* GetManagementObject (void) const + management::ManagementObject::shared_ptr GetManagementObject (void) const { return mgmtObject; } void setFederationTag(const std::string& tag); }; diff --git a/cpp/src/qpid/broker/amqp/Connection.cpp b/cpp/src/qpid/broker/amqp/Connection.cpp new file mode 100644 index 0000000000..1f135cf931 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Connection.cpp @@ -0,0 +1,247 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Connection.h" +#include "Session.h" +#include "qpid/Exception.h" +#include "qpid/broker/Broker.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/ProtocolInitiation.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/OutputControl.h" +#include <sstream> +extern "C" { +#include <proton/engine.h> +#include <proton/error.h> +} + +namespace qpid { +namespace broker { +namespace amqp { + +Connection::Connection(qpid::sys::OutputControl& o, const std::string& i, qpid::broker::Broker& b, bool saslInUse) + : ManagedConnection(b, i), + connection(pn_connection()), + transport(pn_transport()), + out(o), id(i), broker(b), haveOutput(true) +{ + if (pn_transport_bind(transport, connection)) { + //error + } + out.activateOutput(); + bool enableTrace(false); + QPID_LOG_TEST_CAT(trace, protocol, enableTrace); + if (enableTrace) pn_transport_trace(transport, PN_TRACE_FRM); + + if (!saslInUse) { + //feed in a dummy AMQP 1.0 header as engine expects one, but + //we already read it (if sasl is in use we read the sasl + //header,not the AMQP 1.0 header). + std::vector<char> protocolHeader(8); + qpid::framing::ProtocolInitiation pi(getVersion()); + qpid::framing::Buffer buffer(&protocolHeader[0], protocolHeader.size()); + pi.encode(buffer); + pn_transport_input(transport, &protocolHeader[0], protocolHeader.size()); + + //wont get a userid, so set a dummy one on the ManagedConnection to trigger event + setUserid("no authentication used"); + } +} + + +Connection::~Connection() +{ + + pn_transport_free(transport); + pn_connection_free(connection); +} + +pn_transport_t* Connection::getTransport() +{ + return transport; +} +size_t Connection::decode(const char* buffer, size_t size) +{ + QPID_LOG(trace, id << " decode(" << size << ")") + //TODO: Fix pn_engine_input() to take const buffer + ssize_t n = pn_transport_input(transport, const_cast<char*>(buffer), size); + if (n > 0 || n == PN_EOS) { + //If engine returns EOS, have no way of knowing how many bytes + //it processed, but can assume none need to be reprocessed so + //consider them all read: + if (n == PN_EOS) n = size; + QPID_LOG_CAT(debug, network, id << " decoded " << n << " bytes from " << size) + process(); + pn_transport_tick(transport, 0); + if (!haveOutput) { + haveOutput = true; + out.activateOutput(); + } + return n; + } else if (n == PN_ERR) { + throw qpid::Exception(QPID_MSG("Error on input: " << getError())); + } else { + return 0; + } +} + +size_t Connection::encode(char* buffer, size_t size) +{ + QPID_LOG(trace, "encode(" << size << ")") + ssize_t n = pn_transport_output(transport, buffer, size); + if (n > 0) { + QPID_LOG_CAT(debug, network, id << " encoded " << n << " bytes from " << size) + haveOutput = true; + return n; + } else if (n == PN_EOS) { + haveOutput = size; + return size;//Is this right? + } else if (n == PN_ERR) { + throw qpid::Exception(QPID_MSG("Error on output: " << getError())); + } else { + haveOutput = false; + return 0; + } +} +bool Connection::canEncode() +{ + for (Sessions::iterator i = sessions.begin();i != sessions.end(); ++i) { + if (i->second->dispatch()) haveOutput = true; + } + process(); + //TODO: proper handling of time in and out of tick + pn_transport_tick(transport, 0); + QPID_LOG_CAT(trace, network, id << " canEncode(): " << haveOutput) + return haveOutput; +} +void Connection::closed() +{ + //TODO: tear down sessions and associated links + for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) { + i->second->close(); + } +} +bool Connection::isClosed() const +{ + return pn_connection_state(connection) & PN_REMOTE_CLOSED; +} +framing::ProtocolVersion Connection::getVersion() const +{ + return qpid::framing::ProtocolVersion(1,0); +} +namespace { +pn_state_t REQUIRES_OPEN = PN_LOCAL_UNINIT | PN_REMOTE_ACTIVE; +pn_state_t REQUIRES_CLOSE = PN_LOCAL_ACTIVE | PN_REMOTE_CLOSED; +} + +void Connection::process() +{ + QPID_LOG(trace, id << " process()"); + if ((pn_connection_state(connection) & REQUIRES_OPEN) == REQUIRES_OPEN) { + QPID_LOG_CAT(debug, model, id << " connection opened"); + pn_connection_set_container(connection, broker.getFederationTag().c_str()); + pn_connection_open(connection); + } + + for (pn_session_t* s = pn_session_head(connection, REQUIRES_OPEN); s; s = pn_session_next(s, REQUIRES_OPEN)) { + QPID_LOG_CAT(debug, model, id << " session begun"); + pn_session_open(s); + boost::shared_ptr<Session> ssn(new Session(s, broker, *this, out)); + sessions[s] = ssn; + } + for (pn_link_t* l = pn_link_head(connection, REQUIRES_OPEN); l; l = pn_link_next(l, REQUIRES_OPEN)) { + pn_link_open(l); + + Sessions::iterator session = sessions.find(pn_link_session(l)); + if (session == sessions.end()) { + QPID_LOG(error, id << " Link attached on unknown session!"); + } else { + try { + session->second->attach(l); + QPID_LOG_CAT(debug, protocol, id << " link " << l << " attached on " << pn_link_session(l)); + } catch (const std::exception& e) { + QPID_LOG_CAT(error, protocol, "Error on attach: " << e.what()); + //TODO: set error details on detach when that is exposed via engine API + pn_link_close(l); + } + } + } + + //handle deliveries + for (pn_delivery_t* delivery = pn_work_head(connection); delivery; delivery = pn_work_next(delivery)) { + pn_link_t* link = pn_delivery_link(delivery); + if (pn_link_is_receiver(link)) { + Sessions::iterator i = sessions.find(pn_link_session(link)); + if (i != sessions.end()) { + i->second->incoming(link, delivery); + } else { + pn_delivery_update(delivery, PN_REJECTED); + } + } else { //i.e. SENDER + Sessions::iterator i = sessions.find(pn_link_session(link)); + if (i != sessions.end()) { + QPID_LOG(trace, id << " handling outgoing delivery for " << link << " on session " << pn_link_session(link)); + i->second->outgoing(link, delivery); + } else { + QPID_LOG(error, id << " Got delivery for non-existent session: " << pn_link_session(link) << ", link: " << link); + } + } + } + + + for (pn_link_t* l = pn_link_head(connection, REQUIRES_CLOSE); l; l = pn_link_next(l, REQUIRES_CLOSE)) { + pn_link_close(l); + Sessions::iterator session = sessions.find(pn_link_session(l)); + if (session == sessions.end()) { + QPID_LOG(error, id << " peer attempted to detach link on unknown session!"); + } else { + session->second->detach(l); + QPID_LOG_CAT(debug, model, id << " link detached"); + } + } + for (pn_session_t* s = pn_session_head(connection, REQUIRES_CLOSE); s; s = pn_session_next(s, REQUIRES_CLOSE)) { + pn_session_close(s); + Sessions::iterator i = sessions.find(s); + if (i != sessions.end()) { + i->second->close(); + sessions.erase(i); + QPID_LOG_CAT(debug, model, id << " session ended"); + } else { + QPID_LOG(error, id << " peer attempted to close unrecognised session"); + } + } + if ((pn_connection_state(connection) & REQUIRES_CLOSE) == REQUIRES_CLOSE) { + QPID_LOG_CAT(debug, model, id << " connection closed"); + pn_connection_close(connection); + } +} + +std::string Connection::getError() +{ + std::stringstream text; + pn_error_t* cerror = pn_connection_error(connection); + if (cerror) text << "connection error " << pn_error_text(cerror); + pn_error_t* terror = pn_transport_error(transport); + if (terror) text << "transport error " << pn_error_text(terror); + return text.str(); +} + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/Connection.h b/cpp/src/qpid/broker/amqp/Connection.h new file mode 100644 index 0000000000..8af209af7a --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Connection.h @@ -0,0 +1,73 @@ +#ifndef QPID_BROKER_AMQP1_CONNECTION_H +#define QPID_BROKER_AMQP1_CONNECTION_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/broker/amqp/ManagedConnection.h" +#include <map> +#include <boost/shared_ptr.hpp> + +struct pn_connection_t; +struct pn_session_t; +struct pn_transport_t; + +namespace qpid { +namespace broker { + +class Broker; + +namespace amqp { + +class Session; +/** + * AMQP 1.0 protocol support for broker + */ +class Connection : public sys::ConnectionCodec, public ManagedConnection +{ + public: + Connection(qpid::sys::OutputControl& out, const std::string& id, qpid::broker::Broker& broker, bool saslInUse); + ~Connection(); + size_t decode(const char* buffer, size_t size); + size_t encode(char* buffer, size_t size); + bool canEncode(); + + void closed(); + bool isClosed() const; + + framing::ProtocolVersion getVersion() const; + pn_transport_t* getTransport(); + private: + typedef std::map<pn_session_t*, boost::shared_ptr<Session> > Sessions; + pn_connection_t* connection; + pn_transport_t* transport; + qpid::sys::OutputControl& out; + const std::string id; + qpid::broker::Broker& broker; + bool haveOutput; + Sessions sessions; + + void process(); + std::string getError(); +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP1_CONNECTION_H*/ diff --git a/cpp/src/qpid/broker/amqp/DataReader.cpp b/cpp/src/qpid/broker/amqp/DataReader.cpp new file mode 100644 index 0000000000..519dd71c9c --- /dev/null +++ b/cpp/src/qpid/broker/amqp/DataReader.cpp @@ -0,0 +1,187 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "DataReader.h" +#include "qpid/amqp/CharSequence.h" +#include "qpid/amqp/Descriptor.h" +#include "qpid/log/Statement.h" +#include <string> +extern "C" { +#include <proton/engine.h> +} + +namespace qpid { +namespace broker { +namespace amqp { +namespace { +qpid::amqp::CharSequence convert(pn_bytes_t in) +{ + qpid::amqp::CharSequence out; + out.data = in.start; + out.size = in.size; + return out; +} + +qpid::amqp::CharSequence convert(pn_uuid_t in) +{ + qpid::amqp::CharSequence out; + out.data = in.bytes; + out.size = 16; + return out; +} +} + +DataReader::DataReader(qpid::amqp::Reader& r) : reader(r) {} + +void DataReader::read(pn_data_t* data) +{ + /* + while (pn_data_next(data)) { + readOne(data); + } + */ + do { + readOne(data); + } while (pn_data_next(data)); +} +void DataReader::readOne(pn_data_t* data) +{ + qpid::amqp::Descriptor descriptor(0); + bool described = pn_data_is_described(data); + if (described) { + pn_data_enter(data); + pn_data_next(data); + if (pn_data_type(data) == PN_ULONG) { + descriptor = qpid::amqp::Descriptor(pn_data_get_ulong(data)); + } else if (pn_data_type(data) == PN_SYMBOL) { + descriptor = qpid::amqp::Descriptor(convert(pn_data_get_symbol(data))); + } else { + QPID_LOG(notice, "Ignoring descriptor of type " << pn_data_type(data)); + } + pn_data_next(data); + } + switch (pn_data_type(data)) { + case PN_NULL: + reader.onNull(described ? &descriptor : 0); + break; + case PN_BOOL: + reader.onBoolean(pn_data_get_bool(data), described ? &descriptor : 0); + break; + case PN_UBYTE: + reader.onUByte(pn_data_get_ubyte(data), described ? &descriptor : 0); + break; + case PN_BYTE: + reader.onByte(pn_data_get_byte(data), described ? &descriptor : 0); + break; + case PN_USHORT: + reader.onUShort(pn_data_get_ushort(data), described ? &descriptor : 0); + break; + case PN_SHORT: + reader.onShort(pn_data_get_short(data), described ? &descriptor : 0); + break; + case PN_UINT: + reader.onUInt(pn_data_get_uint(data), described ? &descriptor : 0); + break; + case PN_INT: + reader.onInt(pn_data_get_int(data), described ? &descriptor : 0); + break; + case PN_CHAR: + pn_data_get_char(data); + break; + case PN_ULONG: + reader.onULong(pn_data_get_ulong(data), described ? &descriptor : 0); + break; + case PN_LONG: + reader.onLong(pn_data_get_long(data), described ? &descriptor : 0); + break; + case PN_TIMESTAMP: + reader.onTimestamp(pn_data_get_timestamp(data), described ? &descriptor : 0); + break; + case PN_FLOAT: + reader.onFloat(pn_data_get_float(data), described ? &descriptor : 0); + break; + case PN_DOUBLE: + reader.onDouble(pn_data_get_double(data), described ? &descriptor : 0); + break; + case PN_DECIMAL32: + pn_data_get_decimal32(data); + break; + case PN_DECIMAL64: + pn_data_get_decimal64(data); + break; + case PN_DECIMAL128: + pn_data_get_decimal128(data); + break; + case PN_UUID: + reader.onUuid(convert(pn_data_get_uuid(data)), described ? &descriptor : 0); + break; + case PN_BINARY: + reader.onBinary(convert(pn_data_get_binary(data)), described ? &descriptor : 0); + break; + case PN_STRING: + reader.onString(convert(pn_data_get_string(data)), described ? &descriptor : 0); + break; + case PN_SYMBOL: + reader.onSymbol(convert(pn_data_get_symbol(data)), described ? &descriptor : 0); + break; + case PN_DESCRIBED: + break; + case PN_ARRAY: + readArray(data, described ? &descriptor : 0); + break; + case PN_LIST: + readList(data, described ? &descriptor : 0); + break; + case PN_MAP: + readMap(data, described ? &descriptor : 0); + break; + } + if (described) pn_data_exit(data); +} + +void DataReader::readArray(pn_data_t* /*data*/, const qpid::amqp::Descriptor* /*descriptor*/) +{ + //not yet implemented +} + +void DataReader::readList(pn_data_t* data, const qpid::amqp::Descriptor* descriptor) +{ + size_t count = pn_data_get_list(data); + reader.onStartList(count, qpid::amqp::CharSequence(), descriptor); + pn_data_enter(data); + for (size_t i = 0; i < count && pn_data_next(data); ++i) { + read(data); + } + pn_data_exit(data); + reader.onEndList(count, descriptor); +} + +void DataReader::readMap(pn_data_t* data, const qpid::amqp::Descriptor* descriptor) +{ + size_t count = pn_data_get_map(data); + reader.onStartMap(count, qpid::amqp::CharSequence(), descriptor); + pn_data_enter(data); + for (size_t i = 0; i < count && pn_data_next(data); ++i) { + read(data); + } + pn_data_exit(data); + reader.onEndMap(count, descriptor); +} +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/DataReader.h b/cpp/src/qpid/broker/amqp/DataReader.h new file mode 100644 index 0000000000..024507e7f2 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/DataReader.h @@ -0,0 +1,53 @@ +#ifndef QPID_BROKER_AMQP_DATAREADER_H +#define QPID_BROKER_AMQP_DATAREADER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/Reader.h" + +struct pn_data_t; + +namespace qpid { +namespace amqp { +struct Descriptor; +} +namespace broker { +namespace amqp { + +/** + * Allows use of Reader interface to read pn_data_t* data. + */ +class DataReader +{ + public: + DataReader(qpid::amqp::Reader& reader); + void read(pn_data_t*); + private: + qpid::amqp::Reader& reader; + + void readOne(pn_data_t*); + void readMap(pn_data_t*, const qpid::amqp::Descriptor*); + void readList(pn_data_t*, const qpid::amqp::Descriptor*); + void readArray(pn_data_t*, const qpid::amqp::Descriptor*); +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_DATAREADER_H*/ diff --git a/cpp/src/qpid/broker/amqp/Filter.cpp b/cpp/src/qpid/broker/amqp/Filter.cpp new file mode 100644 index 0000000000..38baba0df1 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Filter.cpp @@ -0,0 +1,150 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/Filter.h" +#include "qpid/broker/amqp/DataReader.h" +#include "qpid/broker/DirectExchange.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/amqp/descriptors.h" +#include "qpid/log/Statement.h" +extern "C" { +#include <proton/engine.h> +} + +namespace qpid { +namespace broker { +namespace amqp { + +void Filter::read(pn_data_t* data) +{ + try { + DataReader reader(*this); + reader.read(data); + } catch (const std::exception& e) { + QPID_LOG(warning, "Error parsing filter: " << e.what()); + } +} + +void Filter::write(pn_data_t* data) +{ + pn_data_put_map(data); + pn_data_enter(data); + subjectFilter.write(data); + pn_data_exit(data); +} + +void Filter::onStringValue(const qpid::amqp::CharSequence& key, const qpid::amqp::CharSequence& value, const qpid::amqp::Descriptor* descriptor) +{ + StringFilter filter; + filter.key = std::string(key.data, key.size); + filter.value = std::string(value.data, value.size); + if (descriptor) { + filter.described = true; + filter.descriptor = *descriptor; + if (descriptor->match(qpid::amqp::filters::LEGACY_TOPIC_FILTER_SYMBOL, qpid::amqp::filters::LEGACY_TOPIC_FILTER_CODE) + || descriptor->match(qpid::amqp::filters::LEGACY_DIRECT_FILTER_SYMBOL, qpid::amqp::filters::LEGACY_DIRECT_FILTER_CODE)) { + setSubjectFilter(filter); + } else { + QPID_LOG(notice, "Skipping unrecognised string filter with key " << filter.key << " and descriptor " << filter.descriptor); + } + } else { + setSubjectFilter(filter); + } +} + +bool Filter::hasSubjectFilter() const +{ + return !subjectFilter.value.empty(); +} + +std::string Filter::getSubjectFilter() const +{ + return subjectFilter.value; +} + + +void Filter::setSubjectFilter(const StringFilter& filter) +{ + if (hasSubjectFilter()) { + QPID_LOG(notice, "Skipping filter with key " << filter.key << "; subject filter already set"); + } else { + subjectFilter = filter; + } +} + +void Filter::bind(boost::shared_ptr<Exchange> exchange, boost::shared_ptr<Queue> queue) +{ + subjectFilter.bind(exchange, queue); +} + +Filter::StringFilter::StringFilter() : described(false), descriptor(0) {} +namespace { +pn_bytes_t convert(const std::string& in) +{ + pn_bytes_t out; + out.start = const_cast<char*>(in.data()); + out.size = in.size(); + return out; +} +pn_bytes_t convert(const qpid::amqp::CharSequence& in) +{ + pn_bytes_t out; + out.start = const_cast<char*>(in.data); + out.size = in.size; + return out; +} +} +void Filter::StringFilter::write(pn_data_t* data) +{ + pn_data_put_symbol(data, convert(key)); + if (described) { + pn_data_put_described(data); + pn_data_enter(data); + switch (descriptor.type) { + case qpid::amqp::Descriptor::NUMERIC: + pn_data_put_ulong(data, descriptor.value.code); + break; + case qpid::amqp::Descriptor::SYMBOLIC: + pn_data_put_symbol(data, convert(descriptor.value.symbol)); + break; + } + } + pn_data_put_string(data, convert(value)); + if (described) pn_data_exit(data); +} + +void Filter::StringFilter::bind(boost::shared_ptr<Exchange> exchange, boost::shared_ptr<Queue> queue) +{ + if (described && exchange->getType() == DirectExchange::typeName + && descriptor.match(qpid::amqp::filters::LEGACY_TOPIC_FILTER_SYMBOL, qpid::amqp::filters::LEGACY_TOPIC_FILTER_CODE)) { + QPID_LOG(info, "Using legacy topic filter as a direct matching filter for " << exchange->getName()); + if (descriptor.type == qpid::amqp::Descriptor::NUMERIC) { + descriptor = qpid::amqp::Descriptor(qpid::amqp::filters::LEGACY_DIRECT_FILTER_CODE); + } else { + qpid::amqp::CharSequence symbol; + symbol.data = qpid::amqp::filters::LEGACY_DIRECT_FILTER_SYMBOL.data(); + symbol.size = qpid::amqp::filters::LEGACY_DIRECT_FILTER_SYMBOL.size(); + descriptor = qpid::amqp::Descriptor(symbol); + } + } + exchange->bind(queue, value, 0); +} + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/Filter.h b/cpp/src/qpid/broker/amqp/Filter.h new file mode 100644 index 0000000000..20cceb372a --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Filter.h @@ -0,0 +1,63 @@ +#ifndef QPID_BROKER_AMQP_FILTER_H +#define QPID_BROKER_AMQP_FILTER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/MapReader.h" +#include "qpid/amqp/Descriptor.h" +#include <boost/shared_ptr.hpp> + +struct pn_data_t; +namespace qpid { +namespace broker { +class Exchange; +class Queue; +namespace amqp { + + +class Filter : qpid::amqp::MapReader +{ + public: + void read(pn_data_t*); + void write(pn_data_t*); + bool hasSubjectFilter() const; + std::string getSubjectFilter() const; + void bind(boost::shared_ptr<Exchange> exchange, boost::shared_ptr<Queue> queue); + private: + struct StringFilter + { + bool described; + qpid::amqp::Descriptor descriptor; + std::string key; + std::string value; + StringFilter(); + void write(pn_data_t*); + void bind(boost::shared_ptr<Exchange> exchange, boost::shared_ptr<Queue> queue); + }; + + void onStringValue(const qpid::amqp::CharSequence& key, const qpid::amqp::CharSequence& value, const qpid::amqp::Descriptor* descriptor); + void setSubjectFilter(const StringFilter&); + + StringFilter subjectFilter; +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_FILTER_H*/ diff --git a/cpp/src/qpid/broker/amqp/Header.cpp b/cpp/src/qpid/broker/amqp/Header.cpp new file mode 100644 index 0000000000..493e757a56 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Header.cpp @@ -0,0 +1,65 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/Header.h" +#include "qpid/broker/Message.h" + +namespace qpid { +namespace broker { +namespace amqp { + +bool Header::isDurable() const +{ + return message.isPersistent(); +} + +uint8_t Header::getPriority() const +{ + return message.getPriority(); +} + +bool Header::hasTtl() const +{ + uint64_t dummy(0); + return message.getTtl(dummy); +} + +uint32_t Header::getTtl() const +{ + uint64_t ttl(0); + message.getTtl(ttl); + if (ttl > std::numeric_limits<uint32_t>::max()) return std::numeric_limits<uint32_t>::max(); + else return (uint32_t) ttl; +} + +bool Header::isFirstAcquirer() const +{ + return false;//TODO +} + +uint32_t Header::getDeliveryCount() const +{ + return message.getDeliveryCount(); +} + +Header::Header(const qpid::broker::Message& m) : message(m) {} + + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/Header.h b/cpp/src/qpid/broker/amqp/Header.h new file mode 100644 index 0000000000..6e4f763028 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Header.h @@ -0,0 +1,50 @@ +#ifndef QPID_BROKER_AMQP_HEADER_H +#define QPID_BROKER_AMQP_HEADER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/MessageEncoder.h" + +namespace qpid { +namespace broker { +class Message; +namespace amqp { + +/** + * Adapts the broker current message abstraction to provide that + * required by the AMQP 1.0 message encoder. + */ +class Header : public qpid::amqp::MessageEncoder::Header +{ + public: + Header(const qpid::broker::Message&); + bool isDurable() const; + uint8_t getPriority() const; + bool hasTtl() const; + uint32_t getTtl() const; + bool isFirstAcquirer() const; + uint32_t getDeliveryCount() const; + private: + const qpid::broker::Message& message; +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_HEADER_H*/ diff --git a/cpp/src/qpid/broker/amqp/ManagedConnection.cpp b/cpp/src/qpid/broker/amqp/ManagedConnection.cpp new file mode 100644 index 0000000000..0253ba5552 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/ManagedConnection.cpp @@ -0,0 +1,98 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/ManagedConnection.h" +#include "qpid/broker/Broker.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/log/Statement.h" +#include "qmf/org/apache/qpid/broker/EventClientConnect.h" +#include "qmf/org/apache/qpid/broker/EventClientDisconnect.h" + +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace qpid { +namespace broker { +namespace amqp { + +ManagedConnection::ManagedConnection(Broker& broker, const std::string i) : id(i), agent(0) +{ + //management integration: + agent = broker.getManagementAgent(); + if (agent != 0) { + qpid::management::Manageable* parent = broker.GetVhostObject(); + // TODO set last bool true if system connection + connection = _qmf::Connection::shared_ptr(new _qmf::Connection(agent, this, parent, id, true, false, "AMQP 1.0")); + connection->set_shadow(false); + agent->addObject(connection); + } +} + +ManagedConnection::~ManagedConnection() +{ + if (agent && connection) { + agent->raiseEvent(_qmf::EventClientDisconnect(id, userid, connection->get_remoteProperties())); + connection->resourceDestroy(); + } + QPID_LOG_CAT(debug, model, "Delete connection. user:" << userid << " rhost:" << id); +} + +void ManagedConnection::setUserid(const std::string& uid) +{ + userid = uid; + if (agent && connection) { + connection->set_authIdentity(userid); + agent->raiseEvent(_qmf::EventClientConnect(id, userid, connection->get_remoteProperties())); + } + QPID_LOG_CAT(debug, model, "Create connection. user:" << userid << " rhost:" << id ); +} + +void ManagedConnection::setSaslMechanism(const std::string& mechanism) +{ + connection->set_saslMechanism(mechanism); +} + +void ManagedConnection::setSaslSsf(int ssf) +{ + connection->set_saslSsf(ssf); +} + +qpid::management::ManagementObject::shared_ptr ManagedConnection::GetManagementObject() const +{ + return connection; +} + +std::string ManagedConnection::getId() const { return id; } +std::string ManagedConnection::getUserid() const { return userid; } + +bool ManagedConnection::isLocal(const ConnectionToken* t) const +{ + return this == t; +} +void ManagedConnection::outgoingMessageSent() +{ + if (connection) connection->inc_msgsToClient(); +} + +void ManagedConnection::incomingMessageReceived() +{ + if (connection) connection->inc_msgsFromClient(); +} + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/ManagedConnection.h b/cpp/src/qpid/broker/amqp/ManagedConnection.h new file mode 100644 index 0000000000..e2d0376918 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/ManagedConnection.h @@ -0,0 +1,59 @@ +#ifndef QPID_BROKER_AMQP_MANAGEDCONNECTION_H +#define QPID_BROKER_AMQP_MANAGEDCONNECTION_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/management/Manageable.h" +#include "qpid/broker/ConnectionToken.h" +#include "qmf/org/apache/qpid/broker/Connection.h" + +namespace qpid { +namespace management { +class ManagementAgent; +class ManagementObject; +} +namespace broker { +class Broker; +namespace amqp { + +class ManagedConnection : public qpid::management::Manageable, public ConnectionToken +{ + public: + ManagedConnection(Broker& broker, const std::string id); + virtual ~ManagedConnection(); + void setUserid(const std::string&); + std::string getId() const; + std::string getUserid() const; + void setSaslMechanism(const std::string&); + void setSaslSsf(int); + qpid::management::ManagementObject::shared_ptr GetManagementObject() const; + bool isLocal(const ConnectionToken* t) const; + void incomingMessageReceived(); + void outgoingMessageSent(); + private: + const std::string id; + std::string userid; + qmf::org::apache::qpid::broker::Connection::shared_ptr connection; + qpid::management::ManagementAgent* agent; +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_MANAGEDCONNECTION_H*/ diff --git a/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.cpp b/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.cpp new file mode 100644 index 0000000000..f36a1e8da4 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.cpp @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "ManagedOutgoingLink.h" +#include "qpid/broker/amqp/ManagedSession.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Queue.h" +#include "qpid/types/Variant.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/log/Statement.h" + +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace qpid { +namespace broker { +namespace amqp { + +ManagedOutgoingLink::ManagedOutgoingLink(Broker& broker, Queue& q, ManagedSession& p, const std::string i, bool topic) + : parent(p), id(i) +{ + qpid::management::ManagementAgent* agent = broker.getManagementAgent(); + if (agent) { + subscription = _qmf::Subscription::shared_ptr(new _qmf::Subscription(agent, this, &p, q.GetManagementObject()->getObjectId(), id, + false/*FIXME*/, true/*FIXME*/, topic, qpid::types::Variant::Map())); + agent->addObject(subscription); + subscription->set_creditMode("n/a"); + } +} +ManagedOutgoingLink::~ManagedOutgoingLink() +{ + if (subscription != 0) subscription->resourceDestroy(); +} + +qpid::management::ManagementObject::shared_ptr ManagedOutgoingLink::GetManagementObject() const +{ + return subscription; +} + +void ManagedOutgoingLink::outgoingMessageSent() +{ + if (subscription) { subscription->inc_delivered(); } + parent.outgoingMessageSent(); +} +void ManagedOutgoingLink::outgoingMessageAccepted() +{ + parent.outgoingMessageAccepted(); +} +void ManagedOutgoingLink::outgoingMessageRejected() +{ + parent.outgoingMessageRejected(); +} + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.h b/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.h new file mode 100644 index 0000000000..20a1095db2 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.h @@ -0,0 +1,53 @@ +#ifndef QPID_BROKER_AMQP_MANAGEDOUTGOINGLINK_H +#define QPID_BROKER_AMQP_MANAGEDOUTGOINGLINK_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/management/Manageable.h" +#include "qmf/org/apache/qpid/broker/Subscription.h" + +namespace qpid { +namespace management { +class ManagementObject; +} +namespace broker { +class Broker; +class Queue; +namespace amqp { +class ManagedSession; + +class ManagedOutgoingLink : public qpid::management::Manageable +{ + public: + ManagedOutgoingLink(Broker& broker, Queue&, ManagedSession& parent, const std::string id, bool topic); + virtual ~ManagedOutgoingLink(); + qpid::management::ManagementObject::shared_ptr GetManagementObject() const; + void outgoingMessageSent(); + void outgoingMessageAccepted(); + void outgoingMessageRejected(); + private: + ManagedSession& parent; + const std::string id; + qmf::org::apache::qpid::broker::Subscription::shared_ptr subscription; +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_MANAGEDOUTGOINGLINK_H*/ diff --git a/cpp/src/qpid/broker/amqp/ManagedSession.cpp b/cpp/src/qpid/broker/amqp/ManagedSession.cpp new file mode 100644 index 0000000000..9bef0e842b --- /dev/null +++ b/cpp/src/qpid/broker/amqp/ManagedSession.cpp @@ -0,0 +1,88 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/ManagedSession.h" +#include "qpid/broker/amqp/ManagedConnection.h" +#include "qpid/broker/Broker.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/log/Statement.h" + +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace qpid { +namespace broker { +namespace amqp { + +ManagedSession::ManagedSession(Broker& broker, ManagedConnection& p, const std::string i) : parent(p), id(i), unacked(0) +{ + qpid::management::ManagementAgent* agent = broker.getManagementAgent(); + if (agent != 0) { + session = _qmf::Session::shared_ptr(new _qmf::Session(agent, this, broker.GetVhostObject(), id)); + session->set_attached(true); + session->set_detachedLifespan(0); + session->clr_expireTime(); + session->set_connectionRef(parent.GetManagementObject()->getObjectId()); + agent->addObject(session); + } +} + +ManagedSession::~ManagedSession() +{ + if (session) session->resourceDestroy(); +} + +qpid::management::ManagementObject::shared_ptr ManagedSession::GetManagementObject() const +{ + return session; +} + +bool ManagedSession::isLocal(const ConnectionToken* t) const +{ + return &parent == t; +} + +void ManagedSession::outgoingMessageSent() +{ + if (session) session->set_unackedMessages(++unacked); + parent.outgoingMessageSent(); +} +void ManagedSession::outgoingMessageAccepted() +{ + if (session) session->set_unackedMessages(--unacked); +} +void ManagedSession::outgoingMessageRejected() +{ + if (session) session->set_unackedMessages(--unacked); +} + +void ManagedSession::incomingMessageReceived() +{ + parent.incomingMessageReceived(); +} +void ManagedSession::incomingMessageAccepted() +{ + +} +void ManagedSession::incomingMessageRejected() +{ + +} + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/ManagedSession.h b/cpp/src/qpid/broker/amqp/ManagedSession.h new file mode 100644 index 0000000000..1f56964bb6 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/ManagedSession.h @@ -0,0 +1,59 @@ +#ifndef QPID_BROKER_AMQP_MANAGEDSESSION_H +#define QPID_BROKER_AMQP_MANAGEDSESSION_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/management/Manageable.h" +#include "qmf/org/apache/qpid/broker/Session.h" +#include "qpid/broker/ConnectionToken.h" +#include "qpid/broker/OwnershipToken.h" + +namespace qpid { +namespace management { +class ManagementObject; +} +namespace broker { +class Broker; +namespace amqp { +class ManagedConnection; + +class ManagedSession : public qpid::management::Manageable, public OwnershipToken +{ + public: + ManagedSession(Broker& broker, ManagedConnection& parent, const std::string id); + virtual ~ManagedSession(); + qpid::management::ManagementObject::shared_ptr GetManagementObject() const; + bool isLocal(const ConnectionToken* t) const; + void incomingMessageReceived(); + void incomingMessageAccepted(); + void incomingMessageRejected(); + void outgoingMessageSent(); + void outgoingMessageAccepted(); + void outgoingMessageRejected(); + private: + ManagedConnection& parent; + const std::string id; + qmf::org::apache::qpid::broker::Session::shared_ptr session; + size_t unacked; +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_MANAGEDSESSION_H*/ diff --git a/cpp/src/qpid/broker/amqp/Message.cpp b/cpp/src/qpid/broker/amqp/Message.cpp new file mode 100644 index 0000000000..a4c346e131 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Message.cpp @@ -0,0 +1,264 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Message.h" +#include "qpid/amqp/Decoder.h" +#include "qpid/amqp/descriptors.h" +#include "qpid/amqp/MessageEncoder.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/Buffer.h" +#include <string.h> + +namespace qpid { +namespace broker { +namespace amqp { + +namespace { +std::string empty; +} + +std::string Message::getRoutingKey() const +{ + std::string v; + v.assign(subject.data, subject.size); + return v; +} +std::string Message::getUserId() const +{ + std::string v; + v.assign(userId.data, userId.size); + return v; +} + +bool Message::isPersistent() const +{ + return durable && durable.get(); +} +bool Message::getTtl(uint64_t& t) const +{ + if (!ttl) { + return false; + } else { + t = ttl.get(); + return true; + } +} + +uint8_t Message::getPriority() const +{ + if (!priority) return 4; + else return priority.get(); +} + +std::string Message::getPropertyAsString(const std::string& /*key*/) const { return empty; } +std::string Message::getAnnotationAsString(const std::string& /*key*/) const { return empty; } +void Message::processProperties(MapHandler&) const {} + +//getContentSize() is primarily used in stats about the number of +//bytes enqueued/dequeued etc, not sure whether this is the right name +//and whether it should indeed only be the content that is thus +//measured +uint64_t Message::getContentSize() const { return data.size(); } +//getContent() is used primarily for decoding qmf messages in management and ha +std::string Message::getContent() const { return empty; } + +Message::Message(size_t size) : data(size) +{ + deliveryAnnotations.init(); + messageAnnotations.init(); + bareMessage.init(); + + userId.init(); + to.init(); + subject.init(); + replyTo.init(); + contentType.init(); + contentEncoding.init(); + + applicationProperties.init(); + body.init(); + footer.init(); +} +char* Message::getData() { return &data[0]; } +const char* Message::getData() const { return &data[0]; } +size_t Message::getSize() const { return data.size(); } + +qpid::amqp::MessageId Message::getMessageId() const +{ + return messageId; +} +qpid::amqp::CharSequence Message::getReplyTo() const +{ + return replyTo; +} +qpid::amqp::MessageId Message::getCorrelationId() const +{ + return correlationId; +} +qpid::amqp::CharSequence Message::getContentType() const +{ + return contentType; +} +qpid::amqp::CharSequence Message::getContentEncoding() const +{ + return contentEncoding; +} + +qpid::amqp::CharSequence Message::getDeliveryAnnotations() const +{ + return deliveryAnnotations; +} +qpid::amqp::CharSequence Message::getMessageAnnotations() const +{ + return messageAnnotations; +} +qpid::amqp::CharSequence Message::getApplicationProperties() const +{ + return applicationProperties; +} +qpid::amqp::CharSequence Message::getBareMessage() const +{ + return bareMessage; +} +qpid::amqp::CharSequence Message::getBody() const +{ + return body; +} +qpid::amqp::CharSequence Message::getFooter() const +{ + return footer; +} + +void Message::scan() +{ + qpid::amqp::Decoder decoder(getData(), getSize()); + decoder.read(*this); + bareMessage = qpid::amqp::MessageReader::getBareMessage(); + if (bareMessage.data && !bareMessage.size) { + bareMessage.size = getSize() - (bareMessage.data - getData()); + } +} + +const Message& Message::get(const qpid::broker::Message& message) +{ + const Message* m = dynamic_cast<const Message*>(&message.getEncoding()); + if (!m) throw qpid::Exception("Translation not yet implemented!!"); + return *m; +} + +void Message::onDurable(bool b) { durable = b; } +void Message::onPriority(uint8_t i) { priority = i; } +void Message::onTtl(uint32_t i) { ttl = i; } +void Message::onFirstAcquirer(bool b) { firstAcquirer = b; } +void Message::onDeliveryCount(uint32_t i) { deliveryCount = i; } + +void Message::onMessageId(uint64_t v) { messageId.set(v); } +void Message::onMessageId(const qpid::amqp::CharSequence& v, qpid::types::VariantType t) { messageId.set(v, t); } +void Message::onUserId(const qpid::amqp::CharSequence& v) { userId = v; } +void Message::onTo(const qpid::amqp::CharSequence& v) { to = v; } +void Message::onSubject(const qpid::amqp::CharSequence& v) { subject = v; } +void Message::onReplyTo(const qpid::amqp::CharSequence& v) { replyTo = v; } +void Message::onCorrelationId(uint64_t v) { correlationId.set(v); } +void Message::onCorrelationId(const qpid::amqp::CharSequence& v, qpid::types::VariantType t) { correlationId.set(v, t);} +void Message::onContentType(const qpid::amqp::CharSequence& v) { contentType = v; } +void Message::onContentEncoding(const qpid::amqp::CharSequence& v) { contentEncoding = v; } +void Message::onAbsoluteExpiryTime(int64_t) {} +void Message::onCreationTime(int64_t) {} +void Message::onGroupId(const qpid::amqp::CharSequence&) {} +void Message::onGroupSequence(uint32_t) {} +void Message::onReplyToGroupId(const qpid::amqp::CharSequence&) {} + +void Message::onApplicationProperties(const qpid::amqp::CharSequence& v) { applicationProperties = v; } +void Message::onDeliveryAnnotations(const qpid::amqp::CharSequence& v) { deliveryAnnotations = v; } +void Message::onMessageAnnotations(const qpid::amqp::CharSequence& v) { messageAnnotations = v; } +void Message::onBody(const qpid::amqp::CharSequence& v, const qpid::amqp::Descriptor&) { body = v; } +void Message::onBody(const qpid::types::Variant&, const qpid::amqp::Descriptor&) {} +void Message::onFooter(const qpid::amqp::CharSequence& v) { footer = v; } + + +//PersistableMessage interface: +void Message::encode(framing::Buffer& buffer) const +{ + buffer.putLong(0);//4-byte format indicator + buffer.putRawData((const uint8_t*) getData(), getSize()); + QPID_LOG(debug, "Encoded 1.0 message of " << getSize() << " bytes, including " << bareMessage.size << " bytes of 'bare message'"); +} +uint32_t Message::encodedSize() const +{ + return 4/*format indicator*/ + data.size(); +} +//in 1.0 the binary header/content makes less sense and in any case +//the functionality that split originally supported (i.e. lazy-loaded +//messages) is no longer in use; for 1.0 we therefore treat the whole +//content as 'header' and load it in the first stage. +uint32_t Message::encodedHeaderSize() const +{ + return encodedSize(); +} +void Message::decodeHeader(framing::Buffer& buffer) +{ + if (buffer.available() != getSize()) { + QPID_LOG(warning, "1.0 Message buffer was " << data.size() << " bytes, but " << buffer.available() << " bytes are available. Resizing."); + data.resize(buffer.available()); + } + buffer.getRawData((uint8_t*) getData(), getSize()); + scan(); + QPID_LOG(debug, "Decoded 1.0 message of " << getSize() << " bytes, including " << bareMessage.size << " bytes of 'bare message'"); +} +void Message::decodeContent(framing::Buffer& /*buffer*/) {} + +boost::intrusive_ptr<PersistableMessage> Message::merge(const std::map<std::string, qpid::types::Variant>& annotations) const +{ + //message- or delivery- annotations? would have to determine that from the name, for now assume always message-annotations + size_t extra = 0; + if (messageAnnotations) { + //TODO: actual merge required + } else { + //add whole new section + extra = qpid::amqp::MessageEncoder::getEncodedSize(annotations, true); + } + boost::intrusive_ptr<Message> copy(new Message(data.size()+extra)); + size_t position(0); + if (deliveryAnnotations) { + ::memcpy(©->data[position], deliveryAnnotations.data, deliveryAnnotations.size); + position += deliveryAnnotations.size; + } + if (messageAnnotations) { + //TODO: actual merge required + ::memcpy(©->data[position], messageAnnotations.data, messageAnnotations.size); + position += messageAnnotations.size; + } else { + qpid::amqp::MessageEncoder encoder(©->data[position], extra); + encoder.writeMap(annotations, &qpid::amqp::message::MESSAGE_ANNOTATIONS, true); + position += extra; + } + if (bareMessage) { + ::memcpy(©->data[position], bareMessage.data, bareMessage.size); + position += bareMessage.size; + } + if (footer) { + ::memcpy(©->data[position], footer.data, footer.size); + position += footer.size; + } + copy->scan(); + return copy; +} + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/Message.h b/cpp/src/qpid/broker/amqp/Message.h new file mode 100644 index 0000000000..cc3406f72a --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Message.h @@ -0,0 +1,148 @@ +#ifndef QPID_BROKER_AMQP_MESSAGE_H +#define QPID_BROKER_AMQP_MESSAGE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/Message.h" +#include "qpid/amqp/CharSequence.h" +#include "qpid/amqp/MessageId.h" +#include "qpid/amqp/MessageReader.h" +#include <boost/optional.hpp> + +namespace qpid { +namespace framing { +class Buffer; +} +namespace broker { +namespace amqp { + +/** + * Represents an AMQP 1.0 format message + */ +class Message : public qpid::broker::Message::Encoding, private qpid::amqp::MessageReader, public qpid::broker::PersistableMessage +{ + public: + //Encoding interface: + std::string getRoutingKey() const; + bool isPersistent() const; + uint8_t getPriority() const; + uint64_t getContentSize() const; + std::string getPropertyAsString(const std::string& key) const; + std::string getAnnotationAsString(const std::string& key) const; + bool getTtl(uint64_t&) const; + std::string getContent() const; + void processProperties(MapHandler&) const; + std::string getUserId() const; + + qpid::amqp::MessageId getMessageId() const; + qpid::amqp::CharSequence getReplyTo() const; + qpid::amqp::MessageId getCorrelationId() const; + qpid::amqp::CharSequence getContentType() const; + qpid::amqp::CharSequence getContentEncoding() const; + + qpid::amqp::CharSequence getDeliveryAnnotations() const; + qpid::amqp::CharSequence getMessageAnnotations() const; + qpid::amqp::CharSequence getApplicationProperties() const; + qpid::amqp::CharSequence getBareMessage() const; + qpid::amqp::CharSequence getBody() const; + qpid::amqp::CharSequence getFooter() const; + + Message(size_t size); + char* getData(); + const char* getData() const; + size_t getSize() const; + void scan(); + + //PersistableMessage interface: + void encode(framing::Buffer& buffer) const; + uint32_t encodedSize() const; + void decodeHeader(framing::Buffer& buffer); + void decodeContent(framing::Buffer& buffer); + uint32_t encodedHeaderSize() const; + boost::intrusive_ptr<PersistableMessage> merge(const std::map<std::string, qpid::types::Variant>& annotations) const; + + static const Message& get(const qpid::broker::Message&); + private: + std::vector<char> data; + + //header: + boost::optional<bool> durable; + boost::optional<uint8_t> priority; + boost::optional<uint32_t> ttl; + boost::optional<bool> firstAcquirer; + boost::optional<uint32_t> deliveryCount; + //annotations: + qpid::amqp::CharSequence deliveryAnnotations; + qpid::amqp::CharSequence messageAnnotations; + + qpid::amqp::CharSequence bareMessage;//properties, application-properties and content + //properties: + qpid::amqp::MessageId messageId; + qpid::amqp::CharSequence userId; + qpid::amqp::CharSequence to; + qpid::amqp::CharSequence subject; + qpid::amqp::CharSequence replyTo; + qpid::amqp::MessageId correlationId; + qpid::amqp::CharSequence contentType; + qpid::amqp::CharSequence contentEncoding; + + //application-properties: + qpid::amqp::CharSequence applicationProperties; + + //body: + qpid::amqp::CharSequence body; + + //footer: + qpid::amqp::CharSequence footer; + + //header: + void onDurable(bool b); + void onPriority(uint8_t i); + void onTtl(uint32_t i); + void onFirstAcquirer(bool b); + void onDeliveryCount(uint32_t i); + //properties: + void onMessageId(uint64_t); + void onMessageId(const qpid::amqp::CharSequence&, qpid::types::VariantType); + void onUserId(const qpid::amqp::CharSequence& v); + void onTo(const qpid::amqp::CharSequence& v); + void onSubject(const qpid::amqp::CharSequence& v); + void onReplyTo(const qpid::amqp::CharSequence& v); + void onCorrelationId(uint64_t); + void onCorrelationId(const qpid::amqp::CharSequence&, qpid::types::VariantType); + void onContentType(const qpid::amqp::CharSequence& v); + void onContentEncoding(const qpid::amqp::CharSequence& v); + void onAbsoluteExpiryTime(int64_t i); + void onCreationTime(int64_t); + void onGroupId(const qpid::amqp::CharSequence&); + void onGroupSequence(uint32_t); + void onReplyToGroupId(const qpid::amqp::CharSequence&); + + void onApplicationProperties(const qpid::amqp::CharSequence&); + void onDeliveryAnnotations(const qpid::amqp::CharSequence&); + void onMessageAnnotations(const qpid::amqp::CharSequence&); + void onBody(const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor&); + void onBody(const qpid::types::Variant&, const qpid::amqp::Descriptor&); + void onFooter(const qpid::amqp::CharSequence&); +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_MESSAGE_H*/ diff --git a/cpp/src/qpid/broker/amqp/NodeProperties.cpp b/cpp/src/qpid/broker/amqp/NodeProperties.cpp new file mode 100644 index 0000000000..eea7612cb9 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/NodeProperties.cpp @@ -0,0 +1,179 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/NodeProperties.h" +#include "qpid/broker/amqp/DataReader.h" +#include "qpid/amqp/CharSequence.h" +#include "qpid/types/Variant.h" +#include "qpid/broker/QueueSettings.h" +#include "qpid/log/Statement.h" + +using qpid::amqp::CharSequence; +using qpid::amqp::Descriptor; + +namespace qpid { +namespace broker { +namespace amqp { +namespace { +//distribution modes: +const std::string MOVE("move"); +const std::string COPY("copy"); +const std::string SUPPORTED_DIST_MODES("supported-dist-modes"); + +//AMQP 0-10 standard parameters: +const std::string DURABLE("durable"); +const std::string AUTO_DELETE("auto-delete"); +const std::string ALTERNATE_EXCHANGE("alternate-exchange"); +const std::string EXCHANGE_TYPE("exchange-type"); +} + +NodeProperties::NodeProperties() : queue(true), durable(false), autoDelete(false), exchangeType("topic") {} + +void NodeProperties::read(pn_data_t* data) +{ + DataReader reader(*this); + reader.read(data); +} + +void NodeProperties::process(const std::string& key, const qpid::types::Variant& value) +{ + QPID_LOG(notice, "Processing node property " << key << " = " << value); + if (key == SUPPORTED_DIST_MODES) { + if (value == MOVE) queue = true; + else if (value == COPY) queue = false; + } else if (key == DURABLE) { + durable = value; + } else if (key == AUTO_DELETE) { + autoDelete = value; + } else if (key == ALTERNATE_EXCHANGE) { + alternateExchange = value.asString(); + } else if (key == EXCHANGE_TYPE) { + exchangeType = value.asString(); + } else { + properties[key] = value; + } +} + +void NodeProperties::onNullValue(const CharSequence& key, const Descriptor*) +{ + process(key.str(), qpid::types::Variant()); +} + +void NodeProperties::onBooleanValue(const CharSequence& key, bool value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onUByteValue(const CharSequence& key, uint8_t value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onUShortValue(const CharSequence& key, uint16_t value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onUIntValue(const CharSequence& key, uint32_t value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onULongValue(const CharSequence& key, uint64_t value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onByteValue(const CharSequence& key, int8_t value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onShortValue(const CharSequence& key, int16_t value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onIntValue(const CharSequence& key, int32_t value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onLongValue(const CharSequence& key, int64_t value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onFloatValue(const CharSequence& key, float value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onDoubleValue(const CharSequence& key, double value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onUuidValue(const CharSequence& key, const CharSequence& value, const Descriptor*) +{ + process(key.str(), value.str()); +} + +void NodeProperties::onTimestampValue(const CharSequence& key, int64_t value, const Descriptor*) +{ + process(key.str(), value); +} + +void NodeProperties::onStringValue(const CharSequence& key, const CharSequence& value, const Descriptor*) +{ + process(key.str(), value.str()); +} + +void NodeProperties::onSymbolValue(const CharSequence& key, const CharSequence& value, const Descriptor*) +{ + process(key.str(), value.str()); +} + +QueueSettings NodeProperties::getQueueSettings() +{ + QueueSettings settings(durable, autoDelete); + qpid::types::Variant::Map unused; + settings.populate(properties, unused); + return settings; +} + +bool NodeProperties::isQueue() const +{ + return queue; +} +bool NodeProperties::isDurable() const +{ + return durable; +} +std::string NodeProperties::getExchangeType() const +{ + return exchangeType; +} +std::string NodeProperties::getAlternateExchange() const +{ + return alternateExchange; +} + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/NodeProperties.h b/cpp/src/qpid/broker/amqp/NodeProperties.h new file mode 100644 index 0000000000..b81d1d712c --- /dev/null +++ b/cpp/src/qpid/broker/amqp/NodeProperties.h @@ -0,0 +1,71 @@ +#ifndef QPID_BROKER_AMQP_NODEPROPERTIES_H +#define QPID_BROKER_AMQP_NODEPROPERTIES_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/MapReader.h" +#include "qpid/types/Variant.h" + +struct pn_data_t; +namespace qpid { +namespace broker { +struct QueueSettings; +namespace amqp { + +class NodeProperties : public qpid::amqp::MapReader +{ + public: + NodeProperties(); + void read(pn_data_t*); + void onNullValue(const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor*); + void onBooleanValue(const qpid::amqp::CharSequence&, bool, const qpid::amqp::Descriptor*); + void onUByteValue(const qpid::amqp::CharSequence&, uint8_t, const qpid::amqp::Descriptor*); + void onUShortValue(const qpid::amqp::CharSequence&, uint16_t, const qpid::amqp::Descriptor*); + void onUIntValue(const qpid::amqp::CharSequence&, uint32_t, const qpid::amqp::Descriptor*); + void onULongValue(const qpid::amqp::CharSequence&, uint64_t, const qpid::amqp::Descriptor*); + void onByteValue(const qpid::amqp::CharSequence&, int8_t, const qpid::amqp::Descriptor*); + void onShortValue(const qpid::amqp::CharSequence&, int16_t, const qpid::amqp::Descriptor*); + void onIntValue(const qpid::amqp::CharSequence&, int32_t, const qpid::amqp::Descriptor*); + void onLongValue(const qpid::amqp::CharSequence&, int64_t, const qpid::amqp::Descriptor*); + void onFloatValue(const qpid::amqp::CharSequence&, float, const qpid::amqp::Descriptor*); + void onDoubleValue(const qpid::amqp::CharSequence&, double, const qpid::amqp::Descriptor*); + void onUuidValue(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor*); + void onTimestampValue(const qpid::amqp::CharSequence&, int64_t, const qpid::amqp::Descriptor*); + void onStringValue(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor*); + void onSymbolValue(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor*); + bool isQueue() const; + QueueSettings getQueueSettings(); + bool isDurable() const; + std::string getExchangeType() const; + std::string getAlternateExchange() const; + private: + bool queue; + bool durable; + bool autoDelete; + std::string exchangeType; + std::string alternateExchange; + qpid::types::Variant::Map properties; + + void process(const std::string&, const qpid::types::Variant&); +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_NODEPROPERTIES_H*/ diff --git a/cpp/src/qpid/broker/amqp/Outgoing.cpp b/cpp/src/qpid/broker/amqp/Outgoing.cpp new file mode 100644 index 0000000000..9605cacac1 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Outgoing.cpp @@ -0,0 +1,244 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/Outgoing.h" +#include "qpid/broker/amqp/Header.h" +#include "qpid/broker/amqp/Translation.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/TopicKeyNode.h" +#include "qpid/sys/OutputControl.h" +#include "qpid/amqp/MessageEncoder.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace broker { +namespace amqp { + +Outgoing::Outgoing(Broker& broker, boost::shared_ptr<Queue> q, pn_link_t* l, ManagedSession& session, qpid::sys::OutputControl& o, bool topic) + : Consumer(pn_link_name(l), /*FIXME*/CONSUMER), + ManagedOutgoingLink(broker, *q, session, pn_link_name(l), topic), + exclusive(topic), + queue(q), deliveries(5000), link(l), out(o), + current(0), outstanding(0), + buffer(1024)/*used only for header at present*/ +{ + for (size_t i = 0 ; i < deliveries.capacity(); ++i) { + deliveries[i].init(i); + } +} + +void Outgoing::init() +{ + queue->consume(shared_from_this(), exclusive);//may throw exception +} + +bool Outgoing::dispatch() +{ + QPID_LOG(trace, "Dispatching to " << getName() << ": " << pn_link_credit(link)); + if (canDeliver()) { + if (queue->dispatch(shared_from_this())) { + return true; + } else { + pn_link_drained(link); + QPID_LOG(debug, "No message available on " << queue->getName()); + } + } else { + QPID_LOG(debug, "Can't deliver to " << getName() << " from " << queue->getName() << ": " << pn_link_credit(link)); + } + return false; +} + +void Outgoing::write(const char* data, size_t size) +{ + pn_link_send(link, data, size); +} + +void Outgoing::handle(pn_delivery_t* delivery) +{ + pn_delivery_tag_t tag = pn_delivery_tag(delivery); + size_t i = *reinterpret_cast<const size_t*>(tag.bytes); + Record& r = deliveries[i]; + if (pn_delivery_writable(delivery)) { + assert(r.msg); + assert(!r.delivery); + r.delivery = delivery; + //write header + qpid::amqp::MessageEncoder encoder(&buffer[0], buffer.size()); + encoder.writeHeader(Header(r.msg)); + write(&buffer[0], encoder.getPosition()); + Translation t(r.msg); + t.write(*this); + if (pn_link_advance(link)) { + --outstanding; + outgoingMessageSent(); + QPID_LOG(debug, "Sent message " << r.msg.getSequence() << " from " << queue->getName() << ", index=" << r.index); + } else { + QPID_LOG(error, "Failed to send message " << r.msg.getSequence() << " from " << queue->getName() << ", index=" << r.index); + } + } + if (pn_delivery_updated(delivery)) { + assert(r.delivery == delivery); + r.disposition = pn_delivery_remote_state(delivery); + if (r.disposition) { + switch (r.disposition) { + case PN_ACCEPTED: + //TODO: only if consuming + queue->dequeue(0, r.cursor); + outgoingMessageAccepted(); + break; + case PN_REJECTED: + queue->reject(r.cursor); + outgoingMessageRejected(); + break; + case PN_RELEASED: + queue->release(r.cursor, false);//TODO: for PN_RELEASED, delivery count should not be incremented + outgoingMessageRejected();//TODO: not quite true... + break; + case PN_MODIFIED: + queue->release(r.cursor, true);//TODO: proper handling of modified + outgoingMessageRejected();//TODO: not quite true... + break; + default: + QPID_LOG(warning, "Unhandled disposition: " << r.disposition); + } + //TODO: ony settle once any dequeue on store has completed + pn_delivery_settle(delivery); + r.reset(); + } + } +} + +bool Outgoing::canDeliver() +{ + return deliveries[current].delivery == 0 && pn_link_credit(link) > outstanding; +} + +void Outgoing::detached() +{ + QPID_LOG(debug, "Detaching outgoing link from " << queue->getName()); + queue->cancel(shared_from_this()); + //TODO: release in a clearer order? + for (size_t i = 0 ; i < deliveries.capacity(); ++i) { + if (deliveries[i].msg) queue->release(deliveries[i].cursor, true); + } + Queue::tryAutoDelete(*queue->getBroker(), queue, "", ""); +} + +//Consumer interface: +bool Outgoing::deliver(const QueueCursor& cursor, const qpid::broker::Message& msg) +{ + Record& r = deliveries[current++]; + if (current >= deliveries.capacity()) current = 0; + r.cursor = cursor; + r.msg = msg; + pn_delivery(link, r.tag); + QPID_LOG(debug, "Requested delivery of " << r.msg.getSequence() << " from " << queue->getName() << ", index=" << r.index); + ++outstanding; + return true; +} + +void Outgoing::notify() +{ + QPID_LOG(trace, "Notification received for " << queue->getName()); + out.activateOutput(); +} + +bool Outgoing::accept(const qpid::broker::Message&) +{ + return true; +} + +void Outgoing::setSubjectFilter(const std::string& f) +{ + subjectFilter = f; +} + +namespace { + +bool match(TokenIterator& filter, TokenIterator& target) +{ + bool wild = false; + while (!filter.finished()) + { + if (filter.match1('*')) { + if (target.finished()) return false; + //else move to next word in filter target + filter.next(); + target.next(); + } else if (filter.match1('#')) { + // i.e. filter word is '#' which can match a variable number of words in the target + filter.next(); + if (filter.finished()) return true; + else if (target.finished()) return false; + wild = true; + } else { + //filter word needs to match target exactly + if (target.finished()) return false; + std::string word; + target.pop(word); + if (filter.match(word)) { + wild = false; + filter.next(); + } else if (!wild) { + return false; + } + } + } + return target.finished(); +} +bool match(const std::string& filter, const std::string& target) +{ + TokenIterator lhs(filter); + TokenIterator rhs(target); + return match(lhs, rhs); +} +} + +bool Outgoing::filter(const qpid::broker::Message& m) +{ + return subjectFilter.empty() || subjectFilter == m.getRoutingKey() || match(subjectFilter, m.getRoutingKey()); +} + +void Outgoing::cancel() {} + +void Outgoing::acknowledged(const qpid::broker::DeliveryRecord&) {} + +qpid::broker::OwnershipToken* Outgoing::getSession() +{ + return 0; +} + +Outgoing::Record::Record() : delivery(0), disposition(0), index(0) {} +void Outgoing::Record::init(size_t i) +{ + index = i; + tag.bytes = reinterpret_cast<const char*>(&index); + tag.size = sizeof(index); +} +void Outgoing::Record::reset() +{ + cursor = QueueCursor(); + msg = qpid::broker::Message(); + delivery = 0; + disposition = 0; +} + + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/Outgoing.h b/cpp/src/qpid/broker/amqp/Outgoing.h new file mode 100644 index 0000000000..a8450a48cf --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Outgoing.h @@ -0,0 +1,108 @@ +#ifndef QPID_BROKER_AMQP1_OUTGOING_H +#define QPID_BROKER_AMQP1_OUTGOING_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/Message.h" +#include "qpid/broker/amqp/ManagedOutgoingLink.h" +#include "qpid/broker/Consumer.h" +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> +extern "C" { +#include <proton/engine.h> +} + +namespace qpid { +namespace sys { +class OutputControl; +} +namespace broker { +class Broker; +class Queue; +namespace amqp { +class ManagedSession; +template <class T> +class CircularArray +{ + public: + CircularArray(size_t l) : limit(l), data(new T[limit]) {} + T& operator[](size_t i) { return data[i]; } + size_t capacity() { return limit; } + ~CircularArray() { delete [] data; } + private: + const size_t limit; + T* const data; + size_t next; +}; + +/** + * + */ +class Outgoing : public qpid::broker::Consumer, public boost::enable_shared_from_this<Outgoing>, public ManagedOutgoingLink +{ + public: + Outgoing(Broker&,boost::shared_ptr<Queue> q, pn_link_t* l, ManagedSession&, qpid::sys::OutputControl& o, bool topic); + void setSubjectFilter(const std::string&); + void init(); + bool dispatch(); + void write(const char* data, size_t size); + void handle(pn_delivery_t* delivery); + bool canDeliver(); + void detached(); + + //Consumer interface: + bool deliver(const QueueCursor& cursor, const qpid::broker::Message& msg); + void notify(); + bool accept(const qpid::broker::Message&); + bool filter(const qpid::broker::Message&); + void cancel(); + void acknowledged(const qpid::broker::DeliveryRecord&); + qpid::broker::OwnershipToken* getSession(); + + private: + + struct Record + { + QueueCursor cursor; + qpid::broker::Message msg; + pn_delivery_t* delivery; + int disposition; + size_t index; + pn_delivery_tag_t tag; + + Record(); + void init(size_t i); + void reset(); + }; + + const bool exclusive; + boost::shared_ptr<Queue> queue; + CircularArray<Record> deliveries; + pn_link_t* link; + qpid::sys::OutputControl& out; + size_t current; + int outstanding; + std::vector<char> buffer; + std::string subjectFilter; +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP1_OUTGOING_H*/ diff --git a/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp b/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp new file mode 100644 index 0000000000..711592257c --- /dev/null +++ b/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp @@ -0,0 +1,117 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/Plugin.h" +#include "qpid/SaslFactory.h" +#include "qpid/NullSaslServer.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Protocol.h" +#include "qpid/broker/RecoverableMessage.h" +#include "qpid/broker/RecoverableMessageImpl.h" +#include "qpid/broker/amqp/Connection.h" +#include "qpid/broker/amqp/Message.h" +#include "qpid/broker/amqp/Sasl.h" +#include "qpid/broker/amqp/Translation.h" +#include "qpid/broker/amqp_0_10/MessageTransfer.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace broker { +namespace amqp { + +class ProtocolImpl : public Protocol +{ + public: + ProtocolImpl(Broker& b) : broker(b) {} + qpid::sys::ConnectionCodec* create(const qpid::framing::ProtocolVersion&, qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&); + boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> translate(const qpid::broker::Message&); + boost::shared_ptr<RecoverableMessage> recover(qpid::framing::Buffer&); + private: + Broker& broker; +}; + +struct ProtocolPlugin : public Plugin +{ + void earlyInitialize(Plugin::Target& target) + { + //need to register protocol before recovery from store + broker::Broker* broker = dynamic_cast<qpid::broker::Broker*>(&target); + if (broker) { + broker->getProtocolRegistry().add("AMQP 1.0", new ProtocolImpl(*broker)); + } + } + + void initialize(Plugin::Target&) {} +}; + +ProtocolPlugin instance; // Static initialization + +qpid::sys::ConnectionCodec* ProtocolImpl::create(const qpid::framing::ProtocolVersion& v, qpid::sys::OutputControl& out, const std::string& id, const qpid::sys::SecuritySettings& external) +{ + if (v == qpid::framing::ProtocolVersion(1, 0)) { + if (v.getProtocol() == qpid::framing::ProtocolVersion::SASL) { + if (broker.getOptions().auth) { + QPID_LOG(info, "Using AMQP 1.0 (with SASL layer)"); + return new qpid::broker::amqp::Sasl(out, id, broker, qpid::SaslFactory::getInstance().createServer(broker.getOptions().realm, broker.getOptions().requireEncrypted, external)); + } else { + std::auto_ptr<SaslServer> authenticator(new qpid::NullSaslServer(broker.getOptions().realm)); + QPID_LOG(info, "Using AMQP 1.0 (with dummy SASL layer)"); + return new qpid::broker::amqp::Sasl(out, id, broker, authenticator); + } + } else { + if (broker.getOptions().auth) { + throw qpid::Exception("SASL layer required!"); + } else { + QPID_LOG(info, "Using AMQP 1.0 (no SASL layer)"); + return new qpid::broker::amqp::Connection(out, id, broker, false); + } + } + } + return 0; +} + +boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> ProtocolImpl::translate(const qpid::broker::Message& m) +{ + qpid::broker::amqp::Translation t(m); + return t.getTransfer(); +} + +boost::shared_ptr<RecoverableMessage> ProtocolImpl::recover(qpid::framing::Buffer& buffer) +{ + QPID_LOG(debug, "Recovering, checking for 1.0 message format indicator..."); + uint32_t format = buffer.getLong(); + if (format == 0) { + QPID_LOG(debug, "Recovered message IS in 1.0 format"); + //this is a 1.0 format message + boost::intrusive_ptr<qpid::broker::amqp::Message> m(new qpid::broker::amqp::Message(buffer.available())); + m->decodeHeader(buffer); + return RecoverableMessage::shared_ptr(new RecoverableMessageImpl(qpid::broker::Message(m, m))); + } else { + QPID_LOG(debug, "Recovered message is NOT in 1.0 format"); + return RecoverableMessage::shared_ptr(); + } +} + + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/Sasl.cpp b/cpp/src/qpid/broker/amqp/Sasl.cpp new file mode 100644 index 0000000000..4b89e7b15d --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Sasl.cpp @@ -0,0 +1,167 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/Sasl.h" +#include "qpid/broker/Broker.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/ProtocolInitiation.h" +#include "qpid/sys/OutputControl.h" +#include "qpid/sys/SecurityLayer.h" +#include <boost/format.hpp> +#include <vector> + +namespace qpid { +namespace broker { +namespace amqp { + +Sasl::Sasl(qpid::sys::OutputControl& o, const std::string& id, qpid::broker::Broker& broker, std::auto_ptr<qpid::SaslServer> auth) + : qpid::amqp::SaslServer(id), out(o), connection(out, id, broker, true), + authenticator(auth), + state(INCOMPLETE), writeHeader(true), haveOutput(true) +{ + out.activateOutput(); + mechanisms(authenticator->getMechanisms()); +} + +Sasl::~Sasl() {} + +size_t Sasl::decode(const char* buffer, size_t size) +{ + if (state == AUTHENTICATED) { + if (securityLayer.get()) return securityLayer->decode(buffer, size); + else return connection.decode(buffer, size); + } else if (state == INCOMPLETE && size) { + size_t decoded = read(buffer, size); + QPID_LOG(trace, id << " Sasl::decode(" << size << "): " << decoded); + return decoded; + } else { + return 0; + } +} + +size_t Sasl::encode(char* buffer, size_t size) +{ + if (state == AUTHENTICATED) { + if (securityLayer.get()) return securityLayer->encode(buffer, size); + else return connection.encode(buffer, size); + } else { + size_t encoded = 0; + if (writeHeader) { + encoded += writeProtocolHeader(buffer, size); + if (!encoded) return 0; + writeHeader = false; + } + if (encoded < size) { + encoded += write(buffer + encoded, size - encoded); + } + if (state == SUCCESS_PENDING) { + state = AUTHENTICATED; + } else if (state == FAILURE_PENDING) { + state = FAILED; + } else { + haveOutput = (encoded == size); + } + QPID_LOG(trace, id << " Sasl::encode(" << size << "): " << encoded); + return encoded; + } +} + +bool Sasl::canEncode() +{ + if (state == AUTHENTICATED) { + if (securityLayer.get()) return securityLayer->canEncode(); + else return connection.canEncode(); + } else { + return haveOutput; + } +} + +void Sasl::closed() +{ + if (state == AUTHENTICATED) { + connection.closed(); + } else { + QPID_LOG(info, id << " Connection closed prior to authentication completing"); + state = FAILED; + } +} +bool Sasl::isClosed() const +{ + if (state == AUTHENTICATED) { + return connection.isClosed(); + } else { + return state == FAILED; + } +} + +framing::ProtocolVersion Sasl::getVersion() const +{ + return connection.getVersion(); +} +namespace { +const std::string EMPTY; +} + +void Sasl::init(const std::string& mechanism, const std::string* response, const std::string* /*hostname*/) +{ + QPID_LOG_CAT(debug, protocol, id << " Received SASL-INIT(" << mechanism << ", " << (response ? *response : EMPTY) << ")"); + //TODO: what should we do with hostname here? + std::string c; + respond(authenticator->start(mechanism, response, c), c); + connection.setSaslMechanism(mechanism); +} + +void Sasl::response(const std::string* r) +{ + QPID_LOG_CAT(debug, protocol, id << " Received SASL-RESPONSE(" << (r ? *r : EMPTY) << ")"); + std::string c; + respond(authenticator->step(r, c), c); +} + +void Sasl::respond(qpid::SaslServer::Status status, const std::string& chllnge) +{ + switch (status) { + case qpid::SaslServer::OK: + connection.setUserid(authenticator->getUserid()); + completed(true); + //can't set authenticated & failed until we have actually sent the outcome + state = SUCCESS_PENDING; + securityLayer = authenticator->getSecurityLayer(65535); + if (securityLayer.get()) { + QPID_LOG_CAT(info, security, id << " Security layer installed"); + securityLayer->init(&connection); + connection.setSaslSsf(securityLayer->getSsf()); + } + QPID_LOG_CAT(info, security, id << " Authenticated as " << authenticator->getUserid()); + break; + case qpid::SaslServer::FAIL: + completed(false); + state = FAILURE_PENDING; + QPID_LOG_CAT(info, security, id << " Failed to authenticate"); + break; + case qpid::SaslServer::CHALLENGE: + challenge(&chllnge); + QPID_LOG_CAT(info, security, id << " Challenge issued"); + break; + } + haveOutput = true; + out.activateOutput(); +} +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/Sasl.h b/cpp/src/qpid/broker/amqp/Sasl.h new file mode 100644 index 0000000000..079128be02 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Sasl.h @@ -0,0 +1,72 @@ +#ifndef QPID_BROKER_AMQP_SASL_H +#define QPID_BROKER_AMQP_SASL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/Connection.h" +#include "qpid/SaslServer.h" +#include "qpid/amqp/SaslServer.h" +#include "qpid/sys/ConnectionCodec.h" +#include <memory> +namespace qpid { +namespace sys { +class SecurityLayer; +} +namespace broker { +namespace amqp { + +/** + * An AMQP 1.0 SASL Security Layer for authentication and optionally + * encryption. + */ +class Sasl : public sys::ConnectionCodec, qpid::amqp::SaslServer +{ + public: + Sasl(qpid::sys::OutputControl& out, const std::string& id, qpid::broker::Broker& broker, std::auto_ptr<qpid::SaslServer> authenticator); + ~Sasl(); + + size_t decode(const char* buffer, size_t size); + size_t encode(char* buffer, size_t size); + bool canEncode(); + + void closed(); + bool isClosed() const; + + framing::ProtocolVersion getVersion() const; + private: + qpid::sys::OutputControl& out; + Connection connection; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + std::auto_ptr<qpid::SaslServer> authenticator; + enum { + INCOMPLETE, SUCCESS_PENDING, FAILURE_PENDING, AUTHENTICATED, FAILED + } state; + + bool writeHeader; + bool haveOutput; + + void init(const std::string& mechanism, const std::string* response, const std::string* hostname); + void response(const std::string*); + void respond(qpid::SaslServer::Status status, const std::string& challenge); +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_SASL_H*/ diff --git a/cpp/src/qpid/broker/amqp/Session.cpp b/cpp/src/qpid/broker/amqp/Session.cpp new file mode 100644 index 0000000000..fabe609473 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Session.cpp @@ -0,0 +1,332 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Session.h" +#include "Outgoing.h" +#include "Message.h" +#include "ManagedConnection.h" +#include "qpid/broker/AsyncCompletion.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/DirectExchange.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/broker/FanOutExchange.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/broker/amqp/Filter.h" +#include "qpid/broker/amqp/NodeProperties.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Statement.h" +#include <boost/intrusive_ptr.hpp> +#include <boost/format.hpp> +#include <map> +#include <sstream> +extern "C" { +#include <proton/engine.h> +} + +namespace qpid { +namespace broker { +namespace amqp { + +class Target +{ + public: + Target(pn_link_t* l) : credit(100), window(0), link(l) {} + virtual ~Target() {} + bool flow(); + bool needFlow(); + virtual void handle(qpid::broker::Message& m) = 0;//TODO: revise this for proper message + protected: + const uint32_t credit; + uint32_t window; + pn_link_t* link; +}; + +class Queue : public Target +{ + public: + Queue(boost::shared_ptr<qpid::broker::Queue> q, pn_link_t* l) : Target(l), queue(q) {} + void handle(qpid::broker::Message& m); + private: + boost::shared_ptr<qpid::broker::Queue> queue; +}; + +class Exchange : public Target +{ + public: + Exchange(boost::shared_ptr<qpid::broker::Exchange> e, pn_link_t* l) : Target(l), exchange(e) {} + void handle(qpid::broker::Message& m); + private: + boost::shared_ptr<qpid::broker::Exchange> exchange; +}; + +Session::Session(pn_session_t* s, qpid::broker::Broker& b, ManagedConnection& c, qpid::sys::OutputControl& o) + : ManagedSession(b, c, (boost::format("%1%") % s).str()), session(s), broker(b), connection(c), out(o), deleted(false) {} + + +Session::ResolvedNode Session::resolve(const std::string name, pn_terminus_t* terminus) +{ + ResolvedNode node; + node.exchange = broker.getExchanges().find(name); + node.queue = broker.getQueues().find(name); + if (!node.queue && !node.exchange && pn_terminus_is_dynamic(terminus)) { + //TODO: handle dynamic creation + //is it a queue or an exchange? + NodeProperties properties; + properties.read(pn_terminus_properties(terminus)); + if (properties.isQueue()) { + node.queue = broker.createQueue(name, properties.getQueueSettings(), this, properties.getAlternateExchange(), connection.getUserid(), connection.getId()).first; + } else { + qpid::framing::FieldTable args; + node.exchange = broker.createExchange(name, properties.getExchangeType(), properties.isDurable(), properties.getAlternateExchange(), + args, connection.getUserid(), connection.getId()).first; + } + } else if (node.queue && node.exchange) { + QPID_LOG_CAT(warning, protocol, "Ambiguous node name; " << name << " could be queue or exchange, assuming queue"); + node.exchange.reset(); + } + return node; +} + +void Session::attach(pn_link_t* link) +{ + if (pn_link_is_sender(link)) { + pn_terminus_t* source = pn_link_remote_source(link); + //i.e a subscription + if (pn_terminus_get_type(source) == PN_UNSPECIFIED) { + throw qpid::Exception("No source specified!");/*invalid-field?*/ + } + std::string name = pn_terminus_get_address(source); + QPID_LOG(debug, "Received attach request for outgoing link from " << name); + pn_terminus_set_address(pn_link_source(link), name.c_str()); + + ResolvedNode node = resolve(name, source); + Filter filter; + filter.read(pn_terminus_filter(source)); + + if (node.queue) { + boost::shared_ptr<Outgoing> q(new Outgoing(broker, node.queue, link, *this, out, false)); + q->init(); + if (filter.hasSubjectFilter()) { + q->setSubjectFilter(filter.getSubjectFilter()); + } + senders[link] = q; + } else if (node.exchange) { + QueueSettings settings(false, true); + //TODO: populate settings from source details when available from engine + boost::shared_ptr<qpid::broker::Queue> queue + = broker.createQueue(name + qpid::types::Uuid(true).str(), settings, this, "", connection.getUserid(), connection.getId()).first; + if (filter.hasSubjectFilter()) { + filter.bind(node.exchange, queue); + filter.write(pn_terminus_filter(pn_link_source(link))); + } else if (node.exchange->getType() == FanOutExchange::typeName) { + node.exchange->bind(queue, std::string(), 0); + } else if (node.exchange->getType() == TopicExchange::typeName) { + node.exchange->bind(queue, "#", 0); + } else { + throw qpid::Exception("Exchange type requires a filter: " + node.exchange->getType());/*not-supported?*/ + } + boost::shared_ptr<Outgoing> q(new Outgoing(broker, queue, link, *this, out, true)); + senders[link] = q; + q->init(); + } else { + pn_terminus_set_type(pn_link_source(link), PN_UNSPECIFIED); + throw qpid::Exception("Node not found: " + name);/*not-found*/ + } + QPID_LOG(debug, "Outgoing link attached"); + } else { + pn_terminus_t* target = pn_link_remote_target(link); + if (pn_terminus_get_type(target) == PN_UNSPECIFIED) { + throw qpid::Exception("No target specified!");/*invalid field?*/ + } + std::string name = pn_terminus_get_address(target); + QPID_LOG(debug, "Received attach request for incoming link to " << name); + pn_terminus_set_address(pn_link_target(link), name.c_str()); + + ResolvedNode node = resolve(name, target); + + if (node.queue) { + boost::shared_ptr<Target> q(new Queue(node.queue, link)); + targets[link] = q; + } else if (node.exchange) { + boost::shared_ptr<Target> e(new Exchange(node.exchange, link)); + targets[link] = e; + } else { + pn_terminus_set_type(pn_link_target(link), PN_UNSPECIFIED); + throw qpid::Exception("Node not found: " + name);/*not-found*/ + } + QPID_LOG(debug, "Incoming link attached"); + } +} + +void Session::detach(pn_link_t* link) +{ + if (pn_link_is_sender(link)) { + Senders::iterator i = senders.find(link); + if (i != senders.end()) { + i->second->detached(); + senders.erase(i); + QPID_LOG(debug, "Outgoing link detached"); + } + } else { + targets.erase(link); + QPID_LOG(debug, "Incoming link detached"); + } +} +namespace { + class Transfer : public qpid::broker::AsyncCompletion::Callback + { + public: + Transfer(pn_delivery_t* d, boost::shared_ptr<Session> s) : delivery(d), session(s) {} + void completed(bool sync) { session->accepted(delivery, sync); } + boost::intrusive_ptr<qpid::broker::AsyncCompletion::Callback> clone() + { + boost::intrusive_ptr<qpid::broker::AsyncCompletion::Callback> copy(new Transfer(delivery, session)); + return copy; + } + private: + pn_delivery_t* delivery; + boost::shared_ptr<Session> session; + }; +} + +void Session::accepted(pn_delivery_t* delivery, bool sync) +{ + if (sync) { + //this is on IO thread + pn_delivery_update(delivery, PN_ACCEPTED); + pn_delivery_settle(delivery);//do we need to check settlement modes/orders? + incomingMessageAccepted(); + } else { + //this is not on IO thread, need to delay processing until on IO thread + qpid::sys::Mutex::ScopedLock l(lock); + if (!deleted) { + completed.push_back(delivery); + out.activateOutput(); + } + } +} + +void Session::incoming(pn_link_t* link, pn_delivery_t* delivery) +{ + pn_delivery_tag_t tag = pn_delivery_tag(delivery); + QPID_LOG(debug, "received delivery: " << std::string(tag.bytes, tag.size)); + boost::intrusive_ptr<Message> received(new Message(pn_delivery_pending(delivery))); + /*ssize_t read = */pn_link_recv(link, received->getData(), received->getSize()); + received->scan(); + pn_link_advance(link); + + qpid::broker::Message message(received, received); + + incomingMessageReceived(); + Targets::iterator target = targets.find(link); + if (target == targets.end()) { + QPID_LOG(error, "Received message on unknown link"); + pn_delivery_update(delivery, PN_REJECTED); + pn_delivery_settle(delivery);//do we need to check settlement modes/orders? + incomingMessageRejected(); + } else { + target->second->handle(message); + received->begin(); + Transfer t(delivery, shared_from_this()); + received->end(t); + if (target->second->needFlow()) out.activateOutput(); + } +} +void Session::outgoing(pn_link_t* link, pn_delivery_t* delivery) +{ + Senders::iterator sender = senders.find(link); + if (sender == senders.end()) { + QPID_LOG(error, "Delivery returned for unknown link"); + } else { + sender->second->handle(delivery); + } +} + +bool Session::dispatch() +{ + bool output(false); + for (Senders::iterator s = senders.begin(); s != senders.end(); ++s) { + if (s->second->dispatch()) output = true; + } + if (completed.size()) { + output = true; + std::deque<pn_delivery_t*> copy; + { + qpid::sys::Mutex::ScopedLock l(lock); + completed.swap(copy); + } + for (std::deque<pn_delivery_t*>::iterator i = copy.begin(); i != copy.end(); ++i) { + accepted(*i, true); + } + } + for (Targets::iterator t = targets.begin(); t != targets.end(); ++t) { + if (t->second->flow()) output = true; + } + + return output; +} + +void Session::close() +{ + for (Senders::iterator i = senders.begin(); i != senders.end(); ++i) { + i->second->detached(); + } + senders.clear(); + targets.clear();//at present no explicit cleanup required for targets + QPID_LOG(debug, "Session closed, all senders cancelled."); + qpid::sys::Mutex::ScopedLock l(lock); + deleted = true; +} + +void Queue::handle(qpid::broker::Message& message) +{ + queue->deliver(message); + --window; +} + +void Exchange::handle(qpid::broker::Message& message) +{ + DeliverableMessage deliverable(message, 0); + exchange->route(deliverable); + --window; +} + +bool Target::flow() +{ + bool issue = window < credit; + if (issue) { + pn_link_flow(link, credit - window);//TODO: proper flow control + window = credit; + } + return issue; +} + +bool Target::needFlow() +{ + return window <= (credit/2); +} + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/Session.h b/cpp/src/qpid/broker/amqp/Session.h new file mode 100644 index 0000000000..7dbdaf05fc --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Session.h @@ -0,0 +1,87 @@ +#ifndef QPID_BROKER_AMQP1_SESSION_H +#define QPID_BROKER_AMQP1_SESSION_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/sys/Mutex.h" +#include "qpid/sys/OutputControl.h" +#include "qpid/broker/amqp/ManagedSession.h" +#include <deque> +#include <map> +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +struct pn_delivery_t; +struct pn_link_t; +struct pn_session_t; +struct pn_terminus_t; + +namespace qpid { +namespace broker { + +class Broker; +class Exchange; +class Queue; + +namespace amqp { + +class ManagedConnection; +class Outgoing; +class Target; +/** + * + */ +class Session : public ManagedSession, public boost::enable_shared_from_this<Session> +{ + public: + Session(pn_session_t*, qpid::broker::Broker&, ManagedConnection&, qpid::sys::OutputControl&); + void attach(pn_link_t*); + void detach(pn_link_t*); + void incoming(pn_link_t*, pn_delivery_t*); + void outgoing(pn_link_t*, pn_delivery_t*); + bool dispatch(); + void close(); + + //called when a transfer is completly processed (e.g.including stored on disk) + void accepted(pn_delivery_t*, bool sync); + private: + typedef std::map<pn_link_t*, boost::shared_ptr<Outgoing> > Senders; + typedef std::map<pn_link_t*, boost::shared_ptr<Target> > Targets; + pn_session_t* session; + qpid::broker::Broker& broker; + ManagedConnection& connection; + qpid::sys::OutputControl& out; + Targets targets; + Senders senders; + std::deque<pn_delivery_t*> completed; + bool deleted; + qpid::sys::Mutex lock; + struct ResolvedNode + { + boost::shared_ptr<qpid::broker::Exchange> exchange; + boost::shared_ptr<qpid::broker::Queue> queue; + }; + + ResolvedNode resolve(const std::string name, pn_terminus_t* terminus); +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP1_SESSION_H*/ diff --git a/cpp/src/qpid/broker/amqp/Translation.cpp b/cpp/src/qpid/broker/amqp/Translation.cpp new file mode 100644 index 0000000000..ca2094b965 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Translation.cpp @@ -0,0 +1,241 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/amqp/Translation.h" +#include "qpid/broker/amqp/Outgoing.h" +#include "qpid/broker/amqp_0_10/MessageTransfer.h" +#include "qpid/amqp/Decoder.h" +#include "qpid/amqp/descriptors.h" +#include "qpid/amqp/MessageEncoder.h" +#include "qpid/amqp_0_10/Codecs.h" +#include "qpid/types/Variant.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Statement.h" +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace broker { +namespace amqp { +namespace { + +const std::string EMPTY; +const std::string FORWARD_SLASH("/"); + +std::string translate(const qpid::framing::ReplyTo r) +{ + if (r.hasExchange()) { + if (r.hasRoutingKey()) return r.getExchange() + FORWARD_SLASH + r.getRoutingKey(); + else return r.getExchange(); + } else return r.getRoutingKey(); +} +std::string translate(const qpid::amqp::CharSequence& chars) +{ + if (chars.data && chars.size) return std::string(chars.data, chars.size); + else return EMPTY; +} +bool setMessageId(qpid::framing::MessageProperties& m, const qpid::amqp::CharSequence& chars) +{ + if (chars.data && chars.size) { + if (chars.size == 16) { + m.setMessageId(qpid::framing::Uuid(chars.data)); + return true; + } else { + std::istringstream in(translate(chars)); + qpid::framing::Uuid uuid; + in >> uuid; + if (!in.fail()) { + m.setMessageId(uuid); + return true; + } + } + } + return false; +} +class Properties_0_10 : public qpid::amqp::MessageEncoder::Properties +{ + public: + bool hasMessageId() const { return messageProperties && messageProperties->hasMessageId(); } + std::string getMessageId() const { return messageProperties ? messageProperties->getMessageId().str() : EMPTY; } + bool hasUserId() const { return messageProperties && messageProperties->hasUserId(); } + std::string getUserId() const { return messageProperties ? messageProperties->getUserId() : EMPTY; } + bool hasTo() const { return getDestination().size() || hasSubject(); } + std::string getTo() const { return getDestination().size() ? getDestination() : getSubject(); } + bool hasSubject() const { return deliveryProperties && getDestination().size() && deliveryProperties->hasRoutingKey(); } + std::string getSubject() const { return deliveryProperties && getDestination().size() ? deliveryProperties->getRoutingKey() : EMPTY; } + bool hasReplyTo() const { return messageProperties && messageProperties->hasReplyTo(); } + std::string getReplyTo() const { return messageProperties ? translate(messageProperties->getReplyTo()) : EMPTY; } + bool hasCorrelationId() const { return messageProperties && messageProperties->hasCorrelationId(); } + std::string getCorrelationId() const { return messageProperties ? messageProperties->getCorrelationId() : EMPTY; } + bool hasContentType() const { return messageProperties && messageProperties->hasContentType(); } + std::string getContentType() const { return messageProperties ? messageProperties->getContentType() : EMPTY; } + bool hasContentEncoding() const { return messageProperties && messageProperties->hasContentEncoding(); } + std::string getContentEncoding() const { return messageProperties ? messageProperties->getContentEncoding() : EMPTY; } + bool hasAbsoluteExpiryTime() const { return deliveryProperties && deliveryProperties->hasExpiration(); } + int64_t getAbsoluteExpiryTime() const { return deliveryProperties ? deliveryProperties->getExpiration() : 0; } + bool hasCreationTime() const { return false; } + int64_t getCreationTime() const { return 0; } + bool hasGroupId() const {return false; } + std::string getGroupId() const { return EMPTY; } + bool hasGroupSequence() const { return false; } + uint32_t getGroupSequence() const { return 0; } + bool hasReplyToGroupId() const { return false; } + std::string getReplyToGroupId() const { return EMPTY; } + + const qpid::framing::FieldTable& getApplicationProperties() { return messageProperties->getApplicationHeaders(); } + Properties_0_10(const qpid::broker::amqp_0_10::MessageTransfer& t) : transfer(t), + messageProperties(transfer.getProperties<qpid::framing::MessageProperties>()), + deliveryProperties(transfer.getProperties<qpid::framing::DeliveryProperties>()) + {} + private: + const qpid::broker::amqp_0_10::MessageTransfer& transfer; + const qpid::framing::MessageProperties* messageProperties; + const qpid::framing::DeliveryProperties* deliveryProperties; + + std::string getDestination() const + { + return transfer.getMethod<qpid::framing::MessageTransferBody>()->getDestination(); + } +}; +} + +Translation::Translation(const qpid::broker::Message& m) : original(m) {} + + +boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> Translation::getTransfer() +{ + boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> t = + boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer>(dynamic_cast<const qpid::broker::amqp_0_10::MessageTransfer*>(&original.getEncoding())); + if (t) { + return t;//no translation required + } else { + const Message* message = dynamic_cast<const Message*>(&original.getEncoding()); + if (message) { + //translate 1.0 message into 0-10 + boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> transfer(new qpid::broker::amqp_0_10::MessageTransfer()); + qpid::framing::AMQFrame method((qpid::framing::MessageTransferBody(qpid::framing::ProtocolVersion(), EMPTY, 0, 0))); + qpid::framing::AMQFrame header((qpid::framing::AMQHeaderBody())); + qpid::framing::AMQFrame content((qpid::framing::AMQContentBody())); + method.setEof(false); + header.setBof(false); + header.setEof(false); + content.setBof(false); + + transfer->getFrames().append(method); + transfer->getFrames().append(header); + + qpid::amqp::CharSequence body = message->getBody(); + content.castBody<qpid::framing::AMQContentBody>()->getData().assign(body.data, body.size); + transfer->getFrames().append(content); + + qpid::framing::MessageProperties* props = + transfer->getFrames().getHeaders()->get<qpid::framing::MessageProperties>(true); + props->setContentLength(body.size); + + qpid::amqp::MessageId mid = message->getMessageId(); + qpid::framing::Uuid uuid; + switch (mid.type) { + case qpid::amqp::MessageId::UUID: + case qpid::amqp::MessageId::BYTES: + if (mid.value.bytes.size == 0) break; + if (setMessageId(*props, mid.value.bytes)) break; + case qpid::amqp::MessageId::ULONG: + QPID_LOG(info, "Skipping message id in translation from 1.0 to 0-10 as it is not a UUID"); + break; + } + + qpid::amqp::MessageId cid = message->getCorrelationId(); + switch (cid.type) { + case qpid::amqp::MessageId::UUID: + assert(cid.value.bytes.size = 16); + props->setCorrelationId(qpid::framing::Uuid(cid.value.bytes.data).str()); + break; + case qpid::amqp::MessageId::BYTES: + if (cid.value.bytes.size) { + props->setCorrelationId(translate(cid.value.bytes)); + } + break; + case qpid::amqp::MessageId::ULONG: + props->setCorrelationId(boost::lexical_cast<std::string>(cid.value.ulong)); + break; + } + // TODO: ReplyTo - there is no way to reliably determine + // the type of the node from just its name, unless we + // query the brokers registries + + if (message->getContentType()) props->setContentType(translate(message->getContentType())); + if (message->getContentEncoding()) props->setContentEncoding(translate(message->getContentEncoding())); + props->setUserId(message->getUserId()); + // TODO: FieldTable applicationHeaders; + qpid::amqp::CharSequence ap = message->getApplicationProperties(); + if (ap) { + qpid::amqp::Decoder d(ap.data, ap.size); + qpid::amqp_0_10::translate(d.readMap(), props->getApplicationHeaders()); + } + + qpid::framing::DeliveryProperties* dp = + transfer->getFrames().getHeaders()->get<qpid::framing::DeliveryProperties>(true); + dp->setPriority(message->getPriority()); + if (message->isPersistent()) dp->setDeliveryMode(2); + if (message->getRoutingKey().size()) dp->setRoutingKey(message->getRoutingKey()); + + return transfer.get(); + } else { + throw qpid::Exception("Could not write message data in AMQP 0-10 format"); + } + } +} + +void Translation::write(Outgoing& out) +{ + const Message* message = dynamic_cast<const Message*>(&original.getEncoding()); + if (message) { + //write annotations + //TODO: merge in any newly added annotations + qpid::amqp::CharSequence deliveryAnnotations = message->getDeliveryAnnotations(); + qpid::amqp::CharSequence messageAnnotations = message->getMessageAnnotations(); + if (deliveryAnnotations.size) out.write(deliveryAnnotations.data, deliveryAnnotations.size); + if (messageAnnotations.size) out.write(messageAnnotations.data, messageAnnotations.size); + //write bare message + qpid::amqp::CharSequence bareMessage = message->getBareMessage(); + if (bareMessage.size) out.write(bareMessage.data, bareMessage.size); + //write footer: + qpid::amqp::CharSequence footer = message->getFooter(); + if (footer.size) out.write(footer.data, footer.size); + } else { + const qpid::broker::amqp_0_10::MessageTransfer* transfer = dynamic_cast<const qpid::broker::amqp_0_10::MessageTransfer*>(&original.getEncoding()); + if (transfer) { + Properties_0_10 properties(*transfer); + qpid::types::Variant::Map applicationProperties; + qpid::amqp_0_10::translate(properties.getApplicationProperties(), applicationProperties); + std::string content = transfer->getContent(); + size_t size = qpid::amqp::MessageEncoder::getEncodedSize(properties, applicationProperties, content); + std::vector<char> buffer(size); + qpid::amqp::MessageEncoder encoder(&buffer[0], buffer.size()); + encoder.writeProperties(properties); + encoder.writeApplicationProperties(applicationProperties); + encoder.writeBinary(content, &qpid::amqp::message::DATA); + out.write(&buffer[0], encoder.getPosition()); + } else { + QPID_LOG(error, "Could not write message data in AMQP 1.0 format"); + } + } +} + +}}} // namespace qpid::broker::amqp diff --git a/cpp/src/qpid/broker/amqp/Translation.h b/cpp/src/qpid/broker/amqp/Translation.h new file mode 100644 index 0000000000..64d96560e3 --- /dev/null +++ b/cpp/src/qpid/broker/amqp/Translation.h @@ -0,0 +1,58 @@ +#ifndef QPID_BROKER_AMQP_TRANSLATION_H +#define QPID_BROKER_AMQP_TRANSLATION_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace broker { +class Message; +namespace amqp_0_10 { +class MessageTransfer; +} +namespace amqp { + +class Outgoing; +/** + * + */ +class Translation +{ + public: + Translation(const qpid::broker::Message& message); + + /** + * @returns a pointer to an AMQP 0-10 message transfer suitable + * for sending on an 0-10 session, translating from 1.0 as + * necessary + */ + boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> getTransfer(); + /** + * Writes the AMQP 1.0 bare message and any annotations, translating from 0-10 if necessary + */ + void write(Outgoing&); + private: + const qpid::broker::Message& original; +}; +}}} // namespace qpid::broker::amqp + +#endif /*!QPID_BROKER_AMQP_TRANSLATION_H*/ diff --git a/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.cpp b/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.cpp index cac4434c48..b78b69b2d6 100644 --- a/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.cpp +++ b/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.cpp @@ -29,6 +29,7 @@ #include "qpid/framing/TypeFilter.h" #include "qpid/framing/SendContent.h" #include "qpid/log/Statement.h" +#include "boost/lexical_cast.hpp" using namespace qpid::framing; @@ -51,7 +52,12 @@ std::string MessageTransfer::getAnnotationAsString(const std::string& key) const { const qpid::framing::MessageProperties* mp = getProperties<qpid::framing::MessageProperties>(); if (mp && mp->hasApplicationHeaders()) { - return mp->getApplicationHeaders().getAsString(key); + FieldTable::ValuePtr value = mp->getApplicationHeaders().get(key); + if (value) { + if (value->convertsTo<std::string>()) return value->get<std::string>(); + else if (value->convertsTo<int>()) return boost::lexical_cast<std::string>(value->get<int>()); + } + return std::string(); } else { return std::string(); } @@ -116,11 +122,6 @@ void MessageTransfer::computeRequiredCredit() requiredCredit = sum.getSize(); cachedRequiredCredit = true; } -uint32_t MessageTransfer::getRequiredCredit(const qpid::broker::Message& msg) -{ - //TODO: may need to reflect annotations and other modifications in this also - return get(msg).getRequiredCredit(); -} qpid::framing::FrameSet& MessageTransfer::getFrames() { diff --git a/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.h b/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.h index 590e389518..9e432235e6 100644 --- a/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.h +++ b/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.h @@ -109,7 +109,6 @@ class MessageTransfer : public qpid::broker::Message::Encoding, public qpid::bro QPID_BROKER_EXTERN bool isLastQMFResponse(const std::string correlation) const; static bool isImmediateDeliveryRequired(const qpid::broker::Message& message); - static uint32_t getRequiredCredit(const qpid::broker::Message&); static MessageTransfer& get(qpid::broker::Message& message) { return *dynamic_cast<MessageTransfer*>(&message.getEncoding()); } diff --git a/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp index 40e74be018..c04d037a6e 100644 --- a/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp +++ b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp @@ -23,6 +23,7 @@ // accessing authentication mechanisms, analogous to Cyrus SASL. #include "qpid/broker/Connection.h" +#include "qpid/broker/Broker.h" #include "qpid/log/Statement.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/framing/FieldValue.h" diff --git a/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp b/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp index 420e04e832..a07afe45ae 100644 --- a/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp +++ b/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp @@ -1,352 +1,379 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "qpid/sys/ProtocolFactory.h" - -#include "qpid/Plugin.h" -#include "qpid/broker/Broker.h" -#include "qpid/log/Statement.h" -#include "qpid/sys/AsynchIOHandler.h" -#include "qpid/sys/ConnectionCodec.h" -#include "qpid/sys/Socket.h" -#include "qpid/sys/SocketAddress.h" -#include "qpid/sys/SystemInfo.h" -#include "qpid/sys/windows/SslAsynchIO.h" - -#include <boost/bind.hpp> -#include <boost/ptr_container/ptr_vector.hpp> -#include <memory> - -// security.h needs to see this to distinguish from kernel use. -#define SECURITY_WIN32 -#include <security.h> -#include <Schnlsp.h> -#undef SECURITY_WIN32 - - -namespace qpid { -namespace sys { - -class Timer; - -namespace windows { - -struct SslServerOptions : qpid::Options -{ - std::string certStore; - std::string certStoreLocation; - std::string certName; - uint16_t port; - bool clientAuth; - - SslServerOptions() : qpid::Options("SSL Options"), - certStore("My"), - certStoreLocation("CurrentUser"), - certName("localhost"), - port(5671), - clientAuth(false) - { - qpid::Address me; - if (qpid::sys::SystemInfo::getLocalHostname(me)) - certName = me.host; - - addOptions() - ("ssl-cert-store", optValue(certStore, "NAME"), "Local store name from which to obtain certificate") - ("ssl-cert-store-location", optValue(certStoreLocation, "NAME"), - "Local store name location for certificates ( CurrentUser | LocalMachine | CurrentService )") - ("ssl-cert-name", optValue(certName, "NAME"), "Name of the certificate to use") - ("ssl-port", optValue(port, "PORT"), "Port on which to listen for SSL connections") - ("ssl-require-client-authentication", optValue(clientAuth), - "Forces clients to authenticate in order to establish an SSL connection"); - } -}; - -class SslProtocolFactory : public qpid::sys::ProtocolFactory { - boost::ptr_vector<Socket> listeners; - boost::ptr_vector<AsynchAcceptor> acceptors; - Timer& brokerTimer; - uint32_t maxNegotiateTime; - uint16_t listeningPort; - const bool tcpNoDelay; - std::string brokerHost; - const bool clientAuthSelected; - std::auto_ptr<qpid::sys::AsynchAcceptor> acceptor; - ConnectFailedCallback connectFailedCallback; - CredHandle credHandle; - - public: - SslProtocolFactory(const SslServerOptions&, const std::string& host, const std::string& port, - int backlog, bool nodelay, - Timer& timer, uint32_t maxTime); - ~SslProtocolFactory(); - void accept(sys::Poller::shared_ptr, sys::ConnectionCodec::Factory*); - void connect(sys::Poller::shared_ptr, const std::string& host, const std::string& port, - sys::ConnectionCodec::Factory*, - ConnectFailedCallback failed); - - uint16_t getPort() const; - bool supports(const std::string& capability); - - private: - void connectFailed(const qpid::sys::Socket&, - int err, - const std::string& msg); - void established(sys::Poller::shared_ptr, - const qpid::sys::Socket&, - sys::ConnectionCodec::Factory*, - bool isClient); -}; - -// Static instance to initialise plugin -static struct SslPlugin : public Plugin { - SslServerOptions options; - - Options* getOptions() { return &options; } - - void earlyInitialize(Target&) { - } - - void initialize(Target& target) { - broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); - // Only provide to a Broker - if (broker) { - try { - const broker::Broker::Options& opts = broker->getOptions(); - ProtocolFactory::shared_ptr protocol(new SslProtocolFactory(options, - "", boost::lexical_cast<std::string>(options.port), - opts.connectionBacklog, opts.tcpNoDelay, - broker->getTimer(), opts.maxNegotiateTime)); - QPID_LOG(notice, "Listening for SSL connections on TCP port " << protocol->getPort()); - broker->registerProtocolFactory("ssl", protocol); - } catch (const std::exception& e) { - QPID_LOG(error, "Failed to initialise SSL listener: " << e.what()); - } - } - } -} sslPlugin; - -SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, - const std::string& host, const std::string& port, - int backlog, bool nodelay, - Timer& timer, uint32_t maxTime) - : brokerTimer(timer), - maxNegotiateTime(maxTime), - tcpNoDelay(nodelay), - clientAuthSelected(options.clientAuth) { - - // Make sure that certificate store is good before listening to sockets - // to avoid having open and listening sockets when there is no cert store - SecInvalidateHandle(&credHandle); - - // Get the certificate for this server. - DWORD flags = 0; - std::string certStoreLocation = options.certStoreLocation; +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/ProtocolFactory.h"
+
+#include "qpid/Plugin.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/AsynchIOHandler.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/sys/windows/SslAsynchIO.h"
+
+#include <boost/bind.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <memory>
+
+// security.h needs to see this to distinguish from kernel use.
+#define SECURITY_WIN32
+#include <security.h>
+#include <Schnlsp.h>
+#undef SECURITY_WIN32
+
+
+namespace qpid {
+namespace sys {
+
+class Timer;
+
+namespace windows {
+
+struct SslServerOptions : qpid::Options
+{
+ std::string certStore;
+ std::string certStoreLocation;
+ std::string certName;
+ uint16_t port;
+ bool clientAuth;
+
+ SslServerOptions() : qpid::Options("SSL Options"),
+ certStore("My"),
+ certStoreLocation("CurrentUser"),
+ certName("localhost"),
+ port(5671),
+ clientAuth(false)
+ {
+ qpid::Address me;
+ if (qpid::sys::SystemInfo::getLocalHostname(me))
+ certName = me.host;
+
+ addOptions()
+ ("ssl-cert-store", optValue(certStore, "NAME"), "Local store name from which to obtain certificate")
+ ("ssl-cert-store-location", optValue(certStoreLocation, "NAME"),
+ "Local store name location for certificates ( CurrentUser | LocalMachine | CurrentService )")
+ ("ssl-cert-name", optValue(certName, "NAME"), "Name of the certificate to use")
+ ("ssl-port", optValue(port, "PORT"), "Port on which to listen for SSL connections")
+ ("ssl-require-client-authentication", optValue(clientAuth),
+ "Forces clients to authenticate in order to establish an SSL connection");
+ }
+};
+
+class SslProtocolFactory : public qpid::sys::ProtocolFactory {
+ boost::ptr_vector<Socket> listeners;
+ boost::ptr_vector<AsynchAcceptor> acceptors;
+ Timer& brokerTimer;
+ uint32_t maxNegotiateTime;
+ uint16_t listeningPort;
+ const bool tcpNoDelay;
+ std::string brokerHost;
+ const bool clientAuthSelected;
+ std::auto_ptr<qpid::sys::AsynchAcceptor> acceptor;
+ ConnectFailedCallback connectFailedCallback;
+ CredHandle credHandle;
+
+ public:
+ SslProtocolFactory(const qpid::broker::Broker::Options& opts, const SslServerOptions&, Timer& timer);
+ ~SslProtocolFactory();
+ void accept(sys::Poller::shared_ptr, sys::ConnectionCodec::Factory*);
+ void connect(sys::Poller::shared_ptr, const std::string& name, const std::string& host, const std::string& port,
+ sys::ConnectionCodec::Factory*,
+ ConnectFailedCallback failed);
+
+ uint16_t getPort() const;
+
+ private:
+ void connectFailed(const qpid::sys::Socket&,
+ int err,
+ const std::string& msg);
+ void establishedIncoming(sys::Poller::shared_ptr, const qpid::sys::Socket&, sys::ConnectionCodec::Factory*);
+ void establishedOutgoing(sys::Poller::shared_ptr, const qpid::sys::Socket&, sys::ConnectionCodec::Factory*, std::string& );
+ void establishedCommon(sys::Poller::shared_ptr, sys::AsynchIOHandler*, sys::AsynchIO*, const qpid::sys::Socket&);
+};
+
+// Static instance to initialise plugin
+static struct SslPlugin : public Plugin {
+ SslServerOptions options;
+
+ Options* getOptions() { return &options; }
+
+ void earlyInitialize(Target&) {
+ }
+
+ void initialize(Target& target) {
+ broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ // Only provide to a Broker
+ if (broker) {
+ try {
+ const broker::Broker::Options& opts = broker->getOptions();
+ ProtocolFactory::shared_ptr protocol(new SslProtocolFactory(opts, options, broker->getTimer()));
+ QPID_LOG(notice, "Listening for SSL connections on TCP port " << protocol->getPort());
+ broker->registerProtocolFactory("ssl", protocol);
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to initialise SSL listener: " << e.what());
+ }
+ }
+ }
+} sslPlugin;
+
+namespace {
+ // Expand list of Interfaces and addresses to a list of addresses
+ std::vector<std::string> expandInterfaces(const std::vector<std::string>& interfaces) {
+ std::vector<std::string> addresses;
+ // If there are no specific interfaces listed use a single "" to listen on every interface
+ if (interfaces.empty()) {
+ addresses.push_back("");
+ return addresses;
+ }
+ for (unsigned i = 0; i < interfaces.size(); ++i) {
+ const std::string& interface = interfaces[i];
+ if (!(SystemInfo::getInterfaceAddresses(interface, addresses))) {
+ // We don't have an interface of that name -
+ // Check for IPv6 ('[' ']') brackets and remove them
+ // then pass to be looked up directly
+ if (interface[0]=='[' && interface[interface.size()-1]==']') {
+ addresses.push_back(interface.substr(1, interface.size()-2));
+ } else {
+ addresses.push_back(interface);
+ }
+ }
+ }
+ return addresses;
+ }
+}
+
+SslProtocolFactory::SslProtocolFactory(const qpid::broker::Broker::Options& opts, const SslServerOptions& options, Timer& timer)
+ : brokerTimer(timer),
+ maxNegotiateTime(opts.maxNegotiateTime),
+ tcpNoDelay(opts.tcpNoDelay),
+ clientAuthSelected(options.clientAuth) {
+
+ // Make sure that certificate store is good before listening to sockets
+ // to avoid having open and listening sockets when there is no cert store
+ SecInvalidateHandle(&credHandle);
+
+ // Get the certificate for this server.
+ DWORD flags = 0;
+ std::string certStoreLocation = options.certStoreLocation;
std::transform(certStoreLocation.begin(), certStoreLocation.end(), certStoreLocation.begin(), ::tolower);
- if (certStoreLocation == "currentuser") { - flags = CERT_SYSTEM_STORE_CURRENT_USER; - } else if (certStoreLocation == "localmachine") { - flags = CERT_SYSTEM_STORE_LOCAL_MACHINE; - } else if (certStoreLocation == "currentservice") { - flags = CERT_SYSTEM_STORE_CURRENT_SERVICE; - } else { - QPID_LOG(error, "Unrecognised SSL certificate store location: " << options.certStoreLocation - << " - Using default location"); - } - HCERTSTORE certStoreHandle; - certStoreHandle = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A, - X509_ASN_ENCODING, - 0, - flags | - CERT_STORE_READONLY_FLAG, - options.certStore.c_str()); - if (!certStoreHandle) - throw qpid::Exception(QPID_MSG("Opening store " << options.certStore << " " << qpid::sys::strError(GetLastError()))); - - PCCERT_CONTEXT certContext; - certContext = ::CertFindCertificateInStore(certStoreHandle, - X509_ASN_ENCODING, - 0, - CERT_FIND_SUBJECT_STR_A, - options.certName.c_str(), - NULL); - if (certContext == NULL) { - int err = ::GetLastError(); - ::CertCloseStore(certStoreHandle, 0); - throw qpid::Exception(QPID_MSG("Locating certificate " << options.certName << " in store " << options.certStore << " " << qpid::sys::strError(GetLastError()))); - throw QPID_WINDOWS_ERROR(err); - } - - SCHANNEL_CRED cred; - memset(&cred, 0, sizeof(cred)); - cred.dwVersion = SCHANNEL_CRED_VERSION; - cred.cCreds = 1; - cred.paCred = &certContext; - SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, - UNISP_NAME, - SECPKG_CRED_INBOUND, - NULL, - &cred, - NULL, - NULL, - &credHandle, - NULL); - if (status != SEC_E_OK) - throw QPID_WINDOWS_ERROR(status); - ::CertFreeCertificateContext(certContext); - ::CertCloseStore(certStoreHandle, 0); - - // Listen to socket(s) - SocketAddress sa(host, port); - - // We must have at least one resolved address - QPID_LOG(info, "SSL Listening to: " << sa.asString()) - Socket* s = new Socket; - listeningPort = s->listen(sa, backlog); - listeners.push_back(s); - - // Try any other resolved addresses - while (sa.nextAddress()) { - QPID_LOG(info, "SSL Listening to: " << sa.asString()) - Socket* s = new Socket; - s->listen(sa, backlog); - listeners.push_back(s); - } -} - -SslProtocolFactory::~SslProtocolFactory() { - ::FreeCredentialsHandle(&credHandle); -} - -void SslProtocolFactory::connectFailed(const qpid::sys::Socket&, - int err, - const std::string& msg) { - if (connectFailedCallback) - connectFailedCallback(err, msg); -} - -void SslProtocolFactory::established(sys::Poller::shared_ptr poller, - const qpid::sys::Socket& s, - sys::ConnectionCodec::Factory* f, - bool isClient) { - sys::AsynchIOHandler* async = new sys::AsynchIOHandler(s.getFullAddress(), f); - - if (tcpNoDelay) { - s.setTcpNoDelay(); - QPID_LOG(info, - "Set TCP_NODELAY on connection to " << s.getPeerAddress()); - } - - SslAsynchIO *aio; - if (isClient) { - async->setClient(); - aio = - new qpid::sys::windows::ClientSslAsynchIO(brokerHost, - s, - credHandle, - boost::bind(&AsynchIOHandler::readbuff, async, _1, _2), - boost::bind(&AsynchIOHandler::eof, async, _1), - boost::bind(&AsynchIOHandler::disconnect, async, _1), - boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2), - boost::bind(&AsynchIOHandler::nobuffs, async, _1), - boost::bind(&AsynchIOHandler::idle, async, _1)); - } - else { - aio = - new qpid::sys::windows::ServerSslAsynchIO(clientAuthSelected, - s, - credHandle, - boost::bind(&AsynchIOHandler::readbuff, async, _1, _2), - boost::bind(&AsynchIOHandler::eof, async, _1), - boost::bind(&AsynchIOHandler::disconnect, async, _1), - boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2), - boost::bind(&AsynchIOHandler::nobuffs, async, _1), - boost::bind(&AsynchIOHandler::idle, async, _1)); - } - - async->init(aio, brokerTimer, maxNegotiateTime); - aio->start(poller); -} - -uint16_t SslProtocolFactory::getPort() const { - return listeningPort; // Immutable no need for lock. -} - -void SslProtocolFactory::accept(sys::Poller::shared_ptr poller, - sys::ConnectionCodec::Factory* fact) { - for (unsigned i = 0; i<listeners.size(); ++i) { - acceptors.push_back( - AsynchAcceptor::create(listeners[i], - boost::bind(&SslProtocolFactory::established, this, poller, _1, fact, false))); - acceptors[i].start(poller); - } -} - -void SslProtocolFactory::connect(sys::Poller::shared_ptr poller, - const std::string& host, - const std::string& port, - sys::ConnectionCodec::Factory* fact, - ConnectFailedCallback failed) -{ - SCHANNEL_CRED cred; - memset(&cred, 0, sizeof(cred)); - cred.dwVersion = SCHANNEL_CRED_VERSION; - SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, - UNISP_NAME, - SECPKG_CRED_OUTBOUND, - NULL, - &cred, - NULL, - NULL, - &credHandle, - NULL); - if (status != SEC_E_OK) - throw QPID_WINDOWS_ERROR(status); - - brokerHost = host; - // Note that the following logic does not cause a memory leak. - // The allocated Socket is freed either by the AsynchConnector - // upon connection failure or by the AsynchIO upon connection - // shutdown. The allocated AsynchConnector frees itself when it - // is no longer needed. - qpid::sys::Socket* socket = new qpid::sys::Socket(); - connectFailedCallback = failed; - AsynchConnector::create(*socket, - host, - port, - boost::bind(&SslProtocolFactory::established, - this, poller, _1, fact, true), - boost::bind(&SslProtocolFactory::connectFailed, - this, _1, _2, _3)); -} - -namespace -{ -const std::string SSL = "ssl"; -} - -bool SslProtocolFactory::supports(const std::string& capability) -{ - std::string s = capability; - transform(s.begin(), s.end(), s.begin(), tolower); - return s == SSL; -} - -}}} // namespace qpid::sys::windows + if (certStoreLocation == "currentuser") {
+ flags = CERT_SYSTEM_STORE_CURRENT_USER;
+ } else if (certStoreLocation == "localmachine") {
+ flags = CERT_SYSTEM_STORE_LOCAL_MACHINE;
+ } else if (certStoreLocation == "currentservice") {
+ flags = CERT_SYSTEM_STORE_CURRENT_SERVICE;
+ } else {
+ QPID_LOG(error, "Unrecognised SSL certificate store location: " << options.certStoreLocation
+ << " - Using default location");
+ }
+ HCERTSTORE certStoreHandle;
+ certStoreHandle = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A,
+ X509_ASN_ENCODING,
+ 0,
+ flags |
+ CERT_STORE_READONLY_FLAG,
+ options.certStore.c_str());
+ if (!certStoreHandle)
+ throw qpid::Exception(QPID_MSG("Opening store " << options.certStore << " " << qpid::sys::strError(GetLastError())));
+
+ PCCERT_CONTEXT certContext;
+ certContext = ::CertFindCertificateInStore(certStoreHandle,
+ X509_ASN_ENCODING,
+ 0,
+ CERT_FIND_SUBJECT_STR_A,
+ options.certName.c_str(),
+ NULL);
+ if (certContext == NULL) {
+ int err = ::GetLastError();
+ ::CertCloseStore(certStoreHandle, 0);
+ throw qpid::Exception(QPID_MSG("Locating certificate " << options.certName << " in store " << options.certStore << " " << qpid::sys::strError(GetLastError())));
+ throw QPID_WINDOWS_ERROR(err);
+ }
+
+ SCHANNEL_CRED cred;
+ memset(&cred, 0, sizeof(cred));
+ cred.dwVersion = SCHANNEL_CRED_VERSION;
+ cred.cCreds = 1;
+ cred.paCred = &certContext;
+ SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL,
+ UNISP_NAME,
+ SECPKG_CRED_INBOUND,
+ NULL,
+ &cred,
+ NULL,
+ NULL,
+ &credHandle,
+ NULL);
+ if (status != SEC_E_OK)
+ throw QPID_WINDOWS_ERROR(status);
+ ::CertFreeCertificateContext(certContext);
+ ::CertCloseStore(certStoreHandle, 0);
+
+ std::vector<std::string> addresses = expandInterfaces(opts.listenInterfaces);
+ if (addresses.empty()) {
+ // We specified some interfaces, but couldn't find addresses for them
+ QPID_LOG(warning, "TCP/TCP6: No specified network interfaces found: Not Listening");
+ listeningPort = 0;
+ }
+
+ for (unsigned i = 0; i<addresses.size(); ++i) {
+ QPID_LOG(debug, "Using interface: " << addresses[i]);
+ SocketAddress sa(addresses[i], boost::lexical_cast<std::string>(options.port));
+
+
+ // We must have at least one resolved address
+ QPID_LOG(info, "SSL Listening to: " << sa.asString())
+ Socket* s = createSocket();
+ listeningPort = s->listen(sa, opts.connectionBacklog);
+ listeners.push_back(s);
+
+ // Try any other resolved addresses
+ while (sa.nextAddress()) {
+ QPID_LOG(info, "SSL Listening to: " << sa.asString())
+ Socket* s = createSocket();
+ s->listen(sa, opts.connectionBacklog);
+ listeners.push_back(s);
+ }
+ }
+}
+
+SslProtocolFactory::~SslProtocolFactory() {
+ ::FreeCredentialsHandle(&credHandle);
+}
+
+void SslProtocolFactory::connectFailed(const qpid::sys::Socket&,
+ int err,
+ const std::string& msg) {
+ if (connectFailedCallback)
+ connectFailedCallback(err, msg);
+}
+
+void SslProtocolFactory::establishedIncoming(sys::Poller::shared_ptr poller,
+ const qpid::sys::Socket& s,
+ sys::ConnectionCodec::Factory* f) {
+ sys::AsynchIOHandler* async = new sys::AsynchIOHandler(s.getFullAddress(), f, false, false);
+
+ sys::AsynchIO *aio =
+ new qpid::sys::windows::ServerSslAsynchIO(
+ clientAuthSelected,
+ s,
+ credHandle,
+ boost::bind(&AsynchIOHandler::readbuff, async, _1, _2),
+ boost::bind(&AsynchIOHandler::eof, async, _1),
+ boost::bind(&AsynchIOHandler::disconnect, async, _1),
+ boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2),
+ boost::bind(&AsynchIOHandler::nobuffs, async, _1),
+ boost::bind(&AsynchIOHandler::idle, async, _1));
+
+ establishedCommon(poller, async, aio, s);
+}
+
+void SslProtocolFactory::establishedOutgoing(sys::Poller::shared_ptr poller,
+ const qpid::sys::Socket& s,
+ sys::ConnectionCodec::Factory* f,
+ std::string& name) {
+ sys::AsynchIOHandler* async = new sys::AsynchIOHandler(name, f, true, false);
+
+ sys::AsynchIO *aio =
+ new qpid::sys::windows::ClientSslAsynchIO(
+ brokerHost,
+ s,
+ credHandle,
+ boost::bind(&AsynchIOHandler::readbuff, async, _1, _2),
+ boost::bind(&AsynchIOHandler::eof, async, _1),
+ boost::bind(&AsynchIOHandler::disconnect, async, _1),
+ boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2),
+ boost::bind(&AsynchIOHandler::nobuffs, async, _1),
+ boost::bind(&AsynchIOHandler::idle, async, _1));
+
+ establishedCommon(poller, async, aio, s);
+}
+
+void SslProtocolFactory::establishedCommon(sys::Poller::shared_ptr poller,
+ sys::AsynchIOHandler* async,
+ sys::AsynchIO* aio,
+ const qpid::sys::Socket& s) {
+ if (tcpNoDelay) {
+ s.setTcpNoDelay();
+ QPID_LOG(info,
+ "Set TCP_NODELAY on connection to " << s.getPeerAddress());
+ }
+
+ async->init(aio, brokerTimer, maxNegotiateTime);
+ aio->start(poller);
+}
+
+uint16_t SslProtocolFactory::getPort() const {
+ return listeningPort; // Immutable no need for lock.
+}
+
+void SslProtocolFactory::accept(sys::Poller::shared_ptr poller,
+ sys::ConnectionCodec::Factory* fact) {
+ for (unsigned i = 0; i<listeners.size(); ++i) {
+ acceptors.push_back(
+ AsynchAcceptor::create(listeners[i],
+ boost::bind(&SslProtocolFactory::establishedIncoming, this, poller, _1, fact)));
+ acceptors[i].start(poller);
+ }
+}
+
+void SslProtocolFactory::connect(sys::Poller::shared_ptr poller,
+ const std::string& name,
+ const std::string& host,
+ const std::string& port,
+ sys::ConnectionCodec::Factory* fact,
+ ConnectFailedCallback failed)
+{
+ SCHANNEL_CRED cred;
+ memset(&cred, 0, sizeof(cred));
+ cred.dwVersion = SCHANNEL_CRED_VERSION;
+ SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL,
+ UNISP_NAME,
+ SECPKG_CRED_OUTBOUND,
+ NULL,
+ &cred,
+ NULL,
+ NULL,
+ &credHandle,
+ NULL);
+ if (status != SEC_E_OK)
+ throw QPID_WINDOWS_ERROR(status);
+
+ brokerHost = host;
+ // Note that the following logic does not cause a memory leak.
+ // The allocated Socket is freed either by the AsynchConnector
+ // upon connection failure or by the AsynchIO upon connection
+ // shutdown. The allocated AsynchConnector frees itself when it
+ // is no longer needed.
+ qpid::sys::Socket* socket = createSocket();
+ connectFailedCallback = failed;
+ AsynchConnector::create(*socket,
+ host,
+ port,
+ boost::bind(&SslProtocolFactory::establishedOutgoing,
+ this, poller, _1, fact, name),
+ boost::bind(&SslProtocolFactory::connectFailed,
+ this, _1, _2, _3));
+}
+
+}}} // namespace qpid::sys::windows
diff --git a/cpp/src/qpid/client/ConnectionHandler.cpp b/cpp/src/qpid/client/ConnectionHandler.cpp index 91838d8e8b..4f88cb97ee 100644 --- a/cpp/src/qpid/client/ConnectionHandler.cpp +++ b/cpp/src/qpid/client/ConnectionHandler.cpp @@ -82,9 +82,11 @@ void ConnectionHandler::Adapter::handle(qpid::framing::AMQFrame& f) handler.out(f); } -ConnectionHandler::ConnectionHandler(const ConnectionSettings& s, ProtocolVersion& v, Bounds& b) - : StateManager(NOT_STARTED), ConnectionSettings(s), outHandler(*this, b), proxy(outHandler), - errorCode(CLOSE_CODE_NORMAL), version(v) +ConnectionHandler::ConnectionHandler( + const ConnectionSettings& s, ProtocolVersion& v, Bounds& b) + : StateManager(NOT_STARTED), ConnectionSettings(s), + outHandler(*this, b), proxy(outHandler), errorCode(CLOSE_CODE_NORMAL), version(v), + properties(s.clientProperties) { insist = true; diff --git a/cpp/src/qpid/client/ConnectionImpl.cpp b/cpp/src/qpid/client/ConnectionImpl.cpp index 85b0e8303e..0abfbe09ec 100644 --- a/cpp/src/qpid/client/ConnectionImpl.cpp +++ b/cpp/src/qpid/client/ConnectionImpl.cpp @@ -128,15 +128,17 @@ public: // and we can't do that before we're unloaded as we can't // restart the Poller after shutting it down ~IOThread() { - std::vector<Thread> threads; - { - ScopedLock<Mutex> l(threadLock); - if (poller_) - poller_->shutdown(); - t.swap(threads); - } - for (std::vector<Thread>::iterator i = threads.begin(); i != threads.end(); ++i) { - i->join(); + if (SystemInfo::threadSafeShutdown()) { + std::vector<Thread> threads; + { + ScopedLock<Mutex> l(threadLock); + if (poller_) + poller_->shutdown(); + t.swap(threads); + } + for (std::vector<Thread>::iterator i = threads.begin(); i != threads.end(); ++i) { + i->join(); + } } } }; diff --git a/cpp/src/qpid/client/FailoverManager.cpp b/cpp/src/qpid/client/FailoverManager.cpp index 9405765b47..f27aeb5b52 100644 --- a/cpp/src/qpid/client/FailoverManager.cpp +++ b/cpp/src/qpid/client/FailoverManager.cpp @@ -34,6 +34,9 @@ using qpid::sys::Duration; FailoverManager::FailoverManager(const ConnectionSettings& s, ReconnectionStrategy* rs) : settings(s), strategy(rs), state(IDLE) {} +FailoverManager::~FailoverManager() +{} + void FailoverManager::execute(Command& c) { bool retry = false; diff --git a/cpp/src/qpid/client/LoadPlugins.cpp b/cpp/src/qpid/client/LoadPlugins.cpp index d76e1d458e..c5d8924014 100644 --- a/cpp/src/qpid/client/LoadPlugins.cpp +++ b/cpp/src/qpid/client/LoadPlugins.cpp @@ -48,7 +48,7 @@ struct LoadtimeInitialise { for (vector<string>::iterator iter = moduleOptions.load.begin(); iter != moduleOptions.load.end(); iter++) - qpid::tryShlib (iter->data(), false); + qpid::tryShlib (*iter); if (!moduleOptions.noLoad) { bool isDefault = defaultPath == moduleOptions.loadDir; diff --git a/cpp/src/qpid/client/LoadPlugins.h b/cpp/src/qpid/client/LoadPlugins.h index 0be4ae9f0c..0b398f6831 100644 --- a/cpp/src/qpid/client/LoadPlugins.h +++ b/cpp/src/qpid/client/LoadPlugins.h @@ -22,10 +22,12 @@ #ifndef _LoadPlugins_ #define _LoadPlugins_ +#include "qpid/client/ClientImportExport.h" + namespace qpid { namespace client { -void theModuleLoader(); +QPID_CLIENT_EXTERN void theModuleLoader(); }} diff --git a/cpp/src/qpid/client/QueueOptions.cpp b/cpp/src/qpid/client/QueueOptions.cpp index f4c1483859..460f3f5490 100644 --- a/cpp/src/qpid/client/QueueOptions.cpp +++ b/cpp/src/qpid/client/QueueOptions.cpp @@ -49,8 +49,8 @@ QueueOptions::~QueueOptions() void QueueOptions::setSizePolicy(QueueSizePolicy sp, uint64_t maxSize, uint32_t maxCount) { - if (maxCount) setInt(strMaxCountKey, maxCount); - if (maxSize) setInt(strMaxSizeKey, maxSize); + if (maxCount) setUInt64(strMaxCountKey, maxCount); + if (maxSize) setUInt64(strMaxSizeKey, maxSize); if (maxSize || maxCount){ switch (sp) { diff --git a/cpp/src/qpid/client/RdmaConnector.cpp b/cpp/src/qpid/client/RdmaConnector.cpp index 21143a1a75..9b6dcd645d 100644 --- a/cpp/src/qpid/client/RdmaConnector.cpp +++ b/cpp/src/qpid/client/RdmaConnector.cpp @@ -109,7 +109,7 @@ class RdmaConnector : public Connector, public sys::Codec const qpid::sys::SecuritySettings* getSecuritySettings() { return 0; } size_t decode(const char* buffer, size_t size); - size_t encode(const char* buffer, size_t size); + size_t encode(char* buffer, size_t size); bool canEncode(); public: @@ -371,9 +371,9 @@ bool RdmaConnector::canEncode() return aio->writable() && (lastEof || currentSize >= maxFrameSize); } -size_t RdmaConnector::encode(const char* buffer, size_t size) +size_t RdmaConnector::encode(char* buffer, size_t size) { - framing::Buffer out(const_cast<char*>(buffer), size); + framing::Buffer out(buffer, size); size_t bytesWritten(0); { Mutex::ScopedLock l(lock); diff --git a/cpp/src/qpid/client/SessionImpl.cpp b/cpp/src/qpid/client/SessionImpl.cpp index 91e728d5ae..01e614e041 100644 --- a/cpp/src/qpid/client/SessionImpl.cpp +++ b/cpp/src/qpid/client/SessionImpl.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -61,9 +61,7 @@ SessionImpl::SessionImpl(const std::string& name, boost::shared_ptr<ConnectionIm ioHandler(*this), proxy(ioHandler), nextIn(0), - nextOut(0), - doClearDeliveryPropertiesExchange(true), - autoDetach(true) + nextOut(0) { channel.next = connection.get(); } @@ -72,12 +70,10 @@ SessionImpl::~SessionImpl() { { Lock l(state); if (state != DETACHED && state != DETACHING) { - if (autoDetach) { - QPID_LOG(warning, "Session was not closed cleanly: " << id); - // Inform broker but don't wait for detached as that deadlocks. - // The detached will be ignored as the channel will be invalid. - try { detach(); } catch (...) {} // ignore errors. - } + QPID_LOG(warning, "Session was not closed cleanly: " << id); + // Inform broker but don't wait for detached as that deadlocks. + // The detached will be ignored as the channel will be invalid. + try { detach(); } catch (...) {} // ignore errors. setState(DETACHED); handleClosed(); state.waitWaiters(); @@ -136,10 +132,10 @@ void SessionImpl::resume(boost::shared_ptr<ConnectionImpl>) // user thread void SessionImpl::suspend() //user thread { Lock l(state); - detach(); + detach(); } -void SessionImpl::detach() //call with lock held +void SessionImpl::detach() //call with lock held { if (state == ATTACHED) { setState(DETACHING); @@ -149,8 +145,8 @@ void SessionImpl::detach() //call with lock held uint16_t SessionImpl::getChannel() const // user thread -{ - return channel; +{ + return channel; } void SessionImpl::setChannel(uint16_t c) // user thread @@ -182,7 +178,7 @@ void SessionImpl::waitForCompletionImpl(const SequenceNumber& id) //call with lo bool SessionImpl::isComplete(const SequenceNumber& id) { - Lock l(state); + Lock l(state); return !incompleteOut.contains(id); } @@ -219,7 +215,7 @@ framing::SequenceNumber SessionImpl::getCompleteUpTo() return --firstIncomplete; } -struct MarkCompleted +struct MarkCompleted { const SequenceNumber& id; SequenceSet& completedIn; @@ -230,7 +226,7 @@ struct MarkCompleted { if (id >= end) { completedIn.add(start, end); - } else if (id >= start) { + } else if (id >= start) { completedIn.add(start, id); } } @@ -244,13 +240,13 @@ void SessionImpl::markCompleted(const SequenceSet& ids, bool notifyPeer) completedIn.add(ids); if (notifyPeer) { sendCompletion(); - } + } } void SessionImpl::markCompleted(const SequenceNumber& id, bool cumulative, bool notifyPeer) { Lock l(state); - if (cumulative) { + if (cumulative) { //everything in incompleteIn less than or equal to id is now complete MarkCompleted f(id, completedIn); incompleteIn.for_each(f); @@ -260,11 +256,11 @@ void SessionImpl::markCompleted(const SequenceNumber& id, bool cumulative, bool incompleteIn.remove(completedIn); } else if (incompleteIn.contains(id)) { incompleteIn.remove(id); - completedIn.add(id); + completedIn.add(id); } if (notifyPeer) { sendCompletion(); - } + } } void SessionImpl::setException(const sys::ExceptionHolder& ex) { @@ -310,42 +306,24 @@ namespace { struct SendContentFn { FrameHandler& handler; void operator()(const AMQFrame& f) { - if (!f.getMethod()) + if (!f.getMethod()) handler(const_cast<AMQFrame&>(f)); } SendContentFn(FrameHandler& h) : handler(h) {} }; -// Adaptor to make FrameSet look like MethodContent; used in cluster update client -struct MethodContentAdaptor : MethodContent -{ - AMQHeaderBody header; - const std::string content; - - MethodContentAdaptor(const FrameSet& f) : header(*f.getHeaders()), content(f.getContent()) {} - - const AMQHeaderBody& getHeader() const - { - return header; - } - const std::string& getData() const - { - return content; - } -}; - } - -Future SessionImpl::send(const AMQBody& command, const FrameSet& content, bool reframe) { + +Future SessionImpl::send(const AMQBody& command, const FrameSet& content) { Acquire a(sendLock); SequenceNumber id = nextOut++; { Lock l(state); - checkOpen(); + checkOpen(); incompleteOut.add(id); } Future f(id); - if (command.getMethod()->resultExpected()) { + if (command.getMethod()->resultExpected()) { Lock l(state); //result listener must be set before the command is sent f.setFutureResult(results.listenForResult(id)); @@ -353,14 +331,8 @@ Future SessionImpl::send(const AMQBody& command, const FrameSet& content, bool r AMQFrame frame(command); frame.setEof(false); handleOut(frame); - - if (reframe) { - MethodContentAdaptor c(content); - sendContent(c); - } else { - SendContentFn send(out); - content.map(send); - } + SendContentFn send(out); + content.map(send); return f; } @@ -375,11 +347,11 @@ Future SessionImpl::sendCommand(const AMQBody& command, const MethodContent* con SequenceNumber id = nextOut++; { Lock l(state); - checkOpen(); + checkOpen(); incompleteOut.add(id); } Future f(id); - if (command.getMethod()->resultExpected()) { + if (command.getMethod()->resultExpected()) { Lock l(state); //result listener must be set before the command is sent f.setFutureResult(results.listenForResult(id)); @@ -399,23 +371,13 @@ void SessionImpl::sendContent(const MethodContent& content) { AMQFrame header(content.getHeader()); - // doClearDeliveryPropertiesExchange is set by cluster update client so - // it can send messages with delivery-properties.exchange set. - // - if (doClearDeliveryPropertiesExchange) { - // Normal client is not allowed to set the delivery-properties.exchange - // so clear it here. - AMQHeaderBody* headerp = static_cast<AMQHeaderBody*>(header.getBody()); - if (headerp && headerp->get<DeliveryProperties>()) - headerp->get<DeliveryProperties>(true)->clearExchangeFlag(); - } header.setFirstSegment(false); uint64_t data_length = content.getData().length(); if(data_length > 0){ header.setLastSegment(false); - handleOut(header); + handleOut(header); /*Note: end of frame marker included in overhead but not in size*/ - const uint32_t frag_size = maxFrameSize - AMQFrame::frameOverhead(); + const uint32_t frag_size = maxFrameSize - AMQFrame::frameOverhead(); if(data_length < frag_size){ AMQFrame frame((AMQContentBody(content.getData()))); @@ -442,7 +404,7 @@ void SessionImpl::sendContent(const MethodContent& content) } } } else { - handleOut(header); + handleOut(header); } } @@ -462,7 +424,7 @@ bool isContentFrame(AMQFrame& frame) { AMQBody* body = frame.getBody(); uint8_t type = body->type(); - return type == HEADER_BODY || type == CONTENT_BODY || isMessageMethod(body); + return type == HEADER_BODY || type == CONTENT_BODY || isMessageMethod(body); } void SessionImpl::handleIn(AMQFrame& frame) // network thread @@ -585,7 +547,7 @@ void SessionImpl::timeout(uint32_t t) void SessionImpl::commandPoint(const framing::SequenceNumber& id, uint64_t offset) { if (offset) throw NotImplementedException("Non-zero byte offset not yet supported for command-point"); - + Lock l(state); nextIn = id; } @@ -677,10 +639,10 @@ void SessionImpl::exception(uint16_t errorCode, { Lock l(state); setExceptionLH(createSessionException(errorCode, description)); - QPID_LOG(warning, "Exception received from broker: " << exceptionHolder.what() + QPID_LOG(warning, "Exception received from broker: " << exceptionHolder.what() << " [caused by " << commandId << " " << classCode << ":" << commandCode << "]"); - if (detachedLifetime) + if (detachedLifetime) setTimeout(0); } @@ -748,6 +710,4 @@ boost::shared_ptr<ConnectionImpl> SessionImpl::getConnection() return connection; } -void SessionImpl::disableAutoDetach() { autoDetach = false; } - }} diff --git a/cpp/src/qpid/client/SessionImpl.h b/cpp/src/qpid/client/SessionImpl.h index 4f9213a00a..e6ea8e6b90 100644 --- a/cpp/src/qpid/client/SessionImpl.h +++ b/cpp/src/qpid/client/SessionImpl.h @@ -87,15 +87,7 @@ public: Future send(const framing::AMQBody& command); Future send(const framing::AMQBody& command, const framing::MethodContent& content); - /** - * This method takes the content as a FrameSet; if reframe=false, - * the caller is resposnible for ensuring that the header and - * content frames in that set are correct for this connection - * (right flags, right fragmentation etc). If reframe=true, then - * the header and content from the frameset will be copied and - * reframed correctly for the connection. - */ - QPID_CLIENT_EXTERN Future send(const framing::AMQBody& command, const framing::FrameSet& content, bool reframe=false); + QPID_CLIENT_EXTERN Future send(const framing::AMQBody& command, const framing::FrameSet& content); void sendRawFrame(framing::AMQFrame& frame); Demux& getDemux(); @@ -125,11 +117,6 @@ public: */ boost::shared_ptr<ConnectionImpl> getConnection(); - void setDoClearDeliveryPropertiesExchange(bool b=true) { doClearDeliveryPropertiesExchange = b; } - - /** Suppress sending detach in destructor. Used by cluster to build session state */ - void disableAutoDetach(); - private: enum State { INACTIVE, @@ -225,10 +212,6 @@ private: SessionState sessionState; - bool doClearDeliveryPropertiesExchange; - - bool autoDetach; - friend class client::SessionHandler; }; diff --git a/cpp/src/qpid/client/SslConnector.cpp b/cpp/src/qpid/client/SslConnector.cpp index c2081a88f2..11707eb3f7 100644 --- a/cpp/src/qpid/client/SslConnector.cpp +++ b/cpp/src/qpid/client/SslConnector.cpp @@ -30,8 +30,9 @@ #include "qpid/framing/AMQFrame.h" #include "qpid/framing/InitiationHandler.h" #include "qpid/sys/ssl/util.h" -#include "qpid/sys/ssl/SslIo.h" +#include "qpid/sys/AsynchIO.h" #include "qpid/sys/ssl/SslSocket.h" +#include "qpid/sys/SocketAddress.h" #include "qpid/sys/Dispatcher.h" #include "qpid/sys/Poller.h" #include "qpid/sys/SecuritySettings.h" @@ -72,23 +73,28 @@ class SslConnector : public Connector sys::ssl::SslSocket socket; - sys::ssl::SslIO* aio; + sys::AsynchConnector* connector; + sys::AsynchIO* aio; std::string identifier; Poller::shared_ptr poller; SecuritySettings securitySettings; ~SslConnector(); - void readbuff(qpid::sys::ssl::SslIO&, qpid::sys::ssl::SslIOBufferBase*); - void writebuff(qpid::sys::ssl::SslIO&); + void readbuff(AsynchIO&, AsynchIOBufferBase*); + void writebuff(AsynchIO&); void writeDataBlock(const framing::AMQDataBlock& data); - void eof(qpid::sys::ssl::SslIO&); - void disconnected(qpid::sys::ssl::SslIO&); + void eof(AsynchIO&); + void disconnected(AsynchIO&); void connect(const std::string& host, const std::string& port); + void connected(const sys::Socket&); + void connectFailed(const std::string& msg); + void close(); void send(framing::AMQFrame& frame); - void abort() {} // TODO: Need to fix for heartbeat timeouts to work + void abort(); + void connectAborted(); void setInputHandler(framing::InputHandler* handler); void setShutdownHandler(sys::ShutdownHandler* handler); @@ -96,10 +102,10 @@ class SslConnector : public Connector framing::OutputHandler* getOutputHandler(); const std::string& getIdentifier() const; const SecuritySettings* getSecuritySettings(); - void socketClosed(qpid::sys::ssl::SslIO&, const qpid::sys::ssl::SslSocket&); + void socketClosed(AsynchIO&, const Socket&); size_t decode(const char* buffer, size_t size); - size_t encode(const char* buffer, size_t size); + size_t encode(char* buffer, size_t size); bool canEncode(); public: @@ -164,32 +170,46 @@ SslConnector::~SslConnector() { close(); } -void SslConnector::connect(const std::string& host, const std::string& port){ +void SslConnector::connect(const std::string& host, const std::string& port) { Mutex::ScopedLock l(lock); assert(closed); - try { - socket.connect(host, port); - } catch (const std::exception& e) { - socket.close(); - throw TransportFailure(e.what()); - } - + connector = AsynchConnector::create( + socket, + host, port, + boost::bind(&SslConnector::connected, this, _1), + boost::bind(&SslConnector::connectFailed, this, _3)); closed = false; - aio = new SslIO(socket, - boost::bind(&SslConnector::readbuff, this, _1, _2), - boost::bind(&SslConnector::eof, this, _1), - boost::bind(&SslConnector::disconnected, this, _1), - boost::bind(&SslConnector::socketClosed, this, _1, _2), - 0, // nobuffs - boost::bind(&SslConnector::writebuff, this, _1)); + + connector->start(poller); +} + +void SslConnector::connected(const Socket&) { + connector = 0; + aio = AsynchIO::create(socket, + boost::bind(&SslConnector::readbuff, this, _1, _2), + boost::bind(&SslConnector::eof, this, _1), + boost::bind(&SslConnector::disconnected, this, _1), + boost::bind(&SslConnector::socketClosed, this, _1, _2), + 0, // nobuffs + boost::bind(&SslConnector::writebuff, this, _1)); aio->createBuffers(maxFrameSize); - identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); + identifier = str(format("[%1%]") % socket.getFullAddress()); ProtocolInitiation init(version); writeDataBlock(init); aio->start(poller); } +void SslConnector::connectFailed(const std::string& msg) { + connector = 0; + QPID_LOG(warning, "Connect failed: " << msg); + socket.close(); + if (!closed) + closed = true; + if (shutdownHandler) + shutdownHandler->shutdown(); +} + void SslConnector::close() { Mutex::ScopedLock l(lock); if (!closed) { @@ -199,13 +219,31 @@ void SslConnector::close() { } } -void SslConnector::socketClosed(SslIO&, const SslSocket&) { +void SslConnector::socketClosed(AsynchIO&, const Socket&) { if (aio) aio->queueForDeletion(); if (shutdownHandler) shutdownHandler->shutdown(); } +void SslConnector::connectAborted() { + connector->stop(); + connectFailed("Connection timedout"); +} + +void SslConnector::abort() { + // Can't abort a closed connection + if (!closed) { + if (aio) { + // Established connection + aio->requestCallback(boost::bind(&SslConnector::eof, this, _1)); + } else if (connector) { + // We're still connecting + connector->requestCallback(boost::bind(&SslConnector::connectAborted, this)); + } + } +} + void SslConnector::setInputHandler(InputHandler* handler){ input = handler; } @@ -255,7 +293,7 @@ void SslConnector::send(AMQFrame& frame) { } } -void SslConnector::writebuff(SslIO& /*aio*/) +void SslConnector::writebuff(AsynchIO& /*aio*/) { // It's possible to be disconnected and be writable if (closed) @@ -265,7 +303,7 @@ void SslConnector::writebuff(SslIO& /*aio*/) return; } - SslIO::BufferBase* buffer = aio->getQueuedBuffer(); + AsynchIOBufferBase* buffer = aio->getQueuedBuffer(); if (buffer) { size_t encoded = encode(buffer->bytes, buffer->byteCount); @@ -285,9 +323,9 @@ bool SslConnector::canEncode() } // Called in IO thread. -size_t SslConnector::encode(const char* buffer, size_t size) +size_t SslConnector::encode(char* buffer, size_t size) { - framing::Buffer out(const_cast<char*>(buffer), size); + framing::Buffer out(buffer, size); size_t bytesWritten(0); { Mutex::ScopedLock l(lock); @@ -304,7 +342,7 @@ size_t SslConnector::encode(const char* buffer, size_t size) return bytesWritten; } -void SslConnector::readbuff(SslIO& aio, SslIO::BufferBase* buff) +void SslConnector::readbuff(AsynchIO& aio, AsynchIOBufferBase* buff) { int32_t decoded = decode(buff->bytes+buff->dataStart, buff->dataCount); // TODO: unreading needs to go away, and when we can cope @@ -343,7 +381,7 @@ size_t SslConnector::decode(const char* buffer, size_t size) } void SslConnector::writeDataBlock(const AMQDataBlock& data) { - SslIO::BufferBase* buff = aio->getQueuedBuffer(); + AsynchIOBufferBase* buff = aio->getQueuedBuffer(); assert(buff); framing::Buffer out(buff->bytes, buff->byteCount); data.encode(out); @@ -351,11 +389,11 @@ void SslConnector::writeDataBlock(const AMQDataBlock& data) { aio->queueWrite(buff); } -void SslConnector::eof(SslIO&) { +void SslConnector::eof(AsynchIO&) { close(); } -void SslConnector::disconnected(SslIO&) { +void SslConnector::disconnected(AsynchIO&) { close(); socketClosed(*aio, socket); } diff --git a/cpp/src/qpid/client/TCPConnector.cpp b/cpp/src/qpid/client/TCPConnector.cpp index a5c6465bad..783742764b 100644 --- a/cpp/src/qpid/client/TCPConnector.cpp +++ b/cpp/src/qpid/client/TCPConnector.cpp @@ -72,12 +72,13 @@ TCPConnector::TCPConnector(Poller::shared_ptr p, closed(true), shutdownHandler(0), input(0), + socket(createSocket()), connector(0), aio(0), poller(p) { QPID_LOG(debug, "TCPConnector created for " << version); - settings.configureSocket(socket); + settings.configureSocket(*socket); } TCPConnector::~TCPConnector() { @@ -88,7 +89,7 @@ void TCPConnector::connect(const std::string& host, const std::string& port) { Mutex::ScopedLock l(lock); assert(closed); connector = AsynchConnector::create( - socket, + *socket, host, port, boost::bind(&TCPConnector::connected, this, _1), boost::bind(&TCPConnector::connectFailed, this, _3)); @@ -99,7 +100,7 @@ void TCPConnector::connect(const std::string& host, const std::string& port) { void TCPConnector::connected(const Socket&) { connector = 0; - aio = AsynchIO::create(socket, + aio = AsynchIO::create(*socket, boost::bind(&TCPConnector::readbuff, this, _1, _2), boost::bind(&TCPConnector::eof, this, _1), boost::bind(&TCPConnector::disconnected, this, _1), @@ -116,7 +117,7 @@ void TCPConnector::start(sys::AsynchIO* aio_) { aio->createBuffers(maxFrameSize); - identifier = str(format("[%1%]") % socket.getFullAddress()); + identifier = str(format("[%1%]") % socket->getFullAddress()); } void TCPConnector::initAmqp() { @@ -127,7 +128,7 @@ void TCPConnector::initAmqp() { void TCPConnector::connectFailed(const std::string& msg) { connector = 0; QPID_LOG(warning, "Connect failed: " << msg); - socket.close(); + socket->close(); if (!closed) closed = true; if (shutdownHandler) @@ -150,6 +151,11 @@ void TCPConnector::socketClosed(AsynchIO&, const Socket&) { shutdownHandler->shutdown(); } +void TCPConnector::connectAborted() { + connector->stop(); + connectFailed("Connection timedout"); +} + void TCPConnector::abort() { // Can't abort a closed connection if (!closed) { @@ -158,8 +164,7 @@ void TCPConnector::abort() { aio->requestCallback(boost::bind(&TCPConnector::eof, this, _1)); } else if (connector) { // We're still connecting - connector->stop(); - connectFailed("Connection timedout"); + connector->requestCallback(boost::bind(&TCPConnector::connectAborted, this)); } } } @@ -245,9 +250,9 @@ bool TCPConnector::canEncode() } // Called in IO thread. -size_t TCPConnector::encode(const char* buffer, size_t size) +size_t TCPConnector::encode(char* buffer, size_t size) { - framing::Buffer out(const_cast<char*>(buffer), size); + framing::Buffer out(buffer, size); size_t bytesWritten(0); { Mutex::ScopedLock l(lock); @@ -318,7 +323,7 @@ void TCPConnector::eof(AsynchIO&) { void TCPConnector::disconnected(AsynchIO&) { close(); - socketClosed(*aio, socket); + socketClosed(*aio, *socket); } void TCPConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl) diff --git a/cpp/src/qpid/client/TCPConnector.h b/cpp/src/qpid/client/TCPConnector.h index c0bc26028d..63af3b878a 100644 --- a/cpp/src/qpid/client/TCPConnector.h +++ b/cpp/src/qpid/client/TCPConnector.h @@ -35,7 +35,7 @@ #include "qpid/sys/Thread.h" #include <boost/shared_ptr.hpp> -#include <boost/weak_ptr.hpp> +#include <boost/scoped_ptr.hpp> #include <deque> #include <string> @@ -66,7 +66,7 @@ class TCPConnector : public Connector, public sys::Codec sys::ShutdownHandler* shutdownHandler; framing::InputHandler* input; - sys::Socket socket; + boost::scoped_ptr<sys::Socket> socket; sys::AsynchConnector* connector; sys::AsynchIO* aio; @@ -80,6 +80,7 @@ class TCPConnector : public Connector, public sys::Codec void close(); void send(framing::AMQFrame& frame); void abort(); + void connectAborted(); void setInputHandler(framing::InputHandler* handler); void setShutdownHandler(sys::ShutdownHandler* handler); @@ -90,7 +91,7 @@ class TCPConnector : public Connector, public sys::Codec const qpid::sys::SecuritySettings* getSecuritySettings() { return 0; } size_t decode(const char* buffer, size_t size); - size_t encode(const char* buffer, size_t size); + size_t encode(char* buffer, size_t size); bool canEncode(); protected: diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp index aaebec0720..f43119ea4c 100644 --- a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp @@ -26,6 +26,7 @@ #include "qpid/framing/Uuid.h" #include "qpid/log/Statement.h" #include "qpid/Url.h" +#include "qpid/amqp_0_10/Codecs.h" #include <boost/intrusive_ptr.hpp> #include <vector> #include <sstream> @@ -156,6 +157,8 @@ void ConnectionImpl::setOption(const std::string& name, const Variant& value) settings.sslCertName = value.asString(); } else if (name == "x-reconnect-on-limit-exceeded" || name == "x_reconnect_on_limit_exceeded") { reconnectOnLimitExceeded = value; + } else if (name == "client-properties") { + amqp_0_10::translate(value.asMap(), settings.clientProperties); } else { throw qpid::messaging::MessagingException(QPID_MSG("Invalid option: " << name << " not recognised")); } diff --git a/cpp/src/qpid/client/windows/ClientDllMain.cpp b/cpp/src/qpid/client/windows/ClientDllMain.cpp new file mode 100644 index 0000000000..d636489908 --- /dev/null +++ b/cpp/src/qpid/client/windows/ClientDllMain.cpp @@ -0,0 +1,22 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/sys/windows/QpidDllMain.h" diff --git a/cpp/src/qpid/client/windows/SslConnector.cpp b/cpp/src/qpid/client/windows/SslConnector.cpp index 2aa31e8202..e1f34e7aea 100644 --- a/cpp/src/qpid/client/windows/SslConnector.cpp +++ b/cpp/src/qpid/client/windows/SslConnector.cpp @@ -71,6 +71,8 @@ class SslConnector : public qpid::client::TCPConnector void redirectReadbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); void redirectWritebuff(qpid::sys::AsynchIO&); void redirectEof(qpid::sys::AsynchIO&); + void redirectDisconnect(qpid::sys::AsynchIO&); + void redirectSocketClosed(qpid::sys::AsynchIO&, const qpid::sys::Socket&); public: SslConnector(boost::shared_ptr<qpid::sys::Poller>, @@ -79,7 +81,6 @@ public: ConnectionImpl*); virtual void connect(const std::string& host, const std::string& port); virtual void connected(const Socket&); - unsigned int getSSF(); }; // Static constructor which registers connector here @@ -124,6 +125,14 @@ void SslConnector::redirectEof(qpid::sys::AsynchIO& a) { eof(a); } +void SslConnector::redirectDisconnect(qpid::sys::AsynchIO& a) { + disconnected(a); +} + +void SslConnector::redirectSocketClosed(qpid::sys::AsynchIO& a, const qpid::sys::Socket& s) { + socketClosed(a, s); +} + SslConnector::SslConnector(boost::shared_ptr<qpid::sys::Poller> p, framing::ProtocolVersion ver, const ConnectionSettings& settings, @@ -164,8 +173,8 @@ void SslConnector::connected(const Socket& s) { credHandle, boost::bind(&SslConnector::redirectReadbuff, this, _1, _2), boost::bind(&SslConnector::redirectEof, this, _1), - boost::bind(&SslConnector::redirectEof, this, _1), - 0, // closed + boost::bind(&SslConnector::redirectDisconnect, this, _1), + boost::bind(&SslConnector::redirectSocketClosed, this, _1, _2), 0, // nobuffs boost::bind(&SslConnector::redirectWritebuff, this, _1), boost::bind(&SslConnector::negotiationDone, this, _1)); @@ -173,9 +182,4 @@ void SslConnector::connected(const Socket& s) { shim->start(poller); } -unsigned int SslConnector::getSSF() -{ - return shim->getSslKeySize(); -} - }}} // namespace qpid::client::windows diff --git a/cpp/src/qpid/framing/FieldValue.cpp b/cpp/src/qpid/framing/FieldValue.cpp index ce5a50117c..4abed0f77f 100644 --- a/cpp/src/qpid/framing/FieldValue.cpp +++ b/cpp/src/qpid/framing/FieldValue.cpp @@ -23,6 +23,7 @@ #include "qpid/framing/Buffer.h" #include "qpid/framing/Endian.h" #include "qpid/framing/List.h" +#include "qpid/framing/Uuid.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/Msg.h" @@ -43,7 +44,9 @@ void FieldValue::setType(uint8_t type) data.reset(new EncodedValue<List>()); } else if (typeOctet == 0xAA) { data.reset(new EncodedValue<Array>()); - } else { + } else if (typeOctet == 0x48) { + data.reset(new UuidData()); + } else { uint8_t lenType = typeOctet >> 4; switch(lenType){ case 0: @@ -213,9 +216,12 @@ Integer8Value::Integer8Value(int8_t v) : Integer16Value::Integer16Value(int16_t v) : FieldValue(0x11, new FixedWidthValue<2>(v)) {} -UuidValue::UuidValue(const unsigned char* v) : - FieldValue(0x48, new FixedWidthValue<16>(v)) -{} + +UuidData::UuidData() {} +UuidData::UuidData(const unsigned char* bytes) : FixedWidthValue<16>(bytes) {} +bool UuidData::convertsToString() const { return true; } +std::string UuidData::getString() const { return Uuid(rawOctets()).str(); } +UuidValue::UuidValue(const unsigned char* v) : FieldValue(0x48, new UuidData(v)) {} void FieldValue::print(std::ostream& out) const { data->print(out); diff --git a/cpp/src/qpid/framing/FrameSet.h b/cpp/src/qpid/framing/FrameSet.h index 3b9f60950b..9640abb7ac 100644 --- a/cpp/src/qpid/framing/FrameSet.h +++ b/cpp/src/qpid/framing/FrameSet.h @@ -1,3 +1,5 @@ +#ifndef QPID_FRAMING_FRAMESET_H +#define QPID_FRAMING_FRAMESET_H /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -18,6 +20,7 @@ * under the License. * */ + #include <string> #include "qpid/InlineVector.h" #include "qpid/framing/amqp_framing.h" @@ -25,9 +28,6 @@ #include "qpid/framing/SequenceNumber.h" #include "qpid/CommonImportExport.h" -#ifndef _FrameSet_ -#define _FrameSet_ - namespace qpid { namespace framing { @@ -117,4 +117,4 @@ public: } -#endif +#endif /*!QPID_FRAMING_FRAMESET_H*/ diff --git a/cpp/src/qpid/framing/Handler.h b/cpp/src/qpid/framing/Handler.h index fa8db36f49..e0c0e59d09 100644 --- a/cpp/src/qpid/framing/Handler.h +++ b/cpp/src/qpid/framing/Handler.h @@ -49,29 +49,12 @@ struct Handler { * Functor<F>(f) will copy f. * Functor<F&>(f) will only take a reference to x. */ - template <class F> class Functor : public Handler<T> { - public: - Functor(F f, Handler<T>* next=0) : Handler<T>(next), functor(f) {} - void handle(T t) { functor(t); } - private: - F functor; - }; + template <class F> class Functor; /** Adapt a member function of X as a Handler. * Only holds a reference to its target, not a copy. */ - template <class X, void (X::*F)(T)> - class MemFunRef : public Handler<T> { - public: - MemFunRef(X& x, Handler<T>* next=0) : Handler(next), target(&x) {} - void handle(T t) { (target->*F)(t); } - - /** Allow calling with -> syntax */ - MemFunRef* operator->() { return this; } - - private: - X* target; - }; + template <class X, void (X::*F)(T)> class MemFunRef; /** Interface for a handler that implements a * pair of in/out handle operations. @@ -94,7 +77,29 @@ struct Handler { }; }; +template <class T> +template <class F> +class Handler<T>::Functor : public Handler<T> { + public: + Functor(F f, Handler<T>* next=0) : Handler<T>(next), functor(f) {} + void handle(T t) { functor(t); } + private: + F functor; +}; + +template <class T> +template <class X, void (X::*F)(T)> +class Handler<T>::MemFunRef : public Handler<T> { + public: + MemFunRef(X& x, Handler<T>* next=0) : Handler(next), target(&x) {} + void handle(T t) { (target->*F)(t); } + + /** Allow calling with -> syntax */ + MemFunRef* operator->() { return this; } + private: + X* target; +}; }} #endif /*!QPID_FRAMING_HANDLER_H*/ diff --git a/cpp/src/qpid/framing/ProtocolInitiation.cpp b/cpp/src/qpid/framing/ProtocolInitiation.cpp index 00ddb55a3b..19cb3f0e3d 100644 --- a/cpp/src/qpid/framing/ProtocolInitiation.cpp +++ b/cpp/src/qpid/framing/ProtocolInitiation.cpp @@ -38,10 +38,17 @@ void ProtocolInitiation::encode(Buffer& buffer) const { buffer.putOctet('M'); buffer.putOctet('Q'); buffer.putOctet('P'); - buffer.putOctet(1);//class - buffer.putOctet(1);//instance - buffer.putOctet(version.getMajor()); - buffer.putOctet(version.getMinor()); + if (version.getMajor() == 1) { + buffer.putOctet(version.getProtocol()); + buffer.putOctet(version.getMajor()); + buffer.putOctet(version.getMinor()); + buffer.putOctet(0);//revision + } else { + buffer.putOctet(1);//class + buffer.putOctet(1);//instance + buffer.putOctet(version.getMajor()); + buffer.putOctet(version.getMinor()); + } } bool ProtocolInitiation::decode(Buffer& buffer){ @@ -50,10 +57,18 @@ bool ProtocolInitiation::decode(Buffer& buffer){ buffer.getOctet();//M buffer.getOctet();//Q buffer.getOctet();//P - buffer.getOctet();//class - buffer.getOctet();//instance - version.setMajor(buffer.getOctet()); - version.setMinor(buffer.getOctet()); + uint8_t protocolClass = buffer.getOctet();//class + version.setProtocol(protocolClass); + if (protocolClass == 1) { + //old (pre-1.0) style + buffer.getOctet();//instance + version.setMajor(buffer.getOctet()); + version.setMinor(buffer.getOctet()); + } else { + version.setMajor(buffer.getOctet()); + version.setMinor(buffer.getOctet()); + buffer.getOctet();//revision + } return true; }else{ return false; diff --git a/cpp/src/qpid/framing/ProtocolVersion.cpp b/cpp/src/qpid/framing/ProtocolVersion.cpp index c63cddb4cc..174fc3e65b 100644 --- a/cpp/src/qpid/framing/ProtocolVersion.cpp +++ b/cpp/src/qpid/framing/ProtocolVersion.cpp @@ -27,6 +27,10 @@ const std::string ProtocolVersion::toString() const { std::stringstream ss; ss << major_ << "-" << minor_; + if (major_ == 1) { + if (protocol_ == SASL) ss << " (SASL)"; + else if (protocol_ == TLS) ss << " (TLS)"; + } return ss.str(); } @@ -42,3 +46,7 @@ bool ProtocolVersion::operator==(ProtocolVersion p) const return major_ == p.major_ && minor_ == p.minor_; } +uint8_t ProtocolVersion::AMQP(0); +uint8_t ProtocolVersion::LEGACY_AMQP(1); +uint8_t ProtocolVersion::TLS(2); +uint8_t ProtocolVersion::SASL(3); diff --git a/cpp/src/qpid/ha/AlternateExchangeSetter.h b/cpp/src/qpid/ha/AlternateExchangeSetter.h index 08690e68bc..2386a01084 100644 --- a/cpp/src/qpid/ha/AlternateExchangeSetter.h +++ b/cpp/src/qpid/ha/AlternateExchangeSetter.h @@ -43,12 +43,14 @@ class AlternateExchangeSetter AlternateExchangeSetter(broker::ExchangeRegistry& er) : exchanges(er) {} + /** If altEx is already known, call setter(altEx) now else save for later */ void setAlternate(const std::string& altEx, const SetFunction& setter) { - broker::Exchange::shared_ptr ex = exchanges.find(altEx); + boost::shared_ptr<broker::Exchange> ex = exchanges.find(altEx); if (ex) setter(ex); // Set immediately. else setters.insert(Setters::value_type(altEx, setter)); // Save for later. } + /** Add an exchange and call any setters that are waiting for it. */ void addExchange(boost::shared_ptr<broker::Exchange> exchange) { // Update the setters for this exchange std::pair<Setters::iterator, Setters::iterator> range = setters.equal_range(exchange->getName()); diff --git a/cpp/src/qpid/ha/Backup.cpp b/cpp/src/qpid/ha/Backup.cpp index 6852a58b0c..2affc12bf6 100644 --- a/cpp/src/qpid/ha/Backup.cpp +++ b/cpp/src/qpid/ha/Backup.cpp @@ -20,9 +20,12 @@ */ #include "Backup.h" #include "BrokerReplicator.h" +#include "ConnectionObserver.h" #include "HaBroker.h" +#include "Primary.h" #include "ReplicatingSubscription.h" #include "Settings.h" +#include "StatusCheck.h" #include "qpid/Url.h" #include "qpid/amqp_0_10/Codecs.h" #include "qpid/broker/Bridge.h" @@ -44,28 +47,38 @@ using namespace framing; using namespace broker; using types::Variant; using std::string; +using sys::Mutex; Backup::Backup(HaBroker& hb, const Settings& s) : - logPrefix("Backup: "), haBroker(hb), broker(hb.getBroker()), settings(s) + logPrefix("Backup: "), membership(hb.getMembership()), stopped(false), + haBroker(hb), broker(hb.getBroker()), settings(s), + statusCheck( + new StatusCheck( + logPrefix, broker.getLinkHearbeatInterval(), hb.getBrokerInfo())) { - // Empty brokerUrl means delay initialization until seBrokertUrl() is called. - if (!s.brokerUrl.empty()) initialize(Url(s.brokerUrl)); + // Set link properties to tag outgoing links. + framing::FieldTable linkProperties = broker.getLinkClientProperties(); + linkProperties.setTable( + ConnectionObserver::BACKUP_TAG, hb.getBrokerInfo().asFieldTable()); + broker.setLinkClientProperties(linkProperties); } -void Backup::initialize(const Url& brokers) { - if (brokers.empty()) throw Url::Invalid("HA broker URL is empty"); - QPID_LOG(info, logPrefix << "Connecting to cluster, broker URL: " << brokers); - string protocol = brokers[0].protocol.empty() ? "tcp" : brokers[0].protocol; - types::Uuid uuid(true); - // Declare the link - std::pair<Link::shared_ptr, bool> result = broker.getLinks().declare( - broker::QPID_NAME_PREFIX + string("ha.link.") + uuid.str(), - brokers[0].host, brokers[0].port, protocol, - false, // durable - settings.mechanism, settings.username, settings.password, - false); // no amq.failover - don't want to use client URL. - { - sys::Mutex::ScopedLock l(lock); +void Backup::setBrokerUrl(const Url& brokers) { + if (brokers.empty()) return; + Mutex::ScopedLock l(lock); + if (stopped) return; + if (haBroker.getStatus() == JOINING) statusCheck->setUrl(brokers); + if (!link) { // Not yet initialized + QPID_LOG(info, logPrefix << "Connecting to cluster, broker URL: " << brokers); + string protocol = brokers[0].protocol.empty() ? "tcp" : brokers[0].protocol; + types::Uuid uuid(true); + std::pair<Link::shared_ptr, bool> result; + result = broker.getLinks().declare( + broker::QPID_NAME_PREFIX + string("ha.link.") + uuid.str(), + brokers[0].host, brokers[0].port, protocol, + false, // durable + settings.mechanism, settings.username, settings.password, + false); // no amq.failover - don't want to use client URL. link = result.first; replicator.reset(new BrokerReplicator(haBroker, link)); replicator->initialize(); @@ -74,36 +87,57 @@ void Backup::initialize(const Url& brokers) { link->setUrl(brokers); // Outside the lock, once set link doesn't change. } -Backup::~Backup() { +void Backup::stop(Mutex::ScopedLock&) { + if (stopped) return; + stopped = true; + QPID_LOG(debug, logPrefix << "Leaving backup role."); if (link) link->close(); - if (replicator.get()) broker.getExchanges().destroy(replicator->getName()); + if (replicator.get()) { + replicator->shutdown(); + replicator.reset(); + } } -// Called via management. -void Backup::setBrokerUrl(const Url& url) { - // Ignore empty URLs seen during start-up for some tests. - if (url.empty()) return; - bool linkSet = false; +Role* Backup::recover(Mutex::ScopedLock&) { + BrokerInfo::Set backups; { - sys::Mutex::ScopedLock l(lock); - linkSet = link; + Mutex::ScopedLock l(lock); + if (stopped) return 0; + stop(l); // Stop backup activity before starting primary. + QPID_LOG(notice, "Promoting to primary: " << haBroker.getBrokerInfo()); + // Reset membership before allowing backups to connect. + backups = membership.otherBackups(); + membership.clear(); + return new Primary(haBroker, backups); } - if (linkSet) - link->setUrl(url); // Outside lock, once set link doesn't change - else - initialize(url); // Deferred initialization } -void Backup::setStatus(BrokerStatus status) { - switch (status) { - case READY: - QPID_LOG(notice, logPrefix << "Ready to become primary."); +Role* Backup::promote() { + Mutex::ScopedLock l(lock); + if (stopped) return 0; + switch (haBroker.getStatus()) { + case JOINING: + if (statusCheck->canPromote()) return recover(l); + else { + QPID_LOG(error, + logPrefix << "Joining active cluster, cannot be promoted."); + throw Exception("Joining active cluster, cannot be promoted."); + } break; case CATCHUP: - QPID_LOG(notice, logPrefix << "Catching up on primary, cannot be promoted."); + QPID_LOG(error, logPrefix << "Still catching up, cannot be promoted."); + throw Exception("Still catching up, cannot be promoted."); + break; + case READY: return recover(l); break; default: - assert(0); + assert(0); // Not a valid state for the Backup role.. } + return 0; // Keep compiler happy +} + +Backup::~Backup() { + Mutex::ScopedLock l(lock); + stop(l); } }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/Backup.h b/cpp/src/qpid/ha/Backup.h index 4f2d5babde..4943ca5e2e 100644 --- a/cpp/src/qpid/ha/Backup.h +++ b/cpp/src/qpid/ha/Backup.h @@ -22,6 +22,7 @@ * */ +#include "Role.h" #include "Settings.h" #include "qpid/Url.h" #include "qpid/sys/Mutex.h" @@ -38,30 +39,41 @@ namespace ha { class Settings; class BrokerReplicator; class HaBroker; +class StatusCheck; +class Membership; /** - * State associated with a backup broker. Manages connections to primary. + * Backup role: Manages connections to primary, replicates management events and queue contents. * * THREAD SAFE */ -class Backup +class Backup : public Role { public: Backup(HaBroker&, const Settings&); ~Backup(); + + std::string getLogPrefix() const { return logPrefix; } + void setBrokerUrl(const Url&); - void setStatus(BrokerStatus); + + Role* promote(); private: - void initialize(const Url&); + void stop(sys::Mutex::ScopedLock&); + Role* recover(sys::Mutex::ScopedLock&); + std::string logPrefix; + Membership& membership; sys::Mutex lock; + bool stopped; HaBroker& haBroker; broker::Broker& broker; Settings settings; boost::shared_ptr<broker::Link> link; boost::shared_ptr<BrokerReplicator> replicator; + std::auto_ptr<StatusCheck> statusCheck; }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/BrokerInfo.cpp b/cpp/src/qpid/ha/BrokerInfo.cpp index c8bd1a14be..8efed91b17 100644 --- a/cpp/src/qpid/ha/BrokerInfo.cpp +++ b/cpp/src/qpid/ha/BrokerInfo.cpp @@ -33,27 +33,22 @@ namespace qpid { namespace ha { namespace { -std::string SYSTEM_ID="system-id"; -std::string HOST_NAME="host-name"; -std::string PORT="port"; -std::string STATUS="status"; +const std::string SYSTEM_ID="system-id"; +const std::string HOST_NAME="host-name"; +const std::string PORT="port"; +const std::string STATUS="status"; } using types::Uuid; using types::Variant; using framing::FieldTable; -BrokerInfo::BrokerInfo(const std::string& host, uint16_t port_, const types::Uuid& id) : - hostName(host), port(port_), systemId(id) -{ - updateLogId(); -} +BrokerInfo::BrokerInfo() : port(0), status(JOINING) {} -void BrokerInfo::updateLogId() { - std::ostringstream o; - o << hostName << ":" << port; - logId = o.str(); -} +BrokerInfo::BrokerInfo(const types::Uuid& id, BrokerStatus s, + const std::string& host, uint16_t port_) : + hostName(host), port(port_), systemId(id), status(s) +{} FieldTable BrokerInfo::asFieldTable() const { Variant::Map m = asMap(); @@ -91,7 +86,6 @@ void BrokerInfo::assign(const Variant::Map& m) { hostName = get(m, HOST_NAME).asString(); port = get(m, PORT).asUint16(); status = BrokerStatus(get(m, STATUS).asUint8()); - updateLogId(); } std::ostream& operator<<(std::ostream& o, const BrokerInfo& b) { diff --git a/cpp/src/qpid/ha/BrokerInfo.h b/cpp/src/qpid/ha/BrokerInfo.h index 642f7c1361..40358336b0 100644 --- a/cpp/src/qpid/ha/BrokerInfo.h +++ b/cpp/src/qpid/ha/BrokerInfo.h @@ -43,8 +43,9 @@ class BrokerInfo typedef std::set<BrokerInfo> Set; typedef std::map<types::Uuid, BrokerInfo> Map; - BrokerInfo() {} - BrokerInfo(const std::string& host, uint16_t port_, const types::Uuid& id); + BrokerInfo(); + BrokerInfo(const types::Uuid& id, BrokerStatus, + const std::string& host=std::string(), uint16_t port=0); BrokerInfo(const framing::FieldTable& ft) { assign(ft); } BrokerInfo(const types::Variant::Map& m) { assign(m); } @@ -52,7 +53,6 @@ class BrokerInfo std::string getHostName() const { return hostName; } BrokerStatus getStatus() const { return status; } uint16_t getPort() const { return port; } - std::string getLogId() const { return logId; } void setStatus(BrokerStatus s) { status = s; } @@ -66,8 +66,6 @@ class BrokerInfo bool operator<(const BrokerInfo x) const { return systemId < x.systemId; } private: - void updateLogId(); - std::string logId; std::string hostName; uint16_t port; types::Uuid systemId; diff --git a/cpp/src/qpid/ha/BrokerReplicator.cpp b/cpp/src/qpid/ha/BrokerReplicator.cpp index 3a3c9c2954..983b976d76 100644 --- a/cpp/src/qpid/ha/BrokerReplicator.cpp +++ b/cpp/src/qpid/ha/BrokerReplicator.cpp @@ -23,16 +23,19 @@ #include "QueueReplicator.h" #include "qpid/broker/Broker.h" #include "qpid/broker/Connection.h" +#include "qpid/broker/ConnectionObserver.h" #include "qpid/broker/Queue.h" #include "qpid/broker/QueueSettings.h" #include "qpid/broker/Link.h" #include "qpid/broker/amqp_0_10/MessageTransfer.h" #include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" #include "qpid/log/Statement.h" #include "qpid/amqp_0_10/Codecs.h" #include "qpid/broker/SessionHandler.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/reply_exceptions.h" #include "qmf/org/apache/qpid/broker/EventBind.h" #include "qmf/org/apache/qpid/broker/EventUnbind.h" #include "qmf/org/apache/qpid/broker/EventExchangeDeclare.h" @@ -41,6 +44,7 @@ #include "qmf/org/apache/qpid/broker/EventQueueDelete.h" #include "qmf/org/apache/qpid/broker/EventSubscribe.h" #include "qmf/org/apache/qpid/ha/EventMembersUpdate.h" +#include <boost/bind.hpp> #include <algorithm> #include <sstream> #include <iostream> @@ -57,23 +61,25 @@ using qmf::org::apache::qpid::broker::EventQueueDeclare; using qmf::org::apache::qpid::broker::EventQueueDelete; using qmf::org::apache::qpid::broker::EventSubscribe; using qmf::org::apache::qpid::ha::EventMembersUpdate; +using qpid::broker::amqp_0_10::MessageTransfer; using namespace framing; -using std::string; +using namespace std; using std::ostream; using types::Variant; using namespace broker; namespace { -const string QPID_CONFIGURATION_REPLICATOR("qpid.configuration-replicator"); +const string QPID_CONFIGURATION_REPLICATOR("qpid.broker-replicator"); const string CLASS_NAME("_class_name"); const string EVENT("_event"); const string OBJECT_NAME("_object_name"); const string PACKAGE_NAME("_package_name"); const string QUERY_RESPONSE("_query_response"); -const string SCHEMA_ID("_schema_id"); const string VALUES("_values"); +const string SCHEMA_ID("_schema_id"); +const string WHAT("_what"); const string ALTEX("altEx"); const string ALTEXCHANGE("altExchange"); @@ -81,24 +87,27 @@ const string ARGS("args"); const string ARGUMENTS("arguments"); const string AUTODEL("autoDel"); const string AUTODELETE("autoDelete"); -const string EXCL("excl"); -const string EXCLUSIVE("exclusive"); const string BIND("bind"); -const string UNBIND("unbind"); const string BINDING("binding"); +const string BINDING_KEY("bindingKey"); const string CREATED("created"); const string DISP("disp"); +const string DEST("dest"); const string DURABLE("durable"); const string EXCHANGE("exchange"); +const string EXCL("excl"); +const string EXCLUSIVE("exclusive"); const string EXNAME("exName"); const string EXTYPE("exType"); +const string HA_BROKER("habroker"); const string KEY("key"); const string NAME("name"); +const string PARTIAL("partial"); const string QNAME("qName"); const string QUEUE("queue"); const string TYPE("type"); -const string HA_BROKER("habroker"); -const string PARTIAL("partial"); +const string UNBIND("unbind"); +const string CONSUMER_COUNT("consumerCount"); const string AGENT_EVENT_BROKER("agent.ind.event.org_apache_qpid_broker.#"); const string AGENT_EVENT_HA("agent.ind.event.org_apache_qpid_ha.#"); @@ -107,10 +116,6 @@ const string QMF_CONTENT("qmf.content"); const string QMF_DEFAULT_TOPIC("qmf.default.topic"); const string QMF_OPCODE("qmf.opcode"); -const string _WHAT("_what"); -const string _CLASS_NAME("_class_name"); -const string _PACKAGE_NAME("_package_name"); -const string _SCHEMA_ID("_schema_id"); const string OBJECT("OBJECT"); const string ORG_APACHE_QPID_BROKER("org.apache.qpid.broker"); const string ORG_APACHE_QPID_HA("org.apache.qpid.ha"); @@ -118,21 +123,18 @@ const string QMF_DEFAULT_DIRECT("qmf.default.direct"); const string _QUERY_REQUEST("_query_request"); const string BROKER("broker"); const string MEMBERS("members"); - -template <class T> bool match(Variant::Map& schema) { - return T::match(schema[CLASS_NAME], schema[PACKAGE_NAME]); -} +const string AUTO_DELETE_TIMEOUT("qpid.auto_delete_timeout"); void sendQuery(const string& packageName, const string& className, const string& queueName, SessionHandler& sessionHandler) { framing::AMQP_ServerProxy peer(sessionHandler.out); Variant::Map request; - request[_WHAT] = OBJECT; + request[WHAT] = OBJECT; Variant::Map schema; - schema[_CLASS_NAME] = className; - schema[_PACKAGE_NAME] = packageName; - request[_SCHEMA_ID] = schema; + schema[CLASS_NAME] = className; + schema[PACKAGE_NAME] = packageName; + request[SCHEMA_ID] = schema; AMQFrame method((MessageTransferBody(ProtocolVersion(), QMF_DEFAULT_DIRECT, 0, 0))); method.setBof(true); @@ -170,19 +172,144 @@ Variant::Map asMapVoid(const Variant& value) { } } // namespace +// Listens for errors on the bridge session. +class BrokerReplicator::ErrorListener : public SessionHandler::ErrorListener { + public: + ErrorListener(const std::string& lp, BrokerReplicator& br) : + logPrefix(lp), brokerReplicator(br) {} + + void connectionException(framing::connection::CloseCode, const std::string& msg) { + QPID_LOG(error, logPrefix << "Connection error: " << msg); + } + void channelException(framing::session::DetachCode, const std::string& msg) { + QPID_LOG(error, logPrefix << "Channel error: " << msg); + } + void executionException(framing::execution::ErrorCode, const std::string& msg) { + QPID_LOG(error, logPrefix << "Execution error: " << msg); + } + void detach() { + QPID_LOG(debug, logPrefix << "Session detached."); + } + + private: + std::string logPrefix; + BrokerReplicator& brokerReplicator; +}; + +class BrokerReplicator::ConnectionObserver : public broker::ConnectionObserver +{ + public: + ConnectionObserver(BrokerReplicator& br) : brokerReplicator(br) {} + virtual void connection(Connection&) {} + virtual void opened(Connection&) {} + + virtual void closed(Connection& c) { + if (brokerReplicator.link && &c == brokerReplicator.connection) + brokerReplicator.disconnected(); + } + virtual void forced(Connection& c, const std::string& /*message*/) { closed(c); } + private: + BrokerReplicator& brokerReplicator; +}; + +/** Keep track of queues or exchanges during the update process to solve 2 + * problems. + * + * 1. Once all responses are processed, remove any queues/exchanges + * that were not mentioned as they no longer exist on the primary. + * + * 2. During the update if we see an event for an object we should + * ignore any subsequent responses for that object as they are out + * of date. + */ +class BrokerReplicator::UpdateTracker { + public: + typedef std::set<std::string> Names; + typedef boost::function<void (const std::string&)> CleanFn; + + UpdateTracker(const std::string& type_, // "queue" or "exchange" + CleanFn f, const ReplicationTest& rt) + : type(type_), cleanFn(f), repTest(rt) {} + + /** Destructor cleans up remaining initial queues. */ + ~UpdateTracker() { + // Don't throw in a destructor. + try { for_each(initial.begin(), initial.end(), cleanFn); } + catch (const std::exception& e) { + QPID_LOG(error, "Error in cleanup of lost objects: " << e.what()); + } + } + + /** Add an exchange name */ + void addExchange(Exchange::shared_ptr ex) { + if (repTest.getLevel(*ex)) + initial.insert(ex->getName()); + } + + /** Add a queue name. */ + void addQueue(Queue::shared_ptr q) { + if (repTest.getLevel(*q)) + initial.insert(q->getName()); + } + + /** Received an event for name */ + void event(const std::string& name) { + initial.erase(name); // no longer a candidate for deleting + events.insert(name); // we have seen an event for this name + } + + /** Received a response for name. + *@return true if this response should be processed, false if we have + *already seen an event for this object. + */ + bool response(const std::string& name) { + initial.erase(name); // no longer a candidate for deleting + return events.find(name) == events.end(); // true if no event seen yet. + } + + private: + void clean(const std::string& name) { + QPID_LOG(info, "Backup updated, deleting " << type << " " << name); + cleanFn(name); + } + + std::string type; + Names initial, events; + CleanFn cleanFn; + ReplicationTest repTest; +}; + BrokerReplicator::BrokerReplicator(HaBroker& hb, const boost::shared_ptr<Link>& l) : Exchange(QPID_CONFIGURATION_REPLICATOR), - logPrefix("Backup: "), replicationTest(hb.getReplicationTest()), - haBroker(hb), broker(hb.getBroker()), link(l), + logPrefix("Backup: "), replicationTest(NONE), + haBroker(hb), broker(hb.getBroker()), + exchanges(broker.getExchanges()), queues(broker.getQueues()), + link(l), initialized(false), - alternates(hb.getBroker().getExchanges()) -{} + alternates(hb.getBroker().getExchanges()), + connection(0) +{ + connectionObserver.reset(new ConnectionObserver(*this)); + broker.getConnectionObservers().add(connectionObserver); + framing::FieldTable args = getArgs(); + args.setString(QPID_REPLICATE, printable(NONE).str()); + setArgs(args); + + dispatch[EventQueueDeclare::getFullName()] = &BrokerReplicator::doEventQueueDeclare; + dispatch[EventQueueDelete::getFullName()] = &BrokerReplicator::doEventQueueDelete; + dispatch[EventExchangeDeclare::getFullName()] = &BrokerReplicator::doEventExchangeDeclare; + dispatch[EventExchangeDelete::getFullName()] = &BrokerReplicator::doEventExchangeDelete; + dispatch[EventBind::getFullName()] = &BrokerReplicator::doEventBind; + dispatch[EventUnbind::getFullName()] = &BrokerReplicator::doEventUnbind; + dispatch[EventMembersUpdate::getFullName()] = &BrokerReplicator::doEventMembersUpdate; + dispatch[EventSubscribe::getFullName()] = &BrokerReplicator::doEventSubscribe; +} void BrokerReplicator::initialize() { // Can't do this in the constructor because we need a shared_ptr to this. types::Uuid uuid(true); const std::string name(QPID_CONFIGURATION_REPLICATOR + ".bridge." + uuid.str()); - broker.getLinks().declare( + std::pair<Bridge::shared_ptr, bool> result = broker.getLinks().declare( name, // name for bridge *link, // parent false, // durable @@ -195,21 +322,47 @@ void BrokerReplicator::initialize() { "", // excludes false, // dynamic 0, // sync? - // shared_ptr keeps this in memory until outstanding initializeBridge + // shared_ptr keeps this in memory until outstanding connected // calls are run. - boost::bind(&BrokerReplicator::initializeBridge, shared_from_this(), _1, _2) + boost::bind(&BrokerReplicator::connected, shared_from_this(), _1, _2) ); + assert(result.second); + result.first->setErrorListener( + boost::shared_ptr<ErrorListener>(new ErrorListener(logPrefix, *this))); } -BrokerReplicator::~BrokerReplicator() { } +BrokerReplicator::~BrokerReplicator() { shutdown(); } + +namespace { +void collectQueueReplicators( + const boost::shared_ptr<Exchange> ex, set<boost::shared_ptr<QueueReplicator> >& collect) +{ + boost::shared_ptr<QueueReplicator> qr(boost::dynamic_pointer_cast<QueueReplicator>(ex)); + if (qr) collect.insert(qr); +} +} // namespace + +void BrokerReplicator::shutdown() { + // NOTE: this is called in a QMF dispatch thread, not the Link's connection + // thread. It's OK to be unlocked because it doesn't use any mutable state, + // it only calls thread safe functions objects belonging to the Broker. + + // Unregister with broker objects: + if (connectionObserver) { + broker.getConnectionObservers().remove(connectionObserver); + connectionObserver.reset(); + } + broker.getExchanges().destroy(getName()); +} // This is called in the connection IO thread when the bridge is started. -void BrokerReplicator::initializeBridge(Bridge& bridge, SessionHandler& sessionHandler) { +void BrokerReplicator::connected(Bridge& bridge, SessionHandler& sessionHandler) { // Use the credentials of the outgoing Link connection for creating queues, // exchanges etc. We know link->getConnection() is non-zero because we are // being called in the connections thread context. // - assert(link->getConnection()); + connection = link->getConnection(); + assert(connection); userId = link->getConnection()->getUserId(); remoteHost = link->getConnection()->getUrl(); @@ -221,6 +374,19 @@ void BrokerReplicator::initializeBridge(Bridge& bridge, SessionHandler& sessionH << " status:" << printable(haBroker.getStatus())); initialized = true; + exchangeTracker.reset( + new UpdateTracker("exchange", + boost::bind(&BrokerReplicator::deleteExchange, this, _1), + replicationTest)); + exchanges.eachExchange( + boost::bind(&UpdateTracker::addExchange, exchangeTracker.get(), _1)); + + queueTracker.reset( + new UpdateTracker("queue", + boost::bind(&BrokerReplicator::deleteQueue, this, _1, true), + replicationTest)); + queues.eachQueue(boost::bind(&UpdateTracker::addQueue, queueTracker.get(), _1)); + framing::AMQP_ServerProxy peer(sessionHandler.out); const qmf::org::apache::qpid::broker::ArgsLinkBridge& args(bridge.getArgs()); @@ -231,9 +397,14 @@ void BrokerReplicator::initializeBridge(Bridge& bridge, SessionHandler& sessionH peer.getExchange().bind(queueName, QMF_DEFAULT_TOPIC, AGENT_EVENT_BROKER, FieldTable()); peer.getExchange().bind(queueName, QMF_DEFAULT_TOPIC, AGENT_EVENT_HA, FieldTable()); //subscribe to the queue - peer.getMessage().subscribe(queueName, args.i_dest, 1, 0, false, "", 0, FieldTable()); - peer.getMessage().flow(args.i_dest, 0, 0xFFFFFFFF); - peer.getMessage().flow(args.i_dest, 1, 0xFFFFFFFF); + FieldTable arguments; + arguments.setInt(QueueReplicator::QPID_SYNC_FREQUENCY, 1); // FIXME aconway 2012-05-22: optimize? + peer.getMessage().subscribe( + queueName, args.i_dest, 1/*accept-none*/, 0/*pre-acquired*/, + false/*exclusive*/, "", 0, arguments); + peer.getMessage().setFlowMode(args.i_dest, 1); // Window + peer.getMessage().flow(args.i_dest, 0, haBroker.getSettings().getFlowMessages()); + peer.getMessage().flow(args.i_dest, 1, haBroker.getSettings().getFlowBytes()); // Issue a query request for queues, exchanges, bindings and the habroker // using event queue as the reply-to address @@ -247,12 +418,12 @@ void BrokerReplicator::route(Deliverable& msg) { // We transition from JOINING->CATCHUP on the first message received from the primary. // Until now we couldn't be sure if we had a good connection to the primary. if (haBroker.getStatus() == JOINING) { - haBroker.setStatus(CATCHUP); + haBroker.getMembership().setStatus(CATCHUP); QPID_LOG(notice, logPrefix << "Connected to primary " << primary); } Variant::List list; try { - if (!qpid::broker::amqp_0_10::MessageTransfer::isQMFv2(msg.getMessage())) + if (!MessageTransfer::isQMFv2(msg.getMessage())) throw Exception("Unexpected message, not QMF2 event or query response."); // decode as list string content = msg.getMessage().getContent(); @@ -264,13 +435,9 @@ void BrokerReplicator::route(Deliverable& msg) { QPID_LOG(trace, "Broker replicator event: " << map); Variant::Map& schema = map[SCHEMA_ID].asMap(); Variant::Map& values = map[VALUES].asMap(); - if (match<EventQueueDeclare>(schema)) doEventQueueDeclare(values); - else if (match<EventQueueDelete>(schema)) doEventQueueDelete(values); - else if (match<EventExchangeDeclare>(schema)) doEventExchangeDeclare(values); - else if (match<EventExchangeDelete>(schema)) doEventExchangeDelete(values); - else if (match<EventBind>(schema)) doEventBind(values); - else if (match<EventUnbind>(schema)) doEventUnbind(values); - else if (match<EventMembersUpdate>(schema)) doEventMembersUpdate(values); + EventKey key(schema[PACKAGE_NAME], schema[CLASS_NAME]); + EventDispatchMap::iterator j = dispatch.find(key); + if (j != dispatch.end()) (this->*(j->second))(values); } } else if (msg.getMessage().getPropertyAsString(QMF_OPCODE) == QUERY_RESPONSE) { for (Variant::List::iterator i = list.begin(); i != list.end(); ++i) { @@ -285,15 +452,21 @@ void BrokerReplicator::route(Deliverable& msg) { else if (type == BINDING) doResponseBind(values); else if (type == HA_BROKER) doResponseHaBroker(values); } - if (qpid::broker::amqp_0_10::MessageTransfer::isLastQMFResponse(msg.getMessage(), EXCHANGE)) { - // We have received all of the exchange response. + if (MessageTransfer::isLastQMFResponse(msg.getMessage(), EXCHANGE)) { + QPID_LOG(debug, logPrefix << "All exchange responses received.") + exchangeTracker.reset(); // Clean up exchanges that no longer exist in the primary alternates.clear(); } + if (MessageTransfer::isLastQMFResponse(msg.getMessage(), QUEUE)) { + QPID_LOG(debug, logPrefix << "All queue responses received."); + queueTracker.reset(); // Clean up queues that no longer exist in the primary + } } } catch (const std::exception& e) { - QPID_LOG(critical, logPrefix << "Configuration failed: " << e.what() - << ": while handling: " << list); - haBroker.shutdown(); +; + haBroker.shutdown( + QPID_MSG(logPrefix << "Configuration replication failed: " + << e.what() << ": while handling: " << list)); throw; } } @@ -301,31 +474,22 @@ void BrokerReplicator::route(Deliverable& msg) { void BrokerReplicator::doEventQueueDeclare(Variant::Map& values) { Variant::Map argsMap = asMapVoid(values[ARGS]); - bool autoDel = values[AUTODEL].asBool(); - bool excl = values[EXCL].asBool(); - if (values[DISP] == CREATED && replicationTest.isReplicated(CONFIGURATION, argsMap, autoDel, excl)) { + if (values[DISP] == CREATED && replicationTest.getLevel(argsMap)) { string name = values[QNAME].asString(); QueueSettings settings(values[DURABLE].asBool(), values[AUTODEL].asBool()); + QPID_LOG(debug, logPrefix << "Queue declare event: " << name); + if (queueTracker.get()) queueTracker->event(name); framing::FieldTable args; qpid::amqp_0_10::translate(argsMap, args); // If we already have a queue with this name, replace it. // The queue was definitely created on the primary. - if (broker.getQueues().find(name)) { - QPID_LOG(warning, logPrefix << "Replacing exsiting queue: " << name); - broker.getQueues().destroy(name); - stopQueueReplicator(name); + if (queues.find(name)) { + QPID_LOG(warning, logPrefix << "Declare event, replacing exsiting queue: " + << name); + deleteQueue(name); } - settings.populate(args, settings.storeSettings); - std::pair<boost::shared_ptr<Queue>, bool> result = - broker.createQueue( - name, - settings, - 0 /*i.e. no owner regardless of exclusivity on master*/, - values[ALTEX].asString(), - userId, - remoteHost); - assert(result.second); // Should be true since we destroyed existing queue above - startQueueReplicator(result.first); + replicateQueue(name, values[DURABLE].asBool(), values[AUTODEL].asBool(), args, + values[ALTEX].asString()); } } @@ -333,7 +497,7 @@ boost::shared_ptr<QueueReplicator> BrokerReplicator::findQueueReplicator( const std::string& qname) { string rname = QueueReplicator::replicatorName(qname); - boost::shared_ptr<broker::Exchange> ex = broker.getExchanges().find(rname); + boost::shared_ptr<broker::Exchange> ex = exchanges.find(rname); return boost::dynamic_pointer_cast<QueueReplicator>(ex); } @@ -341,79 +505,85 @@ void BrokerReplicator::doEventQueueDelete(Variant::Map& values) { // The remote queue has already been deleted so replicator // sessions may be closed by a "queue deleted" exception. string name = values[QNAME].asString(); - boost::shared_ptr<Queue> queue = broker.getQueues().find(name); - if (queue && replicationTest.replicateLevel(queue->getSettings().storeSettings)) { + boost::shared_ptr<Queue> queue = queues.find(name); + if (queue && replicationTest.getLevel(*queue)) { QPID_LOG(debug, logPrefix << "Queue delete event: " << name); - stopQueueReplicator(name); - broker.deleteQueue(name, userId, remoteHost); + if (queueTracker.get()) queueTracker->event(name); + deleteQueue(name); } } void BrokerReplicator::doEventExchangeDeclare(Variant::Map& values) { Variant::Map argsMap(asMapVoid(values[ARGS])); - if (!replicationTest.replicateLevel(argsMap)) return; // Not a replicated exchange. - if (values[DISP] == CREATED && replicationTest.replicateLevel(argsMap)) { + if (values[DISP] == CREATED && replicationTest.getLevel(argsMap)) { string name = values[EXNAME].asString(); QPID_LOG(debug, logPrefix << "Exchange declare event: " << name); + if (exchangeTracker.get()) exchangeTracker->event(name); framing::FieldTable args; qpid::amqp_0_10::translate(argsMap, args); // If we already have a exchange with this name, replace it. // The exchange was definitely created on the primary. - if (broker.getExchanges().find(name)) { - broker.getExchanges().destroy(name); - QPID_LOG(warning, logPrefix << "Replaced exsiting exchange: " << name); + if (exchanges.find(name)) { + deleteExchange(name); + QPID_LOG(warning, logPrefix << "Declare event, replacing existing exchange: " + << name); } - boost::shared_ptr<Exchange> exchange = - createExchange(name, values[EXTYPE].asString(), values[DURABLE].asBool(), args, values[ALTEX].asString()); - assert(exchange); + CreateExchangeResult result = createExchange( + name, values[EXTYPE].asString(), values[DURABLE].asBool(), args, + values[ALTEX].asString()); + replicatedExchanges.insert(name); + assert(result.second); } } void BrokerReplicator::doEventExchangeDelete(Variant::Map& values) { string name = values[EXNAME].asString(); - boost::shared_ptr<Exchange> exchange = broker.getExchanges().find(name); + boost::shared_ptr<Exchange> exchange = exchanges.find(name); if (!exchange) { - QPID_LOG(warning, logPrefix << "Exchange delete event, does not exist: " << name); - } else if (!replicationTest.replicateLevel(exchange->getArgs())) { + QPID_LOG(warning, logPrefix << "Exchange delete event, not found: " << name); + } else if (!replicationTest.getLevel(*exchange)) { QPID_LOG(warning, logPrefix << "Exchange delete event, not replicated: " << name); } else { QPID_LOG(debug, logPrefix << "Exchange delete event:" << name); - broker.deleteExchange(name, userId, remoteHost); + if (exchangeTracker.get()) exchangeTracker->event(name); + deleteExchange(name); + replicatedExchanges.erase(name); } } void BrokerReplicator::doEventBind(Variant::Map& values) { boost::shared_ptr<Exchange> exchange = - broker.getExchanges().find(values[EXNAME].asString()); + exchanges.find(values[EXNAME].asString()); boost::shared_ptr<Queue> queue = - broker.getQueues().find(values[QNAME].asString()); - // We only replicate binds for a replicated queue to replicated - // exchange that both exist locally. - if (exchange && replicationTest.replicateLevel(exchange->getArgs()) && - queue && replicationTest.replicateLevel(queue->getSettings().storeSettings)) + queues.find(values[QNAME].asString()); + framing::FieldTable args; + qpid::amqp_0_10::translate(asMapVoid(values[ARGS]), args); + // We only replicate binds for a replicated queue to replicated exchange + // that both exist locally. Respect the replication level set in the + // bind arguments, but replicate by default. + if (exchange && replicationTest.getLevel(*exchange) && + queue && replicationTest.getLevel(*queue) && + ReplicationTest(ALL).getLevel(args)) { - framing::FieldTable args; - qpid::amqp_0_10::translate(asMapVoid(values[ARGS]), args); string key = values[KEY].asString(); QPID_LOG(debug, logPrefix << "Bind event: exchange=" << exchange->getName() << " queue=" << queue->getName() - << " key=" << key); + << " key=" << key + << " args=" << args); exchange->bind(queue, key, &args, 0); } } void BrokerReplicator::doEventUnbind(Variant::Map& values) { boost::shared_ptr<Exchange> exchange = - broker.getExchanges().find(values[EXNAME].asString()); + exchanges.find(values[EXNAME].asString()); boost::shared_ptr<Queue> queue = - broker.getQueues().find(values[QNAME].asString()); + queues.find(values[QNAME].asString()); // We only replicate unbinds for a replicated queue to replicated // exchange that both exist locally. - if (exchange && replicationTest.replicateLevel(exchange->getArgs()) && - queue && replicationTest.replicateLevel(queue->getSettings().storeSettings)) + if (exchange && replicationTest.getLevel(*exchange) && + queue && replicationTest.getLevel(*queue)) { - framing::FieldTable args; - qpid::amqp_0_10::translate(asMapVoid(values[ARGS]), args); string key = values[KEY].asString(); QPID_LOG(debug, logPrefix << "Unbind event: exchange=" << exchange->getName() << " queue=" << queue->getName() @@ -424,7 +594,17 @@ void BrokerReplicator::doEventUnbind(Variant::Map& values) { void BrokerReplicator::doEventMembersUpdate(Variant::Map& values) { Variant::List members = values[MEMBERS].asList(); - haBroker.setMembership(members); + setMembership(members); +} + +void BrokerReplicator::doEventSubscribe(Variant::Map& values) { + // Ignore queue replicator subscriptions. + if (QueueReplicator::isReplicatorName(values[DEST].asString())) return; + boost::shared_ptr<QueueReplicator> qr = findQueueReplicator(values[QNAME]); + if (qr) { + qr->setSubscribed(); + QPID_LOG(debug, logPrefix << "Subscribe event: " << values[QNAME]); + } } namespace { @@ -441,40 +621,68 @@ string getAltExchange(const types::Variant& var) { } else return string(); } + +Variant getHaUuid(const Variant::Map& map) { + Variant::Map::const_iterator i = map.find(QPID_HA_UUID); + return i == map.end() ? Variant() : i->second; } +} // namespace + + void BrokerReplicator::doResponseQueue(Variant::Map& values) { Variant::Map argsMap(asMapVoid(values[ARGUMENTS])); - if (!replicationTest.isReplicated( - CONFIGURATION, - values[ARGUMENTS].asMap(), - values[AUTODELETE].asBool(), - values[EXCLUSIVE].asBool())) - return; + if (!replicationTest.getLevel(argsMap)) return; string name(values[NAME].asString()); + if (!queueTracker.get()) + throw Exception(QPID_MSG("Unexpected queue response: " << values)); + if (!queueTracker->response(name)) return; // Response is out-of-date QPID_LOG(debug, logPrefix << "Queue response: " << name); + // If we see a queue with the same name as one we have, but not the same UUID, + // then replace the one we have. + boost::shared_ptr<Queue> queue = queues.find(name); + if (queue && getHaUuid(queue->getSettings().original) != getHaUuid(argsMap)) { + QPID_LOG(warning, logPrefix << "UUID mismatch, replacing queue: " + << name); + deleteQueue(name); + } framing::FieldTable args; qpid::amqp_0_10::translate(argsMap, args); - boost::shared_ptr<Queue> queue = - createQueue(name, values[DURABLE].asBool(), values[AUTODELETE].asBool(), args, - getAltExchange(values[ALTEXCHANGE])); - // It is normal for the queue to already exist if we are failing over. - if (queue) startQueueReplicator(queue); - else QPID_LOG(debug, logPrefix << "Queue already replicated: " << name); + boost::shared_ptr<QueueReplicator> qr = replicateQueue( + name, values[DURABLE].asBool(), values[AUTODELETE].asBool(), args, + getAltExchange(values[ALTEXCHANGE])); + if (qr) { + Variant::Map::const_iterator i = values.find(CONSUMER_COUNT); + if (i != values.end() && isIntegerType(i->second.getType())) { + if (i->second.asInt64()) qr->setSubscribed(); + } + } } void BrokerReplicator::doResponseExchange(Variant::Map& values) { Variant::Map argsMap(asMapVoid(values[ARGUMENTS])); - if (!replicationTest.replicateLevel(argsMap)) return; + if (!replicationTest.getLevel(argsMap)) return; string name = values[NAME].asString(); + if (!exchangeTracker.get()) + throw Exception(QPID_MSG("Unexpected exchange response: " << values)); + if (!exchangeTracker->response(name)) return; // Response is out of date. QPID_LOG(debug, logPrefix << "Exchange response: " << name); framing::FieldTable args; qpid::amqp_0_10::translate(argsMap, args); - boost::shared_ptr<Exchange> exchange = createExchange( + // If we see an exchange with the same name as one we have, but not the same UUID, + // then replace the one we have. + boost::shared_ptr<Exchange> exchange = exchanges.find(name); + if (exchange && + exchange->getArgs().getAsString(QPID_HA_UUID) != args.getAsString(QPID_HA_UUID)) + { + QPID_LOG(warning, logPrefix << "UUID mismatch, replacing exchange: " + << name); + deleteExchange(name); + } + CreateExchangeResult result = createExchange( name, values[TYPE].asString(), values[DURABLE].asBool(), args, getAltExchange(values[ALTEXCHANGE])); - // It is normal for the exchange to already exist if we are failing over. - QPID_LOG_IF(debug, !exchange, logPrefix << "Exchange already replicated: " << name); + replicatedExchanges.insert(name); } namespace { @@ -501,19 +709,25 @@ const std::string QUEUE_REF("queueRef"); void BrokerReplicator::doResponseBind(Variant::Map& values) { std::string exName = getRefName(EXCHANGE_REF_PREFIX, values[EXCHANGE_REF]); std::string qName = getRefName(QUEUE_REF_PREFIX, values[QUEUE_REF]); - boost::shared_ptr<Exchange> exchange = broker.getExchanges().find(exName); - boost::shared_ptr<Queue> queue = broker.getQueues().find(qName); + boost::shared_ptr<Exchange> exchange = exchanges.find(exName); + boost::shared_ptr<Queue> queue = queues.find(qName); - // Automatically replicate binding if queue and exchange exist and are replicated - if (exchange && replicationTest.replicateLevel(exchange->getArgs()) && - queue && replicationTest.replicateLevel(queue->getSettings().storeSettings)) + framing::FieldTable args; + qpid::amqp_0_10::translate(asMapVoid(values[ARGUMENTS]), args); + + // Automatically replicate binding if queue and exchange exist and are replicated. + // Respect replicate setting in binding args but default to replicated. + if (exchange && replicationTest.getLevel(*exchange) && + queue && replicationTest.getLevel(*queue) && + ReplicationTest(ALL).getLevel(args)) { - string key = values[KEY].asString(); + string key = values[BINDING_KEY].asString(); QPID_LOG(debug, logPrefix << "Bind response: exchange:" << exName << " queue:" << qName - << " key:" << key); - framing::FieldTable args; - qpid::amqp_0_10::translate(asMapVoid(values[ARGUMENTS]), args); + << " key:" << key + << " args:" << args); +// framing::FieldTable args; +// qpid::amqp_0_10::translate(asMapVoid(values[ARGUMENTS]), args); exchange->bind(queue, key, &args, 0); } } @@ -527,42 +741,65 @@ void BrokerReplicator::doResponseHaBroker(Variant::Map& values) { try { QPID_LOG(trace, logPrefix << "HA Broker response: " << values); ReplicateLevel mine = haBroker.getSettings().replicateDefault.get(); - ReplicateLevel primary = replicationTest.replicateLevel( - values[REPLICATE_DEFAULT].asString()); + ReplicateLevel primary = replicationTest.getLevel(values[REPLICATE_DEFAULT].asString()); if (mine != primary) throw Exception(QPID_MSG("Replicate default on backup (" << mine << ") does not match primary (" << primary << ")")); - haBroker.setMembership(values[MEMBERS].asList()); + setMembership(values[MEMBERS].asList()); } catch (const std::exception& e) { - QPID_LOG(critical, logPrefix << "Invalid HA Broker response: " << e.what() - << ": " << values); - haBroker.shutdown(); + haBroker.shutdown( + QPID_MSG(logPrefix << "Invalid HA Broker response: " << e.what() + << ": " << values)); + throw; } } -void BrokerReplicator::startQueueReplicator(const boost::shared_ptr<Queue>& queue) +boost::shared_ptr<QueueReplicator> BrokerReplicator::startQueueReplicator( + const boost::shared_ptr<Queue>& queue) { - if (replicationTest.replicateLevel(queue->getSettings().storeSettings) == ALL) { + if (replicationTest.getLevel(*queue) == ALL) { boost::shared_ptr<QueueReplicator> qr( new QueueReplicator(haBroker, queue, link)); - if (!broker.getExchanges().registerExchange(qr)) + if (!exchanges.registerExchange(qr)) throw Exception(QPID_MSG("Duplicate queue replicator " << qr->getName())); qr->activate(); + return qr; } + return boost::shared_ptr<QueueReplicator>(); } -void BrokerReplicator::stopQueueReplicator(const std::string& name) { - boost::shared_ptr<QueueReplicator> qr = findQueueReplicator(name); - if (qr) { - qr->deactivate(); - // QueueReplicator's bridge is now queued for destruction but may not - // actually be destroyed. - broker.getExchanges().destroy(qr->getName()); +void BrokerReplicator::deleteQueue(const std::string& name, bool purge) { + Queue::shared_ptr queue = queues.find(name); + if (queue) { + // Purge before deleting to ensure that we don't reroute any + // messages. Any reroutes will be done at the primary and + // replicated as normal. + if (purge) queue->purge(0, boost::shared_ptr<Exchange>()); + broker.deleteQueue(name, userId, remoteHost); + QPID_LOG(debug, logPrefix << "Queue deleted: " << name); + } +} + +void BrokerReplicator::deleteExchange(const std::string& name) { + try { + boost::shared_ptr<broker::Exchange> exchange = exchanges.find(name); + if (!exchange) { + QPID_LOG(warning, logPrefix << "Cannot delete exchange, not found: " << name); + return; + } + if (exchange->inUseAsAlternate()) { + QPID_LOG(warning, "Cannot delete exchange, in use as alternate: " << name); + return; + } + broker.deleteExchange(name, userId, remoteHost); + QPID_LOG(debug, logPrefix << "Exchange deleted: " << name); + } catch (const framing::NotFoundException&) { + QPID_LOG(debug, logPrefix << "Exchange not found for deletion: " << name); } } -boost::shared_ptr<Queue> BrokerReplicator::createQueue( +boost::shared_ptr<QueueReplicator> BrokerReplicator::replicateQueue( const std::string& name, bool durable, bool autodelete, @@ -571,7 +808,7 @@ boost::shared_ptr<Queue> BrokerReplicator::createQueue( { QueueSettings settings(durable, autodelete); settings.populate(arguments, settings.storeSettings); - std::pair<boost::shared_ptr<Queue>, bool> result = + CreateQueueResult result = broker.createQueue( name, settings, @@ -579,24 +816,23 @@ boost::shared_ptr<Queue> BrokerReplicator::createQueue( string(), // Set alternate exchange below userId, remoteHost); - if (result.second) { - if (!alternateExchange.empty()) { - alternates.setAlternate( - alternateExchange, boost::bind(&Queue::setAlternateExchange, result.first, _1)); - } - return result.first; + boost::shared_ptr<QueueReplicator> qr; + if (!findQueueReplicator(name)) qr = startQueueReplicator(result.first); + if (result.second && !alternateExchange.empty()) { + alternates.setAlternate( + alternateExchange, boost::bind(&Queue::setAlternateExchange, result.first, _1)); } - else return boost::shared_ptr<Queue>(); + return qr; } -boost::shared_ptr<Exchange> BrokerReplicator::createExchange( +BrokerReplicator::CreateExchangeResult BrokerReplicator::createExchange( const std::string& name, const std::string& type, bool durable, const qpid::framing::FieldTable& args, const std::string& alternateExchange) { - std::pair<boost::shared_ptr<Exchange>, bool> result = + CreateExchangeResult result = broker.createExchange( name, type, @@ -605,15 +841,12 @@ boost::shared_ptr<Exchange> BrokerReplicator::createExchange( args, userId, remoteHost); - if (result.second) { - alternates.addExchange(result.first); - if (!alternateExchange.empty()) { - alternates.setAlternate( - alternateExchange, boost::bind(&Exchange::setAlternate, result.first, _1)); - } - return result.first; + alternates.addExchange(result.first); + if (!alternateExchange.empty()) { + alternates.setAlternate( + alternateExchange, boost::bind(&Exchange::setAlternate, result.first, _1)); } - else return boost::shared_ptr<Exchange>(); + return result; } bool BrokerReplicator::bind(boost::shared_ptr<Queue>, const string&, const framing::FieldTable*, qpid::broker::AsyncStore* const) { return false; } @@ -626,4 +859,61 @@ void BrokerReplicator::write(char* /*target*/) {} string BrokerReplicator::getType() const { return QPID_CONFIGURATION_REPLICATOR; } +void BrokerReplicator::autoDeleteCheck(boost::shared_ptr<Exchange> ex) { + boost::shared_ptr<QueueReplicator> qr(boost::dynamic_pointer_cast<QueueReplicator>(ex)); + if (!qr) return; + assert(qr); + if (qr->getQueue()->isAutoDelete() && qr->isSubscribed()) { + if (qr->getQueue()->getSettings().autoDeleteDelay) { + // Start the auto-delete timer + Queue::tryAutoDelete(broker, qr->getQueue(), remoteHost, userId); + } + else { + // Delete immediately. Don't purge, the primary is gone so we need + // to reroute the deleted messages. + deleteQueue(qr->getQueue()->getName(), false); + } + } +} + +// Callback function for accumulating exchange candidates +namespace { + void exchangeAccumulatorCallback(vector<boost::shared_ptr<Exchange> >& c, const Exchange::shared_ptr& i) { + c.push_back(i); + } +} + +void BrokerReplicator::disconnected() { + QPID_LOG(info, logPrefix << "Disconnected from " << primary); + connection = 0; + // Clean up auto-delete queues + vector<boost::shared_ptr<Exchange> > collect; + // Make a copy so we can work outside the ExchangeRegistry lock + exchanges.eachExchange( + boost::bind(&exchangeAccumulatorCallback, boost::ref(collect), _1)); + for_each(collect.begin(), collect.end(), + boost::bind(&BrokerReplicator::autoDeleteCheck, this, _1)); +} + +void BrokerReplicator::setMembership(const Variant::List& brokers) { + Membership& membership(haBroker.getMembership()); + membership.assign(brokers); + // Check if the primary has signalled a change in my status: + // from CATCHUP to READY when we are caught up. + // from READY TO CATCHUP if we are timed out during fail-over. + BrokerInfo info; + if (membership.get(membership.getSelf(), info)) { + BrokerStatus oldStatus = haBroker.getStatus(); + BrokerStatus newStatus = info.getStatus(); + if (oldStatus == CATCHUP && newStatus == READY) { + QPID_LOG(info, logPrefix << logPrefix << "Caught-up and ready"); + haBroker.getMembership().setStatus(READY); + } + else if (oldStatus == READY && newStatus == CATCHUP) { + QPID_LOG(info, logPrefix << logPrefix << "No longer ready, catching up"); + haBroker.getMembership().setStatus(CATCHUP); + } + } +} + }} // namespace broker diff --git a/cpp/src/qpid/ha/BrokerReplicator.h b/cpp/src/qpid/ha/BrokerReplicator.h index f6983e8719..a42d607263 100644 --- a/cpp/src/qpid/ha/BrokerReplicator.h +++ b/cpp/src/qpid/ha/BrokerReplicator.h @@ -31,6 +31,7 @@ #include "qpid/management/ManagementObject.h" #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> +#include <set> namespace qpid { @@ -40,6 +41,9 @@ class Broker; class Link; class Bridge; class SessionHandler; +class Connection; +class QueueRegistry; +class ExchangeRegistry; } namespace framing { @@ -58,7 +62,9 @@ class QueueReplicator; * exchanges and bindings to replicate the primary. * It also creates QueueReplicators for newly replicated queues. * - * THREAD UNSAFE: Only called in Link connection thread, no need for locking. + * THREAD UNSAFE: + * All members except shutdown are only called in the Link's connection thread context. + * shutdown() does not use any mutable state. * */ class BrokerReplicator : public broker::Exchange, @@ -76,6 +82,7 @@ class BrokerReplicator : public broker::Exchange, bool unbind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*, qpid::broker::AsyncStore* const store); void route(broker::Deliverable&); bool isBound(boost::shared_ptr<broker::Queue>, const std::string* const, const framing::FieldTable* const); + void shutdown(); // DataSource interface - used to write persistence data to async store uint64_t getSize(); @@ -83,8 +90,20 @@ class BrokerReplicator : public broker::Exchange, private: typedef boost::shared_ptr<QueueReplicator> QueueReplicatorPtr; + typedef std::pair<boost::shared_ptr<broker::Queue>, bool> CreateQueueResult; + typedef std::pair<boost::shared_ptr<broker::Exchange>, bool> CreateExchangeResult; - void initializeBridge(broker::Bridge&, broker::SessionHandler&); + typedef std::pair<std::string,std::string> EventKey; + typedef void (BrokerReplicator::*DispatchFunction)(types::Variant::Map&); + typedef std::map<EventKey, DispatchFunction> EventDispatchMap; + + typedef std::map<std::string, QueueReplicatorPtr> QueueReplicatorMap; + + class UpdateTracker; + class ErrorListener; + class ConnectionObserver; + + void connected(broker::Bridge&, broker::SessionHandler&); void doEventQueueDeclare(types::Variant::Map& values); void doEventQueueDelete(types::Variant::Map& values); @@ -93,6 +112,7 @@ class BrokerReplicator : public broker::Exchange, void doEventBind(types::Variant::Map&); void doEventUnbind(types::Variant::Map&); void doEventMembersUpdate(types::Variant::Map&); + void doEventSubscribe(types::Variant::Map&); void doResponseQueue(types::Variant::Map& values); void doResponseExchange(types::Variant::Map& values); @@ -100,32 +120,50 @@ class BrokerReplicator : public broker::Exchange, void doResponseHaBroker(types::Variant::Map& values); QueueReplicatorPtr findQueueReplicator(const std::string& qname); - void startQueueReplicator(const boost::shared_ptr<broker::Queue>&); - void stopQueueReplicator(const std::string& name); + QueueReplicatorPtr startQueueReplicator(const boost::shared_ptr<broker::Queue>&); - boost::shared_ptr<broker::Queue> createQueue( + QueueReplicatorPtr replicateQueue( const std::string& name, bool durable, bool autodelete, const qpid::framing::FieldTable& arguments, const std::string& alternateExchange); - boost::shared_ptr<broker::Exchange> createExchange( + CreateExchangeResult createExchange( const std::string& name, const std::string& type, bool durable, const qpid::framing::FieldTable& args, const std::string& alternateExchange); + bool deactivate(boost::shared_ptr<broker::Exchange> ex, bool destroy); + void deleteQueue(const std::string& name, bool purge=true); + void deleteExchange(const std::string& name); + + void autoDeleteCheck(boost::shared_ptr<broker::Exchange>); + + void disconnected(); + + void setMembership(const types::Variant::List&); // Set membership from list. + std::string logPrefix; - std::string userId, remoteHost; ReplicationTest replicationTest; + std::string userId, remoteHost; HaBroker& haBroker; broker::Broker& broker; + broker::ExchangeRegistry& exchanges; + broker::QueueRegistry& queues; boost::shared_ptr<broker::Link> link; bool initialized; AlternateExchangeSetter alternates; qpid::Address primary; + typedef std::set<std::string> StringSet; + StringSet replicatedExchanges; // exchanges that have been replicated. + broker::Connection* connection; + EventDispatchMap dispatch; + std::auto_ptr<UpdateTracker> queueTracker; + std::auto_ptr<UpdateTracker> exchangeTracker; + boost::shared_ptr<ConnectionObserver> connectionObserver; }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/ha/ConnectionObserver.cpp b/cpp/src/qpid/ha/ConnectionObserver.cpp index 81ba3e4301..775222efd3 100644 --- a/cpp/src/qpid/ha/ConnectionObserver.cpp +++ b/cpp/src/qpid/ha/ConnectionObserver.cpp @@ -30,7 +30,7 @@ namespace qpid { namespace ha { ConnectionObserver::ConnectionObserver(HaBroker& hb, const types::Uuid& uuid) - : haBroker(hb), logPrefix("Connections: "), self(uuid) {} + : haBroker(hb), logPrefix("Backup: "), self(uuid) {} bool ConnectionObserver::getBrokerInfo(const broker::Connection& connection, BrokerInfo& info) { framing::FieldTable ft; @@ -41,9 +41,11 @@ bool ConnectionObserver::getBrokerInfo(const broker::Connection& connection, Bro return false; } -void ConnectionObserver::setObserver(const ObserverPtr& o){ +void ConnectionObserver::setObserver(const ObserverPtr& o, const std::string& newlogPrefix) +{ sys::Mutex::ScopedLock l(lock); observer = o; + logPrefix = newlogPrefix; } ConnectionObserver::ObserverPtr ConnectionObserver::getObserver() { diff --git a/cpp/src/qpid/ha/ConnectionObserver.h b/cpp/src/qpid/ha/ConnectionObserver.h index e3a6d1154a..5374660dbe 100644 --- a/cpp/src/qpid/ha/ConnectionObserver.h +++ b/cpp/src/qpid/ha/ConnectionObserver.h @@ -55,7 +55,7 @@ class ConnectionObserver : public broker::ConnectionObserver ConnectionObserver(HaBroker& haBroker, const types::Uuid& self); - void setObserver(const ObserverPtr&); + void setObserver(const ObserverPtr&, const std::string& logPrefix); ObserverPtr getObserver(); void opened(broker::Connection& connection); diff --git a/cpp/src/qpid/ha/HaBroker.cpp b/cpp/src/qpid/ha/HaBroker.cpp index ffbcb684bc..590db7efa5 100644 --- a/cpp/src/qpid/ha/HaBroker.cpp +++ b/cpp/src/qpid/ha/HaBroker.cpp @@ -26,6 +26,7 @@ #include "QueueReplicator.h" #include "ReplicatingSubscription.h" #include "Settings.h" +#include "StandAlone.h" #include "qpid/amqp_0_10/Codecs.h" #include "qpid/Exception.h" #include "qpid/broker/Broker.h" @@ -41,7 +42,6 @@ #include "qmf/org/apache/qpid/ha/ArgsHaBrokerReplicate.h" #include "qmf/org/apache/qpid/ha/ArgsHaBrokerSetBrokersUrl.h" #include "qmf/org/apache/qpid/ha/ArgsHaBrokerSetPublicUrl.h" -#include "qmf/org/apache/qpid/ha/EventMembersUpdate.h" #include "qpid/log/Statement.h" #include <boost/shared_ptr.hpp> @@ -54,134 +54,100 @@ using namespace std; using types::Variant; using types::Uuid; using sys::Mutex; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; // Called in Plugin::earlyInitialize HaBroker::HaBroker(broker::Broker& b, const Settings& s) - : logPrefix("Broker: "), - broker(b), - systemId(broker.getSystem()->getSystemId().data()), + : systemId(b.getSystem()->getSystemId().data()), settings(s), + broker(b), observer(new ConnectionObserver(*this, systemId)), - mgmtObject(0), - status(STANDALONE), - membership(systemId), - replicationTest(s.replicateDefault.get()) + role(new StandAlone), + membership(BrokerInfo(systemId, STANDALONE), *this) { // If we are joining a cluster we must start excluding clients now, // otherwise there's a window for a client to connect before we get to // initialize() if (settings.cluster) { - QPID_LOG(debug, logPrefix << "Rejecting client connections."); - observer->setObserver(boost::shared_ptr<broker::ConnectionObserver>( - new BackupConnectionExcluder)); + QPID_LOG(debug, "Broker startup, rejecting client connections."); + shared_ptr<broker::ConnectionObserver> excluder(new BackupConnectionExcluder); + observer->setObserver(excluder, "Backup: "); broker.getConnectionObservers().add(observer); } } +namespace { +const std::string NONE("none"); +bool isNone(const std::string& x) { return x.empty() || x == NONE; } +} + // Called in Plugin::initialize void HaBroker::initialize() { - // FIXME aconway 2012-07-19: assumes there's a TCP transport with a meaningful port. - brokerInfo = BrokerInfo( - broker.getSystem()->getNodeName(), - broker.getPort(broker::Broker::TCP_TRANSPORT), - systemId); - - QPID_LOG(notice, logPrefix << "Initializing: " << brokerInfo); + membership.add( + BrokerInfo( + membership.getSelf(), + settings.cluster ? JOINING : membership.getStatus(), + broker.getSystem()->getNodeName(), + broker.getPort(broker::Broker::TCP_TRANSPORT) + ) + ); + QPID_LOG(notice, "Initializing: " << membership.getInfo()); // Set up the management object. ManagementAgent* ma = broker.getManagementAgent(); if (settings.cluster && !ma) throw Exception("Cannot start HA: management is disabled"); _qmf::Package packageInit(ma); - mgmtObject = new _qmf::HaBroker(ma, this, "ha-broker"); + mgmtObject = _qmf::HaBroker::shared_ptr(new _qmf::HaBroker(ma, this, "ha-broker")); mgmtObject->set_replicateDefault(settings.replicateDefault.str()); mgmtObject->set_systemId(systemId); ma->addObject(mgmtObject); + membership.setMgmtObject(mgmtObject); // Register a factory for replicating subscriptions. broker.getConsumerFactories().add( - boost::shared_ptr<ReplicatingSubscription::Factory>( + shared_ptr<ReplicatingSubscription::Factory>( new ReplicatingSubscription::Factory())); // If we are in a cluster, start as backup in joining state. if (settings.cluster) { - status = JOINING; - backup.reset(new Backup(*this, settings)); + assert(membership.getStatus() == JOINING); + role.reset(new Backup(*this, settings)); broker.getKnownBrokers = boost::bind(&HaBroker::getKnownBrokers, this); + if (!isNone(settings.publicUrl)) setPublicUrl(Url(settings.publicUrl)); + if (!isNone(settings.brokerUrl)) setBrokerUrl(Url(settings.brokerUrl)); } - - if (!settings.clientUrl.empty()) setClientUrl(Url(settings.clientUrl)); - if (!settings.brokerUrl.empty()) setBrokerUrl(Url(settings.brokerUrl)); - - - // NOTE: lock is not needed in a constructor, but create one - // to pass to functions that have a ScopedLock parameter. - Mutex::ScopedLock l(lock); - statusChanged(l); } HaBroker::~HaBroker() { - QPID_LOG(notice, logPrefix << "Shut down: " << brokerInfo); + QPID_LOG(notice, role->getLogPrefix() << "Shut down"); broker.getConnectionObservers().remove(observer); } -void HaBroker::recover() { - auto_ptr<Backup> b; - { - Mutex::ScopedLock l(lock); - // No longer replicating, close link. Note: link must be closed before we - // setStatus(RECOVERING) as that will remove our broker info from the - // outgoing link properties so we won't recognize self-connects. - b = backup; - } - b.reset(); // Call destructor outside of lock. - BrokerInfo::Set backups; - { - Mutex::ScopedLock l(lock); - setStatus(RECOVERING, l); - backups = membership.otherBackups(); - membership.reset(brokerInfo); - // Drop the lock, new Primary may call back on activate. - } - // Outside of lock, may call back on activate() - primary.reset(new Primary(*this, backups)); // Starts primary-ready check. -} - -// Called back from Primary active check. -void HaBroker::activate() { setStatus(ACTIVE); } - Manageable::status_t HaBroker::ManagementMethod (uint32_t methodId, Args& args, string&) { switch (methodId) { case _qmf::HaBroker::METHOD_PROMOTE: { - switch (getStatus()) { - case JOINING: recover(); break; - case CATCHUP: - QPID_LOG(error, logPrefix << "Still catching up, cannot be promoted."); - throw Exception("Still catching up, cannot be promoted."); - break; - case READY: recover(); break; - case RECOVERING: break; - case ACTIVE: break; - case STANDALONE: break; - } - break; + Role* r = role->promote(); + if (r) role.reset(r); + break; } case _qmf::HaBroker::METHOD_SETBROKERSURL: { setBrokerUrl(Url(dynamic_cast<_qmf::ArgsHaBrokerSetBrokersUrl&>(args).i_url)); break; } case _qmf::HaBroker::METHOD_SETPUBLICURL: { - setClientUrl(Url(dynamic_cast<_qmf::ArgsHaBrokerSetPublicUrl&>(args).i_url)); + setPublicUrl(Url(dynamic_cast<_qmf::ArgsHaBrokerSetPublicUrl&>(args).i_url)); break; } case _qmf::HaBroker::METHOD_REPLICATE: { _qmf::ArgsHaBrokerReplicate& bq_args = dynamic_cast<_qmf::ArgsHaBrokerReplicate&>(args); - QPID_LOG(debug, logPrefix << "Replicate individual queue " + QPID_LOG(debug, role->getLogPrefix() << "Replicate individual queue " << bq_args.i_queue << " from " << bq_args.i_broker); - boost::shared_ptr<broker::Queue> queue = broker.getQueues().get(bq_args.i_queue); + shared_ptr<broker::Queue> queue = broker.getQueues().get(bq_args.i_queue); Url url(bq_args.i_broker); string protocol = url[0].protocol.empty() ? "tcp" : url[0].protocol; Uuid uuid(true); @@ -191,10 +157,10 @@ Manageable::status_t HaBroker::ManagementMethod (uint32_t methodId, Args& args, false, // durable settings.mechanism, settings.username, settings.password, false); // no amq.failover - don't want to use client URL. - boost::shared_ptr<broker::Link> link = result.first; + shared_ptr<broker::Link> link = result.first; link->setUrl(url); // Create a queue replicator - boost::shared_ptr<QueueReplicator> qr( + shared_ptr<QueueReplicator> qr( new QueueReplicator(*this, queue, link)); qr->activate(); broker.getExchanges().registerExchange(qr); @@ -207,31 +173,23 @@ Manageable::status_t HaBroker::ManagementMethod (uint32_t methodId, Args& args, return Manageable::STATUS_OK; } -void HaBroker::setClientUrl(const Url& url) { +void HaBroker::setPublicUrl(const Url& url) { Mutex::ScopedLock l(lock); - if (url.empty()) throw Exception("Invalid empty URL for HA client failover"); - clientUrl = url; - updateClientUrl(l); -} - -void HaBroker::updateClientUrl(Mutex::ScopedLock&) { - Url url = clientUrl.empty() ? brokerUrl : clientUrl; - if (url.empty()) throw Url::Invalid("HA client URL is empty"); + publicUrl = url; mgmtObject->set_publicUrl(url.str()); knownBrokers.clear(); knownBrokers.push_back(url); - QPID_LOG(debug, logPrefix << "Setting client URL to: " << url); + QPID_LOG(debug, role->getLogPrefix() << "Setting public URL to: " << url); } void HaBroker::setBrokerUrl(const Url& url) { - Mutex::ScopedLock l(lock); - if (url.empty()) throw Url::Invalid("HA broker URL is empty"); - brokerUrl = url; - mgmtObject->set_brokersUrl(brokerUrl.str()); - QPID_LOG(info, logPrefix << "Broker URL set to: " << url); - if (backup.get()) backup->setBrokerUrl(brokerUrl); - // Updating broker URL also updates defaulted client URL: - if (clientUrl.empty()) updateClientUrl(l); + { + Mutex::ScopedLock l(lock); + brokerUrl = url; + mgmtObject->set_brokersUrl(brokerUrl.str()); + QPID_LOG(info, role->getLogPrefix() << "Brokers URL set to: " << url); + } + role->setBrokerUrl(url); // Oustside lock } std::vector<Url> HaBroker::getKnownBrokers() const { @@ -239,116 +197,14 @@ std::vector<Url> HaBroker::getKnownBrokers() const { return knownBrokers; } -void HaBroker::shutdown() { - QPID_LOG(critical, logPrefix << "Critical error, shutting down."); +void HaBroker::shutdown(const std::string& message) { + QPID_LOG(critical, message); broker.shutdown(); + throw Exception(message); } BrokerStatus HaBroker::getStatus() const { - Mutex::ScopedLock l(lock); - return status; -} - -void HaBroker::setStatus(BrokerStatus newStatus) { - Mutex::ScopedLock l(lock); - setStatus(newStatus, l); -} - -namespace { -bool checkTransition(BrokerStatus from, BrokerStatus to) { - // Legal state transitions. Initial state is JOINING, ACTIVE is terminal. - static const BrokerStatus TRANSITIONS[][2] = { - { JOINING, CATCHUP }, // Connected to primary - { JOINING, RECOVERING }, // Chosen as initial primary. - { CATCHUP, READY }, // Caught up all queues, ready to take over. - { READY, RECOVERING }, // Chosen as new primary - { READY, CATCHUP }, // Timed out failing over, demoted to catch-up. - { RECOVERING, ACTIVE } // All expected backups are ready - }; - static const size_t N = sizeof(TRANSITIONS)/sizeof(TRANSITIONS[0]); - for (size_t i = 0; i < N; ++i) { - if (TRANSITIONS[i][0] == from && TRANSITIONS[i][1] == to) - return true; - } - return false; -} -} // namespace - -void HaBroker::setStatus(BrokerStatus newStatus, Mutex::ScopedLock& l) { - QPID_LOG(info, logPrefix << "Status change: " - << printable(status) << " -> " << printable(newStatus)); - bool legal = checkTransition(status, newStatus); - assert(legal); - if (!legal) { - QPID_LOG(critical, logPrefix << "Illegal state transition: " - << printable(status) << " -> " << printable(newStatus)); - shutdown(); - } - status = newStatus; - statusChanged(l); -} - -void HaBroker::statusChanged(Mutex::ScopedLock& l) { - mgmtObject->set_status(printable(status).str()); - brokerInfo.setStatus(status); - setLinkProperties(l); -} - -void HaBroker::membershipUpdated(Mutex::ScopedLock&) { - QPID_LOG(info, logPrefix << "Membership changed: " << membership); - Variant::List brokers = membership.asList(); - mgmtObject->set_members(brokers); - broker.getManagementAgent()->raiseEvent(_qmf::EventMembersUpdate(brokers)); -} - -void HaBroker::setMembership(const Variant::List& brokers) { - Mutex::ScopedLock l(lock); - membership.assign(brokers); - QPID_LOG(info, logPrefix << "Membership update: " << membership); - BrokerInfo info; - // Update my status to what the primary says it is. The primary can toggle - // status between READY and CATCHUP based on the state of our subscriptions. - if (membership.get(systemId, info) && status != info.getStatus()) { - setStatus(info.getStatus(), l); - if (backup.get()) backup->setStatus(status); - } - membershipUpdated(l); -} - -void HaBroker::resetMembership(const BrokerInfo& b) { - Mutex::ScopedLock l(lock); - membership.reset(b); - QPID_LOG(debug, logPrefix << "Membership reset to: " << membership); - membershipUpdated(l); -} - -void HaBroker::addBroker(const BrokerInfo& b) { - Mutex::ScopedLock l(lock); - membership.add(b); - QPID_LOG(debug, logPrefix << "Membership add: " << b); - membershipUpdated(l); -} - -void HaBroker::removeBroker(const Uuid& id) { - Mutex::ScopedLock l(lock); - membership.remove(id); - QPID_LOG(debug, logPrefix << "Membership remove: " << id); - membershipUpdated(l); -} - -void HaBroker::setLinkProperties(Mutex::ScopedLock&) { - framing::FieldTable linkProperties = broker.getLinkClientProperties(); - if (isBackup(status)) { - // If this is a backup then any outgoing links are backup - // links and need to be tagged. - linkProperties.setTable(ConnectionObserver::BACKUP_TAG, brokerInfo.asFieldTable()); - } - else { - // If this is a primary then any outgoing links are federation links - // and should not be tagged. - linkProperties.erase(ConnectionObserver::BACKUP_TAG); - } - broker.setLinkClientProperties(linkProperties); + return membership.getStatus(); } }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/HaBroker.h b/cpp/src/qpid/ha/HaBroker.h index 7dabe6e35b..6b15c88e0a 100644 --- a/cpp/src/qpid/ha/HaBroker.h +++ b/cpp/src/qpid/ha/HaBroker.h @@ -25,14 +25,12 @@ #include "BrokerInfo.h" #include "Membership.h" #include "types.h" -#include "ReplicationTest.h" #include "Settings.h" #include "qpid/Url.h" #include "qpid/sys/Mutex.h" #include "qmf/org/apache/qpid/ha/HaBroker.h" #include "qpid/management/Manageable.h" #include "qpid/types/Variant.h" -#include <memory> #include <set> #include <boost/shared_ptr.hpp> @@ -54,11 +52,15 @@ namespace ha { class Backup; class ConnectionObserver; class Primary; - +class Role; /** * HA state and actions associated with a HA broker. Holds all the management info. * * THREAD SAFE: may be called in arbitrary broker IO or timer threads. + + * NOTE: HaBroker and Role subclasses follow this lock hierarchy: + * - HaBroker MUST NOT hold its own lock across calls Role subclasses. + * - Role subclasses MAY hold their locks accross calls to HaBroker. */ class HaBroker : public management::Manageable { @@ -71,66 +73,46 @@ class HaBroker : public management::Manageable void initialize(); // Implement Manageable. - qpid::management::ManagementObject* GetManagementObject() const { return mgmtObject; } + qpid::management::ManagementObject::shared_ptr GetManagementObject() const { return mgmtObject; } management::Manageable::status_t ManagementMethod ( uint32_t methodId, management::Args& args, std::string& text); broker::Broker& getBroker() { return broker; } const Settings& getSettings() const { return settings; } - /** Shut down the broker. Caller should log a critical error message. */ - void shutdown(); + /** Shut down the broker because of a critical error. */ + void shutdown(const std::string& message); BrokerStatus getStatus() const; - void setStatus(BrokerStatus); - void activate(); - - Backup* getBackup() { return backup.get(); } - ReplicationTest getReplicationTest() const { return replicationTest; } - boost::shared_ptr<ConnectionObserver> getObserver() { return observer; } - const BrokerInfo& getBrokerInfo() const { return brokerInfo; } - - void setMembership(const types::Variant::List&); // Set membership from list. - void resetMembership(const BrokerInfo& b); // Reset to contain just one member. - void addBroker(const BrokerInfo& b); // Add a broker to the membership. - void removeBroker(const types::Uuid& id); // Remove a broker from membership. - + BrokerInfo getBrokerInfo() const { return membership.getInfo(); } + Membership& getMembership() { return membership; } types::Uuid getSystemId() const { return systemId; } private: - void setClientUrl(const Url&); + + void setPublicUrl(const Url&); void setBrokerUrl(const Url&); void updateClientUrl(sys::Mutex::ScopedLock&); - bool isPrimary(sys::Mutex::ScopedLock&) { return !backup.get(); } - - void setStatus(BrokerStatus, sys::Mutex::ScopedLock&); - void recover(); - void statusChanged(sys::Mutex::ScopedLock&); - void setLinkProperties(sys::Mutex::ScopedLock&); - std::vector<Url> getKnownBrokers() const; - void membershipUpdated(sys::Mutex::ScopedLock&); - - std::string logPrefix; - broker::Broker& broker; - types::Uuid systemId; + // Immutable members + const types::Uuid systemId; const Settings settings; + // Member variables protected by lock mutable sys::Mutex lock; - boost::shared_ptr<ConnectionObserver> observer; // Used by Backup and Primary - std::auto_ptr<Backup> backup; - std::auto_ptr<Primary> primary; - qmf::org::apache::qpid::ha::HaBroker* mgmtObject; - Url clientUrl, brokerUrl; + Url publicUrl, brokerUrl; std::vector<Url> knownBrokers; - BrokerStatus status; - BrokerInfo brokerInfo; + + // Independently thread-safe member variables + broker::Broker& broker; + qmf::org::apache::qpid::ha::HaBroker::shared_ptr mgmtObject; + boost::shared_ptr<ConnectionObserver> observer; // Used by Backup and Primary + boost::shared_ptr<Role> role; Membership membership; - ReplicationTest replicationTest; }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/HaPlugin.cpp b/cpp/src/qpid/ha/HaPlugin.cpp index f7fe553d9b..d26b466847 100644 --- a/cpp/src/qpid/ha/HaPlugin.cpp +++ b/cpp/src/qpid/ha/HaPlugin.cpp @@ -33,9 +33,11 @@ struct Options : public qpid::Options { addOptions() ("ha-cluster", optValue(settings.cluster, "yes|no"), "Join a HA active/passive cluster.") + ("ha-queue-replication", optValue(settings.queueReplication, "yes|no"), + "Enable replication of specific queues without joining a cluster") ("ha-brokers-url", optValue(settings.brokerUrl,"URL"), "URL with address of each broker in the cluster.") - ("ha-public-url", optValue(settings.clientUrl,"URL"), + ("ha-public-url", optValue(settings.publicUrl,"URL"), "URL advertized to clients to connect to the cluster.") ("ha-replicate", optValue(settings.replicateDefault, "LEVEL"), @@ -48,6 +50,10 @@ struct Options : public qpid::Options { "Authentication mechanism for connections between HA brokers") ("ha-backup-timeout", optValue(settings.backupTimeout, "SECONDS"), "Maximum time to wait for an expected backup to connect and become ready.") + ("ha-flow-messages", optValue(settings.flowMessages, "N"), + "Flow control message count limit for replication, 0 means no limit") + ("ha-flow-bytes", optValue(settings.flowBytes, "N"), + "Flow control byte limit for replication, 0 means no limit") ; } }; @@ -64,17 +70,23 @@ struct HaPlugin : public Plugin { void earlyInitialize(Plugin::Target& target) { broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); - if (broker) { - // Must create the HaBroker in earlyInitialize so it can set up its - // connection observer before clients start connecting. - haBroker.reset(new ha::HaBroker(*broker, settings)); - broker->addFinalizer(boost::bind(&HaPlugin::finalize, this)); + if (broker && (settings.cluster || settings.queueReplication)) { + if (!broker->getManagementAgent()) { + QPID_LOG(info, "HA plugin disabled because management is disabled"); + if (settings.cluster) + throw Exception("Cannot start HA: management is disabled"); + } else { + // Must create the HaBroker in earlyInitialize so it can set up its + // connection observer before clients start connecting. + haBroker.reset(new ha::HaBroker(*broker, settings)); + broker->addFinalizer(boost::bind(&HaPlugin::finalize, this)); + } } } void initialize(Plugin::Target& target) { broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); - if (broker) haBroker->initialize(); + if (broker && haBroker.get()) haBroker->initialize(); } void finalize() { diff --git a/cpp/src/qpid/ha/Membership.cpp b/cpp/src/qpid/ha/Membership.cpp index 74580f9b1e..6c64d86fd7 100644 --- a/cpp/src/qpid/ha/Membership.cpp +++ b/cpp/src/qpid/ha/Membership.cpp @@ -19,6 +19,12 @@ * */ #include "Membership.h" +#include "HaBroker.h" +#include "qpid/broker/Broker.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/types/Variant.h" +#include "qmf/org/apache/qpid/ha/EventMembersUpdate.h" +#include "qmf/org/apache/qpid/ha/HaBroker.h" #include <boost/bind.hpp> #include <iostream> #include <iterator> @@ -26,37 +32,58 @@ namespace qpid { namespace ha { +namespace _qmf = ::qmf::org::apache::qpid::ha; -void Membership::reset(const BrokerInfo& b) { +using sys::Mutex; +using types::Variant; + +Membership::Membership(const BrokerInfo& info, HaBroker& b) + : haBroker(b), self(info.getSystemId()) +{ + brokers[self] = info; +} + +void Membership::clear() { + Mutex::ScopedLock l(lock); + BrokerInfo me = brokers[self]; brokers.clear(); - brokers[b.getSystemId()] = b; + brokers[self] = me; } void Membership::add(const BrokerInfo& b) { + Mutex::ScopedLock l(lock); brokers[b.getSystemId()] = b; + update(l); } void Membership::remove(const types::Uuid& id) { + Mutex::ScopedLock l(lock); + if (id == self) return; // Never remove myself BrokerInfo::Map::iterator i = brokers.find(id); if (i != brokers.end()) { brokers.erase(i); - } + update(l); + } } bool Membership::contains(const types::Uuid& id) { + Mutex::ScopedLock l(lock); return brokers.find(id) != brokers.end(); } void Membership::assign(const types::Variant::List& list) { - brokers.clear(); + Mutex::ScopedLock l(lock); + clear(); for (types::Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) { BrokerInfo b(i->asMap()); brokers[b.getSystemId()] = b; } + update(l); } types::Variant::List Membership::asList() const { + Mutex::ScopedLock l(lock); types::Variant::List list; for (BrokerInfo::Map::const_iterator i = brokers.begin(); i != brokers.end(); ++i) list.push_back(i->second.asMap()); @@ -64,6 +91,7 @@ types::Variant::List Membership::asList() const { } BrokerInfo::Set Membership::otherBackups() const { + Mutex::ScopedLock l(lock); BrokerInfo::Set result; for (BrokerInfo::Map::const_iterator i = brokers.begin(); i != brokers.end(); ++i) if (i->second.getStatus() == READY && i->second.getSystemId() != self) @@ -71,15 +99,84 @@ BrokerInfo::Set Membership::otherBackups() const { return result; } -bool Membership::get(const types::Uuid& id, BrokerInfo& result) { - BrokerInfo::Map::iterator i = brokers.find(id); +bool Membership::get(const types::Uuid& id, BrokerInfo& result) const { + Mutex::ScopedLock l(lock); + BrokerInfo::Map::const_iterator i = brokers.find(id); if (i == brokers.end()) return false; result = i->second; return true; } -std::ostream& operator<<(std::ostream& o, const Membership& members) { - return o << members.brokers; +void Membership::update(Mutex::ScopedLock& l) { + QPID_LOG(info, "Membership: " << brokers); + Variant::List brokers = asList(); + if (mgmtObject) mgmtObject->set_status(printable(getStatus(l)).str()); + if (mgmtObject) mgmtObject->set_members(brokers); + haBroker.getBroker().getManagementAgent()->raiseEvent( + _qmf::EventMembersUpdate(brokers)); +} + +void Membership::setMgmtObject(boost::shared_ptr<_qmf::HaBroker> mo) { + Mutex::ScopedLock l(lock); + mgmtObject = mo; + update(l); +} + + +namespace { +bool checkTransition(BrokerStatus from, BrokerStatus to) { + // Legal state transitions. Initial state is JOINING, ACTIVE is terminal. + static const BrokerStatus TRANSITIONS[][2] = { + { STANDALONE, JOINING }, // Initialization of backup broker + { JOINING, CATCHUP }, // Connected to primary + { JOINING, RECOVERING }, // Chosen as initial primary. + { CATCHUP, READY }, // Caught up all queues, ready to take over. + { READY, RECOVERING }, // Chosen as new primary + { READY, CATCHUP }, // Timed out failing over, demoted to catch-up. + { RECOVERING, ACTIVE } // All expected backups are ready + }; + static const size_t N = sizeof(TRANSITIONS)/sizeof(TRANSITIONS[0]); + for (size_t i = 0; i < N; ++i) { + if (TRANSITIONS[i][0] == from && TRANSITIONS[i][1] == to) + return true; + } + return false; +} +} // namespace + +void Membership::setStatus(BrokerStatus newStatus) { + BrokerStatus status = getStatus(); + QPID_LOG(info, "Status change: " + << printable(status) << " -> " << printable(newStatus)); + bool legal = checkTransition(status, newStatus); + if (!legal) { + haBroker.shutdown(QPID_MSG("Illegal state transition: " << printable(status) + << " -> " << printable(newStatus))); + } + + Mutex::ScopedLock l(lock); + brokers[self].setStatus(newStatus); + if (mgmtObject) mgmtObject->set_status(printable(newStatus).str()); + update(l); +} + +BrokerStatus Membership::getStatus() const { + Mutex::ScopedLock l(lock); + return getStatus(l); +} + +BrokerStatus Membership::getStatus(sys::Mutex::ScopedLock&) const { + BrokerInfo::Map::const_iterator i = brokers.find(self); + assert(i != brokers.end()); + return i->second.getStatus(); +} + +BrokerInfo Membership::getInfo() const { + Mutex::ScopedLock l(lock); + BrokerInfo::Map::const_iterator i = brokers.find(self); + assert(i != brokers.end()); + return i->second; } +// FIXME aconway 2013-01-23: move to .h? }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/Membership.h b/cpp/src/qpid/ha/Membership.h index 8406dccd5d..956569fbd8 100644 --- a/cpp/src/qpid/ha/Membership.h +++ b/cpp/src/qpid/ha/Membership.h @@ -24,45 +24,72 @@ #include "BrokerInfo.h" #include "types.h" -#include "qpid/framing/Uuid.h" #include "qpid/log/Statement.h" +#include "qpid/sys/Mutex.h" #include "qpid/types/Variant.h" #include <boost/function.hpp> #include <set> #include <vector> #include <iosfwd> + +namespace qmf { namespace org { namespace apache { namespace qpid { namespace ha { +class HaBroker; +}}}}} + namespace qpid { + +namespace broker { +class Broker; +} + +namespace types { +class Uuid; +} + namespace ha { +class HaBroker; /** * Keep track of the brokers in the membership. - * THREAD UNSAFE: caller must serialize + * Send management when events on membership changes. + * THREAD SAFE */ class Membership { public: - Membership(const types::Uuid& self_) : self(self_) {} + Membership(const BrokerInfo& info, HaBroker&); - void reset(const BrokerInfo& b); ///< Reset to contain just one member. + void setMgmtObject(boost::shared_ptr<qmf::org::apache::qpid::ha::HaBroker>); + + void clear(); ///< Clear all but self. void add(const BrokerInfo& b); void remove(const types::Uuid& id); bool contains(const types::Uuid& id); + /** Return IDs of all READY backups other than self */ BrokerInfo::Set otherBackups() const; void assign(const types::Variant::List&); types::Variant::List asList() const; - bool get(const types::Uuid& id, BrokerInfo& result); + bool get(const types::Uuid& id, BrokerInfo& result) const; + + types::Uuid getSelf() const { return self; } + BrokerInfo getInfo() const; + BrokerStatus getStatus() const; + void setStatus(BrokerStatus s); private: - types::Uuid self; + void update(sys::Mutex::ScopedLock&); + BrokerStatus getStatus(sys::Mutex::ScopedLock&) const; + + mutable sys::Mutex lock; + HaBroker& haBroker; + boost::shared_ptr<qmf::org::apache::qpid::ha::HaBroker> mgmtObject; + const types::Uuid self; BrokerInfo::Map brokers; - friend std::ostream& operator<<(std::ostream&, const Membership&); }; -std::ostream& operator<<(std::ostream&, const Membership&); - }} // namespace qpid::ha #endif /*!QPID_HA_MEMBERSHIP_H*/ diff --git a/cpp/src/qpid/ha/Primary.cpp b/cpp/src/qpid/ha/Primary.cpp index e4bf9671b8..93dbbbea85 100644 --- a/cpp/src/qpid/ha/Primary.cpp +++ b/cpp/src/qpid/ha/Primary.cpp @@ -31,6 +31,8 @@ #include "qpid/broker/Connection.h" #include "qpid/broker/Queue.h" #include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/Uuid.h" #include "qpid/log/Statement.h" #include "qpid/sys/Timer.h" #include <boost/bind.hpp> @@ -39,6 +41,8 @@ namespace qpid { namespace ha { using sys::Mutex; +using namespace std; +using namespace framing; namespace { @@ -58,6 +62,8 @@ class PrimaryConfigurationObserver : public broker::ConfigurationObserver PrimaryConfigurationObserver(Primary& p) : primary(p) {} void queueCreate(const Primary::QueuePtr& q) { primary.queueCreate(q); } void queueDestroy(const Primary::QueuePtr& q) { primary.queueDestroy(q); } + void exchangeCreate(const Primary::ExchangePtr& q) { primary.exchangeCreate(q); } + void exchangeDestroy(const Primary::ExchangePtr& q) { primary.exchangeDestroy(q); } private: Primary& primary; }; @@ -76,8 +82,11 @@ class ExpectedBackupTimerTask : public sys::TimerTask { Primary* Primary::instance = 0; Primary::Primary(HaBroker& hb, const BrokerInfo::Set& expect) : - haBroker(hb), logPrefix("Primary: "), active(false) + haBroker(hb), membership(hb.getMembership()), + logPrefix("Primary: "), active(false), + replicationTest(hb.getSettings().replicateDefault.get()) { + hb.getMembership().setStatus(RECOVERING); assert(instance == 0); instance = this; // Let queue replicators find us. if (expect.empty()) { @@ -89,11 +98,10 @@ Primary::Primary(HaBroker& hb, const BrokerInfo::Set& expect) : // the QueueGuards are created. QPID_LOG(notice, logPrefix << "Promoted to primary. Expected backups: " << expect); for (BrokerInfo::Set::const_iterator i = expect.begin(); i != expect.end(); ++i) { - boost::shared_ptr<RemoteBackup> backup( - new RemoteBackup(*i, haBroker.getReplicationTest(), false)); + boost::shared_ptr<RemoteBackup> backup(new RemoteBackup(*i, 0)); backups[i->getSystemId()] = backup; if (!backup->isReady()) expectedBackups.insert(backup); - backup->setInitialQueues(hb.getBroker().getQueues(), true); // Create guards + backup->setCatchupQueues(hb.getBroker().getQueues(), true); // Create guards } // Set timeout for expected brokers to connect and become ready. sys::Duration timeout(int64_t(hb.getSettings().backupTimeout*sys::TIME_SEC)); @@ -102,14 +110,21 @@ Primary::Primary(HaBroker& hb, const BrokerInfo::Set& expect) : hb.getBroker().getTimer().add(timerTask); } + + // Remove backup tag property from outgoing link properties. + framing::FieldTable linkProperties = hb.getBroker().getLinkClientProperties(); + linkProperties.erase(ConnectionObserver::BACKUP_TAG); + hb.getBroker().setLinkClientProperties(linkProperties); + configurationObserver.reset(new PrimaryConfigurationObserver(*this)); haBroker.getBroker().getConfigurationObservers().add(configurationObserver); Mutex::ScopedLock l(lock); // We are now active as a configurationObserver checkReady(l); + // Allow client connections connectionObserver.reset(new PrimaryConnectionObserver(*this)); - haBroker.getObserver()->setObserver(connectionObserver); + haBroker.getObserver()->setObserver(connectionObserver, logPrefix); } Primary::~Primary() { @@ -122,7 +137,7 @@ void Primary::checkReady(Mutex::ScopedLock&) { active = true; Mutex::ScopedUnlock u(lock); // Don't hold lock across callback QPID_LOG(notice, logPrefix << "Finished waiting for backups, primary is active."); - haBroker.activate(); + membership.setStatus(ACTIVE); } } @@ -130,7 +145,7 @@ void Primary::checkReady(BackupMap::iterator i, Mutex::ScopedLock& l) { if (i != backups.end() && i->second->reportReady()) { BrokerInfo info = i->second->getBrokerInfo(); info.setStatus(READY); - haBroker.addBroker(info); + membership.add(info); if (expectedBackups.erase(i->second)) { QPID_LOG(info, logPrefix << "Expected backup is ready: " << info); checkReady(l); @@ -155,9 +170,10 @@ void Primary::timeoutExpectedBackups() { expectedBackups.erase(i++); backups.erase(info.getSystemId()); rb->cancel(); - // Downgrade the broker to CATCHUP + // Downgrade the broker's status to CATCHUP + // The broker will get this status change when it eventually connects. info.setStatus(CATCHUP); - haBroker.addBroker(info); + membership.add(info); } else ++i; } @@ -178,46 +194,78 @@ void Primary::readyReplica(const ReplicatingSubscription& rs) { } } +// NOTE: Called with queue registry lock held. void Primary::queueCreate(const QueuePtr& q) { - // Throw if there is an invalid replication level in the queue settings. - haBroker.getReplicationTest().replicateLevel(q->getSettings().storeSettings); - Mutex::ScopedLock l(lock); - for (BackupMap::iterator i = backups.begin(); i != backups.end(); ++i) { - i->second->queueCreate(q); - checkReady(i, l); + // Set replication argument. + ReplicateLevel level = replicationTest.useLevel(*q); + QPID_LOG(debug, logPrefix << "Created queue " << q->getName() + << " replication: " << printable(level)); + q->addArgument(QPID_REPLICATE, printable(level).str()); + if (level) { + // Give each queue a unique id to avoid confusion of same-named queues. + q->addArgument(QPID_HA_UUID, types::Variant(Uuid(true))); + Mutex::ScopedLock l(lock); + for (BackupMap::iterator i = backups.begin(); i != backups.end(); ++i) { + i->second->queueCreate(q); + checkReady(i, l); + } } } +// NOTE: Called with queue registry lock held. void Primary::queueDestroy(const QueuePtr& q) { + QPID_LOG(debug, logPrefix << "Destroyed queue " << q->getName()); Mutex::ScopedLock l(lock); for (BackupMap::iterator i = backups.begin(); i != backups.end(); ++i) i->second->queueDestroy(q); checkReady(l); } +// NOTE: Called with exchange registry lock held. +void Primary::exchangeCreate(const ExchangePtr& ex) { + ReplicateLevel level = replicationTest.useLevel(*ex); + QPID_LOG(debug, logPrefix << "Created exchange " << ex->getName() + << " replication: " << printable(level)); + FieldTable args = ex->getArgs(); + args.setString(QPID_REPLICATE, printable(level).str()); // Set replication arg. + if (level) { + // Give each exchange a unique id to avoid confusion of same-named exchanges. + args.set(QPID_HA_UUID, FieldTable::ValuePtr(new UuidValue(&Uuid(true)[0]))); + } + ex->setArgs(args); +} + +// NOTE: Called with exchange registry lock held. +void Primary::exchangeDestroy(const ExchangePtr& ex) { + QPID_LOG(debug, logPrefix << "Destroyed exchange " << ex->getName()); + // Do nothing + } + void Primary::opened(broker::Connection& connection) { BrokerInfo info; if (ha::ConnectionObserver::getBrokerInfo(connection, info)) { Mutex::ScopedLock l(lock); BackupMap::iterator i = backups.find(info.getSystemId()); if (i == backups.end()) { - QPID_LOG(debug, logPrefix << "New backup connected: " << info); - boost::shared_ptr<RemoteBackup> backup( - new RemoteBackup(info, haBroker.getReplicationTest(), true)); + QPID_LOG(info, logPrefix << "New backup connected: " << info); + boost::shared_ptr<RemoteBackup> backup(new RemoteBackup(info, &connection)); { // Avoid deadlock with queue registry lock. Mutex::ScopedUnlock u(lock); - backup->setInitialQueues(haBroker.getBroker().getQueues(), false); + backup->setCatchupQueues(haBroker.getBroker().getQueues(), false); } backups[info.getSystemId()] = backup; + i = backups.find(info.getSystemId()); } else { - QPID_LOG(debug, logPrefix << "Known backup connected: " << info); - i->second->setConnected(true); - checkReady(i, l); + QPID_LOG(info, logPrefix << "Known backup connected: " << info); + i->second->setConnection(&connection); } - if (info.getStatus() == JOINING) info.setStatus(CATCHUP); - haBroker.addBroker(info); + if (info.getStatus() == JOINING) { + info.setStatus(CATCHUP); + membership.add(info); + } + if (i != backups.end()) checkReady(i, l); } else QPID_LOG(debug, logPrefix << "Accepted client connection " @@ -225,19 +273,20 @@ void Primary::opened(broker::Connection& connection) { } void Primary::closed(broker::Connection& connection) { - // NOTE: It is possible for a backup connection to be rejected while we are - // a backup, but closed() is called after we have become primary. - // - // For this reason we do not remove from the backups map here, the backups - // map holds all the backups we know about whether connected or not. - // - Mutex::ScopedLock l(lock); BrokerInfo info; if (ha::ConnectionObserver::getBrokerInfo(connection, info)) { - QPID_LOG(debug, logPrefix << "Backup disconnected: " << info); - haBroker.removeBroker(info.getSystemId()); + Mutex::ScopedLock l(lock); BackupMap::iterator i = backups.find(info.getSystemId()); - if (i != backups.end()) i->second->setConnected(false); + // NOTE: It is possible for a backup connection to be rejected while we + // are a backup, but closed() is called after we have become primary. + // Checking isConnected() lets us ignore such spurious closes. + if (i != backups.end() && i->second->isConnected()) { + QPID_LOG(info, logPrefix << "Backup disconnected: " << info); + membership.remove(info.getSystemId()); + expectedBackups.erase(i->second); + backups.erase(i); + checkReady(l); + } } } @@ -249,4 +298,9 @@ boost::shared_ptr<QueueGuard> Primary::getGuard(const QueuePtr& q, const BrokerI return i == backups.end() ? boost::shared_ptr<QueueGuard>() : i->second->guard(q); } +Role* Primary::promote() { + QPID_LOG(info, "Ignoring promotion, already primary: " << haBroker.getBrokerInfo()); + return 0; +} + }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/Primary.h b/cpp/src/qpid/ha/Primary.h index 26883f4416..ff85837882 100644 --- a/cpp/src/qpid/ha/Primary.h +++ b/cpp/src/qpid/ha/Primary.h @@ -24,6 +24,8 @@ #include "types.h" #include "BrokerInfo.h" +#include "ReplicationTest.h" +#include "Role.h" #include "qpid/sys/Mutex.h" #include <boost/shared_ptr.hpp> #include <boost/intrusive_ptr.hpp> @@ -48,6 +50,7 @@ class HaBroker; class ReplicatingSubscription; class RemoteBackup; class QueueGuard; +class Membership; /** * State associated with a primary broker: @@ -56,22 +59,30 @@ class QueueGuard; * * THREAD SAFE: called concurrently in arbitrary connection threads. */ -class Primary +class Primary : public Role { public: typedef boost::shared_ptr<broker::Queue> QueuePtr; + typedef boost::shared_ptr<broker::Exchange> ExchangePtr; static Primary* get() { return instance; } Primary(HaBroker& hb, const BrokerInfo::Set& expectedBackups); ~Primary(); + // Role implementation + std::string getLogPrefix() const { return logPrefix; } + Role* promote(); + void setBrokerUrl(const Url&) {} + void readyReplica(const ReplicatingSubscription&); void removeReplica(const std::string& q); // Called via ConfigurationObserver void queueCreate(const QueuePtr&); void queueDestroy(const QueuePtr&); + void exchangeCreate(const ExchangePtr&); + void exchangeDestroy(const ExchangePtr&); // Called via ConnectionObserver void opened(broker::Connection& connection); @@ -91,17 +102,19 @@ class Primary sys::Mutex lock; HaBroker& haBroker; + Membership& membership; std::string logPrefix; bool active; + ReplicationTest replicationTest; + /** * Set of expected backups that must be ready before we declare ourselves - * active + * active. These are backups that were known and ready before the primary + * crashed. As new primary we expect them to re-connect. */ BackupSet expectedBackups; /** - * Map of all the remote backups we know about: any expected backups plus - * all actual backups that have connected. We do not remove entries when a - * backup disconnects. @see Primary::closed() + * Map of all the expected backups plus all connected backups. */ BackupMap backups; boost::shared_ptr<broker::ConnectionObserver> connectionObserver; diff --git a/cpp/src/qpid/ha/QueueGuard.cpp b/cpp/src/qpid/ha/QueueGuard.cpp index 77e1f81a38..d06d88ca29 100644 --- a/cpp/src/qpid/ha/QueueGuard.cpp +++ b/cpp/src/qpid/ha/QueueGuard.cpp @@ -50,10 +50,10 @@ class QueueGuard::QueueObserver : public broker::QueueObserver QueueGuard::QueueGuard(broker::Queue& q, const BrokerInfo& info) - : queue(q), subscription(0) + : cancelled(false), queue(q), subscription(0) { std::ostringstream os; - os << "Primary guard " << queue.getName() << "@" << info.getLogId() << ": "; + os << "Primary guard " << queue.getName() << "@" << info << ": "; logPrefix = os.str(); observer.reset(new QueueObserver(*this)); queue.addObserver(observer); @@ -66,45 +66,31 @@ QueueGuard::~QueueGuard() { cancel(); } // NOTE: Called with message lock held. void QueueGuard::enqueued(const Message& m) { // Delay completion - QPID_LOG(trace, logPrefix << "Delayed completion of " << m); + QPID_LOG(trace, logPrefix << "Delayed completion of " << m.getSequence()); + Mutex::ScopedLock l(lock); + if (cancelled) return; // Don't record enqueues after we are cancelled. + assert(delayed.find(m.getSequence()) == delayed.end()); + delayed[m.getSequence()] = m.getIngressCompletion(); m.getIngressCompletion()->startCompleter(); - { - Mutex::ScopedLock l(lock); - if (!delayed.insert(Delayed::value_type(m.getSequence(), m.getIngressCompletion())).second) { - QPID_LOG(critical, logPrefix << "Second enqueue for message with sequence " << m.getSequence()); - assert(false); - } - } } // NOTE: Called with message lock held. void QueueGuard::dequeued(const Message& m) { QPID_LOG(trace, logPrefix << "Dequeued " << m); - ReplicatingSubscription* rs=0; - { - Mutex::ScopedLock l(lock); - rs = subscription; - } - if (rs) rs->dequeued(m); - complete(m.getSequence()); -} - -void QueueGuard::completeRange(Delayed::iterator begin, Delayed::iterator end) { - for (Delayed::iterator i = begin; i != end; ++i) { - QPID_LOG(trace, logPrefix << "Completed " << i->first); - i->second->finishCompleter(); - } + Mutex::ScopedLock l(lock); + if (subscription) subscription->dequeued(m); + complete(m.getSequence(), l); } void QueueGuard::cancel() { queue.removeObserver(observer); - Delayed removed; - { - Mutex::ScopedLock l(lock); - if (delayed.empty()) return; // No need if no delayed messages. - delayed.swap(removed); + Mutex::ScopedLock l(lock); + if (cancelled) return; + cancelled = true; + for (Delayed::iterator i = delayed.begin(); i != delayed.end();) { + complete(i, l); + delayed.erase(i++); } - completeRange(removed.begin(), removed.end()); } void QueueGuard::attach(ReplicatingSubscription& rs) { @@ -113,38 +99,36 @@ void QueueGuard::attach(ReplicatingSubscription& rs) { } bool QueueGuard::subscriptionStart(SequenceNumber position) { - Delayed removed; - { - Mutex::ScopedLock l(lock); - // Complete any messages before or at the ReplicatingSubscription start position. - // Those messages are already on the backup. - for (Delayed::iterator i = delayed.begin(); i != delayed.end() && i->first <= position;) { - removed.insert(*i); - delayed.erase(i++); - } + // Complete any messages before or at the ReplicatingSubscription start position. + // Those messages are already on the backup. + Mutex::ScopedLock l(lock); + Delayed::iterator i = delayed.begin(); + while(i != delayed.end() && i->first <= position) { + complete(i, l); + delayed.erase(i++); } - completeRange(removed.begin(), removed.end()); return position >= range.back; } void QueueGuard::complete(SequenceNumber sequence) { - boost::intrusive_ptr<broker::AsyncCompletion> m; - { - Mutex::ScopedLock l(lock); - // The same message can be completed twice, by - // ReplicatingSubscription::acknowledged and dequeued. Remove it - // from the map so we only call finishCompleter() once - Delayed::iterator i = delayed.find(sequence); - if (i != delayed.end()) { - m = i->second; - delayed.erase(i); - } + Mutex::ScopedLock l(lock); + complete(sequence, l); +} +void QueueGuard::complete(SequenceNumber sequence, Mutex::ScopedLock& l) { + // The same message can be completed twice, by + // ReplicatingSubscription::acknowledged and dequeued. Remove it + // from the map so we only call finishCompleter() once + Delayed::iterator i = delayed.find(sequence); + if (i != delayed.end()) { + complete(i, l); + delayed.erase(i); } - if (m) { - QPID_LOG(trace, logPrefix << "Completed " << sequence); - m->finishCompleter(); - } +} + +void QueueGuard::complete(Delayed::iterator i, Mutex::ScopedLock&) { + QPID_LOG(trace, logPrefix << "Completed " << i->first); + i->second->finishCompleter(); } }} // namespaces qpid::ha diff --git a/cpp/src/qpid/ha/QueueGuard.h b/cpp/src/qpid/ha/QueueGuard.h index 3904b3bd3f..e7ceb351e8 100644 --- a/cpp/src/qpid/ha/QueueGuard.h +++ b/cpp/src/qpid/ha/QueueGuard.h @@ -54,6 +54,9 @@ class ReplicatingSubscription; * THREAD SAFE: Concurrent calls: * - enqueued() via QueueObserver in arbitrary connection threads. * - attach(), cancel(), complete() from ReplicatingSubscription in subscription thread. + * + * Lock Hierarchy: ReplicatingSubscription MUS NOT call QueueGuard with it's lock held + * QueueGuard MAY call ReplicatingSubscription with it's lock held. */ class QueueGuard { public: @@ -104,17 +107,20 @@ class QueueGuard { private: class QueueObserver; + typedef std::map<framing::SequenceNumber, + boost::intrusive_ptr<broker::AsyncCompletion> > Delayed; + + void complete(framing::SequenceNumber, sys::Mutex::ScopedLock &); + void complete(Delayed::iterator, sys::Mutex::ScopedLock &); sys::Mutex lock; + bool cancelled; std::string logPrefix; broker::Queue& queue; - typedef std::map<framing::SequenceNumber, boost::intrusive_ptr<broker::AsyncCompletion> > Delayed; Delayed delayed; ReplicatingSubscription* subscription; boost::shared_ptr<QueueObserver> observer; QueueRange range; - - void completeRange(Delayed::iterator begin, Delayed::iterator end); }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/QueueRange.h b/cpp/src/qpid/ha/QueueRange.h index d734326910..f67ac146e6 100644 --- a/cpp/src/qpid/ha/QueueRange.h +++ b/cpp/src/qpid/ha/QueueRange.h @@ -24,6 +24,7 @@ #include "ReplicatingSubscription.h" #include "qpid/broker/Queue.h" +#include "qpid/broker/QueueCursor.h" #include "qpid/framing/FieldTable.h" #include "qpid/framing/SequenceNumber.h" #include <iostream> @@ -51,15 +52,7 @@ struct QueueRange { QueueRange() : front(1), back(0) { } // Empty range. - QueueRange(broker::Queue& q) { - if (ReplicatingSubscription::getFront(q, front)) - back = q.getPosition(); - else { - back = q.getPosition(); - front = back+1; // empty - } - assert(front <= back + 1); - } + QueueRange(broker::Queue& q) { q.getRange(front, back, broker::REPLICATOR); } QueueRange(const framing::FieldTable& args) { back = args.getAsInt(ReplicatingSubscription::QPID_BACK); diff --git a/cpp/src/qpid/ha/QueueReplicator.cpp b/cpp/src/qpid/ha/QueueReplicator.cpp index cac1fdac29..98220b2098 100644 --- a/cpp/src/qpid/ha/QueueReplicator.cpp +++ b/cpp/src/qpid/ha/QueueReplicator.cpp @@ -22,12 +22,15 @@ #include "HaBroker.h" #include "QueueReplicator.h" #include "ReplicatingSubscription.h" +#include "Settings.h" #include "qpid/broker/Bridge.h" #include "qpid/broker/Broker.h" #include "qpid/broker/Link.h" #include "qpid/broker/Queue.h" +#include "qpid/broker/QueueObserver.h" #include "qpid/broker/QueueRegistry.h" #include "qpid/broker/SessionHandler.h" +#include "qpid/broker/SessionHandler.h" #include "qpid/framing/SequenceSet.h" #include "qpid/framing/FieldTable.h" #include "qpid/log/Statement.h" @@ -37,43 +40,89 @@ namespace { const std::string QPID_REPLICATOR_("qpid.replicator-"); const std::string TYPE_NAME("qpid.queue-replicator"); -const std::string QPID_SYNC_FREQUENCY("qpid.sync_frequency"); +const std::string QPID_HA("qpid.ha-"); } namespace qpid { namespace ha { using namespace broker; using namespace framing; +using namespace std; +using sys::Mutex; -const std::string QPID_HA_EVENT_PREFIX("qpid.ha-event:"); -const std::string QueueReplicator::DEQUEUE_EVENT_KEY(QPID_HA_EVENT_PREFIX+"dequeue"); -const std::string QueueReplicator::POSITION_EVENT_KEY(QPID_HA_EVENT_PREFIX+"position"); +const std::string QueueReplicator::DEQUEUE_EVENT_KEY(QPID_HA+"dequeue"); +const std::string QueueReplicator::POSITION_EVENT_KEY(QPID_HA+"position"); +const std::string QueueReplicator::QPID_SYNC_FREQUENCY("qpid.sync_frequency"); std::string QueueReplicator::replicatorName(const std::string& queueName) { return QPID_REPLICATOR_ + queueName; } +bool QueueReplicator::isReplicatorName(const std::string& name) { + return name.compare(0, QPID_REPLICATOR_.size(), QPID_REPLICATOR_) == 0; +} + bool QueueReplicator::isEventKey(const std::string key) { - const std::string& prefix = QPID_HA_EVENT_PREFIX; + const std::string& prefix = QPID_HA; bool ret = key.size() > prefix.size() && key.compare(0, prefix.size(), prefix) == 0; return ret; } +class QueueReplicator::ErrorListener : public SessionHandler::ErrorListener { + public: + ErrorListener(const std::string& prefix) : logPrefix(prefix) {} + void connectionException(framing::connection::CloseCode, const std::string& msg) { + QPID_LOG(error, logPrefix << "Connection error: " << msg); + } + void channelException(framing::session::DetachCode, const std::string& msg) { + QPID_LOG(error, logPrefix << "Channel error: " << msg); + } + void executionException(framing::execution::ErrorCode, const std::string& msg) { + QPID_LOG(error, logPrefix << "Execution error: " << msg); + } + void detach() { + QPID_LOG(debug, logPrefix << "Session detached"); + } + private: + std::string logPrefix; +}; + +class QueueReplicator::QueueObserver : public broker::QueueObserver { + public: + QueueObserver(boost::shared_ptr<QueueReplicator> qr) : queueReplicator(qr) {} + void enqueued(const Message&) {} + void dequeued(const Message&) {} + void acquired(const Message&) {} + void requeued(const Message&) {} + void consumerAdded( const Consumer& ) {} + void consumerRemoved( const Consumer& ) {} + // Queue observer is destroyed when the queue is. + void destroy() { queueReplicator->destroy(); } + private: + boost::shared_ptr<QueueReplicator> queueReplicator; +}; + QueueReplicator::QueueReplicator(HaBroker& hb, boost::shared_ptr<Queue> q, boost::shared_ptr<Link> l) : Exchange(replicatorName(q->getName()), 0, q->getBroker()), haBroker(hb), logPrefix("Backup queue "+q->getName()+": "), - queue(q), link(l), brokerInfo(hb.getBrokerInfo()) + queue(q), link(l), brokerInfo(hb.getBrokerInfo()), subscribed(false), + settings(hb.getSettings()) { + args.setString(QPID_REPLICATE, printable(NONE).str()); Uuid uuid(true); bridgeName = replicatorName(q->getName()) + std::string(".") + uuid.str(); + framing::FieldTable args = getArgs(); + args.setString(QPID_REPLICATE, printable(NONE).str()); + setArgs(args); } // This must be separate from the constructor so we can call shared_from_this. void QueueReplicator::activate() { - sys::Mutex::ScopedLock l(lock); + Mutex::ScopedLock l(lock); + if (!queue) return; // Already destroyed std::pair<Bridge::shared_ptr, bool> result = queue->getBroker()->getLinks().declare( bridgeName, @@ -93,48 +142,57 @@ void QueueReplicator::activate() { boost::bind(&QueueReplicator::initializeBridge, shared_from_this(), _1, _2) ); bridge = result.first; + bridge->setErrorListener( + boost::shared_ptr<ErrorListener>(new ErrorListener(logPrefix))); + boost::shared_ptr<QueueObserver> observer(new QueueObserver(shared_from_this())); + queue->addObserver(observer); } -QueueReplicator::~QueueReplicator() { deactivate(); } +QueueReplicator::~QueueReplicator() {} -void QueueReplicator::deactivate() { - // destroy the route - sys::Mutex::ScopedLock l(lock); - if (bridge) { - bridge->close(); - bridge.reset(); - QPID_LOG(debug, logPrefix << "Deactivated bridge " << bridgeName); - } +void QueueReplicator::destroy() { + // Called from Queue::destroyed() + Mutex::ScopedLock l(lock); + if (!bridge) return; + QPID_LOG(debug, logPrefix << "Destroyed."); + bridge->close(); + // Need to drop shared pointers to avoid pointer cycles keeping this in memory. + queue.reset(); + link.reset(); + bridge.reset(); + getBroker()->getExchanges().destroy(getName()); } // Called in a broker connection thread when the bridge is created. void QueueReplicator::initializeBridge(Bridge& bridge, SessionHandler& sessionHandler) { - sys::Mutex::ScopedLock l(lock); + Mutex::ScopedLock l(lock); + if (!queue) return; // Already destroyed AMQP_ServerProxy peer(sessionHandler.out); const qmf::org::apache::qpid::broker::ArgsLinkBridge& args(bridge.getArgs()); - FieldTable settings; - settings.setInt(ReplicatingSubscription::QPID_REPLICATING_SUBSCRIPTION, 1); - settings.setInt(QPID_SYNC_FREQUENCY, 1); // FIXME aconway 2012-05-22: optimize? - settings.setInt(ReplicatingSubscription::QPID_BACK, - queue->getPosition()); - settings.setTable(ReplicatingSubscription::QPID_BROKER_INFO, - brokerInfo.asFieldTable()); - SequenceNumber front; - if (ReplicatingSubscription::getFront(*queue, front)) { - settings.setInt(ReplicatingSubscription::QPID_FRONT, front); - QPID_LOG(debug, "QPID_FRONT for " << queue->getName() << " is " << front); + FieldTable arguments; + arguments.setInt(ReplicatingSubscription::QPID_REPLICATING_SUBSCRIPTION, 1); + arguments.setInt(QPID_SYNC_FREQUENCY, 1); // FIXME aconway 2012-05-22: optimize? + arguments.setInt(ReplicatingSubscription::QPID_BACK, queue->getPosition()); + arguments.setTable(ReplicatingSubscription::QPID_BROKER_INFO,brokerInfo.asFieldTable()); + SequenceNumber front, back; + queue->getRange(front, back, broker::REPLICATOR); + if (front <= back) arguments.setInt(ReplicatingSubscription::QPID_FRONT, front); + try { + peer.getMessage().subscribe( + args.i_src, args.i_dest, 0/*accept-explicit*/, 1/*not-acquired*/, + false/*exclusive*/, "", 0, arguments); + peer.getMessage().setFlowMode(getName(), 1); // Window + peer.getMessage().flow(getName(), 0, settings.getFlowMessages()); + peer.getMessage().flow(getName(), 1, settings.getFlowBytes()); + } + catch(const exception& e) { + QPID_LOG(error, QPID_MSG(logPrefix + "Cannot connect to primary: " << e.what())); + throw; } - peer.getMessage().subscribe( - args.i_src, args.i_dest, 0/*accept-explicit*/, 1/*not-acquired*/, - false/*exclusive*/, "", 0, settings); - // FIXME aconway 2012-05-22: use a finite credit window? - peer.getMessage().flow(getName(), 0, 0xFFFFFFFF); - peer.getMessage().flow(getName(), 1, 0xFFFFFFFF); - qpid::Address primary; link->getRemoteAddress(primary); QPID_LOG(info, logPrefix << "Connected to " << primary << "(" << bridgeName << ")"); - QPID_LOG(trace, logPrefix << "Subscription settings: " << settings); + QPID_LOG(trace, logPrefix << "Subscription arguments: " << arguments); } namespace { @@ -147,17 +205,35 @@ template <class T> T decodeContent(Message& m) { } } -void QueueReplicator::dequeue(SequenceNumber n, sys::Mutex::ScopedLock&) { +void QueueReplicator::dequeue(SequenceNumber n, Mutex::ScopedLock&) { + boost::shared_ptr<Queue> q; + { + Mutex::ScopedLock l(lock); + if (!queue) return; // Already destroyed + q = queue; + } // Thread safe: only calls thread safe Queue functions. queue->dequeueMessageAt(n); } +namespace { +bool getSequence(const Message& message, SequenceNumber& result) { + result = message.getSequence(); + return true; +} +bool getNext(broker::Queue& q, SequenceNumber position, SequenceNumber& result) { + QueueCursor cursor(REPLICATOR); + return q.seek(cursor, boost::bind(&getSequence, _1, boost::ref(result)), position+1); +} +} // namespace + // Called in connection thread of the queues bridge to primary. void QueueReplicator::route(Deliverable& msg) { try { const std::string& key = msg.getMessage().getRoutingKey(); - sys::Mutex::ScopedLock l(lock); + Mutex::ScopedLock l(lock); + if (!queue) return; // Already destroyed if (!isEventKey(key)) { msg.deliverTo(queue); // We are on a backup so the queue is not modified except via this. @@ -176,16 +252,15 @@ void QueueReplicator::route(Deliverable& msg) << " to " << position); // Verify that there are no messages after the new position in the queue. SequenceNumber next; - if (ReplicatingSubscription::getNext(*queue, position, next)) - throw Exception("Invalid position move, preceeds messages"); + if (getNext(*queue, position, next)) + throw Exception(QPID_MSG(logPrefix << "Invalid position " << position + << " preceeds message at " << next)); queue->setPosition(position); } // Ignore unknown event keys, may be introduced in later versions. } catch (const std::exception& e) { - QPID_LOG(critical, logPrefix << "Replication failed: " << e.what()); - haBroker.shutdown(); - throw; + haBroker.shutdown(QPID_MSG(logPrefix << "Replication failed: " << e.what())); } } diff --git a/cpp/src/qpid/ha/QueueReplicator.h b/cpp/src/qpid/ha/QueueReplicator.h index 8d8a41a5ba..e8a793f611 100644 --- a/cpp/src/qpid/ha/QueueReplicator.h +++ b/cpp/src/qpid/ha/QueueReplicator.h @@ -41,6 +41,7 @@ class Deliverable; namespace ha { class HaBroker; +class Settings; /** * Exchange created on a backup broker to replicate a queue on the primary. @@ -57,7 +58,11 @@ class QueueReplicator : public broker::Exchange, public: static const std::string DEQUEUE_EVENT_KEY; static const std::string POSITION_EVENT_KEY; + static const std::string QPID_SYNC_FREQUENCY; + static std::string replicatorName(const std::string& queueName); + static bool isReplicatorName(const std::string&); + /** Test if a string is an event key */ static bool isEventKey(const std::string key); @@ -68,7 +73,6 @@ class QueueReplicator : public broker::Exchange, ~QueueReplicator(); void activate(); // Call after ctor - void deactivate(); // Call before dtor std::string getType() const; bool bind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*, qpid::broker::AsyncStore* const); @@ -80,8 +84,18 @@ class QueueReplicator : public broker::Exchange, uint64_t getSize(); void write(char* target); + // Set if the queue has ever been subscribed to, used for auto-delete cleanup. + void setSubscribed() { subscribed = true; } + bool isSubscribed() { return subscribed; } + + boost::shared_ptr<broker::Queue> getQueue() const { return queue; } + private: + class ErrorListener; + class QueueObserver; + void initializeBridge(broker::Bridge& bridge, broker::SessionHandler& sessionHandler); + void destroy(); // Called when the queue is destroyed. void dequeue(framing::SequenceNumber, sys::Mutex::ScopedLock&); HaBroker& haBroker; @@ -92,6 +106,8 @@ class QueueReplicator : public broker::Exchange, boost::shared_ptr<broker::Link> link; boost::shared_ptr<broker::Bridge> bridge; BrokerInfo brokerInfo; + bool subscribed; + const Settings& settings; }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/RemoteBackup.cpp b/cpp/src/qpid/ha/RemoteBackup.cpp index 3421380940..798ade3f73 100644 --- a/cpp/src/qpid/ha/RemoteBackup.cpp +++ b/cpp/src/qpid/ha/RemoteBackup.cpp @@ -21,6 +21,7 @@ #include "RemoteBackup.h" #include "QueueGuard.h" #include "qpid/broker/Broker.h" +#include "qpid/broker/Connection.h" #include "qpid/broker/Queue.h" #include "qpid/broker/QueueRegistry.h" #include "qpid/log/Statement.h" @@ -32,32 +33,45 @@ namespace ha { using sys::Mutex; using boost::bind; -RemoteBackup::RemoteBackup(const BrokerInfo& info, ReplicationTest rt, bool con) : - logPrefix("Primary: Remote backup "+info.getLogId()+": "), - brokerInfo(info), replicationTest(rt), connected(con), reportedReady(false) -{} +RemoteBackup::RemoteBackup( + const BrokerInfo& info, broker::Connection* c +) : brokerInfo(info), replicationTest(NONE), connection(c), reportedReady(false) +{ + std::ostringstream oss; + oss << "Primary: Remote backup " << info << ": "; + logPrefix = oss.str(); +} -void RemoteBackup::setInitialQueues(broker::QueueRegistry& queues, bool createGuards) +void RemoteBackup::setCatchupQueues(broker::QueueRegistry& queues, bool createGuards) { - QPID_LOG(debug, logPrefix << "Setting initial queues" << (createGuards ? " and guards" : "")); - queues.eachQueue(boost::bind(&RemoteBackup::initialQueue, this, _1, createGuards)); + queues.eachQueue(boost::bind(&RemoteBackup::catchupQueue, this, _1, createGuards)); + QPID_LOG(debug, logPrefix << "Set " << catchupQueues.size() << " catch-up queues" + << (createGuards ? " and guards" : "")); } RemoteBackup::~RemoteBackup() { cancel(); } void RemoteBackup::cancel() { + QPID_LOG(debug, logPrefix << "Cancelled " << (connection? "connected":"disconnected") + << " backup: " << brokerInfo); for (GuardMap::iterator i = guards.begin(); i != guards.end(); ++i) i->second->cancel(); guards.clear(); + if (connection) { + connection->abort(); + connection = 0; + } } bool RemoteBackup::isReady() { - return connected && initialQueues.empty(); + return connection && catchupQueues.empty(); } -void RemoteBackup::initialQueue(const QueuePtr& q, bool createGuard) { - if (replicationTest.isReplicated(ALL, *q)) { - initialQueues.insert(q); +void RemoteBackup::catchupQueue(const QueuePtr& q, bool createGuard) { + if (replicationTest.getLevel(*q) == ALL) { + QPID_LOG(debug, logPrefix << "Catch-up queue" + << (createGuard ? " and guard" : "") << ": " << q->getName()); + catchupQueues.insert(q); if (createGuard) guards[q].reset(new QueueGuard(*q, brokerInfo)); } } @@ -88,21 +102,24 @@ std::ostream& operator<<(std::ostream& o, const QueueSetPrinter& qp) { } void RemoteBackup::ready(const QueuePtr& q) { - initialQueues.erase(q); - QPID_LOG(debug, logPrefix << "Queue ready: " << q->getName() - << QueueSetPrinter(", waiting for: ", initialQueues)); - if (isReady()) QPID_LOG(debug, logPrefix << "All queues ready"); + catchupQueues.erase(q); + if (catchupQueues.size()) { + QPID_LOG(debug, logPrefix << "Caught up on queue: " << q->getName() << ", " + << catchupQueues.size() << " remain to catch up"); + } + else + QPID_LOG(debug, logPrefix << "Caught up on queue: " << q->getName() ); } -// Called via ConfigurationObserver::queueCreate and from initialQueue +// Called via ConfigurationObserver::queueCreate and from catchupQueue void RemoteBackup::queueCreate(const QueuePtr& q) { - if (replicationTest.isReplicated(ALL, *q)) + if (replicationTest.getLevel(*q) == ALL) guards[q].reset(new QueueGuard(*q, brokerInfo)); } // Called via ConfigurationObserver void RemoteBackup::queueDestroy(const QueuePtr& q) { - initialQueues.erase(q); + catchupQueues.erase(q); GuardMap::iterator i = guards.find(q); if (i != guards.end()) { i->second->cancel(); diff --git a/cpp/src/qpid/ha/RemoteBackup.h b/cpp/src/qpid/ha/RemoteBackup.h index 8ee776e90b..769c50457e 100644 --- a/cpp/src/qpid/ha/RemoteBackup.h +++ b/cpp/src/qpid/ha/RemoteBackup.h @@ -33,6 +33,7 @@ namespace qpid { namespace broker { class Queue; class QueueRegistry; +class Connection; } namespace ha { @@ -54,20 +55,20 @@ class RemoteBackup /** Note: isReady() can be true after construction *@param connected true if the backup is already connected. */ - RemoteBackup(const BrokerInfo& info, ReplicationTest, bool connected); + RemoteBackup(const BrokerInfo&, broker::Connection*); ~RemoteBackup(); - /** Set the initial queues for all queues in the registry. - *@createGuards if true create guards also, if false guards will be created on demand. + /** Set all queues in the registry as catch-up queues. + *@createGuards if true create guards also, if false guards are created on demand. */ - void setInitialQueues(broker::QueueRegistry&, bool createGuards); + void setCatchupQueues(broker::QueueRegistry&, bool createGuards); /** Return guard associated with a queue. Used to create ReplicatingSubscription. */ GuardPtr guard(const QueuePtr&); /** Is the remote backup connected? */ - void setConnected(bool b) { connected=b; } - bool isConnected() const { return connected; } + void setConnection(broker::Connection* c) { connection = c; } + bool isConnected() const { return connection; } /** ReplicatingSubscription associated with queue is ready. * Note: may set isReady() @@ -80,7 +81,7 @@ class RemoteBackup /** Called via ConfigurationObserver. Note: may set isReady() */ void queueDestroy(const QueuePtr&); - /**@return true when all initial queues for this backup are ready. */ + /**@return true when all catch-up queues for this backup are ready. */ bool isReady(); /**@return true if isReady() and this is the first call to reportReady */ @@ -94,15 +95,14 @@ class RemoteBackup typedef std::map<QueuePtr, GuardPtr> GuardMap; typedef std::set<QueuePtr> QueueSet; - /** Add queue to guard as an initial queue */ - void initialQueue(const QueuePtr&, bool createGuard); + void catchupQueue(const QueuePtr&, bool createGuard); std::string logPrefix; BrokerInfo brokerInfo; ReplicationTest replicationTest; GuardMap guards; - QueueSet initialQueues; - bool connected; + QueueSet catchupQueues; + broker::Connection* connection; bool reportedReady; }; diff --git a/cpp/src/qpid/ha/ReplicatingSubscription.cpp b/cpp/src/qpid/ha/ReplicatingSubscription.cpp index 6f7519cd1f..933716e8fa 100644 --- a/cpp/src/qpid/ha/ReplicatingSubscription.cpp +++ b/cpp/src/qpid/ha/ReplicatingSubscription.cpp @@ -91,25 +91,6 @@ string mask(const string& in) return DOLLAR + in + INTERNAL; } -namespace { -bool getSequence(const Message& message, SequenceNumber& result) -{ - result = message.getSequence(); - return true; -} -} -bool ReplicatingSubscription::getNext( - broker::Queue& q, SequenceNumber from, SequenceNumber& result) -{ - QueueCursor cursor(REPLICATOR); - return q.seek(cursor, boost::bind(&getSequence, _1, boost::ref(result)), from); -} - -bool ReplicatingSubscription::getFront(broker::Queue& q, SequenceNumber& front) { - QueueCursor cursor(REPLICATOR); - return q.seek(cursor, boost::bind(&getSequence, _1, boost::ref(front))); -} - /* Called by SemanticState::consume to create a consumer */ boost::shared_ptr<broker::SemanticState::ConsumerImpl> ReplicatingSubscription::Factory::create( @@ -157,7 +138,7 @@ ReplicatingSubscription::ReplicatingSubscription( // Set a log prefix message that identifies the remote broker. ostringstream os; - os << "Primary " << queue->getName() << "@" << info.getLogId() << ": "; + os << "Primary " << queue->getName() << "@" << info << ": "; logPrefix = os.str(); // NOTE: Once the guard is attached we can have concurrent @@ -171,6 +152,7 @@ ReplicatingSubscription::ReplicatingSubscription( guard->attach(*this); QueueRange backup(arguments); // Remote backup range. + QueueRange backupOriginal(backup); QueueRange primary(guard->getRange()); // Unguarded range when the guard was set. backupPosition = backup.back; @@ -207,7 +189,7 @@ ReplicatingSubscription::ReplicatingSubscription( // queue and hasn't been tampered with then that will be the case. QPID_LOG(debug, logPrefix << "Subscribed:" - << " backup:" << backup + << " backup:" << backupOriginal << " adjusted backup:" << backup << " primary:" << primary << " catch-up: " << position << "-" << primary.back << "(" << primary.back-position << ")"); @@ -222,9 +204,7 @@ ReplicatingSubscription::ReplicatingSubscription( } } -ReplicatingSubscription::~ReplicatingSubscription() { - QPID_LOG(debug, logPrefix << "Detroyed replicating subscription"); -} +ReplicatingSubscription::~ReplicatingSubscription() {} // Called in subscription's connection thread when the subscription is created. // Called separate from ctor because sending events requires @@ -248,19 +228,20 @@ void ReplicatingSubscription::initialize() { } // Message is delivered in the subscription's connection thread. -bool ReplicatingSubscription::deliver(const qpid::broker::QueueCursor& c, const qpid::broker::Message& m) { - position = m.getSequence(); +bool ReplicatingSubscription::deliver( + const qpid::broker::QueueCursor& c, const qpid::broker::Message& m) +{ try { - QPID_LOG(trace, logPrefix << "Replicating " << getQueue()->getName() << "[" << m.getSequence() << "]"); + QPID_LOG(trace, logPrefix << "Replicating " << m.getSequence()); { Mutex::ScopedLock l(lock); - //FIXME GRS: position is no longer set//assert(position == m.getSequence()); + position = m.getSequence(); - // m.getSequence() is the position of the newly enqueued message on local queue. + // m.getSequence() is the position of the new message on local queue. // backupPosition is latest position on backup queue before enqueueing if (m.getSequence() <= backupPosition) throw Exception( - QPID_MSG("Expected position > " << backupPosition + QPID_MSG(logPrefix << "Expected position > " << backupPosition << " but got " << m.getSequence())); if (m.getSequence() - backupPosition > 1) { // Position has advanced because of messages dequeued ahead of us. @@ -272,7 +253,7 @@ bool ReplicatingSubscription::deliver(const qpid::broker::QueueCursor& c, const } return ConsumerImpl::deliver(c, m); } catch (const std::exception& e) { - QPID_LOG(critical, logPrefix << "Error replicating " << getQueue()->getName() << "[" << m.getSequence() << "]" + QPID_LOG(critical, logPrefix << "Error replicating " << m.getSequence() << ": " << e.what()); throw; } @@ -292,6 +273,7 @@ void ReplicatingSubscription::setReady() { // Called in the subscription's connection thread. void ReplicatingSubscription::cancel() { + QPID_LOG(debug, logPrefix << "Cancelled"); guard->cancel(); ConsumerImpl::cancel(); } @@ -299,7 +281,7 @@ void ReplicatingSubscription::cancel() // Consumer override, called on primary in the backup's IO thread. void ReplicatingSubscription::acknowledged(const broker::DeliveryRecord& r) { // Finish completion of message, it has been acknowledged by the backup. - QPID_LOG(trace, logPrefix << "Acknowledged " << getQueue()->getName() << "[" << r.getMessageId() << "]"); + QPID_LOG(trace, logPrefix << "Acknowledged " << r.getMessageId()); guard->complete(r.getMessageId()); // If next message is protected by the guard then we are ready if (r.getMessageId() >= guard->getRange().back) setReady(); @@ -328,7 +310,7 @@ void ReplicatingSubscription::sendDequeueEvent(Mutex::ScopedLock&) // arbitrary connection threads. void ReplicatingSubscription::dequeued(const Message& m) { - QPID_LOG(trace, logPrefix << "Dequeued " << getQueue()->getName() << "[" << m.getSequence() << "]"); + QPID_LOG(trace, logPrefix << "Dequeued " << m.getSequence()); { Mutex::ScopedLock l(lock); dequeues.add(m.getSequence()); @@ -396,7 +378,14 @@ bool ReplicatingSubscription::doDispatch() Mutex::ScopedLock l(lock); if (!dequeues.empty()) sendDequeueEvent(l); } - return ConsumerImpl::doDispatch(); + try { + return ConsumerImpl::doDispatch(); + } + catch (const std::exception& e) { + // FIXME aconway 2012-10-05: detect queue deletion, no warning. + QPID_LOG(warning, logPrefix << " exception in dispatch: " << e.what()); + return false; + } } }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/ReplicatingSubscription.h b/cpp/src/qpid/ha/ReplicatingSubscription.h index 8a2984846e..7fcb4ccf13 100644 --- a/cpp/src/qpid/ha/ReplicatingSubscription.h +++ b/cpp/src/qpid/ha/ReplicatingSubscription.h @@ -61,10 +61,14 @@ class QueueGuard; * * Lifecycle: broker::Queue holds shared_ptrs to this as a consumer. * + * Lock Hierarchy: ReplicatingSubscription MUS NOT call QueueGuard with it's lock held + * QueueGuard MAY call ReplicatingSubscription with it's lock held. */ class ReplicatingSubscription : public broker::SemanticState::ConsumerImpl { public: + typedef broker::SemanticState::ConsumerImpl ConsumerImpl; + struct Factory : public broker::ConsumerFactory { boost::shared_ptr<broker::SemanticState::ConsumerImpl> create( broker::SemanticState* parent, @@ -80,17 +84,6 @@ class ReplicatingSubscription : public broker::SemanticState::ConsumerImpl static const std::string QPID_FRONT; static const std::string QPID_BROKER_INFO; - // TODO aconway 2012-05-23: these don't belong on ReplicatingSubscription - /** Get position of front message on queue. - *@return false if queue is empty. - */ - static bool getFront(broker::Queue&, framing::SequenceNumber& result); - /** Get next message after from in queue. - *@return false if none found. - */ - static bool getNext(broker::Queue&, framing::SequenceNumber from, - framing::SequenceNumber& result); - ReplicatingSubscription(broker::SemanticState* parent, const std::string& name, boost::shared_ptr<broker::Queue> , bool ack, bool acquire, bool exclusive, const std::string& tag, @@ -114,6 +107,8 @@ class ReplicatingSubscription : public broker::SemanticState::ConsumerImpl // Hide the "queue deleted" error for a ReplicatingSubscription when a // queue is deleted, this is normal and not an error. bool hideDeletedError() { return true; } + // Not counted for auto deletion and immediate message purposes. + bool isCounted() { return false; } /** Initialization that must be done separately from construction * because it requires a shared_ptr to this to exist. diff --git a/cpp/src/qpid/ha/ReplicationTest.cpp b/cpp/src/qpid/ha/ReplicationTest.cpp index 88a969dbfd..647523ef2c 100644 --- a/cpp/src/qpid/ha/ReplicationTest.cpp +++ b/cpp/src/qpid/ha/ReplicationTest.cpp @@ -19,7 +19,9 @@ * */ #include "ReplicationTest.h" +#include "qpid/log/Statement.h" #include "qpid/broker/Queue.h" +#include "qpid/broker/Exchange.h" #include "qpid/framing/FieldTable.h" namespace qpid { @@ -27,48 +29,47 @@ namespace ha { using types::Variant; -ReplicateLevel ReplicationTest::replicateLevel(const std::string& str) { +ReplicateLevel ReplicationTest::getLevel(const std::string& str) { Enum<ReplicateLevel> rl(replicateDefault); if (!str.empty()) rl.parse(str); return rl.get(); } -ReplicateLevel ReplicationTest::replicateLevel(const framing::FieldTable& f) { +ReplicateLevel ReplicationTest::getLevel(const framing::FieldTable& f) { if (f.isSet(QPID_REPLICATE)) - return replicateLevel(f.getAsString(QPID_REPLICATE)); + return getLevel(f.getAsString(QPID_REPLICATE)); else return replicateDefault; } -ReplicateLevel ReplicationTest::replicateLevel(const Variant::Map& m) { +ReplicateLevel ReplicationTest::getLevel(const Variant::Map& m) { Variant::Map::const_iterator i = m.find(QPID_REPLICATE); if (i != m.end()) - return replicateLevel(i->second.asString()); + return getLevel(i->second.asString()); else return replicateDefault; } -namespace { -const std::string AUTO_DELETE_TIMEOUT("qpid.auto_delete_timeout"); +ReplicateLevel ReplicationTest::getLevel(const broker::Queue& q) { + const Variant::Map& qmap(q.getSettings().original); + Variant::Map::const_iterator i = qmap.find(QPID_REPLICATE); + if (i != qmap.end()) + return getLevel(i->second.asString()); + else + return getLevel(q.getSettings().storeSettings); } -bool ReplicationTest::isReplicated( - ReplicateLevel level, const Variant::Map& args, bool autodelete, bool exclusive) -{ - bool ignore = autodelete && exclusive && args.find(AUTO_DELETE_TIMEOUT) == args.end(); - return !ignore && replicateLevel(args) >= level; +ReplicateLevel ReplicationTest::getLevel(const broker::Exchange& ex) { + return getLevel(ex.getArgs()); } -bool ReplicationTest::isReplicated( - ReplicateLevel level, const framing::FieldTable& args, bool autodelete, bool exclusive) +ReplicateLevel ReplicationTest::useLevel(const broker::Queue& q) { - bool ignore = autodelete && exclusive && !args.isSet(AUTO_DELETE_TIMEOUT); - return !ignore && replicateLevel(args) >= level; + return q.getSettings().isTemporary ? ReplicationTest(NONE).getLevel(q) : getLevel(q); } -bool ReplicationTest::isReplicated(ReplicateLevel level, const broker::Queue& q) -{ - return isReplicated(level, q.getSettings().storeSettings, q.isAutoDelete(), q.hasExclusiveOwner()); +ReplicateLevel ReplicationTest::useLevel(const broker::Exchange& ex) { + return ReplicationTest::getLevel(ex); } diff --git a/cpp/src/qpid/ha/ReplicationTest.h b/cpp/src/qpid/ha/ReplicationTest.h index 9f6976a8e4..7d44d82a21 100644 --- a/cpp/src/qpid/ha/ReplicationTest.h +++ b/cpp/src/qpid/ha/ReplicationTest.h @@ -30,6 +30,7 @@ namespace qpid { namespace broker { class Queue; +class Exchange; } namespace framing { @@ -47,21 +48,24 @@ class ReplicationTest ReplicationTest(ReplicateLevel replicateDefault_) : replicateDefault(replicateDefault_) {} - // Return the simple replication level, accounting for defaults. - ReplicateLevel replicateLevel(const std::string& str); - ReplicateLevel replicateLevel(const framing::FieldTable& f); - ReplicateLevel replicateLevel(const types::Variant::Map& m); + // Get the replication level set on an object, or default if not set. + ReplicateLevel getLevel(const std::string& str); + ReplicateLevel getLevel(const framing::FieldTable& f); + ReplicateLevel getLevel(const types::Variant::Map& m); + ReplicateLevel getLevel(const broker::Queue&); + ReplicateLevel getLevel(const broker::Exchange&); + + // Calculate level for objects that may not have replication set, + // including auto-delete/exclusive settings. + ReplicateLevel useLevel(const types::Variant::Map& args, bool autodelete, bool exclusive); + ReplicateLevel useLevel(const framing::FieldTable& args, bool autodelete, bool exclusive); + ReplicateLevel useLevel(const broker::Queue&); + ReplicateLevel useLevel(const broker::Exchange&); - // Return true if replication for a queue is enabled at level or higher, - // taking account of default level and queue settings. - bool isReplicated(ReplicateLevel level, - const types::Variant::Map& args, bool autodelete, bool exclusive); - bool isReplicated(ReplicateLevel level, - const framing::FieldTable& args, bool autodelete, bool exclusive); - bool isReplicated(ReplicateLevel level, const broker::Queue&); private: ReplicateLevel replicateDefault; }; + }} // namespace qpid::ha #endif /*!QPID_HA_REPLICATIONTEST_H*/ diff --git a/cpp/src/qpid/ha/Role.h b/cpp/src/qpid/ha/Role.h new file mode 100644 index 0000000000..9d6f7cd123 --- /dev/null +++ b/cpp/src/qpid/ha/Role.h @@ -0,0 +1,55 @@ +#ifndef QPID_HA_ROLE_H +#define QPID_HA_ROLE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <string> + +namespace qpid { +struct Url; + +namespace ha { + +/** + * A HaBroker has a role, e.g. Primary, Backup, StandAlone. + * Role subclasses define the actions of the broker in each role. + * The Role interface allows the HaBroker to pass management actions + * to be implemented by the role. + */ +class Role +{ + public: + /** Log prefix appropriate to the role */ + virtual std::string getLogPrefix() const = 0; + + /** QMF promote method handler. + * @return The new role if promoted, 0 if not. Caller takes ownership. + */ + virtual Role* promote() = 0; + + virtual void setBrokerUrl(const Url& url) = 0; + + private: +}; +}} // namespace qpid::ha + +#endif /*!QPID_HA_ROLE_H*/ diff --git a/cpp/src/qpid/ha/Settings.h b/cpp/src/qpid/ha/Settings.h index 37235b5c79..53b61415cf 100644 --- a/cpp/src/qpid/ha/Settings.h +++ b/cpp/src/qpid/ha/Settings.h @@ -23,6 +23,7 @@ */ #include "types.h" +#include "qpid/sys/IntegerTypes.h" #include <string> namespace qpid { @@ -34,16 +35,25 @@ namespace ha { class Settings { public: - Settings() : cluster(false), replicateDefault(NONE), backupTimeout(5) + Settings() : cluster(false), queueReplication(false), + replicateDefault(NONE), backupTimeout(5), + flowMessages(100), flowBytes(0) {} bool cluster; // True if we are a cluster member. - std::string clientUrl; + bool queueReplication; // True if enabled. + std::string publicUrl; std::string brokerUrl; Enum<ReplicateLevel> replicateDefault; std::string username, password, mechanism; double backupTimeout; - private: + + uint32_t flowMessages, flowBytes; + + static const uint32_t NO_LIMIT=0xFFFFFFFF; + static uint32_t flowValue(uint32_t n) { return n ? n : NO_LIMIT; } + uint32_t getFlowMessages() const { return flowValue(flowMessages); } + uint32_t getFlowBytes() const { return flowValue(flowBytes); } }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/broker/ConnectionFactory.h b/cpp/src/qpid/ha/StandAlone.h index 7c1a9a08e1..d052996d40 100644 --- a/cpp/src/qpid/broker/ConnectionFactory.h +++ b/cpp/src/qpid/ha/StandAlone.h @@ -1,3 +1,6 @@ +#ifndef QPID_HA_STANDALONE_H +#define QPID_HA_STANDALONE_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -7,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -18,34 +21,25 @@ * under the License. * */ -#ifndef _ConnectionFactory_ -#define _ConnectionFactory_ - -#include "qpid/sys/ConnectionCodec.h" - namespace qpid { -namespace broker { -class Broker; +struct Url; + +namespace ha { -class ConnectionFactory : public sys::ConnectionCodec::Factory +/** + * Stand-alone role: acts as a stand-alone broker, no clustering. + * HA module needed to setting up replication via QMF methods. + */ +class StandAlone : public Role { public: - ConnectionFactory(Broker& b); - - virtual ~ConnectionFactory(); - - sys::ConnectionCodec* - create(framing::ProtocolVersion, sys::OutputControl&, const std::string& id, - const qpid::sys::SecuritySettings&); - - sys::ConnectionCodec* - create(sys::OutputControl&, const std::string& id, const qpid::sys::SecuritySettings&); + std::string getLogPrefix() const { return logPrefix; } + Role* promote() { return 0; } + void setBrokerUrl(const Url&) {} private: - Broker& broker; + std::string logPrefix; }; +}} // namespace qpid::ha -}} - - -#endif +#endif /*!QPID_HA_STANDALONE_H*/ diff --git a/cpp/src/qpid/ha/StatusCheck.cpp b/cpp/src/qpid/ha/StatusCheck.cpp new file mode 100644 index 0000000000..17613ce3dd --- /dev/null +++ b/cpp/src/qpid/ha/StatusCheck.cpp @@ -0,0 +1,132 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "StatusCheck.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Session.h" +#include "qpid/types/Variant.h" + +namespace qpid { +namespace ha { + +using namespace qpid::messaging; +using namespace qpid::types; +using namespace std; +using namespace sys; + +const string HA_BROKER = "org.apache.qpid.ha:habroker:ha-broker"; + +class StatusCheckThread : public sys::Runnable { + public: + StatusCheckThread(StatusCheck& sc, const qpid::Address& addr, const BrokerInfo& self) + : url(addr), statusCheck(sc), brokerInfo(self) {} + void run(); + private: + Url url; + StatusCheck& statusCheck; + uint16_t linkHeartbeatInterval; + BrokerInfo brokerInfo; +}; + +void StatusCheckThread::run() { + QPID_LOG(debug, statusCheck.logPrefix << "Checking status of " << url); + Variant::Map options, clientProperties; + clientProperties = brokerInfo.asMap(); // Detect self connections. + clientProperties["qpid.ha-admin"] = 1; // Allow connection to backups. + + options["client-properties"] = clientProperties; + options["heartbeat"] = statusCheck.linkHeartbeatInterval; + Connection c(url.str(), options); + + try { + c.open(); + Session session = c.createSession(); + messaging::Address responses("#;{create:always,node:{x-declare:{exclusive:True,auto-delete:True,arguments:{'qpid.replicate':none}}}}"); + Receiver r = session.createReceiver(responses); + Sender s = session.createSender("qmf.default.direct/broker"); + Message request; + request.setReplyTo(responses); + request.setContentType("amqp/map"); + request.setProperty("x-amqp-0-10.app-id", "qmf2"); + request.setProperty("qmf.opcode", "_query_request"); + Variant::Map oid; + oid["_object_name"] = HA_BROKER; + Variant::Map content; + content["_what"] = "OBJECT"; + content["_object_id"] = oid; + encode(content, request); + s.send(request); + Message response = r.fetch(statusCheck.linkHeartbeatInterval*Duration::SECOND); + session.acknowledge(); + Variant::List contentIn; + decode(response, contentIn); + if (contentIn.size() == 1) { + Variant::Map details = contentIn.front().asMap()["_values"].asMap(); + string status = details["status"].getString(); + if (status != "joining") { + statusCheck.setPromote(false); + QPID_LOG(info, statusCheck.logPrefix << "Status of " << url << " is " + << status << ", this broker will refuse promotion."); + } + QPID_LOG(debug, statusCheck.logPrefix << "Status of " << url << ": " << status); + } + } catch(const exception& error) { + QPID_LOG(info, "Checking status of " << url << ": " << error.what()); + } + delete this; +} + +StatusCheck::StatusCheck(const string& lp, uint16_t lh, const BrokerInfo& self) + : logPrefix(lp), promote(true), linkHeartbeatInterval(lh), brokerInfo(self) +{} + +StatusCheck::~StatusCheck() { + // Join any leftovers + for (size_t i = 0; i < threads.size(); ++i) threads[i].join(); +} + +void StatusCheck::setUrl(const Url& url) { + Mutex::ScopedLock l(lock); + for (size_t i = 0; i < url.size(); ++i) + threads.push_back(Thread(new StatusCheckThread(*this, url[i], brokerInfo))); +} + +bool StatusCheck::canPromote() { + Mutex::ScopedLock l(lock); + while (!threads.empty()) { + Thread t = threads.back(); + threads.pop_back(); + Mutex::ScopedUnlock u(lock); + t.join(); + } + return promote; +} + +void StatusCheck::setPromote(bool p) { + Mutex::ScopedLock l(lock); + promote = p; +} + +}} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/StatusCheck.h b/cpp/src/qpid/ha/StatusCheck.h new file mode 100644 index 0000000000..997ced4159 --- /dev/null +++ b/cpp/src/qpid/ha/StatusCheck.h @@ -0,0 +1,71 @@ +#ifndef QPID_HA_STATUSCHECK_H +#define QPID_HA_STATUSCHECK_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "BrokerInfo.h" +#include "qpid/Url.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Runnable.h" +#include <vector> + +namespace qpid { +namespace ha { + +// FIXME aconway 2012-12-21: This solution is incomplete. It will only protect +// against bad promotion if there are READY brokers when this broker starts. +// It will not help the situation where brokers became READY after this one starts. +// + +/** + * Check whether a JOINING broker can be promoted . + * + * A JOINING broker can be promoted as long as all the other brokers are also + * JOINING. If there are READY brokers in the cluster the JOINING broker should + * refuse to promote so that one of the READY brokers can. This situation + * only comes about if the primary is dead and no new primary has been promoted. + * + * THREAD SAFE: setUrl and canPromote are called in arbitrary management threads. + */ +class StatusCheck +{ + public: + StatusCheck(const std::string& logPrefix, uint16_t linkHeartbeatInteval, const BrokerInfo& self); + ~StatusCheck(); + void setUrl(const Url&); + bool canPromote(); + + private: + void setPromote(bool p); + + std::string logPrefix; + sys::Mutex lock; + std::vector<sys::Thread> threads; + bool promote; + uint16_t linkHeartbeatInterval; + BrokerInfo brokerInfo; + friend class StatusCheckThread; +}; +}} // namespace qpid::ha + +#endif /*!QPID_HA_STATUSCHECK_H*/ diff --git a/cpp/src/qpid/ha/types.cpp b/cpp/src/qpid/ha/types.cpp index 53e2056213..bb4bf83574 100644 --- a/cpp/src/qpid/ha/types.cpp +++ b/cpp/src/qpid/ha/types.cpp @@ -33,6 +33,7 @@ namespace ha { using namespace std; const string QPID_REPLICATE("qpid.replicate"); +const string QPID_HA_UUID("qpid.ha-uuid"); string EnumBase::str() const { assert(value < count); @@ -55,6 +56,11 @@ template <> const char* Enum<ReplicateLevel>::NAMES[] = { "none", "configuration template <> const size_t Enum<ReplicateLevel>::N = 3; template <> const char* Enum<BrokerStatus>::NAME = "HA broker status"; + +// NOTE: Changing status names will have an impact on qpid-ha and +// the qpidd-primary init script. +// Don't change them unless you are going to update all dependent code. +// template <> const char* Enum<BrokerStatus>::NAMES[] = { "joining", "catchup", "ready", "recovering", "active", "standalone" }; diff --git a/cpp/src/qpid/ha/types.h b/cpp/src/qpid/ha/types.h index 35faf9f624..f8c48afc5a 100644 --- a/cpp/src/qpid/ha/types.h +++ b/cpp/src/qpid/ha/types.h @@ -99,6 +99,7 @@ inline bool isBackup(BrokerStatus s) { return !isPrimary(s); } // String constants. extern const std::string QPID_REPLICATE; +extern const std::string QPID_HA_UUID; /** Define IdSet type, not a typedef so we can overload operator << */ class IdSet : public std::set<types::Uuid> {}; diff --git a/cpp/src/qpid/legacystore/BindingDbt.cpp b/cpp/src/qpid/legacystore/BindingDbt.cpp new file mode 100644 index 0000000000..a48c156e71 --- /dev/null +++ b/cpp/src/qpid/legacystore/BindingDbt.cpp @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/legacystore/BindingDbt.h" + +namespace mrg { +namespace msgstore { + +BindingDbt::BindingDbt(const qpid::broker::PersistableExchange& e, const qpid::broker::PersistableQueue& q, const std::string& k, const qpid::framing::FieldTable& a) + : data(new char[encodedSize(e, q, k, a)]), + buffer(data, encodedSize(e, q, k, a)) +{ + buffer.putLongLong(q.getPersistenceId()); + buffer.putShortString(q.getName()); + buffer.putShortString(k); + buffer.put(a); + + set_data(data); + set_size(encodedSize(e, q, k, a)); +} + +BindingDbt::~BindingDbt() +{ + delete [] data; +} + +uint32_t BindingDbt::encodedSize(const qpid::broker::PersistableExchange& /*not used*/, const qpid::broker::PersistableQueue& q, const std::string& k, const qpid::framing::FieldTable& a) +{ + return 8 /*queue id*/ + q.getName().size() + 1 + k.size() + 1 + a.encodedSize(); +} + +}} diff --git a/cpp/src/qpid/legacystore/BindingDbt.h b/cpp/src/qpid/legacystore/BindingDbt.h new file mode 100644 index 0000000000..63c7cd144e --- /dev/null +++ b/cpp/src/qpid/legacystore/BindingDbt.h @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_BINDINGDBT_H +#define QPID_LEGACYSTORE_BINDINGDBT_H + +#include "db-inc.h" +#include "qpid/broker/PersistableExchange.h" +#include "qpid/broker/PersistableQueue.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldTable.h" + +namespace mrg{ +namespace msgstore{ + +class BindingDbt : public Dbt +{ + char* data; + qpid::framing::Buffer buffer; + + static uint32_t encodedSize(const qpid::broker::PersistableExchange& e, + const qpid::broker::PersistableQueue& q, + const std::string& k, + const qpid::framing::FieldTable& a); + +public: + BindingDbt(const qpid::broker::PersistableExchange& e, + const qpid::broker::PersistableQueue& q, + const std::string& k, + const qpid::framing::FieldTable& a); + + virtual ~BindingDbt(); + +}; + +}} + +#endif // ifndef QPID_LEGACYSTORE_BINDINGDBT_H diff --git a/cpp/src/qpid/legacystore/BufferValue.cpp b/cpp/src/qpid/legacystore/BufferValue.cpp new file mode 100644 index 0000000000..fb2c471cd7 --- /dev/null +++ b/cpp/src/qpid/legacystore/BufferValue.cpp @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/legacystore/BufferValue.h" + +namespace mrg { +namespace msgstore { + + + +BufferValue::BufferValue(u_int32_t size, u_int64_t offset) + : data(new char[size]), + buffer(data, size) +{ + set_data(data); + set_size(size); + set_flags(DB_DBT_USERMEM | DB_DBT_PARTIAL); + set_doff(offset); + set_dlen(size); + set_ulen(size); +} + +BufferValue::BufferValue(const qpid::broker::Persistable& p) + : data(new char[p.encodedSize()]), + buffer(data, p.encodedSize()) +{ + p.encode(buffer); + + set_data(data); + set_size(p.encodedSize()); +} + +BufferValue::~BufferValue() +{ + delete [] data; +} + +}} diff --git a/cpp/src/qpid/legacystore/BufferValue.h b/cpp/src/qpid/legacystore/BufferValue.h new file mode 100644 index 0000000000..527fbcf577 --- /dev/null +++ b/cpp/src/qpid/legacystore/BufferValue.h @@ -0,0 +1,46 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_BUFFERVALUE_H +#define QPID_LEGACYSTORE_BUFFERVALUE_H + +#include "db-inc.h" +#include "qpid/broker/Persistable.h" +#include "qpid/framing/Buffer.h" + +namespace mrg{ +namespace msgstore{ + +class BufferValue : public Dbt +{ + char* data; + +public: + qpid::framing::Buffer buffer; + + BufferValue(u_int32_t size, u_int64_t offset); + BufferValue(const qpid::broker::Persistable& p); + virtual ~BufferValue(); +}; + +}} + +#endif // ifndef QPID_LEGACYSTORE_BUFFERVALUE_H diff --git a/cpp/src/qpid/legacystore/Cursor.h b/cpp/src/qpid/legacystore/Cursor.h new file mode 100644 index 0000000000..0c869c29a0 --- /dev/null +++ b/cpp/src/qpid/legacystore/Cursor.h @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_CURSOR_H +#define QPID_LEGACYSTORE_CURSOR_H + +#include <boost/shared_ptr.hpp> +#include "db-inc.h" + +namespace mrg{ +namespace msgstore{ + +class Cursor +{ + Dbc* cursor; +public: + typedef boost::shared_ptr<Db> db_ptr; + + Cursor() : cursor(0) {} + virtual ~Cursor() { if(cursor) cursor->close(); } + + void open(db_ptr db, DbTxn* txn, u_int32_t flags = 0) { db->cursor(txn, &cursor, flags); } + void close() { if(cursor) cursor->close(); cursor = 0; } + Dbc* get() { return cursor; } + Dbc* operator->() { return cursor; } + bool next(Dbt& key, Dbt& value) { return cursor->get(&key, &value, DB_NEXT) == 0; } + bool current(Dbt& key, Dbt& value) { return cursor->get(&key, &value, DB_CURRENT) == 0; } +}; + +}} + +#endif // ifndef QPID_LEGACYSTORE_CURSOR_H diff --git a/cpp/src/qpid/legacystore/DataTokenImpl.cpp b/cpp/src/qpid/legacystore/DataTokenImpl.cpp new file mode 100644 index 0000000000..796d4c02f0 --- /dev/null +++ b/cpp/src/qpid/legacystore/DataTokenImpl.cpp @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/legacystore/DataTokenImpl.h" + +using namespace mrg::msgstore; + +DataTokenImpl::DataTokenImpl():data_tok() {} + +DataTokenImpl::~DataTokenImpl() {} diff --git a/cpp/src/qpid/legacystore/DataTokenImpl.h b/cpp/src/qpid/legacystore/DataTokenImpl.h new file mode 100644 index 0000000000..e01d471e1b --- /dev/null +++ b/cpp/src/qpid/legacystore/DataTokenImpl.h @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_DATATOKENIMPL_H +#define QPID_LEGACYSTORE_DATATOKENIMPL_H + +#include "qpid/legacystore/jrnl/data_tok.h" +#include "qpid/broker/PersistableMessage.h" +#include <boost/intrusive_ptr.hpp> + +namespace mrg { +namespace msgstore { + +class DataTokenImpl : public journal::data_tok, public qpid::RefCounted +{ + private: + boost::intrusive_ptr<qpid::broker::PersistableMessage> sourceMsg; + public: + DataTokenImpl(); + virtual ~DataTokenImpl(); + + inline boost::intrusive_ptr<qpid::broker::PersistableMessage>& getSourceMessage() { return sourceMsg; } + inline void setSourceMessage(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg) { sourceMsg = msg; } +}; + +} // namespace msgstore +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_DATATOKENIMPL_H diff --git a/cpp/src/qpid/legacystore/IdDbt.cpp b/cpp/src/qpid/legacystore/IdDbt.cpp new file mode 100644 index 0000000000..d9edaf80e6 --- /dev/null +++ b/cpp/src/qpid/legacystore/IdDbt.cpp @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/legacystore/IdDbt.h" + +using namespace mrg::msgstore; + +IdDbt::IdDbt() : id(0) +{ + init(); +} + +IdDbt::IdDbt(u_int64_t _id) : id(_id) +{ + init(); +} + +void IdDbt::init() +{ + set_data(&id); + set_size(sizeof(u_int64_t)); + set_ulen(sizeof(u_int64_t)); + set_flags(DB_DBT_USERMEM); +} diff --git a/cpp/src/qpid/legacystore/IdDbt.h b/cpp/src/qpid/legacystore/IdDbt.h new file mode 100644 index 0000000000..ecf5922963 --- /dev/null +++ b/cpp/src/qpid/legacystore/IdDbt.h @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_IDDBT_H +#define QPID_LEGACYSTORE_IDDBT_H + +#include "db-inc.h" + +namespace mrg{ +namespace msgstore{ + +class IdDbt : public Dbt +{ + void init(); +public: + u_int64_t id; + + IdDbt(u_int64_t id); + IdDbt(); +}; + +}} + +#endif // ifndef QPID_LEGACYSTORE_IDDBT_H diff --git a/cpp/src/qpid/legacystore/IdSequence.cpp b/cpp/src/qpid/legacystore/IdSequence.cpp new file mode 100644 index 0000000000..975b1107e7 --- /dev/null +++ b/cpp/src/qpid/legacystore/IdSequence.cpp @@ -0,0 +1,40 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/legacystore/IdSequence.h" + +using namespace mrg::msgstore; +using qpid::sys::Mutex; + +IdSequence::IdSequence() : id(1) {} + +u_int64_t IdSequence::next() +{ + Mutex::ScopedLock guard(lock); + if (!id) id++; // avoid 0 when folding around + return id++; +} + +void IdSequence::reset(uint64_t value) +{ + //deliberately not threadsafe, used only on recovery + id = value; +} diff --git a/cpp/src/qpid/legacystore/IdSequence.h b/cpp/src/qpid/legacystore/IdSequence.h new file mode 100644 index 0000000000..11d7ff61ca --- /dev/null +++ b/cpp/src/qpid/legacystore/IdSequence.h @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_IDSEQUENCE_H +#define QPID_LEGACYSTORE_IDSEQUENCE_H + +#include "qpid/framing/amqp_types.h" +#include "qpid/sys/Mutex.h" +#include <sys/types.h> + +namespace mrg{ +namespace msgstore{ + +class IdSequence +{ + qpid::sys::Mutex lock; + uint64_t id; +public: + IdSequence(); + uint64_t next(); + void reset(uint64_t value); +}; + +}} + +#endif // ifndef QPID_LEGACYSTORE_IDSEQUENCE_H diff --git a/cpp/src/qpid/legacystore/JournalImpl.cpp b/cpp/src/qpid/legacystore/JournalImpl.cpp new file mode 100644 index 0000000000..ba3f2aecae --- /dev/null +++ b/cpp/src/qpid/legacystore/JournalImpl.cpp @@ -0,0 +1,633 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/legacystore/JournalImpl.h" + +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" +#include "qmf/org/apache/qpid/legacystore/ArgsJournalExpand.h" +#include "qmf/org/apache/qpid/legacystore/EventCreated.h" +#include "qmf/org/apache/qpid/legacystore/EventEnqThresholdExceeded.h" +#include "qmf/org/apache/qpid/legacystore/EventFull.h" +#include "qmf/org/apache/qpid/legacystore/EventRecovered.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Timer.h" +#include "qpid/legacystore/StoreException.h" + +using namespace mrg::msgstore; +using namespace mrg::journal; +using qpid::management::ManagementAgent; +namespace _qmf = qmf::org::apache::qpid::legacystore; + +InactivityFireEvent::InactivityFireEvent(JournalImpl* p, const qpid::sys::Duration timeout): + qpid::sys::TimerTask(timeout, "JournalInactive:"+p->id()), _parent(p) {} + +void InactivityFireEvent::fire() { qpid::sys::Mutex::ScopedLock sl(_ife_lock); if (_parent) _parent->flushFire(); } + +GetEventsFireEvent::GetEventsFireEvent(JournalImpl* p, const qpid::sys::Duration timeout): + qpid::sys::TimerTask(timeout, "JournalGetEvents:"+p->id()), _parent(p) {} + +void GetEventsFireEvent::fire() { qpid::sys::Mutex::ScopedLock sl(_gefe_lock); if (_parent) _parent->getEventsFire(); } + +JournalImpl::JournalImpl(qpid::sys::Timer& timer_, + const std::string& journalId, + const std::string& journalDirectory, + const std::string& journalBaseFilename, + const qpid::sys::Duration getEventsTimeout, + const qpid::sys::Duration flushTimeout, + qpid::management::ManagementAgent* a, + DeleteCallback onDelete): + jcntl(journalId, journalDirectory, journalBaseFilename), + timer(timer_), + getEventsTimerSetFlag(false), + lastReadRid(0), + writeActivityFlag(false), + flushTriggeredFlag(true), + _xidp(0), + _datap(0), + _dlen(0), + _dtok(), + _external(false), + deleteCallback(onDelete) +{ + getEventsFireEventsPtr = new GetEventsFireEvent(this, getEventsTimeout); + inactivityFireEventPtr = new InactivityFireEvent(this, flushTimeout); + { + timer.start(); + timer.add(inactivityFireEventPtr); + } + + initManagement(a); + + log(LOG_NOTICE, "Created"); + std::ostringstream oss; + oss << "Journal directory = \"" << journalDirectory << "\"; Base file name = \"" << journalBaseFilename << "\""; + log(LOG_DEBUG, oss.str()); +} + +JournalImpl::~JournalImpl() +{ + if (deleteCallback) deleteCallback(*this); + if (_init_flag && !_stop_flag){ + try { stop(true); } // NOTE: This will *block* until all outstanding disk aio calls are complete! + catch (const jexception& e) { log(LOG_ERROR, e.what()); } + } + getEventsFireEventsPtr->cancel(); + inactivityFireEventPtr->cancel(); + free_read_buffers(); + + if (_mgmtObject.get() != 0) { + _mgmtObject->resourceDestroy(); + _mgmtObject.reset(); + } + + log(LOG_NOTICE, "Destroyed"); +} + +void +JournalImpl::initManagement(qpid::management::ManagementAgent* a) +{ + _agent = a; + if (_agent != 0) + { + _mgmtObject = _qmf::Journal::shared_ptr ( + new _qmf::Journal(_agent, this)); + + _mgmtObject->set_name(_jid); + _mgmtObject->set_directory(_jdir.dirname()); + _mgmtObject->set_baseFileName(_base_filename); + _mgmtObject->set_readPageSize(JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE); + _mgmtObject->set_readPages(JRNL_RMGR_PAGES); + + // The following will be set on initialize(), but being properties, these must be set to 0 in the meantime + _mgmtObject->set_initialFileCount(0); + _mgmtObject->set_dataFileSize(0); + _mgmtObject->set_currentFileCount(0); + _mgmtObject->set_writePageSize(0); + _mgmtObject->set_writePages(0); + + _agent->addObject(_mgmtObject, 0, true); + } +} + + +void +JournalImpl::initialize(const u_int16_t num_jfiles, + const bool auto_expand, + const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, + const u_int16_t wcache_num_pages, + const u_int32_t wcache_pgsize_sblks, + mrg::journal::aio_callback* const cbp) +{ + std::ostringstream oss; + oss << "Initialize; num_jfiles=" << num_jfiles << " jfsize_sblks=" << jfsize_sblks; + oss << " wcache_pgsize_sblks=" << wcache_pgsize_sblks; + oss << " wcache_num_pages=" << wcache_num_pages; + log(LOG_DEBUG, oss.str()); + jcntl::initialize(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks, cbp); + log(LOG_DEBUG, "Initialization complete"); + + if (_mgmtObject.get() != 0) + { + _mgmtObject->set_initialFileCount(_lpmgr.num_jfiles()); + _mgmtObject->set_autoExpand(_lpmgr.is_ae()); + _mgmtObject->set_currentFileCount(_lpmgr.num_jfiles()); + _mgmtObject->set_maxFileCount(_lpmgr.ae_max_jfiles()); + _mgmtObject->set_dataFileSize(_jfsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE); + _mgmtObject->set_writePageSize(wcache_pgsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE); + _mgmtObject->set_writePages(wcache_num_pages); + } + if (_agent != 0) + _agent->raiseEvent(qmf::org::apache::qpid::legacystore::EventCreated(_jid, _jfsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE, _lpmgr.num_jfiles()), + qpid::management::ManagementAgent::SEV_NOTE); +} + +void +JournalImpl::recover(const u_int16_t num_jfiles, + const bool auto_expand, + const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, + const u_int16_t wcache_num_pages, + const u_int32_t wcache_pgsize_sblks, + mrg::journal::aio_callback* const cbp, + boost::ptr_list<msgstore::PreparedTransaction>* prep_tx_list_ptr, + u_int64_t& highest_rid, + u_int64_t queue_id) +{ + std::ostringstream oss1; + oss1 << "Recover; num_jfiles=" << num_jfiles << " jfsize_sblks=" << jfsize_sblks; + oss1 << " queue_id = 0x" << std::hex << queue_id << std::dec; + oss1 << " wcache_pgsize_sblks=" << wcache_pgsize_sblks; + oss1 << " wcache_num_pages=" << wcache_num_pages; + log(LOG_DEBUG, oss1.str()); + + if (_mgmtObject.get() != 0) + { + _mgmtObject->set_initialFileCount(_lpmgr.num_jfiles()); + _mgmtObject->set_autoExpand(_lpmgr.is_ae()); + _mgmtObject->set_currentFileCount(_lpmgr.num_jfiles()); + _mgmtObject->set_maxFileCount(_lpmgr.ae_max_jfiles()); + _mgmtObject->set_dataFileSize(_jfsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE); + _mgmtObject->set_writePageSize(wcache_pgsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE); + _mgmtObject->set_writePages(wcache_num_pages); + } + + if (prep_tx_list_ptr) { + // Create list of prepared xids + std::vector<std::string> prep_xid_list; + for (msgstore::PreparedTransaction::list::iterator i = prep_tx_list_ptr->begin(); i != prep_tx_list_ptr->end(); i++) { + prep_xid_list.push_back(i->xid); + } + + jcntl::recover(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks, + cbp, &prep_xid_list, highest_rid); + } else { + jcntl::recover(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks, + cbp, 0, highest_rid); + } + + // Populate PreparedTransaction lists from _tmap + if (prep_tx_list_ptr) + { + for (msgstore::PreparedTransaction::list::iterator i = prep_tx_list_ptr->begin(); i != prep_tx_list_ptr->end(); i++) { + txn_data_list tdl = _tmap.get_tdata_list(i->xid); // tdl will be empty if xid not found + for (tdl_itr tdl_itr = tdl.begin(); tdl_itr < tdl.end(); tdl_itr++) { + if (tdl_itr->_enq_flag) { // enqueue op + i->enqueues->add(queue_id, tdl_itr->_rid); + } else { // dequeue op + i->dequeues->add(queue_id, tdl_itr->_drid); + } + } + } + } + std::ostringstream oss2; + oss2 << "Recover phase 1 complete; highest rid found = 0x" << std::hex << highest_rid; + oss2 << std::dec << "; emap.size=" << _emap.size() << "; tmap.size=" << _tmap.size(); + oss2 << "; journal now read-only."; + log(LOG_DEBUG, oss2.str()); + + if (_mgmtObject.get() != 0) + { + _mgmtObject->inc_recordDepth(_emap.size()); + _mgmtObject->inc_enqueues(_emap.size()); + _mgmtObject->inc_txn(_tmap.size()); + _mgmtObject->inc_txnEnqueues(_tmap.enq_cnt()); + _mgmtObject->inc_txnDequeues(_tmap.deq_cnt()); + } +} + +void +JournalImpl::recover_complete() +{ + jcntl::recover_complete(); + log(LOG_DEBUG, "Recover phase 2 complete; journal now writable."); + if (_agent != 0) + _agent->raiseEvent(qmf::org::apache::qpid::legacystore::EventRecovered(_jid, _jfsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE, _lpmgr.num_jfiles(), + _emap.size(), _tmap.size(), _tmap.enq_cnt(), _tmap.deq_cnt()), qpid::management::ManagementAgent::SEV_NOTE); +} + +//#define MAX_AIO_SLEEPS 1000000 // tot: ~10 sec +//#define AIO_SLEEP_TIME_US 10 // 0.01 ms +// Return true if content is recovered from store; false if content is external and must be recovered from an external store. +// Throw exception for all errors. +bool +JournalImpl::loadMsgContent(u_int64_t rid, std::string& data, size_t length, size_t offset) +{ + qpid::sys::Mutex::ScopedLock sl(_read_lock); + if (_dtok.rid() != rid) + { + // Free any previous msg + free_read_buffers(); + + // Last read encountered out-of-order rids, check if this rid is in that list + bool oooFlag = false; + for (std::vector<u_int64_t>::const_iterator i=oooRidList.begin(); i!=oooRidList.end() && !oooFlag; i++) { + if (*i == rid) { + oooFlag = true; + } + } + + // TODO: This is a brutal approach - very inefficient and slow. Rather introduce a system of remembering + // jumpover points and allow the read to jump back to the first known jumpover point - but this needs + // a mechanism in rrfc to accomplish it. Also helpful is a struct containing a journal address - a + // combination of lid/offset. + // NOTE: The second part of the if stmt (rid < lastReadRid) is required to handle browsing. + if (oooFlag || rid < lastReadRid) { + _rmgr.invalidate(); + oooRidList.clear(); + } + _dlen = 0; + _dtok.reset(); + _dtok.set_wstate(DataTokenImpl::ENQ); + _dtok.set_rid(0); + _external = false; + size_t xlen = 0; + bool transient = false; + bool done = false; + bool rid_found = false; + while (!done) { + iores res = read_data_record(&_datap, _dlen, &_xidp, xlen, transient, _external, &_dtok); + switch (res) { + case mrg::journal::RHM_IORES_SUCCESS: + if (_dtok.rid() != rid) { + // Check if this is an out-of-order rid that may impact next read + if (_dtok.rid() > rid) + oooRidList.push_back(_dtok.rid()); + free_read_buffers(); + // Reset data token for next read + _dlen = 0; + _dtok.reset(); + _dtok.set_wstate(DataTokenImpl::ENQ); + _dtok.set_rid(0); + } else { + rid_found = _dtok.rid() == rid; + lastReadRid = rid; + done = true; + } + break; + case mrg::journal::RHM_IORES_PAGE_AIOWAIT: + if (get_wr_events(&_aio_cmpl_timeout) == journal::jerrno::AIO_TIMEOUT) { + std::stringstream ss; + ss << "read_data_record() returned " << mrg::journal::iores_str(res); + ss << "; timed out waiting for page to be processed."; + throw jexception(mrg::journal::jerrno::JERR__TIMEOUT, ss.str().c_str(), "JournalImpl", + "loadMsgContent"); + } + break; + default: + std::stringstream ss; + ss << "read_data_record() returned " << mrg::journal::iores_str(res); + throw jexception(mrg::journal::jerrno::JERR__UNEXPRESPONSE, ss.str().c_str(), "JournalImpl", + "loadMsgContent"); + } + } + if (!rid_found) { + std::stringstream ss; + ss << "read_data_record() was unable to find rid 0x" << std::hex << rid << std::dec; + ss << " (" << rid << "); last rid found was 0x" << std::hex << _dtok.rid() << std::dec; + ss << " (" << _dtok.rid() << ")"; + throw jexception(mrg::journal::jerrno::JERR__RECNFOUND, ss.str().c_str(), "JournalImpl", "loadMsgContent"); + } + } + + if (_external) return false; + + u_int32_t hdr_offs = qpid::framing::Buffer(static_cast<char*>(_datap), sizeof(u_int32_t)).getLong() + sizeof(u_int32_t); + if (hdr_offs + offset + length > _dlen) { + data.append((const char*)_datap + hdr_offs + offset, _dlen - hdr_offs - offset); + } else { + data.append((const char*)_datap + hdr_offs + offset, length); + } + return true; +} + +void +JournalImpl::enqueue_data_record(const void* const data_buff, const size_t tot_data_len, + const size_t this_data_len, data_tok* dtokp, const bool transient) +{ + handleIoResult(jcntl::enqueue_data_record(data_buff, tot_data_len, this_data_len, dtokp, transient)); + + if (_mgmtObject.get() != 0) + { + _mgmtObject->inc_enqueues(); + _mgmtObject->inc_recordDepth(); + } +} + +void +JournalImpl::enqueue_extern_data_record(const size_t tot_data_len, data_tok* dtokp, + const bool transient) +{ + handleIoResult(jcntl::enqueue_extern_data_record(tot_data_len, dtokp, transient)); + + if (_mgmtObject.get() != 0) + { + _mgmtObject->inc_enqueues(); + _mgmtObject->inc_recordDepth(); + } +} + +void +JournalImpl::enqueue_txn_data_record(const void* const data_buff, const size_t tot_data_len, + const size_t this_data_len, data_tok* dtokp, const std::string& xid, const bool transient) +{ + bool txn_incr = _mgmtObject.get() != 0 ? _tmap.in_map(xid) : false; + + handleIoResult(jcntl::enqueue_txn_data_record(data_buff, tot_data_len, this_data_len, dtokp, xid, transient)); + + if (_mgmtObject.get() != 0) + { + if (!txn_incr) // If this xid was not in _tmap, it will be now... + _mgmtObject->inc_txn(); + _mgmtObject->inc_enqueues(); + _mgmtObject->inc_txnEnqueues(); + _mgmtObject->inc_recordDepth(); + } +} + +void +JournalImpl::enqueue_extern_txn_data_record(const size_t tot_data_len, data_tok* dtokp, + const std::string& xid, const bool transient) +{ + bool txn_incr = _mgmtObject.get() != 0 ? _tmap.in_map(xid) : false; + + handleIoResult(jcntl::enqueue_extern_txn_data_record(tot_data_len, dtokp, xid, transient)); + + if (_mgmtObject.get() != 0) + { + if (!txn_incr) // If this xid was not in _tmap, it will be now... + _mgmtObject->inc_txn(); + _mgmtObject->inc_enqueues(); + _mgmtObject->inc_txnEnqueues(); + _mgmtObject->inc_recordDepth(); + } +} + +void +JournalImpl::dequeue_data_record(data_tok* const dtokp, const bool txn_coml_commit) +{ + handleIoResult(jcntl::dequeue_data_record(dtokp, txn_coml_commit)); + + if (_mgmtObject.get() != 0) + { + _mgmtObject->inc_dequeues(); + _mgmtObject->inc_txnDequeues(); + _mgmtObject->dec_recordDepth(); + } +} + +void +JournalImpl::dequeue_txn_data_record(data_tok* const dtokp, const std::string& xid, const bool txn_coml_commit) +{ + bool txn_incr = _mgmtObject.get() != 0 ? _tmap.in_map(xid) : false; + + handleIoResult(jcntl::dequeue_txn_data_record(dtokp, xid, txn_coml_commit)); + + if (_mgmtObject.get() != 0) + { + if (!txn_incr) // If this xid was not in _tmap, it will be now... + _mgmtObject->inc_txn(); + _mgmtObject->inc_dequeues(); + _mgmtObject->inc_txnDequeues(); + _mgmtObject->dec_recordDepth(); + } +} + +void +JournalImpl::txn_abort(data_tok* const dtokp, const std::string& xid) +{ + handleIoResult(jcntl::txn_abort(dtokp, xid)); + + if (_mgmtObject.get() != 0) + { + _mgmtObject->dec_txn(); + _mgmtObject->inc_txnAborts(); + } +} + +void +JournalImpl::txn_commit(data_tok* const dtokp, const std::string& xid) +{ + handleIoResult(jcntl::txn_commit(dtokp, xid)); + + if (_mgmtObject.get() != 0) + { + _mgmtObject->dec_txn(); + _mgmtObject->inc_txnCommits(); + } +} + +void +JournalImpl::stop(bool block_till_aio_cmpl) +{ + InactivityFireEvent* ifep = dynamic_cast<InactivityFireEvent*>(inactivityFireEventPtr.get()); + assert(ifep); // dynamic_cast can return null if the cast fails + ifep->cancel(); + jcntl::stop(block_till_aio_cmpl); + + if (_mgmtObject.get() != 0) { + _mgmtObject->resourceDestroy(); + _mgmtObject.reset(); + } +} + +iores +JournalImpl::flush(const bool block_till_aio_cmpl) +{ + const iores res = jcntl::flush(block_till_aio_cmpl); + { + qpid::sys::Mutex::ScopedLock sl(_getf_lock); + if (_wmgr.get_aio_evt_rem() && !getEventsTimerSetFlag) { setGetEventTimer(); } + } + return res; +} + +void +JournalImpl::log(mrg::journal::log_level ll, const std::string& log_stmt) const +{ + log(ll, log_stmt.c_str()); +} + +void +JournalImpl::log(mrg::journal::log_level ll, const char* const log_stmt) const +{ + switch (ll) + { + case LOG_TRACE: QPID_LOG(trace, "Journal \"" << _jid << "\": " << log_stmt); break; + case LOG_DEBUG: QPID_LOG(debug, "Journal \"" << _jid << "\": " << log_stmt); break; + case LOG_INFO: QPID_LOG(info, "Journal \"" << _jid << "\": " << log_stmt); break; + case LOG_NOTICE: QPID_LOG(notice, "Journal \"" << _jid << "\": " << log_stmt); break; + case LOG_WARN: QPID_LOG(warning, "Journal \"" << _jid << "\": " << log_stmt); break; + case LOG_ERROR: QPID_LOG(error, "Journal \"" << _jid << "\": " << log_stmt); break; + case LOG_CRITICAL: QPID_LOG(critical, "Journal \"" << _jid << "\": " << log_stmt); break; + } +} + +void +JournalImpl::getEventsFire() +{ + qpid::sys::Mutex::ScopedLock sl(_getf_lock); + getEventsTimerSetFlag = false; + if (_wmgr.get_aio_evt_rem()) { jcntl::get_wr_events(0); } + if (_wmgr.get_aio_evt_rem()) { setGetEventTimer(); } +} + +void +JournalImpl::flushFire() +{ + if (writeActivityFlag) { + writeActivityFlag = false; + flushTriggeredFlag = false; + } else { + if (!flushTriggeredFlag) { + flush(); + flushTriggeredFlag = true; + } + } + inactivityFireEventPtr->setupNextFire(); + { + timer.add(inactivityFireEventPtr); + } +} + +void +JournalImpl::wr_aio_cb(std::vector<data_tok*>& dtokl) +{ + for (std::vector<data_tok*>::const_iterator i=dtokl.begin(); i!=dtokl.end(); i++) + { + DataTokenImpl* dtokp = static_cast<DataTokenImpl*>(*i); + if (/*!is_stopped() &&*/ dtokp->getSourceMessage()) + { + switch (dtokp->wstate()) + { + case data_tok::ENQ: + dtokp->getSourceMessage()->enqueueComplete(); + break; + case data_tok::DEQ: +/* Don't need to signal until we have a way to ack completion of dequeue in AMQP + dtokp->getSourceMessage()->dequeueComplete(); + if ( dtokp->getSourceMessage()->isDequeueComplete() ) // clear id after last dequeue + dtokp->getSourceMessage()->setPersistenceId(0); +*/ + break; + default: ; + } + } + dtokp->release(); + } +} + +void +JournalImpl::rd_aio_cb(std::vector<u_int16_t>& /*pil*/) +{} + +void +JournalImpl::free_read_buffers() +{ + if (_xidp) { + ::free(_xidp); + _xidp = 0; + _datap = 0; + } else if (_datap) { + ::free(_datap); + _datap = 0; + } +} + +void +JournalImpl::handleIoResult(const iores r) +{ + writeActivityFlag = true; + switch (r) + { + case mrg::journal::RHM_IORES_SUCCESS: + return; + case mrg::journal::RHM_IORES_ENQCAPTHRESH: + { + std::ostringstream oss; + oss << "Enqueue capacity threshold exceeded on queue \"" << _jid << "\"."; + log(LOG_WARN, oss.str()); + if (_agent != 0) + _agent->raiseEvent(qmf::org::apache::qpid::legacystore::EventEnqThresholdExceeded(_jid, "Journal enqueue capacity threshold exceeded"), + qpid::management::ManagementAgent::SEV_WARN); + THROW_STORE_FULL_EXCEPTION(oss.str()); + } + case mrg::journal::RHM_IORES_FULL: + { + std::ostringstream oss; + oss << "Journal full on queue \"" << _jid << "\"."; + log(LOG_CRITICAL, oss.str()); + if (_agent != 0) + _agent->raiseEvent(qmf::org::apache::qpid::legacystore::EventFull(_jid, "Journal full"), qpid::management::ManagementAgent::SEV_ERROR); + THROW_STORE_FULL_EXCEPTION(oss.str()); + } + default: + { + std::ostringstream oss; + oss << "Unexpected I/O response (" << mrg::journal::iores_str(r) << ") on queue " << _jid << "\"."; + log(LOG_ERROR, oss.str()); + THROW_STORE_FULL_EXCEPTION(oss.str()); + } + } +} + +qpid::management::Manageable::status_t JournalImpl::ManagementMethod (uint32_t methodId, + qpid::management::Args& /*args*/, + std::string& /*text*/) +{ + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + + switch (methodId) + { + case _qmf::Journal::METHOD_EXPAND : + //_qmf::ArgsJournalExpand& eArgs = (_qmf::ArgsJournalExpand&) args; + + // Implement "expand" using eArgs.i_by (expand-by argument) + + status = Manageable::STATUS_NOT_IMPLEMENTED; + break; + } + + return status; +} diff --git a/cpp/src/qpid/legacystore/JournalImpl.h b/cpp/src/qpid/legacystore/JournalImpl.h new file mode 100644 index 0000000000..7227b2ffd4 --- /dev/null +++ b/cpp/src/qpid/legacystore/JournalImpl.h @@ -0,0 +1,265 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_JOURNALIMPL_H +#define QPID_LEGACYSTORE_JOURNALIMPL_H + +#include <set> +#include "qpid/legacystore/jrnl/enums.h" +#include "qpid/legacystore/jrnl/jcntl.h" +#include "qpid/legacystore/DataTokenImpl.h" +#include "qpid/legacystore/PreparedTransaction.h" +#include "qpid/broker/PersistableQueue.h" +#include "qpid/sys/Timer.h" +#include "qpid/sys/Time.h" +#include <boost/ptr_container/ptr_list.hpp> +#include <boost/intrusive_ptr.hpp> +#include "qpid/management/Manageable.h" +#include "qmf/org/apache/qpid/legacystore/Journal.h" + +namespace qpid { namespace sys { +class Timer; +}} + +namespace mrg { +namespace msgstore { + +class JournalImpl; + +class InactivityFireEvent : public qpid::sys::TimerTask +{ + JournalImpl* _parent; + qpid::sys::Mutex _ife_lock; + + public: + InactivityFireEvent(JournalImpl* p, const qpid::sys::Duration timeout); + virtual ~InactivityFireEvent() {} + void fire(); + inline void cancel() { qpid::sys::Mutex::ScopedLock sl(_ife_lock); _parent = 0; } +}; + +class GetEventsFireEvent : public qpid::sys::TimerTask +{ + JournalImpl* _parent; + qpid::sys::Mutex _gefe_lock; + + public: + GetEventsFireEvent(JournalImpl* p, const qpid::sys::Duration timeout); + virtual ~GetEventsFireEvent() {} + void fire(); + inline void cancel() { qpid::sys::Mutex::ScopedLock sl(_gefe_lock); _parent = 0; } +}; + +class JournalImpl : public qpid::broker::ExternalQueueStore, public mrg::journal::jcntl, public mrg::journal::aio_callback +{ + public: + typedef boost::function<void (JournalImpl&)> DeleteCallback; + + private: +// static qpid::sys::Mutex _static_lock; +// static u_int32_t cnt; + + qpid::sys::Timer& timer; + bool getEventsTimerSetFlag; + boost::intrusive_ptr<qpid::sys::TimerTask> getEventsFireEventsPtr; + qpid::sys::Mutex _getf_lock; + qpid::sys::Mutex _read_lock; + + u_int64_t lastReadRid; // rid of last read msg for loadMsgContent() - detects out-of-order read requests + std::vector<u_int64_t> oooRidList; // list of out-of-order rids (greater than current rid) encountered during read sequence + + bool writeActivityFlag; + bool flushTriggeredFlag; + boost::intrusive_ptr<qpid::sys::TimerTask> inactivityFireEventPtr; + + // temp local vars for loadMsgContent below + void* _xidp; + void* _datap; + size_t _dlen; + mrg::journal::data_tok _dtok; + bool _external; + + qpid::management::ManagementAgent* _agent; + qmf::org::apache::qpid::legacystore::Journal::shared_ptr _mgmtObject; + DeleteCallback deleteCallback; + + public: + + JournalImpl(qpid::sys::Timer& timer, + const std::string& journalId, + const std::string& journalDirectory, + const std::string& journalBaseFilename, + const qpid::sys::Duration getEventsTimeout, + const qpid::sys::Duration flushTimeout, + qpid::management::ManagementAgent* agent, + DeleteCallback deleteCallback=DeleteCallback() ); + + virtual ~JournalImpl(); + + void initManagement(qpid::management::ManagementAgent* agent); + + void initialize(const u_int16_t num_jfiles, + const bool auto_expand, + const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, + const u_int16_t wcache_num_pages, + const u_int32_t wcache_pgsize_sblks, + mrg::journal::aio_callback* const cbp); + + inline void initialize(const u_int16_t num_jfiles, + const bool auto_expand, + const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, + const u_int16_t wcache_num_pages, + const u_int32_t wcache_pgsize_sblks) { + initialize(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks, + this); + } + + void recover(const u_int16_t num_jfiles, + const bool auto_expand, + const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, + const u_int16_t wcache_num_pages, + const u_int32_t wcache_pgsize_sblks, + mrg::journal::aio_callback* const cbp, + boost::ptr_list<msgstore::PreparedTransaction>* prep_tx_list_ptr, + u_int64_t& highest_rid, + u_int64_t queue_id); + + inline void recover(const u_int16_t num_jfiles, + const bool auto_expand, + const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, + const u_int16_t wcache_num_pages, + const u_int32_t wcache_pgsize_sblks, + boost::ptr_list<msgstore::PreparedTransaction>* prep_tx_list_ptr, + u_int64_t& highest_rid, + u_int64_t queue_id) { + recover(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks, + this, prep_tx_list_ptr, highest_rid, queue_id); + } + + void recover_complete(); + + // Temporary fn to read and save last msg read from journal so it can be assigned + // in chunks. To be replaced when coding to do this direct from the journal is ready. + // Returns true if the record is extern, false if local. + bool loadMsgContent(u_int64_t rid, std::string& data, size_t length, size_t offset = 0); + + // Overrides for write inactivity timer + void enqueue_data_record(const void* const data_buff, const size_t tot_data_len, + const size_t this_data_len, mrg::journal::data_tok* dtokp, + const bool transient = false); + + void enqueue_extern_data_record(const size_t tot_data_len, mrg::journal::data_tok* dtokp, + const bool transient = false); + + void enqueue_txn_data_record(const void* const data_buff, const size_t tot_data_len, + const size_t this_data_len, mrg::journal::data_tok* dtokp, const std::string& xid, + const bool transient = false); + + void enqueue_extern_txn_data_record(const size_t tot_data_len, mrg::journal::data_tok* dtokp, + const std::string& xid, const bool transient = false); + + void dequeue_data_record(mrg::journal::data_tok* const dtokp, const bool txn_coml_commit = false); + + void dequeue_txn_data_record(mrg::journal::data_tok* const dtokp, const std::string& xid, const bool txn_coml_commit = false); + + void txn_abort(mrg::journal::data_tok* const dtokp, const std::string& xid); + + void txn_commit(mrg::journal::data_tok* const dtokp, const std::string& xid); + + void stop(bool block_till_aio_cmpl = false); + + // Logging + void log(mrg::journal::log_level level, const std::string& log_stmt) const; + void log(mrg::journal::log_level level, const char* const log_stmt) const; + + // Overrides for get_events timer + mrg::journal::iores flush(const bool block_till_aio_cmpl = false); + + // TimerTask callback + void getEventsFire(); + void flushFire(); + + // AIO callbacks + virtual void wr_aio_cb(std::vector<mrg::journal::data_tok*>& dtokl); + virtual void rd_aio_cb(std::vector<u_int16_t>& pil); + + qpid::management::ManagementObject::shared_ptr GetManagementObject (void) const + { return _mgmtObject; } + + qpid::management::Manageable::status_t ManagementMethod (uint32_t, + qpid::management::Args&, + std::string&); + + void resetDeleteCallback() { deleteCallback = DeleteCallback(); } + + private: + void free_read_buffers(); + + inline void setGetEventTimer() + { + getEventsFireEventsPtr->setupNextFire(); + timer.add(getEventsFireEventsPtr); + getEventsTimerSetFlag = true; + } + void handleIoResult(const mrg::journal::iores r); + + // Management instrumentation callbacks overridden from jcntl + inline void instr_incr_outstanding_aio_cnt() { + if (_mgmtObject.get() != 0) _mgmtObject->inc_outstandingAIOs(); + } + inline void instr_decr_outstanding_aio_cnt() { + if (_mgmtObject.get() != 0) _mgmtObject->dec_outstandingAIOs(); + } + +}; // class JournalImpl + +class TplJournalImpl : public JournalImpl +{ + public: + TplJournalImpl(qpid::sys::Timer& timer, + const std::string& journalId, + const std::string& journalDirectory, + const std::string& journalBaseFilename, + const qpid::sys::Duration getEventsTimeout, + const qpid::sys::Duration flushTimeout, + qpid::management::ManagementAgent* agent) : + JournalImpl(timer, journalId, journalDirectory, journalBaseFilename, getEventsTimeout, flushTimeout, agent) + {} + + virtual ~TplJournalImpl() {} + + // Special version of read_data_record that ignores transactions - needed when reading the TPL + inline mrg::journal::iores read_data_record(void** const datapp, std::size_t& dsize, + void** const xidpp, std::size_t& xidsize, bool& transient, bool& external, + mrg::journal::data_tok* const dtokp) { + return JournalImpl::read_data_record(datapp, dsize, xidpp, xidsize, transient, external, dtokp, true); + } + inline void read_reset() { _rmgr.invalidate(); } +}; // class TplJournalImpl + +} // namespace msgstore +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JOURNALIMPL_H diff --git a/cpp/src/qpid/legacystore/MessageStoreImpl.cpp b/cpp/src/qpid/legacystore/MessageStoreImpl.cpp new file mode 100644 index 0000000000..69e9f48a17 --- /dev/null +++ b/cpp/src/qpid/legacystore/MessageStoreImpl.cpp @@ -0,0 +1,1732 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/legacystore/MessageStoreImpl.h" + +#include "qpid/legacystore/BindingDbt.h" +#include "qpid/legacystore/BufferValue.h" +#include "qpid/legacystore/IdDbt.h" +#include "qpid/legacystore/jrnl/txn_map.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/log/Statement.h" +#include "qmf/org/apache/qpid/legacystore/Package.h" +#include "qpid/legacystore/StoreException.h" +#include <dirent.h> +#include <db.h> + +#define MAX_AIO_SLEEPS 100000 // tot: ~1 sec +#define AIO_SLEEP_TIME_US 10 // 0.01 ms + +namespace _qmf = qmf::org::apache::qpid::legacystore; + +namespace mrg { +namespace msgstore { + + +const std::string MessageStoreImpl::storeTopLevelDir("rhm"); // Sets the top-level store dir name +// FIXME aconway 2010-03-09: was 10 +qpid::sys::Duration MessageStoreImpl::defJournalGetEventsTimeout(1 * qpid::sys::TIME_MSEC); // 10ms +qpid::sys::Duration MessageStoreImpl::defJournalFlushTimeout(500 * qpid::sys::TIME_MSEC); // 0.5s +qpid::sys::Mutex TxnCtxt::globalSerialiser; + +MessageStoreImpl::TplRecoverStruct::TplRecoverStruct(const u_int64_t _rid, + const bool _deq_flag, + const bool _commit_flag, + const bool _tpc_flag) : + rid(_rid), + deq_flag(_deq_flag), + commit_flag(_commit_flag), + tpc_flag(_tpc_flag) +{} + +MessageStoreImpl::MessageStoreImpl(qpid::broker::Broker* broker_, const char* envpath) : + numJrnlFiles(0), + autoJrnlExpand(false), + autoJrnlExpandMaxFiles(0), + jrnlFsizeSblks(0), + truncateFlag(false), + wCachePgSizeSblks(0), + wCacheNumPages(0), + tplNumJrnlFiles(0), + tplJrnlFsizeSblks(0), + tplWCachePgSizeSblks(0), + tplWCacheNumPages(0), + highestRid(0), + isInit(false), + envPath(envpath), + broker(broker_), + mgmtObject(), + agent(0) +{} + +u_int16_t MessageStoreImpl::chkJrnlNumFilesParam(const u_int16_t param, const std::string paramName) +{ + u_int16_t p = param; + if (p < JRNL_MIN_NUM_FILES) { + p = JRNL_MIN_NUM_FILES; + QPID_LOG(warning, "parameter " << paramName << " (" << param << ") is below allowable minimum (" << JRNL_MIN_NUM_FILES << "); changing this parameter to minimum value."); + } else if (p > JRNL_MAX_NUM_FILES) { + p = JRNL_MAX_NUM_FILES; + QPID_LOG(warning, "parameter " << paramName << " (" << param << ") is above allowable maximum (" << JRNL_MAX_NUM_FILES << "); changing this parameter to maximum value."); + } + return p; +} + +u_int32_t MessageStoreImpl::chkJrnlFileSizeParam(const u_int32_t param, const std::string paramName, const u_int32_t wCachePgSizeSblks) +{ + u_int32_t p = param; + u_int32_t min = JRNL_MIN_FILE_SIZE / JRNL_RMGR_PAGE_SIZE; + u_int32_t max = JRNL_MAX_FILE_SIZE / JRNL_RMGR_PAGE_SIZE; + if (p < min) { + p = min; + QPID_LOG(warning, "parameter " << paramName << " (" << param << ") is below allowable minimum (" << min << "); changing this parameter to minimum value."); + } else if (p > max) { + p = max; + QPID_LOG(warning, "parameter " << paramName << " (" << param << ") is above allowable maximum (" << max << "); changing this parameter to maximum value."); + } + if (wCachePgSizeSblks > p * JRNL_RMGR_PAGE_SIZE) { + std::ostringstream oss; + oss << "Cannot create store with file size less than write page cache size. [file size = " << p << " (" << (p * JRNL_RMGR_PAGE_SIZE / 2) << " kB); write page cache = " << (wCachePgSizeSblks / 2) << " kB]"; + THROW_STORE_EXCEPTION(oss.str()); + } + return p; +} + +u_int32_t MessageStoreImpl::chkJrnlWrPageCacheSize(const u_int32_t param, const std::string paramName, const u_int16_t jrnlFsizePgs) +{ + u_int32_t p = param; + switch (p) + { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + case 128: + if (jrnlFsizePgs == 1) { + p = 64; + QPID_LOG(warning, "parameter " << paramName << " (" << param << ") cannot set a page size greater than the journal file size; changing this parameter to the journal file size (" << p << ")"); + } + break; + default: + if (p == 0) { + // For zero value, use default + p = JRNL_WMGR_DEF_PAGE_SIZE * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE / 1024; + QPID_LOG(warning, "parameter " << paramName << " (" << param << ") must be a power of 2 between 1 and 128; changing this parameter to default value (" << p << ")"); + } else { + // For any positive value, use closest value + if (p < 6) p = 4; + else if (p < 12) p = 8; + else if (p < 24) p = 16; + else if (p < 48) p = 32; + else if (p < 96) p = 64; + else p = 128; + QPID_LOG(warning, "parameter " << paramName << " (" << param << ") must be a power of 2 between 1 and 128; changing this parameter to closest allowable value (" << p << ")"); + } + } + return p; +} + +u_int16_t MessageStoreImpl::getJrnlWrNumPages(const u_int32_t wrPageSizeKib) +{ + u_int32_t wrPageSizeSblks = wrPageSizeKib * 1024 / JRNL_DBLK_SIZE / JRNL_SBLK_SIZE; // convert from KiB to number sblks + u_int32_t defTotWCacheSize = JRNL_WMGR_DEF_PAGE_SIZE * JRNL_WMGR_DEF_PAGES; // in sblks. Currently 2014 sblks (1 MiB). + switch (wrPageSizeKib) + { + case 1: + case 2: + case 4: + // 256 KiB total cache + return defTotWCacheSize / wrPageSizeSblks / 4; + case 8: + case 16: + // 512 KiB total cache + return defTotWCacheSize / wrPageSizeSblks / 2; + default: // 32, 64, 128 + // 1 MiB total cache + return defTotWCacheSize / wrPageSizeSblks; + } +} + +void MessageStoreImpl::chkJrnlAutoExpandOptions(const StoreOptions* opts, + bool& autoJrnlExpand, + u_int16_t& autoJrnlExpandMaxFiles, + const std::string& autoJrnlExpandMaxFilesParamName, + const u_int16_t numJrnlFiles, + const std::string& numJrnlFilesParamName) +{ + if (!opts->autoJrnlExpand) { + // auto-expand disabled + autoJrnlExpand = false; + autoJrnlExpandMaxFiles = 0; + return; + } + u_int16_t p = opts->autoJrnlExpandMaxFiles; + if (numJrnlFiles == JRNL_MAX_NUM_FILES) { + // num-jfiles at max; disable auto-expand + autoJrnlExpand = false; + autoJrnlExpandMaxFiles = 0; + QPID_LOG(warning, "parameter " << autoJrnlExpandMaxFilesParamName << " (" << p << ") must be higher than parameter " + << numJrnlFilesParamName << " (" << numJrnlFiles << ") which is at the maximum allowable value; disabling auto-expand."); + return; + } + if (p > JRNL_MAX_NUM_FILES) { + // auto-expand-max-jfiles higher than max allowable, adjust + autoJrnlExpand = true; + autoJrnlExpandMaxFiles = JRNL_MAX_NUM_FILES; + QPID_LOG(warning, "parameter " << autoJrnlExpandMaxFilesParamName << " (" << p << ") is above allowable maximum (" + << JRNL_MAX_NUM_FILES << "); changing this parameter to maximum value."); + return; + } + if (p && p == defAutoJrnlExpandMaxFiles && numJrnlFiles != defTplNumJrnlFiles) { + // num-jfiles is different from the default AND max-auto-expand-jfiles is still at default + // change value of max-auto-expand-jfiles + autoJrnlExpand = true; + if (2 * numJrnlFiles <= JRNL_MAX_NUM_FILES) { + autoJrnlExpandMaxFiles = 2 * numJrnlFiles <= JRNL_MAX_NUM_FILES ? 2 * numJrnlFiles : JRNL_MAX_NUM_FILES; + QPID_LOG(warning, "parameter " << autoJrnlExpandMaxFilesParamName << " adjusted from its default value (" + << defAutoJrnlExpandMaxFiles << ") to twice that of parameter " << numJrnlFilesParamName << " (" << autoJrnlExpandMaxFiles << ")."); + } else { + autoJrnlExpandMaxFiles = 2 * numJrnlFiles <= JRNL_MAX_NUM_FILES ? 2 * numJrnlFiles : JRNL_MAX_NUM_FILES; + QPID_LOG(warning, "parameter " << autoJrnlExpandMaxFilesParamName << " adjusted from its default to maximum allowable value (" + << JRNL_MAX_NUM_FILES << ") because of the value of " << numJrnlFilesParamName << " (" << numJrnlFiles << ")."); + } + return; + } + // No adjustments req'd, set values + autoJrnlExpand = true; + autoJrnlExpandMaxFiles = p; +} + +void MessageStoreImpl::initManagement () +{ + if (broker != 0) { + agent = broker->getManagementAgent(); + if (agent != 0) { + _qmf::Package packageInitializer(agent); + mgmtObject = _qmf::Store::shared_ptr ( + new _qmf::Store(agent, this, broker)); + + mgmtObject->set_location(storeDir); + mgmtObject->set_defaultInitialFileCount(numJrnlFiles); + mgmtObject->set_defaultDataFileSize(jrnlFsizeSblks / JRNL_RMGR_PAGE_SIZE); + mgmtObject->set_tplIsInitialized(false); + mgmtObject->set_tplDirectory(getTplBaseDir()); + mgmtObject->set_tplWritePageSize(tplWCachePgSizeSblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE); + mgmtObject->set_tplWritePages(tplWCacheNumPages); + mgmtObject->set_tplInitialFileCount(tplNumJrnlFiles); + mgmtObject->set_tplDataFileSize(tplJrnlFsizeSblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE); + mgmtObject->set_tplCurrentFileCount(tplNumJrnlFiles); + + agent->addObject(mgmtObject, 0, true); + + // Initialize all existing queues (ie those recovered before management was initialized) + for (JournalListMapItr i=journalList.begin(); i!=journalList.end(); i++) { + i->second->initManagement(agent); + } + } + } +} + +bool MessageStoreImpl::init(const qpid::Options* options) +{ + // Extract and check options + const StoreOptions* opts = static_cast<const StoreOptions*>(options); + u_int16_t numJrnlFiles = chkJrnlNumFilesParam(opts->numJrnlFiles, "num-jfiles"); + u_int32_t jrnlFsizePgs = chkJrnlFileSizeParam(opts->jrnlFsizePgs, "jfile-size-pgs"); + u_int32_t jrnlWrCachePageSizeKib = chkJrnlWrPageCacheSize(opts->wCachePageSizeKib, "wcache-page-size", jrnlFsizePgs); + u_int16_t tplNumJrnlFiles = chkJrnlNumFilesParam(opts->tplNumJrnlFiles, "tpl-num-jfiles"); + u_int32_t tplJrnlFSizePgs = chkJrnlFileSizeParam(opts->tplJrnlFsizePgs, "tpl-jfile-size-pgs"); + u_int32_t tplJrnlWrCachePageSizeKib = chkJrnlWrPageCacheSize(opts->tplWCachePageSizeKib, "tpl-wcache-page-size", tplJrnlFSizePgs); + bool autoJrnlExpand; + u_int16_t autoJrnlExpandMaxFiles; + chkJrnlAutoExpandOptions(opts, autoJrnlExpand, autoJrnlExpandMaxFiles, "auto-expand-max-jfiles", numJrnlFiles, "num-jfiles"); + + // Pass option values to init(...) + return init(opts->storeDir, numJrnlFiles, jrnlFsizePgs, opts->truncateFlag, jrnlWrCachePageSizeKib, tplNumJrnlFiles, tplJrnlFSizePgs, tplJrnlWrCachePageSizeKib, autoJrnlExpand, autoJrnlExpandMaxFiles); +} + +// These params, taken from options, are assumed to be correct and verified +bool MessageStoreImpl::init(const std::string& dir, + u_int16_t jfiles, + u_int32_t jfileSizePgs, + const bool truncateFlag, + u_int32_t wCachePageSizeKib, + u_int16_t tplJfiles, + u_int32_t tplJfileSizePgs, + u_int32_t tplWCachePageSizeKib, + bool autoJExpand, + u_int16_t autoJExpandMaxFiles) +{ + if (isInit) return true; + + // Set geometry members (converting to correct units where req'd) + numJrnlFiles = jfiles; + jrnlFsizeSblks = jfileSizePgs * JRNL_RMGR_PAGE_SIZE; + wCachePgSizeSblks = wCachePageSizeKib * 1024 / JRNL_DBLK_SIZE / JRNL_SBLK_SIZE; // convert from KiB to number sblks + wCacheNumPages = getJrnlWrNumPages(wCachePageSizeKib); + tplNumJrnlFiles = tplJfiles; + tplJrnlFsizeSblks = tplJfileSizePgs * JRNL_RMGR_PAGE_SIZE; + tplWCachePgSizeSblks = tplWCachePageSizeKib * 1024 / JRNL_DBLK_SIZE / JRNL_SBLK_SIZE; // convert from KiB to number sblks + tplWCacheNumPages = getJrnlWrNumPages(tplWCachePageSizeKib); + autoJrnlExpand = autoJExpand; + autoJrnlExpandMaxFiles = autoJExpandMaxFiles; + if (dir.size()>0) storeDir = dir; + + if (truncateFlag) + truncateInit(false); + else + init(); + + QPID_LOG(notice, "Store module initialized; store-dir=" << dir); + QPID_LOG(info, "> Default files per journal: " << jfiles); +// TODO: Uncomment these lines when auto-expand is enabled. +// QPID_LOG(info, "> Auto-expand " << (autoJrnlExpand ? "enabled" : "disabled")); +// if (autoJrnlExpand) QPID_LOG(info, "> Max auto-expand journal files: " << autoJrnlExpandMaxFiles); + QPID_LOG(info, "> Default journal file size: " << jfileSizePgs << " (wpgs)"); + QPID_LOG(info, "> Default write cache page size: " << wCachePageSizeKib << " (KiB)"); + QPID_LOG(info, "> Default number of write cache pages: " << wCacheNumPages); + QPID_LOG(info, "> TPL files per journal: " << tplNumJrnlFiles); + QPID_LOG(info, "> TPL journal file size: " << tplJfileSizePgs << " (wpgs)"); + QPID_LOG(info, "> TPL write cache page size: " << tplWCachePageSizeKib << " (KiB)"); + QPID_LOG(info, "> TPL number of write cache pages: " << tplWCacheNumPages); + + return isInit; +} + +void MessageStoreImpl::init() +{ + const int retryMax = 3; + int bdbRetryCnt = 0; + do { + if (bdbRetryCnt++ > 0) + { + closeDbs(); + ::usleep(1000000); // 1 sec delay + QPID_LOG(error, "Previoius BDB store initialization failed, retrying (" << bdbRetryCnt << " of " << retryMax << ")..."); + } + + try { + journal::jdir::create_dir(getBdbBaseDir()); + + dbenv.reset(new DbEnv(0)); + dbenv->set_errpfx("msgstore"); + dbenv->set_lg_regionmax(256000); // default = 65000 + dbenv->open(getBdbBaseDir().c_str(), DB_THREAD | DB_CREATE | DB_INIT_TXN | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_USE_ENVIRON | DB_RECOVER, 0); + + // Databases are constructed here instead of the constructor so that the DB_RECOVER flag can be used + // against the database environment. Recover can only be performed if no databases have been created + // against the environment at the time of recovery, as recovery invalidates the environment. + queueDb.reset(new Db(dbenv.get(), 0)); + dbs.push_back(queueDb); + configDb.reset(new Db(dbenv.get(), 0)); + dbs.push_back(configDb); + exchangeDb.reset(new Db(dbenv.get(), 0)); + dbs.push_back(exchangeDb); + mappingDb.reset(new Db(dbenv.get(), 0)); + dbs.push_back(mappingDb); + bindingDb.reset(new Db(dbenv.get(), 0)); + dbs.push_back(bindingDb); + generalDb.reset(new Db(dbenv.get(), 0)); + dbs.push_back(generalDb); + + TxnCtxt txn; + txn.begin(dbenv.get(), false); + try { + open(queueDb, txn.get(), "queues.db", false); + open(configDb, txn.get(), "config.db", false); + open(exchangeDb, txn.get(), "exchanges.db", false); + open(mappingDb, txn.get(), "mappings.db", true); + open(bindingDb, txn.get(), "bindings.db", true); + open(generalDb, txn.get(), "general.db", false); + txn.commit(); + } catch (...) { txn.abort(); throw; } + // NOTE: during normal initialization, agent == 0 because the store is initialized before the management infrastructure. + // However during a truncated initialization in a cluster, agent != 0. We always pass 0 as the agent for the + // TplStore to keep things consistent in a cluster. See https://bugzilla.redhat.com/show_bug.cgi?id=681026 + tplStorePtr.reset(new TplJournalImpl(broker->getTimer(), "TplStore", getTplBaseDir(), "tpl", defJournalGetEventsTimeout, defJournalFlushTimeout, 0)); + isInit = true; + } catch (const DbException& e) { + if (e.get_errno() == DB_VERSION_MISMATCH) + { + QPID_LOG(error, "Database environment mismatch: This version of db4 does not match that which created the store database.: " << e.what()); + THROW_STORE_EXCEPTION_2("Database environment mismatch: This version of db4 does not match that which created the store database. " + "(If recovery is not important, delete the contents of the store directory. Otherwise, try upgrading the database using " + "db_upgrade or using db_recover - but the db4-utils package must also be installed to use these utilities.)", e); + } + QPID_LOG(error, "BDB exception occurred while initializing store: " << e.what()); + if (bdbRetryCnt >= retryMax) + THROW_STORE_EXCEPTION_2("BDB exception occurred while initializing store", e); + } catch (const StoreException&) { + throw; + } catch (const journal::jexception& e) { + QPID_LOG(error, "Journal Exception occurred while initializing store: " << e); + THROW_STORE_EXCEPTION_2("Journal Exception occurred while initializing store", e.what()); + } catch (...) { + QPID_LOG(error, "Unknown exception occurred while initializing store."); + throw; + } + } while (!isInit); +} + +void MessageStoreImpl::finalize() +{ + if (tplStorePtr.get() && tplStorePtr->is_ready()) tplStorePtr->stop(true); + { + qpid::sys::Mutex::ScopedLock sl(journalListLock); + for (JournalListMapItr i = journalList.begin(); i != journalList.end(); i++) + { + JournalImpl* jQueue = i->second; + jQueue->resetDeleteCallback(); + if (jQueue->is_ready()) jQueue->stop(true); + } + } + + if (mgmtObject.get() != 0) { + mgmtObject->resourceDestroy(); + mgmtObject.reset(); + } +} + +void MessageStoreImpl::truncateInit(const bool saveStoreContent) +{ + if (isInit) { + { + qpid::sys::Mutex::ScopedLock sl(journalListLock); + if (journalList.size()) { // check no queues exist + std::ostringstream oss; + oss << "truncateInit() called with " << journalList.size() << " queues still in existence"; + THROW_STORE_EXCEPTION(oss.str()); + } + } + closeDbs(); + dbs.clear(); + if (tplStorePtr->is_ready()) tplStorePtr->stop(true); + dbenv->close(0); + isInit = false; + } + std::ostringstream oss; + oss << storeDir << "/" << storeTopLevelDir; + if (saveStoreContent) { + std::string dir = mrg::journal::jdir::push_down(storeDir, storeTopLevelDir, "cluster"); + QPID_LOG(notice, "Store directory " << oss.str() << " was pushed down (saved) into directory " << dir << "."); + } else { + mrg::journal::jdir::delete_dir(oss.str().c_str()); + QPID_LOG(notice, "Store directory " << oss.str() << " was truncated."); + } + init(); +} + +void MessageStoreImpl::chkTplStoreInit() +{ + // Prevent multiple threads from late-initializing the TPL + qpid::sys::Mutex::ScopedLock sl(tplInitLock); + if (!tplStorePtr->is_ready()) { + journal::jdir::create_dir(getTplBaseDir()); + tplStorePtr->initialize(tplNumJrnlFiles, false, 0, tplJrnlFsizeSblks, tplWCacheNumPages, tplWCachePgSizeSblks); + if (mgmtObject.get() != 0) mgmtObject->set_tplIsInitialized(true); + } +} + +void MessageStoreImpl::open(db_ptr db, + DbTxn* txn, + const char* file, + bool dupKey) +{ + if(dupKey) db->set_flags(DB_DUPSORT); + db->open(txn, file, 0, DB_BTREE, DB_CREATE | DB_THREAD, 0); +} + +void MessageStoreImpl::closeDbs() +{ + for (std::list<db_ptr >::iterator i = dbs.begin(); i != dbs.end(); i++) { + (*i)->close(0); + } + dbs.clear(); +} + +MessageStoreImpl::~MessageStoreImpl() +{ + finalize(); + try { + closeDbs(); + } catch (const DbException& e) { + QPID_LOG(error, "Error closing BDB databases: " << e.what()); + } catch (const journal::jexception& e) { + QPID_LOG(error, "Error: " << e.what()); + } catch (const std::exception& e) { + QPID_LOG(error, "Error: " << e.what()); + } catch (...) { + QPID_LOG(error, "Unknown error in MessageStoreImpl::~MessageStoreImpl()"); + } + + if (mgmtObject.get() != 0) { + mgmtObject->resourceDestroy(); + mgmtObject.reset(); + } +} + +void MessageStoreImpl::create(qpid::broker::PersistableQueue& queue, + const qpid::framing::FieldTable& args) +{ + checkInit(); + if (queue.getPersistenceId()) { + THROW_STORE_EXCEPTION("Queue already created: " + queue.getName()); + } + JournalImpl* jQueue = 0; + qpid::framing::FieldTable::ValuePtr value; + + u_int16_t localFileCount = numJrnlFiles; + bool localAutoExpandFlag = autoJrnlExpand; + u_int16_t localAutoExpandMaxFileCount = autoJrnlExpandMaxFiles; + u_int32_t localFileSizeSblks = jrnlFsizeSblks; + + value = args.get("qpid.file_count"); + if (value.get() != 0 && !value->empty() && value->convertsTo<int>()) + localFileCount = chkJrnlNumFilesParam((u_int16_t) value->get<int>(), "qpid.file_count"); + + value = args.get("qpid.file_size"); + if (value.get() != 0 && !value->empty() && value->convertsTo<int>()) + localFileSizeSblks = chkJrnlFileSizeParam((u_int32_t) value->get<int>(), "qpid.file_size", wCachePgSizeSblks) * JRNL_RMGR_PAGE_SIZE; + + if (queue.getName().size() == 0) + { + QPID_LOG(error, "Cannot create store for empty (null) queue name - ignoring and attempting to continue."); + return; + } + + jQueue = new JournalImpl(broker->getTimer(), queue.getName(), getJrnlDir(queue), std::string("JournalData"), + defJournalGetEventsTimeout, defJournalFlushTimeout, agent, + boost::bind(&MessageStoreImpl::journalDeleted, this, _1)); + { + qpid::sys::Mutex::ScopedLock sl(journalListLock); + journalList[queue.getName()]=jQueue; + } + + value = args.get("qpid.auto_expand"); + if (value.get() != 0 && !value->empty() && value->convertsTo<bool>()) + localAutoExpandFlag = (bool) value->get<bool>(); + + value = args.get("qpid.auto_expand_max_jfiles"); + if (value.get() != 0 && !value->empty() && value->convertsTo<int>()) + localAutoExpandMaxFileCount = (u_int16_t) value->get<int>(); + + queue.setExternalQueueStore(dynamic_cast<qpid::broker::ExternalQueueStore*>(jQueue)); + try { + // init will create the deque's for the init... + jQueue->initialize(localFileCount, localAutoExpandFlag, localAutoExpandMaxFileCount, localFileSizeSblks, wCacheNumPages, wCachePgSizeSblks); + } catch (const journal::jexception& e) { + THROW_STORE_EXCEPTION(std::string("Queue ") + queue.getName() + ": create() failed: " + e.what()); + } + try { + if (!create(queueDb, queueIdSequence, queue)) { + THROW_STORE_EXCEPTION("Queue already exists: " + queue.getName()); + } + } catch (const DbException& e) { + THROW_STORE_EXCEPTION_2("Error creating queue named " + queue.getName(), e); + } +} + +void MessageStoreImpl::destroy(qpid::broker::PersistableQueue& queue) +{ + checkInit(); + destroy(queueDb, queue); + deleteBindingsForQueue(queue); + qpid::broker::ExternalQueueStore* eqs = queue.getExternalQueueStore(); + if (eqs) { + JournalImpl* jQueue = static_cast<JournalImpl*>(eqs); + jQueue->delete_jrnl_files(); + queue.setExternalQueueStore(0); // will delete the journal if exists + { + qpid::sys::Mutex::ScopedLock sl(journalListLock); + journalList.erase(queue.getName()); + } + } +} + +void MessageStoreImpl::create(const qpid::broker::PersistableExchange& exchange, + const qpid::framing::FieldTable& /*args*/) +{ + checkInit(); + if (exchange.getPersistenceId()) { + THROW_STORE_EXCEPTION("Exchange already created: " + exchange.getName()); + } + try { + if (!create(exchangeDb, exchangeIdSequence, exchange)) { + THROW_STORE_EXCEPTION("Exchange already exists: " + exchange.getName()); + } + } catch (const DbException& e) { + THROW_STORE_EXCEPTION_2("Error creating exchange named " + exchange.getName(), e); + } +} + +void MessageStoreImpl::destroy(const qpid::broker::PersistableExchange& exchange) +{ + checkInit(); + destroy(exchangeDb, exchange); + //need to also delete bindings + IdDbt key(exchange.getPersistenceId()); + bindingDb->del(0, &key, DB_AUTO_COMMIT); +} + +void MessageStoreImpl::create(const qpid::broker::PersistableConfig& general) +{ + checkInit(); + if (general.getPersistenceId()) { + THROW_STORE_EXCEPTION("General configuration item already created"); + } + try { + if (!create(generalDb, generalIdSequence, general)) { + THROW_STORE_EXCEPTION("General configuration already exists"); + } + } catch (const DbException& e) { + THROW_STORE_EXCEPTION_2("Error creating general configuration", e); + } +} + +void MessageStoreImpl::destroy(const qpid::broker::PersistableConfig& general) +{ + checkInit(); + destroy(generalDb, general); +} + +bool MessageStoreImpl::create(db_ptr db, + IdSequence& seq, + const qpid::broker::Persistable& p) +{ + u_int64_t id (seq.next()); + Dbt key(&id, sizeof(id)); + BufferValue value (p); + + int status; + TxnCtxt txn; + txn.begin(dbenv.get(), true); + try { + status = db->put(txn.get(), &key, &value, DB_NOOVERWRITE); + txn.commit(); + } catch (...) { + txn.abort(); + throw; + } + if (status == DB_KEYEXIST) { + return false; + } else { + p.setPersistenceId(id); + return true; + } +} + +void MessageStoreImpl::destroy(db_ptr db, const qpid::broker::Persistable& p) +{ + qpid::sys::Mutex::ScopedLock sl(bdbLock); + IdDbt key(p.getPersistenceId()); + db->del(0, &key, DB_AUTO_COMMIT); +} + + +void MessageStoreImpl::bind(const qpid::broker::PersistableExchange& e, + const qpid::broker::PersistableQueue& q, + const std::string& k, + const qpid::framing::FieldTable& a) +{ + checkInit(); + IdDbt key(e.getPersistenceId()); + BindingDbt value(e, q, k, a); + TxnCtxt txn; + txn.begin(dbenv.get(), true); + try { + put(bindingDb, txn.get(), key, value); + txn.commit(); + } catch (...) { + txn.abort(); + throw; + } +} + +void MessageStoreImpl::unbind(const qpid::broker::PersistableExchange& e, + const qpid::broker::PersistableQueue& q, + const std::string& k, + const qpid::framing::FieldTable&) +{ + checkInit(); + deleteBinding(e, q, k); +} + +void MessageStoreImpl::recover(qpid::broker::RecoveryManager& registry) +{ + checkInit(); + txn_list prepared; + recoverLockedMappings(prepared); + + queue_index queues;//id->queue + exchange_index exchanges;//id->exchange + message_index messages;//id->message + + TxnCtxt txn; + txn.begin(dbenv.get(), false); + try { + //read all queues, calls recoversMessages + recoverQueues(txn, registry, queues, prepared, messages); + + //recover exchange & bindings: + recoverExchanges(txn, registry, exchanges); + recoverBindings(txn, exchanges, queues); + + //recover general-purpose configuration + recoverGeneral(txn, registry); + + txn.commit(); + } catch (const DbException& e) { + txn.abort(); + THROW_STORE_EXCEPTION_2("Error on recovery", e); + } catch (...) { + txn.abort(); + throw; + } + + //recover transactions: + for (txn_list::iterator i = prepared.begin(); i != prepared.end(); i++) { + const PreparedTransaction pt = *i; + if (mgmtObject.get() != 0) { + mgmtObject->inc_tplTransactionDepth(); + mgmtObject->inc_tplTxnPrepares(); + } + + std::string xid = pt.xid; + + // Restore data token state in TxnCtxt + TplRecoverMapCitr citr = tplRecoverMap.find(xid); + if (citr == tplRecoverMap.end()) THROW_STORE_EXCEPTION("XID not found in tplRecoverMap"); + + // If a record is found that is dequeued but not committed/aborted from tplStore, then a complete() call + // was interrupted part way through committing/aborting the impacted queues. Complete this process. + bool incomplTplTxnFlag = citr->second.deq_flag; + + if (citr->second.tpc_flag) { + // Dtx (2PC) transaction + TPCTxnCtxt* tpcc = new TPCTxnCtxt(xid, &messageIdSequence); + std::auto_ptr<qpid::broker::TPCTransactionContext> txn(tpcc); + tpcc->recoverDtok(citr->second.rid, xid); + tpcc->prepare(tplStorePtr.get()); + + qpid::broker::RecoverableTransaction::shared_ptr dtx; + if (!incomplTplTxnFlag) dtx = registry.recoverTransaction(xid, txn); + if (pt.enqueues.get()) { + for (LockedMappings::iterator j = pt.enqueues->begin(); j != pt.enqueues->end(); j++) { + tpcc->addXidRecord(queues[j->first]->getExternalQueueStore()); + if (!incomplTplTxnFlag) dtx->enqueue(queues[j->first], messages[j->second]); + } + } + if (pt.dequeues.get()) { + for (LockedMappings::iterator j = pt.dequeues->begin(); j != pt.dequeues->end(); j++) { + tpcc->addXidRecord(queues[j->first]->getExternalQueueStore()); + if (!incomplTplTxnFlag) dtx->dequeue(queues[j->first], messages[j->second]); + } + } + + if (incomplTplTxnFlag) { + tpcc->complete(citr->second.commit_flag); + } + } else { + // Local (1PC) transaction + boost::shared_ptr<TxnCtxt> opcc(new TxnCtxt(xid, &messageIdSequence)); + opcc->recoverDtok(citr->second.rid, xid); + opcc->prepare(tplStorePtr.get()); + + if (pt.enqueues.get()) { + for (LockedMappings::iterator j = pt.enqueues->begin(); j != pt.enqueues->end(); j++) { + opcc->addXidRecord(queues[j->first]->getExternalQueueStore()); + } + } + if (pt.dequeues.get()) { + for (LockedMappings::iterator j = pt.dequeues->begin(); j != pt.dequeues->end(); j++) { + opcc->addXidRecord(queues[j->first]->getExternalQueueStore()); + } + } + if (incomplTplTxnFlag) { + opcc->complete(citr->second.commit_flag); + } else { + completed(*opcc.get(), citr->second.commit_flag); + } + } + } + registry.recoveryComplete(); +} + +void MessageStoreImpl::recoverQueues(TxnCtxt& txn, + qpid::broker::RecoveryManager& registry, + queue_index& queue_index, + txn_list& prepared, + message_index& messages) +{ + Cursor queues; + queues.open(queueDb, txn.get()); + + u_int64_t maxQueueId(1); + + IdDbt key; + Dbt value; + //read all queues + while (queues.next(key, value)) { + qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size()); + //create a Queue instance + qpid::broker::RecoverableQueue::shared_ptr queue = registry.recoverQueue(buffer); + //set the persistenceId and update max as required + queue->setPersistenceId(key.id); + + const std::string queueName = queue->getName().c_str(); + JournalImpl* jQueue = 0; + if (queueName.size() == 0) + { + QPID_LOG(error, "Cannot recover empty (null) queue name - ignoring and attempting to continue."); + break; + } + jQueue = new JournalImpl(broker->getTimer(), queueName, getJrnlHashDir(queueName), std::string("JournalData"), + defJournalGetEventsTimeout, defJournalFlushTimeout, agent, + boost::bind(&MessageStoreImpl::journalDeleted, this, _1)); + { + qpid::sys::Mutex::ScopedLock sl(journalListLock); + journalList[queueName] = jQueue; + } + queue->setExternalQueueStore(dynamic_cast<qpid::broker::ExternalQueueStore*>(jQueue)); + + try + { + long rcnt = 0L; // recovered msg count + long idcnt = 0L; // in-doubt msg count + u_int64_t thisHighestRid = 0ULL; + jQueue->recover(numJrnlFiles, autoJrnlExpand, autoJrnlExpandMaxFiles, jrnlFsizeSblks, wCacheNumPages, wCachePgSizeSblks, &prepared, thisHighestRid, key.id); // start recovery + if (highestRid == 0ULL) + highestRid = thisHighestRid; + else if (thisHighestRid - highestRid < 0x8000000000000000ULL) // RFC 1982 comparison for unsigned 64-bit + highestRid = thisHighestRid; + recoverMessages(txn, registry, queue, prepared, messages, rcnt, idcnt); + QPID_LOG(info, "Recovered queue \"" << queueName << "\": " << rcnt << " messages recovered; " << idcnt << " messages in-doubt."); + jQueue->recover_complete(); // start journal. + } catch (const journal::jexception& e) { + THROW_STORE_EXCEPTION(std::string("Queue ") + queueName + ": recoverQueues() failed: " + e.what()); + } + //read all messages: done on a per queue basis if using Journal + + queue_index[key.id] = queue; + maxQueueId = std::max(key.id, maxQueueId); + } + + // NOTE: highestRid is set by both recoverQueues() and recoverTplStore() as + // the messageIdSequence is used for both queue journals and the tpl journal. + messageIdSequence.reset(highestRid + 1); + QPID_LOG(info, "Most recent persistence id found: 0x" << std::hex << highestRid << std::dec); + + queueIdSequence.reset(maxQueueId + 1); +} + + +void MessageStoreImpl::recoverExchanges(TxnCtxt& txn, + qpid::broker::RecoveryManager& registry, + exchange_index& index) +{ + //TODO: this is a copy&paste from recoverQueues - refactor! + Cursor exchanges; + exchanges.open(exchangeDb, txn.get()); + + u_int64_t maxExchangeId(1); + IdDbt key; + Dbt value; + //read all exchanges + while (exchanges.next(key, value)) { + qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size()); + //create a Exchange instance + qpid::broker::RecoverableExchange::shared_ptr exchange = registry.recoverExchange(buffer); + if (exchange) { + //set the persistenceId and update max as required + exchange->setPersistenceId(key.id); + index[key.id] = exchange; + QPID_LOG(info, "Recovered exchange \"" << exchange->getName() << '"'); + } + maxExchangeId = std::max(key.id, maxExchangeId); + } + exchangeIdSequence.reset(maxExchangeId + 1); +} + +void MessageStoreImpl::recoverBindings(TxnCtxt& txn, + exchange_index& exchanges, + queue_index& queues) +{ + Cursor bindings; + bindings.open(bindingDb, txn.get()); + + IdDbt key; + Dbt value; + while (bindings.next(key, value)) { + qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size()); + if (buffer.available() < 8) { + QPID_LOG(error, "Not enough data for binding: " << buffer.available()); + THROW_STORE_EXCEPTION("Not enough data for binding"); + } + uint64_t queueId = buffer.getLongLong(); + std::string queueName; + std::string routingkey; + qpid::framing::FieldTable args; + buffer.getShortString(queueName); + buffer.getShortString(routingkey); + buffer.get(args); + exchange_index::iterator exchange = exchanges.find(key.id); + queue_index::iterator queue = queues.find(queueId); + if (exchange != exchanges.end() && queue != queues.end()) { + //could use the recoverable queue here rather than the name... + exchange->second->bind(queueName, routingkey, args); + QPID_LOG(info, "Recovered binding exchange=" << exchange->second->getName() + << " key=" << routingkey + << " queue=" << queueName); + } else { + //stale binding, delete it + QPID_LOG(warning, "Deleting stale binding"); + bindings->del(0); + } + } +} + +void MessageStoreImpl::recoverGeneral(TxnCtxt& txn, + qpid::broker::RecoveryManager& registry) +{ + Cursor items; + items.open(generalDb, txn.get()); + + u_int64_t maxGeneralId(1); + IdDbt key; + Dbt value; + //read all items + while (items.next(key, value)) { + qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size()); + //create instance + qpid::broker::RecoverableConfig::shared_ptr config = registry.recoverConfig(buffer); + //set the persistenceId and update max as required + config->setPersistenceId(key.id); + maxGeneralId = std::max(key.id, maxGeneralId); + } + generalIdSequence.reset(maxGeneralId + 1); +} + +void MessageStoreImpl::recoverMessages(TxnCtxt& /*txn*/, + qpid::broker::RecoveryManager& recovery, + qpid::broker::RecoverableQueue::shared_ptr& queue, + txn_list& prepared, + message_index& messages, + long& rcnt, + long& idcnt) +{ + size_t preambleLength = sizeof(u_int32_t)/*header size*/; + + JournalImpl* jc = static_cast<JournalImpl*>(queue->getExternalQueueStore()); + DataTokenImpl dtok; + size_t readSize = 0; + unsigned msg_count = 0; + + // TODO: This optimization to skip reading if there are no enqueued messages to read + // breaks the python system test in phase 6 with "Exception: Cannot write lock file" + // Figure out what is breaking. + //bool read = jc->get_enq_cnt() > 0; + bool read = true; + + void* dbuff = NULL; size_t dbuffSize = 0; + void* xidbuff = NULL; size_t xidbuffSize = 0; + bool transientFlag = false; + bool externalFlag = false; + + dtok.set_wstate(DataTokenImpl::ENQ); + + // Read the message from the Journal. + try { + unsigned aio_sleep_cnt = 0; + while (read) { + mrg::journal::iores res = jc->read_data_record(&dbuff, dbuffSize, &xidbuff, xidbuffSize, transientFlag, externalFlag, &dtok); + readSize = dtok.dsize(); + + switch (res) + { + case mrg::journal::RHM_IORES_SUCCESS: { + msg_count++; + qpid::broker::RecoverableMessage::shared_ptr msg; + char* data = (char*)dbuff; + + unsigned headerSize; + if (externalFlag) { + msg = getExternMessage(recovery, dtok.rid(), headerSize); // large message external to jrnl + } else { + headerSize = qpid::framing::Buffer(data, preambleLength).getLong(); + qpid::framing::Buffer headerBuff(data+ preambleLength, headerSize); /// do we want read size or header size ???? + msg = recovery.recoverMessage(headerBuff); + } + msg->setPersistenceId(dtok.rid()); + // At some future point if delivery attempts are stored, then this call would + // become optional depending on that information. + msg->setRedelivered(); + // Reset the TTL for the recovered message + msg->computeExpiration(broker->getExpiryPolicy()); + + u_int32_t contentOffset = headerSize + preambleLength; + u_int64_t contentSize = readSize - contentOffset; + if (msg->loadContent(contentSize) && !externalFlag) { + //now read the content + qpid::framing::Buffer contentBuff(data + contentOffset, contentSize); + msg->decodeContent(contentBuff); + } + + PreparedTransaction::list::iterator i = PreparedTransaction::getLockedPreparedTransaction(prepared, queue->getPersistenceId(), dtok.rid()); + if (i == prepared.end()) { // not in prepared list + rcnt++; + queue->recover(msg); + } else { + u_int64_t rid = dtok.rid(); + std::string xid(i->xid); + TplRecoverMapCitr citr = tplRecoverMap.find(xid); + if (citr == tplRecoverMap.end()) THROW_STORE_EXCEPTION("XID not found in tplRecoverMap"); + + // deq present in prepared list: this xid is part of incomplete txn commit/abort + // or this is a 1PC txn that must be rolled forward + if (citr->second.deq_flag || !citr->second.tpc_flag) { + if (jc->is_enqueued(rid, true)) { + // Enqueue is non-tx, dequeue tx + assert(jc->is_locked(rid)); // This record MUST be locked by a txn dequeue + if (!citr->second.commit_flag) { + rcnt++; + queue->recover(msg); // recover message in abort case only + } + } else { + // Enqueue and/or dequeue tx + journal::txn_map& tmap = jc->get_txn_map(); + journal::txn_data_list txnList = tmap.get_tdata_list(xid); // txnList will be empty if xid not found + bool enq = false; + bool deq = false; + for (journal::tdl_itr j = txnList.begin(); j<txnList.end(); j++) { + if (j->_enq_flag && j->_rid == rid) enq = true; + else if (!j->_enq_flag && j->_drid == rid) deq = true; + } + if (enq && !deq && citr->second.commit_flag) { + rcnt++; + queue->recover(msg); // recover txn message in commit case only + } + } + } else { + idcnt++; + messages[rid] = msg; + } + } + + dtok.reset(); + dtok.set_wstate(DataTokenImpl::ENQ); + + if (xidbuff) + ::free(xidbuff); + else if (dbuff) + ::free(dbuff); + aio_sleep_cnt = 0; + break; + } + case mrg::journal::RHM_IORES_PAGE_AIOWAIT: + if (++aio_sleep_cnt > MAX_AIO_SLEEPS) + THROW_STORE_EXCEPTION("Timeout waiting for AIO in MessageStoreImpl::recoverMessages()"); + ::usleep(AIO_SLEEP_TIME_US); + break; + case mrg::journal::RHM_IORES_EMPTY: + read = false; + break; // done with all messages. (add call in jrnl to test that _emap is empty.) + default: + std::ostringstream oss; + oss << "recoverMessages(): Queue: " << queue->getName() << ": Unexpected return from journal read: " << mrg::journal::iores_str(res); + THROW_STORE_EXCEPTION(oss.str()); + } // switch + } // while + } catch (const journal::jexception& e) { + THROW_STORE_EXCEPTION(std::string("Queue ") + queue->getName() + ": recoverMessages() failed: " + e.what()); + } +} + +qpid::broker::RecoverableMessage::shared_ptr MessageStoreImpl::getExternMessage(qpid::broker::RecoveryManager& /*recovery*/, + uint64_t /*messageId*/, + unsigned& /*headerSize*/) +{ + throw mrg::journal::jexception(mrg::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "getExternMessage"); +} + +int MessageStoreImpl::enqueueMessage(TxnCtxt& txn, + IdDbt& msgId, + qpid::broker::RecoverableMessage::shared_ptr& msg, + queue_index& index, + txn_list& prepared, + message_index& messages) +{ + Cursor mappings; + mappings.open(mappingDb, txn.get()); + + IdDbt value; + + int count(0); + for (int status = mappings->get(&msgId, &value, DB_SET); status == 0; status = mappings->get(&msgId, &value, DB_NEXT_DUP)) { + if (index.find(value.id) == index.end()) { + QPID_LOG(warning, "Recovered message for queue that no longer exists"); + mappings->del(0); + } else { + qpid::broker::RecoverableQueue::shared_ptr queue = index[value.id]; + if (PreparedTransaction::isLocked(prepared, value.id, msgId.id)) { + messages[msgId.id] = msg; + } else { + queue->recover(msg); + } + count++; + } + } + mappings.close(); + return count; +} + +void MessageStoreImpl::readTplStore() +{ + tplRecoverMap.clear(); + journal::txn_map& tmap = tplStorePtr->get_txn_map(); + DataTokenImpl dtok; + void* dbuff = NULL; size_t dbuffSize = 0; + void* xidbuff = NULL; size_t xidbuffSize = 0; + bool transientFlag = false; + bool externalFlag = false; + bool done = false; + try { + unsigned aio_sleep_cnt = 0; + while (!done) { + dtok.reset(); + dtok.set_wstate(DataTokenImpl::ENQ); + mrg::journal::iores res = tplStorePtr->read_data_record(&dbuff, dbuffSize, &xidbuff, xidbuffSize, transientFlag, externalFlag, &dtok); + switch (res) { + case mrg::journal::RHM_IORES_SUCCESS: { + // Every TPL record contains both data and an XID + assert(dbuffSize>0); + assert(xidbuffSize>0); + std::string xid(static_cast<const char*>(xidbuff), xidbuffSize); + bool is2PC = *(static_cast<char*>(dbuff)) != 0; + + // Check transaction details; add to recover map + journal::txn_data_list txnList = tmap.get_tdata_list(xid); // txnList will be empty if xid not found + if (!txnList.empty()) { // xid found in tmap + unsigned enqCnt = 0; + unsigned deqCnt = 0; + u_int64_t rid = 0; + + // Assume commit (roll forward) in cases where only prepare has been called - ie only enqueue record exists. + // Note: will apply to both 1PC and 2PC transactions. + bool commitFlag = true; + + for (journal::tdl_itr j = txnList.begin(); j<txnList.end(); j++) { + if (j->_enq_flag) { + rid = j->_rid; + enqCnt++; + } else { + commitFlag = j->_commit_flag; + deqCnt++; + } + } + assert(enqCnt == 1); + assert(deqCnt <= 1); + tplRecoverMap.insert(TplRecoverMapPair(xid, TplRecoverStruct(rid, deqCnt == 1, commitFlag, is2PC))); + } + + ::free(xidbuff); + aio_sleep_cnt = 0; + break; + } + case mrg::journal::RHM_IORES_PAGE_AIOWAIT: + if (++aio_sleep_cnt > MAX_AIO_SLEEPS) + THROW_STORE_EXCEPTION("Timeout waiting for AIO in MessageStoreImpl::recoverTplStore()"); + ::usleep(AIO_SLEEP_TIME_US); + break; + case mrg::journal::RHM_IORES_EMPTY: + done = true; + break; // done with all messages. (add call in jrnl to test that _emap is empty.) + default: + std::ostringstream oss; + oss << "readTplStore(): Unexpected result from journal read: " << mrg::journal::iores_str(res); + THROW_STORE_EXCEPTION(oss.str()); + } // switch + } + } catch (const journal::jexception& e) { + THROW_STORE_EXCEPTION(std::string("TPL recoverTplStore() failed: ") + e.what()); + } +} + +void MessageStoreImpl::recoverTplStore() +{ + if (journal::jdir::exists(tplStorePtr->jrnl_dir() + tplStorePtr->base_filename() + ".jinf")) { + u_int64_t thisHighestRid = 0ULL; + tplStorePtr->recover(tplNumJrnlFiles, false, 0, tplJrnlFsizeSblks, tplWCachePgSizeSblks, tplWCacheNumPages, 0, thisHighestRid, 0); + if (highestRid == 0ULL) + highestRid = thisHighestRid; + else if (thisHighestRid - highestRid < 0x8000000000000000ULL) // RFC 1982 comparison for unsigned 64-bit + highestRid = thisHighestRid; + + // Load tplRecoverMap by reading the TPL store + readTplStore(); + + tplStorePtr->recover_complete(); // start journal. + } +} + +void MessageStoreImpl::recoverLockedMappings(txn_list& txns) +{ + if (!tplStorePtr->is_ready()) + recoverTplStore(); + + // Abort unprepared xids and populate the locked maps + for (TplRecoverMapCitr i = tplRecoverMap.begin(); i != tplRecoverMap.end(); i++) { + LockedMappings::shared_ptr enq_ptr; + enq_ptr.reset(new LockedMappings); + LockedMappings::shared_ptr deq_ptr; + deq_ptr.reset(new LockedMappings); + txns.push_back(new PreparedTransaction(i->first, enq_ptr, deq_ptr)); + } +} + +void MessageStoreImpl::collectPreparedXids(std::set<std::string>& xids) +{ + if (tplStorePtr->is_ready()) { + tplStorePtr->read_reset(); + readTplStore(); + } else { + recoverTplStore(); + } + for (TplRecoverMapCitr i = tplRecoverMap.begin(); i != tplRecoverMap.end(); i++) { + // Discard all txns that are to be rolled forward/back and 1PC transactions + if (!i->second.deq_flag && i->second.tpc_flag) + xids.insert(i->first); + } +} + +void MessageStoreImpl::stage(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& /*msg*/) +{ + throw mrg::journal::jexception(mrg::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "stage"); +} + +void MessageStoreImpl::destroy(qpid::broker::PersistableMessage& /*msg*/) +{ + throw mrg::journal::jexception(mrg::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "destroy"); +} + +void MessageStoreImpl::appendContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& /*msg*/, + const std::string& /*data*/) +{ + throw mrg::journal::jexception(mrg::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "appendContent"); +} + +void MessageStoreImpl::loadContent(const qpid::broker::PersistableQueue& queue, + const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + std::string& data, + u_int64_t offset, + u_int32_t length) +{ + checkInit(); + u_int64_t messageId (msg->getPersistenceId()); + + if (messageId != 0) { + try { + JournalImpl* jc = static_cast<JournalImpl*>(queue.getExternalQueueStore()); + if (jc && jc->is_enqueued(messageId) ) { + if (!jc->loadMsgContent(messageId, data, length, offset)) { + std::ostringstream oss; + oss << "Queue " << queue.getName() << ": loadContent() failed: Message " << messageId << " is extern"; + THROW_STORE_EXCEPTION(oss.str()); + } + } else { + std::ostringstream oss; + oss << "Queue " << queue.getName() << ": loadContent() failed: Message " << messageId << " not enqueued"; + THROW_STORE_EXCEPTION(oss.str()); + } + } catch (const journal::jexception& e) { + THROW_STORE_EXCEPTION(std::string("Queue ") + queue.getName() + ": loadContent() failed: " + e.what()); + } + } else { + THROW_STORE_EXCEPTION("Cannot load content. Message not known to store!"); + } +} + +void MessageStoreImpl::flush(const qpid::broker::PersistableQueue& queue) +{ + if (queue.getExternalQueueStore() == 0) return; + checkInit(); + std::string qn = queue.getName(); + try { + JournalImpl* jc = static_cast<JournalImpl*>(queue.getExternalQueueStore()); + if (jc) { + // TODO: check if this result should be used... + /*mrg::journal::iores res =*/ jc->flush(); + } + } catch (const journal::jexception& e) { + THROW_STORE_EXCEPTION(std::string("Queue ") + qn + ": flush() failed: " + e.what() ); + } +} + +void MessageStoreImpl::enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg, + const qpid::broker::PersistableQueue& queue) +{ + checkInit(); + u_int64_t queueId (queue.getPersistenceId()); + u_int64_t messageId (msg->getPersistenceId()); + if (queueId == 0) { + THROW_STORE_EXCEPTION("Queue not created: " + queue.getName()); + } + + TxnCtxt implicit; + TxnCtxt* txn = 0; + if (ctxt) { + txn = check(ctxt); + } else { + txn = &implicit; + } + + bool newId = false; + if (messageId == 0) { + messageId = messageIdSequence.next(); + msg->setPersistenceId(messageId); + newId = true; + } + store(&queue, txn, msg, newId); + + // add queue* to the txn map.. + if (ctxt) txn->addXidRecord(queue.getExternalQueueStore()); +} + +u_int64_t MessageStoreImpl::msgEncode(std::vector<char>& buff, const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message) +{ + u_int32_t headerSize = message->encodedHeaderSize(); + u_int64_t size = message->encodedSize() + sizeof(u_int32_t); + try { buff = std::vector<char>(size); } // long + headers + content + catch (const std::exception& e) { + std::ostringstream oss; + oss << "Unable to allocate memory for encoding message; requested size: " << size << "; error: " << e.what(); + THROW_STORE_EXCEPTION(oss.str()); + } + qpid::framing::Buffer buffer(&buff[0],size); + buffer.putLong(headerSize); + message->encode(buffer); + return size; +} + +void MessageStoreImpl::store(const qpid::broker::PersistableQueue* queue, + TxnCtxt* txn, + const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message, + bool /*newId*/) +{ + std::vector<char> buff; + u_int64_t size = msgEncode(buff, message); + + try { + if (queue) { + boost::intrusive_ptr<DataTokenImpl> dtokp(new DataTokenImpl); + dtokp->addRef(); + dtokp->setSourceMessage(message); + dtokp->set_external_rid(true); + dtokp->set_rid(message->getPersistenceId()); // set the messageID into the Journal header (record-id) + + JournalImpl* jc = static_cast<JournalImpl*>(queue->getExternalQueueStore()); + if (txn->getXid().empty()) { + jc->enqueue_data_record(&buff[0], size, size, dtokp.get(), !message->isPersistent()); + } else { + jc->enqueue_txn_data_record(&buff[0], size, size, dtokp.get(), txn->getXid(), !message->isPersistent()); + } + } else { + THROW_STORE_EXCEPTION(std::string("MessageStoreImpl::store() failed: queue NULL.")); + } + } catch (const journal::jexception& e) { + THROW_STORE_EXCEPTION(std::string("Queue ") + queue->getName() + ": MessageStoreImpl::store() failed: " + + e.what()); + } +} + +void MessageStoreImpl::dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg, + const qpid::broker::PersistableQueue& queue) +{ + checkInit(); + u_int64_t queueId (queue.getPersistenceId()); + u_int64_t messageId (msg->getPersistenceId()); + if (queueId == 0) { + THROW_STORE_EXCEPTION("Queue \"" + queue.getName() + "\" has null queue Id (has not been created)"); + } + if (messageId == 0) { + THROW_STORE_EXCEPTION("Queue \"" + queue.getName() + "\": Dequeuing message with null persistence Id."); + } + + TxnCtxt implicit; + TxnCtxt* txn = 0; + if (ctxt) { + txn = check(ctxt); + } else { + txn = &implicit; + } + + // add queue* to the txn map.. + if (ctxt) txn->addXidRecord(queue.getExternalQueueStore()); + async_dequeue(ctxt, msg, queue); + + msg->dequeueComplete(); +} + +void MessageStoreImpl::async_dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg, + const qpid::broker::PersistableQueue& queue) +{ + boost::intrusive_ptr<DataTokenImpl> ddtokp(new DataTokenImpl); + ddtokp->setSourceMessage(msg); + ddtokp->set_external_rid(true); + ddtokp->set_rid(messageIdSequence.next()); + ddtokp->set_dequeue_rid(msg->getPersistenceId()); + ddtokp->set_wstate(DataTokenImpl::ENQ); + std::string tid; + if (ctxt) { + TxnCtxt* txn = check(ctxt); + tid = txn->getXid(); + } + // Manually increase the ref count, as raw pointers are used beyond this point + ddtokp->addRef(); + try { + JournalImpl* jc = static_cast<JournalImpl*>(queue.getExternalQueueStore()); + if (tid.empty()) { + jc->dequeue_data_record(ddtokp.get()); + } else { + jc->dequeue_txn_data_record(ddtokp.get(), tid); + } + } catch (const journal::jexception& e) { + ddtokp->release(); + THROW_STORE_EXCEPTION(std::string("Queue ") + queue.getName() + ": async_dequeue() failed: " + e.what()); + } +} + +u_int32_t MessageStoreImpl::outstandingQueueAIO(const qpid::broker::PersistableQueue& /*queue*/) +{ + checkInit(); + return 0; +} + +void MessageStoreImpl::completed(TxnCtxt& txn, + bool commit) +{ + try { + chkTplStoreInit(); // Late initialize (if needed) + + // Nothing to do if not prepared + if (txn.getDtok()->is_enqueued()) { + txn.incrDtokRef(); + DataTokenImpl* dtokp = txn.getDtok(); + dtokp->set_dequeue_rid(dtokp->rid()); + dtokp->set_rid(messageIdSequence.next()); + tplStorePtr->dequeue_txn_data_record(txn.getDtok(), txn.getXid(), commit); + } + txn.complete(commit); + if (mgmtObject.get() != 0) { + mgmtObject->dec_tplTransactionDepth(); + if (commit) + mgmtObject->inc_tplTxnCommits(); + else + mgmtObject->inc_tplTxnAborts(); + } + } catch (const std::exception& e) { + QPID_LOG(error, "Error completing xid " << txn.getXid() << ": " << e.what()); + throw; + } +} + +std::auto_ptr<qpid::broker::TransactionContext> MessageStoreImpl::begin() +{ + checkInit(); + // pass sequence number for c/a + return std::auto_ptr<qpid::broker::TransactionContext>(new TxnCtxt(&messageIdSequence)); +} + +std::auto_ptr<qpid::broker::TPCTransactionContext> MessageStoreImpl::begin(const std::string& xid) +{ + checkInit(); + IdSequence* jtx = &messageIdSequence; + // pass sequence number for c/a + return std::auto_ptr<qpid::broker::TPCTransactionContext>(new TPCTxnCtxt(xid, jtx)); +} + +void MessageStoreImpl::prepare(qpid::broker::TPCTransactionContext& ctxt) +{ + checkInit(); + TxnCtxt* txn = dynamic_cast<TxnCtxt*>(&ctxt); + if(!txn) throw qpid::broker::InvalidTransactionContextException(); + localPrepare(txn); +} + +void MessageStoreImpl::localPrepare(TxnCtxt* ctxt) +{ + try { + chkTplStoreInit(); // Late initialize (if needed) + + // This sync is required to ensure multi-queue atomicity - ie all txn data + // must hit the disk on *all* queues before the TPL prepare (enq) is written. + ctxt->sync(); + + ctxt->incrDtokRef(); + DataTokenImpl* dtokp = ctxt->getDtok(); + dtokp->set_external_rid(true); + dtokp->set_rid(messageIdSequence.next()); + char tpcFlag = static_cast<char>(ctxt->isTPC()); + tplStorePtr->enqueue_txn_data_record(&tpcFlag, sizeof(char), sizeof(char), dtokp, ctxt->getXid(), false); + ctxt->prepare(tplStorePtr.get()); + // make sure all the data is written to disk before returning + ctxt->sync(); + if (mgmtObject.get() != 0) { + mgmtObject->inc_tplTransactionDepth(); + mgmtObject->inc_tplTxnPrepares(); + } + } catch (const std::exception& e) { + QPID_LOG(error, "Error preparing xid " << ctxt->getXid() << ": " << e.what()); + throw; + } +} + +void MessageStoreImpl::commit(qpid::broker::TransactionContext& ctxt) +{ + checkInit(); + TxnCtxt* txn(check(&ctxt)); + if (!txn->isTPC()) { + if (txn->impactedQueuesEmpty()) return; + localPrepare(dynamic_cast<TxnCtxt*>(txn)); + } + completed(*dynamic_cast<TxnCtxt*>(txn), true); +} + +void MessageStoreImpl::abort(qpid::broker::TransactionContext& ctxt) +{ + checkInit(); + TxnCtxt* txn(check(&ctxt)); + if (!txn->isTPC()) { + if (txn->impactedQueuesEmpty()) return; + localPrepare(dynamic_cast<TxnCtxt*>(txn)); + } + completed(*dynamic_cast<TxnCtxt*>(txn), false); +} + +TxnCtxt* MessageStoreImpl::check(qpid::broker::TransactionContext* ctxt) +{ + TxnCtxt* txn = dynamic_cast<TxnCtxt*>(ctxt); + if(!txn) throw qpid::broker::InvalidTransactionContextException(); + return txn; +} + +void MessageStoreImpl::put(db_ptr db, + DbTxn* txn, + Dbt& key, + Dbt& value) +{ + try { + int status = db->put(txn, &key, &value, DB_NODUPDATA); + if (status == DB_KEYEXIST) { + THROW_STORE_EXCEPTION("duplicate data"); + } else if (status) { + THROW_STORE_EXCEPTION(DbEnv::strerror(status)); + } + } catch (const DbException& e) { + THROW_STORE_EXCEPTION(e.what()); + } +} + +void MessageStoreImpl::deleteBindingsForQueue(const qpid::broker::PersistableQueue& queue) +{ + TxnCtxt txn; + txn.begin(dbenv.get(), true); + try { + { + Cursor bindings; + bindings.open(bindingDb, txn.get()); + + IdDbt key; + Dbt value; + while (bindings.next(key, value)) { + qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size()); + if (buffer.available() < 8) { + THROW_STORE_EXCEPTION("Not enough data for binding"); + } + uint64_t queueId = buffer.getLongLong(); + if (queue.getPersistenceId() == queueId) { + bindings->del(0); + QPID_LOG(debug, "Deleting binding for " << queue.getName() << " " << key.id << "->" << queueId); + } + } + } + txn.commit(); + } catch (const std::exception& e) { + txn.abort(); + THROW_STORE_EXCEPTION_2("Error deleting bindings", e.what()); + } catch (...) { + txn.abort(); + throw; + } + QPID_LOG(debug, "Deleted all bindings for " << queue.getName() << ":" << queue.getPersistenceId()); +} + +void MessageStoreImpl::deleteBinding(const qpid::broker::PersistableExchange& exchange, + const qpid::broker::PersistableQueue& queue, + const std::string& bkey) +{ + TxnCtxt txn; + txn.begin(dbenv.get(), true); + try { + { + Cursor bindings; + bindings.open(bindingDb, txn.get()); + + IdDbt key(exchange.getPersistenceId()); + Dbt value; + + for (int status = bindings->get(&key, &value, DB_SET); status == 0; status = bindings->get(&key, &value, DB_NEXT_DUP)) { + qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size()); + if (buffer.available() < 8) { + THROW_STORE_EXCEPTION("Not enough data for binding"); + } + uint64_t queueId = buffer.getLongLong(); + if (queue.getPersistenceId() == queueId) { + std::string q; + std::string k; + buffer.getShortString(q); + buffer.getShortString(k); + if (bkey == k) { + bindings->del(0); + QPID_LOG(debug, "Deleting binding for " << queue.getName() << " " << key.id << "->" << queueId); + } + } + } + } + txn.commit(); + } catch (const std::exception& e) { + txn.abort(); + THROW_STORE_EXCEPTION_2("Error deleting bindings", e.what()); + } catch (...) { + txn.abort(); + throw; + } +} + +std::string MessageStoreImpl::getJrnlBaseDir() +{ + std::ostringstream dir; + dir << storeDir << "/" << storeTopLevelDir << "/jrnl/" ; + return dir.str(); +} + +std::string MessageStoreImpl::getBdbBaseDir() +{ + std::ostringstream dir; + dir << storeDir << "/" << storeTopLevelDir << "/dat/" ; + return dir.str(); +} + +std::string MessageStoreImpl::getTplBaseDir() +{ + std::ostringstream dir; + dir << storeDir << "/" << storeTopLevelDir << "/tpl/" ; + return dir.str(); +} + +std::string MessageStoreImpl::getJrnlDir(const qpid::broker::PersistableQueue& queue) //for exmaple /var/rhm/ + queueDir/ +{ + return getJrnlHashDir(queue.getName().c_str()); +} + +u_int32_t MessageStoreImpl::bHash(const std::string str) +{ + // Daniel Bernstein hash fn + u_int32_t h = 0; + for (std::string::const_iterator i = str.begin(); i < str.end(); i++) + h = 33*h + *i; + return h; +} + +std::string MessageStoreImpl::getJrnlHashDir(const std::string& queueName) //for exmaple /var/rhm/ + queueDir/ +{ + std::stringstream dir; + dir << getJrnlBaseDir() << std::hex << std::setfill('0') << std::setw(4); + dir << (bHash(queueName.c_str()) % 29); // Use a prime number for better distribution across dirs + dir << "/" << queueName << "/"; + return dir.str(); +} + +std::string MessageStoreImpl::getStoreDir() const { return storeDir; } + +void MessageStoreImpl::journalDeleted(JournalImpl& j) { + qpid::sys::Mutex::ScopedLock sl(journalListLock); + journalList.erase(j.id()); +} + +MessageStoreImpl::StoreOptions::StoreOptions(const std::string& name) : + qpid::Options(name), + numJrnlFiles(defNumJrnlFiles), + autoJrnlExpand(defAutoJrnlExpand), + autoJrnlExpandMaxFiles(defAutoJrnlExpandMaxFiles), + jrnlFsizePgs(defJrnlFileSizePgs), + truncateFlag(defTruncateFlag), + wCachePageSizeKib(defWCachePageSize), + tplNumJrnlFiles(defTplNumJrnlFiles), + tplJrnlFsizePgs(defTplJrnlFileSizePgs), + tplWCachePageSizeKib(defTplWCachePageSize) +{ + std::ostringstream oss1; + oss1 << "Default number of files for each journal instance (queue). [Allowable values: " << + JRNL_MIN_NUM_FILES << " - " << JRNL_MAX_NUM_FILES << "]"; + std::ostringstream oss2; + oss2 << "Default size for each journal file in multiples of read pages (1 read page = 64KiB). [Allowable values: " << + JRNL_MIN_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << " - " << JRNL_MAX_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << "]"; + std::ostringstream oss3; + oss3 << "Number of files for transaction prepared list journal instance. [Allowable values: " << + JRNL_MIN_NUM_FILES << " - " << JRNL_MAX_NUM_FILES << "]"; + std::ostringstream oss4; + oss4 << "Size of each transaction prepared list journal file in multiples of read pages (1 read page = 64KiB) [Allowable values: " << + JRNL_MIN_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << " - " << JRNL_MAX_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << "]"; + addOptions() + ("store-dir", qpid::optValue(storeDir, "DIR"), + "Store directory location for persistence (instead of using --data-dir value). " + "Required if --no-data-dir is also used.") + ("num-jfiles", qpid::optValue(numJrnlFiles, "N"), oss1.str().c_str()) + ("jfile-size-pgs", qpid::optValue(jrnlFsizePgs, "N"), oss2.str().c_str()) +// TODO: Uncomment these lines when auto-expand is enabled. +// ("auto-expand", qpid::optValue(autoJrnlExpand, "yes|no"), +// "If yes|true|1, allows journal to auto-expand by adding additional journal files as needed. " +// "If no|false|0, the number of journal files will remain fixed (num-jfiles).") +// ("max-auto-expand-jfiles", qpid::optValue(autoJrnlExpandMaxFiles, "N"), +// "Maximum number of journal files allowed from auto-expanding; must be greater than --num-jfiles parameter.") + ("truncate", qpid::optValue(truncateFlag, "yes|no"), + "If yes|true|1, will truncate the store (discard any existing records). If no|false|0, will preserve " + "the existing store files for recovery.") + ("wcache-page-size", qpid::optValue(wCachePageSizeKib, "N"), + "Size of the pages in the write page cache in KiB. " + "Allowable values - powers of 2: 1, 2, 4, ... , 128. " + "Lower values decrease latency at the expense of throughput.") + ("tpl-num-jfiles", qpid::optValue(tplNumJrnlFiles, "N"), oss3.str().c_str()) + ("tpl-jfile-size-pgs", qpid::optValue(tplJrnlFsizePgs, "N"), oss4.str().c_str()) + ("tpl-wcache-page-size", qpid::optValue(tplWCachePageSizeKib, "N"), + "Size of the pages in the transaction prepared list write page cache in KiB. " + "Allowable values - powers of 2: 1, 2, 4, ... , 128. " + "Lower values decrease latency at the expense of throughput.") + ; +} + +}} diff --git a/cpp/src/qpid/legacystore/MessageStoreImpl.h b/cpp/src/qpid/legacystore/MessageStoreImpl.h new file mode 100644 index 0000000000..68aceedfbb --- /dev/null +++ b/cpp/src/qpid/legacystore/MessageStoreImpl.h @@ -0,0 +1,380 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_MESSAGESTOREIMPL_H +#define QPID_LEGACYSTORE_MESSAGESTOREIMPL_H + +#include <string> + +#include "db-inc.h" +#include "qpid/legacystore/Cursor.h" +#include "qpid/legacystore/IdDbt.h" +#include "qpid/legacystore/IdSequence.h" +#include "qpid/legacystore/JournalImpl.h" +#include "qpid/legacystore/jrnl/jcfg.h" +#include "qpid/legacystore/PreparedTransaction.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/management/Manageable.h" +#include "qmf/org/apache/qpid/legacystore/Store.h" +#include "qpid/legacystore/TxnCtxt.h" + +// Assume DB_VERSION_MAJOR == 4 +#if (DB_VERSION_MINOR == 2) +#include <errno.h> +#define DB_BUFFER_SMALL ENOMEM +#endif + +namespace qpid { namespace sys { +class Timer; +}} + +namespace mrg { +namespace msgstore { + +/** + * An implementation of the MessageStore interface based on Berkeley DB + */ +class MessageStoreImpl : public qpid::broker::MessageStore, public qpid::management::Manageable +{ + public: + typedef boost::shared_ptr<Db> db_ptr; + typedef boost::shared_ptr<DbEnv> dbEnv_ptr; + + struct StoreOptions : public qpid::Options { + StoreOptions(const std::string& name="Store Options"); + std::string clusterName; + std::string storeDir; + u_int16_t numJrnlFiles; + bool autoJrnlExpand; + u_int16_t autoJrnlExpandMaxFiles; + u_int32_t jrnlFsizePgs; + bool truncateFlag; + u_int32_t wCachePageSizeKib; + u_int16_t tplNumJrnlFiles; + u_int32_t tplJrnlFsizePgs; + u_int32_t tplWCachePageSizeKib; + }; + + protected: + typedef std::map<u_int64_t, qpid::broker::RecoverableQueue::shared_ptr> queue_index; + typedef std::map<u_int64_t, qpid::broker::RecoverableExchange::shared_ptr> exchange_index; + typedef std::map<u_int64_t, qpid::broker::RecoverableMessage::shared_ptr> message_index; + + typedef LockedMappings::map txn_lock_map; + typedef boost::ptr_list<PreparedTransaction> txn_list; + + // Structs for Transaction Recover List (TPL) recover state + struct TplRecoverStruct { + u_int64_t rid; // rid of TPL record + bool deq_flag; + bool commit_flag; + bool tpc_flag; + TplRecoverStruct(const u_int64_t _rid, const bool _deq_flag, const bool _commit_flag, const bool _tpc_flag); + }; + typedef TplRecoverStruct TplRecover; + typedef std::pair<std::string, TplRecover> TplRecoverMapPair; + typedef std::map<std::string, TplRecover> TplRecoverMap; + typedef TplRecoverMap::const_iterator TplRecoverMapCitr; + + typedef std::map<std::string, JournalImpl*> JournalListMap; + typedef JournalListMap::iterator JournalListMapItr; + + // Default store settings + static const u_int16_t defNumJrnlFiles = 8; + static const u_int32_t defJrnlFileSizePgs = 24; + static const bool defTruncateFlag = false; + static const u_int32_t defWCachePageSize = JRNL_WMGR_DEF_PAGE_SIZE * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE / 1024; + static const u_int16_t defTplNumJrnlFiles = 8; + static const u_int32_t defTplJrnlFileSizePgs = 24; + static const u_int32_t defTplWCachePageSize = defWCachePageSize / 8; + // TODO: set defAutoJrnlExpand to true and defAutoJrnlExpandMaxFiles to 16 when auto-expand comes on-line + static const bool defAutoJrnlExpand = false; + static const u_int16_t defAutoJrnlExpandMaxFiles = 0; + + static const std::string storeTopLevelDir; + static qpid::sys::Duration defJournalGetEventsTimeout; + static qpid::sys::Duration defJournalFlushTimeout; + + std::list<db_ptr> dbs; + dbEnv_ptr dbenv; + db_ptr queueDb; + db_ptr configDb; + db_ptr exchangeDb; + db_ptr mappingDb; + db_ptr bindingDb; + db_ptr generalDb; + + // Pointer to Transaction Prepared List (TPL) journal instance + boost::shared_ptr<TplJournalImpl> tplStorePtr; + TplRecoverMap tplRecoverMap; + qpid::sys::Mutex tplInitLock; + JournalListMap journalList; + qpid::sys::Mutex journalListLock; + qpid::sys::Mutex bdbLock; + + IdSequence queueIdSequence; + IdSequence exchangeIdSequence; + IdSequence generalIdSequence; + IdSequence messageIdSequence; + std::string storeDir; + u_int16_t numJrnlFiles; + bool autoJrnlExpand; + u_int16_t autoJrnlExpandMaxFiles; + u_int32_t jrnlFsizeSblks; + bool truncateFlag; + u_int32_t wCachePgSizeSblks; + u_int16_t wCacheNumPages; + u_int16_t tplNumJrnlFiles; + u_int32_t tplJrnlFsizeSblks; + u_int32_t tplWCachePgSizeSblks; + u_int16_t tplWCacheNumPages; + u_int64_t highestRid; + bool isInit; + const char* envPath; + qpid::broker::Broker* broker; + + qmf::org::apache::qpid::legacystore::Store::shared_ptr mgmtObject; + qpid::management::ManagementAgent* agent; + + + // Parameter validation and calculation + static u_int16_t chkJrnlNumFilesParam(const u_int16_t param, + const std::string paramName); + static u_int32_t chkJrnlFileSizeParam(const u_int32_t param, + const std::string paramName, + const u_int32_t wCachePgSizeSblks = 0); + static u_int32_t chkJrnlWrPageCacheSize(const u_int32_t param, + const std::string paramName, + const u_int16_t jrnlFsizePgs); + static u_int16_t getJrnlWrNumPages(const u_int32_t wrPageSizeKib); + void chkJrnlAutoExpandOptions(const MessageStoreImpl::StoreOptions* opts, + bool& autoJrnlExpand, + u_int16_t& autoJrnlExpandMaxFiles, + const std::string& autoJrnlExpandMaxFilesParamName, + const u_int16_t numJrnlFiles, + const std::string& numJrnlFilesParamName); + + void init(); + + void recoverQueues(TxnCtxt& txn, + qpid::broker::RecoveryManager& recovery, + queue_index& index, + txn_list& locked, + message_index& messages); + void recoverMessages(TxnCtxt& txn, + qpid::broker::RecoveryManager& recovery, + queue_index& index, + txn_list& locked, + message_index& prepared); + void recoverMessages(TxnCtxt& txn, + qpid::broker::RecoveryManager& recovery, + qpid::broker::RecoverableQueue::shared_ptr& queue, + txn_list& locked, + message_index& prepared, + long& rcnt, + long& idcnt); + qpid::broker::RecoverableMessage::shared_ptr getExternMessage(qpid::broker::RecoveryManager& recovery, + uint64_t mId, + unsigned& headerSize); + void recoverExchanges(TxnCtxt& txn, + qpid::broker::RecoveryManager& recovery, + exchange_index& index); + void recoverBindings(TxnCtxt& txn, + exchange_index& exchanges, + queue_index& queues); + void recoverGeneral(TxnCtxt& txn, + qpid::broker::RecoveryManager& recovery); + int enqueueMessage(TxnCtxt& txn, + IdDbt& msgId, + qpid::broker::RecoverableMessage::shared_ptr& msg, + queue_index& index, + txn_list& locked, + message_index& prepared); + void readTplStore(); + void recoverTplStore(); + void recoverLockedMappings(txn_list& txns); + TxnCtxt* check(qpid::broker::TransactionContext* ctxt); + u_int64_t msgEncode(std::vector<char>& buff, const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message); + void store(const qpid::broker::PersistableQueue* queue, + TxnCtxt* txn, + const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message, + bool newId); + void async_dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg, + const qpid::broker::PersistableQueue& queue); + void destroy(db_ptr db, + const qpid::broker::Persistable& p); + bool create(db_ptr db, + IdSequence& seq, + const qpid::broker::Persistable& p); + void completed(TxnCtxt& txn, + bool commit); + void deleteBindingsForQueue(const qpid::broker::PersistableQueue& queue); + void deleteBinding(const qpid::broker::PersistableExchange& exchange, + const qpid::broker::PersistableQueue& queue, + const std::string& key); + + void put(db_ptr db, + DbTxn* txn, + Dbt& key, + Dbt& value); + void open(db_ptr db, + DbTxn* txn, + const char* file, + bool dupKey); + void closeDbs(); + + // journal functions + void createJrnlQueue(const qpid::broker::PersistableQueue& queue); + u_int32_t bHash(const std::string str); + std::string getJrnlDir(const qpid::broker::PersistableQueue& queue); //for exmaple /var/rhm/ + queueDir/ + std::string getJrnlHashDir(const std::string& queueName); + std::string getJrnlBaseDir(); + std::string getBdbBaseDir(); + std::string getTplBaseDir(); + inline void checkInit() { + // TODO: change the default dir to ~/.qpidd + if (!isInit) { init("/tmp"); isInit = true; } + } + void chkTplStoreInit(); + + // debug aid for printing XIDs that may contain non-printable chars + static std::string xid2str(const std::string xid) { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (unsigned i=0; i<xid.size(); i++) { + if (isprint(xid[i])) + oss << xid[i]; + else + oss << "/" << std::setw(2) << (int)((char)xid[i]); + } + return oss.str(); + } + + public: + typedef boost::shared_ptr<MessageStoreImpl> shared_ptr; + + MessageStoreImpl(qpid::broker::Broker* broker, const char* envpath = 0); + + virtual ~MessageStoreImpl(); + + bool init(const qpid::Options* options); + + bool init(const std::string& dir, + u_int16_t jfiles = defNumJrnlFiles, + u_int32_t jfileSizePgs = defJrnlFileSizePgs, + const bool truncateFlag = false, + u_int32_t wCachePageSize = defWCachePageSize, + u_int16_t tplJfiles = defTplNumJrnlFiles, + u_int32_t tplJfileSizePgs = defTplJrnlFileSizePgs, + u_int32_t tplWCachePageSize = defTplWCachePageSize, + bool autoJExpand = defAutoJrnlExpand, + u_int16_t autoJExpandMaxFiles = defAutoJrnlExpandMaxFiles); + + void truncateInit(const bool saveStoreContent = false); + + void initManagement (); + + void finalize(); + + void create(qpid::broker::PersistableQueue& queue, + const qpid::framing::FieldTable& args); + + void destroy(qpid::broker::PersistableQueue& queue); + + void create(const qpid::broker::PersistableExchange& queue, + const qpid::framing::FieldTable& args); + + void destroy(const qpid::broker::PersistableExchange& queue); + + void bind(const qpid::broker::PersistableExchange& exchange, + const qpid::broker::PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args); + + void unbind(const qpid::broker::PersistableExchange& exchange, + const qpid::broker::PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args); + + void create(const qpid::broker::PersistableConfig& config); + + void destroy(const qpid::broker::PersistableConfig& config); + + void recover(qpid::broker::RecoveryManager& queues); + + void stage(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg); + + void destroy(qpid::broker::PersistableMessage& msg); + + void appendContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + const std::string& data); + + void loadContent(const qpid::broker::PersistableQueue& queue, + const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + + void enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg, + const qpid::broker::PersistableQueue& queue); + + void dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg, + const qpid::broker::PersistableQueue& queue); + + void flush(const qpid::broker::PersistableQueue& queue); + + u_int32_t outstandingQueueAIO(const qpid::broker::PersistableQueue& queue); + + void collectPreparedXids(std::set<std::string>& xids); + + std::auto_ptr<qpid::broker::TransactionContext> begin(); + + std::auto_ptr<qpid::broker::TPCTransactionContext> begin(const std::string& xid); + + void prepare(qpid::broker::TPCTransactionContext& ctxt); + + void localPrepare(TxnCtxt* ctxt); + + void commit(qpid::broker::TransactionContext& ctxt); + + void abort(qpid::broker::TransactionContext& ctxt); + + qpid::management::ManagementObject::shared_ptr GetManagementObject (void) const + { return mgmtObject; } + + inline qpid::management::Manageable::status_t ManagementMethod (u_int32_t, qpid::management::Args&, std::string&) + { return qpid::management::Manageable::STATUS_OK; } + + std::string getStoreDir() const; + + private: + void journalDeleted(JournalImpl&); + +}; // class MessageStoreImpl + +} // namespace msgstore +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_MESSAGESTOREIMPL_H diff --git a/cpp/src/qpid/legacystore/PreparedTransaction.cpp b/cpp/src/qpid/legacystore/PreparedTransaction.cpp new file mode 100644 index 0000000000..50b81e2824 --- /dev/null +++ b/cpp/src/qpid/legacystore/PreparedTransaction.cpp @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/legacystore/PreparedTransaction.h" +#include <algorithm> + +using namespace mrg::msgstore; +using std::string; + +void LockedMappings::add(queue_id queue, message_id message) +{ + locked.push_back(std::make_pair(queue, message)); +} + +bool LockedMappings::isLocked(queue_id queue, message_id message) +{ + idpair op( std::make_pair(queue, message) ); + return find(locked.begin(), locked.end(), op) != locked.end(); +} + +void LockedMappings::add(LockedMappings::map& map, std::string& key, queue_id queue, message_id message) +{ + LockedMappings::map::iterator i = map.find(key); + if (i == map.end()) { + LockedMappings::shared_ptr ptr(new LockedMappings()); + i = map.insert(std::make_pair(key, ptr)).first; + } + i->second->add(queue, message); +} + +bool PreparedTransaction::isLocked(queue_id queue, message_id message) +{ + return (enqueues.get() && enqueues->isLocked(queue, message)) + || (dequeues.get() && dequeues->isLocked(queue, message)); +} + + +bool PreparedTransaction::isLocked(PreparedTransaction::list& txns, queue_id queue, message_id message) +{ + for (PreparedTransaction::list::iterator i = txns.begin(); i != txns.end(); i++) { + if (i->isLocked(queue, message)) { + return true; + } + } + return false; +} + +PreparedTransaction::list::iterator PreparedTransaction::getLockedPreparedTransaction(PreparedTransaction::list& txns, queue_id queue, message_id message) +{ + for (PreparedTransaction::list::iterator i = txns.begin(); i != txns.end(); i++) { + if (i->isLocked(queue, message)) { + return i; + } + } + return txns.end(); +} + +PreparedTransaction::PreparedTransaction(const std::string& _xid, + LockedMappings::shared_ptr _enqueues, + LockedMappings::shared_ptr _dequeues) + + : xid(_xid), enqueues(_enqueues), dequeues(_dequeues) {} + diff --git a/cpp/src/qpid/legacystore/PreparedTransaction.h b/cpp/src/qpid/legacystore/PreparedTransaction.h new file mode 100644 index 0000000000..c5f7b9458a --- /dev/null +++ b/cpp/src/qpid/legacystore/PreparedTransaction.h @@ -0,0 +1,74 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_PREPAREDTRANSACTION_H +#define QPID_LEGACYSTORE_PREPAREDTRANSACTION_H + +#include <list> +#include <map> +#include <set> +#include <string> +#include <boost/shared_ptr.hpp> +#include <boost/ptr_container/ptr_list.hpp> + +namespace mrg{ +namespace msgstore{ + +typedef u_int64_t queue_id; +typedef u_int64_t message_id; + +class LockedMappings +{ +public: + typedef boost::shared_ptr<LockedMappings> shared_ptr; + typedef std::map<std::string, shared_ptr> map; + typedef std::pair<queue_id, message_id> idpair; + typedef std::list<idpair>::iterator iterator; + + void add(queue_id queue, message_id message); + bool isLocked(queue_id queue, message_id message); + std::size_t size() { return locked.size(); } + iterator begin() { return locked.begin(); } + iterator end() { return locked.end(); } + + static void add(LockedMappings::map& map, std::string& key, queue_id queue, message_id message); + +private: + std::list<idpair> locked; +}; + +struct PreparedTransaction +{ + typedef boost::ptr_list<PreparedTransaction> list; + + const std::string xid; + const LockedMappings::shared_ptr enqueues; + const LockedMappings::shared_ptr dequeues; + + PreparedTransaction(const std::string& xid, LockedMappings::shared_ptr enqueues, LockedMappings::shared_ptr dequeues); + bool isLocked(queue_id queue, message_id message); + static bool isLocked(PreparedTransaction::list& txns, queue_id queue, message_id message); + static PreparedTransaction::list::iterator getLockedPreparedTransaction(PreparedTransaction::list& txns, queue_id queue, message_id message); +}; + +}} + +#endif // ifndef QPID_LEGACYSTORE_PREPAREDTRANSACTION_H diff --git a/cpp/src/qpid/legacystore/StoreException.h b/cpp/src/qpid/legacystore/StoreException.h new file mode 100644 index 0000000000..6624aafd5a --- /dev/null +++ b/cpp/src/qpid/legacystore/StoreException.h @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_STOREEXCEPTION_H +#define QPID_LEGACYSTORE_STOREEXCEPTION_H + +#include "qpid/legacystore/IdDbt.h" +#include <boost/format.hpp> + +namespace mrg{ +namespace msgstore{ + +class StoreException : public std::exception +{ + std::string text; +public: + StoreException(const std::string& _text) : text(_text) {} + StoreException(const std::string& _text, const DbException& cause) : text(_text + ": " + cause.what()) {} + virtual ~StoreException() throw() {} + virtual const char* what() const throw() { return text.c_str(); } +}; + +class StoreFullException : public StoreException +{ +public: + StoreFullException(const std::string& _text) : StoreException(_text) {} + StoreFullException(const std::string& _text, const DbException& cause) : StoreException(_text, cause) {} + virtual ~StoreFullException() throw() {} + +}; + +#define THROW_STORE_EXCEPTION(MESSAGE) throw StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__)) +#define THROW_STORE_EXCEPTION_2(MESSAGE, EXCEPTION) throw StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__), EXCEPTION) +#define THROW_STORE_FULL_EXCEPTION(MESSAGE) throw StoreFullException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__)) + +}} + +#endif // ifndef QPID_LEGACYSTORE_STOREEXCEPTION_H diff --git a/cpp/src/qpid/legacystore/StorePlugin.cpp b/cpp/src/qpid/legacystore/StorePlugin.cpp new file mode 100644 index 0000000000..f9b77ce02c --- /dev/null +++ b/cpp/src/qpid/legacystore/StorePlugin.cpp @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/broker/Broker.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/DataDir.h" +#include "qpid/log/Statement.h" +#include "qpid/legacystore/MessageStoreImpl.h" + +using mrg::msgstore::MessageStoreImpl; + +namespace qpid { +namespace broker { + +using namespace std; + +struct StorePlugin : public Plugin { + + MessageStoreImpl::StoreOptions options; + boost::shared_ptr<MessageStoreImpl> store; + + Options* getOptions() { return &options; } + + void earlyInitialize (Plugin::Target& target) + { + Broker* broker = dynamic_cast<Broker*>(&target); + if (!broker) return; + store.reset(new MessageStoreImpl(broker)); + DataDir& dataDir = broker->getDataDir (); + if (options.storeDir.empty ()) + { + if (!dataDir.isEnabled ()) + throw Exception ("msgstore: If --data-dir is blank or --no-data-dir is specified, --store-dir must be present."); + + options.storeDir = dataDir.getPath (); + } + store->init(&options); + boost::shared_ptr<qpid::broker::MessageStore> brokerStore(store); + broker->setStore(brokerStore); + target.addFinalizer(boost::bind(&StorePlugin::finalize, this)); + } + + void initialize(Plugin::Target& target) + { + Broker* broker = dynamic_cast<Broker*>(&target); + if (!broker) return; + if (!store) return; + QPID_LOG(info, "Enabling management instrumentation for the store."); + store->initManagement(); + } + + void finalize() + { + store.reset(); + } + + const char* id() {return "StorePlugin";} +}; + +static StorePlugin instance; // Static initialization. + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/legacystore/TxnCtxt.cpp b/cpp/src/qpid/legacystore/TxnCtxt.cpp new file mode 100644 index 0000000000..1db41f4c70 --- /dev/null +++ b/cpp/src/qpid/legacystore/TxnCtxt.cpp @@ -0,0 +1,184 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/legacystore/TxnCtxt.h" + +#include <sstream> + +#include "qpid/legacystore/jrnl/jexception.h" +#include "qpid/legacystore/StoreException.h" + +namespace mrg { +namespace msgstore { + +void TxnCtxt::completeTxn(bool commit) { + sync(); + for (ipqItr i = impactedQueues.begin(); i != impactedQueues.end(); i++) { + commitTxn(static_cast<JournalImpl*>(*i), commit); + } + impactedQueues.clear(); + if (preparedXidStorePtr) + commitTxn(preparedXidStorePtr, commit); +} + +void TxnCtxt::commitTxn(JournalImpl* jc, bool commit) { + if (jc && loggedtx) { /* if using journal */ + boost::intrusive_ptr<DataTokenImpl> dtokp(new DataTokenImpl); + dtokp->addRef(); + dtokp->set_external_rid(true); + dtokp->set_rid(loggedtx->next()); + try { + if (commit) { + jc->txn_commit(dtokp.get(), getXid()); + sync(); + } else { + jc->txn_abort(dtokp.get(), getXid()); + } + } catch (const journal::jexception& e) { + THROW_STORE_EXCEPTION(std::string("Error commit") + e.what()); + } + } +} + +// static +uuid_t TxnCtxt::uuid; + +// static +IdSequence TxnCtxt::uuidSeq; + +// static +bool TxnCtxt::staticInit = TxnCtxt::setUuid(); + +// static +bool TxnCtxt::setUuid() { + ::uuid_generate(uuid); + return true; +} + +TxnCtxt::TxnCtxt(IdSequence* _loggedtx) : loggedtx(_loggedtx), dtokp(new DataTokenImpl), preparedXidStorePtr(0), txn(0) { + if (loggedtx) { +// // Human-readable tid: 53 bytes +// // uuit_t is a char[16] +// tid.reserve(53); +// u_int64_t* u1 = (u_int64_t*)uuid; +// u_int64_t* u2 = (u_int64_t*)(uuid + sizeof(u_int64_t)); +// std::stringstream s; +// s << "tid:" << std::hex << std::setfill('0') << std::setw(16) << uuidSeq.next() << ":" << std::setw(16) << *u1 << std::setw(16) << *u2; +// tid.assign(s.str()); + + // Binary tid: 24 bytes + tid.reserve(24); + u_int64_t c = uuidSeq.next(); + tid.append((char*)&c, sizeof(c)); + tid.append((char*)&uuid, sizeof(uuid)); + } +} + +TxnCtxt::TxnCtxt(std::string _tid, IdSequence* _loggedtx) : loggedtx(_loggedtx), dtokp(new DataTokenImpl), preparedXidStorePtr(0), tid(_tid), txn(0) {} + +TxnCtxt::~TxnCtxt() { abort(); } + +void TxnCtxt::sync() { + if (loggedtx) { + try { + for (ipqItr i = impactedQueues.begin(); i != impactedQueues.end(); i++) + jrnl_flush(static_cast<JournalImpl*>(*i)); + if (preparedXidStorePtr) + jrnl_flush(preparedXidStorePtr); + for (ipqItr i = impactedQueues.begin(); i != impactedQueues.end(); i++) + jrnl_sync(static_cast<JournalImpl*>(*i), &journal::jcntl::_aio_cmpl_timeout); + if (preparedXidStorePtr) + jrnl_sync(preparedXidStorePtr, &journal::jcntl::_aio_cmpl_timeout); + } catch (const journal::jexception& e) { + THROW_STORE_EXCEPTION(std::string("Error during txn sync: ") + e.what()); + } + } +} + +void TxnCtxt::jrnl_flush(JournalImpl* jc) { + if (jc && !(jc->is_txn_synced(getXid()))) + jc->flush(); +} + +void TxnCtxt::jrnl_sync(JournalImpl* jc, timespec* timeout) { + if (!jc || jc->is_txn_synced(getXid())) + return; + while (jc->get_wr_aio_evt_rem()) { + if (jc->get_wr_events(timeout) == journal::jerrno::AIO_TIMEOUT && timeout) + THROW_STORE_EXCEPTION(std::string("Error: timeout waiting for TxnCtxt::jrnl_sync()")); + } +} + +void TxnCtxt::begin(DbEnv* env, bool sync) { + int err; + try { err = env->txn_begin(0, &txn, 0); } + catch (const DbException&) { txn = 0; throw; } + if (err != 0) { + std::ostringstream oss; + oss << "Error: Env::txn_begin() returned error code: " << err; + THROW_STORE_EXCEPTION(oss.str()); + } + if (sync) + globalHolder = AutoScopedLock(new qpid::sys::Mutex::ScopedLock(globalSerialiser)); +} + +void TxnCtxt::commit() { + if (txn) { + txn->commit(0); + txn = 0; + globalHolder.reset(); + } +} + +void TxnCtxt::abort(){ + if (txn) { + txn->abort(); + txn = 0; + globalHolder.reset(); + } +} + +DbTxn* TxnCtxt::get() { return txn; } + +bool TxnCtxt::isTPC() { return false; } + +const std::string& TxnCtxt::getXid() { return tid; } + +void TxnCtxt::addXidRecord(qpid::broker::ExternalQueueStore* queue) { impactedQueues.insert(queue); } + +void TxnCtxt::complete(bool commit) { completeTxn(commit); } + +bool TxnCtxt::impactedQueuesEmpty() { return impactedQueues.empty(); } + +DataTokenImpl* TxnCtxt::getDtok() { return dtokp.get(); } + +void TxnCtxt::incrDtokRef() { dtokp->addRef(); } + +void TxnCtxt::recoverDtok(const u_int64_t rid, const std::string xid) { + dtokp->set_rid(rid); + dtokp->set_wstate(DataTokenImpl::ENQ); + dtokp->set_xid(xid); + dtokp->set_external_rid(true); +} + +TPCTxnCtxt::TPCTxnCtxt(const std::string& _xid, IdSequence* _loggedtx) : TxnCtxt(_loggedtx), xid(_xid) {} + +}} diff --git a/cpp/src/qpid/legacystore/TxnCtxt.h b/cpp/src/qpid/legacystore/TxnCtxt.h new file mode 100644 index 0000000000..77eaa27cd7 --- /dev/null +++ b/cpp/src/qpid/legacystore/TxnCtxt.h @@ -0,0 +1,117 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifndef QPID_LEGACYSTORE_TXNCTXT_H +#define QPID_LEGACYSTORE_TXNCTXT_H + +#include "db-inc.h" +#include <memory> +#include <set> +#include <string> + +#include "qpid/legacystore/DataTokenImpl.h" +#include "qpid/legacystore/IdSequence.h" +#include "qpid/legacystore/JournalImpl.h" +#include "qpid/broker/PersistableQueue.h" +#include "qpid/broker/TransactionalStore.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/uuid.h" + +#include <boost/intrusive_ptr.hpp> + +namespace mrg { +namespace msgstore { + +class TxnCtxt : public qpid::broker::TransactionContext +{ + protected: + static qpid::sys::Mutex globalSerialiser; + + static uuid_t uuid; + static IdSequence uuidSeq; + static bool staticInit; + static bool setUuid(); + + typedef std::set<qpid::broker::ExternalQueueStore*> ipqdef; + typedef ipqdef::iterator ipqItr; + typedef std::auto_ptr<qpid::sys::Mutex::ScopedLock> AutoScopedLock; + + ipqdef impactedQueues; // list of Queues used in the txn + IdSequence* loggedtx; + boost::intrusive_ptr<DataTokenImpl> dtokp; + AutoScopedLock globalHolder; + JournalImpl* preparedXidStorePtr; + + /** + * local txn id, if non XA. + */ + std::string tid; + DbTxn* txn; + + virtual void completeTxn(bool commit); + void commitTxn(JournalImpl* jc, bool commit); + void jrnl_flush(JournalImpl* jc); + void jrnl_sync(JournalImpl* jc, timespec* timeout); + + public: + TxnCtxt(IdSequence* _loggedtx=NULL); + TxnCtxt(std::string _tid, IdSequence* _loggedtx); + virtual ~TxnCtxt(); + + /** + * Call to make sure all the data for this txn is written to safe store + * + *@return if the data successfully synced. + */ + void sync(); + void begin(DbEnv* env, bool sync = false); + void commit(); + void abort(); + DbTxn* get(); + virtual bool isTPC(); + virtual const std::string& getXid(); + + void addXidRecord(qpid::broker::ExternalQueueStore* queue); + inline void prepare(JournalImpl* _preparedXidStorePtr) { preparedXidStorePtr = _preparedXidStorePtr; } + void complete(bool commit); + bool impactedQueuesEmpty(); + DataTokenImpl* getDtok(); + void incrDtokRef(); + void recoverDtok(const u_int64_t rid, const std::string xid); +}; + + +class TPCTxnCtxt : public TxnCtxt, public qpid::broker::TPCTransactionContext +{ + protected: + const std::string xid; + + public: + TPCTxnCtxt(const std::string& _xid, IdSequence* _loggedtx); + inline virtual bool isTPC() { return true; } + inline virtual const std::string& getXid() { return xid; } +}; + +}} + +#endif // ifndef QPID_LEGACYSTORE_TXNCTXT_H + + diff --git a/cpp/src/qpid/legacystore/jrnl/aio.cpp b/cpp/src/qpid/legacystore/jrnl/aio.cpp new file mode 100644 index 0000000000..ffbddd887e --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/aio.cpp @@ -0,0 +1,41 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file aio.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::aio (libaio interface + * encapsulation). See comments in file aio.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/aio.h" + +namespace mrg +{ +namespace journal +{ + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/aio.h b/cpp/src/qpid/legacystore/jrnl/aio.h new file mode 100644 index 0000000000..b1de5f79f7 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/aio.h @@ -0,0 +1,153 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file aio.h + * + * Qpid asynchronous store plugin library + * + * This file contains an encapsulation of the libaio interface used + * by the journal. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_AIO_H +#define QPID_LEGACYSTORE_JRNL_AIO_H + +#include <libaio.h> +#include <cstring> +#include <sys/types.h> +#include <string.h> + +namespace mrg +{ +namespace journal +{ + +typedef iocb aio_cb; +typedef io_event aio_event; + +/** + * \brief This class is a C++ wrapper class for the libaio functions used by the journal. Note that only those + * functions used by the journal are included here. This is not a complete implementation of all libaio functions. + */ +class aio +{ +public: + static inline int queue_init(int maxevents, io_context_t* ctxp) + { + return ::io_queue_init(maxevents, ctxp); + } + + static inline int queue_release(io_context_t ctx) + { + return ::io_queue_release(ctx); + } + + static inline int submit(io_context_t ctx, long nr, aio_cb* aios[]) + { + return ::io_submit(ctx, nr, aios); + } + + static inline int getevents(io_context_t ctx, long min_nr, long nr, aio_event* events, timespec* const timeout) + { + return ::io_getevents(ctx, min_nr, nr, events, timeout); + } + + /** + * \brief This function allows iocbs to be initialized with a pointer that can be re-used. This prepares an + * aio_cb struct for read use. (This is a wrapper for libaio's ::io_prep_pread() function.) + * + * \param aiocbp Pointer to the aio_cb struct to be prepared. + * \param fd File descriptor to be used for read. + * \param buf Pointer to buffer in which read data is to be placed. + * \param count Number of bytes to read - buffer must be large enough. + * \param offset Offset within file from which data will be read. + */ + static inline void prep_pread(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset) + { + ::io_prep_pread(aiocbp, fd, buf, count, offset); + } + + /** + * \brief Special version of libaio's io_prep_pread() which preserves the value of the data pointer. This allows + * iocbs to be initialized with a pointer that can be re-used. This prepares a aio_cb struct for read use. + * + * \param aiocbp Pointer to the aio_cb struct to be prepared. + * \param fd File descriptor to be used for read. + * \param buf Pointer to buffer in which read data is to be placed. + * \param count Number of bytes to read - buffer must be large enough. + * \param offset Offset within file from which data will be read. + */ + static inline void prep_pread_2(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset) + { + std::memset((void*) ((char*) aiocbp + sizeof(void*)), 0, sizeof(aio_cb) - sizeof(void*)); + aiocbp->aio_fildes = fd; + aiocbp->aio_lio_opcode = IO_CMD_PREAD; + aiocbp->aio_reqprio = 0; + aiocbp->u.c.buf = buf; + aiocbp->u.c.nbytes = count; + aiocbp->u.c.offset = offset; + } + + /** + * \brief This function allows iocbs to be initialized with a pointer that can be re-used. This function prepares + * an aio_cb struct for write use. (This is a wrapper for libaio's ::io_prep_pwrite() function.) + * + * \param aiocbp Pointer to the aio_cb struct to be prepared. + * \param fd File descriptor to be used for write. + * \param buf Pointer to buffer in which data to be written is located. + * \param count Number of bytes to write. + * \param offset Offset within file to which data will be written. + */ + static inline void prep_pwrite(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset) + { + ::io_prep_pwrite(aiocbp, fd, buf, count, offset); + } + + /** + * \brief Special version of libaio's io_prep_pwrite() which preserves the value of the data pointer. This allows + * iocbs to be initialized with a pointer that can be re-used. This function prepares an aio_cb struct for write + * use. + * + * \param aiocbp Pointer to the aio_cb struct to be prepared. + * \param fd File descriptor to be used for write. + * \param buf Pointer to buffer in which data to be written is located. + * \param count Number of bytes to write. + * \param offset Offset within file to which data will be written. + */ + static inline void prep_pwrite_2(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset) + { + std::memset((void*) ((char*) aiocbp + sizeof(void*)), 0, sizeof(aio_cb) - sizeof(void*)); + aiocbp->aio_fildes = fd; + aiocbp->aio_lio_opcode = IO_CMD_PWRITE; + aiocbp->aio_reqprio = 0; + aiocbp->u.c.buf = buf; + aiocbp->u.c.nbytes = count; + aiocbp->u.c.offset = offset; + } +}; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_AIO_H diff --git a/cpp/src/qpid/legacystore/jrnl/aio_callback.h b/cpp/src/qpid/legacystore/jrnl/aio_callback.h new file mode 100644 index 0000000000..90249278a5 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/aio_callback.h @@ -0,0 +1,57 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file aio_callback.h + * + * Qpid asynchronous store plugin library + * + * This file contains the definition for the AIO callback function + * pointer. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_AIO_CALLBACK_H +#define QPID_LEGACYSTORE_JRNL_AIO_CALLBACK_H + +#include <vector> +#include <sys/types.h> + +namespace mrg +{ +namespace journal +{ + + class data_tok; + + class aio_callback + { + public: + virtual ~aio_callback() {} + virtual void wr_aio_cb(std::vector<data_tok*>& dtokl) = 0; + virtual void rd_aio_cb(std::vector<u_int16_t>& pil) = 0; + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_AIO_CALLBACK_H diff --git a/cpp/src/qpid/legacystore/jrnl/cvar.cpp b/cpp/src/qpid/legacystore/jrnl/cvar.cpp new file mode 100644 index 0000000000..e4010bf91f --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/cvar.cpp @@ -0,0 +1,33 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file cvar.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::cvar (condition variable). See + * comments in file cvar.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/cvar.h" diff --git a/cpp/src/qpid/legacystore/jrnl/cvar.h b/cpp/src/qpid/legacystore/jrnl/cvar.h new file mode 100644 index 0000000000..0498e743a2 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/cvar.h @@ -0,0 +1,87 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file cvar.h + * + * Qpid asynchronous store plugin library + * + * This file contains a posix condition variable class. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_CVAR_H +#define QPID_LEGACYSTORE_JRNL_CVAR_H + +#include <cstring> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include "qpid/legacystore/jrnl/smutex.h" +#include "qpid/legacystore/jrnl/time_ns.h" +#include <pthread.h> +#include <sstream> + +namespace mrg +{ +namespace journal +{ + + // Ultra-simple thread condition variable class + class cvar + { + private: + const smutex& _sm; + pthread_cond_t _c; + public: + inline cvar(const smutex& sm) : _sm(sm) { ::pthread_cond_init(&_c, 0); } + inline ~cvar() { ::pthread_cond_destroy(&_c); } + inline void wait() + { + PTHREAD_CHK(::pthread_cond_wait(&_c, _sm.get()), "::pthread_cond_wait", "cvar", "wait"); + } + inline void timedwait(timespec& ts) + { + PTHREAD_CHK(::pthread_cond_timedwait(&_c, _sm.get(), &ts), "::pthread_cond_timedwait", "cvar", "timedwait"); + } + inline bool waitintvl(const long intvl_ns) + { + time_ns t; t.now(); t+=intvl_ns; + int ret = ::pthread_cond_timedwait(&_c, _sm.get(), &t); + if (ret == ETIMEDOUT) + return true; + PTHREAD_CHK(ret, "::pthread_cond_timedwait", "cvar", "waitintvl"); + return false; + } + inline void signal() + { + PTHREAD_CHK(::pthread_cond_signal(&_c), "::pthread_cond_signal", "cvar", "notify"); + } + inline void broadcast() + { + PTHREAD_CHK(::pthread_cond_broadcast(&_c), "::pthread_cond_broadcast", "cvar", "broadcast"); + } + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_CVAR_H diff --git a/cpp/src/qpid/legacystore/jrnl/data_tok.cpp b/cpp/src/qpid/legacystore/jrnl/data_tok.cpp new file mode 100644 index 0000000000..ce7206d80d --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/data_tok.cpp @@ -0,0 +1,194 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file data_tok.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::data_tok (data block token). + * See comments in file data_tok.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/data_tok.h" + +#include <iomanip> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include "qpid/legacystore/jrnl/slock.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +// Static members + +u_int64_t data_tok::_cnt = 0; +smutex data_tok::_mutex; + +data_tok::data_tok(): + _wstate(NONE), + _rstate(UNREAD), + _dsize(0), + _dblks_written(0), + _dblks_read(0), + _pg_cnt(0), + _fid(0), + _rid(0), + _xid(), + _dequeue_rid(0), + _external_rid(false) +{ + slock s(_mutex); + _icnt = _cnt++; +} + +data_tok::~data_tok() {} + +const char* +data_tok::wstate_str() const +{ + return wstate_str(_wstate); +} + +const char* +data_tok::wstate_str(write_state wstate) +{ + switch (wstate) + { + case NONE: + return "NONE"; + case ENQ_CACHED: + return "ENQ_CACHED"; + case ENQ_PART: + return "ENQ_PART"; + case ENQ_SUBM: + return "ENQ_SUBM"; + case ENQ: + return "ENQ"; + case DEQ_CACHED: + return "DEQ_CACHED"; + case DEQ_PART: + return "DEQ_PART"; + case DEQ_SUBM: + return "DEQ_SUBM"; + case DEQ: + return "DEQ"; + case ABORT_CACHED: + return "ABORT_CACHED"; + case ABORT_PART: + return "ABORT_PART"; + case ABORT_SUBM: + return "ABORT_SUBM"; + case ABORTED: + return "ABORTED"; + case COMMIT_CACHED: + return "COMMIT_CACHED"; + case COMMIT_PART: + return "COMMIT_PART"; + case COMMIT_SUBM: + return "COMMIT_SUBM"; + case COMMITTED: + return "COMMITTED"; + } + // Not using default: forces compiler to ensure all cases are covered. + return "<wstate unknown>"; +} + +const char* +data_tok::rstate_str() const +{ + return rstate_str(_rstate); +} + +const char* +data_tok::rstate_str(read_state rstate) +{ + switch (rstate) + { + case NONE: + return "NONE"; + case READ_PART: + return "READ_PART"; + case SKIP_PART: + return "SKIP_PART"; + case READ: + return "READ"; + // Not using default: forces compiler to ensure all cases are covered. + } + return "<rstate unknown>"; +} + +void +data_tok::set_rstate(const read_state rstate) +{ + if (_wstate != ENQ && rstate != UNREAD) + { + std::ostringstream oss; + oss << "Attempted to change read state to " << rstate_str(rstate); + oss << " while write state is not enqueued (wstate ENQ); wstate=" << wstate_str() << "."; + throw jexception(jerrno::JERR_DTOK_ILLEGALSTATE, oss.str(), "data_tok", + "set_rstate"); + } + _rstate = rstate; +} + +void +data_tok::reset() +{ + _wstate = NONE; + _rstate = UNREAD; + _dsize = 0; + _dblks_written = 0; + _dblks_read = 0; + _pg_cnt = 0; + _fid = 0; + _rid = 0; + _xid.clear(); +} + +// debug aid +std::string +data_tok::status_str() const +{ + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "dtok id=0x" << _icnt << "; ws=" << wstate_str() << "; rs=" << rstate_str(); + oss << "; fid=0x" << _fid << "; rid=0x" << _rid << "; xid="; + for (unsigned i=0; i<_xid.size(); i++) + { + if (isprint(_xid[i])) + oss << _xid[i]; + else + oss << "/" << std::setw(2) << (int)((char)_xid[i]); + } + oss << "; drid=0x" << _dequeue_rid << " extrid=" << (_external_rid?"T":"F"); + oss << "; ds=0x" << _dsize << "; dw=0x" << _dblks_written << "; dr=0x" << _dblks_read; + oss << " pc=0x" << _pg_cnt; + return oss.str(); +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/data_tok.h b/cpp/src/qpid/legacystore/jrnl/data_tok.h new file mode 100644 index 0000000000..e35f069399 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/data_tok.h @@ -0,0 +1,172 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file data_tok.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::data_tok (data block token). + * See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_DATA_TOK_H +#define QPID_LEGACYSTORE_JRNL_DATA_TOK_H + +namespace mrg +{ +namespace journal +{ +class data_tok; +} +} + +#include <cassert> +#include <cstddef> +#include "qpid/legacystore/jrnl/smutex.h" +#include <pthread.h> +#include <string> +#include <sys/types.h> + +namespace mrg +{ + +namespace journal +{ + + /** + * \class data_tok + * \brief Data block token (data_tok) used to track wstate of a data block through asynchronous + * I/O process + */ + class data_tok + { + public: + // TODO: Fix this, separate write state from operation + // ie: wstate = NONE, CACHED, PART, SUBM, COMPL + // op = ENQUEUE, DEQUEUE, ABORT, COMMIT + enum write_state + { + NONE, ///< Data block not sent to journal + ENQ_CACHED, ///< Data block enqueue written to page cache + ENQ_PART, ///< Data block part-submitted to AIO, waiting for page buffer to free up + ENQ_SUBM, ///< Data block enqueue submitted to AIO + ENQ, ///< Data block enqueue AIO write complete (enqueue complete) + DEQ_CACHED, ///< Data block dequeue written to page cache + DEQ_PART, ///< Data block part-submitted to AIO, waiting for page buffer to free up + DEQ_SUBM, ///< Data block dequeue submitted to AIO + DEQ, ///< Data block dequeue AIO write complete (dequeue complete) + ABORT_CACHED, + ABORT_PART, + ABORT_SUBM, + ABORTED, + COMMIT_CACHED, + COMMIT_PART, + COMMIT_SUBM, + COMMITTED + }; + + enum read_state + { + UNREAD, ///< Data block not read + READ_PART, ///< Data block is part-read; waiting for page buffer to fill + SKIP_PART, ///< Prev. dequeued dblock is part-skipped; waiting for page buffer to fill + READ ///< Data block is fully read + }; + + protected: + static smutex _mutex; + static u_int64_t _cnt; + u_int64_t _icnt; + write_state _wstate; ///< Enqueued / dequeued state of data + read_state _rstate; ///< Read state of data + std::size_t _dsize; ///< Data size in bytes + u_int32_t _dblks_written; ///< Data blocks read/written + u_int32_t _dblks_read; ///< Data blocks read/written + u_int32_t _pg_cnt; ///< Page counter - incr for each page containing part of data + u_int16_t _fid; ///< FID containing header of enqueue record + u_int64_t _rid; ///< RID of data set by enqueue operation + std::string _xid; ///< XID set by enqueue operation + u_int64_t _dequeue_rid; ///< RID of data set by dequeue operation + bool _external_rid; ///< Flag to indicate external setting of rid + + public: + data_tok(); + virtual ~data_tok(); + + inline u_int64_t id() const { return _icnt; } + inline write_state wstate() const { return _wstate; } + const char* wstate_str() const; + static const char* wstate_str(write_state wstate); + inline read_state rstate() const { return _rstate; } + const char* rstate_str() const; + static const char* rstate_str(read_state rstate); + inline bool is_writable() const { return _wstate == NONE || _wstate == ENQ_PART; } + inline bool is_enqueued() const { return _wstate == ENQ; } + inline bool is_readable() const { return _wstate == ENQ; } + inline bool is_read() const { return _rstate == READ; } + inline bool is_dequeueable() const { return _wstate == ENQ || _wstate == DEQ_PART; } + inline void set_wstate(const write_state wstate) { _wstate = wstate; } + void set_rstate(const read_state rstate); + inline std::size_t dsize() const { return _dsize; } + inline void set_dsize(std::size_t dsize) { _dsize = dsize; } + + inline u_int32_t dblocks_written() const { return _dblks_written; } + inline void incr_dblocks_written(u_int32_t dblks_written) + { _dblks_written += dblks_written; } + inline void set_dblocks_written(u_int32_t dblks_written) { _dblks_written = dblks_written; } + + inline u_int32_t dblocks_read() const { return _dblks_read; } + inline void incr_dblocks_read(u_int32_t dblks_read) { _dblks_read += dblks_read; } + inline void set_dblocks_read(u_int32_t dblks_read) { _dblks_read = dblks_read; } + + inline u_int32_t pg_cnt() const { return _pg_cnt; } + inline u_int32_t incr_pg_cnt() { return ++_pg_cnt; } + inline u_int32_t decr_pg_cnt() { assert(_pg_cnt != 0); return --_pg_cnt; } + + inline u_int16_t fid() const { return _fid; } + inline void set_fid(const u_int16_t fid) { _fid = fid; } + inline u_int64_t rid() const { return _rid; } + inline void set_rid(const u_int64_t rid) { _rid = rid; } + inline u_int64_t dequeue_rid() const {return _dequeue_rid; } + inline void set_dequeue_rid(const u_int64_t rid) { _dequeue_rid = rid; } + inline bool external_rid() const { return _external_rid; } + inline void set_external_rid(const bool external_rid) { _external_rid = external_rid; } + + inline bool has_xid() const { return !_xid.empty(); } + inline const std::string& xid() const { return _xid; } + inline void clear_xid() { _xid.clear(); } + inline void set_xid(const std::string& xid) { _xid.assign(xid); } + inline void set_xid(const void* xidp, const std::size_t xid_len) + { _xid.assign((const char*)xidp, xid_len); } + + void reset(); + + // debug aid + std::string status_str() const; + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_DATA_TOK_H diff --git a/cpp/src/qpid/legacystore/jrnl/deq_hdr.h b/cpp/src/qpid/legacystore/jrnl/deq_hdr.h new file mode 100644 index 0000000000..ae7081eac1 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/deq_hdr.h @@ -0,0 +1,141 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file deq_hdr.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::deq_hdr (dequeue record), + * used to dequeue a previously enqueued record. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_DEQ_HDR_H +#define QPID_LEGACYSTORE_JRNL_DEQ_HDR_H + +#include <cstddef> +#include "qpid/legacystore/jrnl/rec_hdr.h" + +namespace mrg +{ +namespace journal +{ + +#pragma pack(1) + + /** + * \brief Struct for dequeue record. + * + * Struct for dequeue record. If this record has a non-zero xidsize field (i.e., there is a + * valid XID), then this header is followed by the XID of xidsize bytes and a rec_tail. If, + * on the other hand, this record has a zero xidsize (i.e., there is no XID), then the rec_tail + * is absent. + * + * Note that this record had its own rid distinct from the rid of the record it is dequeueing. + * The rid field below is the rid of the dequeue record itself; the deq-rid field is the rid of a + * previous enqueue record being dequeued by this record. + * + * Record header info in binary format (32 bytes): + * <pre> + * 0 7 + * +---+---+---+---+---+---+---+---+ -+ + * | magic | v | e | flags | | + * +---+---+---+---+---+---+---+---+ | struct hdr + * | rid | | + * +---+---+---+---+---+---+---+---+ -+ + * | deq-rid | + * +---+---+---+---+---+---+---+---+ + * | xidsize | + * +---+---+---+---+---+---+---+---+ + * v = file version (If the format or encoding of this file changes, then this + * number should be incremented) + * e = endian flag, false (0x00) for little endian, true (0x01) for big endian + * </pre> + * + * Note that journal files should be transferable between 32- and 64-bit + * hardware of the same endianness, but not between hardware of opposite + * entianness without some sort of binary conversion utility. Thus buffering + * will be needed for types that change size between 32- and 64-bit compiles. + */ + struct deq_hdr : rec_hdr + { + u_int64_t _deq_rid; ///< Record ID of dequeued record +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler0; ///< Big-endian filler for 32-bit size_t +#endif + std::size_t _xidsize; ///< XID size +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler0; ///< Little-endian filler for 32-bit size_t +#endif + static const u_int16_t DEQ_HDR_TXNCMPLCOMMIT_MASK = 0x10; + + /** + * \brief Default constructor, which sets all values to 0. + */ + inline deq_hdr(): rec_hdr(), _deq_rid(0), +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif + _xidsize(0) +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + , _filler0(0) +#endif + {} + + /** + * \brief Convenience constructor which initializes values during construction. + */ + inline deq_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid, + const u_int64_t deq_rid, const std::size_t xidsize, const bool owi, + const bool txn_coml_commit = false): + rec_hdr(magic, version, rid, owi), _deq_rid(deq_rid), +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif + _xidsize(xidsize) +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + , _filler0(0) +#endif + { set_txn_coml_commit(txn_coml_commit); } + + + inline bool is_txn_coml_commit() const { return _uflag & DEQ_HDR_TXNCMPLCOMMIT_MASK; } + + inline void set_txn_coml_commit(const bool commit) + { + _uflag = commit ? _uflag | DEQ_HDR_TXNCMPLCOMMIT_MASK : + _uflag & (~DEQ_HDR_TXNCMPLCOMMIT_MASK); + } + + /** + * \brief Returns the size of the header in bytes. + */ + inline static std::size_t size() { return sizeof(deq_hdr); } + }; + +#pragma pack() + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_DEQ_HDR_H diff --git a/cpp/src/qpid/legacystore/jrnl/deq_rec.cpp b/cpp/src/qpid/legacystore/jrnl/deq_rec.cpp new file mode 100644 index 0000000000..4de412c201 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/deq_rec.cpp @@ -0,0 +1,459 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file deq_rec.cpp + * + * Qpid asynchronous store plugin library + * + * This file contains the code for the mrg::journal::deq_rec (journal dequeue + * record) class. See comments in file deq_rec.h for details. + * + * \author Kim van der Riet + */ + +#include "jrnl/deq_rec.h" + +#include <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <iomanip> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +deq_rec::deq_rec(): + _deq_hdr(RHM_JDAT_DEQ_MAGIC, RHM_JDAT_VERSION, 0, 0, 0, false), + _xidp(0), + _buff(0), + _deq_tail(_deq_hdr) +{} + +deq_rec::deq_rec(const u_int64_t rid, const u_int64_t drid, const void* const xidp, + const std::size_t xidlen, const bool owi, const bool txn_coml_commit): + _deq_hdr(RHM_JDAT_DEQ_MAGIC, RHM_JDAT_VERSION, rid, drid, xidlen, owi, txn_coml_commit), + _xidp(xidp), + _buff(0), + _deq_tail(_deq_hdr) +{} + +deq_rec::~deq_rec() +{ + clean(); +} + +void +deq_rec::reset() +{ + _deq_hdr._rid = 0; + _deq_hdr.set_owi(false); + _deq_hdr.set_txn_coml_commit(false); + _deq_hdr._deq_rid = 0; + _deq_hdr._xidsize = 0; + _deq_tail._rid = 0; + _xidp = 0; + _buff = 0; +} + +void +deq_rec::reset(const u_int64_t rid, const u_int64_t drid, const void* const xidp, + const std::size_t xidlen, const bool owi, const bool txn_coml_commit) +{ + _deq_hdr._rid = rid; + _deq_hdr.set_owi(owi); + _deq_hdr.set_txn_coml_commit(txn_coml_commit); + _deq_hdr._deq_rid = drid; + _deq_hdr._xidsize = xidlen; + _deq_tail._rid = rid; + _xidp = xidp; + _buff = 0; +} + +u_int32_t +deq_rec::encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks) +{ + assert(wptr != 0); + assert(max_size_dblks > 0); + if (_xidp == 0) + assert(_deq_hdr._xidsize == 0); + + std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE; + std::size_t rem = max_size_dblks * JRNL_DBLK_SIZE; + std::size_t wr_cnt = 0; + if (rec_offs_dblks) // Continuation of split dequeue record (over 2 or more pages) + { + if (size_dblks(rec_size()) - rec_offs_dblks > max_size_dblks) // Further split required + { + rec_offs -= sizeof(_deq_hdr); + std::size_t wsize = _deq_hdr._xidsize > rec_offs ? _deq_hdr._xidsize - rec_offs : 0; + std::size_t wsize2 = wsize; + if (wsize) + { + if (wsize > rem) + wsize = rem; + std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize); + wr_cnt += wsize; + rem -= wsize; + } + rec_offs -= _deq_hdr._xidsize - wsize2; + if (rem) + { + wsize = sizeof(_deq_tail) > rec_offs ? sizeof(_deq_tail) - rec_offs : 0; + wsize2 = wsize; + if (wsize) + { + if (wsize > rem) + wsize = rem; + std::memcpy((char*)wptr + wr_cnt, (char*)&_deq_tail + rec_offs, wsize); + wr_cnt += wsize; + rem -= wsize; + } + rec_offs -= sizeof(_deq_tail) - wsize2; + } + assert(rem == 0); + assert(rec_offs == 0); + } + else // No further split required + { + rec_offs -= sizeof(_deq_hdr); + std::size_t wsize = _deq_hdr._xidsize > rec_offs ? _deq_hdr._xidsize - rec_offs : 0; + if (wsize) + { + std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize); + wr_cnt += wsize; + } + rec_offs -= _deq_hdr._xidsize - wsize; + wsize = sizeof(_deq_tail) > rec_offs ? sizeof(_deq_tail) - rec_offs : 0; + if (wsize) + { + std::memcpy((char*)wptr + wr_cnt, (char*)&_deq_tail + rec_offs, wsize); + wr_cnt += wsize; +#ifdef RHM_CLEAN + std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE; + std::size_t dblk_rec_size = size_dblks(rec_size() - rec_offs) * JRNL_DBLK_SIZE; + std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt); +#endif + } + rec_offs -= sizeof(_deq_tail) - wsize; + assert(rec_offs == 0); + } + } + else // Start at beginning of data record + { + // Assumption: the header will always fit into the first dblk + std::memcpy(wptr, (void*)&_deq_hdr, sizeof(_deq_hdr)); + wr_cnt = sizeof(_deq_hdr); + if (size_dblks(rec_size()) > max_size_dblks) // Split required - can only occur with xid + { + std::size_t wsize; + rem -= sizeof(_deq_hdr); + if (rem) + { + wsize = rem >= _deq_hdr._xidsize ? _deq_hdr._xidsize : rem; + std::memcpy((char*)wptr + wr_cnt, _xidp, wsize); + wr_cnt += wsize; + rem -= wsize; + } + if (rem) + { + wsize = rem >= sizeof(_deq_tail) ? sizeof(_deq_tail) : rem; + std::memcpy((char*)wptr + wr_cnt, (void*)&_deq_tail, wsize); + wr_cnt += wsize; + rem -= wsize; + } + assert(rem == 0); + } + else // No split required + { + if (_deq_hdr._xidsize) + { + std::memcpy((char*)wptr + wr_cnt, _xidp, _deq_hdr._xidsize); + wr_cnt += _deq_hdr._xidsize; + std::memcpy((char*)wptr + wr_cnt, (void*)&_deq_tail, sizeof(_deq_tail)); + wr_cnt += sizeof(_deq_tail); + } +#ifdef RHM_CLEAN + std::size_t dblk_rec_size = size_dblks(rec_size()) * JRNL_DBLK_SIZE; + std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt); +#endif + } + } + return size_dblks(wr_cnt); +} + +u_int32_t +deq_rec::decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks) +{ + assert(rptr != 0); + assert(max_size_dblks > 0); + + std::size_t rd_cnt = 0; + if (rec_offs_dblks) // Continuation of record on new page + { + const u_int32_t hdr_xid_dblks = size_dblks(deq_hdr::size() + _deq_hdr._xidsize); + const u_int32_t hdr_xid_tail_dblks = size_dblks(deq_hdr::size() + _deq_hdr._xidsize + + rec_tail::size()); + const std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE; + + if (hdr_xid_tail_dblks - rec_offs_dblks <= max_size_dblks) + { + // Remainder of xid fits within this page + if (rec_offs - deq_hdr::size() < _deq_hdr._xidsize) + { + // Part of xid still outstanding, copy remainder of xid and tail + const std::size_t xid_offs = rec_offs - deq_hdr::size(); + const std::size_t xid_rem = _deq_hdr._xidsize - xid_offs; + std::memcpy((char*)_buff + xid_offs, rptr, xid_rem); + rd_cnt = xid_rem; + std::memcpy((void*)&_deq_tail, ((char*)rptr + rd_cnt), sizeof(_deq_tail)); + chk_tail(); + rd_cnt += sizeof(_deq_tail); + } + else + { + // Tail or part of tail only outstanding, complete tail + const std::size_t tail_offs = rec_offs - deq_hdr::size() - _deq_hdr._xidsize; + const std::size_t tail_rem = rec_tail::size() - tail_offs; + std::memcpy((char*)&_deq_tail + tail_offs, rptr, tail_rem); + chk_tail(); + rd_cnt = tail_rem; + } + } + else if (hdr_xid_dblks - rec_offs_dblks <= max_size_dblks) + { + // Remainder of xid fits within this page, tail split + const std::size_t xid_offs = rec_offs - deq_hdr::size(); + const std::size_t xid_rem = _deq_hdr._xidsize - xid_offs; + std::memcpy((char*)_buff + xid_offs, rptr, xid_rem); + rd_cnt += xid_rem; + const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + if (tail_rem) + { + std::memcpy((void*)&_deq_tail, ((char*)rptr + xid_rem), tail_rem); + rd_cnt += tail_rem; + } + } + else + { + // Remainder of xid split + const std::size_t xid_cp_size = (max_size_dblks * JRNL_DBLK_SIZE); + std::memcpy((char*)_buff + rec_offs - deq_hdr::size(), rptr, xid_cp_size); + rd_cnt += xid_cp_size; + } + } + else // Start of record + { + // Get and check header + _deq_hdr.hdr_copy(h); + rd_cnt = sizeof(rec_hdr); + _deq_hdr._deq_rid = *(u_int64_t*)((char*)rptr + rd_cnt); + rd_cnt += sizeof(u_int64_t); +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + rd_cnt += sizeof(u_int32_t); // Filler 0 +#endif + _deq_hdr._xidsize = *(std::size_t*)((char*)rptr + rd_cnt); + rd_cnt = _deq_hdr.size(); + chk_hdr(); + if (_deq_hdr._xidsize) + { + _buff = std::malloc(_deq_hdr._xidsize); + MALLOC_CHK(_buff, "_buff", "deq_rec", "decode"); + const u_int32_t hdr_xid_dblks = size_dblks(deq_hdr::size() + _deq_hdr._xidsize); + const u_int32_t hdr_xid_tail_dblks = size_dblks(deq_hdr::size() + _deq_hdr._xidsize + + rec_tail::size()); + + // Check if record (header + xid + tail) fits within this page, we can check the + // tail before the expense of copying data to memory + if (hdr_xid_tail_dblks <= max_size_dblks) + { + // Entire header, xid and tail fits within this page + std::memcpy(_buff, (char*)rptr + rd_cnt, _deq_hdr._xidsize); + rd_cnt += _deq_hdr._xidsize; + std::memcpy((void*)&_deq_tail, (char*)rptr + rd_cnt, sizeof(_deq_tail)); + rd_cnt += sizeof(_deq_tail); + chk_tail(); + } + else if (hdr_xid_dblks <= max_size_dblks) + { + // Entire header and xid fit within this page, tail split + std::memcpy(_buff, (char*)rptr + rd_cnt, _deq_hdr._xidsize); + rd_cnt += _deq_hdr._xidsize; + const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + if (tail_rem) + { + std::memcpy((void*)&_deq_tail, (char*)rptr + rd_cnt, tail_rem); + rd_cnt += tail_rem; + } + } + else + { + // Header fits within this page, xid split + const std::size_t xid_cp_size = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + std::memcpy(_buff, (char*)rptr + rd_cnt, xid_cp_size); + rd_cnt += xid_cp_size; + } + } + } + return size_dblks(rd_cnt); +} + +bool +deq_rec::rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs) +{ + if (rec_offs == 0) + { + _deq_hdr.hdr_copy(h); + ifsp->read((char*)&_deq_hdr._deq_rid, sizeof(u_int64_t)); +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + ifsp->ignore(sizeof(u_int32_t)); // _filler0 +#endif + ifsp->read((char*)&_deq_hdr._xidsize, sizeof(std::size_t)); +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + ifsp->ignore(sizeof(u_int32_t)); // _filler0 +#endif + rec_offs = sizeof(_deq_hdr); + // Read header, allocate (if req'd) for xid + if (_deq_hdr._xidsize) + { + _buff = std::malloc(_deq_hdr._xidsize); + MALLOC_CHK(_buff, "_buff", "enq_rec", "rcv_decode"); + } + } + if (rec_offs < sizeof(_deq_hdr) + _deq_hdr._xidsize) + { + // Read xid (or continue reading xid) + std::size_t offs = rec_offs - sizeof(_deq_hdr); + ifsp->read((char*)_buff + offs, _deq_hdr._xidsize - offs); + std::size_t size_read = ifsp->gcount(); + rec_offs += size_read; + if (size_read < _deq_hdr._xidsize - offs) + { + assert(ifsp->eof()); + // As we may have read past eof, turn off fail bit + ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit)); + assert(!ifsp->fail() && !ifsp->bad()); + return false; + } + } + if (rec_offs < sizeof(_deq_hdr) + + (_deq_hdr._xidsize ? _deq_hdr._xidsize + sizeof(rec_tail) : 0)) + { + // Read tail (or continue reading tail) + std::size_t offs = rec_offs - sizeof(_deq_hdr) - _deq_hdr._xidsize; + ifsp->read((char*)&_deq_tail + offs, sizeof(rec_tail) - offs); + std::size_t size_read = ifsp->gcount(); + rec_offs += size_read; + if (size_read < sizeof(rec_tail) - offs) + { + assert(ifsp->eof()); + // As we may have read past eof, turn off fail bit + ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit)); + assert(!ifsp->fail() && !ifsp->bad()); + return false; + } + } + ifsp->ignore(rec_size_dblks() * JRNL_DBLK_SIZE - rec_size()); + if (_deq_hdr._xidsize) + chk_tail(); // Throws if tail invalid or record incomplete + assert(!ifsp->fail() && !ifsp->bad()); + return true; +} + +std::size_t +deq_rec::get_xid(void** const xidpp) +{ + if (!_buff) + { + *xidpp = 0; + return 0; + } + *xidpp = _buff; + return _deq_hdr._xidsize; +} + +std::string& +deq_rec::str(std::string& str) const +{ + std::ostringstream oss; + oss << "deq_rec: m=" << _deq_hdr._magic; + oss << " v=" << (int)_deq_hdr._version; + oss << " rid=" << _deq_hdr._rid; + oss << " drid=" << _deq_hdr._deq_rid; + if (_xidp) + oss << " xid=\"" << _xidp << "\""; + str.append(oss.str()); + return str; +} + +std::size_t +deq_rec::xid_size() const +{ + return _deq_hdr._xidsize; +} + +std::size_t +deq_rec::rec_size() const +{ + return deq_hdr::size() + (_deq_hdr._xidsize ? _deq_hdr._xidsize + rec_tail::size() : 0); +} + +void +deq_rec::chk_hdr() const +{ + jrec::chk_hdr(_deq_hdr); + if (_deq_hdr._magic != RHM_JDAT_DEQ_MAGIC) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "deq magic: rid=0x" << std::setw(16) << _deq_hdr._rid; + oss << ": expected=0x" << std::setw(8) << RHM_JDAT_DEQ_MAGIC; + oss << " read=0x" << std::setw(2) << (int)_deq_hdr._magic; + throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "deq_rec", "chk_hdr"); + } +} + +void +deq_rec::chk_hdr(u_int64_t rid) const +{ + chk_hdr(); + jrec::chk_rid(_deq_hdr, rid); +} + +void +deq_rec::chk_tail() const +{ + jrec::chk_tail(_deq_tail, _deq_hdr); +} + +void +deq_rec::clean() +{ + // clean up allocated memory here +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/deq_rec.h b/cpp/src/qpid/legacystore/jrnl/deq_rec.h new file mode 100644 index 0000000000..d870b658da --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/deq_rec.h @@ -0,0 +1,103 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file deq_rec.h + * + * Qpid asynchronous store plugin library + * + * This file contains the code for the mrg::journal::deq_rec (journal dequeue + * record) class. See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_DEQ_REQ_H +#define QPID_LEGACYSTORE_JRNL_DEQ_REQ_H + +namespace mrg +{ +namespace journal +{ +class deq_rec; +} +} + +#include <cstddef> +#include "qpid/legacystore/jrnl/deq_hdr.h" +#include "qpid/legacystore/jrnl/jrec.h" + +namespace mrg +{ +namespace journal +{ + + /** + * \class deq_rec + * \brief Class to handle a single journal dequeue record. + */ + class deq_rec : public jrec + { + private: + deq_hdr _deq_hdr; ///< Dequeue header + const void* _xidp; ///< xid pointer for encoding (writing to disk) + void* _buff; ///< Pointer to buffer to receive data read from disk + rec_tail _deq_tail; ///< Record tail, only encoded if XID is present + + public: + // constructor used for read operations and xid will have memory allocated + deq_rec(); + // constructor used for write operations, where xid already exists + deq_rec(const u_int64_t rid, const u_int64_t drid, const void* const xidp, + const std::size_t xidlen, const bool owi, const bool txn_coml_commit); + virtual ~deq_rec(); + + // Prepare instance for use in reading data from journal + void reset(); + // Prepare instance for use in writing data to journal + void reset(const u_int64_t rid, const u_int64_t drid, const void* const xidp, + const std::size_t xidlen, const bool owi, const bool txn_coml_commit); + u_int32_t encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks); + u_int32_t decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, + u_int32_t max_size_dblks); + // Decode used for recover + bool rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs); + + inline bool is_txn_coml_commit() const { return _deq_hdr.is_txn_coml_commit(); } + inline u_int64_t rid() const { return _deq_hdr._rid; } + inline u_int64_t deq_rid() const { return _deq_hdr._deq_rid; } + std::size_t get_xid(void** const xidpp); + std::string& str(std::string& str) const; + inline std::size_t data_size() const { return 0; } // This record never carries data + std::size_t xid_size() const; + std::size_t rec_size() const; + + private: + virtual void chk_hdr() const; + virtual void chk_hdr(u_int64_t rid) const; + virtual void chk_tail() const; + virtual void clean(); + }; // class deq_rec + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_DEQ_REQ_H diff --git a/cpp/src/qpid/legacystore/jrnl/enq_hdr.h b/cpp/src/qpid/legacystore/jrnl/enq_hdr.h new file mode 100644 index 0000000000..0d1e6116be --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/enq_hdr.h @@ -0,0 +1,165 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file enq_hdr.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::enq_hdr (enueue header), + * used to start an enqueue record in the journal. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_ENQ_HDR_H +#define QPID_LEGACYSTORE_JRNL_ENQ_HDR_H + +#include <cstddef> +#include "qpid/legacystore/jrnl/rec_hdr.h" + +namespace mrg +{ +namespace journal +{ + +#pragma pack(1) + + /** + * \brief Struct for enqueue record. + * + * Struct for enqueue record. In addition to the common data, this header includes both the + * xid and data blob sizes. + * + * This header precedes all enqueue data in journal files. + * + * Record header info in binary format (32 bytes): + * <pre> + * 0 7 + * +---+---+---+---+---+---+---+---+ -+ + * | magic | v | e | flags | | + * +---+---+---+---+---+---+---+---+ | struct hdr + * | rid | | + * +---+---+---+---+---+---+---+---+ -+ + * | xidsize | + * +---+---+---+---+---+---+---+---+ + * | dsize | + * +---+---+---+---+---+---+---+---+ + * v = file version (If the format or encoding of this file changes, then this + * number should be incremented) + * e = endian flag, false (0x00) for little endian, true (0x01) for big endian + * </pre> + * + * Note that journal files should be transferable between 32- and 64-bit + * hardware of the same endianness, but not between hardware of opposite + * entianness without some sort of binary conversion utility. Thus buffering + * will be needed for types that change size between 32- and 64-bit compiles. + */ + struct enq_hdr : rec_hdr + { +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler0; ///< Big-endian filler for 32-bit size_t +#endif + std::size_t _xidsize; ///< XID size +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler0; ///< Little-endian filler for 32-bit size_t +#endif +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler1; ///< Big-endian filler for 32-bit size_t +#endif + std::size_t _dsize; ///< Record data size +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler1; ///< Little-endian filler for 32-bit size_t +#endif + static const u_int16_t ENQ_HDR_TRANSIENT_MASK = 0x10; + static const u_int16_t ENQ_HDR_EXTERNAL_MASK = 0x20; + + /** + * \brief Default constructor, which sets all values to 0. + */ + inline enq_hdr(): rec_hdr(), +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif + _xidsize(0), +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler1(0), +#endif + _dsize(0) +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + , _filler1(0) +#endif + {} + + /** + * \brief Convenience constructor which initializes values during construction. + */ + inline enq_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid, + const std::size_t xidsize, const std::size_t dsize, const bool owi, + const bool transient = false): rec_hdr(magic, version, rid, owi), +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif + _xidsize(xidsize), +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler1(0), +#endif + _dsize(dsize) +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + , _filler1(0) +#endif + { set_transient(transient); } + + + inline bool is_transient() const { return _uflag & ENQ_HDR_TRANSIENT_MASK; } + + inline void set_transient(const bool transient) + { + _uflag = transient ? _uflag | ENQ_HDR_TRANSIENT_MASK : + _uflag & (~ENQ_HDR_TRANSIENT_MASK); + } + + inline bool is_external() const { return _uflag & ENQ_HDR_EXTERNAL_MASK; } + + inline void set_external(const bool external) + { + _uflag = external ? _uflag | ENQ_HDR_EXTERNAL_MASK : + _uflag & (~ENQ_HDR_EXTERNAL_MASK); + } + + /** + * \brief Returns the size of the header in bytes. + */ + inline static std::size_t size() { return sizeof(enq_hdr); } + }; + +#pragma pack() + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_ENQ_HDR_H diff --git a/cpp/src/qpid/legacystore/jrnl/enq_map.cpp b/cpp/src/qpid/legacystore/jrnl/enq_map.cpp new file mode 100644 index 0000000000..d024b704a7 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/enq_map.cpp @@ -0,0 +1,183 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file enq_map.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::enq_map (enqueue map). See + * comments in file enq_map.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/enq_map.h" + +#include <iomanip> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/slock.h" +#include <sstream> + + +namespace mrg +{ +namespace journal +{ + +// static return/error codes +int16_t enq_map::EMAP_DUP_RID = -3; +int16_t enq_map::EMAP_LOCKED = -2; +int16_t enq_map::EMAP_RID_NOT_FOUND = -1; +int16_t enq_map::EMAP_OK = 0; +int16_t enq_map::EMAP_FALSE = 0; +int16_t enq_map::EMAP_TRUE = 1; + +enq_map::enq_map(): + _map(), + _pfid_enq_cnt() +{} + +enq_map::~enq_map() {} + +void +enq_map::set_num_jfiles(const u_int16_t num_jfiles) +{ + _pfid_enq_cnt.resize(num_jfiles, 0); +} + + +int16_t +enq_map::insert_pfid(const u_int64_t rid, const u_int16_t pfid) +{ + return insert_pfid(rid, pfid, false); +} + +int16_t +enq_map::insert_pfid(const u_int64_t rid, const u_int16_t pfid, const bool locked) +{ + std::pair<emap_itr, bool> ret; + emap_data_struct rec(pfid, locked); + { + slock s(_mutex); + ret = _map.insert(emap_param(rid, rec)); + } + if (ret.second == false) + return EMAP_DUP_RID; + _pfid_enq_cnt.at(pfid)++; + return EMAP_OK; +} + +int16_t +enq_map::get_pfid(const u_int64_t rid) +{ + slock s(_mutex); + emap_itr itr = _map.find(rid); + if (itr == _map.end()) // not found in map + return EMAP_RID_NOT_FOUND; + if (itr->second._lock) + return EMAP_LOCKED; + return itr->second._pfid; +} + +int16_t +enq_map::get_remove_pfid(const u_int64_t rid, const bool txn_flag) +{ + slock s(_mutex); + emap_itr itr = _map.find(rid); + if (itr == _map.end()) // not found in map + return EMAP_RID_NOT_FOUND; + if (itr->second._lock && !txn_flag) // locked, but not a commit/abort + return EMAP_LOCKED; + u_int16_t pfid = itr->second._pfid; + _map.erase(itr); + _pfid_enq_cnt.at(pfid)--; + return pfid; +} + +bool +enq_map::is_enqueued(const u_int64_t rid, bool ignore_lock) +{ + slock s(_mutex); + emap_itr itr = _map.find(rid); + if (itr == _map.end()) // not found in map + return false; + if (!ignore_lock && itr->second._lock) // locked + return false; + return true; +} + +int16_t +enq_map::lock(const u_int64_t rid) +{ + slock s(_mutex); + emap_itr itr = _map.find(rid); + if (itr == _map.end()) // not found in map + return EMAP_RID_NOT_FOUND; + itr->second._lock = true; + return EMAP_OK; +} + +int16_t +enq_map::unlock(const u_int64_t rid) +{ + slock s(_mutex); + emap_itr itr = _map.find(rid); + if (itr == _map.end()) // not found in map + return EMAP_RID_NOT_FOUND; + itr->second._lock = false; + return EMAP_OK; +} + +int16_t +enq_map::is_locked(const u_int64_t rid) +{ + slock s(_mutex); + emap_itr itr = _map.find(rid); + if (itr == _map.end()) // not found in map + return EMAP_RID_NOT_FOUND; + return itr->second._lock ? EMAP_TRUE : EMAP_FALSE; +} + +void +enq_map::rid_list(std::vector<u_int64_t>& rv) +{ + rv.clear(); + { + slock s(_mutex); + for (emap_itr itr = _map.begin(); itr != _map.end(); itr++) + rv.push_back(itr->first); + } +} + +void +enq_map::pfid_list(std::vector<u_int16_t>& fv) +{ + fv.clear(); + { + slock s(_mutex); + for (emap_itr itr = _map.begin(); itr != _map.end(); itr++) + fv.push_back(itr->second._pfid); + } +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/enq_map.h b/cpp/src/qpid/legacystore/jrnl/enq_map.h new file mode 100644 index 0000000000..75404afebe --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/enq_map.h @@ -0,0 +1,127 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file enq_map.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::enq_map (enqueue map). + * See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_ENQ_MAP_H +#define QPID_LEGACYSTORE_JRNL_ENQ_MAP_H + +namespace mrg +{ +namespace journal +{ +class enq_map; +} +} + +#include "qpid/legacystore/jrnl/jexception.h" +#include "qpid/legacystore/jrnl/smutex.h" +#include <map> +#include <pthread.h> +#include <vector> + +namespace mrg +{ +namespace journal +{ + + /** + * \class enq_map + * \brief Class for storing the physical file id (pfid) and a transaction locked flag for each enqueued + * data block using the record id (rid) as a key. This is the primary mechanism for + * deterimining the enqueue low water mark: if a pfid exists in this map, then there is + * at least one still-enqueued record in that file. (The transaction map must also be + * clear, however.) + * + * Map rids against pfid and lock status. As records are enqueued, they are added to this + * map, and as they are dequeued, they are removed. An enqueue is locked when a transactional + * dequeue is pending that has been neither committed nor aborted. + * <pre> + * key data + * + * rid1 --- [ pfid, txn_lock ] + * rid2 --- [ pfid, txn_lock ] + * rid3 --- [ pfid, txn_lock ] + * ... + * </pre> + */ + class enq_map + { + public: + // return/error codes + static int16_t EMAP_DUP_RID; + static int16_t EMAP_LOCKED; + static int16_t EMAP_RID_NOT_FOUND; + static int16_t EMAP_OK; + static int16_t EMAP_FALSE; + static int16_t EMAP_TRUE; + + private: + + struct emap_data_struct + { + u_int16_t _pfid; + bool _lock; + emap_data_struct(const u_int16_t pfid, const bool lock) : _pfid(pfid), _lock(lock) {} + }; + typedef std::pair<u_int64_t, emap_data_struct> emap_param; + typedef std::map<u_int64_t, emap_data_struct> emap; + typedef emap::iterator emap_itr; + + emap _map; + smutex _mutex; + std::vector<u_int32_t> _pfid_enq_cnt; + + public: + enq_map(); + virtual ~enq_map(); + + void set_num_jfiles(const u_int16_t num_jfiles); + inline u_int32_t get_enq_cnt(const u_int16_t pfid) const { return _pfid_enq_cnt.at(pfid); }; + + int16_t insert_pfid(const u_int64_t rid, const u_int16_t pfid); // 0=ok; -3=duplicate rid; + int16_t insert_pfid(const u_int64_t rid, const u_int16_t pfid, const bool locked); // 0=ok; -3=duplicate rid; + int16_t get_pfid(const u_int64_t rid); // >=0=pfid; -1=rid not found; -2=locked + int16_t get_remove_pfid(const u_int64_t rid, const bool txn_flag = false); // >=0=pfid; -1=rid not found; -2=locked + bool is_enqueued(const u_int64_t rid, bool ignore_lock = false); + int16_t lock(const u_int64_t rid); // 0=ok; -1=rid not found + int16_t unlock(const u_int64_t rid); // 0=ok; -1=rid not found + int16_t is_locked(const u_int64_t rid); // 1=true; 0=false; -1=rid not found + inline void clear() { _map.clear(); } + inline bool empty() const { return _map.empty(); } + inline u_int32_t size() const { return u_int32_t(_map.size()); } + void rid_list(std::vector<u_int64_t>& rv); + void pfid_list(std::vector<u_int16_t>& fv); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_ENQ_MAP_H diff --git a/cpp/src/qpid/legacystore/jrnl/enq_rec.cpp b/cpp/src/qpid/legacystore/jrnl/enq_rec.cpp new file mode 100644 index 0000000000..468599836b --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/enq_rec.cpp @@ -0,0 +1,638 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file enq_rec.cpp + * + * Qpid asynchronous store plugin library + * + * This file contains the code for the mrg::journal::enq_rec (journal enqueue + * record) class. See comments in file enq_rec.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/enq_rec.h" + +#include <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <iomanip> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +// Constructor used for read operations, where buf contains preallocated space to receive data. +enq_rec::enq_rec(): + jrec(), // superclass + _enq_hdr(RHM_JDAT_ENQ_MAGIC, RHM_JDAT_VERSION, 0, 0, 0, false, false), + _xidp(0), + _data(0), + _buff(0), + _enq_tail(_enq_hdr) +{} + +// Constructor used for transactional write operations, where dbuf contains data to be written. +enq_rec::enq_rec(const u_int64_t rid, const void* const dbuf, const std::size_t dlen, + const void* const xidp, const std::size_t xidlen, const bool owi, const bool transient): + jrec(), // superclass + _enq_hdr(RHM_JDAT_ENQ_MAGIC, RHM_JDAT_VERSION, rid, xidlen, dlen, owi, transient), + _xidp(xidp), + _data(dbuf), + _buff(0), + _enq_tail(_enq_hdr) +{} + +enq_rec::~enq_rec() +{ + clean(); +} + +// Prepare instance for use in reading data from journal, where buf contains preallocated space +// to receive data. +void +enq_rec::reset() +{ + _enq_hdr._rid = 0; + _enq_hdr.set_owi(false); + _enq_hdr.set_transient(false); + _enq_hdr._xidsize = 0; + _enq_hdr._dsize = 0; + _xidp = 0; + _data = 0; + _buff = 0; + _enq_tail._rid = 0; +} + +// Prepare instance for use in writing transactional data to journal, where dbuf contains data to +// be written. +void +enq_rec::reset(const u_int64_t rid, const void* const dbuf, const std::size_t dlen, + const void* const xidp, const std::size_t xidlen, const bool owi, const bool transient, + const bool external) +{ + _enq_hdr._rid = rid; + _enq_hdr.set_owi(owi); + _enq_hdr.set_transient(transient); + _enq_hdr.set_external(external); + _enq_hdr._xidsize = xidlen; + _enq_hdr._dsize = dlen; + _xidp = xidp; + _data = dbuf; + _buff = 0; + _enq_tail._rid = rid; +} + +u_int32_t +enq_rec::encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks) +{ + assert(wptr != 0); + assert(max_size_dblks > 0); + if (_xidp == 0) + assert(_enq_hdr._xidsize == 0); + + std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE; + std::size_t rem = max_size_dblks * JRNL_DBLK_SIZE; + std::size_t wr_cnt = 0; + if (rec_offs_dblks) // Continuation of split data record (over 2 or more pages) + { + if (size_dblks(rec_size()) - rec_offs_dblks > max_size_dblks) // Further split required + { + rec_offs -= sizeof(_enq_hdr); + std::size_t wsize = _enq_hdr._xidsize > rec_offs ? _enq_hdr._xidsize - rec_offs : 0; + std::size_t wsize2 = wsize; + if (wsize) + { + if (wsize > rem) + wsize = rem; + std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize); + wr_cnt = wsize; + rem -= wsize; + } + rec_offs -= _enq_hdr._xidsize - wsize2; + if (rem && !_enq_hdr.is_external()) + { + wsize = _enq_hdr._dsize > rec_offs ? _enq_hdr._dsize - rec_offs : 0; + wsize2 = wsize; + if (wsize) + { + if (wsize > rem) + wsize = rem; + std::memcpy((char*)wptr + wr_cnt, (const char*)_data + rec_offs, wsize); + wr_cnt += wsize; + rem -= wsize; + } + rec_offs -= _enq_hdr._dsize - wsize2; + } + if (rem) + { + wsize = sizeof(_enq_tail) > rec_offs ? sizeof(_enq_tail) - rec_offs : 0; + wsize2 = wsize; + if (wsize) + { + if (wsize > rem) + wsize = rem; + std::memcpy((char*)wptr + wr_cnt, (char*)&_enq_tail + rec_offs, wsize); + wr_cnt += wsize; + rem -= wsize; + } + rec_offs -= sizeof(_enq_tail) - wsize2; + } + assert(rem == 0); + assert(rec_offs == 0); + } + else // No further split required + { + rec_offs -= sizeof(_enq_hdr); + std::size_t wsize = _enq_hdr._xidsize > rec_offs ? _enq_hdr._xidsize - rec_offs : 0; + if (wsize) + { + std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize); + wr_cnt += wsize; + } + rec_offs -= _enq_hdr._xidsize - wsize; + wsize = _enq_hdr._dsize > rec_offs ? _enq_hdr._dsize - rec_offs : 0; + if (wsize && !_enq_hdr.is_external()) + { + std::memcpy((char*)wptr + wr_cnt, (const char*)_data + rec_offs, wsize); + wr_cnt += wsize; + } + rec_offs -= _enq_hdr._dsize - wsize; + wsize = sizeof(_enq_tail) > rec_offs ? sizeof(_enq_tail) - rec_offs : 0; + if (wsize) + { + std::memcpy((char*)wptr + wr_cnt, (char*)&_enq_tail + rec_offs, wsize); + wr_cnt += wsize; +#ifdef RHM_CLEAN + std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE; + std::size_t dblk_rec_size = size_dblks(rec_size() - rec_offs) * JRNL_DBLK_SIZE; + std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt); +#endif + } + rec_offs -= sizeof(_enq_tail) - wsize; + assert(rec_offs == 0); + } + } + else // Start at beginning of data record + { + // Assumption: the header will always fit into the first dblk + std::memcpy(wptr, (void*)&_enq_hdr, sizeof(_enq_hdr)); + wr_cnt = sizeof(_enq_hdr); + if (size_dblks(rec_size()) > max_size_dblks) // Split required + { + std::size_t wsize; + rem -= sizeof(_enq_hdr); + if (rem) + { + wsize = rem >= _enq_hdr._xidsize ? _enq_hdr._xidsize : rem; + std::memcpy((char*)wptr + wr_cnt, _xidp, wsize); + wr_cnt += wsize; + rem -= wsize; + } + if (rem && !_enq_hdr.is_external()) + { + wsize = rem >= _enq_hdr._dsize ? _enq_hdr._dsize : rem; + std::memcpy((char*)wptr + wr_cnt, _data, wsize); + wr_cnt += wsize; + rem -= wsize; + } + if (rem) + { + wsize = rem >= sizeof(_enq_tail) ? sizeof(_enq_tail) : rem; + std::memcpy((char*)wptr + wr_cnt, (void*)&_enq_tail, wsize); + wr_cnt += wsize; + rem -= wsize; + } + assert(rem == 0); + } + else // No split required + { + if (_enq_hdr._xidsize) + { + std::memcpy((char*)wptr + wr_cnt, _xidp, _enq_hdr._xidsize); + wr_cnt += _enq_hdr._xidsize; + } + if (!_enq_hdr.is_external()) + { + std::memcpy((char*)wptr + wr_cnt, _data, _enq_hdr._dsize); + wr_cnt += _enq_hdr._dsize; + } + std::memcpy((char*)wptr + wr_cnt, (void*)&_enq_tail, sizeof(_enq_tail)); + wr_cnt += sizeof(_enq_tail); +#ifdef RHM_CLEAN + std::size_t dblk_rec_size = size_dblks(rec_size()) * JRNL_DBLK_SIZE; + std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt); +#endif + } + } + return size_dblks(wr_cnt); +} + +u_int32_t +enq_rec::decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks) +{ + assert(rptr != 0); + assert(max_size_dblks > 0); + + std::size_t rd_cnt = 0; + if (rec_offs_dblks) // Continuation of record on new page + { + const u_int32_t hdr_xid_data_size = enq_hdr::size() + _enq_hdr._xidsize + + (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize); + const u_int32_t hdr_xid_data_tail_size = hdr_xid_data_size + rec_tail::size(); + const u_int32_t hdr_data_dblks = size_dblks(hdr_xid_data_size); + const u_int32_t hdr_tail_dblks = size_dblks(hdr_xid_data_tail_size); + const std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE; + const std::size_t offs = rec_offs - enq_hdr::size(); + + if (hdr_tail_dblks - rec_offs_dblks <= max_size_dblks) + { + // Remainder of record fits within this page + if (offs < _enq_hdr._xidsize) + { + // some XID still outstanding, copy remainder of XID, data and tail + const std::size_t rem = _enq_hdr._xidsize + _enq_hdr._dsize - offs; + std::memcpy((char*)_buff + offs, rptr, rem); + rd_cnt += rem; + std::memcpy((void*)&_enq_tail, ((char*)rptr + rd_cnt), sizeof(_enq_tail)); + chk_tail(); + rd_cnt += sizeof(_enq_tail); + } + else if (offs < _enq_hdr._xidsize + _enq_hdr._dsize && !_enq_hdr.is_external()) + { + // some data still outstanding, copy remainder of data and tail + const std::size_t data_offs = offs - _enq_hdr._xidsize; + const std::size_t data_rem = _enq_hdr._dsize - data_offs; + std::memcpy((char*)_buff + offs, rptr, data_rem); + rd_cnt += data_rem; + std::memcpy((void*)&_enq_tail, ((char*)rptr + rd_cnt), sizeof(_enq_tail)); + chk_tail(); + rd_cnt += sizeof(_enq_tail); + } + else + { + // Tail or part of tail only outstanding, complete tail + const std::size_t tail_offs = rec_offs - enq_hdr::size() - _enq_hdr._xidsize - + _enq_hdr._dsize; + const std::size_t tail_rem = rec_tail::size() - tail_offs; + std::memcpy((char*)&_enq_tail + tail_offs, rptr, tail_rem); + chk_tail(); + rd_cnt = tail_rem; + } + } + else if (hdr_data_dblks - rec_offs_dblks <= max_size_dblks) + { + // Remainder of xid & data fits within this page; tail split + + /* + * TODO: This section needs revision. Since it is known that the end of the page falls within the + * tail record, it is only necessary to write from the current offset to the end of the page under + * all circumstances. The multiple if/else combinations may be eliminated, as well as one memcpy() + * operation. + * + * Also note that Coverity has detected a possible memory overwrite in this block. It occurs if + * both the following two if() stmsts (numbered) are false. With rd_cnt = 0, this would result in + * the value of tail_rem > sizeof(tail_rec). Practically, this could only happen if the start and + * end of a page both fall within the same tail record, in which case the tail would have to be + * (much!) larger. However, the logic here does not account for this possibility. + * + * If the optimization above is undertaken, this code would probably be removed. + */ + if (offs < _enq_hdr._xidsize) // 1 + { + // some XID still outstanding, copy remainder of XID and data + const std::size_t rem = _enq_hdr._xidsize + _enq_hdr._dsize - offs; + std::memcpy((char*)_buff + offs, rptr, rem); + rd_cnt += rem; + } + else if (offs < _enq_hdr._xidsize + _enq_hdr._dsize && !_enq_hdr.is_external()) // 2 + { + // some data still outstanding, copy remainder of data + const std::size_t data_offs = offs - _enq_hdr._xidsize; + const std::size_t data_rem = _enq_hdr._dsize - data_offs; + std::memcpy((char*)_buff + offs, rptr, data_rem); + rd_cnt += data_rem; + } + const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + if (tail_rem) + { + std::memcpy((void*)&_enq_tail, ((char*)rptr + rd_cnt), tail_rem); + rd_cnt += tail_rem; + } + } + else + { + // Since xid and data are contiguous, both fit within current page - copy whole page + const std::size_t data_cp_size = (max_size_dblks * JRNL_DBLK_SIZE); + std::memcpy((char*)_buff + offs, rptr, data_cp_size); + rd_cnt += data_cp_size; + } + } + else // Start of record + { + // Get and check header + _enq_hdr.hdr_copy(h); + rd_cnt = sizeof(rec_hdr); +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + rd_cnt += sizeof(u_int32_t); // Filler 0 +#endif + _enq_hdr._xidsize = *(std::size_t*)((char*)rptr + rd_cnt); + rd_cnt += sizeof(std::size_t); +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + rd_cnt += sizeof(u_int32_t); // Filler 0 +#endif +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + rd_cnt += sizeof(u_int32_t); // Filler 1 +#endif + _enq_hdr._dsize = *(std::size_t*)((char*)rptr + rd_cnt); + rd_cnt = _enq_hdr.size(); + chk_hdr(); + if (_enq_hdr._xidsize + (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize)) + { + _buff = std::malloc(_enq_hdr._xidsize + (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize)); + MALLOC_CHK(_buff, "_buff", "enq_rec", "decode"); + + const u_int32_t hdr_xid_size = enq_hdr::size() + _enq_hdr._xidsize; + const u_int32_t hdr_xid_data_size = hdr_xid_size + (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize); + const u_int32_t hdr_xid_data_tail_size = hdr_xid_data_size + rec_tail::size(); + const u_int32_t hdr_xid_dblks = size_dblks(hdr_xid_size); + const u_int32_t hdr_data_dblks = size_dblks(hdr_xid_data_size); + const u_int32_t hdr_tail_dblks = size_dblks(hdr_xid_data_tail_size); + // Check if record (header + data + tail) fits within this page, we can check the + // tail before the expense of copying data to memory + if (hdr_tail_dblks <= max_size_dblks) + { + // Header, xid, data and tail fits within this page + if (_enq_hdr._xidsize) + { + std::memcpy(_buff, (char*)rptr + rd_cnt, _enq_hdr._xidsize); + rd_cnt += _enq_hdr._xidsize; + } + if (_enq_hdr._dsize && !_enq_hdr.is_external()) + { + std::memcpy((char*)_buff + _enq_hdr._xidsize, (char*)rptr + rd_cnt, + _enq_hdr._dsize); + rd_cnt += _enq_hdr._dsize; + } + std::memcpy((void*)&_enq_tail, (char*)rptr + rd_cnt, sizeof(_enq_tail)); + chk_tail(); + rd_cnt += sizeof(_enq_tail); + } + else if (hdr_data_dblks <= max_size_dblks) + { + // Header, xid and data fit within this page, tail split or separated + if (_enq_hdr._xidsize) + { + std::memcpy(_buff, (char*)rptr + rd_cnt, _enq_hdr._xidsize); + rd_cnt += _enq_hdr._xidsize; + } + if (_enq_hdr._dsize && !_enq_hdr.is_external()) + { + std::memcpy((char*)_buff + _enq_hdr._xidsize, (char*)rptr + rd_cnt, + _enq_hdr._dsize); + rd_cnt += _enq_hdr._dsize; + } + const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + if (tail_rem) + { + std::memcpy((void*)&_enq_tail, (char*)rptr + rd_cnt, tail_rem); + rd_cnt += tail_rem; + } + } + else if (hdr_xid_dblks <= max_size_dblks) + { + // Header and xid fits within this page, data split or separated + if (_enq_hdr._xidsize) + { + std::memcpy(_buff, (char*)rptr + rd_cnt, _enq_hdr._xidsize); + rd_cnt += _enq_hdr._xidsize; + } + if (_enq_hdr._dsize && !_enq_hdr.is_external()) + { + const std::size_t data_cp_size = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + std::memcpy((char*)_buff + _enq_hdr._xidsize, (char*)rptr + rd_cnt, data_cp_size); + rd_cnt += data_cp_size; + } + } + else + { + // Header fits within this page, xid split or separated + const std::size_t data_cp_size = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + std::memcpy(_buff, (char*)rptr + rd_cnt, data_cp_size); + rd_cnt += data_cp_size; + } + } + } + return size_dblks(rd_cnt); +} + +bool +enq_rec::rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs) +{ + if (rec_offs == 0) + { + // Read header, allocate (if req'd) for xid + _enq_hdr.hdr_copy(h); +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + ifsp->ignore(sizeof(u_int32_t)); // _filler0 +#endif + ifsp->read((char*)&_enq_hdr._xidsize, sizeof(std::size_t)); +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + ifsp->ignore(sizeof(u_int32_t)); // _filler0 +#endif +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + ifsp->ignore(sizeof(u_int32_t)); // _filler1 +#endif + ifsp->read((char*)&_enq_hdr._dsize, sizeof(std::size_t)); +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + ifsp->ignore(sizeof(u_int32_t)); // _filler1 +#endif + rec_offs = sizeof(_enq_hdr); + if (_enq_hdr._xidsize) + { + _buff = std::malloc(_enq_hdr._xidsize); + MALLOC_CHK(_buff, "_buff", "enq_rec", "rcv_decode"); + } + } + if (rec_offs < sizeof(_enq_hdr) + _enq_hdr._xidsize) + { + // Read xid (or continue reading xid) + std::size_t offs = rec_offs - sizeof(_enq_hdr); + ifsp->read((char*)_buff + offs, _enq_hdr._xidsize - offs); + std::size_t size_read = ifsp->gcount(); + rec_offs += size_read; + if (size_read < _enq_hdr._xidsize - offs) + { + assert(ifsp->eof()); + // As we may have read past eof, turn off fail bit + ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit)); + assert(!ifsp->fail() && !ifsp->bad()); + return false; + } + } + if (!_enq_hdr.is_external()) + { + if (rec_offs < sizeof(_enq_hdr) + _enq_hdr._xidsize + _enq_hdr._dsize) + { + // Ignore data (or continue ignoring data) + std::size_t offs = rec_offs - sizeof(_enq_hdr) - _enq_hdr._xidsize; + ifsp->ignore(_enq_hdr._dsize - offs); + std::size_t size_read = ifsp->gcount(); + rec_offs += size_read; + if (size_read < _enq_hdr._dsize - offs) + { + assert(ifsp->eof()); + // As we may have read past eof, turn off fail bit + ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit)); + assert(!ifsp->fail() && !ifsp->bad()); + return false; + } + } + } + if (rec_offs < sizeof(_enq_hdr) + _enq_hdr._xidsize + + (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize) + sizeof(rec_tail)) + { + // Read tail (or continue reading tail) + std::size_t offs = rec_offs - sizeof(_enq_hdr) - _enq_hdr._xidsize; + if (!_enq_hdr.is_external()) + offs -= _enq_hdr._dsize; + ifsp->read((char*)&_enq_tail + offs, sizeof(rec_tail) - offs); + std::size_t size_read = ifsp->gcount(); + rec_offs += size_read; + if (size_read < sizeof(rec_tail) - offs) + { + assert(ifsp->eof()); + // As we may have read past eof, turn off fail bit + ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit)); + assert(!ifsp->fail() && !ifsp->bad()); + return false; + } + } + ifsp->ignore(rec_size_dblks() * JRNL_DBLK_SIZE - rec_size()); + chk_tail(); // Throws if tail invalid or record incomplete + assert(!ifsp->fail() && !ifsp->bad()); + return true; +} + +std::size_t +enq_rec::get_xid(void** const xidpp) +{ + if (!_buff || !_enq_hdr._xidsize) + { + *xidpp = 0; + return 0; + } + *xidpp = _buff; + return _enq_hdr._xidsize; +} + +std::size_t +enq_rec::get_data(void** const datapp) +{ + if (!_buff) + { + *datapp = 0; + return 0; + } + if (_enq_hdr.is_external()) + *datapp = 0; + else + *datapp = (void*)((char*)_buff + _enq_hdr._xidsize); + return _enq_hdr._dsize; +} + +std::string& +enq_rec::str(std::string& str) const +{ + std::ostringstream oss; + oss << "enq_rec: m=" << _enq_hdr._magic; + oss << " v=" << (int)_enq_hdr._version; + oss << " rid=" << _enq_hdr._rid; + if (_xidp) + oss << " xid=\"" << _xidp << "\""; + oss << " len=" << _enq_hdr._dsize; + str.append(oss.str()); + return str; +} + +std::size_t +enq_rec::rec_size() const +{ + return rec_size(_enq_hdr._xidsize, _enq_hdr._dsize, _enq_hdr.is_external()); +} + +std::size_t +enq_rec::rec_size(const std::size_t xidsize, const std::size_t dsize, const bool external) +{ + if (external) + return enq_hdr::size() + xidsize + rec_tail::size(); + return enq_hdr::size() + xidsize + dsize + rec_tail::size(); +} + +void +enq_rec::set_rid(const u_int64_t rid) +{ + _enq_hdr._rid = rid; + _enq_tail._rid = rid; +} + +void +enq_rec::chk_hdr() const +{ + jrec::chk_hdr(_enq_hdr); + if (_enq_hdr._magic != RHM_JDAT_ENQ_MAGIC) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "enq magic: rid=0x" << std::setw(16) << _enq_hdr._rid; + oss << ": expected=0x" << std::setw(8) << RHM_JDAT_ENQ_MAGIC; + oss << " read=0x" << std::setw(2) << (int)_enq_hdr._magic; + throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "enq_rec", "chk_hdr"); + } +} + +void +enq_rec::chk_hdr(u_int64_t rid) const +{ + chk_hdr(); + jrec::chk_rid(_enq_hdr, rid); +} + +void +enq_rec::chk_tail() const +{ + jrec::chk_tail(_enq_tail, _enq_hdr); +} + +void +enq_rec::clean() +{ + // clean up allocated memory here +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/enq_rec.h b/cpp/src/qpid/legacystore/jrnl/enq_rec.h new file mode 100644 index 0000000000..805a96a1aa --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/enq_rec.h @@ -0,0 +1,116 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file enq_rec.h + * + * Qpid asynchronous store plugin library + * + * This file contains the code for the mrg::journal::enq_rec (journal enqueue + * record) class. See class documentation for details. + */ + +#ifndef QPID_LEGACYSTORE_JRNL_ENQ_REC_H +#define QPID_LEGACYSTORE_JRNL_ENQ_REC_H + +namespace mrg +{ +namespace journal +{ +class enq_rec; +} +} + +#include <cstddef> +#include "qpid/legacystore/jrnl/enq_hdr.h" +#include "qpid/legacystore/jrnl/jrec.h" + +namespace mrg +{ +namespace journal +{ + + /** + * \class enq_rec + * \brief Class to handle a single journal enqueue record. + */ + class enq_rec : public jrec + { + private: + enq_hdr _enq_hdr; + const void* _xidp; ///< xid pointer for encoding (for writing to disk) + const void* _data; ///< Pointer to data to be written to disk + void* _buff; ///< Pointer to buffer to receive data read from disk + rec_tail _enq_tail; + + public: + /** + * \brief Constructor used for read operations. + */ + enq_rec(); + + /** + * \brief Constructor used for write operations, where mbuf contains data to be written. + */ + enq_rec(const u_int64_t rid, const void* const dbuf, const std::size_t dlen, + const void* const xidp, const std::size_t xidlen, const bool owi, const bool transient); + + /** + * \brief Destructor + */ + virtual ~enq_rec(); + + // Prepare instance for use in reading data from journal, xid and data will be allocated + void reset(); + // Prepare instance for use in writing data to journal + void reset(const u_int64_t rid, const void* const dbuf, const std::size_t dlen, + const void* const xidp, const std::size_t xidlen, const bool owi, const bool transient, + const bool external); + + u_int32_t encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks); + u_int32_t decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, + u_int32_t max_size_dblks); + // Decode used for recover + bool rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs); + + std::size_t get_xid(void** const xidpp); + std::size_t get_data(void** const datapp); + inline bool is_transient() const { return _enq_hdr.is_transient(); } + inline bool is_external() const { return _enq_hdr.is_external(); } + std::string& str(std::string& str) const; + inline std::size_t data_size() const { return _enq_hdr._dsize; } + inline std::size_t xid_size() const { return _enq_hdr._xidsize; } + std::size_t rec_size() const; + static std::size_t rec_size(const std::size_t xidsize, const std::size_t dsize, const bool external); + inline u_int64_t rid() const { return _enq_hdr._rid; } + void set_rid(const u_int64_t rid); + + private: + void chk_hdr() const; + void chk_hdr(u_int64_t rid) const; + void chk_tail() const; + virtual void clean(); + }; // class enq_rec + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_ENQ_REC_H diff --git a/cpp/src/qpid/legacystore/jrnl/enums.h b/cpp/src/qpid/legacystore/jrnl/enums.h new file mode 100644 index 0000000000..169a13fa4d --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/enums.h @@ -0,0 +1,108 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file enums.h + * + * Qpid asynchronous store plugin library + * + * File containing definitions for namespace mrg::journal enums. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_ENUMS_H +#define QPID_LEGACYSTORE_JRNL_ENUMS_H + +namespace mrg +{ +namespace journal +{ + + // TODO: Change this to flags, as multiple of these conditions may exist simultaneously + /** + * \brief Enumeration of possilbe return states from journal read and write operations. + */ + enum _iores + { + RHM_IORES_SUCCESS = 0, ///< Success: IO operation completed noramlly. + RHM_IORES_PAGE_AIOWAIT, ///< IO operation suspended - next page is waiting for AIO. + RHM_IORES_FILE_AIOWAIT, ///< IO operation suspended - next file is waiting for AIO. + RHM_IORES_EMPTY, ///< During read operations, nothing further is available to read. + RHM_IORES_RCINVALID, ///< Read page cache is invalid (ie obsolete or uninitialized) + RHM_IORES_ENQCAPTHRESH, ///< Enqueue capacity threshold (limit) reached. + RHM_IORES_FULL, ///< During write operations, the journal files are full. + RHM_IORES_BUSY, ///< Another blocking operation is in progress. + RHM_IORES_TXPENDING, ///< Operation blocked by pending transaction. + RHM_IORES_NOTIMPL ///< Function is not yet implemented. + }; + typedef _iores iores; + + static inline const char* iores_str(iores res) + { + switch (res) + { + case RHM_IORES_SUCCESS: return "RHM_IORES_SUCCESS"; + case RHM_IORES_PAGE_AIOWAIT: return "RHM_IORES_PAGE_AIOWAIT"; + case RHM_IORES_FILE_AIOWAIT: return "RHM_IORES_FILE_AIOWAIT"; + case RHM_IORES_EMPTY: return "RHM_IORES_EMPTY"; + case RHM_IORES_RCINVALID: return "RHM_IORES_RCINVALID"; + case RHM_IORES_ENQCAPTHRESH: return "RHM_IORES_ENQCAPTHRESH"; + case RHM_IORES_FULL: return "RHM_IORES_FULL"; + case RHM_IORES_BUSY: return "RHM_IORES_BUSY"; + case RHM_IORES_TXPENDING: return "RHM_IORES_TXPENDING"; + case RHM_IORES_NOTIMPL: return "RHM_IORES_NOTIMPL"; + } + return "<iores unknown>"; + } + + enum _log_level + { + LOG_TRACE = 0, + LOG_DEBUG, + LOG_INFO, + LOG_NOTICE, + LOG_WARN, + LOG_ERROR, + LOG_CRITICAL + }; + typedef _log_level log_level; + + static inline const char* log_level_str(log_level ll) + { + switch (ll) + { + case LOG_TRACE: return "TRACE"; + case LOG_DEBUG: return "DEBUG"; + case LOG_INFO: return "INFO"; + case LOG_NOTICE: return "NOTICE"; + case LOG_WARN: return "WARN"; + case LOG_ERROR: return "ERROR"; + case LOG_CRITICAL: return "CRITICAL"; + } + return "<log level unknown>"; + } + + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_ENUMS_H diff --git a/cpp/src/qpid/legacystore/jrnl/fcntl.cpp b/cpp/src/qpid/legacystore/jrnl/fcntl.cpp new file mode 100644 index 0000000000..fbb176667e --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/fcntl.cpp @@ -0,0 +1,375 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file fcntl.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::fcntl (non-logging file + * handle), used for controlling journal log files. See comments in file + * fcntl.h for details. + */ + +#include "qpid/legacystore/jrnl/fcntl.h" + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <fcntl.h> +#include <iomanip> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include <sstream> +#include <unistd.h> + +namespace mrg +{ +namespace journal +{ + +fcntl::fcntl(const std::string& fbasename, const u_int16_t pfid, const u_int16_t lfid, const u_int32_t jfsize_sblks, + const rcvdat* const ro): + _fname(), + _pfid(pfid), + _lfid(lfid), + _ffull_dblks(JRNL_SBLK_SIZE * (jfsize_sblks + 1)), + _wr_fh(-1), + _rec_enqcnt(0), + _rd_subm_cnt_dblks(0), + _rd_cmpl_cnt_dblks(0), + _wr_subm_cnt_dblks(0), + _wr_cmpl_cnt_dblks(0), + _aio_cnt(0), + _fhdr_wr_aio_outstanding(false) +{ + initialize(fbasename, pfid, lfid, jfsize_sblks, ro); + open_wr_fh(); +} + +fcntl::~fcntl() +{ + close_wr_fh(); +} + +bool +fcntl::reset(const rcvdat* const ro) +{ + rd_reset(); + return wr_reset(ro); +} + +void +fcntl::rd_reset() +{ + _rd_subm_cnt_dblks = 0; + _rd_cmpl_cnt_dblks = 0; +} + +bool +fcntl::wr_reset(const rcvdat* const ro) +{ + if (ro) + { + if (!ro->_jempty) + { + if (ro->_lfid == _pfid) + { + _wr_subm_cnt_dblks = ro->_eo/JRNL_DBLK_SIZE; + _wr_cmpl_cnt_dblks = ro->_eo/JRNL_DBLK_SIZE; + } + else + { + _wr_subm_cnt_dblks = _ffull_dblks; + _wr_cmpl_cnt_dblks = _ffull_dblks; + } + _rec_enqcnt = ro->_enq_cnt_list[_pfid]; + return true; + } + } + // Journal overflow test - checks if the file to be reset still contains enqueued records + // or outstanding aios + if (_rec_enqcnt || _aio_cnt) + return false; + _wr_subm_cnt_dblks = 0; + _wr_cmpl_cnt_dblks = 0; + return true; +} + +int +fcntl::open_wr_fh() +{ + if (_wr_fh < 0) + { + _wr_fh = ::open(_fname.c_str(), O_WRONLY | O_DIRECT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); // 0644 -rw-r--r-- + if (_wr_fh < 0) + { + std::ostringstream oss; + oss << "pfid=" << _pfid << " lfid=" << _lfid << " file=\"" << _fname << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_FCNTL_OPENWR, oss.str(), "fcntl", "open_fh"); + } + } + return _wr_fh; +} + +void +fcntl::close_wr_fh() +{ + if (_wr_fh >= 0) + { + ::close(_wr_fh); + _wr_fh = -1; + } +} + +u_int32_t +fcntl::add_enqcnt(u_int32_t a) +{ + _rec_enqcnt += a; + return _rec_enqcnt; +} + +u_int32_t +fcntl::decr_enqcnt() +{ + if (_rec_enqcnt == 0) + { + std::ostringstream oss; + oss << "pfid=" << _pfid << " lfid=" << _lfid; + throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "fcntl", "decr_enqcnt"); + } + return --_rec_enqcnt; +} + +u_int32_t +fcntl::subtr_enqcnt(u_int32_t s) +{ + if (_rec_enqcnt < s) + { + std::ostringstream oss; + oss << "pfid=" << _pfid << " lfid=" << _lfid << " rec_enqcnt=" << _rec_enqcnt << " decr=" << s; + throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "fcntl", "subtr_enqcnt"); + } + _rec_enqcnt -= s; + return _rec_enqcnt; +} + +u_int32_t +fcntl::add_rd_subm_cnt_dblks(u_int32_t a) +{ + if (_rd_subm_cnt_dblks + a > _wr_subm_cnt_dblks) + { + std::ostringstream oss; + oss << "pfid=" << _pfid << " lfid=" << _lfid << " rd_subm_cnt_dblks=" << _rd_subm_cnt_dblks << " incr=" << a; + oss << " wr_subm_cnt_dblks=" << _wr_subm_cnt_dblks; + throw jexception(jerrno::JERR_FCNTL_RDOFFSOVFL, oss.str(), "fcntl", "add_rd_subm_cnt_dblks"); + } + _rd_subm_cnt_dblks += a; + return _rd_subm_cnt_dblks; +} + +u_int32_t +fcntl::add_rd_cmpl_cnt_dblks(u_int32_t a) +{ + if (_rd_cmpl_cnt_dblks + a > _rd_subm_cnt_dblks) + { + std::ostringstream oss; + oss << "pfid=" << _pfid << " lfid=" << _lfid << " rd_cmpl_cnt_dblks=" << _rd_cmpl_cnt_dblks << " incr=" << a; + oss << " rd_subm_cnt_dblks=" << _rd_subm_cnt_dblks; + throw jexception(jerrno::JERR_FCNTL_CMPLOFFSOVFL, oss.str(), "fcntl", "add_rd_cmpl_cnt_dblks"); + } + _rd_cmpl_cnt_dblks += a; + return _rd_cmpl_cnt_dblks; +} + +u_int32_t +fcntl::add_wr_subm_cnt_dblks(u_int32_t a) +{ + if (_wr_subm_cnt_dblks + a > _ffull_dblks) // Allow for file header + { + std::ostringstream oss; + oss << "pfid=" << _pfid << " lfid=" << _lfid << " wr_subm_cnt_dblks=" << _wr_subm_cnt_dblks << " incr=" << a; + oss << " fsize=" << _ffull_dblks << " dblks"; + throw jexception(jerrno::JERR_FCNTL_FILEOFFSOVFL, oss.str(), "fcntl", "add_wr_subm_cnt_dblks"); + } + _wr_subm_cnt_dblks += a; + return _wr_subm_cnt_dblks; +} + +u_int32_t +fcntl::add_wr_cmpl_cnt_dblks(u_int32_t a) +{ + if (_wr_cmpl_cnt_dblks + a > _wr_subm_cnt_dblks) + { + std::ostringstream oss; + oss << "pfid=" << _pfid << " lfid=" << _lfid << " wr_cmpl_cnt_dblks=" << _wr_cmpl_cnt_dblks << " incr=" << a; + oss << " wr_subm_cnt_dblks=" << _wr_subm_cnt_dblks; + throw jexception(jerrno::JERR_FCNTL_CMPLOFFSOVFL, oss.str(), "fcntl", "add_wr_cmpl_cnt_dblks"); + } + _wr_cmpl_cnt_dblks += a; + return _wr_cmpl_cnt_dblks; +} + +u_int16_t +fcntl::decr_aio_cnt() +{ + if(_aio_cnt == 0) + { + std::ostringstream oss; + oss << "pfid=" << _pfid << " lfid=" << _lfid << " Decremented aio_cnt to below zero"; + throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "fcntl", "decr_aio_cnt"); + } + return --_aio_cnt; +} + +// Debug function +const std::string +fcntl::status_str() const +{ + std::ostringstream oss; + oss << "pfid=" << _pfid << " ws=" << _wr_subm_cnt_dblks << " wc=" << _wr_cmpl_cnt_dblks; + oss << " rs=" << _rd_subm_cnt_dblks << " rc=" << _rd_cmpl_cnt_dblks; + oss << " ec=" << _rec_enqcnt << " ac=" << _aio_cnt; + return oss.str(); +} + +// Protected functions + +void +fcntl::initialize(const std::string& fbasename, const u_int16_t pfid, const u_int16_t lfid, const u_int32_t jfsize_sblks, + const rcvdat* const ro) +{ + _pfid = pfid; + _lfid = lfid; + _fname = filename(fbasename, pfid); + +#ifdef RHM_JOWRITE + // In test mode, only create file if it does not exist + struct stat s; + if (::stat(_fname.c_str(), &s)) + { +#endif + if (ro) // Recovery initialization: set counters only + { + if (!ro->_jempty) + { + // For last file only, set write counters to end of last record (the + // continuation point); for all others, set to eof. + if (ro->_lfid == _pfid) + { + _wr_subm_cnt_dblks = ro->_eo/JRNL_DBLK_SIZE; + _wr_cmpl_cnt_dblks = ro->_eo/JRNL_DBLK_SIZE; + } + else + { + _wr_subm_cnt_dblks = _ffull_dblks; + _wr_cmpl_cnt_dblks = _ffull_dblks; + } + // Set the number of enqueued records for this file. + _rec_enqcnt = ro->_enq_cnt_list[_pfid]; + } + } + else // Normal initialization: create empty journal files + create_jfile(jfsize_sblks); +#ifdef RHM_JOWRITE + } +#endif +} + +std::string +fcntl::filename(const std::string& fbasename, const u_int16_t pfid) +{ + std::ostringstream oss; + oss << fbasename << "."; + oss << std::setw(4) << std::setfill('0') << std::hex << pfid; + oss << "." << JRNL_DATA_EXTENSION; + return oss.str(); +} + +void +fcntl::clean_file(const u_int32_t jfsize_sblks) +{ + // NOTE: The journal file size is always one sblock bigger than the specified journal + // file size, which is the data content size. The extra block is for the journal file + // header which precedes all data on each file and is exactly one sblock in size. + u_int32_t nsblks = jfsize_sblks + 1; + + // TODO - look at more efficient alternatives to allocating a null block: + // 1. mmap() against /dev/zero, but can alignment for O_DIRECT be assured? + // 2. ftruncate(), but does this result in a sparse file? If so, then this is no good. + + // Create temp null block for writing + const std::size_t sblksize = JRNL_DBLK_SIZE * JRNL_SBLK_SIZE; + void* nullbuf = 0; + // Allocate no more than 2MB (4096 sblks) as a null buffer + const u_int32_t nullbuffsize_sblks = nsblks > 4096 ? 4096 : nsblks; + const std::size_t nullbuffsize = nullbuffsize_sblks * sblksize; + if (::posix_memalign(&nullbuf, sblksize, nullbuffsize)) + { + std::ostringstream oss; + oss << "posix_memalign() failed: size=" << nullbuffsize << " blk_size=" << sblksize; + oss << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR__MALLOC, oss.str(), "fcntl", "clean_file"); + } + std::memset(nullbuf, 0, nullbuffsize); + + int fh = ::open(_fname.c_str(), O_WRONLY | O_CREAT | O_DIRECT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); // 0644 -rw-r--r-- + if (fh < 0) + { + std::free(nullbuf); + std::ostringstream oss; + oss << "open() failed:" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_FCNTL_OPENWR, oss.str(), "fcntl", "clean_file"); + } + + while (nsblks > 0) + { + u_int32_t this_write_sblks = nsblks >= nullbuffsize_sblks ? nullbuffsize_sblks : nsblks; + if (::write(fh, nullbuf, this_write_sblks * sblksize) == -1) + { + ::close(fh); + std::free(nullbuf); + std::ostringstream oss; + oss << "wr_size=" << (this_write_sblks * sblksize) << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_FCNTL_WRITE, oss.str(), "fcntl", "clean_file"); + } + nsblks -= this_write_sblks; + } + + // Clean up + std::free(nullbuf); + if (::close(fh)) + { + std::ostringstream oss; + oss << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_FCNTL_CLOSE, oss.str(), "fcntl", "clean_file"); + } +} + +void +fcntl::create_jfile(const u_int32_t jfsize_sblks) +{ + clean_file(jfsize_sblks); +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/fcntl.h b/cpp/src/qpid/legacystore/jrnl/fcntl.h new file mode 100644 index 0000000000..a75e3bc84d --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/fcntl.h @@ -0,0 +1,156 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file fcntl.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::fcntl (non-logging file + * handle), used for controlling journal log files. See class documentation for + * details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_FCNTL_H +#define QPID_LEGACYSTORE_JRNL_FCNTL_H + +namespace mrg +{ +namespace journal +{ +class fcntl; +} +} + +#include <cstddef> +#include <string> +#include "qpid/legacystore/jrnl/rcvdat.h" +#include <sys/types.h> + +namespace mrg +{ +namespace journal +{ + + /** + * \class fcntl + * \brief Journal file controller. There is one instance per journal file. + */ + class fcntl + { + protected: + std::string _fname; ///< File name + u_int16_t _pfid; ///< Physical file ID (file number in order of creation) + u_int16_t _lfid; ///< Logical file ID (ordinal number in ring store) + const u_int32_t _ffull_dblks; ///< File size in dblks (incl. file header) + int _wr_fh; ///< Write file handle + u_int32_t _rec_enqcnt; ///< Count of enqueued records + u_int32_t _rd_subm_cnt_dblks; ///< Read file count (data blocks) for submitted AIO + u_int32_t _rd_cmpl_cnt_dblks; ///< Read file count (data blocks) for completed AIO + u_int32_t _wr_subm_cnt_dblks; ///< Write file count (data blocks) for submitted AIO + u_int32_t _wr_cmpl_cnt_dblks; ///< Write file count (data blocks) for completed AIO + u_int16_t _aio_cnt; ///< Outstanding AIO operations on this file + bool _fhdr_wr_aio_outstanding; ///< Outstanding file header write on this file + + public: + // Constructors with implicit initialize() and open() + fcntl(const std::string& fbasename, const u_int16_t pfid, const u_int16_t lfid, const u_int32_t jfsize_sblks, + const rcvdat* const ro); + virtual ~fcntl(); + + virtual bool reset(const rcvdat* const ro = 0); + virtual void rd_reset(); + virtual bool wr_reset(const rcvdat* const ro = 0); + + virtual int open_wr_fh(); + virtual void close_wr_fh(); + inline bool is_wr_fh_open() const { return _wr_fh >= 0; } + + inline const std::string& fname() const { return _fname; } + inline u_int16_t pfid() const { return _pfid; } + inline u_int16_t lfid() const { return _lfid; } + inline void set_lfid(const u_int16_t lfid) { _lfid = lfid; } + inline int wr_fh() const { return _wr_fh; } + inline u_int32_t enqcnt() const { return _rec_enqcnt; } + inline u_int32_t incr_enqcnt() { return ++_rec_enqcnt; } + u_int32_t add_enqcnt(u_int32_t a); + u_int32_t decr_enqcnt(); + u_int32_t subtr_enqcnt(u_int32_t s); + + inline u_int32_t rd_subm_cnt_dblks() const { return _rd_subm_cnt_dblks; } + inline std::size_t rd_subm_offs() const { return _rd_subm_cnt_dblks * JRNL_DBLK_SIZE; } + u_int32_t add_rd_subm_cnt_dblks(u_int32_t a); + + inline u_int32_t rd_cmpl_cnt_dblks() const { return _rd_cmpl_cnt_dblks; } + inline std::size_t rd_cmpl_offs() const { return _rd_cmpl_cnt_dblks * JRNL_DBLK_SIZE; } + u_int32_t add_rd_cmpl_cnt_dblks(u_int32_t a); + + inline u_int32_t wr_subm_cnt_dblks() const { return _wr_subm_cnt_dblks; } + inline std::size_t wr_subm_offs() const { return _wr_subm_cnt_dblks * JRNL_DBLK_SIZE; } + u_int32_t add_wr_subm_cnt_dblks(u_int32_t a); + + inline u_int32_t wr_cmpl_cnt_dblks() const { return _wr_cmpl_cnt_dblks; } + inline std::size_t wr_cmpl_offs() const { return _wr_cmpl_cnt_dblks * JRNL_DBLK_SIZE; } + u_int32_t add_wr_cmpl_cnt_dblks(u_int32_t a); + + inline u_int16_t aio_cnt() const { return _aio_cnt; } + inline u_int16_t incr_aio_cnt() { return ++_aio_cnt; } + u_int16_t decr_aio_cnt(); + + inline bool wr_fhdr_aio_outstanding() { return _fhdr_wr_aio_outstanding; } + inline void set_wr_fhdr_aio_outstanding(const bool wfao) { _fhdr_wr_aio_outstanding = wfao; } + + // Derived helper functions + + inline bool rd_void() const { return _wr_cmpl_cnt_dblks == 0; } + inline bool rd_empty() const { return _wr_cmpl_cnt_dblks <= JRNL_SBLK_SIZE; } + inline u_int32_t rd_remaining_dblks() const { return _wr_cmpl_cnt_dblks - _rd_subm_cnt_dblks; } + inline bool is_rd_full() const { return _wr_cmpl_cnt_dblks == _rd_subm_cnt_dblks; } + inline bool is_rd_compl() const { return _wr_cmpl_cnt_dblks == _rd_cmpl_cnt_dblks; } + inline u_int32_t rd_aio_outstanding_dblks() const { return _rd_subm_cnt_dblks - _rd_cmpl_cnt_dblks; } + inline bool rd_file_rotate() const { return is_rd_full() && is_wr_compl(); } + + inline bool wr_void() const { return _wr_subm_cnt_dblks == 0; } + inline bool wr_empty() const { return _wr_subm_cnt_dblks <= JRNL_SBLK_SIZE; } + inline u_int32_t wr_remaining_dblks() const { return _ffull_dblks - _wr_subm_cnt_dblks; } + inline bool is_wr_full() const { return _ffull_dblks == _wr_subm_cnt_dblks; } + inline bool is_wr_compl() const { return _ffull_dblks == _wr_cmpl_cnt_dblks; } + inline u_int32_t wr_aio_outstanding_dblks() const { return _wr_subm_cnt_dblks - _wr_cmpl_cnt_dblks; } + inline bool wr_file_rotate() const { return is_wr_full(); } + + // Debug aid + const std::string status_str() const; + + protected: + virtual void initialize(const std::string& fbasename, const u_int16_t pfid, const u_int16_t lfid, + const u_int32_t jfsize_sblks, const rcvdat* const ro); + + static std::string filename(const std::string& fbasename, const u_int16_t pfid); + void clean_file(const u_int32_t jfsize_sblks); + void create_jfile(const u_int32_t jfsize_sblks); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_FCNTL_H diff --git a/cpp/src/qpid/legacystore/jrnl/file_hdr.h b/cpp/src/qpid/legacystore/jrnl/file_hdr.h new file mode 100644 index 0000000000..db20834cbb --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/file_hdr.h @@ -0,0 +1,211 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file file_hdr.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::file_hdr (file + * record header), used to start a journal file. It contains some + * file metadata and information to aid journal recovery. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_FILE_HDR_H +#define QPID_LEGACYSTORE_JRNL_FILE_HDR_H + +#include <cerrno> +#include <ctime> +#include "qpid/legacystore/jrnl/rec_hdr.h" +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +#pragma pack(1) + + /** + * \brief Struct for data common to the head of all journal files. In addition to + * the common data, this includes the record ID and offset of the first record in + * the file. + * + * This header precedes all data in journal files and occupies the first complete + * block in the file. The record ID and offset are updated on each overwrite of the + * file. + * + * File header info in binary format (48 bytes): + * <pre> + * 0 7 + * +---+---+---+---+---+---+---+---+ -+ + * | magic | v | e | flags | | + * +---+---+---+---+---+---+---+---+ | struct hdr + * | first rid in file | | + * +---+---+---+---+---+---+---+---+ -+ + * | pfid | lfid | reserved (0) | + * +---+---+---+---+---+---+---+---+ + * | fro | + * +---+---+---+---+---+---+---+---+ + * | timestamp (sec) | + * +---+---+---+---+---+---+---+---+ + * | timestamp (ns) | + * +---+---+---+---+---+---+---+---+ + * v = file version (If the format or encoding of this file changes, then this + * number should be incremented) + * e = endian flag, false (0x00) for little endian, true (0x01) for big endian + * pfid = File ID (number used in naming file) + * lfid = Logical ID (order used in circular buffer) + * fro = First record offset, offset from start of file to first record header + * </pre> + * + * Note that journal files should be transferable between 32- and 64-bit + * hardware of the same endianness, but not between hardware of opposite + * entianness without some sort of binary conversion utility. Thus buffering + * will be needed for types that change size between 32- and 64-bit compiles. + */ + struct file_hdr : rec_hdr + { + u_int16_t _pfid; ///< Physical file ID (pfid) + u_int16_t _lfid; ///< Logical file ID (lfid) + u_int32_t _res; ///< Reserved (for alignment/flags) +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler0; ///< Big-endian filler for 32-bit size_t +#endif + std::size_t _fro; ///< First record offset +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler0; ///< Little-endian filler for 32-bit size_t +#endif +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler1; ///< Big-endian filler for 32-bit time_t +#endif + std::time_t _ts_sec; ///< Timestamp of journal initilailization +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler1; ///< Little-endian filler for 32-bit time_t +#endif +#if defined(JRNL_BIG_ENDIAN) + u_int32_t _filler2; ///< Big endian filler for u_int32_t +#endif + u_int32_t _ts_nsec; ///< Timestamp of journal initilailization +#if defined(JRNL_LITTLE_ENDIAN) + u_int32_t _filler2; ///< Little-endian filler for u_int32_t +#endif + + /** + * \brief Default constructor, which sets all values to 0. + */ + inline file_hdr(): rec_hdr(), _pfid(0), _lfid(0), _res(0), +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif + _fro(0), +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler1(0), +#endif + _ts_sec(0), +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + _filler1(0), +#endif +#if defined(JRNL_BIG_ENDIAN) + _filler2(0), +#endif + _ts_nsec(0) +#if defined(JRNL_LITTLE_ENDIAN) + , _filler2(0) +#endif + {} + + /** + * \brief Convenience constructor which initializes values during construction. + */ + inline file_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid, + const u_int16_t pfid, const u_int16_t lfid, const std::size_t fro, + const bool owi, const bool settime = false): + rec_hdr(magic, version, rid, owi), _pfid(pfid), _lfid(lfid), _res(0), +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif + _fro(fro), +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler1(0), +#endif + _ts_sec(0), +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + _filler1(0), +#endif +#if defined(JRNL_BIG_ENDIAN) + _filler2(0), +#endif + _ts_nsec(0) +#if defined(JRNL_LITTLE_ENDIAN) + , _filler2(0) +#endif + { if (settime) set_time(); } + + /** + * \brief Gets the current time from the system clock and sets the timestamp in the struct. + */ + inline void set_time() + { + // TODO: Standardize on method for getting time that does not requrie a context switch. + timespec ts; + if (::clock_gettime(CLOCK_REALTIME, &ts)) + { + std::ostringstream oss; + oss << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR__RTCLOCK, oss.str(), "file_hdr", "set_time"); + } + _ts_sec = ts.tv_sec; + _ts_nsec = ts.tv_nsec; + } + + /** + * \brief Sets the timestamp in the struct to the provided value (in seconds and + * nanoseconds). + */ + inline void set_time(timespec& ts) + { + _ts_sec = ts.tv_sec; + _ts_nsec = ts.tv_nsec; + } + + /** + * \brief Returns the size of the header in bytes. + */ + inline static std::size_t size() { return sizeof(file_hdr); } + }; // struct file_hdr + +#pragma pack() + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_FILE_HDR_H diff --git a/cpp/src/qpid/legacystore/jrnl/jcfg.h b/cpp/src/qpid/legacystore/jrnl/jcfg.h new file mode 100644 index 0000000000..0a0d0df28d --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jcfg.h @@ -0,0 +1,91 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jcfg.h + * + * Qpid asynchronous store plugin library + * + * This file contains \#defines that control the implementation details of + * the journal. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_JCFG_H +#define QPID_LEGACYSTORE_JRNL_JCFG_H + +#if defined(__i386__) /* little endian, 32 bits */ +#define JRNL_LITTLE_ENDIAN +#define JRNL_32_BIT +#elif defined(__PPC__) || defined(__s390__) /* big endian, 32 bits */ +#define JRNL_BIG_ENDIAN +#define JRNL_32_BIT +#elif defined(__ia64__) || defined(__x86_64__) || defined(__alpha__) /* little endian, 64 bits */ +#define JRNL_LITTLE_ENDIAN +#define JRNL_64_BIT +#elif defined(__powerpc64__) || defined(__s390x__) /* big endian, 64 bits */ +#define JRNL_BIG_ENDIAN +#define JRNL_64_BIT +#else +#error endian? +#endif + + +/** +* <b>Rule:</b> Data block size (JRNL_DBLK_SIZE) MUST be a power of 2 such that +* <pre> +* JRNL_DBLK_SIZE * JRNL_SBLK_SIZE == n * 512 (n = 1,2,3...) +* </pre> +* (The disk softblock size is 512 for Linux kernels >= 2.6) +*/ +#define JRNL_DBLK_SIZE 128 ///< Data block size in bytes (CANNOT BE LESS THAN 32!) +#define JRNL_SBLK_SIZE 4 ///< Disk softblock size in multiples of JRNL_DBLK_SIZE +#define JRNL_MIN_FILE_SIZE 128 ///< Min. jrnl file size in sblks (excl. file_hdr) +#define JRNL_MAX_FILE_SIZE 4194176 ///< Max. jrnl file size in sblks (excl. file_hdr) +#define JRNL_MIN_NUM_FILES 4 ///< Min. number of journal files +#define JRNL_MAX_NUM_FILES 64 ///< Max. number of journal files +#define JRNL_ENQ_THRESHOLD 80 ///< Percent full when enqueue connection will be closed + +#define JRNL_RMGR_PAGE_SIZE 128 ///< Journal page size in softblocks +#define JRNL_RMGR_PAGES 16 ///< Number of pages to use in wmgr + +#define JRNL_WMGR_DEF_PAGE_SIZE 64 ///< Journal write page size in softblocks (default) +#define JRNL_WMGR_DEF_PAGES 32 ///< Number of pages to use in wmgr (default) + +#define JRNL_WMGR_MAXDTOKPP 1024 ///< Max. dtoks (data blocks) per page in wmgr +#define JRNL_WMGR_MAXWAITUS 100 ///< Max. wait time (us) before submitting AIO + +#define JRNL_INFO_EXTENSION "jinf" ///< Extension for journal info files +#define JRNL_DATA_EXTENSION "jdat" ///< Extension for journal data files +#define RHM_JDAT_TXA_MAGIC 0x614d4852 ///< ("RHMa" in little endian) Magic for dtx abort hdrs +#define RHM_JDAT_TXC_MAGIC 0x634d4852 ///< ("RHMc" in little endian) Magic for dtx commit hdrs +#define RHM_JDAT_DEQ_MAGIC 0x644d4852 ///< ("RHMd" in little endian) Magic for deq rec hdrs +#define RHM_JDAT_ENQ_MAGIC 0x654d4852 ///< ("RHMe" in little endian) Magic for enq rec hdrs +#define RHM_JDAT_FILE_MAGIC 0x664d4852 ///< ("RHMf" in little endian) Magic for file hdrs +#define RHM_JDAT_EMPTY_MAGIC 0x784d4852 ///< ("RHMx" in little endian) Magic for empty dblk +#define RHM_JDAT_VERSION 0x01 ///< Version (of file layout) +#define RHM_CLEAN_CHAR 0xff ///< Char used to clear empty space on disk + +#define RHM_LENDIAN_FLAG 0 ///< Value of little endian flag on disk +#define RHM_BENDIAN_FLAG 1 ///< Value of big endian flag on disk + +#endif // ifndef QPID_LEGACYSTORE_JRNL_JCFG_H diff --git a/cpp/src/qpid/legacystore/jrnl/jcntl.cpp b/cpp/src/qpid/legacystore/jrnl/jcntl.cpp new file mode 100644 index 0000000000..a03076dca5 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jcntl.cpp @@ -0,0 +1,984 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jcntl.cpp + * + * Qpid asynchronous store plugin library + * + * Messaging journal top-level control and interface class + * mrg::journal::jcntl. See comments in file jcntl.h for details. + * + * \author Kim van der Riet + */ + + +#include "qpid/legacystore/jrnl/jcntl.h" + +#include <algorithm> +#include <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <iomanip> +#include <iostream> +#include "qpid/legacystore/jrnl/file_hdr.h" +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jinf.h" +#include <limits> +#include <sstream> +#include <unistd.h> + +namespace mrg +{ +namespace journal +{ + +#define AIO_CMPL_TIMEOUT_SEC 5 +#define AIO_CMPL_TIMEOUT_NSEC 0 +#define FINAL_AIO_CMPL_TIMEOUT_SEC 15 +#define FINAL_AIO_CMPL_TIMEOUT_NSEC 0 + +// Static +timespec jcntl::_aio_cmpl_timeout; ///< Timeout for blocking libaio returns +timespec jcntl::_final_aio_cmpl_timeout; ///< Timeout for blocking libaio returns when stopping or finalizing +bool jcntl::_init = init_statics(); +bool jcntl::init_statics() +{ + _aio_cmpl_timeout.tv_sec = AIO_CMPL_TIMEOUT_SEC; + _aio_cmpl_timeout.tv_nsec = AIO_CMPL_TIMEOUT_NSEC; + _final_aio_cmpl_timeout.tv_sec = FINAL_AIO_CMPL_TIMEOUT_SEC; + _final_aio_cmpl_timeout.tv_nsec = FINAL_AIO_CMPL_TIMEOUT_NSEC; + return true; +} + + +// Functions + +jcntl::jcntl(const std::string& jid, const std::string& jdir, const std::string& base_filename): + _jid(jid), + _jdir(jdir, base_filename), + _base_filename(base_filename), + _init_flag(false), + _stop_flag(false), + _readonly_flag(false), + _autostop(true), + _jfsize_sblks(0), + _lpmgr(), + _emap(), + _tmap(), + _rrfc(&_lpmgr), + _wrfc(&_lpmgr), + _rmgr(this, _emap, _tmap, _rrfc), + _wmgr(this, _emap, _tmap, _wrfc), + _rcvdat() +{} + +jcntl::~jcntl() +{ + if (_init_flag && !_stop_flag) + try { stop(true); } + catch (const jexception& e) { std::cerr << e << std::endl; } + _lpmgr.finalize(); +} + +void +jcntl::initialize(const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks, + aio_callback* const cbp) +{ + _init_flag = false; + _stop_flag = false; + _readonly_flag = false; + + _emap.clear(); + _tmap.clear(); + + _lpmgr.finalize(); + + // Set new file geometry parameters + assert(num_jfiles >= JRNL_MIN_NUM_FILES); + assert(num_jfiles <= JRNL_MAX_NUM_FILES); + _emap.set_num_jfiles(num_jfiles); + _tmap.set_num_jfiles(num_jfiles); + + assert(jfsize_sblks >= JRNL_MIN_FILE_SIZE); + assert(jfsize_sblks <= JRNL_MAX_FILE_SIZE); + _jfsize_sblks = jfsize_sblks; + + // Clear any existing journal files + _jdir.clear_dir(); + _lpmgr.initialize(num_jfiles, ae, ae_max_jfiles, this, &new_fcntl); + + _wrfc.initialize(_jfsize_sblks); + _rrfc.initialize(); + _rrfc.set_findex(0); + _rmgr.initialize(cbp); + _wmgr.initialize(cbp, wcache_pgsize_sblks, wcache_num_pages, JRNL_WMGR_MAXDTOKPP, JRNL_WMGR_MAXWAITUS); + + // Write info file (<basename>.jinf) to disk + write_infofile(); + + _init_flag = true; +} + +void +jcntl::recover(const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks, +// const rd_aio_cb rd_cb, const wr_aio_cb wr_cb, const std::vector<std::string>* prep_txn_list_ptr, + aio_callback* const cbp, const std::vector<std::string>* prep_txn_list_ptr, + u_int64_t& highest_rid) +{ + _init_flag = false; + _stop_flag = false; + _readonly_flag = false; + + _emap.clear(); + _tmap.clear(); + + _lpmgr.finalize(); + + assert(num_jfiles >= JRNL_MIN_NUM_FILES); + assert(num_jfiles <= JRNL_MAX_NUM_FILES); + assert(jfsize_sblks >= JRNL_MIN_FILE_SIZE); + assert(jfsize_sblks <= JRNL_MAX_FILE_SIZE); + _jfsize_sblks = jfsize_sblks; + + // Verify journal dir and journal files + _jdir.verify_dir(); + _rcvdat.reset(num_jfiles, ae, ae_max_jfiles); + + rcvr_janalyze(_rcvdat, prep_txn_list_ptr); + highest_rid = _rcvdat._h_rid; + if (_rcvdat._jfull) + throw jexception(jerrno::JERR_JCNTL_RECOVERJFULL, "jcntl", "recover"); + this->log(LOG_DEBUG, _rcvdat.to_log(_jid)); + + _lpmgr.recover(_rcvdat, this, &new_fcntl); + + _wrfc.initialize(_jfsize_sblks, &_rcvdat); + _rrfc.initialize(); + _rrfc.set_findex(_rcvdat.ffid()); + _rmgr.initialize(cbp); + _wmgr.initialize(cbp, wcache_pgsize_sblks, wcache_num_pages, JRNL_WMGR_MAXDTOKPP, JRNL_WMGR_MAXWAITUS, + (_rcvdat._lffull ? 0 : _rcvdat._eo)); + + _readonly_flag = true; + _init_flag = true; +} + +void +jcntl::recover_complete() +{ + if (!_readonly_flag) + throw jexception(jerrno::JERR_JCNTL_NOTRECOVERED, "jcntl", "recover_complete"); + for (u_int16_t i=0; i<_lpmgr.num_jfiles(); i++) + _lpmgr.get_fcntlp(i)->reset(&_rcvdat); + _wrfc.initialize(_jfsize_sblks, &_rcvdat); + _rrfc.initialize(); + _rrfc.set_findex(_rcvdat.ffid()); + _rmgr.recover_complete(); + _readonly_flag = false; +} + +void +jcntl::delete_jrnl_files() +{ + stop(true); // wait for AIO to complete + _jdir.delete_dir(); +} + + +iores +jcntl::enqueue_data_record(const void* const data_buff, const std::size_t tot_data_len, + const std::size_t this_data_len, data_tok* dtokp, const bool transient) +{ + iores r; + check_wstatus("enqueue_data_record"); + { + slock s(_wr_mutex); + while (handle_aio_wait(_wmgr.enqueue(data_buff, tot_data_len, this_data_len, dtokp, 0, 0, transient, false), r, + dtokp)) ; + } + return r; +} + +iores +jcntl::enqueue_extern_data_record(const std::size_t tot_data_len, data_tok* dtokp, const bool transient) +{ + iores r; + check_wstatus("enqueue_extern_data_record"); + { + slock s(_wr_mutex); + while (handle_aio_wait(_wmgr.enqueue(0, tot_data_len, 0, dtokp, 0, 0, transient, true), r, dtokp)) ; + } + return r; +} + +iores +jcntl::enqueue_txn_data_record(const void* const data_buff, const std::size_t tot_data_len, + const std::size_t this_data_len, data_tok* dtokp, const std::string& xid, + const bool transient) +{ + iores r; + check_wstatus("enqueue_tx_data_record"); + { + slock s(_wr_mutex); + while (handle_aio_wait(_wmgr.enqueue(data_buff, tot_data_len, this_data_len, dtokp, xid.data(), xid.size(), + transient, false), r, dtokp)) ; + } + return r; +} + +iores +jcntl::enqueue_extern_txn_data_record(const std::size_t tot_data_len, data_tok* dtokp, + const std::string& xid, const bool transient) +{ + iores r; + check_wstatus("enqueue_extern_txn_data_record"); + { + slock s(_wr_mutex); + while (handle_aio_wait(_wmgr.enqueue(0, tot_data_len, 0, dtokp, xid.data(), xid.size(), transient, true), r, + dtokp)) ; + } + return r; +} + +/* TODO +iores +jcntl::get_data_record(const u_int64_t& rid, const std::size_t& dsize, const std::size_t& dsize_avail, + const void** const data, bool auto_discard) +{ + check_rstatus("get_data_record"); + return _rmgr.get(rid, dsize, dsize_avail, data, auto_discard); +} */ + +/* TODO +iores +jcntl::discard_data_record(data_tok* const dtokp) +{ + check_rstatus("discard_data_record"); + return _rmgr.discard(dtokp); +} */ + +iores +jcntl::read_data_record(void** const datapp, std::size_t& dsize, void** const xidpp, std::size_t& xidsize, + bool& transient, bool& external, data_tok* const dtokp, bool ignore_pending_txns) +{ + check_rstatus("read_data"); + iores res = _rmgr.read(datapp, dsize, xidpp, xidsize, transient, external, dtokp, ignore_pending_txns); + if (res == RHM_IORES_RCINVALID) + { + get_wr_events(0); // check for outstanding write events + iores sres = _rmgr.synchronize(); // flushes all outstanding read events + if (sres != RHM_IORES_SUCCESS) + return sres; + _rmgr.wait_for_validity(&_aio_cmpl_timeout, true); // throw if timeout occurs + res = _rmgr.read(datapp, dsize, xidpp, xidsize, transient, external, dtokp, ignore_pending_txns); + } + return res; +} + +iores +jcntl::dequeue_data_record(data_tok* const dtokp, const bool txn_coml_commit) +{ + iores r; + check_wstatus("dequeue_data"); + { + slock s(_wr_mutex); + while (handle_aio_wait(_wmgr.dequeue(dtokp, 0, 0, txn_coml_commit), r, dtokp)) ; + } + return r; +} + +iores +jcntl::dequeue_txn_data_record(data_tok* const dtokp, const std::string& xid, const bool txn_coml_commit) +{ + iores r; + check_wstatus("dequeue_data"); + { + slock s(_wr_mutex); + while (handle_aio_wait(_wmgr.dequeue(dtokp, xid.data(), xid.size(), txn_coml_commit), r, dtokp)) ; + } + return r; +} + +iores +jcntl::txn_abort(data_tok* const dtokp, const std::string& xid) +{ + iores r; + check_wstatus("txn_abort"); + { + slock s(_wr_mutex); + while (handle_aio_wait(_wmgr.abort(dtokp, xid.data(), xid.size()), r, dtokp)) ; + } + return r; +} + +iores +jcntl::txn_commit(data_tok* const dtokp, const std::string& xid) +{ + iores r; + check_wstatus("txn_commit"); + { + slock s(_wr_mutex); + while (handle_aio_wait(_wmgr.commit(dtokp, xid.data(), xid.size()), r, dtokp)) ; + } + return r; +} + +bool +jcntl::is_txn_synced(const std::string& xid) +{ + slock s(_wr_mutex); + bool res = _wmgr.is_txn_synced(xid); + return res; +} + +int32_t +jcntl::get_wr_events(timespec* const timeout) +{ + stlock t(_wr_mutex); + if (!t.locked()) + return jerrno::LOCK_TAKEN; + int32_t res = _wmgr.get_events(pmgr::UNUSED, timeout); + return res; +} + +int32_t +jcntl::get_rd_events(timespec* const timeout) +{ + return _rmgr.get_events(pmgr::AIO_COMPLETE, timeout); +} + +void +jcntl::stop(const bool block_till_aio_cmpl) +{ + if (_readonly_flag) + check_rstatus("stop"); + else + check_wstatus("stop"); + _stop_flag = true; + if (!_readonly_flag) + flush(block_till_aio_cmpl); + _rrfc.finalize(); + _lpmgr.finalize(); +} + +u_int16_t +jcntl::get_earliest_fid() +{ + u_int16_t ffid = _wrfc.earliest_index(); + u_int16_t fid = _wrfc.index(); + while ( _emap.get_enq_cnt(ffid) == 0 && _tmap.get_txn_pfid_cnt(ffid) == 0 && ffid != fid) + { + if (++ffid >= _lpmgr.num_jfiles()) + ffid = 0; + } + if (!_rrfc.is_active()) + _rrfc.set_findex(ffid); + return ffid; +} + +iores +jcntl::flush(const bool block_till_aio_cmpl) +{ + if (!_init_flag) + return RHM_IORES_SUCCESS; + if (_readonly_flag) + throw jexception(jerrno::JERR_JCNTL_READONLY, "jcntl", "flush"); + iores res; + { + slock s(_wr_mutex); + res = _wmgr.flush(); + } + if (block_till_aio_cmpl) + aio_cmpl_wait(); + return res; +} + +void +jcntl::log(log_level ll, const std::string& log_stmt) const +{ + log(ll, log_stmt.c_str()); +} + +void +jcntl::log(log_level ll, const char* const log_stmt) const +{ + if (ll > LOG_INFO) + { + std::cout << log_level_str(ll) << ": Journal \"" << _jid << "\": " << log_stmt << std::endl; + } +} + +void +jcntl::chk_wr_frot() +{ + if (_wrfc.index() == _rrfc.index()) + _rmgr.invalidate(); +} + +void +jcntl::fhdr_wr_sync(const u_int16_t lid) +{ + fcntl* fcntlp = _lpmgr.get_fcntlp(lid); + while (fcntlp->wr_fhdr_aio_outstanding()) + { + if (get_wr_events(&_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT) + throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "fhdr_wr_sync"); + } +} + +fcntl* +jcntl::new_fcntl(jcntl* const jcp, const u_int16_t lid, const u_int16_t fid, const rcvdat* const rdp) +{ + if (!jcp) return 0; + std::ostringstream oss; + oss << jcp->jrnl_dir() << "/" << jcp->base_filename(); + return new fcntl(oss.str(), fid, lid, jcp->jfsize_sblks(), rdp); +} + +// Protected/Private functions + +void +jcntl::check_wstatus(const char* fn_name) const +{ + if (!_init_flag) + throw jexception(jerrno::JERR__NINIT, "jcntl", fn_name); + if (_readonly_flag) + throw jexception(jerrno::JERR_JCNTL_READONLY, "jcntl", fn_name); + if (_stop_flag) + throw jexception(jerrno::JERR_JCNTL_STOPPED, "jcntl", fn_name); +} + +void +jcntl::check_rstatus(const char* fn_name) const +{ + if (!_init_flag) + throw jexception(jerrno::JERR__NINIT, "jcntl", fn_name); + if (_stop_flag) + throw jexception(jerrno::JERR_JCNTL_STOPPED, "jcntl", fn_name); +} + +void +jcntl::write_infofile() const +{ + timespec ts; + if (::clock_gettime(CLOCK_REALTIME, &ts)) + { + std::ostringstream oss; + oss << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR__RTCLOCK, oss.str(), "jcntl", "write_infofile"); + } + jinf ji(_jid, _jdir.dirname(), _base_filename, _lpmgr.num_jfiles(), _lpmgr.is_ae(), _lpmgr.ae_max_jfiles(), + _jfsize_sblks, _wmgr.cache_pgsize_sblks(), _wmgr.cache_num_pages(), ts); + ji.write(); +} + +void +jcntl::aio_cmpl_wait() +{ + //while (_wmgr.get_aio_evt_rem()) + while (true) + { + u_int32_t aer; + { + slock s(_wr_mutex); + aer = _wmgr.get_aio_evt_rem(); + } + if (aer == 0) break; // no events left + if (get_wr_events(&_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT) + throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "aio_cmpl_wait"); + } +} + +bool +jcntl::handle_aio_wait(const iores res, iores& resout, const data_tok* dtp) +{ + resout = res; + if (res == RHM_IORES_PAGE_AIOWAIT) + { + while (_wmgr.curr_pg_blocked()) + { + if (_wmgr.get_events(pmgr::UNUSED, &_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT) + { + std::ostringstream oss; + oss << "get_events() returned JERR_JCNTL_AIOCMPLWAIT; wmgr_status: " << _wmgr.status_str(); + this->log(LOG_CRITICAL, oss.str()); + throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "handle_aio_wait"); + } + } + return true; + } + else if (res == RHM_IORES_FILE_AIOWAIT) + { + while (_wmgr.curr_file_blocked()) + { + if (_wmgr.get_events(pmgr::UNUSED, &_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT) + { + std::ostringstream oss; + oss << "get_events() returned JERR_JCNTL_AIOCMPLWAIT; wmgr_status: " << _wmgr.status_str(); + this->log(LOG_CRITICAL, oss.str()); + throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "handle_aio_wait"); + } + } + _wrfc.wr_reset(); + resout = RHM_IORES_SUCCESS; + data_tok::write_state ws = dtp->wstate(); + return ws == data_tok::ENQ_PART || ws == data_tok::DEQ_PART || ws == data_tok::ABORT_PART || + ws == data_tok::COMMIT_PART; + } + return false; +} + +void +jcntl::rcvr_janalyze(rcvdat& rd, const std::vector<std::string>* prep_txn_list_ptr) +{ + jinf ji(_jdir.dirname() + "/" + _base_filename + "." + JRNL_INFO_EXTENSION, true); + + // If the number of files does not tie up with the jinf file from the journal being recovered, + // use the jinf data. + if (rd._njf != ji.num_jfiles()) + { + std::ostringstream oss; + oss << "Recovery found " << ji.num_jfiles() << + " files (different from --num-jfiles value of " << rd._njf << ")."; + this->log(LOG_WARN, oss.str()); + rd._njf = ji.num_jfiles(); + _rcvdat._enq_cnt_list.resize(rd._njf); + } + _emap.set_num_jfiles(rd._njf); + _tmap.set_num_jfiles(rd._njf); + if (_jfsize_sblks != ji.jfsize_sblks()) + { + std::ostringstream oss; + oss << "Recovery found file size = " << (ji.jfsize_sblks() / JRNL_RMGR_PAGE_SIZE) << + " (different from --jfile-size-pgs value of " << + (_jfsize_sblks / JRNL_RMGR_PAGE_SIZE) << ")."; + this->log(LOG_WARN, oss.str()); + _jfsize_sblks = ji.jfsize_sblks(); + } + if (_jdir.dirname().compare(ji.jdir())) + { + std::ostringstream oss; + oss << "Journal file location change: original = \"" << ji.jdir() << + "\"; current = \"" << _jdir.dirname() << "\""; + this->log(LOG_WARN, oss.str()); + ji.set_jdir(_jdir.dirname()); + } + + try + { + rd._ffid = ji.get_first_pfid(); + rd._lfid = ji.get_last_pfid(); + rd._owi = ji.get_initial_owi(); + rd._frot = ji.get_frot(); + rd._jempty = false; + ji.get_normalized_pfid_list(rd._fid_list); // _pfid_list + } + catch (const jexception& e) + { + if (e.err_code() != jerrno::JERR_JINF_JDATEMPTY) throw; + } + + // Restore all read and write pointers and transactions + if (!rd._jempty) + { + u_int16_t fid = rd._ffid; + std::ifstream ifs; + bool lowi = rd._owi; // local copy of owi to be used during analysis + while (rcvr_get_next_record(fid, &ifs, lowi, rd)) ; + if (ifs.is_open()) ifs.close(); + + // Remove all txns from tmap that are not in the prepared list + if (prep_txn_list_ptr) + { + std::vector<std::string> xid_list; + _tmap.xid_list(xid_list); + for (std::vector<std::string>::iterator itr = xid_list.begin(); itr != xid_list.end(); itr++) + { + std::vector<std::string>::const_iterator pitr = + std::find(prep_txn_list_ptr->begin(), prep_txn_list_ptr->end(), *itr); + if (pitr == prep_txn_list_ptr->end()) // not found in prepared list + { + txn_data_list tdl = _tmap.get_remove_tdata_list(*itr); // tdl will be empty if xid not found + // Unlock any affected enqueues in emap + for (tdl_itr i=tdl.begin(); i<tdl.end(); i++) + { + if (i->_enq_flag) // enq op - decrement enqueue count + rd._enq_cnt_list[i->_pfid]--; + else if (_emap.is_enqueued(i->_drid, true)) // deq op - unlock enq record + { + int16_t ret = _emap.unlock(i->_drid); + if (ret < enq_map::EMAP_OK) // fail + { + // enq_map::unlock()'s only error is enq_map::EMAP_RID_NOT_FOUND + std::ostringstream oss; + oss << std::hex << "_emap.unlock(): drid=0x\"" << i->_drid; + throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "jcntl", "rcvr_janalyze"); + } + } + } + } + } + } + + // Check for file full condition - add one to _jfsize_sblks to account for file header + rd._lffull = rd._eo == (1 + _jfsize_sblks) * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE; + + // Check for journal full condition + u_int16_t next_wr_fid = (rd._lfid + 1) % rd._njf; + rd._jfull = rd._ffid == next_wr_fid && rd._enq_cnt_list[next_wr_fid] && rd._lffull; + } +} + +bool +jcntl::rcvr_get_next_record(u_int16_t& fid, std::ifstream* ifsp, bool& lowi, rcvdat& rd) +{ + std::size_t cum_size_read = 0; + void* xidp = 0; + rec_hdr h; + + bool hdr_ok = false; + std::streampos file_pos; + while (!hdr_ok) + { + if (!ifsp->is_open()) + { + if (!jfile_cycle(fid, ifsp, lowi, rd, true)) + return false; + } + file_pos = ifsp->tellg(); + ifsp->read((char*)&h, sizeof(rec_hdr)); + if (ifsp->gcount() == sizeof(rec_hdr)) + hdr_ok = true; + else + { + if (!jfile_cycle(fid, ifsp, lowi, rd, true)) + return false; + } + } + + switch(h._magic) + { + case RHM_JDAT_ENQ_MAGIC: + { + enq_rec er; + u_int16_t start_fid = fid; // fid may increment in decode() if record folds over file boundary + if (!decode(er, fid, ifsp, cum_size_read, h, lowi, rd, file_pos)) + return false; + if (!er.is_transient()) // Ignore transient msgs + { + rd._enq_cnt_list[start_fid]++; + if (er.xid_size()) + { + er.get_xid(&xidp); + assert(xidp != 0); + std::string xid((char*)xidp, er.xid_size()); + _tmap.insert_txn_data(xid, txn_data(h._rid, 0, start_fid, true)); + if (_tmap.set_aio_compl(xid, h._rid) < txn_map::TMAP_OK) // fail - xid or rid not found + { + std::ostringstream oss; + oss << std::hex << "_tmap.set_aio_compl: txn_enq xid=\"" << xid << "\" rid=0x" << h._rid; + throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "jcntl", "rcvr_get_next_record"); + } + std::free(xidp); + } + else + { + if (_emap.insert_pfid(h._rid, start_fid) < enq_map::EMAP_OK) // fail + { + // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID. + std::ostringstream oss; + oss << std::hex << "rid=0x" << h._rid << " _pfid=0x" << start_fid; + throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "jcntl", "rcvr_get_next_record"); + } + } + } + } + break; + case RHM_JDAT_DEQ_MAGIC: + { + deq_rec dr; + u_int16_t start_fid = fid; // fid may increment in decode() if record folds over file boundary + if (!decode(dr, fid, ifsp, cum_size_read, h, lowi, rd, file_pos)) + return false; + if (dr.xid_size()) + { + // If the enqueue is part of a pending txn, it will not yet be in emap + _emap.lock(dr.deq_rid()); // ignore not found error + dr.get_xid(&xidp); + assert(xidp != 0); + std::string xid((char*)xidp, dr.xid_size()); + _tmap.insert_txn_data(xid, txn_data(dr.rid(), dr.deq_rid(), start_fid, false, + dr.is_txn_coml_commit())); + if (_tmap.set_aio_compl(xid, dr.rid()) < txn_map::TMAP_OK) // fail - xid or rid not found + { + std::ostringstream oss; + oss << std::hex << "_tmap.set_aio_compl: txn_deq xid=\"" << xid << "\" rid=0x" << dr.rid(); + throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "jcntl", "rcvr_get_next_record"); + } + std::free(xidp); + } + else + { + int16_t enq_fid = _emap.get_remove_pfid(dr.deq_rid(), true); + if (enq_fid >= enq_map::EMAP_OK) // ignore not found error + rd._enq_cnt_list[enq_fid]--; + } + } + break; + case RHM_JDAT_TXA_MAGIC: + { + txn_rec ar; + if (!decode(ar, fid, ifsp, cum_size_read, h, lowi, rd, file_pos)) + return false; + // Delete this txn from tmap, unlock any locked records in emap + ar.get_xid(&xidp); + assert(xidp != 0); + std::string xid((char*)xidp, ar.xid_size()); + txn_data_list tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found + for (tdl_itr itr = tdl.begin(); itr != tdl.end(); itr++) + { + if (itr->_enq_flag) + rd._enq_cnt_list[itr->_pfid]--; + else + _emap.unlock(itr->_drid); // ignore not found error + } + std::free(xidp); + } + break; + case RHM_JDAT_TXC_MAGIC: + { + txn_rec cr; + if (!decode(cr, fid, ifsp, cum_size_read, h, lowi, rd, file_pos)) + return false; + // Delete this txn from tmap, process records into emap + cr.get_xid(&xidp); + assert(xidp != 0); + std::string xid((char*)xidp, cr.xid_size()); + txn_data_list tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found + for (tdl_itr itr = tdl.begin(); itr != tdl.end(); itr++) + { + if (itr->_enq_flag) // txn enqueue + { + if (_emap.insert_pfid(itr->_rid, itr->_pfid) < enq_map::EMAP_OK) // fail + { + // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID. + std::ostringstream oss; + oss << std::hex << "rid=0x" << itr->_rid << " _pfid=0x" << itr->_pfid; + throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "jcntl", "rcvr_get_next_record"); + } + } + else // txn dequeue + { + int16_t enq_fid = _emap.get_remove_pfid(itr->_drid, true); + if (enq_fid >= enq_map::EMAP_OK) + rd._enq_cnt_list[enq_fid]--; + } + } + std::free(xidp); + } + break; + case RHM_JDAT_EMPTY_MAGIC: + { + u_int32_t rec_dblks = jrec::size_dblks(sizeof(rec_hdr)); + ifsp->ignore(rec_dblks * JRNL_DBLK_SIZE - sizeof(rec_hdr)); + assert(!ifsp->fail() && !ifsp->bad()); + if (!jfile_cycle(fid, ifsp, lowi, rd, false)) + return false; + } + break; + case 0: + check_journal_alignment(fid, file_pos, rd); + return false; + default: + // Stop as this is the overwrite boundary. + check_journal_alignment(fid, file_pos, rd); + return false; + } + return true; +} + +bool +jcntl::decode(jrec& rec, u_int16_t& fid, std::ifstream* ifsp, std::size_t& cum_size_read, + rec_hdr& h, bool& lowi, rcvdat& rd, std::streampos& file_offs) +{ + u_int16_t start_fid = fid; + std::streampos start_file_offs = file_offs; + if (!check_owi(fid, h, lowi, rd, file_offs)) + return false; + bool done = false; + while (!done) + { + try { done = rec.rcv_decode(h, ifsp, cum_size_read); } + catch (const jexception& e) + { +// TODO - review this logic and tidy up how rd._lfid is assigned. See new jinf.get_end_file() fn. +// Original +// if (e.err_code() != jerrno::JERR_JREC_BADRECTAIL || +// fid != (rd._ffid ? rd._ffid - 1 : _num_jfiles - 1)) throw; +// Tried this, but did not work +// if (e.err_code() != jerrno::JERR_JREC_BADRECTAIL || h._magic != 0) throw; + check_journal_alignment(start_fid, start_file_offs, rd); +// rd._lfid = start_fid; + return false; + } + if (!done && !jfile_cycle(fid, ifsp, lowi, rd, false)) + { + check_journal_alignment(start_fid, start_file_offs, rd); + return false; + } + } + return true; +} + +bool +jcntl::jfile_cycle(u_int16_t& fid, std::ifstream* ifsp, bool& lowi, rcvdat& rd, const bool jump_fro) +{ + if (ifsp->is_open()) + { + if (ifsp->eof() || !ifsp->good()) + { + ifsp->clear(); + rd._eo = ifsp->tellg(); // remember file offset before closing + assert(rd._eo != std::numeric_limits<std::size_t>::max()); // Check for error code -1 + ifsp->close(); + if (++fid >= rd._njf) + { + fid = 0; + lowi = !lowi; // Flip local owi + } + if (fid == rd._ffid) // used up all journal files + return false; + } + } + if (!ifsp->is_open()) + { + std::ostringstream oss; + oss << _jdir.dirname() << "/" << _base_filename << "."; + oss << std::hex << std::setfill('0') << std::setw(4) << fid << "." << JRNL_DATA_EXTENSION; + ifsp->clear(); // clear eof flag, req'd for older versions of c++ + ifsp->open(oss.str().c_str(), std::ios_base::in | std::ios_base::binary); + if (!ifsp->good()) + throw jexception(jerrno::JERR__FILEIO, oss.str(), "jcntl", "jfile_cycle"); + + // Read file header + file_hdr fhdr; + ifsp->read((char*)&fhdr, sizeof(fhdr)); + assert(ifsp->good()); + if (fhdr._magic == RHM_JDAT_FILE_MAGIC) + { + assert(fhdr._lfid == fid); + if (!rd._fro) + rd._fro = fhdr._fro; + std::streamoff foffs = jump_fro ? fhdr._fro : JRNL_DBLK_SIZE * JRNL_SBLK_SIZE; + ifsp->seekg(foffs); + } + else + { + ifsp->close(); + return false; + } + } + return true; +} + +bool +jcntl::check_owi(const u_int16_t fid, rec_hdr& h, bool& lowi, rcvdat& rd, std::streampos& file_pos) +{ + if (rd._ffid ? h.get_owi() == lowi : h.get_owi() != lowi) // Overwrite indicator changed + { + u_int16_t expected_fid = rd._ffid ? rd._ffid - 1 : rd._njf - 1; + if (fid == expected_fid) + { + check_journal_alignment(fid, file_pos, rd); + return false; + } + std::ostringstream oss; + oss << std::hex << std::setfill('0') << "Magic=0x" << std::setw(8) << h._magic; + oss << " fid=0x" << std::setw(4) << fid << " rid=0x" << std::setw(8) << h._rid; + oss << " foffs=0x" << std::setw(8) << file_pos; + oss << " expected_fid=0x" << std::setw(4) << expected_fid; + throw jexception(jerrno::JERR_JCNTL_OWIMISMATCH, oss.str(), "jcntl", + "check_owi"); + } + if (rd._h_rid == 0) + rd._h_rid = h._rid; + else if (h._rid - rd._h_rid < 0x8000000000000000ULL) // RFC 1982 comparison for unsigned 64-bit + rd._h_rid = h._rid; + return true; +} + + +void +jcntl::check_journal_alignment(const u_int16_t fid, std::streampos& file_pos, rcvdat& rd) +{ + unsigned sblk_offs = file_pos % (JRNL_DBLK_SIZE * JRNL_SBLK_SIZE); + if (sblk_offs) + { + { + std::ostringstream oss; + oss << std::hex << "Bad record alignment found at fid=0x" << fid; + oss << " offs=0x" << file_pos << " (likely journal overwrite boundary); " << std::dec; + oss << (JRNL_SBLK_SIZE - (sblk_offs/JRNL_DBLK_SIZE)) << " filler record(s) required."; + this->log(LOG_WARN, oss.str()); + } + const u_int32_t xmagic = RHM_JDAT_EMPTY_MAGIC; + std::ostringstream oss; + oss << _jdir.dirname() << "/" << _base_filename << "."; + oss << std::hex << std::setfill('0') << std::setw(4) << fid << "." << JRNL_DATA_EXTENSION; + std::ofstream ofsp(oss.str().c_str(), + std::ios_base::in | std::ios_base::out | std::ios_base::binary); + if (!ofsp.good()) + throw jexception(jerrno::JERR__FILEIO, oss.str(), "jcntl", "check_journal_alignment"); + ofsp.seekp(file_pos); + void* buff = std::malloc(JRNL_DBLK_SIZE); + assert(buff != 0); + std::memcpy(buff, (const void*)&xmagic, sizeof(xmagic)); + // Normally, RHM_CLEAN must be set before these fills are done, but this is a recover + // situation (i.e. performance is not an issue), and it makes the location of the write + // clear should inspection of the file be required. + std::memset((char*)buff + sizeof(xmagic), RHM_CLEAN_CHAR, JRNL_DBLK_SIZE - sizeof(xmagic)); + + while (file_pos % (JRNL_DBLK_SIZE * JRNL_SBLK_SIZE)) + { + ofsp.write((const char*)buff, JRNL_DBLK_SIZE); + assert(!ofsp.fail()); + std::ostringstream oss; + oss << std::hex << "Recover phase write: Wrote filler record: fid=0x" << fid << " offs=0x" << file_pos; + this->log(LOG_NOTICE, oss.str()); + file_pos = ofsp.tellp(); + } + ofsp.close(); + std::free(buff); + rd._lfid = fid; + if (!rd._frot) + rd._ffid = (fid + 1) % rd._njf; + this->log(LOG_INFO, "Bad record alignment fixed."); + } + rd._eo = file_pos; +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/jcntl.h b/cpp/src/qpid/legacystore/jrnl/jcntl.h new file mode 100644 index 0000000000..294e9ced05 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jcntl.h @@ -0,0 +1,722 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jcntl.h + * + * Qpid asynchronous store plugin library + * + * Messaging journal top-level control and interface class + * mrg::journal::jcntl. See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_JCNTL_H +#define QPID_LEGACYSTORE_JRNL_JCNTL_H + +namespace mrg +{ +namespace journal +{ + class jcntl; +} +} + +#include <cstddef> +#include <deque> +#include "qpid/legacystore/jrnl/jdir.h" +#include "qpid/legacystore/jrnl/fcntl.h" +#include "qpid/legacystore/jrnl/lpmgr.h" +#include "qpid/legacystore/jrnl/rcvdat.h" +#include "qpid/legacystore/jrnl/slock.h" +#include "qpid/legacystore/jrnl/smutex.h" +#include "qpid/legacystore/jrnl/rmgr.h" +#include "qpid/legacystore/jrnl/wmgr.h" +#include "qpid/legacystore/jrnl/wrfc.h" + +namespace mrg +{ +namespace journal +{ + + /** + * \brief Access and control interface for the journal. This is the top-level class for the + * journal. + * + * This is the top-level journal class; one instance of this class controls one instance of the + * journal and all its files and associated control structures. Besides this class, the only + * other class that needs to be used at a higher level is the data_tok class, one instance of + * which is used per data block written to the journal, and is used to track its status through + * the AIO enqueue, read and dequeue process. + */ + class jcntl + { + protected: + /** + * \brief Journal ID + * + * This string uniquely identifies this journal instance. It will most likely be associated + * with the identity of the message queue with which it is associated. + */ + // TODO: This is not included in any files at present, add to file_hdr? + std::string _jid; + + /** + * \brief Journal directory + * + * This string stores the path to the journal directory. It may be absolute or relative, and + * should not end in a file separator character. (e.g. "/fastdisk/jdata" is correct, + * "/fastdisk/jdata/" is not.) + */ + jdir _jdir; + + /** + * \brief Base filename + * + * This string contains the base filename used for the journal files. The filenames will + * start with this base, and have various sections added to it to derive the final file names + * that will be written to disk. No file separator characters should be included here, but + * all other legal filename characters are valid. + */ + std::string _base_filename; + + /** + * \brief Initialized flag + * + * This flag starts out set to false, is set to true once this object has been initialized, + * either by calling initialize() or recover(). + */ + bool _init_flag; + + /** + * \brief Stopped flag + * + * This flag starts out false, and is set to true when stop() is called. At this point, the + * journal will no longer accept messages until either initialize() or recover() is called. + * There is no way other than through initialization to reset this flag. + */ + // TODO: It would be helpful to distinguish between states stopping and stopped. If stop(true) is called, + // then we are stopping, but must wait for all outstanding aios to return before being finally stopped. During + // this period, however, no new enqueue/dequeue/read requests may be accepted. + bool _stop_flag; + + /** + * \brief Read-only state flag used during recover. + * + * When true, this flag prevents journal write operations (enqueue and dequeue), but + * allows read to occur. It is used during recovery, and is reset when recovered() is + * called. + */ + bool _readonly_flag; + + /** + * \brief If set, calls stop() if the jouranl write pointer overruns dequeue low water + * marker. If not set, then attempts to write will throw exceptions until the journal + * file low water marker moves to the next journal file. + */ + bool _autostop; ///< Autostop flag - stops journal when overrun occurs + + // Journal control structures + u_int32_t _jfsize_sblks; ///< Journal file size in sblks + lpmgr _lpmgr; ///< LFID-PFID manager tracks inserted journal files + enq_map _emap; ///< Enqueue map for low water mark management + txn_map _tmap; ///< Transaction map open transactions + rrfc _rrfc; ///< Read journal rotating file controller + wrfc _wrfc; ///< Write journal rotating file controller + rmgr _rmgr; ///< Read page manager which manages AIO + wmgr _wmgr; ///< Write page manager which manages AIO + rcvdat _rcvdat; ///< Recovery data used for recovery + smutex _wr_mutex; ///< Mutex for journal writes + + public: + static timespec _aio_cmpl_timeout; ///< Timeout for blocking libaio returns + static timespec _final_aio_cmpl_timeout; ///< Timeout for blocking libaio returns when stopping or finalizing + + /** + * \brief Journal constructor. + * + * Constructor which sets the physical file location and base name. + * + * \param jid A unique identifier for this journal instance. + * \param jdir The directory which will contain the journal files. + * \param base_filename The string which will be used to start all journal filenames. + */ + jcntl(const std::string& jid, const std::string& jdir, const std::string& base_filename); + + /** + * \brief Destructor. + */ + virtual ~jcntl(); + + inline const std::string& id() const { return _jid; } + inline const std::string& jrnl_dir() const { return _jdir.dirname(); } + + /** + * \brief Initialize the journal for storing data. + * + * Initialize the journal by creating new journal data files and initializing internal + * control structures. When complete, the journal will be empty, and ready to store data. + * + * <b>NOTE: Any existing journal will be ignored by this operation.</b> To use recover + * the data from an existing journal, use recover(). + * + * <b>NOTE: If <i>NULL</i> is passed to the deque pointers, they will be internally created + * and deleted.</b> + * + * <b>NOTE: If <i>NULL</i> is passed to the callbacks, internal default callbacks will be + * used.</b> + * + * \param num_jfiles The number of journal files to be created. + * \param auto_expand If true, allows journal file auto-expansion. In this mode, the journal will automatically + * add files to the journal if it runs out of space. No more than ae_max_jfiles may be added. If false, then + * no files are added and an exception will be thrown if the journal runs out of file space. + * \param ae_max_jfiles Upper limit of journal files for auto-expand mode. When auto_expand is true, this is the + * maximum total number of files allowed in the journal (original plus those added by auto-expand mode). If + * this number of files exist and the journal runs out of space, an exception will be thrown. This number + * must be greater than the num_jfiles parameter value but cannot exceed the maximum number of files for a + * single journal; if num_jfiles is already at its maximum value, then auto-expand will be disabled. + * \param jfsize_sblks The size of each journal file expressed in softblocks. + * \param wcache_num_pages The number of write cache pages to create. + * \param wcache_pgsize_sblks The size in sblks of each write cache page. + * \param cbp Pointer to object containing callback functions for read and write operations. May be 0 (NULL). + * + * \exception TODO + */ + void initialize(const u_int16_t num_jfiles, const bool auto_expand, const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks, + aio_callback* const cbp); + + /** + * /brief Initialize journal by recovering state from previously written journal. + * + * Initialize journal by recovering state from previously written journal. The journal files + * are analyzed, and all records that have not been dequeued and that remain in the journal + * will be available for reading. The journal is placed in a read-only state until + * recovered() is called; any calls to enqueue or dequeue will fail with an exception + * in this state. + * + * <b>NOTE: If <i>NULL</i> is passed to the deque pointers, they will be internally created + * and deleted.</b> + * + * <b>NOTE: If <i>NULL</i> is passed to the callbacks, internal default callbacks will be + * used.</b> + * + * \param num_jfiles The number of journal files to be created. + * \param auto_expand If true, allows journal file auto-expansion. In this mode, the journal will automatically + * add files to the journal if it runs out of space. No more than ae_max_jfiles may be added. If false, then + * no files are added and an exception will be thrown if the journal runs out of file space. + * \param ae_max_jfiles Upper limit of journal files for auto-expand mode. When auto_expand is true, this is the + * maximum total number of files allowed in the journal (original plus those added by auto-expand mode). If + * this number of files exist and the journal runs out of space, an exception will be thrown. This number + * must be greater than the num_jfiles parameter value but cannot exceed the maximum number of files for a + * single journal; if num_jfiles is already at its maximum value, then auto-expand will be disabled. + * \param jfsize_sblks The size of each journal file expressed in softblocks. + * \param wcache_num_pages The number of write cache pages to create. + * \param wcache_pgsize_sblks The size in sblks of each write cache page. + * \param cbp Pointer to object containing callback functions for read and write operations. May be 0 (NULL). + * \param prep_txn_list_ptr + * \param highest_rid Returns the highest rid found in the journal during recover + * + * \exception TODO + */ + void recover(const u_int16_t num_jfiles, const bool auto_expand, const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks, + aio_callback* const cbp, const std::vector<std::string>* prep_txn_list_ptr, u_int64_t& highest_rid); + + /** + * \brief Notification to the journal that recovery is complete and that normal operation + * may resume. + * + * This call notifies the journal that recovery is complete and that normal operation + * may resume. The read pointers are reset so that all records read as a part of recover + * may be re-read during normal operation. The read-only flag is then reset, allowing + * enqueue and dequeue operations to resume. + * + * \exception TODO + */ + void recover_complete(); + + /** + * \brief Stops journal and deletes all journal files. + * + * Clear the journal directory of all journal files matching the base filename. + * + * \exception TODO + */ + void delete_jrnl_files(); + + /** + * \brief Enqueue data. + * + * Enqueue data or part thereof. If a large data block is being written, then it may be + * enqueued in parts by setting this_data_len to the size of the data being written in this + * call. The total data size must be known in advance, however, as this is written into the + * record header on the first record write. The state of the write (i.e. how much has been + * written so far) is maintained in the data token dtokp. Partial writes will return in state + * ENQ_PART. + * + * Note that a return value of anything other than RHM_IORES_SUCCESS implies that this write + * operation did not complete successfully or was partially completed. The action taken under + * these conditions depends on the value of the return. For example, RHM_IORES_AIO_WAIT + * implies that all pages in the write page cache are waiting for AIO operations to return, + * and that the call should be remade after waiting a bit. + * + * Example: If a write of 99 kB is divided into three equal parts, then the following states + * and returns would characterize a successful operation: + * <pre> + * dtok. dtok. dtok. + * Pperation Return wstate() dsize() written() Comment + * -----------------+--------+--------+-------+---------+------------------------------------ + * NONE 0 0 Value of dtok before op + * edr(99000, 33000) SUCCESS ENQ_PART 99000 33000 Enqueue part 1 + * edr(99000, 33000) AIO_WAIT ENQ_PART 99000 50000 Enqueue part 2, not completed + * edr(99000, 33000) SUCCESS ENQ_PART 99000 66000 Enqueue part 2 again + * edr(99000, 33000) SUCCESS ENQ 99000 99000 Enqueue part 3 + * </pre> + * + * \param data_buff Pointer to data to be enqueued for this enqueue operation. + * \param tot_data_len Total data length. + * \param this_data_len Amount to be written in this enqueue operation. + * \param dtokp Pointer to data token which contains the details of the enqueue operation. + * \param transient Flag indicating transient persistence (ie, ignored on recover). + * + * \exception TODO + */ + iores enqueue_data_record(const void* const data_buff, const std::size_t tot_data_len, + const std::size_t this_data_len, data_tok* dtokp, const bool transient = false); + + iores enqueue_extern_data_record(const std::size_t tot_data_len, data_tok* dtokp, + const bool transient = false); + + /** + * \brief Enqueue data. + * + * \param data_buff Pointer to data to be enqueued for this enqueue operation. + * \param tot_data_len Total data length. + * \param this_data_len Amount to be written in this enqueue operation. + * \param dtokp Pointer to data token which contains the details of the enqueue operation. + * \param xid String containing xid. An empty string (i.e. length=0) will be considered + * non-transactional. + * \param transient Flag indicating transient persistence (ie, ignored on recover). + * + * \exception TODO + */ + iores enqueue_txn_data_record(const void* const data_buff, const std::size_t tot_data_len, + const std::size_t this_data_len, data_tok* dtokp, const std::string& xid, + const bool transient = false); + iores enqueue_extern_txn_data_record(const std::size_t tot_data_len, data_tok* dtokp, + const std::string& xid, const bool transient = false); + + /* TODO + ** + * \brief Retrieve details of next record to be read without consuming the record. + * + * Retrieve information about current read record. A pointer to the data is returned, along + * with the data size and available data size. Data is considered "available" when the AIO + * operations to fill page-cache pages from disk have returned, and is ready for consumption. + * + * If <i>dsize_avail</i> < <i>dsize</i>, then not all of the data is available or part of + * the data is in non-contiguous memory, and a subsequent call will update both the pointer + * and <i>dsize_avail</i> if more pages have returned from AIO. + * + * The <i>dsize_avail</i> parameter will return the amount of data from this record that is + * available in the page cache as contiguous memory, even if it spans page cache boundaries. + * However, if a record spans the end of the page cache and continues at the beginning, even + * if both parts are ready for consumption, then this must be divided into at least two + * get_data_record() operations, as the data is contained in at least two non-contiguous + * segments of the page cache. + * + * Once all the available data for a record is exposed, it can not be read again using + * this function. It must be consumed prior to getting the next record. This can be done by + * calling discard_data_record() or read_data_record(). However, if parameter + * <i>auto_discard</i> is set to <b><i>true</i></b>, then this record will be automatically + * consumed when the entire record has become available without having to explicitly call + * discard_next_data_record() or read_data_record(). + * + * If the current record is an open transactional record, then it cannot be read until it is + * committed. If it is aborted, it can never be read. Under this condition, get_data_record() + * will return RHM_IORES_TXPENDING, the data pointer will be set to NULL and all data + * lengths will be set to 0. + * + * Example: Read a record of 30k. Assume a read page cache of 10 pages of size 10k starting + * at address base_ptr (page0 = base_ptr, page1 = page_ptr+10k, etc.). The first 15k of + * the record falls at the end of the page cache, the remaining 15k folded to the beginning. + * The current page (page 8) containing 5k is available, the remaining pages which contain + * this record are pending AIO return: + * <pre> + * call dsize + * no. dsize avail data ptr Return Comment + * ----+-----+-----+------------+--------+-------------------------------------------------- + * 1 30k 5k base_ptr+85k SUCCESS Initial call, read first 5k + * 2 30k 0k base_ptr+90k AIO_WAIT AIO still pending; no further pages avail + * 3 30k 10k base_ptr+90k SUCCESS AIO now returned; now read till end of page cache + * 4 30k 15k base_ptr SUCCESS data_ptr now pointing to start of page cache + * </pre> + * + * \param rid Reference that returns the record ID (rid) + * \param dsize Reference that returns the total data size of the record data . + * \param dsize_avail Reference that returns the amount of the data that is available for + * consumption. + * \param data Pointer to data pointer which will point to the first byte of the next record + * data. + * \param auto_discard If <b><i>true</i></b>, automatically discard the record being read if + * the entire record is available (i.e. dsize == dsize_avail). Otherwise + * discard_next_data_record() must be explicitly called. + * + * \exception TODO + * + // *** NOT YET IMPLEMENTED *** + iores get_data_record(const u_int64_t& rid, const std::size_t& dsize, + const std::size_t& dsize_avail, const void** const data, bool auto_discard = false); + */ + + /* TODO + ** + * \brief Discard (skip) next record to be read without reading or retrieving it. + * + * \exception TODO + * + // *** NOT YET IMPLEMENTED *** + iores discard_data_record(data_tok* const dtokp); + */ + + /** + * \brief Reads data from the journal. It is the responsibility of the reader to free + * the memory that is allocated through this call - see below for details. + * + * Reads the next non-dequeued data record from the journal. + * + * <b>Note</b> that this call allocates memory into which the data and XID are copied. It + * is the responsibility of the caller to free this memory. The memory for the data and + * XID are allocated in a single call, and the XID precedes the data in the memory space. + * Thus, where an XID exists, freeing the XID pointer will free both the XID and data memory. + * However, if an XID does not exist for the message, the XID pointer xidpp is set to NULL, + * and it is the data pointer datapp that must be freed. Should neither an XID nor data be + * present (ie an empty record), then no memory is allocated, and both pointers will be NULL. + * In this case, there is no need to free memory. + * + * TODO: Fix this lousy interface. The caller should NOT be required to clean up these + * pointers! Rather use a struct, or better still, let the data token carry the data and + * xid pointers and lengths, and have the data token both allocate and delete. + * + * \param datapp Pointer to pointer that will be set to point to memory allocated and + * containing the data. Will be set to NULL if the call fails or there is no data + * in the record. + * \param dsize Ref that will be set to the size of the data. Will be set to 0 if the call + * fails or if there is no data in the record. + * \param xidpp Pointer to pointer that will be set to point to memory allocated and + * containing the XID. Will be set to NULL if the call fails or there is no XID attached + * to this record. + * \param xidsize Ref that will be set to the size of the XID. + * \param transient Ref that will be set true if record is transient. + * \param external Ref that will be set true if record is external. In this case, the data + * pointer datapp will be set to NULL, but dsize will contain the size of the data. + * NOTE: If there is an xid, then xidpp must be freed. + * \param dtokp Pointer to data_tok instance for this data, used to track state of data + * through journal. + * \param ignore_pending_txns When false (default), if the next record to be read is locked + * by a pending transaction, the read fails with RHM_IORES_TXPENDING. However, if set + * to true, then locks are ignored. This is required for reading of the Transaction + * Prepared List (TPL) which may have its entries locked, but may be read from + * time-to-time, and needs all its records (locked and unlocked) to be available. + * + * \exception TODO + */ + iores read_data_record(void** const datapp, std::size_t& dsize, void** const xidpp, + std::size_t& xidsize, bool& transient, bool& external, data_tok* const dtokp, + bool ignore_pending_txns = false); + + /** + * \brief Dequeues (marks as no longer needed) data record in journal. + * + * Dequeues (marks as no longer needed) data record in journal. Note that it is possible + * to use the same data token instance used to enqueue this data; it contains the record ID + * needed to correctly mark this data as dequeued in the journal. Otherwise the RID of the + * record to be dequeued and the write state of ENQ must be manually set in a new or reset + * instance of data_tok. + * + * \param dtokp Pointer to data_tok instance for this data, used to track state of data + * through journal. + * \param txn_coml_commit Only used for preparedXID journal. When used for dequeueing + * prepared XID list items, sets whether the complete() was called in commit or abort + * mode. + * + * \exception TODO + */ + iores dequeue_data_record(data_tok* const dtokp, const bool txn_coml_commit = false); + + /** + * \brief Dequeues (marks as no longer needed) data record in journal. + * + * Dequeues (marks as no longer needed) data record in journal as part of a transaction. + * Note that it is possible to use the same data token instance used to enqueue this data; + * it contains the RID needed to correctly mark this data as dequeued in the journal. + * Otherwise the RID of the record to be dequeued and the write state of ENQ must be + * manually set in a new or reset instance of data_tok. + * + * \param dtokp Pointer to data_tok instance for this data, used to track state of data + * through journal. + * \param xid String containing xid. An empty string (i.e. length=0) will be considered + * non-transactional. + * \param txn_coml_commit Only used for preparedXID journal. When used for dequeueing + * prepared XID list items, sets whether the complete() was called in commit or abort + * mode. + * + * \exception TODO + */ + iores dequeue_txn_data_record(data_tok* const dtokp, const std::string& xid, const bool txn_coml_commit = false); + + /** + * \brief Abort the transaction for all records enqueued or dequeued with the matching xid. + * + * Abort the transaction for all records enqueued with the matching xid. All enqueued records + * are effectively deleted from the journal, and can not be read. All dequeued records remain + * as though they had never been dequeued. + * + * \param dtokp Pointer to data_tok instance for this data, used to track state of data + * through journal. + * \param xid String containing xid. + * + * \exception TODO + */ + iores txn_abort(data_tok* const dtokp, const std::string& xid); + + /** + * \brief Commit the transaction for all records enqueued or dequeued with the matching xid. + * + * Commit the transaction for all records enqueued with the matching xid. All enqueued + * records are effectively released for reading and dequeueing. All dequeued records are + * removed and can no longer be accessed. + * + * \param dtokp Pointer to data_tok instance for this data, used to track state of data + * through journal. + * \param xid String containing xid. + * + * \exception TODO + */ + iores txn_commit(data_tok* const dtokp, const std::string& xid); + + /** + * \brief Check whether all the enqueue records for the given xid have reached disk. + * + * \param xid String containing xid. + * + * \exception TODO + */ + bool is_txn_synced(const std::string& xid); + + /** + * \brief Forces a check for returned AIO write events. + * + * Forces a check for returned AIO write events. This is normally performed by enqueue() and + * dequeue() operations, but if these operations cease, then this call needs to be made to + * force the processing of any outstanding AIO operations. + */ + int32_t get_wr_events(timespec* const timeout); + + /** + * \brief Forces a check for returned AIO read events. + * + * Forces a check for returned AIO read events. This is normally performed by read_data() + * operations, but if these operations cease, then this call needs to be made to force the + * processing of any outstanding AIO operations. + */ + int32_t get_rd_events(timespec* const timeout); + + /** + * \brief Stop the journal from accepting any further requests to read or write data. + * + * This operation is used to stop the journal. This is the normal mechanism for bringing the + * journal to an orderly stop. Any outstanding AIO operations or partially written pages in + * the write page cache will by flushed and will complete. + * + * <b>Note:</b> The journal cannot be restarted without either initializing it or restoring + * it. + * + * \param block_till_aio_cmpl If true, will block the thread while waiting for all + * outstanding AIO operations to complete. + */ + void stop(const bool block_till_aio_cmpl = false); + + /** + * \brief Force a flush of the write page cache, creating a single AIO write operation. + */ + iores flush(const bool block_till_aio_cmpl = false); + + inline u_int32_t get_enq_cnt() const { return _emap.size(); } + + inline u_int32_t get_wr_aio_evt_rem() const { slock l(_wr_mutex); return _wmgr.get_aio_evt_rem(); } + + inline u_int32_t get_rd_aio_evt_rem() const { return _rmgr.get_aio_evt_rem(); } + + inline u_int32_t get_wr_outstanding_aio_dblks() const + { return _wrfc.aio_outstanding_dblks(); } + + inline u_int32_t get_wr_outstanding_aio_dblks(u_int16_t lfid) const + { return _lpmgr.get_fcntlp(lfid)->wr_aio_outstanding_dblks(); } + + inline u_int32_t get_rd_outstanding_aio_dblks() const + { return _rrfc.aio_outstanding_dblks(); } + + inline u_int32_t get_rd_outstanding_aio_dblks(u_int16_t lfid) const + { return _lpmgr.get_fcntlp(lfid)->rd_aio_outstanding_dblks(); } + + inline u_int16_t get_rd_fid() const { return _rrfc.index(); } + inline u_int16_t get_wr_fid() const { return _wrfc.index(); } + u_int16_t get_earliest_fid(); + + /** + * \brief Check if a particular rid is enqueued. Note that this function will return + * false if the rid is transactionally enqueued and is not committed, or if it is + * locked (i.e. transactionally dequeued, but the dequeue has not been committed). + */ + inline bool is_enqueued(const u_int64_t rid, bool ignore_lock = false) + { return _emap.is_enqueued(rid, ignore_lock); } + inline bool is_locked(const u_int64_t rid) + { if (_emap.is_enqueued(rid, true) < enq_map::EMAP_OK) return false; return _emap.is_locked(rid) == enq_map::EMAP_TRUE; } + inline void enq_rid_list(std::vector<u_int64_t>& rids) { _emap.rid_list(rids); } + inline void enq_xid_list(std::vector<std::string>& xids) { _tmap.xid_list(xids); } + inline u_int32_t get_open_txn_cnt() const { return _tmap.size(); } + // TODO Make this a const, but txn_map must support const first. + inline txn_map& get_txn_map() { return _tmap; } + + /** + * \brief Check if the journal is stopped. + * + * \return <b><i>true</i></b> if the jouranl is stopped; + * <b><i>false</i></b> otherwise. + */ + inline bool is_stopped() { return _stop_flag; } + + /** + * \brief Check if the journal is ready to read and write data. + * + * Checks if the journal is ready to read and write data. This function will return + * <b><i>true</i></b> if the journal has been either initialized or restored, and the stop() + * function has not been called since the initialization. + * + * Note that the journal may also be stopped if an internal error occurs (such as running out + * of data journal file space). + * + * \return <b><i>true</i></b> if the journal is ready to read and write data; + * <b><i>false</i></b> otherwise. + */ + inline bool is_ready() const { return _init_flag && !_stop_flag; } + + inline bool is_read_only() const { return _readonly_flag; } + + /** + * \brief Get the journal directory. + * + * This returns the journal directory as set during initialization. This is the directory + * into which the journal files will be written. + */ + inline const std::string& dirname() const { return _jdir.dirname(); } + + /** + * \brief Get the journal base filename. + * + * Get the journal base filename as set during initialization. This is the prefix used in all + * journal files of this instance. Note that if more than one instance of the journal shares + * the same directory, their base filenames <b>MUST</b> be different or else the instances + * will overwrite one another. + */ + inline const std::string& base_filename() const { return _base_filename; } + + inline u_int16_t num_jfiles() const { return _lpmgr.num_jfiles(); } + + inline fcntl* get_fcntlp(const u_int16_t lfid) const { return _lpmgr.get_fcntlp(lfid); } + + inline u_int32_t jfsize_sblks() const { return _jfsize_sblks; } + + // Logging + virtual void log(log_level level, const std::string& log_stmt) const; + virtual void log(log_level level, const char* const log_stmt) const; + + // FIXME these are _rmgr to _wmgr interactions, remove when _rmgr contains ref to _wmgr: + void chk_wr_frot(); + inline u_int32_t unflushed_dblks() { return _wmgr.unflushed_dblks(); } + void fhdr_wr_sync(const u_int16_t lid); + inline u_int32_t wr_subm_cnt_dblks(const u_int16_t lfid) const { return _lpmgr.get_fcntlp(lfid)->wr_subm_cnt_dblks(); } + + // Management instrumentation callbacks + inline virtual void instr_incr_outstanding_aio_cnt() {} + inline virtual void instr_decr_outstanding_aio_cnt() {} + + /** + * /brief Static function for creating new fcntl objects for use with obj_arr. + */ + static fcntl* new_fcntl(jcntl* const jcp, const u_int16_t lid, const u_int16_t fid, const rcvdat* const rdp); + + protected: + static bool _init; + static bool init_statics(); + + /** + * \brief Check status of journal before allowing write operations. + */ + void check_wstatus(const char* fn_name) const; + + /** + * \brief Check status of journal before allowing read operations. + */ + void check_rstatus(const char* fn_name) const; + + /** + * \brief Write info file <basefilename>.jinf to disk + */ + void write_infofile() const; + + /** + * \brief Call that blocks while waiting for all outstanding AIOs to complete + */ + void aio_cmpl_wait(); + + /** + * \brief Call that blocks until at least one message returns; used to wait for + * AIO wait conditions to clear. + */ + bool handle_aio_wait(const iores res, iores& resout, const data_tok* dtp); + + /** + * \brief Analyze journal for recovery. + */ + void rcvr_janalyze(rcvdat& rd, const std::vector<std::string>* prep_txn_list_ptr); + + bool rcvr_get_next_record(u_int16_t& fid, std::ifstream* ifsp, bool& lowi, rcvdat& rd); + + bool decode(jrec& rec, u_int16_t& fid, std::ifstream* ifsp, std::size_t& cum_size_read, + rec_hdr& h, bool& lowi, rcvdat& rd, std::streampos& rec_offset); + + bool jfile_cycle(u_int16_t& fid, std::ifstream* ifsp, bool& lowi, rcvdat& rd, + const bool jump_fro); + + bool check_owi(const u_int16_t fid, rec_hdr& h, bool& lowi, rcvdat& rd, + std::streampos& read_pos); + + void check_journal_alignment(const u_int16_t fid, std::streampos& rec_offset, rcvdat& rd); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_JCNTL_H diff --git a/cpp/src/qpid/legacystore/jrnl/jdir.cpp b/cpp/src/qpid/legacystore/jrnl/jdir.cpp new file mode 100644 index 0000000000..a874c6c945 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jdir.cpp @@ -0,0 +1,463 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jdir.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::jdir (journal data + * directory), used for controlling and manipulating journal data + * direcories and files. See comments in file jdir.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/jdir.h" + +#include <cstdlib> +#include <cstring> +#include <cerrno> +#include <iomanip> +#include "qpid/legacystore/jrnl/jcfg.h" +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include <sstream> +#include <sys/stat.h> +#include <unistd.h> + +namespace mrg +{ +namespace journal +{ + +jdir::jdir(const std::string& dirname, const std::string& _base_filename): + _dirname(dirname), + _base_filename(_base_filename) +{} + +jdir::~jdir() +{} + +// === create_dir === + +void +jdir::create_dir() +{ + create_dir(_dirname); +} + + +void +jdir::create_dir(const char* dirname) +{ + create_dir(std::string(dirname)); +} + + +void +jdir::create_dir(const std::string& dirname) +{ + std::size_t fdp = dirname.find_last_of('/'); + if (fdp != std::string::npos) + { + std::string parent_dir = dirname.substr(0, fdp); + if (!exists(parent_dir)) + create_dir(parent_dir); + } + if (::mkdir(dirname.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) + { + if (errno != EEXIST) // Dir exists, ignore + { + std::ostringstream oss; + oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_MKDIR, oss.str(), "jdir", "create_dir"); + } + } +} + + +// === clear_dir === + +void +jdir::clear_dir(const bool create_flag) +{ + clear_dir(_dirname, _base_filename, create_flag); +} + +void +jdir::clear_dir(const char* dirname, const char* base_filename, const bool create_flag) +{ + clear_dir(std::string(dirname), std::string(base_filename), create_flag); +} + + +void +jdir::clear_dir(const std::string& dirname, const std::string& +#ifndef RHM_JOWRITE + base_filename +#endif + , const bool create_flag) +{ + DIR* dir = ::opendir(dirname.c_str()); + if (!dir) + { + if (errno == 2 && create_flag) // ENOENT (No such file or dir) + { + create_dir(dirname); + return; + } + std::ostringstream oss; + oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_OPENDIR, oss.str(), "jdir", "clear_dir"); + } +#ifndef RHM_JOWRITE + struct dirent* entry; + bool found = false; + std::string bak_dir; + while ((entry = ::readdir(dir)) != 0) + { + // Ignore . and .. + if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0) + { + if (std::strlen(entry->d_name) > base_filename.size()) + { + if (std::strncmp(entry->d_name, base_filename.c_str(), base_filename.size()) == 0) + { + if (!found) + { + bak_dir = create_bak_dir(dirname, base_filename); + found = true; + } + std::ostringstream oldname; + oldname << dirname << "/" << entry->d_name; + std::ostringstream newname; + newname << bak_dir << "/" << entry->d_name; + if (::rename(oldname.str().c_str(), newname.str().c_str())) + { + ::closedir(dir); + std::ostringstream oss; + oss << "file=\"" << oldname.str() << "\" dest=\"" << + newname.str() << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_FMOVE, oss.str(), "jdir", "clear_dir"); + } + } + } + } + } +// FIXME: Find out why this fails with false alarms/errors from time to time... +// While commented out, there is no error capture from reading dir entries. +// check_err(errno, dir, dirname, "clear_dir"); +#endif + close_dir(dir, dirname, "clear_dir"); +} + +// === push_down === + +std::string +jdir::push_down(const std::string& dirname, const std::string& target_dir, const std::string& bak_dir_base) +{ + std::string bak_dir_name = create_bak_dir(dirname, bak_dir_base); + + DIR* dir = ::opendir(dirname.c_str()); + if (!dir) + { + std::ostringstream oss; + oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_OPENDIR, oss.str(), "jdir", "push_down"); + } + // Copy contents of targetDirName into bak dir + struct dirent* entry; + while ((entry = ::readdir(dir)) != 0) + { + // Search for targetDirName in storeDirName + if (std::strcmp(entry->d_name, target_dir.c_str()) == 0) + { + std::ostringstream oldname; + oldname << dirname << "/" << target_dir; + std::ostringstream newname; + newname << bak_dir_name << "/" << target_dir; + if (::rename(oldname.str().c_str(), newname.str().c_str())) + { + ::closedir(dir); + std::ostringstream oss; + oss << "file=\"" << oldname.str() << "\" dest=\"" << newname.str() << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_FMOVE, oss.str(), "jdir", "push_down"); + } + break; + } + } + close_dir(dir, dirname, "push_down"); + return bak_dir_name; +} + +// === verify_dir === + +void +jdir::verify_dir() +{ + verify_dir(_dirname, _base_filename); +} + +void +jdir::verify_dir(const char* dirname, const char* base_filename) +{ + verify_dir(std::string(dirname), std::string(base_filename)); +} + + +void +jdir::verify_dir(const std::string& dirname, const std::string& base_filename) +{ + if (!is_dir(dirname)) + { + std::ostringstream oss; + oss << "dir=\"" << dirname << "\""; + throw jexception(jerrno::JERR_JDIR_NOTDIR, oss.str(), "jdir", "verify_dir"); + } + + // Read jinf file, then verify all journal files are present + jinf ji(dirname + "/" + base_filename + "." + JRNL_INFO_EXTENSION, true); + for (u_int16_t fnum=0; fnum < ji.num_jfiles(); fnum++) + { + std::ostringstream oss; + oss << dirname << "/" << base_filename << "."; + oss << std::setw(4) << std::setfill('0') << std::hex << fnum; + oss << "." << JRNL_DATA_EXTENSION; + if (!exists(oss.str())) + throw jexception(jerrno::JERR_JDIR_NOSUCHFILE, oss.str(), "jdir", "verify_dir"); + } +} + + +// === delete_dir === + +void +jdir::delete_dir(bool children_only) +{ + delete_dir(_dirname, children_only); +} + +void +jdir::delete_dir(const char* dirname, bool children_only) +{ + delete_dir(std::string(dirname), children_only); +} + +void +jdir::delete_dir(const std::string& dirname, bool children_only) +{ + struct dirent* entry; + struct stat s; + DIR* dir = ::opendir(dirname.c_str()); + if (!dir) + { + if (errno == ENOENT) // dir does not exist. + return; + + std::ostringstream oss; + oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_OPENDIR, oss.str(), "jdir", "delete_dir"); + } + else + { + while ((entry = ::readdir(dir)) != 0) + { + // Ignore . and .. + if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0) + { + std::string full_name(dirname + "/" + entry->d_name); + if (::lstat(full_name.c_str(), &s)) + { + ::closedir(dir); + std::ostringstream oss; + oss << "stat: file=\"" << full_name << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "delete_dir"); + } + if (S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) // This is a file or slink + { + if(::unlink(full_name.c_str())) + { + ::closedir(dir); + std::ostringstream oss; + oss << "unlink: file=\"" << entry->d_name << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_UNLINK, oss.str(), "jdir", "delete_dir"); + } + } + else if (S_ISDIR(s.st_mode)) // This is a dir + { + delete_dir(full_name); + } + else // all other types, throw up! + { + ::closedir(dir); + std::ostringstream oss; + oss << "file=\"" << entry->d_name << "\" is not a dir, file or slink."; + oss << " (mode=0x" << std::hex << s.st_mode << std::dec << ")"; + throw jexception(jerrno::JERR_JDIR_BADFTYPE, oss.str(), "jdir", "delete_dir"); + } + } + } + +// FIXME: Find out why this fails with false alarms/errors from time to time... +// While commented out, there is no error capture from reading dir entries. +// check_err(errno, dir, dirname, "delete_dir"); + } + // Now dir is empty, close and delete it + close_dir(dir, dirname, "delete_dir"); + + if (!children_only) + if (::rmdir(dirname.c_str())) + { + std::ostringstream oss; + oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_RMDIR, oss.str(), "jdir", "delete_dir"); + } +} + + +std::string +jdir::create_bak_dir(const std::string& dirname, const std::string& base_filename) +{ + DIR* dir = ::opendir(dirname.c_str()); + long dir_num = 0L; + if (!dir) + { + std::ostringstream oss; + oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_OPENDIR, oss.str(), "jdir", "create_bak_dir"); + } + struct dirent* entry; + while ((entry = ::readdir(dir)) != 0) + { + // Ignore . and .. + if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0) + { + if (std::strlen(entry->d_name) == base_filename.size() + 10) // Format: basename.bak.XXXX + { + std::ostringstream oss; + oss << "_" << base_filename << ".bak."; + if (std::strncmp(entry->d_name, oss.str().c_str(), base_filename.size() + 6) == 0) + { + long this_dir_num = std::strtol(entry->d_name + base_filename.size() + 6, 0, 16); + if (this_dir_num > dir_num) + dir_num = this_dir_num; + } + } + } + } +// FIXME: Find out why this fails with false alarms/errors from time to time... +// While commented out, there is no error capture from reading dir entries. +// check_err(errno, dir, dirname, "create_bak_dir"); + close_dir(dir, dirname, "create_bak_dir"); + + std::ostringstream dn; + dn << dirname << "/_" << base_filename << ".bak." << std::hex << std::setw(4) << + std::setfill('0') << ++dir_num; + if (::mkdir(dn.str().c_str(), S_IRWXU | S_IRWXG | S_IROTH)) + { + std::ostringstream oss; + oss << "dir=\"" << dn.str() << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_MKDIR, oss.str(), "jdir", "create_bak_dir"); + } + return std::string(dn.str()); +} + +bool +jdir::is_dir(const char* name) +{ + struct stat s; + if (::stat(name, &s)) + { + std::ostringstream oss; + oss << "file=\"" << name << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "is_dir"); + } + return S_ISDIR(s.st_mode); +} + +bool +jdir::is_dir(const std::string& name) +{ + return is_dir(name.c_str()); +} + +bool +jdir::exists(const char* name) +{ + struct stat s; + if (::stat(name, &s)) + { + if (errno == ENOENT) // No such dir or file + return false; + // Throw for any other condition + std::ostringstream oss; + oss << "file=\"" << name << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "exists"); + } + return true; +} + +bool +jdir::exists(const std::string& name) +{ + return exists(name.c_str()); +} + +void +jdir::check_err(const int err_num, DIR* dir, const std::string& dir_name, const std::string& fn_name) +{ + if (err_num) + { + std::ostringstream oss; + oss << "dir=\"" << dir_name << "\"" << FORMAT_SYSERR(err_num); + ::closedir(dir); // Try to close, it makes no sense to trap errors here... + throw jexception(jerrno::JERR_JDIR_READDIR, oss.str(), "jdir", fn_name); + } +} + +void +jdir::close_dir(DIR* dir, const std::string& dir_name, const std::string& fn_name) +{ + if (::closedir(dir)) + { + std::ostringstream oss; + oss << "dir=\"" << dir_name << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JDIR_CLOSEDIR, oss.str(), "jdir", fn_name); + } +} + +std::ostream& +operator<<(std::ostream& os, const jdir& jdir) +{ + os << jdir._dirname; + return os; +} + +std::ostream& +operator<<(std::ostream& os, const jdir* jdirPtr) +{ + os << jdirPtr->_dirname; + return os; +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/jdir.h b/cpp/src/qpid/legacystore/jrnl/jdir.h new file mode 100644 index 0000000000..e129b794d6 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jdir.h @@ -0,0 +1,379 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jdir.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::jdir (%journal data + * directory), used for controlling and manipulating %journal data + * directories and files. See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_JDIR_H +#define QPID_LEGACYSTORE_JRNL_JDIR_H + +namespace mrg +{ +namespace journal +{ +class jdir; +} +} + +#include "qpid/legacystore/jrnl/jinf.h" +#include <dirent.h> + +namespace mrg +{ +namespace journal +{ + + /** + * \class jdir + * \brief Class to manage the %journal directory + */ + class jdir + { + private: + std::string _dirname; + std::string _base_filename; + + public: + + /** + * \brief Sole constructor + * + * \param dirname Name of directory to be managed. + * \param base_filename Filename root used in the creation of %journal files + * and sub-directories. + */ + jdir(const std::string& dirname, const std::string& base_filename); + + virtual ~jdir(); + + + /** + * \brief Create %journal directory as set in the dirname parameter of the constructor. + * Recursive creation is supported. + * + * \exception jerrno::JERR_JDIR_MKDIR The creation of dirname failed. + */ + void create_dir(); + + /** + * \brief Static function to create a directory. Recursive creation is supported. + * + * \param dirname C-string containing name of directory. + * + * \exception jerrno::JERR_JDIR_MKDIR The creation of dirname failed. + */ + static void create_dir(const char* dirname); + + /** + * \brief Static function to create a directory. Recursive creation is supported. + * + * \param dirname String containing name of directory. + * + * \exception jerrno::JERR_JDIR_MKDIR The creation of dirname failed. + */ + static void create_dir(const std::string& dirname); + + + /** + * \brief Clear the %journal directory of files matching the base filename + * by moving them into a subdirectory. This fn uses the dirname and base_filename + * that were set on construction. + * + * \param create_flag If set, create dirname if it is non-existent, otherwise throw + * exception. + * + * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened. + * \exception jerrno::JERR_JDIR_FMOVE Moving the files from the %journal directory to the created backup + * directory failed. + * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed. + */ + void clear_dir(const bool create_flag = true); + + /** + * \brief Clear the directory dirname of %journal files matching base_filename + * by moving them into a subdirectory. + * + * \param dirname C-string containing name of %journal directory. + * \param base_filename C-string containing base filename of %journal files to be matched + * for moving into subdirectory. + * \param create_flag If set, create dirname if it is non-existent, otherwise throw + * exception + * + * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened. + * \exception jerrno::JERR_JDIR_FMOVE Moving the files from the %journal directory to the created backup + * directory failed. + * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed. + */ + static void clear_dir(const char* dirname, const char* base_filename, + const bool create_flag = true); + + /** + * \brief Clear the directory dirname of %journal files matching base_filename + * by moving them into a subdirectory. + * + * \param dirname String containing name of %journal directory. + * \param base_filename String containing base filename of %journal files to be matched + * for moving into subdirectory. + * \param create_flag If set, create dirname if it is non-existent, otherwise throw + * exception + * + * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened. + * \exception jerrno::JERR_JDIR_FMOVE Moving the files from the %journal directory to the created backup + * directory failed. + * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed. + */ + static void clear_dir(const std::string& dirname, const std::string& base_filename, + const bool create_flag = true); + + + + /** + * \brief Move (push down) the directory target_dir located in directory dirname into a backup directory + * named _bak_dir_base.XXXX (note prepended underscore), where XXXX is an increasing hex serial number + * starting at 0000. + * + * \param dirname Full path to directory containing directory to be pushed down. + * \param target_dir Name of directory in dirname to be pushed down. + * \param bak_dir_base Base name for backup directory to be created in dirname, into which target_dir will be moved. + * \return Name of backup dir into which target_dir was pushed. + */ + static std::string push_down(const std::string& dirname, const std::string& target_dir, const std::string& bak_dir_base); + + + /** + * \brief Verify that dirname is a valid %journal directory. + * + * The validation reads the .%jinf file, and using this information verifies that all the expected %journal + * (.jdat) files are present. + * + * \exception jerrno::JERR_JDIR_NOTDIR dirname is not a directory + * \exception jerrno::JERR_JDIR_STAT Could not stat dirname + * \exception jerrno::JERR__FILEIO Error reading %jinf file + * \exception jerrno::JERR_JINF_CVALIDFAIL Error validating %jinf file + * \exception jerrno::JERR_JDIR_NOSUCHFILE Expected jdat file is missing + */ + void verify_dir(); + + /** + * \brief Verify that dirname is a valid %journal directory. + * + * The validation reads the .%jinf file, and using this information verifies that all the expected %journal + * (.jdat) files are present. + * + * \param dirname C-string containing name of %journal directory. + * \param base_filename C-string containing base filename of %journal files to be matched for moving into sub-directory. + * + * \exception jerrno::JERR_JDIR_NOTDIR dirname is not a directory + * \exception jerrno::JERR_JDIR_STAT Could not stat dirname + * \exception jerrno::JERR__FILEIO Error reading %jinf file + * \exception jerrno::JERR_JINF_CVALIDFAIL Error validating %jinf file + * \exception jerrno::JERR_JDIR_NOSUCHFILE Expected jdat file is missing + */ + static void verify_dir(const char* dirname, const char* base_filename); + + /** + * \brief Verify that dirname is a valid %journal directory. + * + * The validation reads the .%jinf file, and using this information verifies that all the expected %journal + * (.jdat) files are present. + * + * \param dirname String containing name of %journal directory. + * \param base_filename String containing base filename of %journal files to be matched for moving into sub-directory. + * + * \exception jerrno::JERR_JDIR_NOTDIR dirname is not a directory + * \exception jerrno::JERR_JDIR_STAT Could not stat dirname + * \exception jerrno::JERR__FILEIO Error reading %jinf file + * \exception jerrno::JERR_JINF_CVALIDFAIL Error validating %jinf file + * \exception jerrno::JERR_JDIR_NOSUCHFILE Expected jdat file is missing + */ + static void verify_dir(const std::string& dirname, const std::string& base_filename); + + /** + * \brief Delete the %journal directory and all files and sub--directories that it may + * contain. This is equivilent of rm -rf. + * + * FIXME: links are not handled correctly. + * + * \param children_only If true, delete only children of dirname, but leave dirname itself. + * + * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened. + * \exception jerrno::JERR_JDIR_STAT Could not stat dirname. + * \exception jerrno::JERR_JDIR_UNLINK A file could not be deleted. + * \exception jerrno::JERR_JDIR_BADFTYPE A dir entry is neiter a file nor a dir. + * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed. + * \exception jerrno::JERR_JDIR_RMDIR A directory could not be deleted. + */ + void delete_dir(bool children_only = false ); + + /** + * \brief Delete the %journal directory and all files and sub--directories that it may + * contain. This is equivilent of rm -rf. + * + * FIXME: links are not handled correctly. + * + * \param dirname C-string containing name of directory to be deleted. + * \param children_only If true, delete only children of dirname, but leave dirname itself. + * + * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened. + * \exception jerrno::JERR_JDIR_STAT Could not stat dirname. + * \exception jerrno::JERR_JDIR_UNLINK A file could not be deleted. + * \exception jerrno::JERR_JDIR_BADFTYPE A dir entry is neiter a file nor a dir. + * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed. + * \exception jerrno::JERR_JDIR_RMDIR A directory could not be deleted. + */ + static void delete_dir(const char* dirname, bool children_only = false); + + /** + * \brief Delete the %journal directory and all files and sub--directories that it may + * contain. This is equivilent of rm -rf. + * + * FIXME: links are not handled correctly. + * + * \param dirname String containing name of directory to be deleted. + * \param children_only If true, delete only children of dirname, but leave dirname itself. + * + * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened. + * \exception jerrno::JERR_JDIR_STAT Could not stat dirname. + * \exception jerrno::JERR_JDIR_UNLINK A file could not be deleted. + * \exception jerrno::JERR_JDIR_BADFTYPE A dir entry is neiter a file nor a dir. + * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed. + * \exception jerrno::JERR_JDIR_RMDIR A directory could not be deleted. + */ + static void delete_dir(const std::string& dirname, bool children_only = false); + + /** + * \brief Create bakup directory that is next in sequence and move all %journal files + * matching base_filename into it. + * + * In directory dirname, search for existing backup directory using pattern + * "_basename.bak.XXXX" where XXXX is a hexadecimal sequence, and create next directory + * based on highest number found. Move all %journal files which match the base_fileaname + * parameter into this new backup directory. + * + * \param dirname String containing name of %journal directory. + * \param base_filename String containing base filename of %journal files to be matched + * for moving into subdirectory. + * + * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened. + * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed. + * \exception jerrno::JERR_JDIR_MKDIR The backup directory could not be deleted. + */ + static std::string create_bak_dir(const std::string& dirname, + const std::string& base_filename); + + /** + * \brief Return the directory name as a string. + */ + inline const std::string& dirname() const { return _dirname; } + + /** + * \brief Return the %journal base filename name as a string. + */ + inline const std::string& base_filename() const { return _base_filename; } + + /** + * \brief Test whether the named file is a directory. + * + * \param name Name of file to be tested. + * \return <b><i>true</i></b> if the named file is a directory; <b><i>false</i></b> + * otherwise. + * \exception jerrno::JERR_JDIR_STAT Could not stat name. + */ + static bool is_dir(const char* name); + + /** + * \brief Test whether the named file is a directory. + * + * \param name Name of file to be tested. + * \return <b><i>true</i></b> if the named file is a directory; <b><i>false</i></b> + * otherwise. + * \exception jerrno::JERR_JDIR_STAT Could not stat name. + */ + static bool is_dir(const std::string& name); + + + /** + * \brief Test whether the named entity exists on the filesystem. + * + * If stat() fails with error ENOENT, then this will return <b><i>false</i></b>. If + * stat() succeeds, then <b><i>true</i></b> is returned, irrespective of the file type. + * If stat() fails with any other error, an exception is thrown. + * + * \param name Name of entity to be tested. + * \return <b><i>true</i></b> if the named entity exists; <b><i>false</i></b> + * otherwise. + * \exception jerrno::JERR_JDIR_STAT Could not stat name. + */ + static bool exists(const char* name); + + /** + * \brief Test whether the named entity exists on the filesystem. + * + * If stat() fails with error ENOENT, then this will return <b><i>false</i></b>. If + * stat() succeeds, then <b><i>true</i></b> is returned, irrespective of the file type. + * If stat() fails with any other error, an exception is thrown. + * + * \param name Name of entity to be tested. + * \return <b><i>true</i></b> if the named entity exists; <b><i>false</i></b> + * otherwise. + * \exception jerrno::JERR_JDIR_STAT Could not stat name. + */ + static bool exists(const std::string& name); + + /** + * \brief Stream operator + */ + friend std::ostream& operator<<(std::ostream& os, const jdir& jdir); + + /** + * \brief Stream operator + */ + friend std::ostream& operator<<(std::ostream& os, const jdir* jdirPtr); + + private: + /** + * \brief Check for error, if non-zero close DIR handle and throw JERR_JDIR_READDIR + * + * \exception jerrno::JERR_JDIR_READDIR Error while reading contents of dir. + */ + static void check_err(const int err_num, DIR* dir, const std::string& dir_name, const std::string& fn_name); + + /** + * \brief Close a DIR handle, throw JERR_JDIR_CLOSEDIR if error occurs during close + * + * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed. + */ + static void close_dir(DIR* dir, const std::string& dir_name, const std::string& fn_name); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_JDIR_H diff --git a/cpp/src/qpid/legacystore/jrnl/jerrno.cpp b/cpp/src/qpid/legacystore/jrnl/jerrno.cpp new file mode 100644 index 0000000000..4962ce63ab --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jerrno.cpp @@ -0,0 +1,253 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jerrno.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::jerrno (journal error + * codes). See comments in file jerrno.h for details. + * + * See file jerrno.h for class details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/jerrno.h" + +namespace mrg +{ +namespace journal +{ + +std::map<u_int32_t, const char*> jerrno::_err_map; +std::map<u_int32_t, const char*>::iterator jerrno::_err_map_itr; +bool jerrno::_initialized = jerrno::__init(); + +// generic errors +const u_int32_t jerrno::JERR__MALLOC = 0x0100; +const u_int32_t jerrno::JERR__UNDERFLOW = 0x0101; +const u_int32_t jerrno::JERR__NINIT = 0x0102; +const u_int32_t jerrno::JERR__AIO = 0x0103; +const u_int32_t jerrno::JERR__FILEIO = 0x0104; +const u_int32_t jerrno::JERR__RTCLOCK = 0x0105; +const u_int32_t jerrno::JERR__PTHREAD = 0x0106; +const u_int32_t jerrno::JERR__TIMEOUT = 0x0107; +const u_int32_t jerrno::JERR__UNEXPRESPONSE = 0x0108; +const u_int32_t jerrno::JERR__RECNFOUND = 0x0109; +const u_int32_t jerrno::JERR__NOTIMPL = 0x010a; + +// class jcntl +const u_int32_t jerrno::JERR_JCNTL_STOPPED = 0x0200; +const u_int32_t jerrno::JERR_JCNTL_READONLY = 0x0201; +const u_int32_t jerrno::JERR_JCNTL_AIOCMPLWAIT = 0x0202; +const u_int32_t jerrno::JERR_JCNTL_UNKNOWNMAGIC = 0x0203; +const u_int32_t jerrno::JERR_JCNTL_NOTRECOVERED = 0x0204; +const u_int32_t jerrno::JERR_JCNTL_RECOVERJFULL = 0x0205; +const u_int32_t jerrno::JERR_JCNTL_OWIMISMATCH = 0x0206; + +// class jdir +const u_int32_t jerrno::JERR_JDIR_NOTDIR = 0x0300; +const u_int32_t jerrno::JERR_JDIR_MKDIR = 0x0301; +const u_int32_t jerrno::JERR_JDIR_OPENDIR = 0x0302; +const u_int32_t jerrno::JERR_JDIR_READDIR = 0x0303; +const u_int32_t jerrno::JERR_JDIR_CLOSEDIR = 0x0304; +const u_int32_t jerrno::JERR_JDIR_RMDIR = 0x0305; +const u_int32_t jerrno::JERR_JDIR_NOSUCHFILE = 0x0306; +const u_int32_t jerrno::JERR_JDIR_FMOVE = 0x0307; +const u_int32_t jerrno::JERR_JDIR_STAT = 0x0308; +const u_int32_t jerrno::JERR_JDIR_UNLINK = 0x0309; +const u_int32_t jerrno::JERR_JDIR_BADFTYPE = 0x030a; + +// class fcntl +const u_int32_t jerrno::JERR_FCNTL_OPENWR = 0x0400; +const u_int32_t jerrno::JERR_FCNTL_WRITE = 0x0401; +const u_int32_t jerrno::JERR_FCNTL_CLOSE = 0x0402; +const u_int32_t jerrno::JERR_FCNTL_FILEOFFSOVFL = 0x0403; +const u_int32_t jerrno::JERR_FCNTL_CMPLOFFSOVFL = 0x0404; +const u_int32_t jerrno::JERR_FCNTL_RDOFFSOVFL = 0x0405; + +// class lfmgr +const u_int32_t jerrno::JERR_LFMGR_BADAEFNUMLIM = 0x0500; +const u_int32_t jerrno::JERR_LFMGR_AEFNUMLIMIT = 0x0501; +const u_int32_t jerrno::JERR_LFMGR_AEDISABLED = 0x0502; + +// class rrfc +const u_int32_t jerrno::JERR_RRFC_OPENRD = 0x0600; + +// class jrec, enq_rec, deq_rec, txn_rec +const u_int32_t jerrno::JERR_JREC_BADRECHDR = 0x0700; +const u_int32_t jerrno::JERR_JREC_BADRECTAIL = 0x0701; + +// class wmgr +const u_int32_t jerrno::JERR_WMGR_BADPGSTATE = 0x0801; +const u_int32_t jerrno::JERR_WMGR_BADDTOKSTATE = 0x0802; +const u_int32_t jerrno::JERR_WMGR_ENQDISCONT = 0x0803; +const u_int32_t jerrno::JERR_WMGR_DEQDISCONT = 0x0804; +const u_int32_t jerrno::JERR_WMGR_DEQRIDNOTENQ = 0x0805; + +// class rmgr +const u_int32_t jerrno::JERR_RMGR_UNKNOWNMAGIC = 0x0900; +const u_int32_t jerrno::JERR_RMGR_RIDMISMATCH = 0x0901; +//const u_int32_t jerrno::JERR_RMGR_FIDMISMATCH = 0x0902; +const u_int32_t jerrno::JERR_RMGR_ENQSTATE = 0x0903; +const u_int32_t jerrno::JERR_RMGR_BADRECTYPE = 0x0904; + +// class data_tok +const u_int32_t jerrno::JERR_DTOK_ILLEGALSTATE = 0x0a00; +// const u_int32_t jerrno::JERR_DTOK_RIDNOTSET = 0x0a01; + +// class enq_map, txn_map +const u_int32_t jerrno::JERR_MAP_DUPLICATE = 0x0b00; +const u_int32_t jerrno::JERR_MAP_NOTFOUND = 0x0b01; +const u_int32_t jerrno::JERR_MAP_LOCKED = 0x0b02; + +// class jinf +const u_int32_t jerrno::JERR_JINF_CVALIDFAIL = 0x0c00; +const u_int32_t jerrno::JERR_JINF_NOVALUESTR = 0x0c01; +const u_int32_t jerrno::JERR_JINF_BADVALUESTR = 0x0c02; +const u_int32_t jerrno::JERR_JINF_JDATEMPTY = 0x0c03; +const u_int32_t jerrno::JERR_JINF_TOOMANYFILES = 0x0c04; +const u_int32_t jerrno::JERR_JINF_INVALIDFHDR = 0x0c05; +const u_int32_t jerrno::JERR_JINF_STAT = 0x0c06; +const u_int32_t jerrno::JERR_JINF_NOTREGFILE = 0x0c07; +const u_int32_t jerrno::JERR_JINF_BADFILESIZE = 0x0c08; +const u_int32_t jerrno::JERR_JINF_OWIBAD = 0x0c09; +const u_int32_t jerrno::JERR_JINF_ZEROLENFILE = 0x0c0a; + +// Negative returns for some functions +const int32_t jerrno::AIO_TIMEOUT = -1; +const int32_t jerrno::LOCK_TAKEN = -2; + + +// static initialization fn + +bool +jerrno::__init() +{ + // generic errors + _err_map[JERR__MALLOC] = "JERR__MALLOC: Buffer memory allocation failed."; + _err_map[JERR__UNDERFLOW] = "JERR__UNDERFLOW: Underflow error"; + _err_map[JERR__NINIT] = "JERR__NINIT: Operation on uninitialized class."; + _err_map[JERR__AIO] = "JERR__AIO: AIO error."; + _err_map[JERR__FILEIO] = "JERR__FILEIO: File read or write failure."; + _err_map[JERR__RTCLOCK] = "JERR__RTCLOCK: Reading real-time clock failed."; + _err_map[JERR__PTHREAD] = "JERR__PTHREAD: pthread failure."; + _err_map[JERR__TIMEOUT] = "JERR__TIMEOUT: Timeout waiting for event."; + _err_map[JERR__UNEXPRESPONSE] = "JERR__UNEXPRESPONSE: Unexpected response to call or event."; + _err_map[JERR__RECNFOUND] = "JERR__RECNFOUND: Record not found."; + _err_map[JERR__NOTIMPL] = "JERR__NOTIMPL: Not implemented"; + + // class jcntl + _err_map[JERR_JCNTL_STOPPED] = "JERR_JCNTL_STOPPED: Operation on stopped journal."; + _err_map[JERR_JCNTL_READONLY] = "JERR_JCNTL_READONLY: Write operation on read-only journal (during recovery)."; + _err_map[JERR_JCNTL_AIOCMPLWAIT] = "JERR_JCNTL_AIOCMPLWAIT: Timeout waiting for AIOs to complete."; + _err_map[JERR_JCNTL_UNKNOWNMAGIC] = "JERR_JCNTL_UNKNOWNMAGIC: Found record with unknown magic."; + _err_map[JERR_JCNTL_NOTRECOVERED] = "JERR_JCNTL_NOTRECOVERED: Operation requires recover() to be run first."; + _err_map[JERR_JCNTL_RECOVERJFULL] = "JERR_JCNTL_RECOVERJFULL: Journal data files full, cannot write."; + _err_map[JERR_JCNTL_OWIMISMATCH] = "JERR_JCNTL_OWIMISMATCH: Overwrite Indicator (OWI) change found in unexpected location."; + + // class jdir + _err_map[JERR_JDIR_NOTDIR] = "JERR_JDIR_NOTDIR: Directory name exists but is not a directory."; + _err_map[JERR_JDIR_MKDIR] = "JERR_JDIR_MKDIR: Directory creation failed."; + _err_map[JERR_JDIR_OPENDIR] = "JERR_JDIR_OPENDIR: Directory open failed."; + _err_map[JERR_JDIR_READDIR] = "JERR_JDIR_READDIR: Directory read failed."; + _err_map[JERR_JDIR_CLOSEDIR] = "JERR_JDIR_CLOSEDIR: Directory close failed."; + _err_map[JERR_JDIR_RMDIR] = "JERR_JDIR_RMDIR: Directory delete failed."; + _err_map[JERR_JDIR_NOSUCHFILE] = "JERR_JDIR_NOSUCHFILE: File does not exist."; + _err_map[JERR_JDIR_FMOVE] = "JERR_JDIR_FMOVE: File move failed."; + _err_map[JERR_JDIR_STAT] = "JERR_JDIR_STAT: File stat failed."; + _err_map[JERR_JDIR_UNLINK] = "JERR_JDIR_UNLINK: File delete failed."; + _err_map[JERR_JDIR_BADFTYPE] = "JERR_JDIR_BADFTYPE: Bad or unknown file type (stat mode)."; + + // class fcntl + _err_map[JERR_FCNTL_OPENWR] = "JERR_FCNTL_OPENWR: Unable to open file for write."; + _err_map[JERR_FCNTL_WRITE] = "JERR_FCNTL_WRITE: Unable to write to file."; + _err_map[JERR_FCNTL_CLOSE] = "JERR_FCNTL_CLOSE: File close failed."; + _err_map[JERR_FCNTL_FILEOFFSOVFL] = "JERR_FCNTL_FILEOFFSOVFL: Attempted increase file offset past file size."; + _err_map[JERR_FCNTL_CMPLOFFSOVFL] = "JERR_FCNTL_CMPLOFFSOVFL: Attempted increase completed file offset past submitted offset."; + _err_map[JERR_FCNTL_RDOFFSOVFL] = "JERR_FCNTL_RDOFFSOVFL: Attempted increase read offset past write offset."; + + // class lfmgr + _err_map[JERR_LFMGR_BADAEFNUMLIM] = "JERR_LFMGR_BADAEFNUMLIM: Auto-expand file number limit lower than initial number of journal files."; + _err_map[JERR_LFMGR_AEFNUMLIMIT] = "JERR_LFMGR_AEFNUMLIMIT: Exceeded auto-expand file number limit."; + _err_map[JERR_LFMGR_AEDISABLED] = "JERR_LFMGR_AEDISABLED: Attempted to expand with auto-expand disabled."; + + // class rrfc + _err_map[JERR_RRFC_OPENRD] = "JERR_RRFC_OPENRD: Unable to open file for read."; + + // class jrec, enq_rec, deq_rec, txn_rec + _err_map[JERR_JREC_BADRECHDR] = "JERR_JREC_BADRECHDR: Invalid data record header."; + _err_map[JERR_JREC_BADRECTAIL] = "JERR_JREC_BADRECTAIL: Invalid data record tail."; + + // class wmgr + _err_map[JERR_WMGR_BADPGSTATE] = "JERR_WMGR_BADPGSTATE: Page buffer in illegal state for operation."; + _err_map[JERR_WMGR_BADDTOKSTATE] = "JERR_WMGR_BADDTOKSTATE: Data token in illegal state for operation."; + _err_map[JERR_WMGR_ENQDISCONT] = "JERR_WMGR_ENQDISCONT: Enqueued new dtok when previous enqueue returned partly completed (state ENQ_PART)."; + _err_map[JERR_WMGR_DEQDISCONT] = "JERR_WMGR_DEQDISCONT: Dequeued new dtok when previous dequeue returned partly completed (state DEQ_PART)."; + _err_map[JERR_WMGR_DEQRIDNOTENQ] = "JERR_WMGR_DEQRIDNOTENQ: Dequeue rid is not enqueued."; + + // class rmgr + _err_map[JERR_RMGR_UNKNOWNMAGIC] = "JERR_RMGR_UNKNOWNMAGIC: Found record with unknown magic."; + _err_map[JERR_RMGR_RIDMISMATCH] = "JERR_RMGR_RIDMISMATCH: RID mismatch between current record and dtok RID"; + //_err_map[JERR_RMGR_FIDMISMATCH] = "JERR_RMGR_FIDMISMATCH: FID mismatch between emap and rrfc"; + _err_map[JERR_RMGR_ENQSTATE] = "JERR_RMGR_ENQSTATE: Attempted read when data token wstate was not ENQ"; + _err_map[JERR_RMGR_BADRECTYPE] = "JERR_RMGR_BADRECTYPE: Attempted operation on inappropriate record type"; + + // class data_tok + _err_map[JERR_DTOK_ILLEGALSTATE] = "JERR_MTOK_ILLEGALSTATE: Attempted to change to illegal state."; + //_err_map[JERR_DTOK_RIDNOTSET] = "JERR_DTOK_RIDNOTSET: Record ID not set."; + + // class enq_map, txn_map + _err_map[JERR_MAP_DUPLICATE] = "JERR_MAP_DUPLICATE: Attempted to insert record into map using duplicate key."; + _err_map[JERR_MAP_NOTFOUND] = "JERR_MAP_NOTFOUND: Key not found in map."; + _err_map[JERR_MAP_LOCKED] = "JERR_MAP_LOCKED: Record ID locked by a pending transaction."; + + // class jinf + _err_map[JERR_JINF_CVALIDFAIL] = "JERR_JINF_CVALIDFAIL: Journal compatibility validation failure."; + _err_map[JERR_JINF_NOVALUESTR] = "JERR_JINF_NOVALUESTR: No value attribute found in jinf file."; + _err_map[JERR_JINF_BADVALUESTR] = "JERR_JINF_BADVALUESTR: Bad format for value attribute in jinf file"; + _err_map[JERR_JINF_JDATEMPTY] = "JERR_JINF_JDATEMPTY: Journal data files empty."; + _err_map[JERR_JINF_TOOMANYFILES] = "JERR_JINF_TOOMANYFILES: Too many journal data files."; + _err_map[JERR_JINF_INVALIDFHDR] = "JERR_JINF_INVALIDFHDR: Invalid journal data file header"; + _err_map[JERR_JINF_STAT] = "JERR_JINF_STAT: Error while trying to stat a journal data file"; + _err_map[JERR_JINF_NOTREGFILE] = "JERR_JINF_NOTREGFILE: Target journal data file is not a regular file"; + _err_map[JERR_JINF_BADFILESIZE] = "JERR_JINF_BADFILESIZE: Journal data file is of incorrect or unexpected size"; + _err_map[JERR_JINF_OWIBAD] = "JERR_JINF_OWIBAD: Journal data files have inconsistent OWI flags; >1 transition found in non-auto-expand or min-size journal"; + _err_map[JERR_JINF_ZEROLENFILE] = "JERR_JINF_ZEROLENFILE: Journal info file zero length"; + + //_err_map[] = ""; + + return true; +} + +const char* +jerrno::err_msg(const u_int32_t err_no) throw () +{ + _err_map_itr = _err_map.find(err_no); + if (_err_map_itr == _err_map.end()) + return "<Unknown error code>"; + return _err_map_itr->second; +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/jerrno.h b/cpp/src/qpid/legacystore/jrnl/jerrno.h new file mode 100644 index 0000000000..4c8b71c423 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jerrno.h @@ -0,0 +1,173 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jerrno.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::jerrno (journal error + * codes). See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_JERRNO_H +#define QPID_LEGACYSTORE_JRNL_JERRNO_H + +namespace mrg +{ +namespace journal +{ +class jerrno; +} +} + +#include <map> +#include <string> +#include <sys/types.h> + +namespace mrg +{ +namespace journal +{ + + /** + * \class jerrno + * \brief Class containing static error definitions and static map for error messages. + */ + class jerrno + { + static std::map<u_int32_t, const char*> _err_map; ///< Map of error messages + static std::map<u_int32_t, const char*>::iterator _err_map_itr; ///< Iterator + static bool _initialized; ///< Dummy flag, used to initialise map. + + public: + // generic errors + static const u_int32_t JERR__MALLOC; ///< Buffer memory allocation failed + static const u_int32_t JERR__UNDERFLOW; ///< Underflow error + static const u_int32_t JERR__NINIT; ///< Operation on uninitialized class + static const u_int32_t JERR__AIO; ///< AIO failure + static const u_int32_t JERR__FILEIO; ///< File read or write failure + static const u_int32_t JERR__RTCLOCK; ///< Reading real-time clock failed + static const u_int32_t JERR__PTHREAD; ///< pthread failure + static const u_int32_t JERR__TIMEOUT; ///< Timeout waiting for an event + static const u_int32_t JERR__UNEXPRESPONSE; ///< Unexpected response to call or event + static const u_int32_t JERR__RECNFOUND; ///< Record not found + static const u_int32_t JERR__NOTIMPL; ///< Not implemented + + // class jcntl + static const u_int32_t JERR_JCNTL_STOPPED; ///< Operation on stopped journal + static const u_int32_t JERR_JCNTL_READONLY; ///< Write operation on read-only journal + static const u_int32_t JERR_JCNTL_AIOCMPLWAIT; ///< Timeout waiting for AIOs to complete + static const u_int32_t JERR_JCNTL_UNKNOWNMAGIC; ///< Found record with unknown magic + static const u_int32_t JERR_JCNTL_NOTRECOVERED; ///< Req' recover() to be called first + static const u_int32_t JERR_JCNTL_RECOVERJFULL; ///< Journal data files full, cannot write + static const u_int32_t JERR_JCNTL_OWIMISMATCH; ///< OWI change found in unexpected location + + // class jdir + static const u_int32_t JERR_JDIR_NOTDIR; ///< Exists but is not a directory + static const u_int32_t JERR_JDIR_MKDIR; ///< Directory creation failed + static const u_int32_t JERR_JDIR_OPENDIR; ///< Directory open failed + static const u_int32_t JERR_JDIR_READDIR; ///< Directory read failed + static const u_int32_t JERR_JDIR_CLOSEDIR; ///< Directory close failed + static const u_int32_t JERR_JDIR_RMDIR; ///< Directory delete failed + static const u_int32_t JERR_JDIR_NOSUCHFILE; ///< File does not exist + static const u_int32_t JERR_JDIR_FMOVE; ///< File move failed + static const u_int32_t JERR_JDIR_STAT; ///< File stat failed + static const u_int32_t JERR_JDIR_UNLINK; ///< File delete failed + static const u_int32_t JERR_JDIR_BADFTYPE; ///< Bad or unknown file type (stat mode) + + // class fcntl + static const u_int32_t JERR_FCNTL_OPENWR; ///< Unable to open file for write + static const u_int32_t JERR_FCNTL_WRITE; ///< Unable to write to file + static const u_int32_t JERR_FCNTL_CLOSE; ///< File close failed + static const u_int32_t JERR_FCNTL_FILEOFFSOVFL; ///< Increased offset past file size + static const u_int32_t JERR_FCNTL_CMPLOFFSOVFL; ///< Increased cmpl offs past subm offs + static const u_int32_t JERR_FCNTL_RDOFFSOVFL; ///< Increased read offs past write offs + + // class lfmgr + static const u_int32_t JERR_LFMGR_BADAEFNUMLIM; ///< Bad auto-expand file number limit + static const u_int32_t JERR_LFMGR_AEFNUMLIMIT; ///< Exceeded auto-expand file number limit + static const u_int32_t JERR_LFMGR_AEDISABLED; ///< Attempted to expand with auto-expand disabled + + // class rrfc + static const u_int32_t JERR_RRFC_OPENRD; ///< Unable to open file for read + + // class jrec, enq_rec, deq_rec, txn_rec + static const u_int32_t JERR_JREC_BADRECHDR; ///< Invalid data record header + static const u_int32_t JERR_JREC_BADRECTAIL; ///< Invalid data record tail + + // class wmgr + static const u_int32_t JERR_WMGR_BADPGSTATE; ///< Page buffer in illegal state. + static const u_int32_t JERR_WMGR_BADDTOKSTATE; ///< Data token in illegal state. + static const u_int32_t JERR_WMGR_ENQDISCONT; ///< Enq. new dtok when previous part compl. + static const u_int32_t JERR_WMGR_DEQDISCONT; ///< Deq. new dtok when previous part compl. + static const u_int32_t JERR_WMGR_DEQRIDNOTENQ; ///< Deq. rid not enqueued + + // class rmgr + static const u_int32_t JERR_RMGR_UNKNOWNMAGIC; ///< Found record with unknown magic + static const u_int32_t JERR_RMGR_RIDMISMATCH; ///< RID mismatch between rec and dtok + //static const u_int32_t JERR_RMGR_FIDMISMATCH; ///< FID mismatch between emap and rrfc + static const u_int32_t JERR_RMGR_ENQSTATE; ///< Attempted read when wstate not ENQ + static const u_int32_t JERR_RMGR_BADRECTYPE; ///< Attempted op on incorrect rec type + + // class data_tok + static const u_int32_t JERR_DTOK_ILLEGALSTATE; ///< Attempted to change to illegal state +// static const u_int32_t JERR_DTOK_RIDNOTSET; ///< Record ID not set + + // class enq_map, txn_map + static const u_int32_t JERR_MAP_DUPLICATE; ///< Attempted to insert using duplicate key + static const u_int32_t JERR_MAP_NOTFOUND; ///< Key not found in map + static const u_int32_t JERR_MAP_LOCKED; ///< rid locked by pending txn + + // class jinf + static const u_int32_t JERR_JINF_CVALIDFAIL; ///< Compatibility validation failure + static const u_int32_t JERR_JINF_NOVALUESTR; ///< No value attr found in jinf file + static const u_int32_t JERR_JINF_BADVALUESTR; ///< Bad format for value attr in jinf file + static const u_int32_t JERR_JINF_JDATEMPTY; ///< Journal data files empty + static const u_int32_t JERR_JINF_TOOMANYFILES; ///< Too many journal data files + static const u_int32_t JERR_JINF_INVALIDFHDR; ///< Invalid file header + static const u_int32_t JERR_JINF_STAT; ///< Error while trying to stat a file + static const u_int32_t JERR_JINF_NOTREGFILE; ///< Target file is not a regular file + static const u_int32_t JERR_JINF_BADFILESIZE; ///< File is of incorrect or unexpected size + static const u_int32_t JERR_JINF_OWIBAD; ///< OWI inconsistent (>1 transition in non-ae journal) + static const u_int32_t JERR_JINF_ZEROLENFILE; ///< Journal info file is zero length (empty). + + // Negative returns for some functions + static const int32_t AIO_TIMEOUT; ///< Timeout waiting for AIO return + static const int32_t LOCK_TAKEN; ///< Attempted to take lock, but it was taken by another thread + /** + * \brief Method to access error message from known error number. + */ + static const char* err_msg(const u_int32_t err_no) throw (); + + private: + /** + * \brief Static function to initialize map. + */ + static bool __init(); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_JERRNO_H diff --git a/cpp/src/qpid/legacystore/jrnl/jexception.cpp b/cpp/src/qpid/legacystore/jrnl/jexception.cpp new file mode 100644 index 0000000000..5c571020e4 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jexception.cpp @@ -0,0 +1,183 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jexception.cpp + * + * Qpid asynchronous store plugin library + * + * Generic journal exception class mrg::journal::jexception. See comments + * in file jexception.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/jexception.h" + +#include <iomanip> +#include <sstream> +#include "qpid/legacystore/jrnl/jerrno.h" + +#define CATLEN(p) MAX_MSG_SIZE - std::strlen(p) - 1 + +namespace mrg +{ +namespace journal +{ + +jexception::jexception() throw (): + std::exception(), + _err_code(0) +{ + format(); +} + +jexception::jexception(const u_int32_t err_code) throw (): + std::exception(), + _err_code(err_code) +{ + format(); +} + +jexception::jexception(const char* additional_info) throw (): + std::exception(), + _err_code(0), + _additional_info(additional_info) +{ + format(); +} + +jexception::jexception(const std::string& additional_info) throw (): + std::exception(), + _err_code(0), + _additional_info(additional_info) +{ + format(); +} + +jexception::jexception(const u_int32_t err_code, const char* additional_info) throw (): + std::exception(), + _err_code(err_code), + _additional_info(additional_info) +{ + format(); +} + +jexception::jexception(const u_int32_t err_code, const std::string& additional_info) throw (): + std::exception(), + _err_code(err_code), + _additional_info(additional_info) +{ + format(); +} + +jexception::jexception(const u_int32_t err_code, const char* throwing_class, + const char* throwing_fn) throw (): + std::exception(), + _err_code(err_code), + _throwing_class(throwing_class), + _throwing_fn(throwing_fn) +{ + format(); +} + +jexception::jexception(const u_int32_t err_code, const std::string& throwing_class, + const std::string& throwing_fn) throw (): + std::exception(), + _err_code(err_code), + _throwing_class(throwing_class), + _throwing_fn(throwing_fn) +{ + format(); +} + +jexception::jexception(const u_int32_t err_code, const char* additional_info, + const char* throwing_class, const char* throwing_fn) throw (): + std::exception(), + _err_code(err_code), + _additional_info(additional_info), + _throwing_class(throwing_class), + _throwing_fn(throwing_fn) +{ + format(); +} + +jexception::jexception(const u_int32_t err_code, const std::string& additional_info, + const std::string& throwing_class, const std::string& throwing_fn) throw (): + std::exception(), + _err_code(err_code), + _additional_info(additional_info), + _throwing_class(throwing_class), + _throwing_fn(throwing_fn) +{ + format(); +} + +jexception::~jexception() throw () +{} + +void +jexception::format() +{ + const bool ai = !_additional_info.empty(); + const bool tc = !_throwing_class.empty(); + const bool tf = !_throwing_fn.empty(); + std::ostringstream oss; + oss << "jexception 0x" << std::hex << std::setfill('0') << std::setw(4) << _err_code << " "; + if (tc) + { + oss << _throwing_class; + if (tf) + oss << "::"; + else + oss << " "; + } + if (tf) + oss << _throwing_fn << "() "; + if (tc || tf) + oss << "threw " << jerrno::err_msg(_err_code); + if (ai) + oss << " (" << _additional_info << ")"; + _what.assign(oss.str()); +} + +const char* +jexception::what() const throw () +{ + return _what.c_str(); +} + +std::ostream& +operator<<(std::ostream& os, const jexception& je) +{ + os << je.what(); + return os; +} + +std::ostream& +operator<<(std::ostream& os, const jexception* jePtr) +{ + os << jePtr->what(); + return os; +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/jexception.h b/cpp/src/qpid/legacystore/jrnl/jexception.h new file mode 100644 index 0000000000..34d8373235 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jexception.h @@ -0,0 +1,142 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jexception.h + * + * Qpid asynchronous store plugin library + * + * Generic journal exception class mrg::journal::jexception (derived + * from class std::exception). Intended to serve as a common exception + * class for all more speicalized exceptions in the message journal. See + * class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_JEXCEPTION_H +#define QPID_LEGACYSTORE_JRNL_JEXCEPTION_H + +namespace mrg +{ +namespace journal +{ +class jexception; +} +} + +#include <cerrno> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <exception> +#include "qpid/legacystore/jrnl/jerrno.h" +#include <sstream> +#include <string> +#include <sys/types.h> + +// Macro for formatting commom system errors +#define FORMAT_SYSERR(errno) " errno=" << errno << " (" << std::strerror(errno) << ")" + +#define MALLOC_CHK(ptr, var, cls, fn) if(ptr == 0) { \ + clean(); \ + std::ostringstream oss; \ + oss << var << ": malloc() failed: " << FORMAT_SYSERR(errno); \ + throw jexception(jerrno::JERR__MALLOC, oss.str(), cls, fn); \ + } + +// TODO: The following is a temporary bug-tracking aid which forces a core. +// Replace with the commented out version below when BZ484048 is resolved. +#define PTHREAD_CHK(err, pfn, cls, fn) if(err != 0) { \ + std::ostringstream oss; \ + oss << cls << "::" << fn << "(): " << pfn; \ + errno = err; \ + ::perror(oss.str().c_str()); \ + ::abort(); \ + } +/* +#define PTHREAD_CHK(err, pfn, cls, fn) if(err != 0) { \ + std::ostringstream oss; \ + oss << pfn << " failed: " << FORMAT_SYSERR(err); \ + throw jexception(jerrno::JERR__PTHREAD, oss.str(), cls, fn); \ + } +*/ + +#define ASSERT(cond, msg) if(cond == 0) { \ + std::cerr << msg << std::endl; \ + ::abort(); \ + } + +namespace mrg +{ +namespace journal +{ + /** + * \class jexception + * \brief Generic journal exception class + */ + class jexception : public std::exception + { + private: + u_int32_t _err_code; + std::string _additional_info; + std::string _throwing_class; + std::string _throwing_fn; + std::string _what; + void format(); + + public: + jexception() throw (); + + jexception(const u_int32_t err_code) throw (); + + jexception(const char* additional_info) throw (); + jexception(const std::string& additional_info) throw (); + + jexception(const u_int32_t err_code, const char* additional_info) throw (); + jexception(const u_int32_t err_code, const std::string& additional_info) throw (); + + jexception(const u_int32_t err_code, const char* throwing_class, const char* throwing_fn) + throw (); + jexception(const u_int32_t err_code, const std::string& throwing_class, + const std::string& throwing_fn) throw (); + + jexception(const u_int32_t err_code, const char* additional_info, + const char* throwing_class, const char* throwing_fn) throw (); + jexception(const u_int32_t err_code, const std::string& additional_info, + const std::string& throwing_class, const std::string& throwing_fn) throw (); + + virtual ~jexception() throw (); + virtual const char* what() const throw (); // override std::exception::what() + + inline u_int32_t err_code() const throw () { return _err_code; } + inline const std::string additional_info() const throw () { return _additional_info; } + inline const std::string throwing_class() const throw () { return _throwing_class; } + inline const std::string throwing_fn() const throw () { return _throwing_fn; } + + friend std::ostream& operator<<(std::ostream& os, const jexception& je); + friend std::ostream& operator<<(std::ostream& os, const jexception* jePtr); + }; // class jexception + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_JEXCEPTION_H diff --git a/cpp/src/qpid/legacystore/jrnl/jinf.cpp b/cpp/src/qpid/legacystore/jrnl/jinf.cpp new file mode 100644 index 0000000000..4117bd3581 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jinf.cpp @@ -0,0 +1,540 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jinf.cpp + * + * Qpid asynchronous store plugin library + * + * This file contains the code for the mrg::journal::jinf class. + * + * See jinf.h comments for details of this class. + * + * \author Kim van der Riet + */ + +#include "jrnl/jinf.h" + +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <fstream> +#include "qpid/legacystore/jrnl/file_hdr.h" +#include "qpid/legacystore/jrnl/jcntl.h" +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/lp_map.h" +#include <sstream> +#include <sys/stat.h> + +namespace mrg +{ +namespace journal +{ + +jinf::jinf(const std::string& jinf_filename, bool validate_flag): + _jver(0), + _filename(jinf_filename), + _num_jfiles(0), + _ae(false), + _ae_max_jfiles(0), + _jfsize_sblks(0), + _sblk_size_dblks(0), + _dblk_size(0), + _wcache_pgsize_sblks(0), + _wcache_num_pages(0), + _rcache_pgsize_sblks(0), + _rcache_num_pages(0), + _tm_ptr(0), + _valid_flag(false), + _analyzed_flag(false), + _initial_owi(false), + _frot(false) +{ + read(_filename); + if (validate_flag) + validate(); +} + +jinf::jinf(const std::string& jid, const std::string& jdir, const std::string& base_filename, const u_int16_t num_jfiles, + const bool auto_expand, const u_int16_t ae_max_jfiles, const u_int32_t jfsize_sblks, + const u_int32_t wcache_pgsize_sblks, const u_int16_t wcache_num_pages, const timespec& ts): + _jver(RHM_JDAT_VERSION), + _jid(jid), + _jdir(jdir), + _base_filename(base_filename), + _ts(ts), + _num_jfiles(num_jfiles), + _ae(auto_expand), + _ae_max_jfiles(ae_max_jfiles), + _jfsize_sblks(jfsize_sblks), + _sblk_size_dblks(JRNL_SBLK_SIZE), + _dblk_size(JRNL_DBLK_SIZE), + _wcache_pgsize_sblks(wcache_pgsize_sblks), + _wcache_num_pages(wcache_num_pages), + _rcache_pgsize_sblks(JRNL_RMGR_PAGE_SIZE), + _rcache_num_pages(JRNL_RMGR_PAGES), + _tm_ptr(std::localtime(&ts.tv_sec)), + _valid_flag(false), + _analyzed_flag(false), + _initial_owi(false) +{ + set_filename(); +} + +jinf::~jinf() +{} + +void +jinf::validate() +{ + bool err = false; + std::ostringstream oss; + if (_jver != RHM_JDAT_VERSION) + { + oss << "File \"" << _filename << "\": "; + oss << "RHM_JDAT_VERSION mismatch: found=" << (int)_jver; + oss << "; required=" << RHM_JDAT_VERSION << std::endl; + err = true; + } + if (_num_jfiles < JRNL_MIN_NUM_FILES) + { + oss << "File \"" << _filename << "\": "; + oss << "Number of journal files too small: found=" << _num_jfiles; + oss << "; minimum=" << JRNL_MIN_NUM_FILES << std::endl; + err = true; + } + if (_num_jfiles > JRNL_MAX_NUM_FILES) + { + oss << "File \"" << _filename << "\": "; + oss << "Number of journal files too large: found=" << _num_jfiles; + oss << "; maximum=" << JRNL_MAX_NUM_FILES << std::endl; + err = true; + } + if (_ae) + { + if (_ae_max_jfiles < _num_jfiles) + { + oss << "File \"" << _filename << "\": "; + oss << "Number of journal files exceeds auto-expansion limit: found=" << _num_jfiles; + oss << "; maximum=" << _ae_max_jfiles; + err = true; + } + if (_ae_max_jfiles > JRNL_MAX_NUM_FILES) + { + oss << "File \"" << _filename << "\": "; + oss << "Auto-expansion file limit too large: found=" << _ae_max_jfiles; + oss << "; maximum=" << JRNL_MAX_NUM_FILES; + err = true; + } + } + if (_jfsize_sblks < JRNL_MIN_FILE_SIZE) + { + oss << "File \"" << _filename << "\": "; + oss << "Journal file size too small: found=" << _jfsize_sblks; + oss << "; minimum=" << JRNL_MIN_FILE_SIZE << " (sblks)" << std::endl; + err = true; + } + if (_sblk_size_dblks != JRNL_SBLK_SIZE) + { + oss << "File \"" << _filename << "\": "; + oss << "JRNL_SBLK_SIZE mismatch: found=" << _sblk_size_dblks; + oss << "; required=" << JRNL_SBLK_SIZE << std::endl; + err = true; + } + if (_dblk_size != JRNL_DBLK_SIZE) + { + oss << "File \"" << _filename << "\": "; + oss << "JRNL_DBLK_SIZE mismatch: found=" << _dblk_size; + oss << "; required=" << JRNL_DBLK_SIZE << std::endl; + err = true; + } + if (err) + throw jexception(jerrno::JERR_JINF_CVALIDFAIL, oss.str(), "jinf", "validate"); + _valid_flag = true; +} + +void +jinf::analyze() +{ + lp_map early_map; // map for all owi flags same as pfid 0 + lp_map late_map; // map for all owi flags opposite to pfid 0 + bool late_latch = false; // latch for owi switchover + + if (!_valid_flag) + validate(); + bool done = false; + for (u_int16_t pfid=0; pfid<_num_jfiles && !done; pfid++) + { + std::ostringstream oss; + if (_jdir.at(_jdir.size() - 1) == '/') + oss << _jdir << _base_filename << "."; + else + oss << _jdir << "/" << _base_filename << "."; + oss << std::setw(4) << std::setfill('0') << std::hex << pfid; + oss << "." << JRNL_DATA_EXTENSION; + + // Check size of each file is consistent and expected + u_int32_t fsize = get_filesize(oss.str()); + if (fsize != (_jfsize_sblks + 1) * _sblk_size_dblks * _dblk_size) + { + std::ostringstream oss1; + oss1 << "File \"" << oss.str() << "\": size=" << fsize << "; expected=" << ((_jfsize_sblks + 1) * _sblk_size_dblks * _dblk_size); + throw jexception(jerrno::JERR_JINF_BADFILESIZE, oss1.str(), "jinf", "analyze"); + } + + std::ifstream jifs(oss.str().c_str()); + if (!jifs.good()) + throw jexception(jerrno::JERR__FILEIO, oss.str(), "jinf", "analyze"); + file_hdr fhdr; + jifs.read((char*)&fhdr, sizeof(fhdr)); + if (fhdr._magic != RHM_JDAT_FILE_MAGIC) // No file header + { + if (fhdr._magic != 0) + throw jexception(jerrno::JERR_JINF_INVALIDFHDR, oss.str(), "jinf", "analyze"); + if (!pfid) // pfid 0 == lid 0 cannot be empty + throw jexception(jerrno::JERR_JINF_JDATEMPTY, oss.str(), "jinf", "analyze"); + _frot = true; + done = true; + } + else + { + assert(pfid == fhdr._pfid); + if (pfid == 0) + { + _initial_owi = fhdr.get_owi(); + early_map.insert(fhdr._lfid, pfid); + } + else + { + if (_initial_owi == fhdr.get_owi()) + { + early_map.insert(fhdr._lfid, pfid); + if (late_latch && (!_ae || _num_jfiles == JRNL_MIN_NUM_FILES)) + throw jexception(jerrno::JERR_JINF_OWIBAD, oss.str(), "jinf", "analyze"); + } + else + { + late_map.insert(fhdr._lfid, pfid); + late_latch = true; + } + } + } + jifs.close(); + } // for (pfid) + + // If this is not the first rotation, all files should be in either early or late maps + if (!_frot) assert(early_map.size() + late_map.size() == _num_jfiles); + + _pfid_list.clear(); + late_map.get_pfid_list(_pfid_list); + early_map.get_pfid_list(_pfid_list); + + // Check OWI consistency +// for (u_int16_t lfid=0; lfid<_num_jfiles && !done; lfid++) +// { +// throw jexception(jerrno::JERR_JINF_OWIBAD, oss.str(), "jinf", "analyze"); +// } + + _analyzed_flag = true; +} + +void +jinf::write() +{ + std::ostringstream oss; + oss << _jdir << "/" << _base_filename << "." << JRNL_INFO_EXTENSION; + std::ofstream of(oss.str().c_str(), std::ofstream::out | std::ofstream::trunc); + if (!of.good()) + throw jexception(jerrno::JERR__FILEIO, oss.str(), "jinf", "write"); + of << xml_str(); + of.close(); +} + +u_int16_t +jinf::incr_num_jfiles() +{ + if (_num_jfiles >= JRNL_MAX_NUM_FILES) + throw jexception(jerrno::JERR_JINF_TOOMANYFILES, "jinf", "incr_num_jfiles"); + return ++_num_jfiles; +} + +u_int16_t +jinf::get_first_pfid() +{ + if (!_analyzed_flag) + analyze(); + return *_pfid_list.begin(); +} + +u_int16_t +jinf::get_last_pfid() +{ + if (!_analyzed_flag) + analyze(); + return *_pfid_list.rbegin(); +} + +jinf::pfid_list& +jinf::get_pfid_list() +{ + if (!_analyzed_flag) + analyze(); + return _pfid_list; +} + +void +jinf::get_normalized_pfid_list(pfid_list& pfid_list) +{ + if (!_analyzed_flag) + analyze(); + pfid_list.clear(); + u_int16_t s = _pfid_list.size(); + u_int16_t iz = 0; // index of 0 value + while (_pfid_list[iz] && iz < s) + iz++; + assert(_pfid_list[iz] == 0); + for (u_int16_t i = iz; i < iz + s; i++) + pfid_list.push_back(_pfid_list[i % s]); + assert(pfid_list[0] == 0); + assert(pfid_list.size() == s); +} + +bool +jinf::get_initial_owi() +{ + if (!_analyzed_flag) + analyze(); + return _initial_owi; +} + +bool +jinf::get_frot() +{ + if (!_analyzed_flag) + analyze(); + return _frot; +} + +std::string +jinf::to_string() const +{ + std::ostringstream oss; + oss << std::setfill('0'); + oss << "Journal ID \"" << _jid << "\" initialized " << (_tm_ptr->tm_year + 1900) << "/"; + oss << std::setw(2) << (_tm_ptr->tm_mon + 1) << "/" << std::setw(2) << _tm_ptr->tm_mday << " "; + oss << std::setw(2) << _tm_ptr->tm_hour << ":" << std::setw(2) << _tm_ptr->tm_min << ":"; + oss << std::setw(2) << _tm_ptr->tm_sec << "." << std::setw(9) << _ts.tv_nsec << ":" << std::endl; + oss << " Journal directory: \"" << _jdir << "\"" << std::endl; + oss << " Journal base filename: \"" << _base_filename << "\"" << std::endl; + oss << " Journal version: " << (unsigned)_jver << std::endl; + oss << " Number of journal files: " << _num_jfiles << std::endl; +// TODO: Uncomment these lines when auto-expand is enabled. +// oss << " Auto-expand mode: " << (_ae ? "enabled" : "disabled") << std::endl; +// if (_ae) oss << " Max. number of journal files (in auto-expand mode): " << _ae_max_jfiles << std::endl; + oss << " Journal file size: " << _jfsize_sblks << " sblks" << std::endl; + oss << " Softblock size (JRNL_SBLK_SIZE): " << _sblk_size_dblks << " dblks" << std::endl; + oss << " Datablock size (JRNL_DBLK_SIZE): " << _dblk_size << " bytes" << std::endl; + oss << " Write page size: " << _wcache_pgsize_sblks << " sblks" << std::endl; + oss << " Number of write pages: " << _wcache_num_pages << std::endl; + oss << " Read page size (JRNL_RMGR_PAGE_SIZE): " << _rcache_pgsize_sblks << " sblks" << std::endl; + oss << " Number of read pages (JRNL_RMGR_PAGES): " << _rcache_num_pages << std::endl; + return oss.str(); +} + +std::string +jinf::xml_str() const +{ + // TODO: This is *not* an XML writer, rather for simplicity, it uses literals. I'm sure a more elegant way can be + // found to do this using the real thing... + + std::ostringstream oss; + oss << std::setfill('0'); + oss << "<?xml version=\"1.0\" ?>" << std::endl; + oss << "<jrnl>" << std::endl; + oss << " <journal_version value=\"" << (unsigned)_jver << "\" />" << std::endl; + oss << " <journal_id>" << std::endl; + oss << " <id_string value=\"" << _jid << "\" />" << std::endl; + oss << " <directory value=\"" << _jdir << "\" />" << std::endl; + oss << " <base_filename value=\"" << _base_filename << "\" />" << std::endl; + oss << " </journal_id>" << std::endl; + oss << " <creation_time>" << std::endl; + oss << " <seconds value=\"" << _ts.tv_sec << "\" />" << std::endl; + oss << " <nanoseconds value=\"" << _ts.tv_nsec << "\" />" << std::endl; + oss << " <string value=\"" << (_tm_ptr->tm_year + 1900) << "/"; + oss << std::setw(2) << (_tm_ptr->tm_mon + 1) << "/" << std::setw(2) << _tm_ptr->tm_mday << " "; + oss << std::setw(2) << _tm_ptr->tm_hour << ":" << std::setw(2) << _tm_ptr->tm_min << ":"; + oss << std::setw(2) << _tm_ptr->tm_sec << "." << std::setw(9) << _ts.tv_nsec; + oss << "\" />" << std::endl; + oss << " </creation_time>" << std::endl; + oss << " <journal_file_geometry>" << std::endl; + oss << " <number_jrnl_files value=\"" << _num_jfiles << "\" />" << std::endl; + oss << " <auto_expand value=\"" << (_ae ? "true" : "false") << "\" />" << std::endl; + if (_ae) oss << " <auto_expand_max_jrnl_files value=\"" << _ae_max_jfiles << "\" />" << std::endl; + oss << " <jrnl_file_size_sblks value=\"" << _jfsize_sblks << "\" />" << std::endl; + oss << " <JRNL_SBLK_SIZE value=\"" << _sblk_size_dblks << "\" />" << std::endl; + oss << " <JRNL_DBLK_SIZE value=\"" << _dblk_size << "\" />" << std::endl; + oss << " </journal_file_geometry>" << std::endl; + oss << " <cache_geometry>" << std::endl; + oss << " <wcache_pgsize_sblks value=\"" << _wcache_pgsize_sblks << "\" />" << std::endl; + oss << " <wcache_num_pages value=\"" << _wcache_num_pages << "\" />" << std::endl; + oss << " <JRNL_RMGR_PAGE_SIZE value=\"" << _rcache_pgsize_sblks << "\" />" << std::endl; + oss << " <JRNL_RMGR_PAGES value=\"" << _rcache_num_pages << "\" />" << std::endl; + oss << " </cache_geometry>" << std::endl; + oss << "</jrnl>" << std::endl; + return oss.str(); +} + +void +jinf::set_filename() +{ + std::ostringstream oss; + oss << _jdir << "/" << _base_filename << "." << JRNL_INFO_EXTENSION; + _filename = oss.str().c_str(); +} + +void +jinf::read(const std::string& jinf_filename) +{ + // TODO: This is *not* an XML reader, rather for simplicity, it is a brute-force line reader which relies on string + // recognition. It relies on the format of xml_str() above; it will not handle a XML restructuring. + // *** Can it be replaced cheaply by a real XML reader? Should it be, or is this sufficient? *** + + char buff[1024]; // limit of line input length + std::ifstream jinfs(jinf_filename.c_str()); + if (!jinfs.good()) + throw jexception(jerrno::JERR__FILEIO, jinf_filename.c_str(), "jinf", "read"); + u_int32_t charcnt = 0; + while (jinfs.good()) + { + jinfs.getline(buff, 1023); + charcnt += std::strlen(buff); + if (std::strstr(buff, "journal_version")) + _jver = u_int16_value(buff); + else if(std::strstr(buff, "id_string")) + string_value(_jid, buff); + else if(std::strstr(buff, "directory")) + string_value(_jdir, buff); + else if(std::strstr(buff, "base_filename")) + string_value(_base_filename, buff); + else if(std::strstr(buff, "number_jrnl_files")) + _num_jfiles = u_int16_value(buff); + else if(std::strstr(buff, "auto_expand_max_jrnl_files")) + _ae_max_jfiles = u_int16_value(buff); + else if(std::strstr(buff, "auto_expand")) + _ae = bool_value(buff); + else if(std::strstr(buff, "jrnl_file_size_sblks")) + _jfsize_sblks = u_int32_value(buff); + else if(std::strstr(buff, "JRNL_SBLK_SIZE")) + _sblk_size_dblks = u_int16_value(buff); + else if(std::strstr(buff, "JRNL_DBLK_SIZE")) + _dblk_size = u_int32_value(buff); + else if(std::strstr(buff, "wcache_pgsize_sblks")) + _wcache_pgsize_sblks = u_int32_value(buff); + else if(std::strstr(buff, "wcache_num_pages")) + _wcache_num_pages = u_int32_value(buff); + else if(std::strstr(buff, "JRNL_RMGR_PAGE_SIZE")) + _rcache_pgsize_sblks = u_int32_value(buff); + else if(std::strstr(buff, "JRNL_RMGR_PAGES")) + _rcache_num_pages = u_int32_value(buff); + else if(std::strstr(buff, "nanoseconds")) + _ts.tv_nsec = u_int32_value(buff); + else if(std::strstr(buff, "seconds")) + { + _ts.tv_sec = u_int32_value(buff); + _tm_ptr = std::localtime(&_ts.tv_sec); + } + } + jinfs.close(); + if (charcnt == 0) + throw jexception(jerrno::JERR_JINF_ZEROLENFILE, jinf_filename.c_str(), "jinf", "read"); +} + +bool +jinf::bool_value(char* line) const +{ + return std::strcmp(find_value(line), "true") == 0; +} + +u_int16_t +jinf::u_int16_value(char* line) const +{ + return std::atoi(find_value(line)); +} + +u_int32_t +jinf::u_int32_value(char* line) const +{ + return std::atol(find_value(line)); +} + +std::string& +jinf::string_value(std::string& str, char* line) const +{ + str.assign(find_value(line)); + return str; +} + +char* +jinf::find_value(char* line) const +{ + const char* target1_str = "value=\""; + int target2_char = '\"'; + char* t1 = std::strstr(line, target1_str); + if (t1 == 0) + { + std::ostringstream oss; + oss << "File \"" << _filename << "\": line=" << line; + throw jexception(jerrno::JERR_JINF_NOVALUESTR, oss.str(), "jinf", "find_value"); + } + t1 += std::strlen(target1_str); + + char* t2 = std::strchr(t1, target2_char); + if (t2 == 0) + { + std::ostringstream oss; + oss << "File \"" << _filename << "\": line=" << line; + throw jexception(jerrno::JERR_JINF_BADVALUESTR, oss.str(), "jinf", "find_value"); + } + *t2 = '\0'; + return t1; +} + +u_int32_t +jinf::get_filesize(const std::string& file_name) const +{ + struct stat s; + if (::stat(file_name.c_str(), &s)) + { + std::ostringstream oss; + oss << "stat: file=\"" << file_name << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_JINF_STAT, oss.str(), "jinf", "get_filesize"); + } + if (!S_ISREG(s.st_mode)) // not a regular file, + { + std::ostringstream oss; + oss << "File \"" << file_name << "\" is not a regular file: mode=0x" << std::hex << s.st_mode; + throw jexception(jerrno::JERR_JINF_NOTREGFILE, oss.str(), "jinf", "get_filesize"); + } + return u_int32_t(s.st_size); +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/jinf.h b/cpp/src/qpid/legacystore/jrnl/jinf.h new file mode 100644 index 0000000000..73f5386a19 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jinf.h @@ -0,0 +1,133 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jinf.h + * + * Qpid asynchronous store plugin library + * + * This file contains the code for the mrg::journal::jinf class. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_JINF_H +#define QPID_LEGACYSTORE_JRNL_JINF_H + +#include <ctime> +#include <string> +#include <sys/types.h> +#include <vector> + +namespace mrg +{ +namespace journal +{ + /** + * \class jinf + * \brief Class to handle the journal information file <basename>.jinf. + */ + class jinf + { + public: + typedef std::vector<u_int16_t> pfid_list; // pfids + typedef pfid_list::const_iterator pfidl_citr; + + private: + u_int8_t _jver; + std::string _jid; + std::string _jdir; + std::string _base_filename; + std::string _filename; + timespec _ts; + u_int16_t _num_jfiles; + bool _ae; + u_int32_t _ae_max_jfiles; + u_int32_t _jfsize_sblks; + u_int16_t _sblk_size_dblks; + u_int32_t _dblk_size; + u_int32_t _wcache_pgsize_sblks; + u_int16_t _wcache_num_pages; + u_int32_t _rcache_pgsize_sblks; + u_int16_t _rcache_num_pages; + std::tm* _tm_ptr; + bool _valid_flag; + bool _analyzed_flag; + pfid_list _pfid_list; + bool _initial_owi; + bool _frot; + + public: + // constructor for reading existing jinf file + jinf(const std::string& jinf_filename, bool validate_flag); + // constructor for writing jinf file + jinf(const std::string& jid, const std::string& jdir, const std::string& base_filename, + const u_int16_t num_jfiles, const bool auto_expand, const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks, const u_int32_t wcache_pgsize_sblks, const u_int16_t wcache_num_pages, + const timespec& ts); + virtual ~jinf(); + + void validate(); + void analyze(); + void write(); + + inline u_int8_t jver() const { return _jver; } + inline const std::string& jid() const { return _jid; } + inline const std::string& jdir() const { return _jdir; } + inline void set_jdir(const std::string& jdir) { _jdir = jdir; } + inline const std::string& base_filename() const { return _base_filename; } + inline const timespec& ts() const { return _ts; } + inline u_int16_t num_jfiles() const { return _num_jfiles; } + u_int16_t incr_num_jfiles(); + inline bool is_ae() const { return _ae; } + inline u_int16_t ae_max_jfiles() const { return _ae_max_jfiles; } + inline u_int32_t jfsize_sblks() const { return _jfsize_sblks; } + inline u_int16_t sblk_size_dblks() const { return _sblk_size_dblks; } + inline u_int32_t dblk_size() const { return _dblk_size; } + inline u_int32_t wcache_pgsize_sblks() const { return _wcache_pgsize_sblks; } + inline u_int16_t wcache_num_pages() const { return _wcache_num_pages; } + inline u_int32_t rcache_pgsize_sblks() const { return _rcache_pgsize_sblks; } + inline u_int16_t rcache_num_pages() const { return _rcache_num_pages; } + u_int16_t get_first_pfid(); + u_int16_t get_last_pfid(); + pfid_list& get_pfid_list(); + void get_normalized_pfid_list(pfid_list& pfid_list); + bool get_initial_owi(); + bool get_frot(); + + std::string to_string() const; + std::string xml_str() const; + + private: + void set_filename(); + void read(const std::string& jinf_filename); + bool bool_value(char* line) const; + u_int16_t u_int16_value(char* line) const; + u_int32_t u_int32_value(char* line) const; + std::string& string_value(std::string& str, char* line) const; + char* find_value(char* line) const; + u_int32_t get_filesize(const std::string& file_name) const; + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_JINF_H diff --git a/cpp/src/qpid/legacystore/jrnl/jrec.cpp b/cpp/src/qpid/legacystore/jrnl/jrec.cpp new file mode 100644 index 0000000000..61b9b6cc9b --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jrec.cpp @@ -0,0 +1,119 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jrec.cpp + * + * Qpid asynchronous store plugin library + * + * File containing source code for class mrg::journal::jrec (abstract journal + * jrecord). See comments in file jrec.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/jrec.h" + +#include <iomanip> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +jrec::jrec() {} +jrec::~jrec() {} + +void +jrec::chk_hdr(const rec_hdr& hdr) +{ + if (hdr._magic == 0) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "enq magic NULL: rid=0x" << hdr._rid; + throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "jrec", "chk_hdr"); + } + if (hdr._version != RHM_JDAT_VERSION) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "version: rid=0x" << hdr._rid; + oss << ": expected=0x" << std::setw(2) << (int)RHM_JDAT_VERSION; + oss << " read=0x" << std::setw(2) << (int)hdr._version; + throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "jrec", "chk_hdr"); + } +#if defined (JRNL_LITTLE_ENDIAN) + u_int8_t endian_flag = RHM_LENDIAN_FLAG; +#else + u_int8_t endian_flag = RHM_BENDIAN_FLAG; +#endif + if (hdr._eflag != endian_flag) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "endian_flag: rid=" << hdr._rid; + oss << ": expected=0x" << std::setw(2) << (int)endian_flag; + oss << " read=0x" << std::setw(2) << (int)hdr._eflag; + throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "jrec", "chk_hdr"); + } +} + +void +jrec::chk_rid(const rec_hdr& hdr, const u_int64_t rid) +{ + if (hdr._rid != rid) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "rid mismatch: expected=0x" << rid; + oss << " read=0x" << hdr._rid; + throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "jrec", "chk_hdr"); + } +} + +void +jrec::chk_tail(const rec_tail& tail, const rec_hdr& hdr) +{ + if (tail._xmagic != ~hdr._magic) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "magic: rid=0x" << hdr._rid; + oss << ": expected=0x" << ~hdr._magic; + oss << " read=0x" << tail._xmagic; + throw jexception(jerrno::JERR_JREC_BADRECTAIL, oss.str(), "jrec", "chk_tail"); + } + if (tail._rid != hdr._rid) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "rid: rid=0x" << hdr._rid; + oss << ": read=0x" << tail._rid; + throw jexception(jerrno::JERR_JREC_BADRECTAIL, oss.str(), "jrec", "chk_tail"); + } +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/jrec.h b/cpp/src/qpid/legacystore/jrnl/jrec.h new file mode 100644 index 0000000000..9d0771cabd --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/jrec.h @@ -0,0 +1,183 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file jrec.h + * + * Qpid asynchronous store plugin library + * + * File containing source code for class mrg::journal::jrec (abstract journal + * jrecord). See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_JREC_H +#define QPID_LEGACYSTORE_JRNL_JREC_H + +namespace mrg +{ +namespace journal +{ +class jrec; +} +} + +#include <cstddef> +#include <fstream> +#include "qpid/legacystore/jrnl/rec_hdr.h" +#include "qpid/legacystore/jrnl/rec_tail.h" +#include <string> +#include <sys/types.h> + +namespace mrg +{ +namespace journal +{ + + /** + * \class jrec + * \brief Abstract class for all file jrecords, both data and log. This class establishes + * the common data format and structure for these jrecords. + */ + class jrec + { + public: + jrec(); + virtual ~jrec(); + + /** + * \brief Encode this instance of jrec into the write buffer at the disk-block-aligned + * pointer wptr starting at position rec_offs_dblks in the encoded record to a + * maximum size of max_size_dblks. + * + * This call encodes the content of the data contianed in this instance of jrec into a + * disk-softblock-aligned (defined by JRNL_SBLK_SIZE) buffer pointed to by parameter + * wptr. No more than paramter max_size_dblks data-blocks may be written to the buffer. + * The parameter rec_offs_dblks is the offset in data-blocks within the fully encoded + * data block this instance represents at which to start encoding. + * + * Encoding entails writing the record header (struct enq_hdr), the data and the record tail + * (struct enq_tail). The record must be data-block-aligned (defined by JRNL_DBLK_SIZE), + * thus any remaining space in the final data-block is ignored; the returned value is the + * number of data-blocks consumed from the page by the encode action. Provided the initial + * alignment requirements are met, records may be of arbitrary size and may span multiple + * data-blocks, disk-blocks and/or pages. + * + * Since the record size in data-blocks is known, the general usage pattern is to call + * encode() as many times as is needed to fully encode the data. Each call to encode() + * will encode as much of the record as it can to what remains of the current page cache, + * and will return the number of data-blocks actually encoded. + * + * <b>Example:</b> Assume that record r1 was previously written to page 0, and that this + * is an instance representing record r2. Being larger than the page size ps, r2 would span + * multiple pages as follows: + * <pre> + * |<---ps--->| + * +----------+----------+----------+----... + * | |r2a| r2b | r2c | | + * |<-r1-><----------r2----------> | + * +----------+----------+----------+----... + * page: p0 p1 p2 + * </pre> + * Encoding record r2 will require multiple calls to encode; one for each page which + * is involved. Record r2 is divided logically into sections r2a, r2b and r2c at the + * points where the page boundaries intersect with the record. Assuming a page size + * of ps, the page boundary pointers are represented by their names p0, p1... and the + * sizes of the record segments are represented by their names r1, r2a, r2b..., the calls + * should be as follows: + * <pre> + * encode(p0+r1, 0, ps-r1); (returns r2a data-blocks) + * encode(p1, r2a, ps); (returns r2b data-blocks which equals ps) + * encode(p2, r2a+r2b, ps); (returns r2c data-blocks) + * </pre> + * + * \param wptr Data-block-aligned pointer to position in page buffer where encoding is to + * take place. + * \param rec_offs_dblks Offset in data-blocks within record from which to start encoding. + * \param max_size_dblks Maximum number of data-blocks to write to pointer wptr. + * \returns Number of data-blocks encoded. + */ + virtual u_int32_t encode(void* wptr, u_int32_t rec_offs_dblks, + u_int32_t max_size_dblks) = 0; + + /** + * \brief Decode into this instance of jrec from the read buffer at the disk-block-aligned + * pointer rptr starting at position jrec_offs_dblks in the encoded record to a + * maximum size of max_size_blks. + * + * This call decodes a record in the page buffer pointed to by the data-block-aligned + * (defined by JRNL_DBLK_SIZE) parameter rptr into this instance of jrec. No more than + * paramter max_size_dblks data-blocks may be read from the buffer. The parameter + * jrec_offs_dblks is the offset in data-blocks within the encoded record at which to start + * decoding. + * + * Decoding entails reading the record header, the data and the tail. The record is + * data-block-aligned (defined by JRNL_DBLK_SIZE); the returned value is the number of + * data-blocks read from the buffer by the decode action. As the record data size is only + * known once the header is read, the number of calls required to complete reading the + * record will depend on the vlaues within this instance which are set when the + * header is decoded. + * + * A non-zero value for jrec_offs_dblks implies that this is not the first call to + * decode and the record data will be appended at this offset. + * + * \param h Reference to instance of struct hdr, already read from page buffer and used + * to determine record type + * \param rptr Data-block-aligned pointer to position in page buffer where decoding is to + * begin. + * \param rec_offs_dblks Offset within record from which to start appending the decoded + * record. + * \param max_size_dblks Maximum number of data-blocks to read from pointer rptr. + * \returns Number of data-blocks read (consumed). + */ + virtual u_int32_t decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, + u_int32_t max_size_dblks) = 0; + + virtual bool rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs) = 0; + + virtual std::string& str(std::string& str) const = 0; + virtual std::size_t data_size() const = 0; + virtual std::size_t xid_size() const = 0; + virtual std::size_t rec_size() const = 0; + inline virtual u_int32_t rec_size_dblks() const { return size_dblks(rec_size()); } + static inline u_int32_t size_dblks(const std::size_t size) + { return size_blks(size, JRNL_DBLK_SIZE); } + static inline u_int32_t size_sblks(const std::size_t size) + { return size_blks(size, JRNL_DBLK_SIZE * JRNL_SBLK_SIZE); } + static inline u_int32_t size_blks(const std::size_t size, const std::size_t blksize) + { return (size + blksize - 1)/blksize; } + virtual u_int64_t rid() const = 0; + + protected: + virtual void chk_hdr() const = 0; + virtual void chk_hdr(u_int64_t rid) const = 0; + virtual void chk_tail() const = 0; + static void chk_hdr(const rec_hdr& hdr); + static void chk_rid(const rec_hdr& hdr, u_int64_t rid); + static void chk_tail(const rec_tail& tail, const rec_hdr& hdr); + virtual void clean() = 0; + }; // class jrec + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_JREC_H diff --git a/cpp/src/qpid/legacystore/jrnl/lp_map.cpp b/cpp/src/qpid/legacystore/jrnl/lp_map.cpp new file mode 100644 index 0000000000..8024ddadd2 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/lp_map.cpp @@ -0,0 +1,82 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file lp_map.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::lp_map (logical file map). See + * comments in file lp_map.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/lp_map.h" + +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ +lp_map::lp_map() : _map() {} +lp_map::~lp_map() {} + +void +lp_map::insert(u_int16_t lfid, u_int16_t pfid) +{ + lfpair ip = lfpair(lfid, pfid); + lfret ret = _map.insert(ip); + if (ret.second == false) + { + std::ostringstream oss; + oss << std::hex << "lfid=0x" << lfid << " pfid=0x" << pfid; + throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "lp_map", "insert"); + } +} + +void +lp_map::get_pfid_list(std::vector<u_int16_t>& pfid_list) +{ + for (lp_map_citr_t i = _map.begin(); i != _map.end(); i++) + pfid_list.push_back(i->second); +} + +// debug aid +std::string +lp_map::to_string() +{ + std::ostringstream oss; + oss << "{lfid:pfid "; + for (lp_map_citr_t i=_map.begin(); i!=_map.end(); i++) + { + if (i != _map.begin()) oss << ", "; + oss << (*i).first << ":" << (*i).second; + } + oss << "}"; + return oss.str(); +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/lp_map.h b/cpp/src/qpid/legacystore/jrnl/lp_map.h new file mode 100644 index 0000000000..c43cbc0173 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/lp_map.h @@ -0,0 +1,83 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file lp_map.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::lp_map (logical file map). + * See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_LP_MAP_H +#define QPID_LEGACYSTORE_JRNL_LP_MAP_H + +#include <map> +#include <string> +#include <sys/types.h> +#include <vector> + +namespace mrg +{ +namespace journal +{ + /** + * \class lp_map + * \brief Maps the logical file id (lfid) to the physical file id (pfid) in the journal. + * + * NOTE: NOT THREAD SAFE + */ + class lp_map + { + public: + typedef std::map<u_int16_t, u_int16_t> lp_map_t; + typedef lp_map_t::const_iterator lp_map_citr_t; + typedef lp_map_t::const_reverse_iterator lp_map_critr_t; + + private: + typedef std::pair<u_int16_t, u_int16_t> lfpair; + typedef std::pair<lp_map_t::iterator, bool> lfret; + lp_map_t _map; + + public: + lp_map(); + virtual ~lp_map(); + + void insert(u_int16_t lfid, u_int16_t pfid); + inline u_int16_t size() const { return u_int16_t(_map.size()); } + inline bool empty() const { return _map.empty(); } + inline lp_map_citr_t begin() { return _map.begin(); } + inline lp_map_citr_t end() { return _map.end(); } + inline lp_map_critr_t rbegin() { return _map.rbegin(); } + inline lp_map_critr_t rend() { return _map.rend(); } + void get_pfid_list(std::vector<u_int16_t>& pfid_list); + + // debug aid + std::string to_string(); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_LP_MAP_H diff --git a/cpp/src/qpid/legacystore/jrnl/lpmgr.cpp b/cpp/src/qpid/legacystore/jrnl/lpmgr.cpp new file mode 100644 index 0000000000..d7b0c9f516 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/lpmgr.cpp @@ -0,0 +1,226 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file lpmgr.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::lpmgr (non-logging file + * handle), used for controlling journal log files. See comments in file + * lpmgr.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/lpmgr.h" + +#include <cassert> +#include <qpid/legacystore/jrnl/jerrno.h> +#include <qpid/legacystore/jrnl/jexception.h> + +namespace mrg +{ +namespace journal +{ + +lpmgr::lpmgr() : _ae(false), _ae_max_jfiles(0) +{} + +lpmgr::~lpmgr() +{ + finalize(); +} + +void +lpmgr::initialize(const u_int16_t num_jfiles, + const bool ae, + const u_int16_t ae_max_jfiles, + jcntl* const jcp, + new_obj_fn_ptr fp) +{ + assert(jcp != 0); + finalize(); + + // Validate params + if (ae && ae_max_jfiles > 0 && ae_max_jfiles <= num_jfiles) + { + std::ostringstream oss; + oss << "ae_max_jfiles (" << ae_max_jfiles << ") <= num_jfiles (" << num_jfiles << ")"; + throw jexception(jerrno::JERR_LFMGR_BADAEFNUMLIM, oss.str(), "lpmgr", "initialize"); + } + _ae = ae; + _ae_max_jfiles = ae_max_jfiles; + + const std::size_t num_res_files = ae + ? (ae_max_jfiles ? ae_max_jfiles : JRNL_MAX_NUM_FILES) + : num_jfiles; + _fcntl_arr.reserve(num_res_files); + append(jcp, fp, num_jfiles); +} + +void +lpmgr::recover(const rcvdat& rd, + jcntl* const jcp, + new_obj_fn_ptr fp) +{ + assert(jcp != 0); + finalize(); + + // Validate rd params + if (rd._aemjf > 0 && rd._aemjf <= rd._njf) + { + std::ostringstream oss; + oss << "ae_max_jfiles (" << rd._aemjf << ") <= num_jfiles (" << rd._njf << ")"; + throw jexception(jerrno::JERR_LFMGR_BADAEFNUMLIM, oss.str(), "lpmgr", "recover"); + } + _ae = rd._ae; + _ae_max_jfiles = rd._aemjf; + + const std::size_t num_res_files = rd._ae + ? (rd._aemjf ? rd._aemjf : JRNL_MAX_NUM_FILES) + : rd._njf; + _fcntl_arr.reserve(num_res_files); + _fcntl_arr.assign(rd._njf, 0); + std::vector<u_int16_t> lfid_list(rd._fid_list.size(), 0); + for (std::size_t lid = 0; lid < rd._fid_list.size(); lid++) + lfid_list[rd._fid_list[lid]] = lid; + // NOTE: rd._fid_list may be smaller than rd._njf (journal may be empty or not yet file-cycled) + for (std::size_t pfid = 0; pfid < rd._njf; pfid++) + if (pfid < rd._fid_list.size()) + _fcntl_arr[lfid_list[pfid]] = fp(jcp, lfid_list[pfid], pfid, &rd); + else + _fcntl_arr[pfid] = fp(jcp, pfid, pfid, &rd); +} + +void +lpmgr::insert(const u_int16_t after_lfid, + jcntl* const jcp, + new_obj_fn_ptr fp, + const u_int16_t num_jfiles) +{ + assert(jcp != 0); + assert(after_lfid < _fcntl_arr.size()); + if (!_ae) throw jexception(jerrno::JERR_LFMGR_AEDISABLED, "lpmgr", "insert"); + if (num_jfiles == 0) return; + std::size_t pfid = _fcntl_arr.size(); + const u_int16_t eff_ae_max_jfiles = _ae_max_jfiles ? _ae_max_jfiles : JRNL_MAX_NUM_FILES; + if (pfid + num_jfiles > eff_ae_max_jfiles) + { + std::ostringstream oss; + oss << "num_files=" << pfid << " incr=" << num_jfiles << " limit=" << _ae_max_jfiles; + throw jexception(jerrno::JERR_LFMGR_AEFNUMLIMIT, oss.str(), "lpmgr", "insert"); + } + for (std::size_t lid = after_lfid + 1; lid <= after_lfid + num_jfiles; lid++, pfid++) + _fcntl_arr.insert(_fcntl_arr.begin() + lid, fp(jcp, lid, pfid, 0)); + for (std::size_t lid = after_lfid + num_jfiles + 1; lid < _fcntl_arr.size(); lid++) + { + fcntl* p = _fcntl_arr[lid]; + assert(p != 0); + p->set_lfid(p->lfid() + num_jfiles); + } +} + +void +lpmgr::finalize() +{ + for (u_int32_t i = 0; i < _fcntl_arr.size(); i++) + delete _fcntl_arr[i]; + _fcntl_arr.clear(); + _ae = false; + _ae_max_jfiles = 0; +} + +void +lpmgr::set_ae(const bool ae) +{ + if (ae && _ae_max_jfiles > 0 && _ae_max_jfiles <= _fcntl_arr.size()) + { + std::ostringstream oss; + oss << "ae_max_jfiles (" << _ae_max_jfiles << ") <= _fcntl_arr.size (" << _fcntl_arr.size() << ")"; + throw jexception(jerrno::JERR_LFMGR_BADAEFNUMLIM, oss.str(), "lpmgr", "set_ae"); + } + if (ae && _fcntl_arr.max_size() < _ae_max_jfiles) + _fcntl_arr.reserve(_ae_max_jfiles ? _ae_max_jfiles : JRNL_MAX_NUM_FILES); + _ae = ae; +} + +void +lpmgr::set_ae_max_jfiles(const u_int16_t ae_max_jfiles) +{ + if (_ae && ae_max_jfiles > 0 && ae_max_jfiles <= _fcntl_arr.size()) + { + std::ostringstream oss; + oss << "ae_max_jfiles (" << _ae_max_jfiles << ") <= _fcntl_arr.size() (" << _fcntl_arr.size() << ")"; + throw jexception(jerrno::JERR_LFMGR_BADAEFNUMLIM, oss.str(), "lpmgr", "set_ae_max_jfiles"); + } + if (_ae && _fcntl_arr.max_size() < ae_max_jfiles) + _fcntl_arr.reserve(ae_max_jfiles ? ae_max_jfiles : JRNL_MAX_NUM_FILES); + _ae_max_jfiles = ae_max_jfiles; +} + +u_int16_t +lpmgr::ae_jfiles_rem() const +{ + if (_ae_max_jfiles > _fcntl_arr.size()) return _ae_max_jfiles - _fcntl_arr.size(); + if (_ae_max_jfiles == 0) return JRNL_MAX_NUM_FILES - _fcntl_arr.size(); + return 0; +} + +// Testing functions + +void +lpmgr::get_pfid_list(std::vector<u_int16_t>& pfid_list) const +{ + pfid_list.clear(); + for (std::size_t i = 0; i < _fcntl_arr.size(); i++) + pfid_list.push_back(_fcntl_arr[i]->pfid()); +} + +void +lpmgr::get_lfid_list(std::vector<u_int16_t>& lfid_list) const +{ + lfid_list.clear(); + lfid_list.assign(_fcntl_arr.size(), 0); + for (std::size_t i = 0; i < _fcntl_arr.size(); i++) + lfid_list[_fcntl_arr[i]->pfid()] = i; +} + +// === protected fns === + +void +lpmgr::append(jcntl* const jcp, + new_obj_fn_ptr fp, + const u_int16_t num_jfiles) +{ + std::size_t s = _fcntl_arr.size(); + if (_ae_max_jfiles && s + num_jfiles > _ae_max_jfiles) + { + std::ostringstream oss; + oss << "num_files=" << s << " incr=" << num_jfiles << " limit=" << _ae_max_jfiles; + throw jexception(jerrno::JERR_LFMGR_AEFNUMLIMIT, oss.str(), "lpmgr", "append"); + } + for (std::size_t i = s; i < s + num_jfiles; i++) + _fcntl_arr.push_back(fp(jcp, i, i, 0)); +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/lpmgr.h b/cpp/src/qpid/legacystore/jrnl/lpmgr.h new file mode 100644 index 0000000000..be5c4494cc --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/lpmgr.h @@ -0,0 +1,303 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file lpmgr.h + * + * Qpid asynchronous store plugin library + * + * Class mrg::journal::lpmgr. See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_LPMGR_H +#define QPID_LEGACYSTORE_JRNL_LPMGR_H + +namespace mrg +{ +namespace journal +{ + class jcntl; + class lpmgr; +} +} + +#include "qpid/legacystore/jrnl/fcntl.h" +#include <vector> + +namespace mrg +{ +namespace journal +{ + + /** + * \brief LFID-PFID manager. This class maps the logical file id (lfid) to the physical file id (pfid) so that files + * may be inserted into the file ring buffer in (nearly) arbitrary logical locations while the physical ids continue + * to be appended. NOTE: NOT THREAD SAFE. + * + * The entire functionality of the LFID-PFID manager is to maintain an array of pointers to fcntl objects which have + * a one-to-one relationship to the physical %journal files. The logical file id (lfid) is used as an index to the + * array to read the mapped physical file id (pfid). By altering the order of these pointers within the array, the + * mapping of logical to physical files may be altered. This can be used to allow for the logical insertion of + * %journal files into a ring buffer, even though the physical file ids must be appended to those that preceded them. + * + * Since the insert() operation uses after-lfid as its position parameter, it is not possible to insert before lfid + * 0 - i.e. It is only possible to insert after an existing lfid. Consequently, lfid 0 and pfid 0 are always + * coincident in a %journal. Note, however, that inserting before lfid 0 is logically equivilent to inserting after + * the last lfid. + * + * When one or more files are inserted after a particular lfid, the lfids of the following files are incremented. The + * pfids of the inserted files follow those of all existing files, thus leading to a lfid-pfid discreppancy (ie no + * longer a one-to-one mapping): + * + * Example: Before insertion, %journal file headers would look as follows: + * <pre> + * Logical view (sorted by lfid): Physical view (sorted by pfid): + * +---+---+---+---+---+---+ +---+---+---+---+---+---+ + * pfid --> | 0 | 1 | 2 | 3 | 4 | 5 | pfid --> | 0 | 1 | 2 | 3 | 4 | 5 | + * lfid --> | 0 | 1 | 2 | 3 | 4 | 5 | lfid --> | 0 | 1 | 2 | 3 | 4 | 5 | + * +---+---+---+---+---+---+ +---+---+---+---+---+---+ + * </pre> + * + * After insertion of 2 files after lid 2 (marked with *s): + * <pre> + * Logical view (sorted by lfid): Physical view (sorted by pfid): + * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ + * pfid --> | 0 | 1 | 2 |*6*|*7*| 3 | 4 | 5 | pfid --> | 0 | 1 | 2 | 3 | 4 | 5 |*6*|*7*| + * lfid --> | 0 | 1 | 2 |*3*|*4*| 5 | 6 | 7 | lfid --> | 0 | 1 | 2 | 5 | 6 | 7 |*3*|*4*| + * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ + * </pre> + * + * The insert() function updates the internal map immediately, but the physical files (which have both the pfid and + * lfid written into the file header) are only updated as they are overwritten in the normal course of enqueueing + * and dequeueing messages. If the %journal should fail after insertion but before the files following those inserted + * are overwritten, then duplicate lfids will be present (though no duplicate pfids are possible). The overwrite + * indicator (owi) flag and the pfid numbers may be used to resolve the ambiguity and determine the logically earlier + * lfid in this case. + * + * Example: Before insertion, the current active write file being lfid/pfid 2 as determined by the owi flag, %journal + * file headers would look as follows: + * <pre> + * Logical view (sorted by lfid): Physical view (sorted by pfid): + * +---+---+---+---+---+---+ +---+---+---+---+---+---+ + * pfid --> | 0 | 1 | 2 | 3 | 4 | 5 | pfid --> | 0 | 1 | 2 | 3 | 4 | 5 | + * lfid --> | 0 | 1 | 2 | 3 | 4 | 5 | lfid --> | 0 | 1 | 2 | 3 | 4 | 5 | + * owi --> | t | t | t | f | f | f | owi --> | t | t | t | f | f | f | + * +---+---+---+---+---+---+ +---+---+---+---+---+---+ + * </pre> + * + * After inserting 2 files after lfid 2 and then 3 (the newly inserted file) - marked with *s: + * <pre> + * Logical view (sorted by lfid): Physical view (sorted by pfid): + * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ + * pfid --> | 0 | 1 | 2 |*6*|*7*| 3 | 4 | 5 | pfid --> | 0 | 1 | 2 | 3 | 4 | 5 |*3*|*4*| + * lfid --> | 0 | 1 | 2 |*3*|*4*| 3 | 4 | 5 | lfid --> | 0 | 1 | 2 | 3 | 4 | 5 |*3*|*4*| + * owi --> | t | t | t | t | t | f | f | f | owi --> | t | t | t | f | f | f | t | t | + * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ + * </pre> + * + * If a broker failure occurs at this point, then there are two independent tests that may be made to resolve + * duplicate lfids during recovery in such cases: + * <ol> + * <li>The correct lfid has owi flag that matches that of pfid/lfid 0</li> + * <li>The most recently inserted (hence correct) lfid has pfids that are higher than the duplicate that was not + * overwritten</li> + * </ol> + * + * NOTE: NOT THREAD SAFE. Provide external thread protection if used in multi-threaded environments. + */ + class lpmgr + { + public: + /** + * \brief Function pointer to function that will create a new fcntl object and return its pointer. + * + * \param jcp Pointer to jcntl instance from which journal file details will be obtained. + * \param lfid Logical file ID for new fcntl instance. + * \param pfid Physical file ID for file associated with new fcntl instance. + * \param rdp Pointer to rcvdat instance which conatins recovery information for new fcntl instance when + * recovering an existing file, or null if a new file is to be created. + */ + typedef fcntl* (new_obj_fn_ptr)(jcntl* const jcp, + const u_int16_t lfid, + const u_int16_t pfid, + const rcvdat* const rdp); + + private: + bool _ae; ///< Auto-expand mode + u_int16_t _ae_max_jfiles; ///< Max file count for auto-expansion; 0 = no limit + std::vector<fcntl*> _fcntl_arr; ///< Array of pointers to fcntl objects + + public: + lpmgr(); + virtual ~lpmgr(); + + /** + * \brief Initialize from scratch for a known number of %journal files. All lfid values are identical to pfid + * values (which is normal before any inserts have occurred). + * + * \param num_jfiles Number of files to be created, and consequently the number of fcntl objects in array + * _fcntl_arr. + * \param ae If true, allows auto-expansion; if false, disables auto-expansion. + * \param ae_max_jfiles The maximum number of files allowed for auto-expansion. Cannot be lower than the current + * number of files. However, a zero value disables the limit checks, and allows unlimited + * expansion. + * \param jcp Pointer to jcntl instance. This is used to find the file path and base filename so that + * new files may be created. + * \param fp Pointer to function which creates and returns a pointer to a new fcntl object (and hence + * causes a new %journal file to be created). + */ + void initialize(const u_int16_t num_jfiles, + const bool ae, + const u_int16_t ae_max_jfiles, + jcntl* const jcp, + new_obj_fn_ptr fp); + + /** + * \brief Initialize from a known lfid-pfid map pfid_list (within rcvdat param rd), which is usually obtained + * from a recover. The index of pfid_list is the logical file id (lfid); the value contained in the vector is + * the physical file id (pfid). + * + * \param rd Ref to rcvdat struct which contains recovery data and the pfid_list. + * \param jcp Pointer to jcntl instance. This is used to find the file path and base filename so that + * new files may be created. + * \param fp Pointer to function which creates and returns a pointer to a new fcntl object (and hence + * causes a new %journal file to be created). + */ + void recover(const rcvdat& rd, + jcntl* const jcp, + new_obj_fn_ptr fp); + + /** + * \brief Insert num_jfiles files after lfid index after_lfid. This causes all lfids after after_lfid to be + * increased by num_jfiles. + * + * Note that it is not possible to insert <i>before</i> lfid 0, and thus lfid 0 should always point to pfid 0. + * Inserting before lfid 0 is logically equivilent to inserting after the last lfid in a circular buffer. + * + * \param after_lfid Lid index after which to insert file(s). + * \param jcp Pointer to jcntl instance. This is used to find the file path and base filename so that + * new files may be created. + * \param fp Pointer to function which creates and returns a pointer to a new fcntl object (and hence + * causes a new %journal file to be created). + * \param num_jfiles The number of files by which to increase. + */ + void insert(const u_int16_t after_lfid, + jcntl* const jcp, + new_obj_fn_ptr fp, + const u_int16_t num_jfiles = 1); + + /** + * \brief Clears _fcntl_arr and deletes all fcntl instances. + */ + void finalize(); + + /** + * \brief Returns true if initialized; false otherwise. After construction, will return false until initialize() + * is called; thereafter true until finalize() is called, whereupon it will return false again. + * + * \return True if initialized; false otherwise. + */ + inline bool is_init() const { return _fcntl_arr.size() > 0; } + + /** + * \brief Returns true if auto-expand mode is enabled; false if not. + * + * \return True if auto-expand mode is enabled; false if not. + */ + inline bool is_ae() const { return _ae; } + + /** + * \brief Sets the auto-expand mode to enabled if ae is true, to disabled otherwise. The value of _ae_max_jfiles + * must be valid to succeed (i.e. _ae_max_jfiles must be greater than the current number of files or be zero). + * + * \param ae If true will enable auto-expand mode; if false will disable it. + */ + void set_ae(const bool ae); + + /** + * \brief Returns the number of %journal files, including any that were appended or inserted since + * initialization. + * + * \return Number of %journal files if initialized; 0 otherwise. + */ + inline u_int16_t num_jfiles() const { return static_cast<u_int16_t>(_fcntl_arr.size()); } + + /** + * \brief Returns the maximum number of files allowed for auto-expansion. + * + * \return Maximum number of files allowed for auto-expansion. A zero value represents a disabled limit + * - i.e. unlimited expansion. + */ + inline u_int16_t ae_max_jfiles() const { return _ae_max_jfiles; } + + /** + * \brief Sets the maximum number of files allowed for auto-expansion. A zero value disables the limit. + * + * \param ae_max_jfiles The maximum number of files allowed for auto-expansion. Cannot be lower than the current + * number of files. However, a zero value disables the limit checks, and allows unlimited + * expansion. + */ + void set_ae_max_jfiles(const u_int16_t ae_max_jfiles); + + /** + * \brief Calculates the number of future files available for auto-expansion. + * + * \return The number of future files available for auto-expansion. + */ + u_int16_t ae_jfiles_rem() const; + + /** + * \brief Get a pointer to fcntl instance for a given lfid. + * + * \return Pointer to fcntl object corresponding to logical file id lfid, or 0 if lfid is out of range + * (greater than number of files in use). + */ + inline fcntl* get_fcntlp(const u_int16_t lfid) const + { if (lfid >= _fcntl_arr.size()) return 0; return _fcntl_arr[lfid]; } + + // Testing functions + void get_pfid_list(std::vector<u_int16_t>& pfid_list) const; + void get_lfid_list(std::vector<u_int16_t>& lfid_list) const; + + protected: + + /** + * \brief Append num_jfiles files to the end of the logical and file id sequence. This is similar to extending + * the from-scratch initialization. + * + * \param jcp Pointer to jcntl instance. This is used to find the file path and base filename so that + * new files may be created. + * \param fp Pointer to function which creates and returns a pointer to a new fcntl object (and hence + * causes a new %journal file to be created). + * \param num_jfiles The number of files by which to increase. + */ + void append(jcntl* const jcp, + new_obj_fn_ptr fp, + const u_int16_t num_jfiles = 1); + + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_LPMGR_H diff --git a/cpp/src/qpid/legacystore/jrnl/pmgr.cpp b/cpp/src/qpid/legacystore/jrnl/pmgr.cpp new file mode 100644 index 0000000000..3dc61e2661 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/pmgr.cpp @@ -0,0 +1,215 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file pmgr.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::pmgr (page manager). See + * comments in file pmgr.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/pmgr.h" + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include "qpid/legacystore/jrnl/jcfg.h" +#include "qpid/legacystore/jrnl/jcntl.h" +#include "qpid/legacystore/jrnl/jerrno.h" +#include <sstream> + + +namespace mrg +{ +namespace journal +{ + +pmgr::page_cb::page_cb(u_int16_t index): + _index(index), + _state(UNUSED), + _wdblks(0), + _rdblks(0), + _pdtokl(0), + _wfh(0), + _rfh(0), + _pbuff(0) +{} + +const char* +pmgr::page_cb::state_str() const +{ + switch(_state) + { + case UNUSED: + return "UNUSED"; + case IN_USE: + return "IN_USE"; + case AIO_PENDING: + return "AIO_PENDING"; + case AIO_COMPLETE: + return "AIO_COMPLETE"; + } + return "<unknown>"; +} + +const u_int32_t pmgr::_sblksize = JRNL_SBLK_SIZE * JRNL_DBLK_SIZE; + +pmgr::pmgr(jcntl* jc, enq_map& emap, txn_map& tmap): + _cache_pgsize_sblks(0), + _cache_num_pages(0), + _jc(jc), + _emap(emap), + _tmap(tmap), + _page_base_ptr(0), + _page_ptr_arr(0), + _page_cb_arr(0), + _aio_cb_arr(0), + _aio_event_arr(0), + _ioctx(0), + _pg_index(0), + _pg_cntr(0), + _pg_offset_dblks(0), + _aio_evt_rem(0), + _cbp(0), + _enq_rec(), + _deq_rec(), + _txn_rec() +{} + +pmgr::~pmgr() +{ + pmgr::clean(); +} + +void +pmgr::initialize(aio_callback* const cbp, const u_int32_t cache_pgsize_sblks, const u_int16_t cache_num_pages) +{ + // As static use of this class keeps old values around, clean up first... + pmgr::clean(); + _pg_index = 0; + _pg_cntr = 0; + _pg_offset_dblks = 0; + _aio_evt_rem = 0; + _cache_pgsize_sblks = cache_pgsize_sblks; + _cache_num_pages = cache_num_pages; + _cbp = cbp; + + // 1. Allocate page memory (as a single block) + std::size_t cache_pgsize = _cache_num_pages * _cache_pgsize_sblks * _sblksize; + if (::posix_memalign(&_page_base_ptr, _sblksize, cache_pgsize)) + { + clean(); + std::ostringstream oss; + oss << "posix_memalign(): blksize=" << _sblksize << " size=" << cache_pgsize; + oss << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR__MALLOC, oss.str(), "pmgr", "initialize"); + } + // 2. Allocate array of page pointers + _page_ptr_arr = (void**)std::malloc(_cache_num_pages * sizeof(void*)); + MALLOC_CHK(_page_ptr_arr, "_page_ptr_arr", "pmgr", "initialize"); + + // 3. Allocate and initilaize page control block (page_cb) array + _page_cb_arr = (page_cb*)std::malloc(_cache_num_pages * sizeof(page_cb)); + MALLOC_CHK(_page_cb_arr, "_page_cb_arr", "pmgr", "initialize"); + std::memset(_page_cb_arr, 0, _cache_num_pages * sizeof(page_cb)); + + // 5. Allocate IO control block (iocb) array + _aio_cb_arr = (aio_cb*)std::malloc(_cache_num_pages * sizeof(aio_cb)); + MALLOC_CHK(_aio_cb_arr, "_aio_cb_arr", "pmgr", "initialize"); + + // 6. Set page pointers in _page_ptr_arr, _page_cb_arr and iocbs to pages within page block + for (u_int16_t i=0; i<_cache_num_pages; i++) + { + _page_ptr_arr[i] = (void*)((char*)_page_base_ptr + _cache_pgsize_sblks * _sblksize * i); + _page_cb_arr[i]._index = i; + _page_cb_arr[i]._state = UNUSED; + _page_cb_arr[i]._pbuff = _page_ptr_arr[i]; + _page_cb_arr[i]._pdtokl = new std::deque<data_tok*>; + _page_cb_arr[i]._pdtokl->clear(); + _aio_cb_arr[i].data = (void*)&_page_cb_arr[i]; + } + + // 7. Allocate io_event array, max one event per cache page plus one for each file + const u_int16_t max_aio_evts = _cache_num_pages + _jc->num_jfiles(); + _aio_event_arr = (aio_event*)std::malloc(max_aio_evts * sizeof(aio_event)); + MALLOC_CHK(_aio_event_arr, "_aio_event_arr", "pmgr", "initialize"); + + // 8. Initialize AIO context + if (int ret = aio::queue_init(max_aio_evts, &_ioctx)) + { + std::ostringstream oss; + oss << "io_queue_init() failed: " << FORMAT_SYSERR(-ret); + throw jexception(jerrno::JERR__AIO, oss.str(), "pmgr", "initialize"); + } +} + +void +pmgr::clean() +{ + // clean up allocated memory here + + if (_ioctx) + aio::queue_release(_ioctx); + + std::free(_page_base_ptr); + _page_base_ptr = 0; + + if (_page_cb_arr) + { + for (int i=0; i<_cache_num_pages; i++) + delete _page_cb_arr[i]._pdtokl; + std::free(_page_ptr_arr); + _page_ptr_arr = 0; + } + + std::free(_page_cb_arr); + _page_cb_arr = 0; + + std::free(_aio_cb_arr); + _aio_cb_arr = 0; + + std::free(_aio_event_arr); + _aio_event_arr = 0; +} + +const char* +pmgr::page_state_str(page_state ps) +{ + switch (ps) + { + case UNUSED: + return "UNUSED"; + case IN_USE: + return "IN_USE"; + case AIO_PENDING: + return "AIO_PENDING"; + case AIO_COMPLETE: + return "AIO_COMPLETE"; + } + return "<page_state unknown>"; +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/pmgr.h b/cpp/src/qpid/legacystore/jrnl/pmgr.h new file mode 100644 index 0000000000..64115e225e --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/pmgr.h @@ -0,0 +1,142 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file pmgr.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::pmgr (page manager). See + * class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_PMGR_H +#define QPID_LEGACYSTORE_JRNL_PMGR_H + +namespace mrg +{ +namespace journal +{ + class pmgr; + class jcntl; +} +} + +#include <deque> +#include "qpid/legacystore/jrnl/aio.h" +#include "qpid/legacystore/jrnl/aio_callback.h" +#include "qpid/legacystore/jrnl/data_tok.h" +#include "qpid/legacystore/jrnl/deq_rec.h" +#include "qpid/legacystore/jrnl/enq_map.h" +#include "qpid/legacystore/jrnl/enq_rec.h" +#include "qpid/legacystore/jrnl/fcntl.h" +#include "qpid/legacystore/jrnl/txn_map.h" +#include "qpid/legacystore/jrnl/txn_rec.h" + +namespace mrg +{ +namespace journal +{ + + /** + * \brief Abstract class for managing either read or write page cache of arbitrary size and + * number of cache_num_pages. + */ + class pmgr + { + public: + /** + * \brief Enumeration of possible stats of a page within a page cache. + */ + enum page_state + { + UNUSED, ///< A page is uninitialized, contains no data. + IN_USE, ///< Page is in use. + AIO_PENDING, ///< An AIO request outstanding. + AIO_COMPLETE ///< An AIO request is complete. + }; + + protected: + /** + * \brief Page control block, carries control and state information for each page in the + * cache. + */ + struct page_cb + { + u_int16_t _index; ///< Index of this page + page_state _state; ///< Status of page + u_int64_t _frid; ///< First rid in page (used for fhdr init) + u_int32_t _wdblks; ///< Total number of dblks in page so far + u_int32_t _rdblks; ///< Total number of dblks in page + std::deque<data_tok*>* _pdtokl; ///< Page message tokens list + fcntl* _wfh; ///< File handle for incrementing write compl counts + fcntl* _rfh; ///< File handle for incrementing read compl counts + void* _pbuff; ///< Page buffer + + page_cb(u_int16_t index); ///< Convenience constructor + const char* state_str() const; ///< Return state as string for this pcb + }; + + static const u_int32_t _sblksize; ///< Disk softblock size + u_int32_t _cache_pgsize_sblks; ///< Size of page cache cache_num_pages + u_int16_t _cache_num_pages; ///< Number of page cache cache_num_pages + jcntl* _jc; ///< Pointer to journal controller + enq_map& _emap; ///< Ref to enqueue map + txn_map& _tmap; ///< Ref to transaction map + void* _page_base_ptr; ///< Base pointer to page memory + void** _page_ptr_arr; ///< Array of pointers to cache_num_pages in page memory + page_cb* _page_cb_arr; ///< Array of page_cb structs + aio_cb* _aio_cb_arr; ///< Array of iocb structs + aio_event* _aio_event_arr; ///< Array of io_events + io_context_t _ioctx; ///< AIO context for read/write operations + u_int16_t _pg_index; ///< Index of current page being used + u_int32_t _pg_cntr; ///< Page counter; determines if file rotation req'd + u_int32_t _pg_offset_dblks; ///< Page offset (used so far) in data blocks + u_int32_t _aio_evt_rem; ///< Remaining AIO events + aio_callback* _cbp; ///< Pointer to callback object + + enq_rec _enq_rec; ///< Enqueue record used for encoding/decoding + deq_rec _deq_rec; ///< Dequeue record used for encoding/decoding + txn_rec _txn_rec; ///< Transaction record used for encoding/decoding + + public: + pmgr(jcntl* jc, enq_map& emap, txn_map& tmap); + virtual ~pmgr(); + + virtual int32_t get_events(page_state state, timespec* const timeout, bool flush = false) = 0; + inline u_int32_t get_aio_evt_rem() const { return _aio_evt_rem; } + static const char* page_state_str(page_state ps); + inline u_int32_t cache_pgsize_sblks() const { return _cache_pgsize_sblks; } + inline u_int16_t cache_num_pages() const { return _cache_num_pages; } + + protected: + virtual void initialize(aio_callback* const cbp, const u_int32_t cache_pgsize_sblks, + const u_int16_t cache_num_pages); + virtual void rotate_page() = 0; + virtual void clean(); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_PMGR_H diff --git a/cpp/src/qpid/legacystore/jrnl/rcvdat.h b/cpp/src/qpid/legacystore/jrnl/rcvdat.h new file mode 100644 index 0000000000..a7ef2341f0 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/rcvdat.h @@ -0,0 +1,181 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file rcvdat.h + * + * Qpid asynchronous store plugin library + * + * Contains structure for recovery status and offset data. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_RCVDAT_H +#define QPID_LEGACYSTORE_JRNL_RCVDAT_H + +#include <cstddef> +#include <iomanip> +#include <map> +#include "qpid/legacystore/jrnl/jcfg.h" +#include <sstream> +#include <sys/types.h> +#include <vector> + +namespace mrg +{ +namespace journal +{ + + struct rcvdat + { + u_int16_t _njf; ///< Number of journal files + bool _ae; ///< Auto-expand mode + u_int16_t _aemjf; ///< Auto-expand mode max journal files + bool _owi; ///< Overwrite indicator + bool _frot; ///< First rotation flag + bool _jempty; ///< Journal data files empty + u_int16_t _ffid; ///< First file id + std::size_t _fro; ///< First record offset in ffid + u_int16_t _lfid; ///< Last file id + std::size_t _eo; ///< End offset (first byte past last record) + u_int64_t _h_rid; ///< Highest rid found + bool _lffull; ///< Last file is full + bool _jfull; ///< Journal is full + std::vector<u_int16_t> _fid_list; ///< Fid-lid mapping - list of fids in order of lid + std::vector<u_int32_t> _enq_cnt_list; ///< Number enqueued records found for each file + + rcvdat(): + _njf(0), + _ae(false), + _aemjf(0), + _owi(false), + _frot(false), + _jempty(true), + _ffid(0), + _fro(0), + _lfid(0), + _eo(0), + _h_rid(0), + _lffull(false), + _jfull(false), + _fid_list(), + _enq_cnt_list() + {} + + void reset(const u_int16_t num_jfiles, const bool auto_expand, const u_int16_t ae_max_jfiles) + { + _njf = num_jfiles; + _ae = auto_expand; + _aemjf = ae_max_jfiles; + _owi = false; + _frot = false; + _jempty = true; + _ffid = 0; + _fro = 0; + _lfid = 0; + _eo = 0; + _h_rid = 0; + _lffull = false; + _jfull = false; + _fid_list.clear(); + _enq_cnt_list.clear(); + _enq_cnt_list.resize(num_jfiles, 0); + } + + // Find first fid with enqueued records + u_int16_t ffid() + { + u_int16_t index = _ffid; + while (index != _lfid && _enq_cnt_list[index] == 0) + { + if (++index >= _njf) + index = 0; + } + return index; + } + + std::string to_string(const std::string& jid) + { + std::ostringstream oss; + oss << "Recover file analysis (jid=\"" << jid << "\"):" << std::endl; + oss << " Number of journal files (_njf) = " << _njf << std::endl; + oss << " Auto-expand mode (_ae) = " << (_ae ? "TRUE" : "FALSE") << std::endl; + if (_ae) oss << " Auto-expand mode max journal files (_aemjf) = " << _aemjf << std::endl; + oss << " Overwrite indicator (_owi) = " << (_owi ? "TRUE" : "FALSE") << std::endl; + oss << " First rotation (_frot) = " << (_frot ? "TRUE" : "FALSE") << std::endl; + oss << " Journal empty (_jempty) = " << (_jempty ? "TRUE" : "FALSE") << std::endl; + oss << " First (earliest) fid (_ffid) = " << _ffid << std::endl; + oss << " First record offset in first fid (_fro) = 0x" << std::hex << _fro << + std::dec << " (" << (_fro/JRNL_DBLK_SIZE) << " dblks)" << std::endl; + oss << " Last (most recent) fid (_lfid) = " << _lfid << std::endl; + oss << " End offset (_eo) = 0x" << std::hex << _eo << std::dec << " (" << + (_eo/JRNL_DBLK_SIZE) << " dblks)" << std::endl; + oss << " Highest rid (_h_rid) = 0x" << std::hex << _h_rid << std::dec << std::endl; + oss << " Last file full (_lffull) = " << (_lffull ? "TRUE" : "FALSE") << std::endl; + oss << " Journal full (_jfull) = " << (_jfull ? "TRUE" : "FALSE") << std::endl; + oss << " Normalized fid list (_fid_list) = ["; + for (std::vector<u_int16_t>::const_iterator i = _fid_list.begin(); i < _fid_list.end(); i++) + { + if (i != _fid_list.begin()) oss << ", "; + oss << *i; + } + oss << "]" << std::endl; + oss << " Enqueued records (txn & non-txn):" << std::endl; + for (unsigned i=0; i<_enq_cnt_list.size(); i++) + oss << " File " << std::setw(2) << i << ": " << _enq_cnt_list[i] << + std::endl; + return oss.str(); + } + + std::string to_log(const std::string& jid) + { + std::ostringstream oss; + oss << "Recover file analysis (jid=\"" << jid << "\"):"; + oss << " njf=" << _njf; + oss << " ae=" << (_owi ? "T" : "F"); + oss << " aemjf=" << _aemjf; + oss << " owi=" << (_ae ? "T" : "F"); + oss << " frot=" << (_frot ? "T" : "F"); + oss << " jempty=" << (_jempty ? "T" : "F"); + oss << " ffid=" << _ffid; + oss << " fro=0x" << std::hex << _fro << std::dec << " (" << + (_fro/JRNL_DBLK_SIZE) << " dblks)"; + oss << " lfid=" << _lfid; + oss << " eo=0x" << std::hex << _eo << std::dec << " (" << + (_eo/JRNL_DBLK_SIZE) << " dblks)"; + oss << " h_rid=0x" << std::hex << _h_rid << std::dec; + oss << " lffull=" << (_lffull ? "T" : "F"); + oss << " jfull=" << (_jfull ? "T" : "F"); + oss << " Enqueued records (txn & non-txn): [ "; + for (unsigned i=0; i<_enq_cnt_list.size(); i++) + { + if (i) oss << " "; + oss << "fid_" << std::setw(2) << std::setfill('0') << i << "=" << _enq_cnt_list[i]; + } + oss << " ]"; + return oss.str(); + } + }; +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_RCVDAT_H diff --git a/cpp/src/qpid/legacystore/jrnl/rec_hdr.h b/cpp/src/qpid/legacystore/jrnl/rec_hdr.h new file mode 100644 index 0000000000..ff6325a760 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/rec_hdr.h @@ -0,0 +1,143 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file rec_hdr.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::rec_hdr (record header), + * which is a common initial header used for all journal record structures + * except the record tail (rec_tail). + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_REC_HDR_H +#define QPID_LEGACYSTORE_JRNL_REC_HDR_H + +#include <cstddef> +#include "qpid/legacystore/jrnl/jcfg.h" +#include <sys/types.h> + +namespace mrg +{ +namespace journal +{ + +#pragma pack(1) + + /** + * \brief Struct for data common to the head of all journal files and records. + * This includes identification for the file type, the encoding version, endian + * indicator and a record ID. + * + * File header info in binary format (16 bytes): + * <pre> + * 0 7 + * +---+---+---+---+---+---+---+---+ + * | magic | v | e | flags | + * +---+---+---+---+---+---+---+---+ + * | rid | + * +---+---+---+---+---+---+---+---+ + * v = file version (If the format or encoding of this file changes, then this + * number should be incremented) + * e = endian flag, false (0x00) for little endian, true (0x01) for big endian + * </pre> + * + * Note that journal files should be transferable between 32- and 64-bit + * hardware of the same endianness, but not between hardware of opposite + * entianness without some sort of binary conversion utility. Thus buffering + * will be needed for types that change size between 32- and 64-bit compiles. + */ + struct rec_hdr + { + u_int32_t _magic; ///< File type identifier (magic number) + u_int8_t _version; ///< File encoding version + u_int8_t _eflag; ///< Flag for determining endianness + u_int16_t _uflag; ///< User-defined flags + u_int64_t _rid; ///< Record ID (rotating 64-bit counter) + + // Global flags + static const u_int16_t HDR_OVERWRITE_INDICATOR_MASK = 0x1; + + // Convenience constructors and methods + /** + * \brief Default constructor, which sets all values to 0. + */ + inline rec_hdr(): _magic(0), _version(0), _eflag(0), _uflag(0), _rid(0) {} + + /** + * \brief Convenience constructor which initializes values during construction. + */ + inline rec_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid, + const bool owi): _magic(magic), _version(version), +#if defined(JRNL_BIG_ENDIAN) + _eflag(RHM_BENDIAN_FLAG), +#else + _eflag(RHM_LENDIAN_FLAG), +#endif + _uflag(owi ? HDR_OVERWRITE_INDICATOR_MASK : 0), _rid(rid) {} + + /** + * \brief Convenience copy method. + */ + inline void hdr_copy(const rec_hdr& h) + { + _magic = h._magic; + _version = h._version; + _eflag = h._eflag; + _uflag = h._uflag; + _rid =h._rid; + } + + /** + * \brief Resets all fields to 0 + */ + inline void reset() + { + _magic = 0; + _version = 0; + _eflag = 0; + _uflag = 0; + _rid = 0; + } + + inline bool get_owi() const { return _uflag & HDR_OVERWRITE_INDICATOR_MASK; } + + inline void set_owi(const bool owi) + { + _uflag = owi ? _uflag | HDR_OVERWRITE_INDICATOR_MASK : + _uflag & (~HDR_OVERWRITE_INDICATOR_MASK); + } + + /** + * \brief Returns the size of the header in bytes. + */ + inline static std::size_t size() { return sizeof(rec_hdr); } + }; // struct rec_hdr + +#pragma pack() + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_REC_HDR_H diff --git a/cpp/src/qpid/legacystore/jrnl/rec_tail.h b/cpp/src/qpid/legacystore/jrnl/rec_tail.h new file mode 100644 index 0000000000..0c36151927 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/rec_tail.h @@ -0,0 +1,98 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file rec_tail.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::rec_tail (record tail), used to + * finalize a persistent record. The presence of a valid tail at the expected + * position in the journal file indicates that the record write was completed. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_REC_TAIL_H +#define QPID_LEGACYSTORE_JRNL_REC_TAIL_H + +#include <cstddef> +#include "qpid/legacystore/jrnl/jcfg.h" + +namespace mrg +{ +namespace journal +{ + +#pragma pack(1) + + /** + * \brief Struct for data common to the tail of all records. The magic number + * used here is the binary inverse (1's complement) of the magic used in the + * record header; this minimizes possible confusion with other headers that may + * be present during recovery. The tail is used with all records that have either + * XIDs or data - ie any size-variable content. Currently the only records that + * do NOT use the tail are non-transactional dequeues and filler records. + * + * Record header info in binary format (12 bytes): + * <pre> + * 0 7 + * +---+---+---+---+---+---+---+---+ + * | ~(magic) | rid | + * +---+---+---+---+---+---+---+---+ + * | rid (con't) | + * +---+---+---+---+ + * </pre> + */ + struct rec_tail + { + u_int32_t _xmagic; ///< Binary inverse (1's complement) of hdr magic number + u_int64_t _rid; ///< ID (rotating 64-bit counter) + + + /** + * \brief Default constructor, which sets all values to 0. + */ + inline rec_tail(): _xmagic(0xffffffff), _rid(0) {} + + /** + * \brief Convenience constructor which initializes values during construction from + * existing enq_hdr instance. + */ + inline rec_tail(const rec_hdr& h): _xmagic(~h._magic), _rid(h._rid) {} + + /** + * \brief Convenience constructor which initializes values during construction. + */ + inline rec_tail(const u_int32_t xmagic, const u_int64_t rid): _xmagic(xmagic), _rid(rid) {} + + /** + * \brief Returns the size of the header in bytes. + */ + inline static std::size_t size() { return sizeof(rec_tail); } + }; + +#pragma pack() + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_REC_TAIL_H diff --git a/cpp/src/qpid/legacystore/jrnl/rfc.cpp b/cpp/src/qpid/legacystore/jrnl/rfc.cpp new file mode 100644 index 0000000000..9b5ed95e81 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/rfc.cpp @@ -0,0 +1,82 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file rfc.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::rfc (rotating + * file controller). See comments in file rfc.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/rfc.h" + +#include <cassert> + +namespace mrg +{ +namespace journal +{ + +rfc::rfc(const lpmgr* lpmp): _lpmp(lpmp), _fc_index(0), _curr_fc(0) +{} + +rfc::~rfc() +{} + +void +rfc::finalize() +{ + unset_findex(); +} + +void +rfc::set_findex(const u_int16_t fc_index) +{ + _fc_index = fc_index; + _curr_fc = _lpmp->get_fcntlp(fc_index); + _curr_fc->rd_reset(); +} + +void +rfc::unset_findex() +{ + _fc_index = 0; + _curr_fc = 0; +} + +std::string +rfc::status_str() const +{ + if (!_lpmp->is_init()) + return "state: Uninitialized"; + if (_curr_fc == 0) + return "state: Inactive"; + std::ostringstream oss; + oss << "state: Active"; + return oss.str(); +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/rfc.h b/cpp/src/qpid/legacystore/jrnl/rfc.h new file mode 100644 index 0000000000..faa5d566ba --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/rfc.h @@ -0,0 +1,193 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file rfc.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::rfc (rotating + * file controller). See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_RFC_H +#define QPID_LEGACYSTORE_JRNL_RFC_H + +namespace mrg +{ +namespace journal +{ +class rfc; +} +} + +#include "qpid/legacystore/jrnl/lpmgr.h" +#include "qpid/legacystore/jrnl/enums.h" + +namespace mrg +{ +namespace journal +{ + + /** + * \class rfc + * \brief Rotating File Controller (rfc) - Class to handle the manangement of an array of file controllers (fcntl) + * objects for use in a circular disk buffer (journal). Each fcntl object corresponds to a file in the journal. + * + * The following states exist in this class: + * + * <pre> + * is_init() is_active() + * +===+ _lpmp.is_init() == false + * +---------->| | Uninitialized: _curr_fc == 0 F F + * | +-->+===+ --+ + * | | | + * | | | + * | finalize() initialize() + * | | | + * | | | + * | +-- +===+<--+ _lpmp.is_init() == true + * finalize() | | Inactive: _curr_fc == 0 T F + * | +-->+===+ --+ + * | | | + * | | | + * | unset_findex() set_findex() + * | | | + * | | | + * | +-- +===+<--+ _lpmp.is_init() == true + * +---------- | | Active: _curr_fc != 0 T T + * +===+ + * </pre> + * + * The Uninitialized state is where the class starts after construction. Once the number of files is known and + * the array of file controllers allocated, then initialize() is called to set these, causing the state to move + * to Inactive. + * + * The Inactive state has the file controllers allocated and pointing to their respective journal files, but no + * current file controller has been selected. The pointer to the current file controller _curr_fc is null. Once the + * index of the active file is known, then calling set_findex() will set the index and internal pointer + * to the currently active file controller. This moves the state to Active. + * + * Note TODO: Comment on sync issues between change in num files in _lpmp and _fc_index/_curr_fc. + */ + class rfc + { + protected: + const lpmgr* _lpmp; ///< Pointer to jcntl's lpmgr instance containing lfid/pfid map and fcntl objects + u_int16_t _fc_index; ///< Index of current file controller + fcntl* _curr_fc; ///< Pointer to current file controller + + public: + rfc(const lpmgr* lpmp); + virtual ~rfc(); + + /** + * \brief Initialize the controller, moving from state Uninitialized to Inactive. The main function of + * initialize() is to set the number of files and the pointer to the fcntl array. + */ + virtual inline void initialize() {} + + /** + * \brief Reset the controller to Uninitialized state, usually called when the journal is stopped. Once called, + * initialize() must be called to reuse an instance. + */ + virtual void finalize(); + + /** + * \brief Check initialization state: true = Not Uninitialized, ie Initialized or Active; false = Uninitialized. + */ + virtual inline bool is_init() const { return _lpmp->is_init(); } + + /** + * \brief Check active state: true = Initialized and _curr_fc not null; false otherwise. + */ + virtual inline bool is_active() const { return _lpmp->is_init() && _curr_fc != 0; } + + /** + * \brief Sets the current file index and active fcntl object. Moves to state Active. + */ + virtual void set_findex(const u_int16_t fc_index); + + /** + * \brief Nulls the current file index and active fcntl pointer, moves to state Inactive. + */ + virtual void unset_findex(); + + /** + * \brief Rotate active file controller to next file in rotating file group. + * \exception jerrno::JERR__NINIT if called before calling initialize(). + */ + virtual iores rotate() = 0; + + /** + * \brief Returns the index of the currently active file within the rotating file group. + */ + inline u_int16_t index() const { return _fc_index; } + + /** + * \brief Returns the currently active journal file controller within the rotating file group. + */ + inline fcntl* file_controller() const { return _curr_fc; } + + /** + * \brief Returns the currently active physical file id (pfid) + */ + inline u_int16_t pfid() const { return _curr_fc->pfid(); } + + // Convenience access methods to current file controller + // Note: Do not call when not in active state + + inline u_int32_t enqcnt() const { return _curr_fc->enqcnt(); } + inline u_int32_t incr_enqcnt() { return _curr_fc->incr_enqcnt(); } + inline u_int32_t incr_enqcnt(const u_int16_t fid) { return _lpmp->get_fcntlp(fid)->incr_enqcnt(); } + inline u_int32_t add_enqcnt(const u_int32_t a) { return _curr_fc->add_enqcnt(a); } + inline u_int32_t add_enqcnt(const u_int16_t fid, const u_int32_t a) + { return _lpmp->get_fcntlp(fid)->add_enqcnt(a); } + inline u_int32_t decr_enqcnt(const u_int16_t fid) { return _lpmp->get_fcntlp(fid)->decr_enqcnt(); } + inline u_int32_t subtr_enqcnt(const u_int16_t fid, const u_int32_t s) + { return _lpmp->get_fcntlp(fid)->subtr_enqcnt(s); } + + virtual inline u_int32_t subm_cnt_dblks() const = 0; + virtual inline std::size_t subm_offs() const = 0; + virtual inline u_int32_t add_subm_cnt_dblks(u_int32_t a) = 0; + + virtual inline u_int32_t cmpl_cnt_dblks() const = 0; + virtual inline std::size_t cmpl_offs() const = 0; + virtual inline u_int32_t add_cmpl_cnt_dblks(u_int32_t a) = 0; + + virtual inline bool is_void() const = 0; + virtual inline bool is_empty() const = 0; + virtual inline u_int32_t remaining_dblks() const = 0; + virtual inline bool is_full() const = 0; + virtual inline bool is_compl() const = 0; + virtual inline u_int32_t aio_outstanding_dblks() const = 0; + virtual inline bool file_rotate() const = 0; + + // Debug aid + virtual std::string status_str() const; + }; // class rfc + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_RFC_H diff --git a/cpp/src/qpid/legacystore/jrnl/rmgr.cpp b/cpp/src/qpid/legacystore/jrnl/rmgr.cpp new file mode 100644 index 0000000000..3a11817d1e --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/rmgr.cpp @@ -0,0 +1,698 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file rmgr.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::rmgr (read manager). See + * comments in file rmgr.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/rmgr.h" + +#include <cassert> +#include <cerrno> +#include <cstdlib> +#include "qpid/legacystore/jrnl/jcntl.h" +#include "qpid/legacystore/jrnl/jerrno.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +rmgr::rmgr(jcntl* jc, enq_map& emap, txn_map& tmap, rrfc& rrfc): + pmgr(jc, emap, tmap), + _rrfc(rrfc), + _hdr(), + _fhdr_buffer(0), + _fhdr_aio_cb_ptr(0), + _fhdr_rd_outstanding(false) +{} + +rmgr::~rmgr() +{ + rmgr::clean(); +} + +void +rmgr::initialize(aio_callback* const cbp) +{ + pmgr::initialize(cbp, JRNL_RMGR_PAGE_SIZE, JRNL_RMGR_PAGES); + clean(); + // Allocate memory for reading file header + if (::posix_memalign(&_fhdr_buffer, _sblksize, _sblksize)) + { + std::ostringstream oss; + oss << "posix_memalign(): blksize=" << _sblksize << " size=" << _sblksize; + oss << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR__MALLOC, oss.str(), "rmgr", "initialize"); + } + _fhdr_aio_cb_ptr = new aio_cb; + std::memset(_fhdr_aio_cb_ptr, 0, sizeof(aio_cb*)); +} + +void +rmgr::clean() +{ + std::free(_fhdr_buffer); + _fhdr_buffer = 0; + + if (_fhdr_aio_cb_ptr) + { + delete _fhdr_aio_cb_ptr; + _fhdr_aio_cb_ptr = 0; + } +} + +iores +rmgr::read(void** const datapp, std::size_t& dsize, void** const xidpp, std::size_t& xidsize, + bool& transient, bool& external, data_tok* dtokp, bool ignore_pending_txns) +{ + iores res = pre_read_check(dtokp); + if (res != RHM_IORES_SUCCESS) + { + set_params_null(datapp, dsize, xidpp, xidsize); + return res; + } + + if (dtokp->rstate() == data_tok::SKIP_PART) + { + if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE) + { + aio_cycle(); // check if rd AIOs returned; initiate new reads if possible + return RHM_IORES_PAGE_AIOWAIT; + } + const iores res = skip(dtokp); + if (res != RHM_IORES_SUCCESS) + { + set_params_null(datapp, dsize, xidpp, xidsize); + return res; + } + } + if (dtokp->rstate() == data_tok::READ_PART) + { + assert(dtokp->rid() == _hdr._rid); + void* rptr = (void*)((char*)_page_ptr_arr[_pg_index] + (_pg_offset_dblks * JRNL_DBLK_SIZE)); + const iores res = read_enq(_hdr, rptr, dtokp); + dsize = _enq_rec.get_data(datapp); + xidsize = _enq_rec.get_xid(xidpp); + transient = _enq_rec.is_transient(); + external = _enq_rec.is_external(); + return res; + } + + set_params_null(datapp, dsize, xidpp, xidsize); + _hdr.reset(); + // Read header, determine next record type + while (true) + { + if(dblks_rem() == 0 && _rrfc.is_compl() && !_rrfc.is_wr_aio_outstanding()) + { + aio_cycle(); // check if rd AIOs returned; initiate new reads if possible + if(dblks_rem() == 0 && _rrfc.is_compl() && !_rrfc.is_wr_aio_outstanding()) + { + if (_jc->unflushed_dblks() > 0) + _jc->flush(); + else if (!_aio_evt_rem) + return RHM_IORES_EMPTY; + } + } + if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE) + { + aio_cycle(); + return RHM_IORES_PAGE_AIOWAIT; + } + void* rptr = (void*)((char*)_page_ptr_arr[_pg_index] + (_pg_offset_dblks * JRNL_DBLK_SIZE)); + std::memcpy(&_hdr, rptr, sizeof(rec_hdr)); + switch (_hdr._magic) + { + case RHM_JDAT_ENQ_MAGIC: + { + _enq_rec.reset(); // sets enqueue rec size + // Check if RID of this rec is still enqueued, if so read it, else skip + bool is_enq = false; + int16_t fid = _emap.get_pfid(_hdr._rid); + if (fid < enq_map::EMAP_OK) + { + bool enforce_txns = !_jc->is_read_only() && !ignore_pending_txns; + // Block read for transactionally locked record (only when not recovering) + if (fid == enq_map::EMAP_LOCKED && enforce_txns) + return RHM_IORES_TXPENDING; + + // (Recover mode only) Ok, not in emap - now search tmap, if present then read + is_enq = _tmap.is_enq(_hdr._rid); + if (enforce_txns && is_enq) + return RHM_IORES_TXPENDING; + } + else + is_enq = true; + + if (is_enq) // ok, this record is enqueued, check it, then read it... + { + if (dtokp->rid()) + { + if (_hdr._rid != dtokp->rid()) + { + std::ostringstream oss; + oss << std::hex << "rid=0x" << _hdr._rid << "; dtok_rid=0x" << dtokp->rid() + << "; dtok_id=0x" << dtokp->id(); + throw jexception(jerrno::JERR_RMGR_RIDMISMATCH, oss.str(), "rmgr", "read"); + } + } + else + dtokp->set_rid(_hdr._rid); + +// TODO: Add member _fid to pmgr::page_cb which indicates the fid from which this page was +// populated. When this value is set in wmgr::flush() somewehere, then uncomment the following +// check: +// if (fid != _page_cb_arr[_pg_index]._fid) +// { +// std::ostringstream oss; +// oss << std::hex << std::setfill('0'); +// oss << "rid=0x" << std::setw(16) << _hdr._rid; +// oss << "; emap_fid=0x" << std::setw(4) << fid; +// oss << "; current_fid=" << _rrfc.fid(); +// throw jexception(jerrno::JERR_RMGR_FIDMISMATCH, oss.str(), "rmgr", +// "read"); +// } + + const iores res = read_enq(_hdr, rptr, dtokp); + dsize = _enq_rec.get_data(datapp); + xidsize = _enq_rec.get_xid(xidpp); + transient = _enq_rec.is_transient(); + external = _enq_rec.is_external(); + return res; + } + else // skip this record, it is already dequeued + consume_xid_rec(_hdr, rptr, dtokp); + break; + } + case RHM_JDAT_DEQ_MAGIC: + consume_xid_rec(_hdr, rptr, dtokp); + break; + case RHM_JDAT_TXA_MAGIC: + consume_xid_rec(_hdr, rptr, dtokp); + break; + case RHM_JDAT_TXC_MAGIC: + consume_xid_rec(_hdr, rptr, dtokp); + break; + case RHM_JDAT_EMPTY_MAGIC: + consume_filler(); + break; + default: + return RHM_IORES_EMPTY; + } + } +} + +int32_t +rmgr::get_events(page_state state, timespec* const timeout, bool flush) +{ + if (_aio_evt_rem == 0) // no events to get + return 0; + + int32_t ret; + if ((ret = aio::getevents(_ioctx, flush ? _aio_evt_rem : 1, _aio_evt_rem/*_cache_num_pages + _jc->num_jfiles()*/, _aio_event_arr, timeout)) < 0) + { + if (ret == -EINTR) // Interrupted by signal + return 0; + std::ostringstream oss; + oss << "io_getevents() failed: " << std::strerror(-ret) << " (" << ret << ")"; + throw jexception(jerrno::JERR__AIO, oss.str(), "rmgr", "get_events"); + } + if (ret == 0 && timeout) + return jerrno::AIO_TIMEOUT; + + std::vector<u_int16_t> pil; + pil.reserve(ret); + for (int i=0; i<ret; i++) // Index of returned AIOs + { + if (_aio_evt_rem == 0) + { + std::ostringstream oss; + oss << "_aio_evt_rem; evt " << (i + 1) << " of " << ret; + throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "rmgr", "get_events"); + } + _aio_evt_rem--; + aio_cb* aiocbp = _aio_event_arr[i].obj; // This I/O control block (iocb) + page_cb* pcbp = (page_cb*)(aiocbp->data); // This page control block (pcb) + long aioret = (long)_aio_event_arr[i].res; + if (aioret < 0) + { + std::ostringstream oss; + oss << "AIO read operation failed: " << std::strerror(-aioret) << " (" << aioret << ")"; + oss << " [pg=" << pcbp->_index << " buf=" << aiocbp->u.c.buf; + oss << " rsize=0x" << std::hex << aiocbp->u.c.nbytes; + oss << " offset=0x" << aiocbp->u.c.offset << std::dec; + oss << " fh=" << aiocbp->aio_fildes << "]"; + throw jexception(jerrno::JERR__AIO, oss.str(), "rmgr", "get_events"); + } + + if (pcbp) // Page reads have pcb + { + if (pcbp->_rfh->rd_subm_cnt_dblks() >= JRNL_SBLK_SIZE) // Detects if write reset of this fcntl obj has occurred. + { + // Increment the completed read offset + // NOTE: We cannot use _rrfc here, as it may have rotated since submitting count. + // Use stored pointer to fcntl in the pcb instead. + pcbp->_rdblks = aiocbp->u.c.nbytes / JRNL_DBLK_SIZE; + pcbp->_rfh->add_rd_cmpl_cnt_dblks(pcbp->_rdblks); + pcbp->_state = state; + pil[i] = pcbp->_index; + } + } + else // File header reads have no pcb + { + std::memcpy(&_fhdr, _fhdr_buffer, sizeof(file_hdr)); + _rrfc.add_cmpl_cnt_dblks(JRNL_SBLK_SIZE); + + u_int32_t fro_dblks = (_fhdr._fro / JRNL_DBLK_SIZE) - JRNL_SBLK_SIZE; + // Check fro_dblks does not exceed the write pointers which can happen in some corrupted journal recoveries + if (fro_dblks > _jc->wr_subm_cnt_dblks(_fhdr._pfid) - JRNL_SBLK_SIZE) + fro_dblks = _jc->wr_subm_cnt_dblks(_fhdr._pfid) - JRNL_SBLK_SIZE; + _pg_cntr = fro_dblks / (JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE); + u_int32_t tot_pg_offs_dblks = _pg_cntr * JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE; + _pg_index = _pg_cntr % JRNL_RMGR_PAGES; + _pg_offset_dblks = fro_dblks - tot_pg_offs_dblks; + _rrfc.add_subm_cnt_dblks(tot_pg_offs_dblks); + _rrfc.add_cmpl_cnt_dblks(tot_pg_offs_dblks); + + _fhdr_rd_outstanding = false; + _rrfc.set_valid(); + } + } + + // Perform AIO return callback + if (_cbp && ret) + _cbp->rd_aio_cb(pil); + return ret; +} + +void +rmgr::recover_complete() +{} + +void +rmgr::invalidate() +{ + if (_rrfc.is_valid()) + _rrfc.set_invalid(); +} + +void +rmgr::flush(timespec* timeout) +{ + // Wait for any outstanding AIO read operations to complete before synchronizing + while (_aio_evt_rem) + { + if (get_events(AIO_COMPLETE, timeout) == jerrno::AIO_TIMEOUT) // timed out, nothing returned + { + throw jexception(jerrno::JERR__TIMEOUT, + "Timed out waiting for outstanding read aio to return", "rmgr", "init_validation"); + } + } + + // Reset all read states and pointers + for (int i=0; i<_cache_num_pages; i++) + _page_cb_arr[i]._state = UNUSED; + _rrfc.unset_findex(); + _pg_index = 0; + _pg_offset_dblks = 0; +} + +bool +rmgr::wait_for_validity(timespec* timeout, const bool throw_on_timeout) +{ + bool timed_out = false; + while (!_rrfc.is_valid() && !timed_out) + { + timed_out = get_events(AIO_COMPLETE, timeout) == jerrno::AIO_TIMEOUT; + if (timed_out && throw_on_timeout) + throw jexception(jerrno::JERR__TIMEOUT, "Timed out waiting for read validity", "rmgr", "wait_for_validity"); + } + return _rrfc.is_valid(); +} + +iores +rmgr::pre_read_check(data_tok* dtokp) +{ + if (_aio_evt_rem) + get_events(AIO_COMPLETE, 0); + + if (!_rrfc.is_valid()) + return RHM_IORES_RCINVALID; + + // block reads until outstanding file header read completes as fro is needed to read + if (_fhdr_rd_outstanding) + return RHM_IORES_PAGE_AIOWAIT; + + if(dblks_rem() == 0 && _rrfc.is_compl() && !_rrfc.is_wr_aio_outstanding()) + { + aio_cycle(); // check if any AIOs have returned + if(dblks_rem() == 0 && _rrfc.is_compl() && !_rrfc.is_wr_aio_outstanding()) + { + if (_jc->unflushed_dblks() > 0) + _jc->flush(); + else if (!_aio_evt_rem) + return RHM_IORES_EMPTY; + } + } + + // Check write state of this token is ENQ - required for read + if (dtokp) + { + if (!dtokp->is_readable()) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "dtok_id=0x" << std::setw(8) << dtokp->id(); + oss << "; dtok_rid=0x" << std::setw(16) << dtokp->rid(); + oss << "; dtok_wstate=" << dtokp->wstate_str(); + throw jexception(jerrno::JERR_RMGR_ENQSTATE, oss.str(), "rmgr", "pre_read_check"); + } + } + + return RHM_IORES_SUCCESS; +} + +iores +rmgr::read_enq(rec_hdr& h, void* rptr, data_tok* dtokp) +{ + if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE) + { + aio_cycle(); // check if any AIOs have returned + return RHM_IORES_PAGE_AIOWAIT; + } + + // Read data from this page, first block will have header and data size. + u_int32_t dblks_rd = _enq_rec.decode(h, rptr, dtokp->dblocks_read(), dblks_rem()); + dtokp->incr_dblocks_read(dblks_rd); + + _pg_offset_dblks += dblks_rd; + + // If data still incomplete, move to next page and decode again + while (dtokp->dblocks_read() < _enq_rec.rec_size_dblks()) + { + rotate_page(); + if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE) + { + dtokp->set_rstate(data_tok::READ_PART); + dtokp->set_dsize(_enq_rec.data_size()); + return RHM_IORES_PAGE_AIOWAIT; + } + + rptr = (void*)((char*)_page_ptr_arr[_pg_index]); + dblks_rd = _enq_rec.decode(h, rptr, dtokp->dblocks_read(), dblks_rem()); + dtokp->incr_dblocks_read(dblks_rd); + _pg_offset_dblks += dblks_rd; + } + + // If we have finished with this page, rotate it + if (dblks_rem() == 0) + rotate_page(); + + // Set the record size in dtokp + dtokp->set_rstate(data_tok::READ); + dtokp->set_dsize(_enq_rec.data_size()); + return RHM_IORES_SUCCESS; +} + +void +rmgr::consume_xid_rec(rec_hdr& h, void* rptr, data_tok* dtokp) +{ + if (h._magic == RHM_JDAT_ENQ_MAGIC) + { + enq_hdr ehdr; + std::memcpy(&ehdr, rptr, sizeof(enq_hdr)); + if (ehdr.is_external()) + dtokp->set_dsize(ehdr._xidsize + sizeof(enq_hdr) + sizeof(rec_tail)); + else + dtokp->set_dsize(ehdr._xidsize + ehdr._dsize + sizeof(enq_hdr) + sizeof(rec_tail)); + } + else if (h._magic == RHM_JDAT_DEQ_MAGIC) + { + deq_hdr dhdr; + std::memcpy(&dhdr, rptr, sizeof(deq_hdr)); + if (dhdr._xidsize) + dtokp->set_dsize(dhdr._xidsize + sizeof(deq_hdr) + sizeof(rec_tail)); + else + dtokp->set_dsize(sizeof(deq_hdr)); + } + else if (h._magic == RHM_JDAT_TXA_MAGIC || h._magic == RHM_JDAT_TXC_MAGIC) + { + txn_hdr thdr; + std::memcpy(&thdr, rptr, sizeof(txn_hdr)); + dtokp->set_dsize(thdr._xidsize + sizeof(txn_hdr) + sizeof(rec_tail)); + } + else + { + std::ostringstream oss; + oss << "Record type found = \"" << (char*)&h._magic << "\""; + throw jexception(jerrno::JERR_RMGR_BADRECTYPE, oss.str(), "rmgr", "consume_xid_rec"); + } + dtokp->set_dblocks_read(0); + skip(dtokp); +} + +void +rmgr::consume_filler() +{ + // Filler (Magic "RHMx") is one dblk by definition + _pg_offset_dblks++; + if (dblks_rem() == 0) + rotate_page(); +} + +iores +rmgr::skip(data_tok* dtokp) +{ + u_int32_t dsize_dblks = jrec::size_dblks(dtokp->dsize()); + u_int32_t tot_dblk_cnt = dtokp->dblocks_read(); + while (true) + { + u_int32_t this_dblk_cnt = 0; + if (dsize_dblks - tot_dblk_cnt > dblks_rem()) + this_dblk_cnt = dblks_rem(); + else + this_dblk_cnt = dsize_dblks - tot_dblk_cnt; + if (this_dblk_cnt) + { + dtokp->incr_dblocks_read(this_dblk_cnt); + _pg_offset_dblks += this_dblk_cnt; + tot_dblk_cnt += this_dblk_cnt; + } + // If skip still incomplete, move to next page and decode again + if (tot_dblk_cnt < dsize_dblks) + { + if (dblks_rem() == 0) + rotate_page(); + if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE) + { + dtokp->set_rstate(data_tok::SKIP_PART); + return RHM_IORES_PAGE_AIOWAIT; + } + } + else + { + // Skip complete, put state back to unread + dtokp->set_rstate(data_tok::UNREAD); + dtokp->set_dsize(0); + dtokp->set_dblocks_read(0); + + // If we have finished with this page, rotate it + if (dblks_rem() == 0) + rotate_page(); + return RHM_IORES_SUCCESS; + } + } +} + +iores +rmgr::aio_cycle() +{ + // Perform validity checks + if (_fhdr_rd_outstanding) // read of file header still outstanding in aio + return RHM_IORES_SUCCESS; + if (!_rrfc.is_valid()) + { + // Flush and reset all read states and pointers + flush(&jcntl::_aio_cmpl_timeout); + + _jc->get_earliest_fid(); // determine initial file to read; calls _rrfc.set_findex() to set value + // If this file has not yet been written to, return RHM_IORES_EMPTY + if (_rrfc.is_void() && !_rrfc.is_wr_aio_outstanding()) + return RHM_IORES_EMPTY; + init_file_header_read(); // send off AIO read request for file header + return RHM_IORES_SUCCESS; + } + + int16_t first_uninit = -1; + u_int16_t num_uninit = 0; + u_int16_t num_compl = 0; + bool outstanding = false; + // Index must start with current buffer and cycle around so that first + // uninitialized buffer is initialized first + for (u_int16_t i=_pg_index; i<_pg_index+_cache_num_pages; i++) + { + int16_t ci = i % _cache_num_pages; + switch (_page_cb_arr[ci]._state) + { + case UNUSED: + if (first_uninit < 0) + first_uninit = ci; + num_uninit++; + break; + case IN_USE: + break; + case AIO_PENDING: + outstanding = true; + break; + case AIO_COMPLETE: + num_compl++; + break; + default:; + } + } + iores res = RHM_IORES_SUCCESS; + if (num_uninit) + res = init_aio_reads(first_uninit, num_uninit); + else if (num_compl == _cache_num_pages) // This condition exists after invalidation + res = init_aio_reads(0, _cache_num_pages); + if (outstanding) + get_events(AIO_COMPLETE, 0); + return res; +} + +iores +rmgr::init_aio_reads(const int16_t first_uninit, const u_int16_t num_uninit) +{ + for (int16_t i=0; i<num_uninit; i++) + { + if (_rrfc.is_void()) // Nothing to do; this file not yet written to + break; + + if (_rrfc.subm_offs() == 0) + { + _rrfc.add_subm_cnt_dblks(JRNL_SBLK_SIZE); + _rrfc.add_cmpl_cnt_dblks(JRNL_SBLK_SIZE); + } + + // TODO: Future perf improvement: Do a single AIO read for all available file + // space into all contiguous empty pages in one AIO operation. + + u_int32_t file_rem_dblks = _rrfc.remaining_dblks(); + file_rem_dblks -= file_rem_dblks % JRNL_SBLK_SIZE; // round down to closest sblk boundary + u_int32_t pg_size_dblks = JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE; + u_int32_t rd_size = file_rem_dblks > pg_size_dblks ? pg_size_dblks : file_rem_dblks; + if (rd_size) + { + int16_t pi = (i + first_uninit) % _cache_num_pages; + // TODO: For perf, combine contiguous pages into single read + // 1 or 2 AIOs needed depending on whether read block folds + aio_cb* aiocbp = &_aio_cb_arr[pi]; + aio::prep_pread_2(aiocbp, _rrfc.fh(), _page_ptr_arr[pi], rd_size * JRNL_DBLK_SIZE, _rrfc.subm_offs()); + if (aio::submit(_ioctx, 1, &aiocbp) < 0) + throw jexception(jerrno::JERR__AIO, "rmgr", "init_aio_reads"); + _rrfc.add_subm_cnt_dblks(rd_size); + _aio_evt_rem++; + _page_cb_arr[pi]._state = AIO_PENDING; + _page_cb_arr[pi]._rfh = _rrfc.file_controller(); + } + else // If there is nothing to read for this page, neither will there be for the others... + break; + if (_rrfc.file_rotate()) + _rrfc.rotate(); + } + return RHM_IORES_SUCCESS; +} + +void +rmgr::rotate_page() +{ + _page_cb_arr[_pg_index]._rdblks = 0; + _page_cb_arr[_pg_index]._state = UNUSED; + if (_pg_offset_dblks >= JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE) + { + _pg_offset_dblks = 0; + _pg_cntr++; + } + if (++_pg_index >= _cache_num_pages) + _pg_index = 0; + aio_cycle(); + _pg_offset_dblks = 0; + // This counter is for bookkeeping only, page rotates are handled directly in init_aio_reads() + // FIXME: _pg_cntr should be sync'd with aio ops, not use of page as it is now... + // Need to move reset into if (_rrfc.file_rotate()) above. + if (_pg_cntr >= (_jc->jfsize_sblks() / JRNL_RMGR_PAGE_SIZE)) + _pg_cntr = 0; +} + +u_int32_t +rmgr::dblks_rem() const +{ + return _page_cb_arr[_pg_index]._rdblks - _pg_offset_dblks; +} + +void +rmgr::set_params_null(void** const datapp, std::size_t& dsize, void** const xidpp, std::size_t& xidsize) +{ + *datapp = 0; + dsize = 0; + *xidpp = 0; + xidsize = 0; +} + +void +rmgr::init_file_header_read() +{ + _jc->fhdr_wr_sync(_rrfc.index()); // wait if the file header write is outstanding + int rfh = _rrfc.fh(); + aio::prep_pread_2(_fhdr_aio_cb_ptr, rfh, _fhdr_buffer, _sblksize, 0); + if (aio::submit(_ioctx, 1, &_fhdr_aio_cb_ptr) < 0) + throw jexception(jerrno::JERR__AIO, "rmgr", "init_file_header_read"); + _aio_evt_rem++; + _rrfc.add_subm_cnt_dblks(JRNL_SBLK_SIZE); + _fhdr_rd_outstanding = true; +} + +/* TODO (sometime in the future) +const iores +rmgr::get(const u_int64_t& rid, const std::size_t& dsize, const std::size_t& dsize_avail, + const void** const data, bool auto_discard) +{ + return RHM_IORES_SUCCESS; +} + +const iores +rmgr::discard(data_tok* dtokp) +{ + return RHM_IORES_SUCCESS; +} +*/ + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/rmgr.h b/cpp/src/qpid/legacystore/jrnl/rmgr.h new file mode 100644 index 0000000000..ae4b5f56c8 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/rmgr.h @@ -0,0 +1,114 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file rmgr.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::rmgr (read manager). See + * class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_RMGR_H +#define QPID_LEGACYSTORE_JRNL_RMGR_H + +namespace mrg +{ +namespace journal +{ +class rmgr; +} +} + +#include <cstring> +#include "jrnl/enums.h" +#include "jrnl/file_hdr.h" +#include "jrnl/pmgr.h" +#include "jrnl/rec_hdr.h" +#include "jrnl/rrfc.h" + +namespace mrg +{ +namespace journal +{ + + /** + * \brief Class for managing a read page cache of arbitrary size and number of pages. + * + * The read page cache works on the principle of filling as many pages as possilbe in advance of + * reading the data. This ensures that delays caused by AIO operations are minimized. + */ + class rmgr : public pmgr + { + private: + rrfc& _rrfc; ///< Ref to read rotating file controller + rec_hdr _hdr; ///< Header used to determind record type + + void* _fhdr_buffer; ///< Buffer used for fhdr reads + aio_cb* _fhdr_aio_cb_ptr; ///< iocb pointer for fhdr reads + file_hdr _fhdr; ///< file header instance for reading file headers + bool _fhdr_rd_outstanding; ///< true if a fhdr read is outstanding + + public: + rmgr(jcntl* jc, enq_map& emap, txn_map& tmap, rrfc& rrfc); + virtual ~rmgr(); + + using pmgr::initialize; + void initialize(aio_callback* const cbp); + iores read(void** const datapp, std::size_t& dsize, void** const xidpp, + std::size_t& xidsize, bool& transient, bool& external, data_tok* dtokp, + bool ignore_pending_txns); + int32_t get_events(page_state state, timespec* const timeout, bool flush = false); + void recover_complete(); + inline iores synchronize() { if (_rrfc.is_valid()) return RHM_IORES_SUCCESS; return aio_cycle(); } + void invalidate(); + bool wait_for_validity(timespec* const timeout, const bool throw_on_timeout = false); + + /* TODO (if required) + const iores get(const u_int64_t& rid, const std::size_t& dsize, const std::size_t& dsize_avail, + const void** const data, bool auto_discard); + const iores discard(data_tok* dtok); + */ + + private: + void clean(); + void flush(timespec* timeout); + iores pre_read_check(data_tok* dtokp); + iores read_enq(rec_hdr& h, void* rptr, data_tok* dtokp); + void consume_xid_rec(rec_hdr& h, void* rptr, data_tok* dtokp); + void consume_filler(); + iores skip(data_tok* dtokp); + iores aio_cycle(); + iores init_aio_reads(const int16_t first_uninit, const u_int16_t num_uninit); + void rotate_page(); + u_int32_t dblks_rem() const; + void set_params_null(void** const datapp, std::size_t& dsize, void** const xidpp, + std::size_t& xidsize); + void init_file_header_read(); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_RMGR_H diff --git a/cpp/src/qpid/legacystore/jrnl/rrfc.cpp b/cpp/src/qpid/legacystore/jrnl/rrfc.cpp new file mode 100644 index 0000000000..fc6f5d427f --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/rrfc.cpp @@ -0,0 +1,125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file rrfc.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::rrfc (rotating + * file controller). See comments in file rrfc.h for details. + * + * \author Kim van der Riet + */ + + +#include "qpid/legacystore/jrnl/rrfc.h" + +#include <cerrno> +#include <fcntl.h> +#include <unistd.h> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" + +namespace mrg +{ +namespace journal +{ + +rrfc::rrfc(const lpmgr* lpmp): rfc(lpmp), _fh(-1), _valid(false) +{} + +rrfc::~rrfc() +{ + close_fh(); +} + +void +rrfc::finalize() +{ + unset_findex(); + rfc::finalize(); +} + +void +rrfc::set_findex(const u_int16_t fc_index) +{ + rfc::set_findex(fc_index); + open_fh(_curr_fc->fname()); +} + +void +rrfc::unset_findex() +{ + set_invalid(); + close_fh(); + rfc::unset_findex(); +} + +iores +rrfc::rotate() +{ + if (!_lpmp->num_jfiles()) + throw jexception(jerrno::JERR__NINIT, "rrfc", "rotate"); + u_int16_t next_fc_index = _fc_index + 1; + if (next_fc_index == _lpmp->num_jfiles()) + next_fc_index = 0; + set_findex(next_fc_index); + return RHM_IORES_SUCCESS; +} + +std::string +rrfc::status_str() const +{ + std::ostringstream oss; + oss << "rrfc: " << rfc::status_str(); + if (is_active()) + oss << " fcntl[" << _fc_index << "]: " << _curr_fc->status_str(); + return oss.str(); +} + +// === protected functions === + +void +rrfc::open_fh(const std::string& fn) +{ + close_fh(); + _fh = ::open(fn.c_str(), O_RDONLY | O_DIRECT); + if (_fh < 0) + { + std::ostringstream oss; + oss << "file=\"" << fn << "\"" << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR_RRFC_OPENRD, oss.str(), "rrfc", "open_fh"); + } +} + +void +rrfc::close_fh() +{ + if (_fh >= 0) + { + ::close(_fh); + _fh = -1; + } +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/rrfc.h b/cpp/src/qpid/legacystore/jrnl/rrfc.h new file mode 100644 index 0000000000..5066d6048a --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/rrfc.h @@ -0,0 +1,179 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file rrfc.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::rrfc (rotating + * file controller). See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_RRFC_H +#define QPID_LEGACYSTORE_JRNL_RRFC_H + +namespace mrg +{ +namespace journal +{ +class rrfc; +} +} + +#include "qpid/legacystore/jrnl/fcntl.h" +#include "qpid/legacystore/jrnl/rfc.h" + +namespace mrg +{ +namespace journal +{ + + /** + * \class rrfc + * \brief Read Rotating File Controller (rrfc) - Subclassed from pure virtual class rfc. Used to control the read + * pipeline in a rotating file buffer or journal. See class rfc for further details. + * + * The states that exist in this class are identical to class rfc from which it inherits, but in addition, the value + * of the read file handle _fh is also considered. The calls to set_findex also opens the file handle _fh to the + * active file for reading. Similarly, unset_findex() closes this file handle. + * + * <pre> + * is_init() is_active() + * +===+ _lpmp.is_init() == false + * +---------->| | Uninitialized: _curr_fc == 0 F F + * | +-->+===+ --+ _fh == -1 + * | | | + * | | | + * | finalize() initialize() + * | | | + * | | | + * | +-- +===+<--+ _lpmp.is_init() == true + * finalize() | | Inactive: _curr_fc == 0 T F + * | +-->+===+ --+ _fh == -1 + * | | | + * | | | + * | unset_findex() set_findex() + * | | | + * | | | + * | +-- +===+<--+ _lpmp.is_init() == true + * +---------- | | Active: _curr_fc != 0 T T + * +===+ _fh >= 0 + * </pre> + * + * In adition to the states above, class rrfc contains a validity flag. This is operated indepenedently of the state + * machine. This flag (_valid) indicates when the read buffers are valid for reading. This is not strictly a state, + * but simply a flag used to keep track of the status, and is set/unset with calls to set_valid() and set_invalid() + * respectively. + */ + class rrfc : public rfc + { + protected: + int _fh; ///< Read file handle + bool _valid; ///< Flag is true when read pages contain vailid data + + public: + rrfc(const lpmgr* lpmp); + virtual ~rrfc(); + + /** + * \brief Initialize the controller, moving from state Uninitialized to Initialized. The main function of + * initialize() is to set the number of files and the pointer to the fcntl array. + */ + inline void initialize() { rfc::initialize(); _valid = false; } + + /** + * \brief Reset the controller to Uninitialized state, usually called when the journal is stopped. Once called, + * initialize() must be called to reuse an instance. + */ + void finalize(); + + /** + * \brief Opens the file handle for reading a particular fid. Moves to state open. + */ + void set_findex(const u_int16_t fc_index); + + /** + * \brief Closes the read file handle and nulls the active fcntl pointer. Moves to state closed. + */ + void unset_findex(); + + /** + * \brief Check the state: true = open; false = closed. + */ + inline bool is_active() const { return _curr_fc != 0 && _fh >= 0; } + + /** + * \brief Sets the validity flag which indicates that the read buffers contain valid data for reading. + */ + inline void set_invalid() { _valid = false; } + + /** + * \brief Resets the validity flag wich indicates that the read buffers are no longer synchronized and cannot + * be read whithout resynchronization. + */ + inline void set_valid() { _valid = true; } + + /** + * \brief Checks the read buffer validity status: true = valid, can be read; false = invalid, synchronization + * required. + */ + inline bool is_valid() const { return _valid; } + + /** + * \brief Rotate active file controller to next file in rotating file group. + * \exception jerrno::JERR__NINIT if called before calling initialize(). + */ + iores rotate(); + + inline int fh() const { return _fh; } + + inline u_int32_t subm_cnt_dblks() const { return _curr_fc->rd_subm_cnt_dblks(); } + inline std::size_t subm_offs() const { return _curr_fc->rd_subm_offs(); } + inline u_int32_t add_subm_cnt_dblks(u_int32_t a) { return _curr_fc->add_rd_subm_cnt_dblks(a); } + + inline u_int32_t cmpl_cnt_dblks() const { return _curr_fc->rd_cmpl_cnt_dblks(); } + inline std::size_t cmpl_offs() const { return _curr_fc->rd_cmpl_offs(); } + inline u_int32_t add_cmpl_cnt_dblks(u_int32_t a) { return _curr_fc->add_rd_cmpl_cnt_dblks(a); } + + inline bool is_void() const { return _curr_fc->rd_void(); } + inline bool is_empty() const { return _curr_fc->rd_empty(); } + inline u_int32_t remaining_dblks() const { return _curr_fc->rd_remaining_dblks(); } + inline bool is_full() const { return _curr_fc->is_rd_full(); } + inline bool is_compl() const { return _curr_fc->is_rd_compl(); } + inline u_int32_t aio_outstanding_dblks() const { return _curr_fc->rd_aio_outstanding_dblks(); } + inline bool file_rotate() const { return _curr_fc->rd_file_rotate(); } + inline bool is_wr_aio_outstanding() const { return _curr_fc->wr_aio_outstanding_dblks() > 0; } + + // Debug aid + std::string status_str() const; + + protected: + void open_fh(const std::string& fn); + void close_fh(); + }; // class rrfc + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_RRFC_H diff --git a/cpp/src/qpid/legacystore/jrnl/slock.cpp b/cpp/src/qpid/legacystore/jrnl/slock.cpp new file mode 100644 index 0000000000..8f26d349ef --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/slock.cpp @@ -0,0 +1,33 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file slock.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::slock (scoped lock). See + * comments in file slock.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/slock.h" diff --git a/cpp/src/qpid/legacystore/jrnl/slock.h b/cpp/src/qpid/legacystore/jrnl/slock.h new file mode 100644 index 0000000000..c05b5cf336 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/slock.h @@ -0,0 +1,85 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file slock.h + * + * Qpid asynchronous store plugin library + * + * Messaging journal scoped lock class mrg::journal::slock and scoped try-lock + * class mrg::journal::stlock. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_SLOCK_H +#define QPID_LEGACYSTORE_JRNL_SLOCK_H + +#include "qpid/legacystore/jrnl/jexception.h" +#include "qpid/legacystore/jrnl/smutex.h" +#include <pthread.h> + +namespace mrg +{ +namespace journal +{ + + // Ultra-simple scoped lock class, auto-releases mutex when it goes out-of-scope + class slock + { + protected: + const smutex& _sm; + public: + inline slock(const smutex& sm) : _sm(sm) + { + PTHREAD_CHK(::pthread_mutex_lock(_sm.get()), "::pthread_mutex_lock", "slock", "slock"); + } + inline ~slock() + { + PTHREAD_CHK(::pthread_mutex_unlock(_sm.get()), "::pthread_mutex_unlock", "slock", "~slock"); + } + }; + + // Ultra-simple scoped try-lock class, auto-releases mutex when it goes out-of-scope + class stlock + { + protected: + const smutex& _sm; + bool _locked; + public: + inline stlock(const smutex& sm) : _sm(sm), _locked(false) + { + int ret = ::pthread_mutex_trylock(_sm.get()); + _locked = (ret == 0); // check if lock obtained + if (!_locked && ret != EBUSY) PTHREAD_CHK(ret, "::pthread_mutex_trylock", "stlock", "stlock"); + } + inline ~stlock() + { + if (_locked) + PTHREAD_CHK(::pthread_mutex_unlock(_sm.get()), "::pthread_mutex_unlock", "stlock", "~stlock"); + } + inline bool locked() const { return _locked; } + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_SLOCK_H diff --git a/cpp/src/qpid/legacystore/jrnl/smutex.cpp b/cpp/src/qpid/legacystore/jrnl/smutex.cpp new file mode 100644 index 0000000000..6f8991ca5b --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/smutex.cpp @@ -0,0 +1,33 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file smutex.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::smutex (scoped mutex). See + * comments in file smutex.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/smutex.h" diff --git a/cpp/src/qpid/legacystore/jrnl/smutex.h b/cpp/src/qpid/legacystore/jrnl/smutex.h new file mode 100644 index 0000000000..def0fb70f6 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/smutex.h @@ -0,0 +1,64 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file smutex.h + * + * Qpid asynchronous store plugin library + * + * Messaging journal scoped mutex class mrg::journal::smutex. + * + * \author Kim van der Riet + */ + + +#ifndef QPID_LEGACYSTORE_JRNL_SMUTEX_H +#define QPID_LEGACYSTORE_JRNL_SMUTEX_H + +#include "qpid/legacystore/jrnl/jexception.h" +#include <pthread.h> + +namespace mrg +{ +namespace journal +{ + + // Ultra-simple scoped mutex class that allows a posix mutex to be initialized and destroyed with error checks + class smutex + { + protected: + mutable pthread_mutex_t _m; + public: + inline smutex() + { + PTHREAD_CHK(::pthread_mutex_init(&_m, 0), "::pthread_mutex_init", "smutex", "smutex"); + } + inline virtual ~smutex() + { + PTHREAD_CHK(::pthread_mutex_destroy(&_m), "::pthread_mutex_destroy", "smutex", "~smutex"); + } + inline pthread_mutex_t* get() const { return &_m; } + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_SMUTEX_H diff --git a/cpp/src/qpid/legacystore/jrnl/time_ns.cpp b/cpp/src/qpid/legacystore/jrnl/time_ns.cpp new file mode 100644 index 0000000000..976068ef68 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/time_ns.cpp @@ -0,0 +1,55 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file time_ns.cpp + * + * Qpid asynchronous store plugin library + * + * Messaging journal time struct mrg::journal::time_ns, derived from + * the timespec struct and provided with helper functions. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/time_ns.h" + +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +const std::string +time_ns::str(int precision) const +{ + const double t = tv_sec + (tv_nsec/1e9); + std::ostringstream oss; + oss.setf(std::ios::fixed, std::ios::floatfield); + oss.precision(precision); + oss << t; + return oss.str(); +} + + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/time_ns.h b/cpp/src/qpid/legacystore/jrnl/time_ns.h new file mode 100644 index 0000000000..a9f69e2631 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/time_ns.h @@ -0,0 +1,105 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file time_ns.h + * + * Qpid asynchronous store plugin library + * + * Messaging journal time struct mrg::journal::time_ns, derived from + * the timespec struct and provided with helper functions. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_TIME_NS_H +#define QPID_LEGACYSTORE_JRNL_TIME_NS_H + +#include <cerrno> +#include <ctime> +#include <string> + +namespace mrg +{ +namespace journal +{ + +struct time_ns : public timespec +{ + inline time_ns() { tv_sec = 0; tv_nsec = 0; } + inline time_ns(const std::time_t sec, const long nsec = 0) { tv_sec = sec; tv_nsec = nsec; } + inline time_ns(const time_ns& t) { tv_sec = t.tv_sec; tv_nsec = t.tv_nsec; } + + inline void set_zero() { tv_sec = 0; tv_nsec = 0; } + inline bool is_zero() const { return tv_sec == 0 && tv_nsec == 0; } + inline int now() { if(::clock_gettime(CLOCK_REALTIME, this)) return errno; return 0; } + const std::string str(int precision = 6) const; + + inline time_ns& operator=(const time_ns& rhs) + { tv_sec = rhs.tv_sec; tv_nsec = rhs.tv_nsec; return *this; } + inline time_ns& operator+=(const time_ns& rhs) + { + tv_nsec += rhs.tv_nsec; + if (tv_nsec >= 1000000000L) { tv_sec++; tv_nsec -= 1000000000L; } + tv_sec += rhs.tv_sec; + return *this; + } + inline time_ns& operator+=(const long ns) + { + tv_nsec += ns; + if (tv_nsec >= 1000000000L) { tv_sec++; tv_nsec -= 1000000000L; } + return *this; + } + inline time_ns& operator-=(const long ns) + { + tv_nsec -= ns; + if (tv_nsec < 0) { tv_sec--; tv_nsec += 1000000000L; } + return *this; + } + inline time_ns& operator-=(const time_ns& rhs) + { + tv_nsec -= rhs.tv_nsec; + if (tv_nsec < 0) { tv_sec--; tv_nsec += 1000000000L; } + tv_sec -= rhs.tv_sec; + return *this; + } + inline const time_ns operator+(const time_ns& rhs) + { time_ns t(*this); t += rhs; return t; } + inline const time_ns operator-(const time_ns& rhs) + { time_ns t(*this); t -= rhs; return t; } + inline bool operator==(const time_ns& rhs) + { return tv_sec == rhs.tv_sec && tv_nsec == rhs.tv_nsec; } + inline bool operator!=(const time_ns& rhs) + { return tv_sec != rhs.tv_sec || tv_nsec != rhs.tv_nsec; } + inline bool operator>(const time_ns& rhs) + { if(tv_sec == rhs.tv_sec) return tv_nsec > rhs.tv_nsec; return tv_sec > rhs.tv_sec; } + inline bool operator>=(const time_ns& rhs) + { if(tv_sec == rhs.tv_sec) return tv_nsec >= rhs.tv_nsec; return tv_sec >= rhs.tv_sec; } + inline bool operator<(const time_ns& rhs) + { if(tv_sec == rhs.tv_sec) return tv_nsec < rhs.tv_nsec; return tv_sec < rhs.tv_sec; } + inline bool operator<=(const time_ns& rhs) + { if(tv_sec == rhs.tv_sec) return tv_nsec <= rhs.tv_nsec; return tv_sec <= rhs.tv_sec; } +}; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_TIME_NS_H diff --git a/cpp/src/qpid/legacystore/jrnl/txn_hdr.h b/cpp/src/qpid/legacystore/jrnl/txn_hdr.h new file mode 100644 index 0000000000..94b812ccec --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/txn_hdr.h @@ -0,0 +1,125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file txn_hdr.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::txn_hdr (transaction + * record header), used to start a transaction (commit or abort) record. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_TXN_HDR_H +#define QPID_LEGACYSTORE_JRNL_TXN_HDR_H + +#include <cstddef> +#include "qpid/legacystore/jrnl/rec_hdr.h" + +namespace mrg +{ +namespace journal +{ + +#pragma pack(1) + + /** + * \brief Struct for transaction commit and abort records. + * + * Struct for DTX commit and abort records. Only the magic distinguishes between them. Since + * this record must be used in the context of a valid XID, the xidsize field must not be zero. + * Immediately following this record is the XID itself which is xidsize bytes long, followed by + * a rec_tail. + * + * Note that this record had its own rid distinct from the rids of the record(s) making up the + * transaction it is committing or aborting. + * + * Record header info in binary format (24 bytes): + * <pre> + * 0 7 + * +---+---+---+---+---+---+---+---+ -+ + * | magic | v | e | flags | | + * +---+---+---+---+---+---+---+---+ | struct hdr + * | rid | | + * +---+---+---+---+---+---+---+---+ -+ + * | xidsize | + * +---+---+---+---+---+---+---+---+ + * v = file version (If the format or encoding of this file changes, then this + * number should be incremented) + * e = endian flag, false (0x00) for little endian, true (0x01) for big endian + * </pre> + * + * Note that journal files should be transferable between 32- and 64-bit + * hardware of the same endianness, but not between hardware of opposite + * entianness without some sort of binary conversion utility. Thus buffering + * will be needed for types that change size between 32- and 64-bit compiles. + */ + struct txn_hdr : rec_hdr + { +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler0; ///< Big-endian filler for 32-bit size_t +#endif + std::size_t _xidsize; ///< XID size +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + u_int32_t _filler0; ///< Little-endian filler for 32-bit size_t +#endif + + /** + * \brief Default constructor, which sets all values to 0. + */ + txn_hdr(): rec_hdr(), +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif + _xidsize(0) +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + , _filler0(0) +#endif + {} + + /** + * \brief Convenience constructor which initializes values during construction. + */ + txn_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid, + const std::size_t xidsize, const bool owi): rec_hdr(magic, version, rid, owi), +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + _filler0(0), +#endif + _xidsize(xidsize) +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + , _filler0(0) +#endif + {} + + /** + * \brief Returns the size of the header in bytes. + */ + inline static std::size_t size() { return sizeof(txn_hdr); } + }; + +#pragma pack() + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_TXN_HDR_H diff --git a/cpp/src/qpid/legacystore/jrnl/txn_map.cpp b/cpp/src/qpid/legacystore/jrnl/txn_map.cpp new file mode 100644 index 0000000000..c514670601 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/txn_map.cpp @@ -0,0 +1,256 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file txn_map.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::txn_map (transaction map). See + * comments in file txn_map.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/txn_map.h" + +#include <iomanip> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include "qpid/legacystore/jrnl/slock.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +// return/error codes +int16_t txn_map::TMAP_RID_NOT_FOUND = -2; +int16_t txn_map::TMAP_XID_NOT_FOUND = -1; +int16_t txn_map::TMAP_OK = 0; +int16_t txn_map::TMAP_NOT_SYNCED = 0; +int16_t txn_map::TMAP_SYNCED = 1; + +txn_data_struct::txn_data_struct(const u_int64_t rid, const u_int64_t drid, const u_int16_t pfid, + const bool enq_flag, const bool commit_flag): + _rid(rid), + _drid(drid), + _pfid(pfid), + _enq_flag(enq_flag), + _commit_flag(commit_flag), + _aio_compl(false) +{} + +txn_map::txn_map(): + _map(), + _pfid_txn_cnt() +{} + +txn_map::~txn_map() {} + +void +txn_map::set_num_jfiles(const u_int16_t num_jfiles) +{ + _pfid_txn_cnt.resize(num_jfiles, 0); +} + +u_int32_t +txn_map::get_txn_pfid_cnt(const u_int16_t pfid) const +{ + return _pfid_txn_cnt.at(pfid); +} + +bool +txn_map::insert_txn_data(const std::string& xid, const txn_data& td) +{ + bool ok = true; + slock s(_mutex); + xmap_itr itr = _map.find(xid); + if (itr == _map.end()) // not found in map + { + txn_data_list list; + list.push_back(td); + std::pair<xmap_itr, bool> ret = _map.insert(xmap_param(xid, list)); + if (!ret.second) // duplicate + ok = false; + } + else + itr->second.push_back(td); + _pfid_txn_cnt.at(td._pfid)++; + return ok; +} + +const txn_data_list +txn_map::get_tdata_list(const std::string& xid) +{ + slock s(_mutex); + return get_tdata_list_nolock(xid); +} + +const txn_data_list +txn_map::get_tdata_list_nolock(const std::string& xid) +{ + xmap_itr itr = _map.find(xid); + if (itr == _map.end()) // not found in map + return _empty_data_list; + return itr->second; +} + +const txn_data_list +txn_map::get_remove_tdata_list(const std::string& xid) +{ + slock s(_mutex); + xmap_itr itr = _map.find(xid); + if (itr == _map.end()) // not found in map + return _empty_data_list; + txn_data_list list = itr->second; + _map.erase(itr); + for (tdl_itr i=list.begin(); i!=list.end(); i++) + _pfid_txn_cnt.at(i->_pfid)--; + return list; +} + +bool +txn_map::in_map(const std::string& xid) +{ + slock s(_mutex); + xmap_itr itr= _map.find(xid); + return itr != _map.end(); +} + +u_int32_t +txn_map::enq_cnt() +{ + return cnt(true); +} + +u_int32_t +txn_map::deq_cnt() +{ + return cnt(true); +} + +u_int32_t +txn_map::cnt(const bool enq_flag) +{ + slock s(_mutex); + u_int32_t c = 0; + for (xmap_itr i = _map.begin(); i != _map.end(); i++) + { + for (tdl_itr j = i->second.begin(); j < i->second.end(); j++) + { + if (j->_enq_flag == enq_flag) + c++; + } + } + return c; +} + +int16_t +txn_map::is_txn_synced(const std::string& xid) +{ + slock s(_mutex); + xmap_itr itr = _map.find(xid); + if (itr == _map.end()) // not found in map + return TMAP_XID_NOT_FOUND; + bool is_synced = true; + for (tdl_itr litr = itr->second.begin(); litr < itr->second.end(); litr++) + { + if (!litr->_aio_compl) + { + is_synced = false; + break; + } + } + return is_synced ? TMAP_SYNCED : TMAP_NOT_SYNCED; +} + +int16_t +txn_map::set_aio_compl(const std::string& xid, const u_int64_t rid) +{ + slock s(_mutex); + xmap_itr itr = _map.find(xid); + if (itr == _map.end()) // xid not found in map + return TMAP_XID_NOT_FOUND; + for (tdl_itr litr = itr->second.begin(); litr < itr->second.end(); litr++) + { + if (litr->_rid == rid) + { + litr->_aio_compl = true; + return TMAP_OK; // rid found + } + } + // xid present, but rid not found + return TMAP_RID_NOT_FOUND; +} + +bool +txn_map::data_exists(const std::string& xid, const u_int64_t rid) +{ + bool found = false; + { + slock s(_mutex); + txn_data_list tdl = get_tdata_list_nolock(xid); + tdl_itr itr = tdl.begin(); + while (itr != tdl.end() && !found) + { + found = itr->_rid == rid; + itr++; + } + } + return found; +} + +bool +txn_map::is_enq(const u_int64_t rid) +{ + bool found = false; + { + slock s(_mutex); + for (xmap_itr i = _map.begin(); i != _map.end() && !found; i++) + { + txn_data_list list = i->second; + for (tdl_itr j = list.begin(); j < list.end() && !found; j++) + { + if (j->_enq_flag) + found = j->_rid == rid; + else + found = j->_drid == rid; + } + } + } + return found; +} + +void +txn_map::xid_list(std::vector<std::string>& xv) +{ + xv.clear(); + { + slock s(_mutex); + for (xmap_itr itr = _map.begin(); itr != _map.end(); itr++) + xv.push_back(itr->first); + } +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/txn_map.h b/cpp/src/qpid/legacystore/jrnl/txn_map.h new file mode 100644 index 0000000000..6b38564e53 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/txn_map.h @@ -0,0 +1,159 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file txn_map.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::txn_map (transaction map). + * See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_TXN_MAP_H +#define QPID_LEGACYSTORE_JRNL_TXN_MAP_H + +namespace mrg +{ +namespace journal +{ + class txn_map; +} +} + +#include "qpid/legacystore/jrnl/smutex.h" +#include <map> +#include <pthread.h> +#include <string> +#include <sys/types.h> +#include <vector> + +namespace mrg +{ +namespace journal +{ + + /** + * \struct txn_data_struct + * \brief Struct encapsulating transaction data necessary for processing a transaction + * in the journal once it is closed with either a commit or abort. + */ + struct txn_data_struct + { + u_int64_t _rid; ///< Record id for this operation + u_int64_t _drid; ///< Dequeue record id for this operation + u_int16_t _pfid; ///< Physical file id, to be used when transferring to emap on commit + bool _enq_flag; ///< If true, enq op, otherwise deq op + bool _commit_flag; ///< (2PC transactions) Records 2PC complete c/a mode + bool _aio_compl; ///< Initially false, set to true when record AIO returns + txn_data_struct(const u_int64_t rid, const u_int64_t drid, const u_int16_t pfid, + const bool enq_flag, const bool commit_flag = false); + }; + typedef txn_data_struct txn_data; + typedef std::vector<txn_data> txn_data_list; + typedef txn_data_list::iterator tdl_itr; + + /** + * \class txn_map + * \brief Class for storing transaction data for each open (ie not committed or aborted) + * xid in the store. If aborted, records are discarded; if committed, they are + * transferred to the enqueue map. + * + * The data is encapsulated by struct txn_data_struct. A vector containing the information + * for each operation included as part of the same transaction is mapped against the + * xid. + * + * The aio_compl flag is set true as each AIO write operation for the enqueue or dequeue + * returns. Checking that all of these flags are true for a given xid is the mechanism + * used to determine if the transaction is syncronized (through method is_txn_synced()). + * + * On transaction commit, then each operation is handled as follows: + * + * If an enqueue (_enq_flag is true), then the rid and pfid are transferred to the enq_map. + * If a dequeue (_enq_flag is false), then the rid stored in the drid field is used to + * remove the corresponding record from the enq_map. + * + * On transaction abort, then each operation is handled as follows: + * + * If an enqueue (_enq_flag is true), then the data is simply discarded. + * If a dequeue (_enq_flag is false), then the lock for the corresponding enqueue in enq_map + * (if not a part of the same transaction) is removed, and the data discarded. + * + * <pre> + * key data + * + * xid1 --- vector< [ rid, drid, pfid, enq_flag, commit_flag, aio_compl ] > + * xid2 --- vector< [ rid, drid, pfid, enq_flag, commit_flag, aio_compl ] > + * xid3 --- vector< [ rid, drid, pfid, enq_flag, commit_flag, aio_compl ] > + * ... + * </pre> + */ + class txn_map + { + public: + // return/error codes + static int16_t TMAP_RID_NOT_FOUND; + static int16_t TMAP_XID_NOT_FOUND; + static int16_t TMAP_OK; + static int16_t TMAP_NOT_SYNCED; + static int16_t TMAP_SYNCED; + + private: + typedef std::pair<std::string, txn_data_list> xmap_param; + typedef std::map<std::string, txn_data_list> xmap; + typedef xmap::iterator xmap_itr; + + xmap _map; + smutex _mutex; + std::vector<u_int32_t> _pfid_txn_cnt; + const txn_data_list _empty_data_list; + + public: + txn_map(); + virtual ~txn_map(); + + void set_num_jfiles(const u_int16_t num_jfiles); + u_int32_t get_txn_pfid_cnt(const u_int16_t pfid) const; + bool insert_txn_data(const std::string& xid, const txn_data& td); + const txn_data_list get_tdata_list(const std::string& xid); + const txn_data_list get_remove_tdata_list(const std::string& xid); + bool in_map(const std::string& xid); + u_int32_t enq_cnt(); + u_int32_t deq_cnt(); + int16_t is_txn_synced(const std::string& xid); // -1=xid not found; 0=not synced; 1=synced + int16_t set_aio_compl(const std::string& xid, const u_int64_t rid); // -2=rid not found; -1=xid not found; 0=done + bool data_exists(const std::string& xid, const u_int64_t rid); + bool is_enq(const u_int64_t rid); + inline void clear() { _map.clear(); } + inline bool empty() const { return _map.empty(); } + inline size_t size() const { return _map.size(); } + void xid_list(std::vector<std::string>& xv); + private: + u_int32_t cnt(const bool enq_flag); + const txn_data_list get_tdata_list_nolock(const std::string& xid); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_TXN_MAP_H diff --git a/cpp/src/qpid/legacystore/jrnl/txn_rec.cpp b/cpp/src/qpid/legacystore/jrnl/txn_rec.cpp new file mode 100644 index 0000000000..918a6ce902 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/txn_rec.cpp @@ -0,0 +1,447 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file txn_rec.cpp + * + * Qpid asynchronous store plugin library + * + * This file contains the code for the mrg::journal::txn_rec (journal dequeue + * record) class. See comments in file txn_rec.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/txn_rec.h" + +#include <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <iomanip> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +txn_rec::txn_rec(): + _txn_hdr(), + _xidp(0), + _buff(0), + _txn_tail() +{ + _txn_hdr._version = RHM_JDAT_VERSION; +} + +txn_rec::txn_rec(const u_int32_t magic, const u_int64_t rid, const void* const xidp, + const std::size_t xidlen, const bool owi): + _txn_hdr(magic, RHM_JDAT_VERSION, rid, xidlen, owi), + _xidp(xidp), + _buff(0), + _txn_tail(_txn_hdr) +{} + +txn_rec::~txn_rec() +{ + clean(); +} + +void +txn_rec::reset(const u_int32_t magic) +{ + _txn_hdr._magic = magic; + _txn_hdr._rid = 0; + _txn_hdr._xidsize = 0; + _xidp = 0; + _buff = 0; + _txn_tail._xmagic = ~magic; + _txn_tail._rid = 0; +} + +void +txn_rec::reset(const u_int32_t magic, const u_int64_t rid, const void* const xidp, + const std::size_t xidlen, const bool owi) +{ + _txn_hdr._magic = magic; + _txn_hdr._rid = rid; + _txn_hdr.set_owi(owi); + _txn_hdr._xidsize = xidlen; + _xidp = xidp; + _buff = 0; + _txn_tail._xmagic = ~magic; + _txn_tail._rid = rid; +} + +u_int32_t +txn_rec::encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks) +{ + assert(wptr != 0); + assert(max_size_dblks > 0); + assert(_xidp != 0 && _txn_hdr._xidsize > 0); + + std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE; + std::size_t rem = max_size_dblks * JRNL_DBLK_SIZE; + std::size_t wr_cnt = 0; + if (rec_offs_dblks) // Continuation of split dequeue record (over 2 or more pages) + { + if (size_dblks(rec_size()) - rec_offs_dblks > max_size_dblks) // Further split required + { + rec_offs -= sizeof(_txn_hdr); + std::size_t wsize = _txn_hdr._xidsize > rec_offs ? _txn_hdr._xidsize - rec_offs : 0; + std::size_t wsize2 = wsize; + if (wsize) + { + if (wsize > rem) + wsize = rem; + std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize); + wr_cnt += wsize; + rem -= wsize; + } + rec_offs -= _txn_hdr._xidsize - wsize2; + if (rem) + { + wsize = sizeof(_txn_tail) > rec_offs ? sizeof(_txn_tail) - rec_offs : 0; + wsize2 = wsize; + if (wsize) + { + if (wsize > rem) + wsize = rem; + std::memcpy((char*)wptr + wr_cnt, (char*)&_txn_tail + rec_offs, wsize); + wr_cnt += wsize; + rem -= wsize; + } + rec_offs -= sizeof(_txn_tail) - wsize2; + } + assert(rem == 0); + assert(rec_offs == 0); + } + else // No further split required + { + rec_offs -= sizeof(_txn_hdr); + std::size_t wsize = _txn_hdr._xidsize > rec_offs ? _txn_hdr._xidsize - rec_offs : 0; + if (wsize) + { + std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize); + wr_cnt += wsize; + } + rec_offs -= _txn_hdr._xidsize - wsize; + wsize = sizeof(_txn_tail) > rec_offs ? sizeof(_txn_tail) - rec_offs : 0; + if (wsize) + { + std::memcpy((char*)wptr + wr_cnt, (char*)&_txn_tail + rec_offs, wsize); + wr_cnt += wsize; +#ifdef RHM_CLEAN + std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE; + std::size_t dblk_rec_size = size_dblks(rec_size() - rec_offs) * JRNL_DBLK_SIZE; + std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt); +#endif + } + rec_offs -= sizeof(_txn_tail) - wsize; + assert(rec_offs == 0); + } + } + else // Start at beginning of data record + { + // Assumption: the header will always fit into the first dblk + std::memcpy(wptr, (void*)&_txn_hdr, sizeof(_txn_hdr)); + wr_cnt = sizeof(_txn_hdr); + if (size_dblks(rec_size()) > max_size_dblks) // Split required + { + std::size_t wsize; + rem -= sizeof(_txn_hdr); + if (rem) + { + wsize = rem >= _txn_hdr._xidsize ? _txn_hdr._xidsize : rem; + std::memcpy((char*)wptr + wr_cnt, _xidp, wsize); + wr_cnt += wsize; + rem -= wsize; + } + if (rem) + { + wsize = rem >= sizeof(_txn_tail) ? sizeof(_txn_tail) : rem; + std::memcpy((char*)wptr + wr_cnt, (void*)&_txn_tail, wsize); + wr_cnt += wsize; + rem -= wsize; + } + assert(rem == 0); + } + else // No split required + { + std::memcpy((char*)wptr + wr_cnt, _xidp, _txn_hdr._xidsize); + wr_cnt += _txn_hdr._xidsize; + std::memcpy((char*)wptr + wr_cnt, (void*)&_txn_tail, sizeof(_txn_tail)); + wr_cnt += sizeof(_txn_tail); +#ifdef RHM_CLEAN + std::size_t dblk_rec_size = size_dblks(rec_size()) * JRNL_DBLK_SIZE; + std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt); +#endif + } + } + return size_dblks(wr_cnt); +} + +u_int32_t +txn_rec::decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks) +{ + assert(rptr != 0); + assert(max_size_dblks > 0); + + std::size_t rd_cnt = 0; + if (rec_offs_dblks) // Continuation of record on new page + { + const u_int32_t hdr_xid_dblks = size_dblks(txn_hdr::size() + _txn_hdr._xidsize); + const u_int32_t hdr_xid_tail_dblks = size_dblks(txn_hdr::size() + _txn_hdr._xidsize + + rec_tail::size()); + const std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE; + + if (hdr_xid_tail_dblks - rec_offs_dblks <= max_size_dblks) + { + // Remainder of xid fits within this page + if (rec_offs - txn_hdr::size() < _txn_hdr._xidsize) + { + // Part of xid still outstanding, copy remainder of xid and tail + const std::size_t xid_offs = rec_offs - txn_hdr::size(); + const std::size_t xid_rem = _txn_hdr._xidsize - xid_offs; + std::memcpy((char*)_buff + xid_offs, rptr, xid_rem); + rd_cnt = xid_rem; + std::memcpy((void*)&_txn_tail, ((char*)rptr + rd_cnt), sizeof(_txn_tail)); + chk_tail(); + rd_cnt += sizeof(_txn_tail); + } + else + { + // Tail or part of tail only outstanding, complete tail + const std::size_t tail_offs = rec_offs - txn_hdr::size() - _txn_hdr._xidsize; + const std::size_t tail_rem = rec_tail::size() - tail_offs; + std::memcpy((char*)&_txn_tail + tail_offs, rptr, tail_rem); + chk_tail(); + rd_cnt = tail_rem; + } + } + else if (hdr_xid_dblks - rec_offs_dblks <= max_size_dblks) + { + // Remainder of xid fits within this page, tail split + const std::size_t xid_offs = rec_offs - txn_hdr::size(); + const std::size_t xid_rem = _txn_hdr._xidsize - xid_offs; + std::memcpy((char*)_buff + xid_offs, rptr, xid_rem); + rd_cnt += xid_rem; + const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + if (tail_rem) + { + std::memcpy((void*)&_txn_tail, ((char*)rptr + xid_rem), tail_rem); + rd_cnt += tail_rem; + } + } + else + { + // Remainder of xid split + const std::size_t xid_cp_size = (max_size_dblks * JRNL_DBLK_SIZE); + std::memcpy((char*)_buff + rec_offs - txn_hdr::size(), rptr, xid_cp_size); + rd_cnt += xid_cp_size; + } + } + else // Start of record + { + // Get and check header + _txn_hdr.hdr_copy(h); + rd_cnt = sizeof(rec_hdr); +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + rd_cnt += sizeof(u_int32_t); // Filler 0 +#endif + _txn_hdr._xidsize = *(std::size_t*)((char*)rptr + rd_cnt); + rd_cnt = _txn_hdr.size(); + chk_hdr(); + _buff = std::malloc(_txn_hdr._xidsize); + MALLOC_CHK(_buff, "_buff", "txn_rec", "decode"); + const u_int32_t hdr_xid_dblks = size_dblks(txn_hdr::size() + _txn_hdr._xidsize); + const u_int32_t hdr_xid_tail_dblks = size_dblks(txn_hdr::size() + _txn_hdr._xidsize + + rec_tail::size()); + + // Check if record (header + xid + tail) fits within this page, we can check the + // tail before the expense of copying data to memory + if (hdr_xid_tail_dblks <= max_size_dblks) + { + // Entire header, xid and tail fits within this page + std::memcpy(_buff, (char*)rptr + rd_cnt, _txn_hdr._xidsize); + rd_cnt += _txn_hdr._xidsize; + std::memcpy((void*)&_txn_tail, (char*)rptr + rd_cnt, sizeof(_txn_tail)); + rd_cnt += sizeof(_txn_tail); + chk_tail(); + } + else if (hdr_xid_dblks <= max_size_dblks) + { + // Entire header and xid fit within this page, tail split + std::memcpy(_buff, (char*)rptr + rd_cnt, _txn_hdr._xidsize); + rd_cnt += _txn_hdr._xidsize; + const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + if (tail_rem) + { + std::memcpy((void*)&_txn_tail, (char*)rptr + rd_cnt, tail_rem); + rd_cnt += tail_rem; + } + } + else + { + // Header fits within this page, xid split + const std::size_t xid_cp_size = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt; + std::memcpy(_buff, (char*)rptr + rd_cnt, xid_cp_size); + rd_cnt += xid_cp_size; + } + } + return size_dblks(rd_cnt); +} + +bool +txn_rec::rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs) +{ + if (rec_offs == 0) + { + // Read header, allocate for xid + _txn_hdr.hdr_copy(h); +#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT) + ifsp->ignore(sizeof(u_int32_t)); // _filler0 +#endif + ifsp->read((char*)&_txn_hdr._xidsize, sizeof(std::size_t)); +#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT) + ifsp->ignore(sizeof(u_int32_t)); // _filler0 +#endif + rec_offs = sizeof(_txn_hdr); + _buff = std::malloc(_txn_hdr._xidsize); + MALLOC_CHK(_buff, "_buff", "txn_rec", "rcv_decode"); + } + if (rec_offs < sizeof(_txn_hdr) + _txn_hdr._xidsize) + { + // Read xid (or continue reading xid) + std::size_t offs = rec_offs - sizeof(_txn_hdr); + ifsp->read((char*)_buff + offs, _txn_hdr._xidsize - offs); + std::size_t size_read = ifsp->gcount(); + rec_offs += size_read; + if (size_read < _txn_hdr._xidsize - offs) + { + assert(ifsp->eof()); + // As we may have read past eof, turn off fail bit + ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit)); + assert(!ifsp->fail() && !ifsp->bad()); + return false; + } + } + if (rec_offs < sizeof(_txn_hdr) + _txn_hdr._xidsize + sizeof(rec_tail)) + { + // Read tail (or continue reading tail) + std::size_t offs = rec_offs - sizeof(_txn_hdr) - _txn_hdr._xidsize; + ifsp->read((char*)&_txn_tail + offs, sizeof(rec_tail) - offs); + std::size_t size_read = ifsp->gcount(); + rec_offs += size_read; + if (size_read < sizeof(rec_tail) - offs) + { + assert(ifsp->eof()); + // As we may have read past eof, turn off fail bit + ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit)); + assert(!ifsp->fail() && !ifsp->bad()); + return false; + } + } + ifsp->ignore(rec_size_dblks() * JRNL_DBLK_SIZE - rec_size()); + chk_tail(); // Throws if tail invalid or record incomplete + assert(!ifsp->fail() && !ifsp->bad()); + return true; +} + +std::size_t +txn_rec::get_xid(void** const xidpp) +{ + if (!_buff) + { + *xidpp = 0; + return 0; + } + *xidpp = _buff; + return _txn_hdr._xidsize; +} + +std::string& +txn_rec::str(std::string& str) const +{ + std::ostringstream oss; + if (_txn_hdr._magic == RHM_JDAT_TXA_MAGIC) + oss << "dtxa_rec: m=" << _txn_hdr._magic; + else + oss << "dtxc_rec: m=" << _txn_hdr._magic; + oss << " v=" << (int)_txn_hdr._version; + oss << " rid=" << _txn_hdr._rid; + oss << " xid=\"" << _xidp << "\""; + str.append(oss.str()); + return str; +} + +std::size_t +txn_rec::xid_size() const +{ + return _txn_hdr._xidsize; +} + +std::size_t +txn_rec::rec_size() const +{ + return txn_hdr::size() + _txn_hdr._xidsize + rec_tail::size(); +} + +void +txn_rec::chk_hdr() const +{ + jrec::chk_hdr(_txn_hdr); + if (_txn_hdr._magic != RHM_JDAT_TXA_MAGIC && _txn_hdr._magic != RHM_JDAT_TXC_MAGIC) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + oss << "dtx magic: rid=0x" << std::setw(16) << _txn_hdr._rid; + oss << ": expected=(0x" << std::setw(8) << RHM_JDAT_TXA_MAGIC; + oss << " or 0x" << RHM_JDAT_TXC_MAGIC; + oss << ") read=0x" << std::setw(2) << (int)_txn_hdr._magic; + throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "txn_rec", "chk_hdr"); + } +} + +void +txn_rec::chk_hdr(u_int64_t rid) const +{ + chk_hdr(); + jrec::chk_rid(_txn_hdr, rid); +} + +void +txn_rec::chk_tail() const +{ + jrec::chk_tail(_txn_tail, _txn_hdr); +} + +void +txn_rec::clean() +{ + // clean up allocated memory here +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/txn_rec.h b/cpp/src/qpid/legacystore/jrnl/txn_rec.h new file mode 100644 index 0000000000..1a49df1c96 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/txn_rec.h @@ -0,0 +1,101 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file txn_rec.h + * + * Qpid asynchronous store plugin library + * + * This file contains the code for the mrg::journal::txn_rec (journal data + * record) class. See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_TXN_REC_H +#define QPID_LEGACYSTORE_JRNL_TXN_REC_H + +namespace mrg +{ +namespace journal +{ +class txn_rec; +} +} + +#include <cstddef> +#include "qpid/legacystore/jrnl/jrec.h" +#include "qpid/legacystore/jrnl/txn_hdr.h" + +namespace mrg +{ +namespace journal +{ + + /** + * \class txn_rec + * \brief Class to handle a single journal DTX commit or abort record. + */ + class txn_rec : public jrec + { + private: + txn_hdr _txn_hdr; ///< transaction header + const void* _xidp; ///< xid pointer for encoding (writing to disk) + void* _buff; ///< Pointer to buffer to receive data read from disk + rec_tail _txn_tail; ///< Record tail + + public: + // constructor used for read operations and xid must have memory allocated + txn_rec(); + // constructor used for write operations, where xid already exists + txn_rec(const u_int32_t magic, const u_int64_t rid, const void* const xidp, + const std::size_t xidlen, const bool owi); + virtual ~txn_rec(); + + // Prepare instance for use in reading data from journal + void reset(const u_int32_t magic); + // Prepare instance for use in writing data to journal + void reset(const u_int32_t magic, const u_int64_t rid, const void* const xidp, + const std::size_t xidlen, const bool owi); + u_int32_t encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks); + u_int32_t decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, + u_int32_t max_size_dblks); + // Decode used for recover + bool rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs); + + std::size_t get_xid(void** const xidpp); + std::string& str(std::string& str) const; + inline std::size_t data_size() const { return 0; } // This record never carries data + std::size_t xid_size() const; + std::size_t rec_size() const; + inline u_int64_t rid() const { return _txn_hdr._rid; } + + private: + void chk_hdr() const; + void chk_hdr(u_int64_t rid) const; + void chk_tail() const; + virtual void clean(); + }; // class txn_rec + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_TXN_REC_H diff --git a/cpp/src/qpid/legacystore/jrnl/wmgr.cpp b/cpp/src/qpid/legacystore/jrnl/wmgr.cpp new file mode 100644 index 0000000000..4353fcfbca --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/wmgr.cpp @@ -0,0 +1,1051 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file wmgr.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::wmgr (write manager). See + * comments in file wmgr.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/wmgr.h" + +#include <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include "qpid/legacystore/jrnl/file_hdr.h" +#include "qpid/legacystore/jrnl/jcntl.h" +#include "qpid/legacystore/jrnl/jerrno.h" +#include <sstream> + +namespace mrg +{ +namespace journal +{ + +wmgr::wmgr(jcntl* jc, enq_map& emap, txn_map& tmap, wrfc& wrfc): + pmgr(jc, emap, tmap), + _wrfc(wrfc), + _max_dtokpp(0), + _max_io_wait_us(0), + _fhdr_base_ptr(0), + _fhdr_ptr_arr(0), + _fhdr_aio_cb_arr(0), + _cached_offset_dblks(0), + _jfsize_dblks(0), + _jfsize_pgs(0), + _num_jfiles(0), + _enq_busy(false), + _deq_busy(false), + _abort_busy(false), + _commit_busy(false), + _txn_pending_set() +{} + +wmgr::wmgr(jcntl* jc, enq_map& emap, txn_map& tmap, wrfc& wrfc, + const u_int32_t max_dtokpp, const u_int32_t max_iowait_us): + pmgr(jc, emap, tmap /* , dtoklp */), + _wrfc(wrfc), + _max_dtokpp(max_dtokpp), + _max_io_wait_us(max_iowait_us), + _fhdr_base_ptr(0), + _fhdr_ptr_arr(0), + _fhdr_aio_cb_arr(0), + _cached_offset_dblks(0), + _jfsize_dblks(0), + _jfsize_pgs(0), + _num_jfiles(0), + _enq_busy(false), + _deq_busy(false), + _abort_busy(false), + _commit_busy(false), + _txn_pending_set() +{} + +wmgr::~wmgr() +{ + wmgr::clean(); +} + +void +wmgr::initialize(aio_callback* const cbp, const u_int32_t wcache_pgsize_sblks, + const u_int16_t wcache_num_pages, const u_int32_t max_dtokpp, const u_int32_t max_iowait_us, + std::size_t eo) +{ + _enq_busy = false; + _deq_busy = false; + _abort_busy = false; + _commit_busy = false; + _max_dtokpp = max_dtokpp; + _max_io_wait_us = max_iowait_us; + + initialize(cbp, wcache_pgsize_sblks, wcache_num_pages); + + _jfsize_dblks = _jc->jfsize_sblks() * JRNL_SBLK_SIZE; + _jfsize_pgs = _jc->jfsize_sblks() / _cache_pgsize_sblks; + assert(_jc->jfsize_sblks() % JRNL_RMGR_PAGE_SIZE == 0); + + if (eo) + { + const u_int32_t wr_pg_size_dblks = _cache_pgsize_sblks * JRNL_SBLK_SIZE; + u_int32_t data_dblks = (eo / JRNL_DBLK_SIZE) - 4; // 4 dblks for file hdr + _pg_cntr = data_dblks / wr_pg_size_dblks; + _pg_offset_dblks = data_dblks - (_pg_cntr * wr_pg_size_dblks); + } +} + +iores +wmgr::enqueue(const void* const data_buff, const std::size_t tot_data_len, + const std::size_t this_data_len, data_tok* dtokp, const void* const xid_ptr, + const std::size_t xid_len, const bool transient, const bool external) +{ + if (xid_len) + assert(xid_ptr != 0); + + if (_deq_busy || _abort_busy || _commit_busy) + return RHM_IORES_BUSY; + + if (this_data_len != tot_data_len && !external) + return RHM_IORES_NOTIMPL; + + iores res = pre_write_check(WMGR_ENQUEUE, dtokp, xid_len, tot_data_len, external); + if (res != RHM_IORES_SUCCESS) + return res; + + bool cont = false; + if (_enq_busy) // If enqueue() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT + { + if (dtokp->wstate() == data_tok::ENQ_PART) + cont = true; + else + { + std::ostringstream oss; + oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str(); + throw jexception(jerrno::JERR_WMGR_ENQDISCONT, oss.str(), "wmgr", "enqueue"); + } + } + + u_int64_t rid = (dtokp->external_rid() | cont) ? dtokp->rid() : _wrfc.get_incr_rid(); + _enq_rec.reset(rid, data_buff, tot_data_len, xid_ptr, xid_len, _wrfc.owi(), transient, + external); + if (!cont) + { + dtokp->set_rid(rid); + dtokp->set_dequeue_rid(0); + if (xid_len) + dtokp->set_xid(xid_ptr, xid_len); + else + dtokp->clear_xid(); + _enq_busy = true; + } + bool done = false; + while (!done) + { + assert(_pg_offset_dblks < _cache_pgsize_sblks * JRNL_SBLK_SIZE); + void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE); + u_int32_t data_offs_dblks = dtokp->dblocks_written(); + u_int32_t ret = _enq_rec.encode(wptr, data_offs_dblks, + (_cache_pgsize_sblks * JRNL_SBLK_SIZE) - _pg_offset_dblks); + + // Remember fid which contains the record header in case record is split over several files + if (data_offs_dblks == 0) + dtokp->set_fid(_wrfc.index()); + _pg_offset_dblks += ret; + _cached_offset_dblks += ret; + dtokp->incr_dblocks_written(ret); + dtokp->incr_pg_cnt(); + _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp); + + // Is the encoding of this record complete? + if (dtokp->dblocks_written() >= _enq_rec.rec_size_dblks()) + { + // TODO: Incorrect - must set state to ENQ_CACHED; ENQ_SUBM is set when AIO returns. + dtokp->set_wstate(data_tok::ENQ_SUBM); + dtokp->set_dsize(tot_data_len); + // Only add this data token to page token list when submit is complete, this way + // long multi-page messages have their token on the page containing the END of the + // message. AIO callbacks will then only process this token when entire message is + // enqueued. + _wrfc.incr_enqcnt(dtokp->fid()); + + if (xid_len) // If part of transaction, add to transaction map + { + std::string xid((const char*)xid_ptr, xid_len); + _tmap.insert_txn_data(xid, txn_data(rid, 0, dtokp->fid(), true)); + } + else + { + if (_emap.insert_pfid(rid, dtokp->fid()) < enq_map::EMAP_OK) // fail + { + // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID. + std::ostringstream oss; + oss << std::hex << "rid=0x" << rid << " _pfid=0x" << dtokp->fid(); + throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "enqueue"); + } + } + + done = true; + } + else + dtokp->set_wstate(data_tok::ENQ_PART); + + file_header_check(rid, cont, _enq_rec.rec_size_dblks() - data_offs_dblks); + flush_check(res, cont, done); + } + if (dtokp->wstate() >= data_tok::ENQ_SUBM) + _enq_busy = false; + return res; +} + +iores +wmgr::dequeue(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len, const bool txn_coml_commit) +{ + if (xid_len) + assert(xid_ptr != 0); + + if (_enq_busy || _abort_busy || _commit_busy) + return RHM_IORES_BUSY; + + iores res = pre_write_check(WMGR_DEQUEUE, dtokp); + if (res != RHM_IORES_SUCCESS) + return res; + + bool cont = false; + if (_deq_busy) // If dequeue() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT + { + if (dtokp->wstate() == data_tok::DEQ_PART) + cont = true; + else + { + std::ostringstream oss; + oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str(); + throw jexception(jerrno::JERR_WMGR_DEQDISCONT, oss.str(), "wmgr", "dequeue"); + } + } + + const bool ext_rid = dtokp->external_rid(); + u_int64_t rid = (ext_rid | cont) ? dtokp->rid() : _wrfc.get_incr_rid(); + u_int64_t dequeue_rid = (ext_rid | cont) ? dtokp->dequeue_rid() : dtokp->rid(); + _deq_rec.reset(rid, dequeue_rid, xid_ptr, xid_len, _wrfc.owi(), txn_coml_commit); + if (!cont) + { + if (!ext_rid) + { + dtokp->set_rid(rid); + dtokp->set_dequeue_rid(dequeue_rid); + } + if (xid_len) + dtokp->set_xid(xid_ptr, xid_len); + else + dtokp->clear_xid(); + dequeue_check(dtokp->xid(), dequeue_rid); + dtokp->set_dblocks_written(0); // Reset dblks_written from previous op + _deq_busy = true; + } + bool done = false; + while (!done) + { + assert(_pg_offset_dblks < _cache_pgsize_sblks * JRNL_SBLK_SIZE); + void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE); + u_int32_t data_offs_dblks = dtokp->dblocks_written(); + u_int32_t ret = _deq_rec.encode(wptr, data_offs_dblks, + (_cache_pgsize_sblks * JRNL_SBLK_SIZE) - _pg_offset_dblks); + + // Remember fid which contains the record header in case record is split over several files + if (data_offs_dblks == 0) + dtokp->set_fid(_wrfc.index()); + _pg_offset_dblks += ret; + _cached_offset_dblks += ret; + dtokp->incr_dblocks_written(ret); + dtokp->incr_pg_cnt(); + _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp); + + // Is the encoding of this record complete? + if (dtokp->dblocks_written() >= _deq_rec.rec_size_dblks()) + { + // TODO: Incorrect - must set state to ENQ_CACHED; ENQ_SUBM is set when AIO returns. + dtokp->set_wstate(data_tok::DEQ_SUBM); + + if (xid_len) // If part of transaction, add to transaction map + { + // If the enqueue is part of a pending txn, it will not yet be in emap + _emap.lock(dequeue_rid); // ignore rid not found error + std::string xid((const char*)xid_ptr, xid_len); + _tmap.insert_txn_data(xid, txn_data(rid, dequeue_rid, dtokp->fid(), false)); + } + else + { + int16_t fid = _emap.get_remove_pfid(dtokp->dequeue_rid()); + if (fid < enq_map::EMAP_OK) // fail + { + if (fid == enq_map::EMAP_RID_NOT_FOUND) + { + std::ostringstream oss; + oss << std::hex << "rid=0x" << rid; + throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", "dequeue"); + } + if (fid == enq_map::EMAP_LOCKED) + { + std::ostringstream oss; + oss << std::hex << "rid=0x" << rid; + throw jexception(jerrno::JERR_MAP_LOCKED, oss.str(), "wmgr", "dequeue"); + } + } + _wrfc.decr_enqcnt(fid); + } + + done = true; + } + else + dtokp->set_wstate(data_tok::DEQ_PART); + + file_header_check(rid, cont, _deq_rec.rec_size_dblks() - data_offs_dblks); + flush_check(res, cont, done); + } + if (dtokp->wstate() >= data_tok::DEQ_SUBM) + _deq_busy = false; + return res; +} + +iores +wmgr::abort(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len) +{ + // commit and abort MUST have a valid xid + assert(xid_ptr != 0 && xid_len > 0); + + if (_enq_busy || _deq_busy || _commit_busy) + return RHM_IORES_BUSY; + + iores res = pre_write_check(WMGR_ABORT, dtokp); + if (res != RHM_IORES_SUCCESS) + return res; + + bool cont = false; + if (_abort_busy) // If abort() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT + { + if (dtokp->wstate() == data_tok::ABORT_PART) + cont = true; + else + { + std::ostringstream oss; + oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str(); + throw jexception(jerrno::JERR_WMGR_DEQDISCONT, oss.str(), "wmgr", "abort"); + } + } + + u_int64_t rid = (dtokp->external_rid() | cont) ? dtokp->rid() : _wrfc.get_incr_rid(); + _txn_rec.reset(RHM_JDAT_TXA_MAGIC, rid, xid_ptr, xid_len, _wrfc.owi()); + if (!cont) + { + dtokp->set_rid(rid); + dtokp->set_dequeue_rid(0); + dtokp->set_xid(xid_ptr, xid_len); + dtokp->set_dblocks_written(0); // Reset dblks_written from previous op + _abort_busy = true; + } + bool done = false; + while (!done) + { + assert(_pg_offset_dblks < _cache_pgsize_sblks * JRNL_SBLK_SIZE); + void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE); + u_int32_t data_offs_dblks = dtokp->dblocks_written(); + u_int32_t ret = _txn_rec.encode(wptr, data_offs_dblks, + (_cache_pgsize_sblks * JRNL_SBLK_SIZE) - _pg_offset_dblks); + + // Remember fid which contains the record header in case record is split over several files + if (data_offs_dblks == 0) + dtokp->set_fid(_wrfc.index()); + _pg_offset_dblks += ret; + _cached_offset_dblks += ret; + dtokp->incr_dblocks_written(ret); + dtokp->incr_pg_cnt(); + _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp); + + // Is the encoding of this record complete? + if (dtokp->dblocks_written() >= _txn_rec.rec_size_dblks()) + { + dtokp->set_wstate(data_tok::ABORT_SUBM); + + // Delete this txn from tmap, unlock any locked records in emap + std::string xid((const char*)xid_ptr, xid_len); + txn_data_list tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found + for (tdl_itr itr = tdl.begin(); itr != tdl.end(); itr++) + { + if (!itr->_enq_flag) + _emap.unlock(itr->_drid); // ignore rid not found error + if (itr->_enq_flag) + _wrfc.decr_enqcnt(itr->_pfid); + } + std::pair<std::set<std::string>::iterator, bool> res = _txn_pending_set.insert(xid); + if (!res.second) + { + std::ostringstream oss; + oss << std::hex << "_txn_pending_set: xid=\"" << xid << "\""; + throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "abort"); + } + + done = true; + } + else + dtokp->set_wstate(data_tok::ABORT_PART); + + file_header_check(rid, cont, _txn_rec.rec_size_dblks() - data_offs_dblks); + flush_check(res, cont, done); + } + if (dtokp->wstate() >= data_tok::ABORT_SUBM) + _abort_busy = false; + return res; +} + +iores +wmgr::commit(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len) +{ + // commit and abort MUST have a valid xid + assert(xid_ptr != 0 && xid_len > 0); + + if (_enq_busy || _deq_busy || _abort_busy) + return RHM_IORES_BUSY; + + iores res = pre_write_check(WMGR_COMMIT, dtokp); + if (res != RHM_IORES_SUCCESS) + return res; + + bool cont = false; + if (_commit_busy) // If commit() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT + { + if (dtokp->wstate() == data_tok::COMMIT_PART) + cont = true; + else + { + std::ostringstream oss; + oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str(); + throw jexception(jerrno::JERR_WMGR_DEQDISCONT, oss.str(), "wmgr", "commit"); + } + } + + u_int64_t rid = (dtokp->external_rid() | cont) ? dtokp->rid() : _wrfc.get_incr_rid(); + _txn_rec.reset(RHM_JDAT_TXC_MAGIC, rid, xid_ptr, xid_len, _wrfc.owi()); + if (!cont) + { + dtokp->set_rid(rid); + dtokp->set_dequeue_rid(0); + dtokp->set_xid(xid_ptr, xid_len); + dtokp->set_dblocks_written(0); // Reset dblks_written from previous op + _commit_busy = true; + } + bool done = false; + while (!done) + { + assert(_pg_offset_dblks < _cache_pgsize_sblks * JRNL_SBLK_SIZE); + void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE); + u_int32_t data_offs_dblks = dtokp->dblocks_written(); + u_int32_t ret = _txn_rec.encode(wptr, data_offs_dblks, + (_cache_pgsize_sblks * JRNL_SBLK_SIZE) - _pg_offset_dblks); + + // Remember fid which contains the record header in case record is split over several files + if (data_offs_dblks == 0) + dtokp->set_fid(_wrfc.index()); + _pg_offset_dblks += ret; + _cached_offset_dblks += ret; + dtokp->incr_dblocks_written(ret); + dtokp->incr_pg_cnt(); + _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp); + + // Is the encoding of this record complete? + if (dtokp->dblocks_written() >= _txn_rec.rec_size_dblks()) + { + dtokp->set_wstate(data_tok::COMMIT_SUBM); + + // Delete this txn from tmap, process records into emap + std::string xid((const char*)xid_ptr, xid_len); + txn_data_list tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found + for (tdl_itr itr = tdl.begin(); itr != tdl.end(); itr++) + { + if (itr->_enq_flag) // txn enqueue + { + if (_emap.insert_pfid(itr->_rid, itr->_pfid) < enq_map::EMAP_OK) // fail + { + // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID. + std::ostringstream oss; + oss << std::hex << "rid=0x" << itr->_rid << " _pfid=0x" << itr->_pfid; + throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "commit"); + } + } + else // txn dequeue + { + int16_t fid = _emap.get_remove_pfid(itr->_drid, true); + if (fid < enq_map::EMAP_OK) // fail + { + if (fid == enq_map::EMAP_RID_NOT_FOUND) + { + std::ostringstream oss; + oss << std::hex << "rid=0x" << rid; + throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", "dequeue"); + } + if (fid == enq_map::EMAP_LOCKED) + { + std::ostringstream oss; + oss << std::hex << "rid=0x" << rid; + throw jexception(jerrno::JERR_MAP_LOCKED, oss.str(), "wmgr", "dequeue"); + } + } + _wrfc.decr_enqcnt(fid); + } + } + std::pair<std::set<std::string>::iterator, bool> res = _txn_pending_set.insert(xid); + if (!res.second) + { + std::ostringstream oss; + oss << std::hex << "_txn_pending_set: xid=\"" << xid << "\""; + throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "commit"); + } + + done = true; + } + else + dtokp->set_wstate(data_tok::COMMIT_PART); + + file_header_check(rid, cont, _txn_rec.rec_size_dblks() - data_offs_dblks); + flush_check(res, cont, done); + } + if (dtokp->wstate() >= data_tok::COMMIT_SUBM) + _commit_busy = false; + return res; +} + +void +wmgr::file_header_check(const u_int64_t rid, const bool cont, const u_int32_t rec_dblks_rem) +{ + // Has the file header been written (i.e. write pointers still at 0)? + if (_wrfc.is_void()) + { + bool file_fit = rec_dblks_rem <= _jfsize_dblks; + bool file_full = rec_dblks_rem == _jfsize_dblks; + std::size_t fro = 0; + if (cont) + { + if (file_fit && !file_full) + fro = (rec_dblks_rem + JRNL_SBLK_SIZE) * JRNL_DBLK_SIZE; + } + else + fro = JRNL_SBLK_SIZE * JRNL_DBLK_SIZE; + write_fhdr(rid, _wrfc.index(), _wrfc.index(), fro); + } +} + +void +wmgr::flush_check(iores& res, bool& cont, bool& done) +{ + // Is page is full, flush + if (_pg_offset_dblks >= _cache_pgsize_sblks * JRNL_SBLK_SIZE) + { + res = write_flush(); + assert(res == RHM_IORES_SUCCESS); + + if (_page_cb_arr[_pg_index]._state == AIO_PENDING && !done) + { + res = RHM_IORES_PAGE_AIOWAIT; + done = true; + } + + // If file is full, rotate to next file + if (_pg_cntr >= _jfsize_pgs) + { + iores rfres = rotate_file(); + if (rfres != RHM_IORES_SUCCESS) + res = rfres; + if (!done) + { + if (rfres == RHM_IORES_SUCCESS) + cont = true; + else + done = true; + } + } + } +} + +iores +wmgr::flush() +{ + iores res = write_flush(); + if (_pg_cntr >= _jfsize_pgs) + { + iores rfres = rotate_file(); + if (rfres != RHM_IORES_SUCCESS) + res = rfres; + } + return res; +} + +iores +wmgr::write_flush() +{ + iores res = RHM_IORES_SUCCESS; + // Don't bother flushing an empty page or one that is still in state AIO_PENDING + if (_cached_offset_dblks) + { + if (_page_cb_arr[_pg_index]._state == AIO_PENDING) + res = RHM_IORES_PAGE_AIOWAIT; + else + { + if (_page_cb_arr[_pg_index]._state != IN_USE) + { + std::ostringstream oss; + oss << "pg_index=" << _pg_index << " state=" << _page_cb_arr[_pg_index].state_str(); + throw jexception(jerrno::JERR_WMGR_BADPGSTATE, oss.str(), "wmgr", + "write_flush"); + } + + // Send current page using AIO + + // In manual flushes, dblks may not coincide with sblks, add filler records ("RHMx") + // if necessary. + dblk_roundup(); + + std::size_t pg_offs = (_pg_offset_dblks - _cached_offset_dblks) * JRNL_DBLK_SIZE; + aio_cb* aiocbp = &_aio_cb_arr[_pg_index]; + aio::prep_pwrite_2(aiocbp, _wrfc.fh(), + (char*)_page_ptr_arr[_pg_index] + pg_offs, _cached_offset_dblks * JRNL_DBLK_SIZE, + _wrfc.subm_offs()); + page_cb* pcbp = (page_cb*)(aiocbp->data); // This page control block (pcb) + pcbp->_wdblks = _cached_offset_dblks; + pcbp->_wfh = _wrfc.file_controller(); + if (aio::submit(_ioctx, 1, &aiocbp) < 0) + throw jexception(jerrno::JERR__AIO, "wmgr", "write_flush"); + _wrfc.add_subm_cnt_dblks(_cached_offset_dblks); + _wrfc.incr_aio_cnt(); + _aio_evt_rem++; + _cached_offset_dblks = 0; + _jc->instr_incr_outstanding_aio_cnt(); + + rotate_page(); // increments _pg_index, resets _pg_offset_dblks if req'd + if (_page_cb_arr[_pg_index]._state == UNUSED) + _page_cb_arr[_pg_index]._state = IN_USE; + } + } + get_events(UNUSED, 0); + if (_page_cb_arr[_pg_index]._state == UNUSED) + _page_cb_arr[_pg_index]._state = IN_USE; + return res; +} + +iores +wmgr::rotate_file() +{ + _pg_cntr = 0; + iores res = _wrfc.rotate(); + _jc->chk_wr_frot(); + return res; +} + +int32_t +wmgr::get_events(page_state state, timespec* const timeout, bool flush) +{ + if (_aio_evt_rem == 0) // no events to get + return 0; + + int ret = 0; + if ((ret = aio::getevents(_ioctx, flush ? _aio_evt_rem : 1, _aio_evt_rem/*_cache_num_pages + _jc->num_jfiles()*/, _aio_event_arr, timeout)) < 0) + { + if (ret == -EINTR) // Interrupted by signal + return 0; + std::ostringstream oss; + oss << "io_getevents() failed: " << std::strerror(-ret) << " (" << ret << ")"; + throw jexception(jerrno::JERR__AIO, oss.str(), "wmgr", "get_events"); + } + + if (ret == 0 && timeout) + return jerrno::AIO_TIMEOUT; + + int32_t tot_data_toks = 0; + for (int i=0; i<ret; i++) // Index of returned AIOs + { + if (_aio_evt_rem == 0) + { + std::ostringstream oss; + oss << "_aio_evt_rem; evt " << (i + 1) << " of " << ret; + throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "wmgr", "get_events"); + } + _aio_evt_rem--; + aio_cb* aiocbp = _aio_event_arr[i].obj; // This I/O control block (iocb) + page_cb* pcbp = (page_cb*)(aiocbp->data); // This page control block (pcb) + long aioret = (long)_aio_event_arr[i].res; + if (aioret < 0) + { + std::ostringstream oss; + oss << "AIO write operation failed: " << std::strerror(-aioret) << " (" << aioret << ") ["; + if (pcbp) + oss << "pg=" << pcbp->_index; + else + { + file_hdr* fhp = (file_hdr*)aiocbp->u.c.buf; + oss << "fid=" << fhp->_pfid; + } + oss << " size=" << aiocbp->u.c.nbytes; + oss << " offset=" << aiocbp->u.c.offset << " fh=" << aiocbp->aio_fildes << "]"; + throw jexception(jerrno::JERR__AIO, oss.str(), "wmgr", "get_events"); + } + if (pcbp) // Page writes have pcb + { + u_int32_t s = pcbp->_pdtokl->size(); + std::vector<data_tok*> dtokl; + dtokl.reserve(s); + for (u_int32_t k=0; k<s; k++) + { + data_tok* dtokp = pcbp->_pdtokl->at(k); + if (dtokp->decr_pg_cnt() == 0) + { + std::set<std::string>::iterator it; + switch (dtokp->wstate()) + { + case data_tok::ENQ_SUBM: + dtokl.push_back(dtokp); + tot_data_toks++; + dtokp->set_wstate(data_tok::ENQ); + if (dtokp->has_xid()) + // Ignoring return value here. A non-zero return can signify that the transaction + // has committed or aborted, and which was completed prior to the aio returning. + _tmap.set_aio_compl(dtokp->xid(), dtokp->rid()); + break; + case data_tok::DEQ_SUBM: + dtokl.push_back(dtokp); + tot_data_toks++; + dtokp->set_wstate(data_tok::DEQ); + if (dtokp->has_xid()) + // Ignoring return value - see note above. + _tmap.set_aio_compl(dtokp->xid(), dtokp->rid()); + break; + case data_tok::ABORT_SUBM: + dtokl.push_back(dtokp); + tot_data_toks++; + dtokp->set_wstate(data_tok::ABORTED); + it = _txn_pending_set.find(dtokp->xid()); + if (it == _txn_pending_set.end()) + { + std::ostringstream oss; + oss << std::hex << "_txn_pending_set: abort xid=\""; + oss << dtokp->xid() << "\""; + throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", + "get_events"); + } + _txn_pending_set.erase(it); + break; + case data_tok::COMMIT_SUBM: + dtokl.push_back(dtokp); + tot_data_toks++; + dtokp->set_wstate(data_tok::COMMITTED); + it = _txn_pending_set.find(dtokp->xid()); + if (it == _txn_pending_set.end()) + { + std::ostringstream oss; + oss << std::hex << "_txn_pending_set: commit xid=\""; + oss << dtokp->xid() << "\""; + throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", + "get_events"); + } + _txn_pending_set.erase(it); + break; + case data_tok::ENQ_PART: + case data_tok::DEQ_PART: + case data_tok::ABORT_PART: + case data_tok::COMMIT_PART: + // ignore these + break; + default: + // throw for anything else + std::ostringstream oss; + oss << "dtok_id=" << dtokp->id() << " dtok_state=" << dtokp->wstate_str(); + throw jexception(jerrno::JERR_WMGR_BADDTOKSTATE, oss.str(), "wmgr", + "get_events"); + } // switch + } // if + } // for + + // Increment the completed write offset + // NOTE: We cannot use _wrfc here, as it may have rotated since submitting count. + // Use stored pointer to fcntl in the pcb instead. + pcbp->_wfh->add_wr_cmpl_cnt_dblks(pcbp->_wdblks); + pcbp->_wfh->decr_aio_cnt(); + _jc->instr_decr_outstanding_aio_cnt(); + + // Clean up this pcb's data_tok list + pcbp->_pdtokl->clear(); + pcbp->_state = state; + + // Perform AIO return callback + if (_cbp && tot_data_toks) + _cbp->wr_aio_cb(dtokl); + } + else // File header writes have no pcb + { + // get lfid from original file header record, update info for that lfid + file_hdr* fhp = (file_hdr*)aiocbp->u.c.buf; + u_int32_t lfid = fhp->_lfid; + fcntl* fcntlp = _jc->get_fcntlp(lfid); + fcntlp->add_wr_cmpl_cnt_dblks(JRNL_SBLK_SIZE); + fcntlp->decr_aio_cnt(); + fcntlp->set_wr_fhdr_aio_outstanding(false); + } + } + + return tot_data_toks; +} + +bool +wmgr::is_txn_synced(const std::string& xid) +{ + // Ignore xid not found error here + if (_tmap.is_txn_synced(xid) == txn_map::TMAP_NOT_SYNCED) + return false; + // Check for outstanding commit/aborts + std::set<std::string>::iterator it = _txn_pending_set.find(xid); + return it == _txn_pending_set.end(); +} + +void +wmgr::initialize(aio_callback* const cbp, const u_int32_t wcache_pgsize_sblks, const u_int16_t wcache_num_pages) +{ + pmgr::initialize(cbp, wcache_pgsize_sblks, wcache_num_pages); + wmgr::clean(); + _num_jfiles = _jc->num_jfiles(); + if (::posix_memalign(&_fhdr_base_ptr, _sblksize, _sblksize * _num_jfiles)) + { + wmgr::clean(); + std::ostringstream oss; + oss << "posix_memalign(): blksize=" << _sblksize << " size=" << _sblksize; + oss << FORMAT_SYSERR(errno); + throw jexception(jerrno::JERR__MALLOC, oss.str(), "wmgr", "initialize"); + } + _fhdr_ptr_arr = (void**)std::malloc(_num_jfiles * sizeof(void*)); + MALLOC_CHK(_fhdr_ptr_arr, "_fhdr_ptr_arr", "wmgr", "initialize"); + _fhdr_aio_cb_arr = (aio_cb**)std::malloc(sizeof(aio_cb*) * _num_jfiles); + MALLOC_CHK(_fhdr_aio_cb_arr, "_fhdr_aio_cb_arr", "wmgr", "initialize"); + std::memset(_fhdr_aio_cb_arr, 0, sizeof(aio_cb*) * _num_jfiles); + for (u_int16_t i=0; i<_num_jfiles; i++) + { + _fhdr_ptr_arr[i] = (void*)((char*)_fhdr_base_ptr + _sblksize * i); + _fhdr_aio_cb_arr[i] = new aio_cb; + } + _page_cb_arr[0]._state = IN_USE; + _ddtokl.clear(); + _cached_offset_dblks = 0; + _enq_busy = false; +} + +iores +wmgr::pre_write_check(const _op_type op, const data_tok* const dtokp, + const std::size_t xidsize, const std::size_t dsize, const bool external + ) const +{ + // Check status of current file + if (!_wrfc.is_wr_reset()) + { + if (!_wrfc.wr_reset()) + return RHM_IORES_FULL; + } + + // Check status of current page is ok for writing + if (_page_cb_arr[_pg_index]._state != IN_USE) + { + if (_page_cb_arr[_pg_index]._state == UNUSED) + _page_cb_arr[_pg_index]._state = IN_USE; + else if (_page_cb_arr[_pg_index]._state == AIO_PENDING) + return RHM_IORES_PAGE_AIOWAIT; + else + { + std::ostringstream oss; + oss << "jrnl=" << _jc->id() << " op=" << _op_str[op]; + oss << " index=" << _pg_index << " pg_state=" << _page_cb_arr[_pg_index].state_str(); + throw jexception(jerrno::JERR_WMGR_BADPGSTATE, oss.str(), "wmgr", "pre_write_check"); + } + } + + // operation-specific checks + switch (op) + { + case WMGR_ENQUEUE: + { + // Check for enqueue reaching cutoff threshold + u_int32_t size_dblks = jrec::size_dblks(enq_rec::rec_size(xidsize, dsize, + external)); + if (!_enq_busy && _wrfc.enq_threshold(_cached_offset_dblks + size_dblks)) + return RHM_IORES_ENQCAPTHRESH; + if (!dtokp->is_writable()) + { + std::ostringstream oss; + oss << "jrnl=" << _jc->id() << " op=" << _op_str[op]; + oss << " dtok_id=" << dtokp->id() << " dtok_state=" << dtokp->wstate_str(); + throw jexception(jerrno::JERR_WMGR_BADDTOKSTATE, oss.str(), "wmgr", + "pre_write_check"); + } + } + break; + case WMGR_DEQUEUE: + if (!dtokp->is_dequeueable()) + { + std::ostringstream oss; + oss << "jrnl=" << _jc->id() << " op=" << _op_str[op]; + oss << " dtok_id=" << dtokp->id() << " dtok_state=" << dtokp->wstate_str(); + throw jexception(jerrno::JERR_WMGR_BADDTOKSTATE, oss.str(), "wmgr", + "pre_write_check"); + } + break; + case WMGR_ABORT: + break; + case WMGR_COMMIT: + break; + } + + return RHM_IORES_SUCCESS; +} + +void +wmgr::dequeue_check(const std::string& xid, const u_int64_t drid) +{ + // First check emap + bool found = false; + int16_t fid = _emap.get_pfid(drid); + if (fid < enq_map::EMAP_OK) // fail + { + if (fid == enq_map::EMAP_RID_NOT_FOUND) + { + if (xid.size()) + found = _tmap.data_exists(xid, drid); + } + else if (fid == enq_map::EMAP_LOCKED) + { + std::ostringstream oss; + oss << std::hex << "drid=0x" << drid; + throw jexception(jerrno::JERR_MAP_LOCKED, oss.str(), "wmgr", "dequeue_check"); + } + } + else + found = true; + if (!found) + { + std::ostringstream oss; + oss << "jrnl=" << _jc->id() << " drid=0x" << std::hex << drid; + throw jexception(jerrno::JERR_WMGR_DEQRIDNOTENQ, oss.str(), "wmgr", "dequeue_check"); + } +} + +void +wmgr::dblk_roundup() +{ + const u_int32_t xmagic = RHM_JDAT_EMPTY_MAGIC; + u_int32_t wdblks = jrec::size_blks(_cached_offset_dblks, JRNL_SBLK_SIZE) * JRNL_SBLK_SIZE; + while (_cached_offset_dblks < wdblks) + { + void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE); + std::memcpy(wptr, (const void*)&xmagic, sizeof(xmagic)); +#ifdef RHM_CLEAN + std::memset((char*)wptr + sizeof(xmagic), RHM_CLEAN_CHAR, JRNL_DBLK_SIZE - sizeof(xmagic)); +#endif + _pg_offset_dblks++; + _cached_offset_dblks++; + } +} + +void +wmgr::write_fhdr(u_int64_t rid, u_int16_t fid, u_int16_t lid, std::size_t fro) +{ + file_hdr fhdr(RHM_JDAT_FILE_MAGIC, RHM_JDAT_VERSION, rid, fid, lid, fro, _wrfc.owi(), true); + std::memcpy(_fhdr_ptr_arr[fid], &fhdr, sizeof(fhdr)); +#ifdef RHM_CLEAN + std::memset((char*)_fhdr_ptr_arr[fid] + sizeof(fhdr), RHM_CLEAN_CHAR, _sblksize - sizeof(fhdr)); +#endif + aio_cb* aiocbp = _fhdr_aio_cb_arr[fid]; + aio::prep_pwrite(aiocbp, _wrfc.fh(), _fhdr_ptr_arr[fid], _sblksize, 0); + if (aio::submit(_ioctx, 1, &aiocbp) < 0) + throw jexception(jerrno::JERR__AIO, "wmgr", "write_fhdr"); + _aio_evt_rem++; + _wrfc.add_subm_cnt_dblks(JRNL_SBLK_SIZE); + _wrfc.incr_aio_cnt(); + _wrfc.file_controller()->set_wr_fhdr_aio_outstanding(true); +} + +void +wmgr::rotate_page() +{ + _page_cb_arr[_pg_index]._state = AIO_PENDING; + if (_pg_offset_dblks >= _cache_pgsize_sblks * JRNL_SBLK_SIZE) + { + _pg_offset_dblks = 0; + _pg_cntr++; + } + if (++_pg_index >= _cache_num_pages) + _pg_index = 0; +} + +void +wmgr::clean() +{ + std::free(_fhdr_base_ptr); + _fhdr_base_ptr = 0; + + std::free(_fhdr_ptr_arr); + _fhdr_ptr_arr = 0; + + if (_fhdr_aio_cb_arr) + { + for (u_int32_t i=0; i<_num_jfiles; i++) + delete _fhdr_aio_cb_arr[i]; + std::free(_fhdr_aio_cb_arr); + _fhdr_aio_cb_arr = 0; + } +} + +const std::string +wmgr::status_str() const +{ + std::ostringstream oss; + oss << "wmgr: pi=" << _pg_index << " pc=" << _pg_cntr; + oss << " po=" << _pg_offset_dblks << " aer=" << _aio_evt_rem; + oss << " edac:" << (_enq_busy?"T":"F") << (_deq_busy?"T":"F"); + oss << (_abort_busy?"T":"F") << (_commit_busy?"T":"F"); + oss << " ps=["; + for (int i=0; i<_cache_num_pages; i++) + { + switch (_page_cb_arr[i]._state) + { + case UNUSED: oss << "-"; break; + case IN_USE: oss << "U"; break; + case AIO_PENDING: oss << "A"; break; + case AIO_COMPLETE: oss << "*"; break; + default: oss << _page_cb_arr[i]._state; + } + } + oss << "] " << _wrfc.status_str(); + return oss.str(); +} + +// static + +const char* wmgr::_op_str[] = {"enqueue", "dequeue", "abort", "commit"}; + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/wmgr.h b/cpp/src/qpid/legacystore/jrnl/wmgr.h new file mode 100644 index 0000000000..8347221b1d --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/wmgr.h @@ -0,0 +1,147 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file wmgr.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::wmgr (write manager). See + * class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_WMGR_H +#define QPID_LEGACYSTORE_JRNL_WMGR_H + +namespace mrg +{ +namespace journal +{ +class wmgr; +} +} + +#include <cstring> +#include "qpid/legacystore/jrnl/enums.h" +#include "qpid/legacystore/jrnl/pmgr.h" +#include "qpid/legacystore/jrnl/wrfc.h" +#include <set> + +namespace mrg +{ +namespace journal +{ + + /** + * \brief Class for managing a write page cache of arbitrary size and number of pages. + * + * The write page cache works on the principle of caching the write data within a page until + * that page is either full or flushed; this initiates a single AIO write operation to store + * the data on disk. + * + * The maximum disk throughput is achieved by keeping the write operations of uniform size. + * Waiting for a page cache to fill achieves this; and in high data volume/throughput situations + * achieves the optimal disk throughput. Calling flush() forces a write of the current page cache + * no matter how full it is, and disrupts the uniformity of the write operations. This should + * normally only be done if throughput drops and there is a danger of a page of unwritten data + * waiting around for excessive time. + * + * The usual tradeoff between data storage latency and throughput performance applies. + */ + class wmgr : public pmgr + { + private: + wrfc& _wrfc; ///< Ref to write rotating file controller + u_int32_t _max_dtokpp; ///< Max data writes per page + u_int32_t _max_io_wait_us; ///< Max wait in microseconds till submit + void* _fhdr_base_ptr; ///< Base pointer to file header memory + void** _fhdr_ptr_arr; ///< Array of pointers to file headers memory + aio_cb** _fhdr_aio_cb_arr; ///< Array of iocb pointers for file header writes + u_int32_t _cached_offset_dblks; ///< Amount of unwritten data in page (dblocks) + std::deque<data_tok*> _ddtokl; ///< Deferred dequeue data_tok list + u_int32_t _jfsize_dblks; ///< Journal file size in dblks (NOT sblks!) + u_int32_t _jfsize_pgs; ///< Journal file size in cache pages + u_int16_t _num_jfiles; ///< Number of files used in iocb mallocs + + // TODO: Convert _enq_busy etc into a proper threadsafe lock + // TODO: Convert to enum? Are these encodes mutually exclusive? + bool _enq_busy; ///< Flag true if enqueue is in progress + bool _deq_busy; ///< Flag true if dequeue is in progress + bool _abort_busy; ///< Flag true if abort is in progress + bool _commit_busy; ///< Flag true if commit is in progress + + enum _op_type { WMGR_ENQUEUE = 0, WMGR_DEQUEUE, WMGR_ABORT, WMGR_COMMIT }; + static const char* _op_str[]; + + enq_rec _enq_rec; ///< Enqueue record used for encoding/decoding + deq_rec _deq_rec; ///< Dequeue record used for encoding/decoding + txn_rec _txn_rec; ///< Transaction record used for encoding/decoding + std::set<std::string> _txn_pending_set; ///< Set containing xids of pending commits/aborts + + public: + wmgr(jcntl* jc, enq_map& emap, txn_map& tmap, wrfc& wrfc); + wmgr(jcntl* jc, enq_map& emap, txn_map& tmap, wrfc& wrfc, const u_int32_t max_dtokpp, + const u_int32_t max_iowait_us); + virtual ~wmgr(); + + void initialize(aio_callback* const cbp, const u_int32_t wcache_pgsize_sblks, + const u_int16_t wcache_num_pages, const u_int32_t max_dtokpp, + const u_int32_t max_iowait_us, std::size_t eo = 0); + iores enqueue(const void* const data_buff, const std::size_t tot_data_len, + const std::size_t this_data_len, data_tok* dtokp, const void* const xid_ptr, + const std::size_t xid_len, const bool transient, const bool external); + iores dequeue(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len, + const bool txn_coml_commit); + iores abort(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len); + iores commit(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len); + iores flush(); + int32_t get_events(page_state state, timespec* const timeout, bool flush = false); + bool is_txn_synced(const std::string& xid); + inline bool curr_pg_blocked() const { return _page_cb_arr[_pg_index]._state != UNUSED; } + inline bool curr_file_blocked() const { return _wrfc.aio_cnt() > 0; } + inline u_int32_t unflushed_dblks() { return _cached_offset_dblks; } + + // Debug aid + const std::string status_str() const; + + private: + void initialize(aio_callback* const cbp, const u_int32_t wcache_pgsize_sblks, + const u_int16_t wcache_num_pages); + iores pre_write_check(const _op_type op, const data_tok* const dtokp, + const std::size_t xidsize = 0, const std::size_t dsize = 0, const bool external = false) + const; + void dequeue_check(const std::string& xid, const u_int64_t drid); + void file_header_check(const u_int64_t rid, const bool cont, const u_int32_t rec_dblks_rem); + void flush_check(iores& res, bool& cont, bool& done); + iores write_flush(); + iores rotate_file(); + void dblk_roundup(); + void write_fhdr(u_int64_t rid, u_int16_t fid, u_int16_t lid, std::size_t fro); + void rotate_page(); + void clean(); + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_WMGR_H diff --git a/cpp/src/qpid/legacystore/jrnl/wrfc.cpp b/cpp/src/qpid/legacystore/jrnl/wrfc.cpp new file mode 100644 index 0000000000..43461b66a3 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/wrfc.cpp @@ -0,0 +1,162 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file wrfc.cpp + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::wrfc (rotating + * file controller). See comments in file wrfc.h for details. + * + * \author Kim van der Riet + */ + +#include "qpid/legacystore/jrnl/wrfc.h" + +#include <cmath> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" + +namespace mrg +{ +namespace journal +{ + +wrfc::wrfc(const lpmgr* lpmp): + rfc(lpmp), + _fsize_sblks(0), + _fsize_dblks(0), + _enq_cap_offs_dblks(0), + _rid(0), + _reset_ok(false), + _owi(false), + _frot(true) +{} + +wrfc::~wrfc() +{} + +void +wrfc::initialize(const u_int32_t fsize_sblks, rcvdat* rdp) +{ + if (rdp) + { + _fc_index = rdp->_lfid; + _curr_fc = _lpmp->get_fcntlp(_fc_index); + _curr_fc->wr_reset(rdp); + _rid = rdp->_h_rid + 1; + _reset_ok = true; + _owi = rdp->_owi; + _frot = rdp->_frot; + if (rdp->_lffull) + rotate(); + } + else + { + rfc::initialize(); + rfc::set_findex(0); + _rid = 0ULL; + _reset_ok = false; + } + _fsize_sblks = fsize_sblks; + _fsize_dblks = fsize_sblks * JRNL_SBLK_SIZE; + _enq_cap_offs_dblks = (u_int32_t)std::ceil(_fsize_dblks * _lpmp->num_jfiles() * (100.0 - JRNL_ENQ_THRESHOLD) / 100); + // Check the offset is at least one file; if not, make it so + if (_enq_cap_offs_dblks < _fsize_dblks) + _enq_cap_offs_dblks = _fsize_dblks; +} + +iores wrfc::rotate() +{ + if (!_lpmp->num_jfiles()) + throw jexception(jerrno::JERR__NINIT, "wrfc", "rotate"); + _fc_index++; + if (_fc_index == _lpmp->num_jfiles()) + { + _fc_index = 0; + _owi = !_owi; + _frot = false; + } + _curr_fc = _lpmp->get_fcntlp(_fc_index); + if (_curr_fc->aio_cnt()) + return RHM_IORES_FILE_AIOWAIT; + if (!wr_reset()) //Checks if file is still in use (ie not fully dequeued yet) + return RHM_IORES_FULL; + return RHM_IORES_SUCCESS; +} + +u_int16_t wrfc::earliest_index() const +{ + if (_frot) + return 0; + u_int16_t next_index = _fc_index + 1; + if (next_index >= _lpmp->num_jfiles()) + next_index = 0; + return next_index; +} + +bool +wrfc::enq_threshold(const u_int32_t enq_dsize_dblks) const +{ + u_int32_t subm_dblks = subm_cnt_dblks(); // includes file hdr if > 0 + // This compensates for new files which don't have their file headers written yet, + // as file header space cannot be included in this calculation. + if (subm_dblks != 0) + subm_dblks -= 4; + u_int32_t fwd_dblks = subm_dblks + enq_dsize_dblks + _enq_cap_offs_dblks; + u_int16_t findex = _fc_index; + fcntl* fcp = _curr_fc; + bool in_use = false; + while (fwd_dblks && !(findex != _fc_index && fcp->enqcnt())) + { + fwd_dblks -= fwd_dblks > _fsize_dblks ? _fsize_dblks : fwd_dblks; + if (fwd_dblks) + { + if (++findex == _lpmp->num_jfiles()) + findex = 0; + fcp = _lpmp->get_fcntlp(findex); + } + in_use |= fcp->enqcnt() > 0; + } + // Return true if threshold exceeded + return findex != _fc_index && in_use; +} + +bool wrfc::wr_reset() +{ + _reset_ok = _curr_fc->reset(); // returns false if full (ie file still contains enqueued recs) + return _reset_ok; +} + +// TODO: update this to reflect all status data +std::string +wrfc::status_str() const +{ + std::ostringstream oss; + oss << "wrfc: " << rfc::status_str(); + if (is_active()) + oss << " fcntl[" << _fc_index << "]: " << _curr_fc->status_str(); + return oss.str(); +} + +} // namespace journal +} // namespace mrg diff --git a/cpp/src/qpid/legacystore/jrnl/wrfc.h b/cpp/src/qpid/legacystore/jrnl/wrfc.h new file mode 100644 index 0000000000..f0e4e73151 --- /dev/null +++ b/cpp/src/qpid/legacystore/jrnl/wrfc.h @@ -0,0 +1,154 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** + * \file wrfc.h + * + * Qpid asynchronous store plugin library + * + * File containing code for class mrg::journal::wrfc (write rotating + * file controller). See class documentation for details. + * + * \author Kim van der Riet + */ + +#ifndef QPID_LEGACYSTORE_JRNL_WRFC_H +#define QPID_LEGACYSTORE_JRNL_WRFC_H + +namespace mrg +{ +namespace journal +{ +class wrfc; +} +} + +#include <cstddef> +#include "qpid/legacystore/jrnl/enums.h" +#include "qpid/legacystore/jrnl/rrfc.h" + +namespace mrg +{ +namespace journal +{ + + /** + * \class wrfc + * \brief Class to handle write management of a journal rotating file controller. + */ + class wrfc : public rfc + { + private: + u_int32_t _fsize_sblks; ///< Size of journal files in sblks + u_int32_t _fsize_dblks; ///< Size of journal files in dblks + u_int32_t _enq_cap_offs_dblks; ///< Enqueue capacity offset + u_int64_t _rid; ///< Master counter for record ID (rid) + bool _reset_ok; ///< Flag set when reset succeeds + bool _owi; ///< Overwrite indicator + bool _frot; ///< Flag is true for first rotation, false otherwise + + public: + wrfc(const lpmgr* lpmp); + virtual ~wrfc(); + + /** + * \brief Initialize the controller. + * \param fsize_sblks Size of each journal file in sblks. + * \param rdp Struct carrying restore information. Optional for non-restore use, defaults to 0 (NULL). + */ + using rfc::initialize; + void initialize(const u_int32_t fsize_sblks, rcvdat* rdp = 0); + + /** + * \brief Rotate active file controller to next file in rotating file group. + * \exception jerrno::JERR__NINIT if called before calling initialize(). + */ + iores rotate(); + + /** + * \brief Returns the index of the earliest complete file within the rotating + * file group. Unwritten files are excluded. The currently active file is + * excluded unless it is the only written file. + */ + u_int16_t earliest_index() const; + + /** + * \brief Determines if a proposed write would cause the enqueue threshold to be exceeded. + * + * The following routine finds whether the next write will take the write pointer to beyond the + * enqueue limit threshold. The following illustrates how this is achieved. + * <pre> + * Current file index: 4 +---+----------+ + * X's mark still-enqueued records |msg| 1-thresh | + * msg = current msg size + unwritten cache +---+----------+ + * thresh = JRNL_ENQ_THRESHOLD as a fraction ^ V + * +-------+-------+-------+-------+--+----+-------+-+-----+-------+ + * file num ->| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | + * enq recs ->| X XX |XX XXX |XX XXXX|XXXXXXX|XX | | | X | + * +-------+-------+-------+-------+--+----+-------+-+-----+-------+ + * ^ ^ ^ + * subm_dblks --+ | | + * These files must be free of enqueues + * If not, return true. + * </pre> + * \param enq_dsize_dblks Proposed size of write in dblocks + */ + bool enq_threshold(const u_int32_t enq_dsize_dblks) const; + + inline u_int64_t rid() const { return _rid; } + inline u_int64_t get_incr_rid() { return _rid++; } + bool wr_reset(); + inline bool is_wr_reset() const { return _reset_ok; } + inline bool owi() const { return _owi; } + inline bool frot() const { return _frot; } + + // Convenience access methods to current file controller + + inline int fh() const { return _curr_fc->wr_fh(); } + + inline u_int32_t subm_cnt_dblks() const { return _curr_fc->wr_subm_cnt_dblks(); } + inline std::size_t subm_offs() const { return _curr_fc->wr_subm_offs(); } + inline u_int32_t add_subm_cnt_dblks(u_int32_t a) { return _curr_fc->add_wr_subm_cnt_dblks(a); } + + inline u_int32_t cmpl_cnt_dblks() const { return _curr_fc->wr_cmpl_cnt_dblks(); } + inline std::size_t cmpl_offs() const { return _curr_fc->wr_cmpl_offs(); } + inline u_int32_t add_cmpl_cnt_dblks(u_int32_t a) { return _curr_fc->add_wr_cmpl_cnt_dblks(a); } + + inline u_int16_t aio_cnt() const { return _curr_fc->aio_cnt(); } + inline u_int16_t incr_aio_cnt() { return _curr_fc->incr_aio_cnt(); } + inline u_int16_t decr_aio_cnt() { return _curr_fc->decr_aio_cnt(); } + + inline bool is_void() const { return _curr_fc->wr_void(); } + inline bool is_empty() const { return _curr_fc->wr_empty(); } + inline u_int32_t remaining_dblks() const { return _curr_fc->wr_remaining_dblks(); } + inline bool is_full() const { return _curr_fc->is_wr_full(); }; + inline bool is_compl() const { return _curr_fc->is_wr_compl(); }; + inline u_int32_t aio_outstanding_dblks() const { return _curr_fc->wr_aio_outstanding_dblks(); } + inline bool file_rotate() const { return _curr_fc->wr_file_rotate(); } + + // Debug aid + std::string status_str() const; + }; + +} // namespace journal +} // namespace mrg + +#endif // ifndef QPID_LEGACYSTORE_JRNL_WRFC_H diff --git a/cpp/src/qpid/legacystore/management-schema.xml b/cpp/src/qpid/legacystore/management-schema.xml new file mode 100644 index 0000000000..65969f0fb2 --- /dev/null +++ b/cpp/src/qpid/legacystore/management-schema.xml @@ -0,0 +1,99 @@ +<schema package="org.apache.qpid.legacystore"> + +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + + <class name="Store"> + <property name="brokerRef" type="objId" access="RO" references="qpid.Broker" index="y" parentRef="y"/> + <property name="location" type="sstr" access="RO" desc="Logical directory on disk"/> + <property name="defaultInitialFileCount" type="uint16" access="RO" unit="file" desc="Default number of files initially allocated to each journal"/> + <property name="defaultDataFileSize" type="uint32" access="RO" unit="RdPg" desc="Default size of each journal data file"/> + <property name="tplIsInitialized" type="bool" access="RO" desc="Transaction prepared list has been initialized by a transactional prepare"/> + <property name="tplDirectory" type="sstr" access="RO" desc="Transaction prepared list directory"/> + <property name="tplWritePageSize" type="uint32" access="RO" unit="byte" desc="Page size in transaction prepared list write-page-cache"/> + <property name="tplWritePages" type="uint32" access="RO" unit="wpage" desc="Number of pages in transaction prepared list write-page-cache"/> + <property name="tplInitialFileCount" type="uint16" access="RO" unit="file" desc="Number of files initially allocated to transaction prepared list journal"/> + <property name="tplDataFileSize" type="uint32" access="RO" unit="byte" desc="Size of each journal data file in transaction prepared list journal"/> + <property name="tplCurrentFileCount" type="uint32" access="RO" unit="file" desc="Number of files currently allocated to transaction prepared list journal"/> + + <statistic name="tplTransactionDepth" type="hilo32" unit="txn" desc="Number of currently enqueued prepared transactions"/> + <statistic name="tplTxnPrepares" type="count64" unit="record" desc="Total transaction prepares on transaction prepared list"/> + <statistic name="tplTxnCommits" type="count64" unit="record" desc="Total transaction commits on transaction prepared list"/> + <statistic name="tplTxnAborts" type="count64" unit="record" desc="Total transaction aborts on transaction prepared list"/> + <statistic name="tplOutstandingAIOs" type="hilo32" unit="aio_op" desc="Number of currently outstanding AIO requests in Async IO system"/> + </class> + + <class name="Journal"> + <property name="queueRef" type="objId" access="RO" references="qpid.Queue" isGeneralReference="y"/> + <property name="name" type="sstr" access="RO" index="y"/> + <property name="directory" type="sstr" access="RO" desc="Directory containing journal files"/> + <property name="baseFileName" type="sstr" access="RO" desc="Base filename prefix for journal"/> + <property name="writePageSize" type="uint32" access="RO" unit="byte" desc="Page size in write-page-cache"/> + <property name="writePages" type="uint32" access="RO" unit="wpage" desc="Number of pages in write-page-cache"/> + <property name="readPageSize" type="uint32" access="RO" unit="byte" desc="Page size in read-page-cache"/> + <property name="readPages" type="uint32" access="RO" unit="rpage" desc="Number of pages in read-page-cache"/> + <property name="initialFileCount" type="uint16" access="RO" unit="file" desc="Number of files initially allocated to this journal"/> + <property name="autoExpand" type="bool" access="RO" desc="Auto-expand enabled"/> + <property name="currentFileCount" type="uint16" access="RO" unit="file" desc="Number of files currently allocated to this journal"/> + <property name="maxFileCount" type="uint16" access="RO" unit="file" desc="Max number of files allowed for this journal"/> + <property name="dataFileSize" type="uint32" access="RO" unit="byte" desc="Size of each journal data file"/> + + <statistic name="recordDepth" type="hilo32" unit="record" desc="Number of currently enqueued records (durable messages)"/> + <statistic name="enqueues" type="count64" unit="record" desc="Total enqueued records on journal"/> + <statistic name="dequeues" type="count64" unit="record" desc="Total dequeued records on journal"/> + <statistic name="txn" type="count32" unit="record" desc="Total open transactions (xids) on journal"/> + <statistic name="txnEnqueues" type="count64" unit="record" desc="Total transactional enqueued records on journal"/> + <statistic name="txnDequeues" type="count64" unit="record" desc="Total transactional dequeued records on journal"/> + <statistic name="txnCommits" type="count64" unit="record" desc="Total transactional commit records on journal"/> + <statistic name="txnAborts" type="count64" unit="record" desc="Total transactional abort records on journal"/> + <statistic name="outstandingAIOs" type="hilo32" unit="aio_op" desc="Number of currently outstanding AIO requests in Async IO system"/> + +<!-- + The following are not yet "wired up" in JournalImpl.cpp +--> + <statistic name="freeFileCount" type="hilo32" unit="file" desc="Number of files free on this journal. Includes free files trapped in holes."/> + <statistic name="availableFileCount" type="hilo32" unit="file" desc="Number of files available to be written. Excluding holes"/> + <statistic name="writeWaitFailures" type="count64" unit="record" desc="AIO Wait failures on write"/> + <statistic name="writeBusyFailures" type="count64" unit="record" desc="AIO Busy failures on write"/> + <statistic name="readRecordCount" type="count64" unit="record" desc="Records read from the journal"/> + <statistic name="readBusyFailures" type="count64" unit="record" desc="AIO Busy failures on read"/> + <statistic name="writePageCacheDepth" type="hilo32" unit="wpage" desc="Current depth of write-page-cache"/> + <statistic name="readPageCacheDepth" type="hilo32" unit="rpage" desc="Current depth of read-page-cache"/> + + <method name="expand" desc="Increase number of files allocated for this journal"> + <arg name="by" type="uint32" dir="I" desc="Number of files to increase journal size by"/> + </method> + </class> + + <eventArguments> + <arg name="autoExpand" type="bool" desc="Journal auto-expand enabled"/> + <arg name="fileSize" type="uint32" desc="Journal file size in bytes"/> + <arg name="jrnlId" type="sstr" desc="Journal Id"/> + <arg name="numEnq" type="uint32" desc="Number of recovered enqueues"/> + <arg name="numFiles" type="uint16" desc="Number of journal files"/> + <arg name="numTxn" type="uint32" desc="Number of recovered transactions"/> + <arg name="numTxnDeq" type="uint32" desc="Number of recovered transactional dequeues"/> + <arg name="numTxnEnq" type="uint32" desc="Number of recovered transactional enqueues"/> + <arg name="what" type="sstr" desc="Description of event"/> + </eventArguments> + <event name="enqThresholdExceeded" sev="warn" args="jrnlId, what"/> + <event name="created" sev="notice" args="jrnlId, fileSize, numFiles"/> + <event name="full" sev="error" args="jrnlId, what"/> + <event name="recovered" sev="notice" args="jrnlId, fileSize, numFiles, numEnq, numTxn, numTxnEnq, numTxnDeq"/> +</schema> diff --git a/cpp/src/qpid/log/Logger.cpp b/cpp/src/qpid/log/Logger.cpp index 7c2e807ca9..7e7e1e94c1 100644 --- a/cpp/src/qpid/log/Logger.cpp +++ b/cpp/src/qpid/log/Logger.cpp @@ -177,4 +177,20 @@ void Logger::reconfigure(const std::vector<std::string>& selectors) { void Logger::setPrefix(const std::string& p) { prefix = p; } + +bool Logger::getHiresTimestamp() +{ + return flags & HIRES; +} + + +void Logger::setHiresTimestamp(bool setting) +{ + ScopedLock l(lock); + if (setting) + flags |= HIRES; + else + flags &= ~HIRES; +} + }} // namespace qpid::log diff --git a/cpp/src/qpid/management/ManagementAgent.cpp b/cpp/src/qpid/management/ManagementAgent.cpp index 474c86ed48..86e9d0be8d 100644 --- a/cpp/src/qpid/management/ManagementAgent.cpp +++ b/cpp/src/qpid/management/ManagementAgent.cpp @@ -28,12 +28,15 @@ #include "qpid/management/ManagementObject.h" #include "qpid/broker/DeliverableMessage.h" #include "qpid/log/Statement.h" -#include <qpid/broker/Message.h> +#include "qpid/broker/Message.h" +#include "qpid/broker/Broker.h" #include "qpid/framing/MessageTransferBody.h" #include "qpid/framing/FieldValue.h" #include "qpid/broker/amqp_0_10/MessageTransfer.h" #include "qpid/sys/Time.h" +#include "qpid/sys/Timer.h" #include "qpid/sys/Thread.h" +#include "qpid/sys/PollableQueue.h" #include "qpid/broker/ConnectionState.h" #include "qpid/broker/AclModule.h" #include "qpid/types/Variant.h" @@ -46,6 +49,9 @@ #include <sstream> #include <typeinfo> +#include <boost/bind.hpp> +#include <boost/function.hpp> + namespace qpid { namespace management { @@ -62,22 +68,23 @@ namespace _qmf = qmf::org::apache::qpid::broker; namespace { - const string defaultVendorName("vendor"); - const string defaultProductName("product"); +const size_t qmfV1BufferSize(65536); +const string defaultVendorName("vendor"); +const string defaultProductName("product"); - // Create a valid binding key substring by - // replacing all '.' chars with '_' - const string keyifyNameStr(const string& name) - { - string n2 = name; +// Create a valid binding key substring by +// replacing all '.' chars with '_' +const string keyifyNameStr(const string& name) +{ + string n2 = name; - size_t pos = n2.find('.'); - while (pos != n2.npos) { - n2.replace(pos, 1, "_"); - pos = n2.find('.', pos); - } - return n2; + size_t pos = n2.find('.'); + while (pos != n2.npos) { + n2.replace(pos, 1, "_"); + pos = n2.find('.', pos); } + return n2; +} struct ScopedManagementContext { @@ -90,6 +97,32 @@ struct ScopedManagementContext setManagementExecutionContext(0); } }; + +typedef boost::function0<void> FireFunction; +struct Periodic : public qpid::sys::TimerTask +{ + FireFunction fireFunction; + qpid::sys::Timer* timer; + + Periodic (FireFunction f, qpid::sys::Timer* t, uint32_t seconds); + virtual ~Periodic (); + void fire (); +}; + +Periodic::Periodic (FireFunction f, qpid::sys::Timer* t, uint32_t _seconds) + : TimerTask(sys::Duration((_seconds ? _seconds : 1) * sys::TIME_SEC), + "ManagementAgent::periodicProcessing"), + fireFunction(f), timer(t) {} + +Periodic::~Periodic() {} + +void Periodic::fire() +{ + setupNextFire(); + timer->add(this); + fireFunction(); +} + } @@ -113,9 +146,8 @@ ManagementAgent::RemoteAgent::~RemoteAgent () QPID_LOG(debug, "Remote Agent removed bank=[" << brokerBank << "." << agentBank << "]"); if (mgmtObject != 0) { mgmtObject->resourceDestroy(); - agent.deleteObjectNowLH(mgmtObject->getObjectId()); - delete mgmtObject; - mgmtObject = 0; + agent.deleteObjectNow(mgmtObject->getObjectId()); + mgmtObject.reset(); } } @@ -124,8 +156,7 @@ ManagementAgent::ManagementAgent (const bool qmfV1, const bool qmfV2) : startTime(sys::now()), suppressed(false), disallowAllV1Methods(false), vendorNameKey(defaultVendorName), productNameKey(defaultProductName), - qmf1Support(qmfV1), qmf2Support(qmfV2), maxReplyObjs(100), - msgBuffer(MA_BUFFER_SIZE), memstat(0) + qmf1Support(qmfV1), qmf2Support(qmfV2), maxReplyObjs(100) { nextObjectId = 1; brokerBank = 1; @@ -136,7 +167,7 @@ ManagementAgent::ManagementAgent (const bool qmfV1, const bool qmfV2) : attrMap["_vendor"] = defaultVendorName; attrMap["_product"] = defaultProductName; - memstat = new qmf::org::apache::qpid::broker::Memory(this, 0, "amqp-broker"); + memstat = _qmf::Memory::shared_ptr(new qmf::org::apache::qpid::broker::Memory(this, 0, "amqp-broker")); addObject(memstat, "amqp-broker"); } @@ -155,15 +186,6 @@ ManagementAgent::~ManagementAgent () v2Direct.reset(); remoteAgents.clear(); - - moveNewObjectsLH(); - for (ManagementObjectMap::iterator iter = managementObjects.begin (); - iter != managementObjects.end (); - iter++) { - ManagementObject* object = iter->second; - delete object; - } - managementObjects.clear(); } } @@ -176,6 +198,11 @@ void ManagementAgent::configure(const string& _dataDir, bool _publish, uint16_t broker = _broker; threadPoolSize = _threads; ManagementObject::maxThreads = threadPoolSize; + sendQueue.reset( + new EventQueue(boost::bind(&ManagementAgent::sendEvents, this, _1), broker->getPoller())); + sendQueue->start(); + timer = &broker->getTimer(); + timer->add(new Periodic(boost::bind(&ManagementAgent::periodicProcessing, this), timer, interval)); // Get from file or generate and save to file. if (dataDir.empty()) @@ -218,13 +245,6 @@ void ManagementAgent::configure(const string& _dataDir, bool _publish, uint16_t } } -void ManagementAgent::pluginsInitialized() { - // Do this here so cluster plugin has the chance to set up the timer. - timer = &broker->getClusterTimer(); - timer->add(new Periodic(*this, interval)); -} - - void ManagementAgent::setName(const string& vendor, const string& product, const string& instance) { if (vendor.find(':') != vendor.npos) { @@ -245,13 +265,13 @@ void ManagementAgent::setName(const string& vendor, const string& product, const } else inst = instance; - name_address = vendor + ":" + product + ":" + inst; - attrMap["_instance"] = inst; - attrMap["_name"] = name_address; + name_address = vendor + ":" + product + ":" + inst; + attrMap["_instance"] = inst; + attrMap["_name"] = name_address; - vendorNameKey = keyifyNameStr(vendor); - productNameKey = keyifyNameStr(product); - instanceNameKey = keyifyNameStr(inst); + vendorNameKey = keyifyNameStr(vendor); + productNameKey = keyifyNameStr(product); + instanceNameKey = keyifyNameStr(inst); } @@ -316,11 +336,12 @@ void ManagementAgent::registerEvent (const string& packageName, } // Deprecated: V1 objects -ObjectId ManagementAgent::addObject(ManagementObject* object, uint64_t persistId, bool persistent) +ObjectId ManagementAgent::addObject(ManagementObject::shared_ptr object, uint64_t persistId, bool persistent) { uint16_t sequence; uint64_t objectNum; + sys::Mutex::ScopedLock lock(addLock); sequence = persistent ? 0 : bootSequence; objectNum = persistId ? persistId : nextObjectId++; @@ -329,17 +350,14 @@ ObjectId ManagementAgent::addObject(ManagementObject* object, uint64_t persistId object->setObjectId(objId); - { - sys::Mutex::ScopedLock lock(addLock); - newManagementObjects.push_back(object); - } + newManagementObjects.push_back(object); QPID_LOG(debug, "Management object (V1) added: " << objId.getV2Key()); return objId; } -ObjectId ManagementAgent::addObject(ManagementObject* object, +ObjectId ManagementAgent::addObject(ManagementObject::shared_ptr object, const string& key, bool persistent) { @@ -369,12 +387,11 @@ void ManagementAgent::raiseEvent(const ManagementEvent& event, severity_t severi "emerg", "alert", "crit", "error", "warn", "note", "info", "debug" }; - sys::Mutex::ScopedLock lock (userLock); uint8_t sev = (severity == SEV_DEFAULT) ? event.getSeverity() : (uint8_t) severity; if (qmf1Support) { - Buffer outBuffer(eventBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + char buffer[qmfV1BufferSize]; + Buffer outBuffer(buffer, qmfV1BufferSize); encodeHeader(outBuffer, 'e'); outBuffer.putShortString(event.getPackageName()); @@ -385,9 +402,7 @@ void ManagementAgent::raiseEvent(const ManagementEvent& event, severity_t severi string sBuf; event.encode(sBuf); outBuffer.putRawData(sBuf); - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, mExchange, + sendBuffer(outBuffer, mExchange, "console.event.1.0." + event.getPackageName() + "." + event.getEventName()); QPID_LOG(debug, "SEND raiseEvent (v1) class=" << event.getPackageName() << "." << event.getEventName()); } @@ -426,25 +441,11 @@ void ManagementAgent::raiseEvent(const ManagementEvent& event, severity_t severi Variant::List list_; list_.push_back(map_); ListCodec::encode(list_, content); - sendBufferLH(content, "", headers, "amqp/list", v2Topic, key.str()); + sendBuffer(content, "", headers, "amqp/list", v2Topic, key.str()); QPID_LOG(debug, "SEND raiseEvent (v2) class=" << event.getPackageName() << "." << event.getEventName()); } } -ManagementAgent::Periodic::Periodic (ManagementAgent& _agent, uint32_t _seconds) - : TimerTask(sys::Duration((_seconds ? _seconds : 1) * sys::TIME_SEC), - "ManagementAgent::periodicProcessing"), - agent(_agent) {} - -ManagementAgent::Periodic::~Periodic() {} - -void ManagementAgent::Periodic::fire() -{ - setupNextFire(); - agent.timer->add(this); - agent.periodicProcessing(); -} - void ManagementAgent::clientAdded (const string& routingKey) { sys::Mutex::ScopedLock lock(userLock); @@ -480,28 +481,14 @@ void ManagementAgent::clientAdded (const string& routingKey) while (rkeys.size()) { char localBuffer[16]; Buffer outBuffer(localBuffer, 16); - uint32_t outLen; encodeHeader(outBuffer, 'x'); - outLen = outBuffer.getPosition(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, dExchange, rkeys.front()); + sendBuffer(outBuffer, dExchange, rkeys.front()); QPID_LOG(debug, "SEND ConsoleAddedIndication to=" << rkeys.front()); rkeys.pop_front(); } } -void ManagementAgent::clusterUpdate() { - // Called on all cluster memebers when a new member joins a cluster. - // Set clientWasAdded so that on the next periodicProcessing we will do - // a full update on all cluster members. - sys::Mutex::ScopedLock l(userLock); - moveNewObjectsLH(); // keep lists consistent with updater/updatee. - moveDeletedObjectsLH(); - clientWasAdded = true; - debugSnapshot("Cluster member joined"); -} - void ManagementAgent::encodeHeader (Buffer& buf, uint8_t opcode, uint32_t seq) { buf.putOctet ('A'); @@ -523,12 +510,9 @@ bool ManagementAgent::checkHeader (Buffer& buf, uint8_t *opcode, uint32_t *seq) return h1 == 'A' && h2 == 'M' && h3 == '2'; } -// NOTE WELL: assumes userLock is held by caller (LH) -// NOTE EVEN WELLER: drops this lock when delivering the message!!! -void ManagementAgent::sendBufferLH(Buffer& buf, - uint32_t length, - qpid::broker::Exchange::shared_ptr exchange, - const string& routingKey) +void ManagementAgent::sendBuffer(Buffer& buf, + qpid::broker::Exchange::shared_ptr exchange, + const string& routingKey) { if (suppressed) { QPID_LOG(debug, "Suppressing management message to " << routingKey); @@ -541,6 +525,8 @@ void ManagementAgent::sendBufferLH(Buffer& buf, AMQFrame header((AMQHeaderBody())); AMQFrame content((AMQContentBody())); + size_t length = buf.getPosition(); + buf.reset(); content.castBody<AMQContentBody>()->decode(buf, length); method.setEof(false); @@ -560,42 +546,30 @@ void ManagementAgent::sendBufferLH(Buffer& buf, dp->setRoutingKey(routingKey); transfer->getFrames().append(content); - Message msg(transfer, transfer); msg.setIsManagementMessage(true); - - { - sys::Mutex::ScopedUnlock u(userLock); - - DeliverableMessage deliverable (msg, 0); - try { - exchange->route(deliverable); - } catch(exception&) {} - } + sendQueue->push(make_pair(exchange, msg)); buf.reset(); } -void ManagementAgent::sendBufferLH(Buffer& buf, - uint32_t length, - const string& exchange, - const string& routingKey) +void ManagementAgent::sendBuffer(Buffer& buf, + const string& exchange, + const string& routingKey) { qpid::broker::Exchange::shared_ptr ex(broker->getExchanges().get(exchange)); if (ex.get() != 0) - sendBufferLH(buf, length, ex, routingKey); + sendBuffer(buf, ex, routingKey); } -// NOTE WELL: assumes userLock is held by caller (LH) -// NOTE EVEN WELLER: drops this lock when delivering the message!!! -void ManagementAgent::sendBufferLH(const string& data, - const string& cid, - const Variant::Map& headers, - const string& content_type, - qpid::broker::Exchange::shared_ptr exchange, - const string& routingKey, - uint64_t ttl_msec) +void ManagementAgent::sendBuffer(const string& data, + const string& cid, + const Variant::Map& headers, + const string& content_type, + qpid::broker::Exchange::shared_ptr exchange, + const string& routingKey, + uint64_t ttl_msec) { Variant::Map::const_iterator i; @@ -643,34 +617,27 @@ void ManagementAgent::sendBufferLH(const string& data, msg.setIsManagementMessage(true); msg.computeExpiration(broker->getExpiryPolicy()); - { - sys::Mutex::ScopedUnlock u(userLock); - - DeliverableMessage deliverable (msg, 0); - try { - exchange->route(deliverable); - } catch(exception&) {} - } + sendQueue->push(make_pair(exchange, msg)); } -void ManagementAgent::sendBufferLH(const string& data, - const string& cid, - const Variant::Map& headers, - const string& content_type, - const string& exchange, - const string& routingKey, - uint64_t ttl_msec) +void ManagementAgent::sendBuffer(const string& data, + const string& cid, + const Variant::Map& headers, + const string& content_type, + const string& exchange, + const string& routingKey, + uint64_t ttl_msec) { qpid::broker::Exchange::shared_ptr ex(broker->getExchanges().get(exchange)); if (ex.get() != 0) - sendBufferLH(data, cid, headers, content_type, ex, routingKey, ttl_msec); + sendBuffer(data, cid, headers, content_type, ex, routingKey, ttl_msec); } /** Objects that have been added since the last periodic poll are temporarily * saved in the newManagementObjects list. This allows objects to be - * added without needing to block on the userLock (addLock is used instead). + * added without needing to block on the userLock (objectLock is used instead). * These new objects need to be integrated into the object database * (managementObjects) *before* they can be properly managed. This routine * performs the integration. @@ -680,34 +647,33 @@ void ManagementAgent::sendBufferLH(const string& data, * duplicate object ids. To avoid clashes, don't put deleted objects * into the active object database. */ -void ManagementAgent::moveNewObjectsLH() +void ManagementAgent::moveNewObjects() { - sys::Mutex::ScopedLock lock (addLock); + sys::Mutex::ScopedLock lock(addLock); + sys::Mutex::ScopedLock objLock (objectLock); while (!newManagementObjects.empty()) { - ManagementObject *object = newManagementObjects.back(); + ManagementObject::shared_ptr object = newManagementObjects.back(); newManagementObjects.pop_back(); if (object->isDeleted()) { DeletedObject::shared_ptr dptr(new DeletedObject(object, qmf1Support, qmf2Support)); pendingDeletedObjs[dptr->getKey()].push_back(dptr); - delete object; } else { // add to active object list, check for duplicates. ObjectId oid = object->getObjectId(); ManagementObjectMap::iterator destIter = managementObjects.find(oid); if (destIter != managementObjects.end()) { // duplicate found. It is OK if the old object has been marked // deleted, just replace the old with the new. - ManagementObject *oldObj = destIter->second; - if (oldObj->isDeleted()) { - DeletedObject::shared_ptr dptr(new DeletedObject(oldObj, qmf1Support, qmf2Support)); - pendingDeletedObjs[dptr->getKey()].push_back(dptr); - delete oldObj; - } else { + ManagementObject::shared_ptr oldObj = destIter->second; + if (!oldObj->isDeleted()) { // Duplicate non-deleted objects? This is a user error - oids must be unique. // for now, leak the old object (safer than deleting - may still be referenced) // and complain loudly... QPID_LOG(error, "Detected two management objects with the same identifier: " << oid); + oldObj->resourceDestroy(); } + DeletedObject::shared_ptr dptr(new DeletedObject(oldObj, qmf1Support, qmf2Support)); + pendingDeletedObjs[dptr->getKey()].push_back(dptr); // QPID-3666: be sure to replace the -index- also, as non-key members of // the index object may be different for the new object! So erase the // entry, rather than []= assign here: @@ -720,32 +686,41 @@ void ManagementAgent::moveNewObjectsLH() void ManagementAgent::periodicProcessing (void) { -#define BUFSIZE 65536 #define HEADROOM 4096 debugSnapshot("Management agent periodic processing"); sys::Mutex::ScopedLock lock (userLock); - uint32_t contentSize; string routingKey; string sBuf; - moveNewObjectsLH(); + moveNewObjects(); // // If we're publishing updates, get the latest memory statistics and uptime now // if (publish) { uint64_t uptime = sys::Duration(startTime, sys::now()); - static_cast<_qmf::Broker*>(broker->GetManagementObject())->set_uptime(uptime); - qpid::sys::MemStat::loadMemInfo(memstat); + boost::dynamic_pointer_cast<_qmf::Broker>(broker->GetManagementObject())->set_uptime(uptime); + qpid::sys::MemStat::loadMemInfo(memstat.get()); + } + + // + // Use a copy of the management object map to avoid holding the objectLock + // + ManagementObjectVector localManagementObjects; + { + sys::Mutex::ScopedLock objLock(objectLock); + std::transform(managementObjects.begin(), managementObjects.end(), + std::back_inserter(localManagementObjects), + boost::bind(&ManagementObjectMap::value_type::second, _1)); } // // Clear the been-here flag on all objects in the map. // - for (ManagementObjectMap::iterator iter = managementObjects.begin(); - iter != managementObjects.end(); + for (ManagementObjectVector::iterator iter = localManagementObjects.begin(); + iter != localManagementObjects.end(); iter++) { - ManagementObject* object = iter->second; + ManagementObject::shared_ptr object = *iter; object->setFlags(0); if (clientWasAdded) { object->setForcePublish(true); @@ -760,22 +735,25 @@ void ManagementAgent::periodicProcessing (void) // if we sent the active update first, _then_ the delete update, clients // would incorrectly think the object was deleted. See QPID-2997 // - bool objectsDeleted = moveDeletedObjectsLH(); + bool objectsDeleted = moveDeletedObjects(); + PendingDeletedObjsMap localPendingDeletedObjs; + { + sys::Mutex::ScopedLock objLock(objectLock); + localPendingDeletedObjs.swap(pendingDeletedObjs); + } // // If we are not publishing updates, just clear the pending deletes. There's no // need to tell anybody. // if (!publish) - pendingDeletedObjs.clear(); - - if (!pendingDeletedObjs.empty()) { - // use a temporary copy of the pending deletes so dropping the lock when - // the buffer is sent is safe. - PendingDeletedObjsMap tmp(pendingDeletedObjs); - pendingDeletedObjs.clear(); + localPendingDeletedObjs.clear(); - for (PendingDeletedObjsMap::iterator mIter = tmp.begin(); mIter != tmp.end(); mIter++) { + ResizableBuffer msgBuffer(qmfV1BufferSize); + if (!localPendingDeletedObjs.empty()) { + for (PendingDeletedObjsMap::iterator mIter = localPendingDeletedObjs.begin(); + mIter != localPendingDeletedObjs.end(); + mIter++) { std::string packageName; std::string className; msgBuffer.reset(); @@ -807,11 +785,10 @@ void ManagementAgent::periodicProcessing (void) } if (v1Objs >= maxReplyObjs) { v1Objs = 0; - contentSize = msgBuffer.getSize(); stringstream key; key << "console.obj.1.0." << packageName << "." << className; - msgBuffer.reset(); - sendBufferLH(msgBuffer, contentSize, mExchange, key.str()); // UNLOCKS USERLOCK + size_t contentSize = msgBuffer.getPosition(); + sendBuffer(msgBuffer, mExchange, key.str()); QPID_LOG(debug, "SEND V1 Multicast ContentInd V1 (delete) to=" << key.str() << " len=" << contentSize); } @@ -840,7 +817,7 @@ void ManagementAgent::periodicProcessing (void) headers["qmf.content"] = "_data"; headers["qmf.agent"] = name_address; - sendBufferLH(content, "", headers, "amqp/list", v2Topic, key.str()); // UNLOCKS USERLOCK + sendBuffer(content, "", headers, "amqp/list", v2Topic, key.str(), 0); QPID_LOG(debug, "SEND Multicast ContentInd V2 (delete) to=" << key.str() << " len=" << content.length()); } } @@ -850,11 +827,10 @@ void ManagementAgent::periodicProcessing (void) // send any remaining objects... if (v1Objs) { - contentSize = BUFSIZE - msgBuffer.available(); stringstream key; key << "console.obj.1.0." << packageName << "." << className; - msgBuffer.reset(); - sendBufferLH(msgBuffer, contentSize, mExchange, key.str()); // UNLOCKS USERLOCK + size_t contentSize = msgBuffer.getPosition(); + sendBuffer(msgBuffer, mExchange, key.str()); QPID_LOG(debug, "SEND V1 Multicast ContentInd V1 (delete) to=" << key.str() << " len=" << contentSize); } @@ -877,7 +853,7 @@ void ManagementAgent::periodicProcessing (void) headers["qmf.content"] = "_data"; headers["qmf.agent"] = name_address; - sendBufferLH(content, "", headers, "amqp/list", v2Topic, key.str()); // UNLOCKS USERLOCK + sendBuffer(content, "", headers, "amqp/list", v2Topic, key.str(), 0); QPID_LOG(debug, "SEND Multicast ContentInd V2 (delete) to=" << key.str() << " len=" << content.length()); } } @@ -885,9 +861,7 @@ void ManagementAgent::periodicProcessing (void) } // - // Process the entire object map. Remember: we drop the userLock each time we call - // sendBuffer(). This allows the managementObjects map to be altered during the - // sendBuffer() call, so always restart the search after a sendBuffer() call + // Process the entire object map. // // If publish is disabled, don't send any updates. // @@ -897,14 +871,14 @@ void ManagementAgent::periodicProcessing (void) uint32_t pcount; uint32_t scount; uint32_t v1Objs, v2Objs; - ManagementObjectMap::iterator baseIter; + ManagementObjectVector::iterator baseIter; std::string packageName; std::string className; - for (baseIter = managementObjects.begin(); - baseIter != managementObjects.end(); + for (baseIter = localManagementObjects.begin(); + baseIter != localManagementObjects.end(); baseIter++) { - ManagementObject* baseObject = baseIter->second; + ManagementObject::shared_ptr baseObject = *baseIter; // // Skip until we find a base object requiring processing... // @@ -915,7 +889,7 @@ void ManagementAgent::periodicProcessing (void) } } - if (baseIter == managementObjects.end()) + if (baseIter == localManagementObjects.end()) break; // done - all objects processed pcount = scount = 0; @@ -924,12 +898,12 @@ void ManagementAgent::periodicProcessing (void) list_.clear(); msgBuffer.reset(); - for (ManagementObjectMap::iterator iter = baseIter; - iter != managementObjects.end(); + for (ManagementObjectVector::iterator iter = baseIter; + iter != localManagementObjects.end(); iter++) { msgBuffer.makeAvailable(HEADROOM); // Make sure there's buffer space - ManagementObject* baseObject = baseIter->second; - ManagementObject* object = iter->second; + ManagementObject::shared_ptr baseObject = *baseIter; + ManagementObject::shared_ptr object = *iter; bool send_stats, send_props; if (baseObject->isSameClass(*object) && object->getFlags() == 0) { object->setFlags(1); @@ -1004,12 +978,11 @@ void ManagementAgent::periodicProcessing (void) if (pcount || scount) { if (qmf1Support) { - contentSize = BUFSIZE - msgBuffer.available(); - if (contentSize > 0) { + if (msgBuffer.getPosition() > 0) { stringstream key; key << "console.obj.1.0." << packageName << "." << className; - msgBuffer.reset(); - sendBufferLH(msgBuffer, contentSize, mExchange, key.str()); // UNLOCKS USERLOCK + size_t contentSize = msgBuffer.getPosition(); + sendBuffer(msgBuffer, mExchange, key.str()); QPID_LOG(debug, "SEND V1 Multicast ContentInd to=" << key.str() << " props=" << pcount << " stats=" << scount @@ -1035,7 +1008,7 @@ void ManagementAgent::periodicProcessing (void) headers["qmf.content"] = "_data"; headers["qmf.agent"] = name_address; - sendBufferLH(content, "", headers, "amqp/list", v2Topic, key.str()); // UNLOCKS USERLOCK + sendBuffer(content, "", headers, "amqp/list", v2Topic, key.str(), 0); QPID_LOG(debug, "SEND Multicast ContentInd to=" << key.str() << " props=" << pcount << " stats=" << scount @@ -1045,21 +1018,21 @@ void ManagementAgent::periodicProcessing (void) } } // end processing updates for all objects - if (objectsDeleted) deleteOrphanedAgentsLH(); + if (objectsDeleted) { + sys::Mutex::ScopedLock lock (userLock); + deleteOrphanedAgentsLH(); + } // heartbeat generation. Note that heartbeats need to be sent even if publish is disabled. if (qmf1Support) { - uint32_t contentSize; - char msgChars[BUFSIZE]; - Buffer msgBuffer(msgChars, BUFSIZE); + char msgChars[qmfV1BufferSize]; + Buffer msgBuffer(msgChars, qmfV1BufferSize); encodeHeader(msgBuffer, 'h'); msgBuffer.putLongLong(uint64_t(sys::Duration(sys::EPOCH, sys::now()))); - contentSize = BUFSIZE - msgBuffer.available (); - msgBuffer.reset (); routingKey = "console.heartbeat.1.0"; - sendBufferLH(msgBuffer, contentSize, mExchange, routingKey); + sendBuffer(msgBuffer, mExchange, routingKey); QPID_LOG(debug, "SEND HeartbeatInd to=" << routingKey); } @@ -1087,23 +1060,26 @@ void ManagementAgent::periodicProcessing (void) // Set TTL (in msecs) on outgoing heartbeat indications based on the interval // time to prevent stale heartbeats from getting to the consoles. - sendBufferLH(content, "", headers, "amqp/map", v2Topic, addr_key.str(), interval * 2 * 1000); + sendBuffer(content, "", headers, "amqp/map", v2Topic, addr_key.str(), interval * 2 * 1000); QPID_LOG(debug, "SENT AgentHeartbeat name=" << name_address); } } -void ManagementAgent::deleteObjectNowLH(const ObjectId& oid) +void ManagementAgent::deleteObjectNow(const ObjectId& oid) { - ManagementObjectMap::iterator iter = managementObjects.find(oid); - if (iter == managementObjects.end()) - return; - ManagementObject* object = iter->second; - if (!object->isDeleted()) - return; + ManagementObject::shared_ptr object; + { + sys::Mutex::ScopedLock lock(objectLock); + ManagementObjectMap::iterator iter = managementObjects.find(oid); + if (iter == managementObjects.end()) + return; + object = iter->second; + if (!object->isDeleted()) + return; + managementObjects.erase(oid); + } - // since sendBufferLH drops the userLock, don't call it until we - // are done manipulating the object. #define DNOW_BUFSIZE 2048 char msgChars[DNOW_BUFSIZE]; Buffer msgBuffer(msgChars, DNOW_BUFSIZE); @@ -1139,15 +1115,12 @@ void ManagementAgent::deleteObjectNowLH(const ObjectId& oid) v2key << "." << instanceNameKey; } - object = 0; - managementObjects.erase(oid); + object.reset(); // object deleted, ok to drop lock now. if (publish && qmf1Support) { - uint32_t contentSize = msgBuffer.getPosition(); - msgBuffer.reset(); - sendBufferLH(msgBuffer, contentSize, mExchange, v1key.str()); + sendBuffer(msgBuffer, mExchange, v1key.str()); QPID_LOG(debug, "SEND Immediate(delete) ContentInd to=" << v1key.str()); } @@ -1160,29 +1133,26 @@ void ManagementAgent::deleteObjectNowLH(const ObjectId& oid) string content; ListCodec::encode(list_, content); - sendBufferLH(content, "", headers, "amqp/list", v2Topic, v2key.str()); + sendBuffer(content, "", headers, "amqp/list", v2Topic, v2key.str(), 0); QPID_LOG(debug, "SEND Immediate(delete) ContentInd to=" << v2key.str()); } } -void ManagementAgent::sendCommandCompleteLH(const string& replyToKey, uint32_t sequence, - uint32_t code, const string& text) +void ManagementAgent::sendCommandComplete(const string& replyToKey, uint32_t sequence, + uint32_t code, const string& text) { - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer (qmfV1BufferSize); encodeHeader (outBuffer, 'z', sequence); outBuffer.putLong (code); outBuffer.putShortString (text); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND CommandCompleteInd code=" << code << " text=" << text << " to=" << replyToKey << " seq=" << sequence); } -void ManagementAgent::sendExceptionLH(const string& rte, const string& rtk, const string& cid, - const string& text, uint32_t code, bool viaLocal) +void ManagementAgent::sendException(const string& rte, const string& rtk, const string& cid, + const string& text, uint32_t code, bool viaLocal) { static const string addr_exchange("qmf.default.direct"); @@ -1200,7 +1170,7 @@ void ManagementAgent::sendExceptionLH(const string& rte, const string& rtk, cons map["_values"] = values; MapCodec::encode(map, content); - sendBufferLH(content, cid, headers, "amqp/map", rte, rtk); + sendBuffer(content, cid, headers, "amqp/map", rte, rtk); QPID_LOG(debug, "SENT Exception code=" << code <<" text=" << text); } @@ -1211,7 +1181,6 @@ bool ManagementAgent::dispatchCommand (Deliverable& deliverable, const bool topic, int qmfVersion) { - sys::Mutex::ScopedLock lock (userLock); Message& msg = ((DeliverableMessage&) deliverable).getMessage (); if (topic && qmfVersion == 1) { @@ -1225,23 +1194,23 @@ bool ManagementAgent::dispatchCommand (Deliverable& deliverable, // schema.# if (routingKey == "broker") { - dispatchAgentCommandLH(msg); + dispatchAgentCommand(msg); return false; } if (routingKey.length() > 6) { if (routingKey.compare(0, 9, "agent.1.0") == 0) { - dispatchAgentCommandLH(msg); + dispatchAgentCommand(msg); return false; } if (routingKey.compare(0, 8, "agent.1.") == 0) { - return authorizeAgentMessageLH(msg); + return authorizeAgentMessage(msg); } if (routingKey.compare(0, 7, "schema.") == 0) { - dispatchAgentCommandLH(msg); + dispatchAgentCommand(msg); return true; } } @@ -1253,7 +1222,7 @@ bool ManagementAgent::dispatchCommand (Deliverable& deliverable, // Intercept messages bound to: // "console.ind.locate.# - process these messages, and also allow them to be forwarded. if (routingKey == "console.request.agent_locate") { - dispatchAgentCommandLH(msg); + dispatchAgentCommand(msg); return true; } @@ -1264,7 +1233,7 @@ bool ManagementAgent::dispatchCommand (Deliverable& deliverable, // "<name_address>" - the broker agent's proper name // and do not forward them futher if (routingKey == "broker" || routingKey == name_address) { - dispatchAgentCommandLH(msg, routingKey == "broker"); + dispatchAgentCommand(msg, routingKey == "broker"); return false; } } @@ -1273,16 +1242,15 @@ bool ManagementAgent::dispatchCommand (Deliverable& deliverable, return true; } -void ManagementAgent::handleMethodRequestLH(Buffer& inBuffer, const string& replyToKey, uint32_t sequence, const ConnectionToken* connToken) +void ManagementAgent::handleMethodRequest(Buffer& inBuffer, const string& replyToKey, uint32_t sequence, const ConnectionToken* connToken) { - moveNewObjectsLH(); + moveNewObjects(); string methodName; string packageName; string className; uint8_t hash[16]; - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer (qmfV1BufferSize); AclModule* acl = broker->getAcl(); string inArgs; @@ -1304,9 +1272,7 @@ void ManagementAgent::handleMethodRequestLH(Buffer& inBuffer, const string& repl if (disallowAllV1Methods) { outBuffer.putLong(Manageable::STATUS_FORBIDDEN); outBuffer.putMediumString("QMFv1 methods forbidden on this broker, use QMFv2"); - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN reason='All QMFv1 Methods Forbidden' seq=" << sequence); return; } @@ -1315,9 +1281,7 @@ void ManagementAgent::handleMethodRequestLH(Buffer& inBuffer, const string& repl if (i != disallowed.end()) { outBuffer.putLong(Manageable::STATUS_FORBIDDEN); outBuffer.putMediumString(i->second); - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN text=" << i->second << " seq=" << sequence); return; } @@ -1331,30 +1295,34 @@ void ManagementAgent::handleMethodRequestLH(Buffer& inBuffer, const string& repl if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, ¶ms)) { outBuffer.putLong(Manageable::STATUS_FORBIDDEN); outBuffer.putMediumString(Manageable::StatusText(Manageable::STATUS_FORBIDDEN)); - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN" << " seq=" << sequence); return; } } - ManagementObjectMap::iterator iter = numericFind(objId); - if (iter == managementObjects.end() || iter->second->isDeleted()) { + ManagementObject::shared_ptr object; + { + sys::Mutex::ScopedLock lock(objectLock); + ManagementObjectMap::iterator iter = numericFind(objId); + if (iter != managementObjects.end()) + object = iter->second; + } + + if (!object || object->isDeleted()) { outBuffer.putLong (Manageable::STATUS_UNKNOWN_OBJECT); outBuffer.putMediumString(Manageable::StatusText (Manageable::STATUS_UNKNOWN_OBJECT)); } else { - if ((iter->second->getPackageName() != packageName) || - (iter->second->getClassName() != className)) { + if ((object->getPackageName() != packageName) || + (object->getClassName() != className)) { outBuffer.putLong (Manageable::STATUS_PARAMETER_INVALID); outBuffer.putMediumString(Manageable::StatusText (Manageable::STATUS_PARAMETER_INVALID)); } else { uint32_t pos = outBuffer.getPosition(); try { - sys::Mutex::ScopedUnlock u(userLock); string outBuf; - iter->second->doMethod(methodName, inArgs, outBuf, userId); + object->doMethod(methodName, inArgs, outBuf, userId); outBuffer.putRawData(outBuf); } catch(exception& e) { outBuffer.setPosition(pos);; @@ -1364,17 +1332,15 @@ void ManagementAgent::handleMethodRequestLH(Buffer& inBuffer, const string& repl } } - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND MethodResponse (v1) to=" << replyToKey << " seq=" << sequence); } -void ManagementAgent::handleMethodRequestLH (const string& body, const string& rte, const string& rtk, - const string& cid, const ConnectionToken* connToken, bool viaLocal) +void ManagementAgent::handleMethodRequest (const string& body, const string& rte, const string& rtk, + const string& cid, const ConnectionToken* connToken, bool viaLocal) { - moveNewObjectsLH(); + moveNewObjects(); string methodName; Variant::Map inMap; @@ -1393,8 +1359,8 @@ void ManagementAgent::handleMethodRequestLH (const string& body, const string& r if ((oid = inMap.find("_object_id")) == inMap.end() || (mid = inMap.find("_method_name")) == inMap.end()) { - sendExceptionLH(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_PARAMETER_INVALID), - Manageable::STATUS_PARAMETER_INVALID, viaLocal); + sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_PARAMETER_INVALID), + Manageable::STATUS_PARAMETER_INVALID, viaLocal); return; } @@ -1412,16 +1378,22 @@ void ManagementAgent::handleMethodRequestLH (const string& body, const string& r inArgs = (mid->second).asMap(); } } catch(exception& e) { - sendExceptionLH(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal); + sendException(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal); return; } - ManagementObjectMap::iterator iter = managementObjects.find(objId); + ManagementObject::shared_ptr object; + { + sys::Mutex::ScopedLock lock(objectLock); + ManagementObjectMap::iterator iter = managementObjects.find(objId); + if (iter != managementObjects.end()) + object = iter->second; + } - if (iter == managementObjects.end() || iter->second->isDeleted()) { + if (!object || object->isDeleted()) { stringstream estr; estr << "No object found with ID=" << objId; - sendExceptionLH(rte, rtk, cid, estr.str(), 1, viaLocal); + sendException(rte, rtk, cid, estr.str(), 1, viaLocal); return; } @@ -1429,34 +1401,33 @@ void ManagementAgent::handleMethodRequestLH (const string& body, const string& r AclModule* acl = broker->getAcl(); DisallowedMethods::const_iterator i; - i = disallowed.find(make_pair(iter->second->getClassName(), methodName)); + i = disallowed.find(make_pair(object->getClassName(), methodName)); if (i != disallowed.end()) { - sendExceptionLH(rte, rtk, cid, i->second, Manageable::STATUS_FORBIDDEN, viaLocal); + sendException(rte, rtk, cid, i->second, Manageable::STATUS_FORBIDDEN, viaLocal); return; } string userId = ((const qpid::broker::ConnectionState*) connToken)->getUserId(); if (acl != 0) { map<acl::Property, string> params; - params[acl::PROP_SCHEMAPACKAGE] = iter->second->getPackageName(); - params[acl::PROP_SCHEMACLASS] = iter->second->getClassName(); + params[acl::PROP_SCHEMAPACKAGE] = object->getPackageName(); + params[acl::PROP_SCHEMACLASS] = object->getClassName(); if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, ¶ms)) { - sendExceptionLH(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN), - Manageable::STATUS_FORBIDDEN, viaLocal); + sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN), + Manageable::STATUS_FORBIDDEN, viaLocal); return; } } // invoke the method - QPID_LOG(debug, "RECV MethodRequest (v2) class=" << iter->second->getPackageName() - << ":" << iter->second->getClassName() << " method=" << + QPID_LOG(debug, "RECV MethodRequest (v2) class=" << object->getPackageName() + << ":" << object->getClassName() << " method=" << methodName << " replyTo=" << rte << "/" << rtk << " objId=" << objId << " inArgs=" << inArgs); try { - sys::Mutex::ScopedUnlock u(userLock); - iter->second->doMethod(methodName, inArgs, callMap, userId); + object->doMethod(methodName, inArgs, callMap, userId); errorCode = callMap["_status_code"].asUint32(); if (errorCode == 0) { outMap["_arguments"] = Variant::Map(); @@ -1467,62 +1438,59 @@ void ManagementAgent::handleMethodRequestLH (const string& body, const string& r } else error = callMap["_status_text"].asString(); } catch(exception& e) { - sendExceptionLH(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal); + sendException(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal); return; } if (errorCode != 0) { - sendExceptionLH(rte, rtk, cid, error, errorCode, viaLocal); + sendException(rte, rtk, cid, error, errorCode, viaLocal); return; } MapCodec::encode(outMap, content); - sendBufferLH(content, cid, headers, "amqp/map", rte, rtk); + sendBuffer(content, cid, headers, "amqp/map", rte, rtk); QPID_LOG(debug, "SEND MethodResponse (v2) to=" << rte << "/" << rtk << " seq=" << cid << " map=" << outMap); } -void ManagementAgent::handleBrokerRequestLH (Buffer&, const string& replyToKey, uint32_t sequence) +void ManagementAgent::handleBrokerRequest (Buffer&, const string& replyToKey, uint32_t sequence) { - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer (qmfV1BufferSize); QPID_LOG(debug, "RECV BrokerRequest replyTo=" << replyToKey); encodeHeader (outBuffer, 'b', sequence); uuid.encode (outBuffer); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND BrokerResponse to=" << replyToKey); } -void ManagementAgent::handlePackageQueryLH (Buffer&, const string& replyToKey, uint32_t sequence) +void ManagementAgent::handlePackageQuery (Buffer&, const string& replyToKey, uint32_t sequence) { QPID_LOG(debug, "RECV PackageQuery replyTo=" << replyToKey); - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer (qmfV1BufferSize); - for (PackageMap::iterator pIter = packages.begin (); - pIter != packages.end (); - pIter++) { - encodeHeader (outBuffer, 'p', sequence); - encodePackageIndication (outBuffer, pIter); + sys::Mutex::ScopedLock lock(userLock); + for (PackageMap::iterator pIter = packages.begin (); + pIter != packages.end (); + pIter++) + { + encodeHeader (outBuffer, 'p', sequence); + encodePackageIndication (outBuffer, pIter); + } } - outLen = MA_BUFFER_SIZE - outBuffer.available (); - if (outLen) { - outBuffer.reset (); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + if (outBuffer.getPosition() > 0) { + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND PackageInd to=" << replyToKey << " seq=" << sequence); } - sendCommandCompleteLH(replyToKey, sequence); + sendCommandComplete(replyToKey, sequence); } -void ManagementAgent::handlePackageIndLH (Buffer& inBuffer, const string& replyToKey, uint32_t sequence) +void ManagementAgent::handlePackageInd (Buffer& inBuffer, const string& replyToKey, uint32_t sequence) { string packageName; @@ -1530,10 +1498,11 @@ void ManagementAgent::handlePackageIndLH (Buffer& inBuffer, const string& replyT QPID_LOG(debug, "RECV PackageInd package=" << packageName << " replyTo=" << replyToKey << " seq=" << sequence); + sys::Mutex::ScopedLock lock(userLock); findOrAddPackageLH(packageName); } -void ManagementAgent::handleClassQueryLH(Buffer& inBuffer, const string& replyToKey, uint32_t sequence) +void ManagementAgent::handleClassQuery(Buffer& inBuffer, const string& replyToKey, uint32_t sequence) { string packageName; @@ -1541,40 +1510,39 @@ void ManagementAgent::handleClassQueryLH(Buffer& inBuffer, const string& replyTo QPID_LOG(debug, "RECV ClassQuery package=" << packageName << " replyTo=" << replyToKey << " seq=" << sequence); - PackageMap::iterator pIter = packages.find(packageName); - if (pIter != packages.end()) + typedef std::pair<SchemaClassKey, uint8_t> _ckeyType; + std::list<_ckeyType> classes; { - typedef std::pair<SchemaClassKey, uint8_t> _ckeyType; - std::list<_ckeyType> classes; - ClassMap &cMap = pIter->second; - for (ClassMap::iterator cIter = cMap.begin(); - cIter != cMap.end(); - cIter++) { - if (cIter->second.hasSchema()) { - classes.push_back(make_pair(cIter->first, cIter->second.kind)); + sys::Mutex::ScopedLock lock(userLock); + PackageMap::iterator pIter = packages.find(packageName); + if (pIter != packages.end()) + { + ClassMap &cMap = pIter->second; + for (ClassMap::iterator cIter = cMap.begin(); + cIter != cMap.end(); + cIter++) { + if (cIter->second.hasSchema()) { + classes.push_back(make_pair(cIter->first, cIter->second.kind)); + } } } + } - while (classes.size()) { - Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + while (classes.size()) { + ResizableBuffer outBuffer(qmfV1BufferSize); - encodeHeader(outBuffer, 'q', sequence); - encodeClassIndication(outBuffer, packageName, classes.front().first, classes.front().second); - - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); - QPID_LOG(debug, "SEND ClassInd class=" << packageName << ":" << classes.front().first.name << - "(" << Uuid(classes.front().first.hash) << ") to=" << replyToKey << " seq=" << sequence); - classes.pop_front(); - } + encodeHeader(outBuffer, 'q', sequence); + encodeClassIndication(outBuffer, packageName, classes.front().first, classes.front().second); + sendBuffer(outBuffer, dExchange, replyToKey); + QPID_LOG(debug, "SEND ClassInd class=" << packageName << ":" << classes.front().first.name << + "(" << Uuid(classes.front().first.hash) << ") to=" << replyToKey << " seq=" << sequence); + classes.pop_front(); } - sendCommandCompleteLH(replyToKey, sequence); + sendCommandComplete(replyToKey, sequence); } -void ManagementAgent::handleClassIndLH (Buffer& inBuffer, const string& replyToKey, uint32_t) +void ManagementAgent::handleClassInd (Buffer& inBuffer, const string& replyToKey, uint32_t) { string packageName; SchemaClassKey key; @@ -1587,20 +1555,18 @@ void ManagementAgent::handleClassIndLH (Buffer& inBuffer, const string& replyToK QPID_LOG(debug, "RECV ClassInd class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << "), replyTo=" << replyToKey); + sys::Mutex::ScopedLock lock(userLock); PackageMap::iterator pIter = findOrAddPackageLH(packageName); ClassMap::iterator cIter = pIter->second.find(key); if (cIter == pIter->second.end() || !cIter->second.hasSchema()) { - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer (qmfV1BufferSize); uint32_t sequence = nextRequestSequence++; // Schema Request encodeHeader (outBuffer, 'S', sequence); outBuffer.putShortString(packageName); key.encode(outBuffer); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND SchemaRequest class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << "), to=" << replyToKey << " seq=" << sequence); @@ -1625,7 +1591,7 @@ void ManagementAgent::SchemaClass::appendSchema(Buffer& buf) buf.putRawData(reinterpret_cast<uint8_t*>(&data[0]), data.size()); } -void ManagementAgent::handleSchemaRequestLH(Buffer& inBuffer, const string& rte, const string& rtk, uint32_t sequence) +void ManagementAgent::handleSchemaRequest(Buffer& inBuffer, const string& rte, const string& rtk, uint32_t sequence) { string packageName; SchemaClassKey key; @@ -1636,34 +1602,32 @@ void ManagementAgent::handleSchemaRequestLH(Buffer& inBuffer, const string& rte, QPID_LOG(debug, "RECV SchemaRequest class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << "), replyTo=" << rte << "/" << rtk << " seq=" << sequence); + sys::Mutex::ScopedLock lock(userLock); PackageMap::iterator pIter = packages.find(packageName); if (pIter != packages.end()) { ClassMap& cMap = pIter->second; ClassMap::iterator cIter = cMap.find(key); if (cIter != cMap.end()) { - Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer(qmfV1BufferSize); SchemaClass& classInfo = cIter->second; if (classInfo.hasSchema()) { encodeHeader(outBuffer, 's', sequence); classInfo.appendSchema(outBuffer); - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, rte, rtk); + sendBuffer(outBuffer, rte, rtk); QPID_LOG(debug, "SEND SchemaResponse to=" << rte << "/" << rtk << " seq=" << sequence); } else - sendCommandCompleteLH(rtk, sequence, 1, "Schema not available"); + sendCommandComplete(rtk, sequence, 1, "Schema not available"); } else - sendCommandCompleteLH(rtk, sequence, 1, "Class key not found"); + sendCommandComplete(rtk, sequence, 1, "Class key not found"); } else - sendCommandCompleteLH(rtk, sequence, 1, "Package not found"); + sendCommandComplete(rtk, sequence, 1, "Package not found"); } -void ManagementAgent::handleSchemaResponseLH(Buffer& inBuffer, const string& /*replyToKey*/, uint32_t sequence) +void ManagementAgent::handleSchemaResponse(Buffer& inBuffer, const string& /*replyToKey*/, uint32_t sequence) { string packageName; SchemaClassKey key; @@ -1676,6 +1640,7 @@ void ManagementAgent::handleSchemaResponseLH(Buffer& inBuffer, const string& /*r QPID_LOG(debug, "RECV SchemaResponse class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << ")" << " seq=" << sequence); + sys::Mutex::ScopedLock lock(userLock); PackageMap::iterator pIter = packages.find(packageName); if (pIter != packages.end()) { ClassMap& cMap = pIter->second; @@ -1690,14 +1655,11 @@ void ManagementAgent::handleSchemaResponseLH(Buffer& inBuffer, const string& /*r inBuffer.getRawData(reinterpret_cast<uint8_t*>(&cIter->second.data[0]), length); // Publish a class-indication message - Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer(qmfV1BufferSize); encodeHeader(outBuffer, 'q'); encodeClassIndication(outBuffer, pIter->first, cIter->first, cIter->second.kind); - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, mExchange, "schema.class"); + sendBuffer(outBuffer, mExchange, "schema.class"); QPID_LOG(debug, "SEND ClassInd class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << ")" << " to=schema.class"); } @@ -1756,7 +1718,7 @@ void ManagementAgent::deleteOrphanedAgentsLH() remoteAgents.erase(*dIter); } -void ManagementAgent::handleAttachRequestLH (Buffer& inBuffer, const string& replyToKey, uint32_t sequence, const ConnectionToken* connToken) +void ManagementAgent::handleAttachRequest (Buffer& inBuffer, const string& replyToKey, uint32_t sequence, const ConnectionToken* connToken) { string label; uint32_t requestedBrokerBank, requestedAgentBank; @@ -1764,12 +1726,14 @@ void ManagementAgent::handleAttachRequestLH (Buffer& inBuffer, const string& rep ObjectId connectionRef = ((const ConnectionState*) connToken)->GetManagementObject()->getObjectId(); Uuid systemId; - moveNewObjectsLH(); + moveNewObjects(); + + sys::Mutex::ScopedLock lock(userLock); deleteOrphanedAgentsLH(); RemoteAgentMap::iterator aIter = remoteAgents.find(connectionRef); if (aIter != remoteAgents.end()) { // There already exists an agent on this session. Reject the request. - sendCommandCompleteLH(replyToKey, sequence, 1, "Connection already has remote agent"); + sendCommandComplete(replyToKey, sequence, 1, "Connection already has remote agent"); return; } @@ -1788,7 +1752,7 @@ void ManagementAgent::handleAttachRequestLH (Buffer& inBuffer, const string& rep agent->agentBank = assignedBank; agent->routingKey = replyToKey; agent->connectionRef = connectionRef; - agent->mgmtObject = new _qmf::Agent (this, agent.get()); + agent->mgmtObject = _qmf::Agent::shared_ptr(new _qmf::Agent (this, agent.get())); agent->mgmtObject->set_connectionRef(agent->connectionRef); agent->mgmtObject->set_label (label); agent->mgmtObject->set_registeredTo (broker->GetManagementObject()->getObjectId()); @@ -1801,25 +1765,22 @@ void ManagementAgent::handleAttachRequestLH (Buffer& inBuffer, const string& rep QPID_LOG(debug, "Remote Agent registered bank=[" << brokerBank << "." << assignedBank << "] replyTo=" << replyToKey); // Send an Attach Response - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer (qmfV1BufferSize); encodeHeader (outBuffer, 'a', sequence); outBuffer.putLong (brokerBank); outBuffer.putLong (assignedBank); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND AttachResponse brokerBank=" << brokerBank << " agentBank=" << assignedBank << " to=" << replyToKey << " seq=" << sequence); } -void ManagementAgent::handleGetQueryLH(Buffer& inBuffer, const string& replyToKey, uint32_t sequence) +void ManagementAgent::handleGetQuery(Buffer& inBuffer, const string& replyToKey, uint32_t sequence) { FieldTable ft; FieldTable::ValuePtr value; - moveNewObjectsLH(); + moveNewObjects(); ft.decode(inBuffer); @@ -1832,11 +1793,17 @@ void ManagementAgent::handleGetQueryLH(Buffer& inBuffer, const string& replyToKe return; ObjectId selector(value->get<string>()); - ManagementObjectMap::iterator iter = numericFind(selector); - if (iter != managementObjects.end()) { - ManagementObject* object = iter->second; - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + + ManagementObject::shared_ptr object; + { + sys::Mutex::ScopedLock lock(objectLock); + ManagementObjectMap::iterator iter = numericFind(selector); + if (iter != managementObjects.end()) + object = iter->second; + } + + if (object) { + ResizableBuffer outBuffer (qmfV1BufferSize); if (object->getConfigChanged() || object->getInstChanged()) object->setUpdateTime(); @@ -1849,89 +1816,80 @@ void ManagementAgent::handleGetQueryLH(Buffer& inBuffer, const string& replyToKe sBuf.clear(); object->writeStatistics(sBuf, true); outBuffer.putRawData(sBuf); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence); } } - sendCommandCompleteLH(replyToKey, sequence); + sendCommandComplete(replyToKey, sequence); return; } string className (value->get<string>()); - std::list<ObjectId>matches; + std::list<ManagementObject::shared_ptr> matches; if (className == "memory") - qpid::sys::MemStat::loadMemInfo(memstat); + qpid::sys::MemStat::loadMemInfo(memstat.get()); if (className == "broker") { uint64_t uptime = sys::Duration(startTime, sys::now()); - static_cast<_qmf::Broker*>(broker->GetManagementObject())->set_uptime(uptime); + boost::dynamic_pointer_cast<_qmf::Broker>(broker->GetManagementObject())->set_uptime(uptime); } // build up a set of all objects to be dumped - for (ManagementObjectMap::iterator iter = managementObjects.begin(); - iter != managementObjects.end(); - iter++) { - ManagementObject* object = iter->second; - if (object->getClassName () == className) { - matches.push_back(object->getObjectId()); + { + sys::Mutex::ScopedLock lock(objectLock); + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + ManagementObject::shared_ptr object = iter->second; + if (object->getClassName () == className) { + matches.push_back(object); + } } } - // send them (as sendBufferLH drops the userLock) - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + // send them + ResizableBuffer outBuffer (qmfV1BufferSize); while (matches.size()) { - ObjectId objId = matches.front(); - ManagementObjectMap::iterator oIter = managementObjects.find( objId ); - if (oIter != managementObjects.end()) { - ManagementObject* object = oIter->second; - - if (object->getConfigChanged() || object->getInstChanged()) - object->setUpdateTime(); - - if (!object->isDeleted()) { - string sProps, sStats; - object->writeProperties(sProps); - object->writeStatistics(sStats, true); - - size_t len = 8 + sProps.length() + sStats.length(); // 8 == size of header in bytes. - if (len > MA_BUFFER_SIZE) { - QPID_LOG(error, "Object " << objId << " too large for output buffer - discarded!"); - } else { - if (outBuffer.available() < len) { // not enough room in current buffer, send it. - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); // drops lock - QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence); - continue; // lock dropped, need to re-find _SAME_ objid as it may have been deleted. - } - encodeHeader(outBuffer, 'g', sequence); - outBuffer.putRawData(sProps); - outBuffer.putRawData(sStats); + ManagementObject::shared_ptr object = matches.front(); + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + if (!object->isDeleted()) { + string sProps, sStats; + object->writeProperties(sProps); + object->writeStatistics(sStats, true); + + size_t len = 8 + sProps.length() + sStats.length(); // 8 == size of header in bytes. + if (len > qmfV1BufferSize) { + QPID_LOG(error, "Object " << object->getObjectId() << " too large for output buffer - discarded!"); + } else { + if (outBuffer.available() < len) { // not enough room in current buffer, send it. + sendBuffer(outBuffer, dExchange, replyToKey); + QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence); + continue; // lock dropped, need to re-find _SAME_ objid as it may have been deleted. } + encodeHeader(outBuffer, 'g', sequence); + outBuffer.putRawData(sProps); + outBuffer.putRawData(sStats); } } matches.pop_front(); } - outLen = MA_BUFFER_SIZE - outBuffer.available (); - if (outLen) { - outBuffer.reset (); - sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + if (outBuffer.getPosition() > 0) { + sendBuffer(outBuffer, dExchange, replyToKey); QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence); } - sendCommandCompleteLH(replyToKey, sequence); + sendCommandComplete(replyToKey, sequence); } -void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, const string& rtk, const string& cid, bool viaLocal) +void ManagementAgent::handleGetQuery(const string& body, const string& rte, const string& rtk, const string& cid, bool viaLocal) { - moveNewObjectsLH(); + moveNewObjects(); Variant::Map inMap; Variant::Map::const_iterator i; @@ -1950,17 +1908,17 @@ void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, co */ i = inMap.find("_what"); if (i == inMap.end()) { - sendExceptionLH(rte, rtk, cid, "_what element missing in Query"); + sendException(rte, rtk, cid, "_what element missing in Query"); return; } if (i->second.getType() != qpid::types::VAR_STRING) { - sendExceptionLH(rte, rtk, cid, "_what element is not a string"); + sendException(rte, rtk, cid, "_what element is not a string"); return; } if (i->second.asString() != "OBJECT") { - sendExceptionLH(rte, rtk, cid, "Query for _what => '" + i->second.asString() + "' not supported"); + sendException(rte, rtk, cid, "Query for _what => '" + i->second.asString() + "' not supported"); return; } @@ -1984,11 +1942,11 @@ void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, co } if (className == "memory") - qpid::sys::MemStat::loadMemInfo(memstat); + qpid::sys::MemStat::loadMemInfo(memstat.get()); if (className == "broker") { uint64_t uptime = sys::Duration(startTime, sys::now()); - static_cast<_qmf::Broker*>(broker->GetManagementObject())->set_uptime(uptime); + boost::dynamic_pointer_cast<_qmf::Broker>(broker->GetManagementObject())->set_uptime(uptime); } /* @@ -2000,10 +1958,14 @@ void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, co Variant::List list_; ObjectId objId(i->second.asMap()); - ManagementObjectMap::iterator iter = managementObjects.find(objId); - if (iter != managementObjects.end()) { - ManagementObject* object = iter->second; - + ManagementObject::shared_ptr object; + { + sys::Mutex::ScopedLock lock (objectLock); + ManagementObjectMap::iterator iter = managementObjects.find(objId); + if (iter != managementObjects.end()) + object = iter->second; + } + if (object) { if (object->getConfigChanged() || object->getInstChanged()) object->setUpdateTime(); @@ -2027,7 +1989,7 @@ void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, co string content; ListCodec::encode(list_, content); - sendBufferLH(content, cid, headers, "amqp/list", rte, rtk); + sendBuffer(content, cid, headers, "amqp/list", rte, rtk); QPID_LOG(debug, "SENT QueryResponse (query by object_id) to=" << rte << "/" << rtk); return; } @@ -2037,10 +1999,18 @@ void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, co Variant::List _subList; unsigned int objCount = 0; - for (ManagementObjectMap::iterator iter = managementObjects.begin(); - iter != managementObjects.end(); + ManagementObjectVector localManagementObjects; + { + sys::Mutex::ScopedLock objLock(objectLock); + std::transform(managementObjects.begin(), managementObjects.end(), + std::back_inserter(localManagementObjects), + boost::bind(&ManagementObjectMap::value_type::second, _1)); + } + + for (ManagementObjectVector::iterator iter = localManagementObjects.begin(); + iter != localManagementObjects.end(); iter++) { - ManagementObject* object = iter->second; + ManagementObject::shared_ptr object = *iter; if (object->getClassName() == className && (packageName.empty() || object->getPackageName() == packageName)) { @@ -2055,7 +2025,7 @@ void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, co object->writeTimestamps(map_); object->mapEncodeValues(values, true, true); // write both stats and properties - iter->first.mapEncode(oidMap); + object->getObjectId().mapEncode(oidMap); map_["_values"] = values; map_["_object_id"] = oidMap; @@ -2080,13 +2050,13 @@ void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, co string content; while (_list.size() > 1) { ListCodec::encode(_list.front().asList(), content); - sendBufferLH(content, cid, headers, "amqp/list", rte, rtk); + sendBuffer(content, cid, headers, "amqp/list", rte, rtk); _list.pop_front(); QPID_LOG(debug, "SENT QueryResponse (partial, query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length()); } headers.erase("partial"); ListCodec::encode(_list.size() ? _list.front().asList() : Variant::List(), content); - sendBufferLH(content, cid, headers, "amqp/list", rte, rtk); + sendBuffer(content, cid, headers, "amqp/list", rte, rtk); QPID_LOG(debug, "SENT QueryResponse (query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length()); return; } @@ -2094,12 +2064,12 @@ void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, co // Unrecognized query - Send empty message to indicate CommandComplete string content; ListCodec::encode(Variant::List(), content); - sendBufferLH(content, cid, headers, "amqp/list", rte, rtk); + sendBuffer(content, cid, headers, "amqp/list", rte, rtk); QPID_LOG(debug, "SENT QueryResponse (empty) to=" << rte << "/" << rtk); } -void ManagementAgent::handleLocateRequestLH(const string&, const string& rte, const string& rtk, const string& cid) +void ManagementAgent::handleLocateRequest(const string&, const string& rte, const string& rtk, const string& cid) { QPID_LOG(debug, "RCVD AgentLocateRequest"); @@ -2117,16 +2087,17 @@ void ManagementAgent::handleLocateRequestLH(const string&, const string& rte, co string content; MapCodec::encode(map, content); - sendBufferLH(content, cid, headers, "amqp/map", rte, rtk); + sendBuffer(content, cid, headers, "amqp/map", rte, rtk); clientWasAdded = true; QPID_LOG(debug, "SENT AgentLocateResponse replyTo=" << rte << "/" << rtk); } -bool ManagementAgent::authorizeAgentMessageLH(Message& msg) +bool ManagementAgent::authorizeAgentMessage(Message& msg) { - Buffer inBuffer (inputBuffer, MA_BUFFER_SIZE); + sys::Mutex::ScopedLock lock(userLock); + ResizableBuffer inBuffer (qmfV1BufferSize); uint32_t sequence = 0; bool methodReq = false; bool mapMsg = false; @@ -2140,7 +2111,7 @@ bool ManagementAgent::authorizeAgentMessageLH(Message& msg) // authorized or not. In this case, return true (authorized) if there is no ACL in place, // otherwise return false; // - if (msg.getContentSize() > MA_BUFFER_SIZE) + if (msg.getContentSize() > qmfV1BufferSize) return broker->getAcl() == 0; inBuffer.putRawData(msg.getContent()); @@ -2149,7 +2120,7 @@ bool ManagementAgent::authorizeAgentMessageLH(Message& msg) qpid::broker::amqp_0_10::MessageTransfer& transfer(qpid::broker::amqp_0_10::MessageTransfer::get(msg)); const framing::MessageProperties* p = - transfer.getFrames().getHeaders()->get<framing::MessageProperties>(); + transfer.getFrames().getHeaders()->get<framing::MessageProperties>(); const framing::FieldTable *headers = p ? &p->getApplicationHeaders() : 0; @@ -2193,11 +2164,11 @@ bool ManagementAgent::authorizeAgentMessageLH(Message& msg) } // look up schema for object to get package and class name - + sys::Mutex::ScopedLock lock(objectLock); ManagementObjectMap::iterator iter = managementObjects.find(objId); if (iter == managementObjects.end() || iter->second->isDeleted()) { - QPID_LOG(debug, "ManagementAgent::authorizeAgentMessageLH: stale object id " << + QPID_LOG(debug, "ManagementAgent::authorizeAgentMessage: stale object id " << objId); return false; } @@ -2256,19 +2227,16 @@ bool ManagementAgent::authorizeAgentMessageLH(Message& msg) cid = p->getCorrelationId(); if (mapMsg) { - sendExceptionLH(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN), - Manageable::STATUS_FORBIDDEN, false); + sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN), + Manageable::STATUS_FORBIDDEN, false); } else { - Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer(qmfV1BufferSize); encodeHeader(outBuffer, 'm', sequence); outBuffer.putLong(Manageable::STATUS_FORBIDDEN); outBuffer.putMediumString(Manageable::StatusText(Manageable::STATUS_FORBIDDEN)); - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - sendBufferLH(outBuffer, outLen, rte, rtk); + sendBuffer(outBuffer, rte, rtk); } QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN" << " seq=" << sequence); @@ -2280,7 +2248,7 @@ bool ManagementAgent::authorizeAgentMessageLH(Message& msg) return true; } -void ManagementAgent::dispatchAgentCommandLH(Message& msg, bool viaLocal) +void ManagementAgent::dispatchAgentCommand(Message& msg, bool viaLocal) { string rte; string rtk; @@ -2295,10 +2263,10 @@ void ManagementAgent::dispatchAgentCommandLH(Message& msg, bool viaLocal) else return; - Buffer inBuffer(inputBuffer, MA_BUFFER_SIZE); + ResizableBuffer inBuffer(qmfV1BufferSize); uint8_t opcode; - if (msg.getContentSize() > MA_BUFFER_SIZE) { + if (msg.getContentSize() > qmfV1BufferSize) { QPID_LOG(debug, "ManagementAgent::dispatchAgentCommandLH: Message too large: " << msg.getContentSize()); return; @@ -2317,39 +2285,38 @@ void ManagementAgent::dispatchAgentCommandLH(Message& msg, bool viaLocal) string body; string cid; inBuffer.getRawData(body, bufferLen); + { + if (p && p->hasCorrelationId()) { + cid = p->getCorrelationId(); + } - if (p && p->hasCorrelationId()) { - cid = p->getCorrelationId(); + if (opcode == "_method_request") + return handleMethodRequest(body, rte, rtk, cid, msg.getPublisher(), viaLocal); + else if (opcode == "_query_request") + return handleGetQuery(body, rte, rtk, cid, viaLocal); + else if (opcode == "_agent_locate_request") + return handleLocateRequest(body, rte, rtk, cid); } - - if (opcode == "_method_request") - return handleMethodRequestLH(body, rte, rtk, cid, msg.getPublisher(), viaLocal); - else if (opcode == "_query_request") - return handleGetQueryLH(body, rte, rtk, cid, viaLocal); - else if (opcode == "_agent_locate_request") - return handleLocateRequestLH(body, rte, rtk, cid); - QPID_LOG(warning, "Support for QMF Opcode [" << opcode << "] TBD!!!"); return; } // old preV2 binary messages - while (inBuffer.getPosition() < bufferLen) { uint32_t sequence; if (!checkHeader(inBuffer, &opcode, &sequence)) return; - if (opcode == 'B') handleBrokerRequestLH (inBuffer, rtk, sequence); - else if (opcode == 'P') handlePackageQueryLH (inBuffer, rtk, sequence); - else if (opcode == 'p') handlePackageIndLH (inBuffer, rtk, sequence); - else if (opcode == 'Q') handleClassQueryLH (inBuffer, rtk, sequence); - else if (opcode == 'q') handleClassIndLH (inBuffer, rtk, sequence); - else if (opcode == 'S') handleSchemaRequestLH (inBuffer, rte, rtk, sequence); - else if (opcode == 's') handleSchemaResponseLH (inBuffer, rtk, sequence); - else if (opcode == 'A') handleAttachRequestLH (inBuffer, rtk, sequence, msg.getPublisher()); - else if (opcode == 'G') handleGetQueryLH (inBuffer, rtk, sequence); - else if (opcode == 'M') handleMethodRequestLH (inBuffer, rtk, sequence, msg.getPublisher()); + if (opcode == 'B') handleBrokerRequest (inBuffer, rtk, sequence); + else if (opcode == 'P') handlePackageQuery (inBuffer, rtk, sequence); + else if (opcode == 'p') handlePackageInd (inBuffer, rtk, sequence); + else if (opcode == 'Q') handleClassQuery (inBuffer, rtk, sequence); + else if (opcode == 'q') handleClassInd (inBuffer, rtk, sequence); + else if (opcode == 'S') handleSchemaRequest (inBuffer, rte, rtk, sequence); + else if (opcode == 's') handleSchemaResponse (inBuffer, rtk, sequence); + else if (opcode == 'A') handleAttachRequest (inBuffer, rtk, sequence, msg.getPublisher()); + else if (opcode == 'G') handleGetQuery (inBuffer, rtk, sequence); + else if (opcode == 'M') handleMethodRequest (inBuffer, rtk, sequence, msg.getPublisher()); } } @@ -2365,14 +2332,11 @@ ManagementAgent::PackageMap::iterator ManagementAgent::findOrAddPackageLH(string QPID_LOG (debug, "ManagementAgent added package " << name); // Publish a package-indication message - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + ResizableBuffer outBuffer (qmfV1BufferSize); encodeHeader (outBuffer, 'p'); encodePackageIndication (outBuffer, result.first); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - sendBufferLH(outBuffer, outLen, mExchange, "schema.package"); + sendBuffer(outBuffer, mExchange, "schema.package"); QPID_LOG(debug, "SEND PackageInd package=" << name << " to=schema.package"); return result.first; @@ -2587,70 +2551,6 @@ void ManagementAgent::SchemaClass::mapDecode(const Variant::Map& _map) { } } -void ManagementAgent::exportSchemas(string& out) { - Variant::List list_; - Variant::Map map_, kmap, cmap; - - for (PackageMap::const_iterator i = packages.begin(); i != packages.end(); ++i) { - string name = i->first; - const ClassMap& classes = i ->second; - for (ClassMap::const_iterator j = classes.begin(); j != classes.end(); ++j) { - const SchemaClassKey& key = j->first; - const SchemaClass& klass = j->second; - if (klass.writeSchemaCall == 0) { // Ignore built-in schemas. - // Encode name, schema-key, schema-class - - map_.clear(); - kmap.clear(); - cmap.clear(); - - key.mapEncode(kmap); - klass.mapEncode(cmap); - - map_["_pname"] = name; - map_["_key"] = kmap; - map_["_class"] = cmap; - list_.push_back(map_); - } - } - } - - ListCodec::encode(list_, out); -} - -void ManagementAgent::importSchemas(qpid::framing::Buffer& inBuf) { - - string buf(inBuf.getPointer(), inBuf.available()); - Variant::List content; - ListCodec::decode(buf, content); - Variant::List::const_iterator l; - - - for (l = content.begin(); l != content.end(); l++) { - string package; - SchemaClassKey key; - SchemaClass klass; - Variant::Map map_, kmap, cmap; - Variant::Map::const_iterator i; - - map_ = l->asMap(); - - if ((i = map_.find("_pname")) != map_.end()) { - package = i->second.asString(); - - if ((i = map_.find("_key")) != map_.end()) { - key.mapDecode(i->second.asMap()); - - if ((i = map_.find("_class")) != map_.end()) { - klass.mapDecode(i->second.asMap()); - - packages[package][key] = klass; - } - } - } - } -} - void ManagementAgent::RemoteAgent::mapEncode(Variant::Map& map_) const { Variant::Map _objId, _values; @@ -2684,7 +2584,7 @@ void ManagementAgent::RemoteAgent::mapDecode(const Variant::Map& map_) { connectionRef.mapDecode(i->second.asMap()); } - mgmtObject = new _qmf::Agent(&agent, this); + mgmtObject = _qmf::Agent::shared_ptr(new _qmf::Agent(&agent, this)); if ((i = map_.find("_values")) != map_.end()) { mgmtObject->mapDecodeValues(i->second.asMap()); @@ -2694,52 +2594,6 @@ void ManagementAgent::RemoteAgent::mapDecode(const Variant::Map& map_) { mgmtObject->set_connectionRef(connectionRef); } -void ManagementAgent::exportAgents(string& out) { - Variant::List list_; - Variant::Map map_, omap, amap; - - for (RemoteAgentMap::const_iterator i = remoteAgents.begin(); - i != remoteAgents.end(); - ++i) - { - // TODO aconway 2010-03-04: see comment in ManagementAgent::RemoteAgent::encode - boost::shared_ptr<RemoteAgent> agent(i->second); - - map_.clear(); - amap.clear(); - - agent->mapEncode(amap); - map_["_remote_agent"] = amap; - list_.push_back(map_); - } - - ListCodec::encode(list_, out); -} - -void ManagementAgent::importAgents(qpid::framing::Buffer& inBuf) { - string buf(inBuf.getPointer(), inBuf.available()); - Variant::List content; - ListCodec::decode(buf, content); - Variant::List::const_iterator l; - sys::Mutex::ScopedLock lock(userLock); - - for (l = content.begin(); l != content.end(); l++) { - boost::shared_ptr<RemoteAgent> agent(new RemoteAgent(*this)); - Variant::Map map_; - Variant::Map::const_iterator i; - - map_ = l->asMap(); - - if ((i = map_.find("_remote_agent")) != map_.end()) { - - agent->mapDecode(i->second.asMap()); - - addObject (agent->mgmtObject, 0, false); - remoteAgents[agent->connectionRef] = agent; - } - } -} - namespace { bool isDeletedMap(const ManagementObjectMap::value_type& value) { return value.second->isDeleted(); @@ -2818,56 +2672,8 @@ Variant::Map ManagementAgent::toMap(const FieldTable& from) return map; } - -// Build up a list of the current set of deleted objects that are pending their -// next (last) publish-ment. -void ManagementAgent::exportDeletedObjects(DeletedObjectList& outList) -{ - outList.clear(); - - sys::Mutex::ScopedLock lock (userLock); - - moveNewObjectsLH(); - moveDeletedObjectsLH(); - - // now copy the pending deletes into the outList - for (PendingDeletedObjsMap::iterator mIter = pendingDeletedObjs.begin(); - mIter != pendingDeletedObjs.end(); mIter++) { - for (DeletedObjectList::iterator lIter = mIter->second.begin(); - lIter != mIter->second.end(); lIter++) { - outList.push_back(*lIter); - } - } -} - -// Called by cluster to reset the management agent's list of deleted -// objects to match the rest of the cluster. -void ManagementAgent::importDeletedObjects(const DeletedObjectList& inList) -{ - sys::Mutex::ScopedLock lock (userLock); - // Clear out any existing deleted objects - moveNewObjectsLH(); - pendingDeletedObjs.clear(); - ManagementObjectMap::iterator i = managementObjects.begin(); - // Silently drop any deleted objects left over from receiving the update. - while (i != managementObjects.end()) { - ManagementObject* object = i->second; - if (object->isDeleted()) { - delete object; - managementObjects.erase(i++); - } - else ++i; - } - for (DeletedObjectList::const_iterator lIter = inList.begin(); lIter != inList.end(); lIter++) { - - std::string classkey((*lIter)->packageName + std::string(":") + (*lIter)->className); - pendingDeletedObjs[classkey].push_back(*lIter); - } -} - - // construct a DeletedObject from a management object. -ManagementAgent::DeletedObject::DeletedObject(ManagementObject *src, bool v1, bool v2) +ManagementAgent::DeletedObject::DeletedObject(ManagementObject::shared_ptr src, bool v1, bool v2) : packageName(src->getPackageName()), className(src->getClassName()) { @@ -2903,54 +2709,18 @@ ManagementAgent::DeletedObject::DeletedObject(ManagementObject *src, bool v1, bo } } +// Remove Deleted objects, and save for later publishing... +bool ManagementAgent::moveDeletedObjects() { + typedef vector<pair<ObjectId, ManagementObject::shared_ptr> > DeleteList; + sys::Mutex::ScopedLock lock (objectLock); -// construct a DeletedObject from an encoded representation. Used by -// clustering to move deleted objects between clustered brokers. See -// DeletedObject::encode() for the reverse. -ManagementAgent::DeletedObject::DeletedObject(const std::string& encoded) -{ - qpid::types::Variant::Map map_; - MapCodec::decode(encoded, map_); - - packageName = map_["_package_name"].getString(); - className = map_["_class_name"].getString(); - objectId = map_["_object_id"].getString(); - - encodedV1Config = map_["_v1_config"].getString(); - encodedV1Inst = map_["_v1_inst"].getString(); - encodedV2 = map_["_v2_data"].asMap(); -} - - -// encode a DeletedObject to a string buffer. Used by -// clustering to move deleted objects between clustered brokers. See -// DeletedObject(const std::string&) for the reverse. -void ManagementAgent::DeletedObject::encode(std::string& toBuffer) -{ - qpid::types::Variant::Map map_; - - - map_["_package_name"] = packageName; - map_["_class_name"] = className; - map_["_object_id"] = objectId; - - map_["_v1_config"] = encodedV1Config; - map_["_v1_inst"] = encodedV1Inst; - map_["_v2_data"] = encodedV2; - - MapCodec::encode(map_, toBuffer); -} - -// Remove Deleted objects, and save for later publishing... -bool ManagementAgent::moveDeletedObjectsLH() { - typedef vector<pair<ObjectId, ManagementObject*> > DeleteList; DeleteList deleteList; for (ManagementObjectMap::iterator iter = managementObjects.begin(); iter != managementObjects.end(); ++iter) { - ManagementObject* object = iter->second; + ManagementObject::shared_ptr object = iter->second; if (object->isDeleted()) deleteList.push_back(*iter); } @@ -2959,17 +2729,31 @@ bool ManagementAgent::moveDeletedObjectsLH() { iter != deleteList.rend(); iter++) { - ManagementObject* delObj = iter->second; + ManagementObject::shared_ptr delObj = iter->second; assert(delObj->isDeleted()); DeletedObject::shared_ptr dptr(new DeletedObject(delObj, qmf1Support, qmf2Support)); pendingDeletedObjs[dptr->getKey()].push_back(dptr); managementObjects.erase(iter->first); - delete iter->second; } return !deleteList.empty(); } +ManagementAgent::EventQueue::Batch::const_iterator ManagementAgent::sendEvents( + const EventQueue::Batch& batch) +{ + EventQueue::Batch::const_iterator i; + for (i = batch.begin(); i != batch.end(); ++i) { + DeliverableMessage deliverable (i->second, 0); + try { + i->first->route(deliverable); + } catch(exception& e) { + QPID_LOG(warning, "ManagementAgent failed to route event: " << e.what()); + } + } + return i; +} + namespace { QPID_TSS const qpid::broker::ConnectionState* executionContext = 0; } diff --git a/cpp/src/qpid/management/ManagementAgent.h b/cpp/src/qpid/management/ManagementAgent.h index c7e830dcf5..6de5d1d719 100644 --- a/cpp/src/qpid/management/ManagementAgent.h +++ b/cpp/src/qpid/management/ManagementAgent.h @@ -26,7 +26,6 @@ #include "qpid/broker/Exchange.h" #include "qpid/framing/Uuid.h" #include "qpid/sys/Mutex.h" -#include "qpid/sys/Timer.h" #include "qpid/broker/ConnectionToken.h" #include "qpid/management/ManagementObject.h" #include "qpid/management/ManagementEvent.h" @@ -34,9 +33,11 @@ #include "qmf/org/apache/qpid/broker/Agent.h" #include "qmf/org/apache/qpid/broker/Memory.h" #include "qpid/sys/MemStat.h" +#include "qpid/sys/PollableQueue.h" #include "qpid/types/Variant.h" #include <qpid/framing/AMQFrame.h> #include <qpid/framing/ResizableBuffer.h> +#include <boost/shared_ptr.hpp> #include <memory> #include <string> #include <map> @@ -45,6 +46,9 @@ namespace qpid { namespace broker { class ConnectionState; } +namespace sys { +class Timer; +} namespace management { class ManagementAgent @@ -73,11 +77,6 @@ public: /** Called before plugins are initialized */ void configure (const std::string& dataDir, bool publish, uint16_t interval, qpid::broker::Broker* broker, int threadPoolSize); - /** Called after plugins are initialized. */ - void pluginsInitialized(); - - /** Called by cluster to suppress management output during update. */ - void suppress(bool s) { suppressed = s; } void setName(const std::string& vendor, const std::string& product, @@ -100,18 +99,16 @@ public: const std::string& eventName, uint8_t* md5Sum, ManagementObject::writeSchemaCall_t schemaCall); - QPID_BROKER_EXTERN ObjectId addObject (ManagementObject* object, - uint64_t persistId = 0, - bool persistent = false); - QPID_BROKER_EXTERN ObjectId addObject (ManagementObject* object, - const std::string& key, - bool persistent = false); + QPID_BROKER_EXTERN ObjectId addObject (ManagementObject::shared_ptr object, + uint64_t persistId = 0, + bool persistent = false); + QPID_BROKER_EXTERN ObjectId addObject (ManagementObject::shared_ptr object, + const std::string& key, + bool persistent = false); QPID_BROKER_EXTERN void raiseEvent(const ManagementEvent& event, severity_t severity = SEV_DEFAULT); QPID_BROKER_EXTERN void clientAdded (const std::string& routingKey); - QPID_BROKER_EXTERN void clusterUpdate(); - bool dispatchCommand (qpid::broker::Deliverable& msg, const std::string& routingKey, const framing::FieldTable* args, @@ -121,25 +118,6 @@ public: /** Disallow a method. Attempts to call it will receive an exception with message. */ void disallow(const std::string& className, const std::string& methodName, const std::string& message); - /** Disallow all QMFv1 methods (used in clustered brokers). */ - void disallowV1Methods() { disallowAllV1Methods = true; } - - /** Serialize my schemas as a binary blob into schemaOut */ - void exportSchemas(std::string& schemaOut); - - /** Serialize my remote-agent map as a binary blob into agentsOut */ - void exportAgents(std::string& agentsOut); - - /** Decode a serialized schemas and add to my schema cache */ - void importSchemas(framing::Buffer& inBuf); - - /** Decode a serialized agent map */ - void importAgents(framing::Buffer& inBuf); - - // these are in support of the managementSetup-state stuff, for synch'ing clustered brokers - uint64_t getNextObjectId(void) { return nextObjectId; } - void setNextObjectId(uint64_t o) { nextObjectId = o; } - uint16_t getBootSequence(void) { return bootSequence; } void setBootSequence(uint16_t b) { bootSequence = b; writeData(); } @@ -148,20 +126,11 @@ public: static types::Variant::Map toMap(const framing::FieldTable& from); - // For Clustering: management objects that have been marked as - // "deleted", but are waiting for their last published object - // update are not visible to the cluster replication code. These - // interfaces allow clustering to gather up all the management - // objects that are deleted in order to allow all clustered - // brokers to publish the same set of deleted objects. - class DeletedObject { public: typedef boost::shared_ptr<DeletedObject> shared_ptr; - DeletedObject(ManagementObject *, bool v1, bool v2); - DeletedObject( const std::string &encoded ); + DeletedObject(ManagementObject::shared_ptr, bool v1, bool v2); ~DeletedObject() {}; - void encode( std::string& toBuffer ); const std::string getKey() const { // used to batch up objects of the same class type return std::string(packageName + std::string(":") + className); @@ -181,22 +150,7 @@ public: typedef std::vector<DeletedObject::shared_ptr> DeletedObjectList; - /** returns a snapshot of all currently deleted management objects. */ - void exportDeletedObjects( DeletedObjectList& outList ); - - /** Import a list of deleted objects to send on next publish interval. */ - void importDeletedObjects( const DeletedObjectList& inList ); - private: - struct Periodic : public qpid::sys::TimerTask - { - ManagementAgent& agent; - - Periodic (ManagementAgent& agent, uint32_t seconds); - virtual ~Periodic (); - void fire (); - }; - // Storage for tracking remote management agents, attached via the client // management agent API. // @@ -207,9 +161,9 @@ private: uint32_t agentBank; std::string routingKey; ObjectId connectionRef; - qmf::org::apache::qpid::broker::Agent* mgmtObject; - RemoteAgent(ManagementAgent& _agent) : agent(_agent), mgmtObject(0) {} - ManagementObject* GetManagementObject (void) const { return mgmtObject; } + qmf::org::apache::qpid::broker::Agent::shared_ptr mgmtObject; + RemoteAgent(ManagementAgent& _agent) : agent(_agent) {} + ManagementObject::shared_ptr GetManagementObject (void) const { return mgmtObject; } virtual ~RemoteAgent (); void mapEncode(qpid::types::Variant::Map& _map) const; @@ -276,7 +230,7 @@ private: PackageMap packages; // - // Protected by userLock + // Protected by objectLock // ManagementObjectMap managementObjects; @@ -288,11 +242,11 @@ private: framing::Uuid uuid; // - // Lock hierarchy: If a thread needs to take both addLock and userLock, - // it MUST take userLock first, then addLock. + // Lock ordering: userLock -> addLock -> objectLock // sys::Mutex userLock; sys::Mutex addLock; + sys::Mutex objectLock; qpid::broker::Exchange::shared_ptr mExchange; qpid::broker::Exchange::shared_ptr dExchange; @@ -335,53 +289,51 @@ private: // list of objects that have been deleted, but have yet to be published // one final time. // Indexed by a string composed of the object's package and class name. - // Protected by userLock. + // Protected by objectLock. typedef std::map<std::string, DeletedObjectList> PendingDeletedObjsMap; PendingDeletedObjsMap pendingDeletedObjs; -# define MA_BUFFER_SIZE 65536 - char inputBuffer[MA_BUFFER_SIZE]; - char outputBuffer[MA_BUFFER_SIZE]; - char eventBuffer[MA_BUFFER_SIZE]; - framing::ResizableBuffer msgBuffer; + // Pollable queue to serialize event messages + typedef std::pair<boost::shared_ptr<broker::Exchange>, + broker::Message> ExchangeAndMessage; + typedef sys::PollableQueue<ExchangeAndMessage> EventQueue; // // Memory statistics object // - qmf::org::apache::qpid::broker::Memory *memstat; + qmf::org::apache::qpid::broker::Memory::shared_ptr memstat; void writeData (); void periodicProcessing (void); - void deleteObjectNowLH(const ObjectId& oid); + void deleteObjectNow(const ObjectId& oid); void encodeHeader (framing::Buffer& buf, uint8_t opcode, uint32_t seq = 0); bool checkHeader (framing::Buffer& buf, uint8_t *opcode, uint32_t *seq); - void sendBufferLH(framing::Buffer& buf, - uint32_t length, - qpid::broker::Exchange::shared_ptr exchange, - const std::string& routingKey); - void sendBufferLH(framing::Buffer& buf, - uint32_t length, - const std::string& exchange, - const std::string& routingKey); - void sendBufferLH(const std::string& data, - const std::string& cid, - const qpid::types::Variant::Map& headers, - const std::string& content_type, - qpid::broker::Exchange::shared_ptr exchange, - const std::string& routingKey, - uint64_t ttl_msec = 0); - void sendBufferLH(const std::string& data, - const std::string& cid, - const qpid::types::Variant::Map& headers, - const std::string& content_type, - const std::string& exchange, - const std::string& routingKey, - uint64_t ttl_msec = 0); - void moveNewObjectsLH(); - bool moveDeletedObjectsLH(); - - bool authorizeAgentMessageLH(qpid::broker::Message& msg); - void dispatchAgentCommandLH(qpid::broker::Message& msg, bool viaLocal=false); + EventQueue::Batch::const_iterator sendEvents(const EventQueue::Batch& batch); + void sendBuffer(framing::Buffer& buf, + qpid::broker::Exchange::shared_ptr exchange, + const std::string& routingKey); + void sendBuffer(framing::Buffer& buf, + const std::string& exchange, + const std::string& routingKey); + void sendBuffer(const std::string& data, + const std::string& cid, + const qpid::types::Variant::Map& headers, + const std::string& content_type, + qpid::broker::Exchange::shared_ptr exchange, + const std::string& routingKey, + uint64_t ttl_msec = 0); + void sendBuffer(const std::string& data, + const std::string& cid, + const qpid::types::Variant::Map& headers, + const std::string& content_type, + const std::string& exchange, + const std::string& routingKey, + uint64_t ttl_msec = 0); + void moveNewObjects(); + bool moveDeletedObjects(); + + bool authorizeAgentMessage(qpid::broker::Message& msg); + void dispatchAgentCommand(qpid::broker::Message& msg, bool viaLocal=false); PackageMap::iterator findOrAddPackageLH(std::string name); void addClassLH(uint8_t kind, @@ -399,22 +351,22 @@ private: uint32_t allocateNewBank (); uint32_t assignBankLH (uint32_t requestedPrefix); void deleteOrphanedAgentsLH(); - void sendCommandCompleteLH(const std::string& replyToKey, uint32_t sequence, - uint32_t code = 0, const std::string& text = "OK"); - void sendExceptionLH(const std::string& rte, const std::string& rtk, const std::string& cid, const std::string& text, uint32_t code=1, bool viaLocal=false); - void handleBrokerRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); - void handlePackageQueryLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); - void handlePackageIndLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); - void handleClassQueryLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); - void handleClassIndLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); - void handleSchemaRequestLH (framing::Buffer& inBuffer, const std::string& replyToEx, const std::string& replyToKey, uint32_t sequence); - void handleSchemaResponseLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); - void handleAttachRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken); - void handleGetQueryLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); - void handleMethodRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken); - void handleGetQueryLH (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, bool viaLocal); - void handleMethodRequestLH (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, const qpid::broker::ConnectionToken* connToken, bool viaLocal); - void handleLocateRequestLH (const std::string& body, const std::string& replyToEx, const std::string &replyToKey, const std::string& cid); + void sendCommandComplete(const std::string& replyToKey, uint32_t sequence, + uint32_t code = 0, const std::string& text = "OK"); + void sendException(const std::string& rte, const std::string& rtk, const std::string& cid, const std::string& text, uint32_t code=1, bool viaLocal=false); + void handleBrokerRequest (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handlePackageQuery (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handlePackageInd (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleClassQuery (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleClassInd (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleSchemaRequest (framing::Buffer& inBuffer, const std::string& replyToEx, const std::string& replyToKey, uint32_t sequence); + void handleSchemaResponse (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleAttachRequest (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken); + void handleGetQuery (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleMethodRequest (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken); + void handleGetQuery (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, bool viaLocal); + void handleMethodRequest (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, const qpid::broker::ConnectionToken* connToken, bool viaLocal); + void handleLocateRequest (const std::string& body, const std::string& replyToEx, const std::string &replyToKey, const std::string& cid); size_t validateSchema(framing::Buffer&, uint8_t kind); @@ -424,6 +376,7 @@ private: std::string summarizeAgents(); void debugSnapshot(const char* title); + std::auto_ptr<EventQueue> sendQueue; }; void setManagementExecutionContext(const qpid::broker::ConnectionState*); diff --git a/cpp/src/qpid/messaging/Connection.cpp b/cpp/src/qpid/messaging/Connection.cpp index bd90aa54a7..fde931038b 100644 --- a/cpp/src/qpid/messaging/Connection.cpp +++ b/cpp/src/qpid/messaging/Connection.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -24,6 +24,7 @@ #include "qpid/messaging/Session.h" #include "qpid/messaging/SessionImpl.h" #include "qpid/messaging/PrivateImplRef.h" +#include "qpid/messaging/ProtocolRegistry.h" #include "qpid/client/amqp0_10/ConnectionImpl.h" #include "qpid/log/Statement.h" @@ -40,22 +41,32 @@ Connection& Connection::operator=(const Connection& c) { return PI::assign(*this Connection::~Connection() { PI::dtor(*this); } Connection::Connection(const std::string& url, const std::string& o) -{ +{ Variant::Map options; AddressParser parser(o); if (o.empty() || parser.parseMap(options)) { - PI::ctor(*this, new qpid::client::amqp0_10::ConnectionImpl(url, options)); + ConnectionImpl* impl = ProtocolRegistry::create(url, options); + if (impl) { + PI::ctor(*this, impl); + } else { + PI::ctor(*this, new qpid::client::amqp0_10::ConnectionImpl(url, options)); + } } else { throw InvalidOptionString("Invalid option string: " + o); } } Connection::Connection(const std::string& url, const Variant::Map& options) { - PI::ctor(*this, new qpid::client::amqp0_10::ConnectionImpl(url, options)); + ConnectionImpl* impl = ProtocolRegistry::create(url, options); + if (impl) { + PI::ctor(*this, impl); + } else { + PI::ctor(*this, new qpid::client::amqp0_10::ConnectionImpl(url, options)); + } } Connection::Connection() -{ +{ Variant::Map options; std::string url = "amqp:tcp:127.0.0.1:5672"; PI::ctor(*this, new qpid::client::amqp0_10::ConnectionImpl(url, options)); @@ -67,12 +78,12 @@ bool Connection::isOpen() const { return impl->isOpen(); } void Connection::close() { impl->close(); } Session Connection::createSession(const std::string& name) { return impl->newSession(false, name); } Session Connection::createTransactionalSession(const std::string& name) -{ +{ return impl->newSession(true, name); } Session Connection::getSession(const std::string& name) const { return impl->getSession(name); } void Connection::setOption(const std::string& name, const Variant& value) -{ +{ impl->setOption(name, value); } std::string Connection::getAuthenticatedUsername() diff --git a/cpp/src/qpid/messaging/ConnectionOptions.cpp b/cpp/src/qpid/messaging/ConnectionOptions.cpp new file mode 100644 index 0000000000..ecd5ba9693 --- /dev/null +++ b/cpp/src/qpid/messaging/ConnectionOptions.cpp @@ -0,0 +1,121 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/ConnectionOptions.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/types/Variant.h" +#include "qpid/log/Statement.h" +#include <algorithm> +#include <limits> + +namespace qpid { +namespace messaging { + +namespace { +double FOREVER(std::numeric_limits<double>::max()); + +double timeValue(const qpid::types::Variant& value) { + if (types::isIntegerType(value.getType())) + return double(value.asInt64()); + return value.asDouble(); +} + +void merge(const std::string& value, std::vector<std::string>& list) { + if (std::find(list.begin(), list.end(), value) == list.end()) + list.push_back(value); +} + +void merge(const qpid::types::Variant::List& from, std::vector<std::string>& to) +{ + for (qpid::types::Variant::List::const_iterator i = from.begin(); i != from.end(); ++i) + merge(i->asString(), to); +} + +} + +ConnectionOptions::ConnectionOptions(const std::map<std::string, qpid::types::Variant>& options) + : replaceUrls(false), reconnect(false), timeout(FOREVER), limit(-1), minReconnectInterval(0.001), maxReconnectInterval(2), + retries(0), reconnectOnLimitExceeded(true) +{ + for (qpid::types::Variant::Map::const_iterator i = options.begin(); i != options.end(); ++i) { + set(i->first, i->second); + } +} + +void ConnectionOptions::set(const std::string& name, const qpid::types::Variant& value) +{ + if (name == "reconnect") { + reconnect = value; + } else if (name == "reconnect-timeout" || name == "reconnect_timeout") { + timeout = timeValue(value); + } else if (name == "reconnect-limit" || name == "reconnect_limit") { + limit = value; + } else if (name == "reconnect-interval" || name == "reconnect_interval") { + maxReconnectInterval = minReconnectInterval = timeValue(value); + } else if (name == "reconnect-interval-min" || name == "reconnect_interval_min") { + minReconnectInterval = timeValue(value); + } else if (name == "reconnect-interval-max" || name == "reconnect_interval_max") { + maxReconnectInterval = timeValue(value); + } else if (name == "reconnect-urls-replace" || name == "reconnect_urls_replace") { + replaceUrls = value.asBool(); + } else if (name == "reconnect-urls" || name == "reconnect_urls") { + if (replaceUrls) urls.clear(); + if (value.getType() == qpid::types::VAR_LIST) { + merge(value.asList(), urls); + } else { + merge(value.asString(), urls); + } + } else if (name == "username") { + username = value.asString(); + } else if (name == "password") { + password = value.asString(); + } else if (name == "sasl-mechanism" || name == "sasl_mechanism" || + name == "sasl-mechanisms" || name == "sasl_mechanisms") { + mechanism = value.asString(); + } else if (name == "sasl-service" || name == "sasl_service") { + service = value.asString(); + } else if (name == "sasl-min-ssf" || name == "sasl_min_ssf") { + minSsf = value; + } else if (name == "sasl-max-ssf" || name == "sasl_max_ssf") { + maxSsf = value; + } else if (name == "heartbeat") { + heartbeat = value; + } else if (name == "tcp-nodelay" || name == "tcp_nodelay") { + tcpNoDelay = value; + } else if (name == "locale") { + locale = value.asString(); + } else if (name == "max-channels" || name == "max_channels") { + maxChannels = value; + } else if (name == "max-frame-size" || name == "max_frame_size") { + maxFrameSize = value; + } else if (name == "bounds") { + bounds = value; + } else if (name == "transport") { + protocol = value.asString(); + } else if (name == "ssl-cert-name" || name == "ssl_cert_name") { + sslCertName = value.asString(); + } else if (name == "x-reconnect-on-limit-exceeded" || name == "x_reconnect_on_limit_exceeded") { + reconnectOnLimitExceeded = value; + } else { + throw qpid::messaging::MessagingException(QPID_MSG("Invalid option: " << name << " not recognised")); + } +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/ConnectionOptions.h b/cpp/src/qpid/messaging/ConnectionOptions.h new file mode 100644 index 0000000000..6786fd4a64 --- /dev/null +++ b/cpp/src/qpid/messaging/ConnectionOptions.h @@ -0,0 +1,51 @@ +#ifndef QPID_MESSAGING_CONNECTIONOPTIONS_H +#define QPID_MESSAGING_CONNECTIONOPTIONS_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/client/ConnectionSettings.h" +#include <map> +#include <vector> + +namespace qpid { +namespace types { +class Variant; +} +namespace messaging { + +struct ConnectionOptions : qpid::client::ConnectionSettings +{ + std::vector<std::string> urls; + bool replaceUrls; + bool reconnect; + double timeout; + int32_t limit; + double minReconnectInterval; + double maxReconnectInterval; + int32_t retries; + bool reconnectOnLimitExceeded; + + ConnectionOptions(const std::map<std::string, qpid::types::Variant>&); + void set(const std::string& name, const qpid::types::Variant& value); +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_CONNECTIONOPTIONS_H*/ diff --git a/cpp/src/qpid/messaging/Message.cpp b/cpp/src/qpid/messaging/Message.cpp index ef70c103e9..0f03bc8ca3 100644 --- a/cpp/src/qpid/messaging/Message.cpp +++ b/cpp/src/qpid/messaging/Message.cpp @@ -46,26 +46,26 @@ const std::string& Message::getSubject() const { return impl->getSubject(); } void Message::setContentType(const std::string& s) { impl->setContentType(s); } const std::string& Message::getContentType() const { return impl->getContentType(); } -void Message::setMessageId(const std::string& id) { impl->messageId = id; } -const std::string& Message::getMessageId() const { return impl->messageId; } +void Message::setMessageId(const std::string& id) { impl->setMessageId(id); } +const std::string& Message::getMessageId() const { return impl->getMessageId(); } -void Message::setUserId(const std::string& id) { impl->userId = id; } -const std::string& Message::getUserId() const { return impl->userId; } +void Message::setUserId(const std::string& id) { impl->setUserId(id); } +const std::string& Message::getUserId() const { return impl->getUserId(); } -void Message::setCorrelationId(const std::string& id) { impl->correlationId = id; } -const std::string& Message::getCorrelationId() const { return impl->correlationId; } +void Message::setCorrelationId(const std::string& id) { impl->setCorrelationId(id); } +const std::string& Message::getCorrelationId() const { return impl->getCorrelationId(); } -uint8_t Message::getPriority() const { return impl->priority; } -void Message::setPriority(uint8_t priority) { impl->priority = priority; } +uint8_t Message::getPriority() const { return impl->getPriority(); } +void Message::setPriority(uint8_t priority) { impl->setPriority(priority); } -void Message::setTtl(Duration ttl) { impl->ttl = ttl.getMilliseconds(); } -Duration Message::getTtl() const { return Duration(impl->ttl); } +void Message::setTtl(Duration ttl) { impl->setTtl(ttl.getMilliseconds()); } +Duration Message::getTtl() const { return Duration(impl->getTtl()); } -void Message::setDurable(bool durable) { impl->durable = durable; } -bool Message::getDurable() const { return impl->durable; } +void Message::setDurable(bool durable) { impl->setDurable(durable); } +bool Message::getDurable() const { return impl->isDurable(); } -bool Message::getRedelivered() const { return impl->redelivered; } -void Message::setRedelivered(bool redelivered) { impl->redelivered = redelivered; } +bool Message::getRedelivered() const { return impl->isRedelivered(); } +void Message::setRedelivered(bool redelivered) { impl->setRedelivered(redelivered); } const Variant::Map& Message::getProperties() const { return impl->getHeaders(); } Variant::Map& Message::getProperties() { return impl->getHeaders(); } diff --git a/cpp/src/qpid/messaging/MessageImpl.cpp b/cpp/src/qpid/messaging/MessageImpl.cpp index 0601800e46..fc9bc5dfa1 100644 --- a/cpp/src/qpid/messaging/MessageImpl.cpp +++ b/cpp/src/qpid/messaging/MessageImpl.cpp @@ -45,28 +45,163 @@ MessageImpl::MessageImpl(const char* chars, size_t count) : bytes(chars, count), internalId(0) {} -void MessageImpl::setReplyTo(const Address& d) { replyTo = d; } -const Address& MessageImpl::getReplyTo() const { return replyTo; } +void MessageImpl::setReplyTo(const Address& d) +{ + replyTo = d; + updated(); +} +const Address& MessageImpl::getReplyTo() const +{ + if (!replyTo && encoded) encoded->getReplyTo(replyTo); + return replyTo; +} -void MessageImpl::setSubject(const std::string& s) { subject = s; } -const std::string& MessageImpl::getSubject() const { return subject; } +void MessageImpl::setSubject(const std::string& s) +{ + subject = s; + updated(); +} +const std::string& MessageImpl::getSubject() const +{ + if (!subject.size() && encoded) encoded->getSubject(subject); + return subject; +} -void MessageImpl::setContentType(const std::string& s) { contentType = s; } -const std::string& MessageImpl::getContentType() const { return contentType; } +void MessageImpl::setContentType(const std::string& s) +{ + contentType = s; + updated(); +} +const std::string& MessageImpl::getContentType() const +{ + if (!contentType.size() && encoded) encoded->getContentType(contentType); + return contentType; +} -const Variant::Map& MessageImpl::getHeaders() const { return headers; } -Variant::Map& MessageImpl::getHeaders() { return headers; } -void MessageImpl::setHeader(const std::string& key, const qpid::types::Variant& val) { headers[key] = val; } +void MessageImpl::setMessageId(const std::string& s) +{ + messageId = s; + updated(); +} +const std::string& MessageImpl::getMessageId() const +{ + if (!messageId.size() && encoded) encoded->getMessageId(messageId); + return messageId; +} +void MessageImpl::setUserId(const std::string& s) +{ + userId = s; + updated(); +} +const std::string& MessageImpl::getUserId() const +{ + if (!userId.size() && encoded) encoded->getUserId(userId); + return userId; +} +void MessageImpl::setCorrelationId(const std::string& s) +{ + correlationId = s; + updated(); +} +const std::string& MessageImpl::getCorrelationId() const +{ + if (!correlationId.size() && encoded) encoded->getCorrelationId(correlationId); + return correlationId; +} +void MessageImpl::setPriority(uint8_t p) +{ + priority = p; +} +uint8_t MessageImpl::getPriority() const +{ + return priority; +} +void MessageImpl::setTtl(uint64_t t) +{ + ttl = t; +} +uint64_t MessageImpl::getTtl() const +{ + return ttl; +} +void MessageImpl::setDurable(bool d) +{ + durable = d; +} +bool MessageImpl::isDurable() const +{ + return durable; +} +void MessageImpl::setRedelivered(bool b) +{ + redelivered = b; +} +bool MessageImpl::isRedelivered() const +{ + return redelivered; +} + +const Variant::Map& MessageImpl::getHeaders() const +{ + if (!headers.size() && encoded) encoded->populate(headers); + return headers; +} +Variant::Map& MessageImpl::getHeaders() { + if (!headers.size() && encoded) encoded->populate(headers); + updated(); + return headers; +} +void MessageImpl::setHeader(const std::string& key, const qpid::types::Variant& val) +{ + headers[key] = val; updated(); +} //should these methods be on MessageContent? -void MessageImpl::setBytes(const std::string& c) { bytes = c; } -void MessageImpl::setBytes(const char* chars, size_t count) { bytes.assign(chars, count); } -const std::string& MessageImpl::getBytes() const { return bytes; } -std::string& MessageImpl::getBytes() { return bytes; } +void MessageImpl::setBytes(const std::string& c) +{ + bytes = c; + updated(); +} +void MessageImpl::setBytes(const char* chars, size_t count) +{ + bytes.assign(chars, count); + updated(); +} +void MessageImpl::appendBytes(const char* chars, size_t count) +{ + bytes.append(chars, count); + updated(); +} +const std::string& MessageImpl::getBytes() const +{ + if (!bytes.size() && encoded) encoded->getBody(bytes); + return bytes; +} +std::string& MessageImpl::getBytes() +{ + if (!bytes.size() && encoded) encoded->getBody(bytes); + updated();//have to assume body may be edited, invalidating our message + return bytes; +} void MessageImpl::setInternalId(qpid::framing::SequenceNumber i) { internalId = i; } qpid::framing::SequenceNumber MessageImpl::getInternalId() { return internalId; } +void MessageImpl::updated() +{ + + if (!replyTo && encoded) encoded->getReplyTo(replyTo); + if (!subject.size() && encoded) encoded->getSubject(subject); + if (!contentType.size() && encoded) encoded->getContentType(contentType); + if (!messageId.size() && encoded) encoded->getMessageId(messageId); + if (!userId.size() && encoded) encoded->getUserId(userId); + if (!correlationId.size() && encoded) encoded->getCorrelationId(correlationId); + if (!headers.size() && encoded) encoded->populate(headers); + if (!bytes.size() && encoded) encoded->getBody(bytes); + + encoded.reset(); +} + MessageImpl& MessageImplAccess::get(Message& msg) { return *msg.impl; diff --git a/cpp/src/qpid/messaging/MessageImpl.h b/cpp/src/qpid/messaging/MessageImpl.h index 57df6b3fda..915c790153 100644 --- a/cpp/src/qpid/messaging/MessageImpl.h +++ b/cpp/src/qpid/messaging/MessageImpl.h @@ -24,52 +24,77 @@ #include "qpid/messaging/Address.h" #include "qpid/types/Variant.h" #include "qpid/framing/SequenceNumber.h" +#include "qpid/messaging/amqp/EncodedMessage.h" +#include <vector> +#include <boost/shared_ptr.hpp> namespace qpid { namespace messaging { -struct MessageImpl +class MessageImpl { - Address replyTo; - std::string subject; - std::string contentType; - std::string messageId; - std::string userId; - std::string correlationId; + private: + mutable Address replyTo; + mutable std::string subject; + mutable std::string contentType; + mutable std::string messageId; + mutable std::string userId; + mutable std::string correlationId; uint8_t priority; uint64_t ttl; bool durable; bool redelivered; - qpid::types::Variant::Map headers; + mutable qpid::types::Variant::Map headers; - std::string bytes; + mutable std::string bytes; + boost::shared_ptr<const qpid::messaging::amqp::EncodedMessage> encoded; qpid::framing::SequenceNumber internalId; + void updated(); + public: MessageImpl(const std::string& c); MessageImpl(const char* chars, size_t count); void setReplyTo(const Address& d); const Address& getReplyTo() const; - + void setSubject(const std::string& s); const std::string& getSubject() const; - + void setContentType(const std::string& s); const std::string& getContentType() const; - + + void setMessageId(const std::string&); + const std::string& getMessageId() const; + void setUserId(const std::string& ); + const std::string& getUserId() const; + void setCorrelationId(const std::string& ); + const std::string& getCorrelationId() const; + void setPriority(uint8_t); + uint8_t getPriority() const; + void setTtl(uint64_t); + uint64_t getTtl() const; + void setDurable(bool); + bool isDurable() const; + void setRedelivered(bool); + bool isRedelivered() const; + + const qpid::types::Variant::Map& getHeaders() const; qpid::types::Variant::Map& getHeaders(); void setHeader(const std::string& key, const qpid::types::Variant& val); - + void setBytes(const std::string& bytes); void setBytes(const char* chars, size_t count); + void appendBytes(const char* chars, size_t count); const std::string& getBytes() const; std::string& getBytes(); void setInternalId(qpid::framing::SequenceNumber id); qpid::framing::SequenceNumber getInternalId(); - + void setEncoded(boost::shared_ptr<const qpid::messaging::amqp::EncodedMessage> e) { encoded = e; } + boost::shared_ptr<const qpid::messaging::amqp::EncodedMessage> getEncoded() const { return encoded; } }; class Message; diff --git a/cpp/src/qpid/messaging/ProtocolRegistry.cpp b/cpp/src/qpid/messaging/ProtocolRegistry.cpp new file mode 100644 index 0000000000..0232da8ae1 --- /dev/null +++ b/cpp/src/qpid/messaging/ProtocolRegistry.cpp @@ -0,0 +1,73 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "ProtocolRegistry.h" +#include "qpid/Exception.h" +#include "qpid/client/amqp0_10/ConnectionImpl.h" +#include "qpid/client/LoadPlugins.h" +#include <map> + +using qpid::types::Variant; + +namespace qpid { +namespace messaging { +namespace { +typedef std::map<std::string, ProtocolRegistry::Factory*> Registry; + +Registry& theRegistry() +{ + static Registry factories; + return factories; +} + +bool extract(const std::string& key, Variant& value, const Variant::Map& in, Variant::Map& out) +{ + bool matched = false; + for (Variant::Map::const_iterator i = in.begin(); i != in.end(); ++i) { + if (i->first == key) { + value = i->second; + matched = true; + } else { + out.insert(*i); + } + } + return matched; +} +} + +ConnectionImpl* ProtocolRegistry::create(const std::string& url, const Variant::Map& options) +{ + qpid::client::theModuleLoader();//ensure modules are loaded + Variant name; + Variant::Map stripped; + if (extract("protocol", name, options, stripped)) { + Registry::const_iterator i = theRegistry().find(name.asString()); + if (i != theRegistry().end()) return (i->second)(url, stripped); + else if (name.asString() == "amqp0-10") return new qpid::client::amqp0_10::ConnectionImpl(url, stripped); + else throw qpid::Exception("Unsupported protocol: " + name.asString()); + } + return 0; +} +void ProtocolRegistry::add(const std::string& name, Factory* factory) +{ + theRegistry()[name] = factory; +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/ProtocolRegistry.h b/cpp/src/qpid/messaging/ProtocolRegistry.h new file mode 100644 index 0000000000..bcb62248a5 --- /dev/null +++ b/cpp/src/qpid/messaging/ProtocolRegistry.h @@ -0,0 +1,42 @@ +#ifndef QPID_MESSAGING_PROTOCOLREGISTRY_H +#define QPID_MESSAGING_PROTOCOLREGISTRY_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/types/Variant.h" + +namespace qpid { +namespace messaging { +class ConnectionImpl; +/** + * Registry for different implementations of the messaging API e.g AMQP 1.0 + */ +class ProtocolRegistry +{ + public: + typedef ConnectionImpl* Factory(const std::string& url, const qpid::types::Variant::Map& options); + static ConnectionImpl* create(const std::string& url, const qpid::types::Variant::Map& options); + static void add(const std::string& name, Factory* factory); + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_PROTOCOLREGISTRY_H*/ diff --git a/cpp/src/qpid/messaging/ReceiverImpl.h b/cpp/src/qpid/messaging/ReceiverImpl.h index 57059bfd28..e450693d2c 100644 --- a/cpp/src/qpid/messaging/ReceiverImpl.h +++ b/cpp/src/qpid/messaging/ReceiverImpl.h @@ -22,10 +22,12 @@ * */ #include "qpid/RefCounted.h" +#include "qpid/sys/IntegerTypes.h" namespace qpid { namespace messaging { +class Duration; class Message; class MessageListener; class Session; diff --git a/cpp/src/qpid/messaging/SenderImpl.h b/cpp/src/qpid/messaging/SenderImpl.h index a1ca02c72c..d978463fdb 100644 --- a/cpp/src/qpid/messaging/SenderImpl.h +++ b/cpp/src/qpid/messaging/SenderImpl.h @@ -22,6 +22,7 @@ * */ #include "qpid/RefCounted.h" +#include "qpid/sys/IntegerTypes.h" namespace qpid { namespace messaging { diff --git a/cpp/src/qpid/messaging/amqp/AddressHelper.cpp b/cpp/src/qpid/messaging/amqp/AddressHelper.cpp new file mode 100644 index 0000000000..359660dce5 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/AddressHelper.cpp @@ -0,0 +1,182 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/amqp/AddressHelper.h" +#include "qpid/messaging/Address.h" +#include <vector> +#include <boost/assign.hpp> +extern "C" { +#include <proton/engine.h> +} + +namespace qpid { +namespace messaging { +namespace amqp { + +using qpid::types::Variant; + +namespace { +//policy types +const std::string CREATE("create"); +const std::string ASSERT("assert"); +const std::string DELETE("delete"); + +//policy values +const std::string ALWAYS("always"); +const std::string NEVER("never"); +const std::string RECEIVER("receiver"); +const std::string SENDER("sender"); + +const std::string NODE("node"); +const std::string LINK("link"); + +const std::string TYPE("type"); +const std::string TOPIC("topic"); +const std::string QUEUE("queue"); + +//distribution modes: +const std::string MOVE("move"); +const std::string COPY("copy"); + +const std::string SUPPORTED_DIST_MODES("supported-dist-modes"); + + +const std::vector<std::string> RECEIVER_MODES = boost::assign::list_of<std::string>(ALWAYS) (RECEIVER); +const std::vector<std::string> SENDER_MODES = boost::assign::list_of<std::string>(ALWAYS) (SENDER); + +pn_bytes_t convert(const std::string& s) +{ + pn_bytes_t result; + result.start = const_cast<char*>(s.data()); + result.size = s.size(); + return result; +} + +bool bind(const Variant::Map& options, const std::string& name, std::string& variable) +{ + Variant::Map::const_iterator j = options.find(name); + if (j == options.end()) { + return false; + } else { + variable = j->second.asString(); + return true; + } +} + +bool bind(const Variant::Map& options, const std::string& name, Variant::Map& variable) +{ + Variant::Map::const_iterator j = options.find(name); + if (j == options.end()) { + return false; + } else { + variable = j->second.asMap(); + return true; + } +} + +bool bind(const Address& address, const std::string& name, std::string& variable) +{ + return bind(address.getOptions(), name, variable); +} + +bool bind(const Address& address, const std::string& name, Variant::Map& variable) +{ + return bind(address.getOptions(), name, variable); +} + +bool in(const std::string& value, const std::vector<std::string>& choices) +{ + for (std::vector<std::string>::const_iterator i = choices.begin(); i != choices.end(); ++i) { + if (value == *i) return true; + } + return false; +} +} + +AddressHelper::AddressHelper(const Address& address) +{ + bind(address, CREATE, createPolicy); + bind(address, DELETE, deletePolicy); + bind(address, ASSERT, assertPolicy); + + bind(address, NODE, node); + bind(address, LINK, link); +} + +bool AddressHelper::createEnabled(CheckMode mode) const +{ + return enabled(createPolicy, mode); +} +bool AddressHelper::deleteEnabled(CheckMode mode) const +{ + return enabled(deletePolicy, mode); +} +bool AddressHelper::assertEnabled(CheckMode mode) const +{ + return enabled(assertPolicy, mode); +} +bool AddressHelper::enabled(const std::string& policy, CheckMode mode) const +{ + bool result = false; + switch (mode) { + case FOR_RECEIVER: + result = in(policy, RECEIVER_MODES); + break; + case FOR_SENDER: + result = in(policy, SENDER_MODES); + break; + } + return result; +} + +const qpid::types::Variant::Map& AddressHelper::getNodeProperties() const +{ + return node; +} +const qpid::types::Variant::Map& AddressHelper::getLinkProperties() const +{ + return link; +} + +void AddressHelper::setNodeProperties(pn_terminus_t* terminus) +{ + pn_terminus_set_dynamic(terminus, true); + + //properties for dynamically created node: + pn_data_t* data = pn_terminus_properties(terminus); + if (node.size()) { + pn_data_put_map(data); + pn_data_enter(data); + } + for (qpid::types::Variant::Map::const_iterator i = node.begin(); i != node.end(); ++i) { + if (i->first == TYPE) { + pn_data_put_symbol(data, convert(SUPPORTED_DIST_MODES)); + pn_data_put_string(data, convert(i->second == TOPIC ? COPY : MOVE)); + } else { + pn_data_put_symbol(data, convert(i->first)); + pn_data_put_string(data, convert(i->second.asString())); + } + } + if (node.size()) { + pn_data_exit(data); + } +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/AddressHelper.h b/cpp/src/qpid/messaging/amqp/AddressHelper.h new file mode 100644 index 0000000000..cd0aa1be9e --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/AddressHelper.h @@ -0,0 +1,57 @@ +#ifndef QPID_MESSAGING_AMQP_ADDRESSHELPER_H +#define QPID_MESSAGING_AMQP_ADDRESSHELPER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/types/Variant.h" + +struct pn_terminus_t; + +namespace qpid { +namespace messaging { +class Address; +namespace amqp { + +class AddressHelper +{ + public: + enum CheckMode {FOR_RECEIVER, FOR_SENDER}; + + AddressHelper(const Address& address); + bool createEnabled(CheckMode mode) const; + bool deleteEnabled(CheckMode mode) const; + bool assertEnabled(CheckMode mode) const; + + void setNodeProperties(pn_terminus_t*); + const qpid::types::Variant::Map& getNodeProperties() const; + const qpid::types::Variant::Map& getLinkProperties() const; + private: + std::string createPolicy; + std::string assertPolicy; + std::string deletePolicy; + qpid::types::Variant::Map node; + qpid::types::Variant::Map link; + + bool enabled(const std::string& policy, CheckMode mode) const; +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_ADDRESSHELPER_H*/ diff --git a/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp b/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp new file mode 100644 index 0000000000..b2a9b979b6 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp @@ -0,0 +1,612 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "ConnectionContext.h" +#include "DriverImpl.h" +#include "ReceiverContext.h" +#include "Sasl.h" +#include "SenderContext.h" +#include "SessionContext.h" +#include "Transport.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/messaging/Duration.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/ProtocolInitiation.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include <vector> +extern "C" { +#include <proton/engine.h> +} + +namespace qpid { +namespace messaging { +namespace amqp { + + +ConnectionContext::ConnectionContext(const std::string& u, const qpid::types::Variant::Map& o) + : qpid::messaging::ConnectionOptions(o), + url(u), + engine(pn_transport()), + connection(pn_connection()), + //note: disabled read/write of header as now handled by engine + writeHeader(false), + readHeader(false), + haveOutput(false), + state(DISCONNECTED), + codecSwitch(*this) +{ + if (pn_transport_bind(engine, connection)) { + //error + } + pn_connection_set_container(connection, "qpid::messaging");//TODO: take this from a connection option + bool enableTrace(false); + QPID_LOG_TEST_CAT(trace, protocol, enableTrace); + if (enableTrace) pn_transport_trace(engine, PN_TRACE_FRM); +} + +ConnectionContext::~ConnectionContext() +{ + close(); + sessions.clear(); + pn_transport_free(engine); + pn_connection_free(connection); +} + +namespace { +const std::string COLON(":"); +} +void ConnectionContext::open() +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + if (state != DISCONNECTED) throw qpid::messaging::ConnectionError("Connection was already opened!"); + if (!driver) driver = DriverImpl::getDefault(); + + for (Url::const_iterator i = url.begin(); state != CONNECTED && i != url.end(); ++i) { + transport = driver->getTransport(i->protocol, *this); + std::stringstream port; + port << i->port; + id = i->host + COLON + port.str(); + if (useSasl()) { + sasl = std::auto_ptr<Sasl>(new Sasl(id, *this, i->host)); + } + state = CONNECTING; + try { + QPID_LOG(debug, id << " Connecting ..."); + transport->connect(i->host, port.str()); + } catch (const std::exception& e) { + QPID_LOG(info, id << " Error while connecting: " << e.what()); + } + while (state == CONNECTING) { + lock.wait(); + } + if (state == DISCONNECTED) { + QPID_LOG(debug, id << " Failed to connect"); + transport = boost::shared_ptr<Transport>(); + } else { + QPID_LOG(debug, id << " Connected"); + } + } + + if (state != CONNECTED) throw qpid::messaging::TransportFailure(QPID_MSG("Could not connect to " << url)); + + if (sasl.get()) { + wakeupDriver(); + while (!sasl->authenticated()) { + QPID_LOG(debug, id << " Waiting to be authenticated..."); + wait(); + } + QPID_LOG(debug, id << " Authenticated"); + } + + QPID_LOG(debug, id << " Opening..."); + pn_connection_open(connection); + wakeupDriver(); //want to write + while (pn_connection_state(connection) & PN_REMOTE_UNINIT) { + wait(); + } + if (!(pn_connection_state(connection) & PN_REMOTE_ACTIVE)) { + throw qpid::messaging::ConnectionError("Failed to open connection"); + } + QPID_LOG(debug, id << " Opened"); +} + +bool ConnectionContext::isOpen() const +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + return pn_connection_state(connection) & (PN_LOCAL_ACTIVE | PN_REMOTE_ACTIVE); +} + +void ConnectionContext::endSession(boost::shared_ptr<SessionContext> ssn) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + pn_session_close(ssn->session); + //TODO: need to destroy session and remove context from map + wakeupDriver(); +} + +void ConnectionContext::close() +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + if (state != CONNECTED) return; + if (!(pn_connection_state(connection) & PN_LOCAL_CLOSED)) { + for (SessionMap::iterator i = sessions.begin(); i != sessions.end(); ++i) { + //wait for outstanding sends to settle + while (!i->second->settled()) { + QPID_LOG(debug, "Waiting for sends to settle before closing"); + wait();//wait until message has been confirmed + } + + + if (!(pn_session_state(i->second->session) & PN_LOCAL_CLOSED)) { + pn_session_close(i->second->session); + } + } + pn_connection_close(connection); + wakeupDriver(); + //wait for close to be confirmed by peer? + while (!(pn_connection_state(connection) & PN_REMOTE_CLOSED)) { + wait(); + } + sessions.clear(); + } + transport->close(); + while (state != DISCONNECTED) { + lock.wait(); + } +} + +bool ConnectionContext::fetch(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk, qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + { + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + if (!lnk->capacity) { + pn_link_flow(lnk->receiver, 1); + wakeupDriver(); + } + } + if (get(ssn, lnk, message, timeout)) { + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + if (lnk->capacity) { + pn_link_flow(lnk->receiver, 1);//TODO: is this the right approach? + wakeupDriver(); + } + return true; + } else { + { + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + pn_link_drain(lnk->receiver, 0); + wakeupDriver(); + while (pn_link_credit(lnk->receiver) && !pn_link_queued(lnk->receiver)) { + QPID_LOG(debug, "Waiting for message or for credit to be drained: credit=" << pn_link_credit(lnk->receiver) << ", queued=" << pn_link_queued(lnk->receiver)); + wait(); + } + if (lnk->capacity && pn_link_queued(lnk->receiver) == 0) { + pn_link_flow(lnk->receiver, lnk->capacity); + } + } + if (get(ssn, lnk, message, qpid::messaging::Duration::IMMEDIATE)) { + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + if (lnk->capacity) { + pn_link_flow(lnk->receiver, 1); + wakeupDriver(); + } + return true; + } else { + return false; + } + } +} + +qpid::sys::AbsTime convert(qpid::messaging::Duration timeout) +{ + qpid::sys::AbsTime until; + uint64_t ms = timeout.getMilliseconds(); + if (ms < (uint64_t) (qpid::sys::TIME_INFINITE/qpid::sys::TIME_MSEC)) { + return qpid::sys::AbsTime(qpid::sys::now(), ms * qpid::sys::TIME_MSEC); + } else { + return qpid::sys::FAR_FUTURE; + } +} + +bool ConnectionContext::get(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk, qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + qpid::sys::AbsTime until(convert(timeout)); + while (true) { + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + pn_delivery_t* current = pn_link_current((pn_link_t*) lnk->receiver); + QPID_LOG(debug, "In ConnectionContext::get(), current=" << current); + if (current) { + qpid::messaging::MessageImpl& impl = MessageImplAccess::get(message); + boost::shared_ptr<EncodedMessage> encoded(new EncodedMessage(pn_delivery_pending(current))); + ssize_t read = pn_link_recv(lnk->receiver, encoded->getData(), encoded->getSize()); + if (read < 0) throw qpid::messaging::MessagingException("Failed to read message"); + encoded->trim((size_t) read); + QPID_LOG(debug, "Received message of " << encoded->getSize() << " bytes: "); + encoded->init(impl); + impl.setEncoded(encoded); + impl.setInternalId(ssn->record(current)); + pn_link_advance(lnk->receiver); + return true; + } else if (until > qpid::sys::now()) { + wait(); + } else { + return false; + } + } + return false; +} + +void ConnectionContext::acknowledge(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Message* message, bool cumulative) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + if (message) { + ssn->acknowledge(MessageImplAccess::get(*message).getInternalId(), cumulative); + } else { + ssn->acknowledge(); + } + wakeupDriver(); +} + + +void ConnectionContext::attach(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<SenderContext> lnk) +{ + lnk->configure(); + attach(ssn->session, (pn_link_t*) lnk->sender); + if (!pn_link_remote_target((pn_link_t*) lnk->sender)) { + std::string msg("No such target : "); + msg += lnk->getTarget(); + throw qpid::messaging::NotFound(msg); + } +} + +void ConnectionContext::attach(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk) +{ + lnk->configure(); + attach(ssn->session, lnk->receiver, lnk->capacity); + if (!pn_link_remote_source(lnk->receiver)) { + std::string msg("No such source : "); + msg += lnk->getSource(); + throw qpid::messaging::NotFound(msg); + } +} + +void ConnectionContext::attach(pn_session_t* /*session*/, pn_link_t* link, int credit) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + QPID_LOG(debug, "Attaching link " << link << ", state=" << pn_link_state(link)); + pn_link_open(link); + QPID_LOG(debug, "Link attached " << link << ", state=" << pn_link_state(link)); + if (credit) pn_link_flow(link, credit); + wakeupDriver(); + while (pn_link_state(link) & PN_REMOTE_UNINIT) { + QPID_LOG(debug, "waiting for confirmation of link attach for " << link << ", state=" << pn_link_state(link)); + wait(); + } +} + +void ConnectionContext::send(boost::shared_ptr<SenderContext> snd, const qpid::messaging::Message& message, bool sync) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + SenderContext::Delivery* delivery(0); + while (!(delivery = snd->send(message))) { + QPID_LOG(debug, "Waiting for capacity..."); + wait();//wait for capacity + } + wakeupDriver(); + if (sync) { + while (!delivery->accepted()) { + QPID_LOG(debug, "Waiting for confirmation..."); + wait();//wait until message has been confirmed + } + } +} + +void ConnectionContext::setCapacity(boost::shared_ptr<SenderContext> sender, uint32_t capacity) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + sender->setCapacity(capacity); +} +uint32_t ConnectionContext::getCapacity(boost::shared_ptr<SenderContext> sender) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + return sender->getCapacity(); +} +uint32_t ConnectionContext::getUnsettled(boost::shared_ptr<SenderContext> sender) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + return sender->getUnsettled(); +} + +void ConnectionContext::setCapacity(boost::shared_ptr<ReceiverContext> receiver, uint32_t capacity) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + receiver->setCapacity(capacity); + pn_link_flow((pn_link_t*) receiver->receiver, receiver->getCapacity()); + wakeupDriver(); +} +uint32_t ConnectionContext::getCapacity(boost::shared_ptr<ReceiverContext> receiver) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + return receiver->getCapacity(); +} +uint32_t ConnectionContext::getAvailable(boost::shared_ptr<ReceiverContext> receiver) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + return receiver->getAvailable(); +} +uint32_t ConnectionContext::getUnsettled(boost::shared_ptr<ReceiverContext> receiver) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + return receiver->getUnsettled(); +} + +void ConnectionContext::activateOutput() +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + wakeupDriver(); +} +/** + * Expects lock to be held by caller + */ +void ConnectionContext::wakeupDriver() +{ + switch (state) { + case CONNECTED: + haveOutput = true; + transport->activateOutput(); + QPID_LOG(debug, "wakeupDriver()"); + break; + case DISCONNECTED: + case CONNECTING: + QPID_LOG(error, "wakeupDriver() called while not connected"); + break; + } +} + +void ConnectionContext::wait() +{ + lock.wait(); + if (state == DISCONNECTED) { + throw qpid::messaging::TransportFailure("Disconnected"); + } + //check for any closed links, sessions or indeed the connection +} + +boost::shared_ptr<SessionContext> ConnectionContext::newSession(bool transactional, const std::string& n) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + if (transactional) throw qpid::messaging::MessagingException("Transactions not yet supported"); + std::string name = n.empty() ? qpid::framing::Uuid(true).str() : n; + SessionMap::const_iterator i = sessions.find(name); + if (i == sessions.end()) { + boost::shared_ptr<SessionContext> s(new SessionContext(connection)); + s->session = pn_session(connection); + pn_session_open(s->session); + sessions[name] = s; + wakeupDriver(); + while (pn_session_state(s->session) & PN_REMOTE_UNINIT) { + wait(); + } + return s; + } else { + throw qpid::messaging::KeyError(std::string("Session already exists: ") + name); + } + +} +boost::shared_ptr<SessionContext> ConnectionContext::getSession(const std::string& name) const +{ + SessionMap::const_iterator i = sessions.find(name); + if (i == sessions.end()) { + throw qpid::messaging::KeyError(std::string("No such session") + name); + } else { + return i->second; + } +} + +void ConnectionContext::setOption(const std::string& name, const qpid::types::Variant& value) +{ + set(name, value); +} + +std::string ConnectionContext::getAuthenticatedUsername() +{ + return sasl.get() ? sasl->getAuthenticatedUsername() : std::string(); +} + +std::size_t ConnectionContext::decode(const char* buffer, std::size_t size) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + QPID_LOG(trace, id << " decode(" << size << ")"); + if (readHeader) { + size_t decoded = readProtocolHeader(buffer, size); + if (decoded < size) { + decoded += decode(buffer + decoded, size - decoded); + } + return decoded; + } + + //TODO: Fix pn_engine_input() to take const buffer + ssize_t n = pn_transport_input(engine, const_cast<char*>(buffer), size); + if (n > 0 || n == PN_EOS) { + //If engine returns EOS, have no way of knowing how many bytes + //it processed, but can assume none need to be reprocessed so + //consider them all read: + if (n == PN_EOS) n = size; + QPID_LOG_CAT(debug, network, id << " decoded " << n << " bytes from " << size) + pn_transport_tick(engine, 0); + lock.notifyAll(); + return n; + } else if (n == PN_ERR) { + throw qpid::Exception(QPID_MSG("Error on input: " << getError())); + } else { + return 0; + } + +} +std::size_t ConnectionContext::encode(char* buffer, std::size_t size) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + QPID_LOG(trace, id << " encode(" << size << ")"); + if (writeHeader) { + size_t encoded = writeProtocolHeader(buffer, size); + if (encoded < size) { + encoded += encode(buffer + encoded, size - encoded); + } + return encoded; + } + + ssize_t n = pn_transport_output(engine, buffer, size); + if (n > 0) { + QPID_LOG_CAT(debug, network, id << " encoded " << n << " bytes from " << size) + haveOutput = true; + return n; + } else if (n == PN_ERR) { + throw qpid::Exception(QPID_MSG("Error on output: " << getError())); + } else if (n == PN_EOS) { + haveOutput = false; + return 0;//Is this right? + } else { + haveOutput = false; + return 0; + } +} +bool ConnectionContext::canEncode() +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + return haveOutput && state == CONNECTED; +} +void ConnectionContext::closed() +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + state = DISCONNECTED; + lock.notifyAll(); +} +void ConnectionContext::opened() +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); + state = CONNECTED; + lock.notifyAll(); +} +bool ConnectionContext::isClosed() const +{ + return !isOpen(); +} +namespace { +qpid::framing::ProtocolVersion AMQP_1_0_PLAIN(1,0,qpid::framing::ProtocolVersion::AMQP); +} + +std::string ConnectionContext::getError() +{ + std::stringstream text; + pn_error_t* cerror = pn_connection_error(connection); + if (cerror) text << "connection error " << pn_error_text(cerror); + pn_error_t* terror = pn_transport_error(engine); + if (terror) text << "transport error " << pn_error_text(terror); + return text.str(); +} + +framing::ProtocolVersion ConnectionContext::getVersion() const +{ + return AMQP_1_0_PLAIN; +} + +std::size_t ConnectionContext::readProtocolHeader(const char* buffer, std::size_t size) +{ + framing::ProtocolInitiation pi(getVersion()); + if (size >= pi.encodedSize()) { + readHeader = false; + qpid::framing::Buffer out(const_cast<char*>(buffer), size); + pi.decode(out); + QPID_LOG_CAT(debug, protocol, id << " read protocol header: " << pi); + return pi.encodedSize(); + } else { + return 0; + } +} +std::size_t ConnectionContext::writeProtocolHeader(char* buffer, std::size_t size) +{ + framing::ProtocolInitiation pi(getVersion()); + if (size >= pi.encodedSize()) { + QPID_LOG_CAT(debug, protocol, id << " writing protocol header: " << pi); + writeHeader = false; + qpid::framing::Buffer out(buffer, size); + pi.encode(out); + return pi.encodedSize(); + } else { + QPID_LOG_CAT(debug, protocol, id << " insufficient buffer for protocol header: " << size) + return 0; + } +} +bool ConnectionContext::useSasl() +{ + return !(mechanism == "none" || mechanism == "NONE" || mechanism == "None"); +} + +qpid::sys::Codec& ConnectionContext::getCodec() +{ + return codecSwitch; +} + +ConnectionContext::CodecSwitch::CodecSwitch(ConnectionContext& p) : parent(p) {} +std::size_t ConnectionContext::CodecSwitch::decode(const char* buffer, std::size_t size) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(parent.lock); + size_t decoded = 0; + if (parent.sasl.get() && !parent.sasl->authenticated()) { + decoded = parent.sasl->decode(buffer, size); + if (!parent.sasl->authenticated()) return decoded; + } + if (decoded < size) { + if (parent.sasl.get() && parent.sasl->getSecurityLayer()) decoded += parent.sasl->getSecurityLayer()->decode(buffer+decoded, size-decoded); + else decoded += parent.decode(buffer+decoded, size-decoded); + } + return decoded; +} +std::size_t ConnectionContext::CodecSwitch::encode(char* buffer, std::size_t size) +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(parent.lock); + size_t encoded = 0; + if (parent.sasl.get() && parent.sasl->canEncode()) { + encoded += parent.sasl->encode(buffer, size); + if (!parent.sasl->authenticated()) return encoded; + } + if (encoded < size) { + if (parent.sasl.get() && parent.sasl->getSecurityLayer()) encoded += parent.sasl->getSecurityLayer()->encode(buffer+encoded, size-encoded); + else encoded += parent.encode(buffer+encoded, size-encoded); + } + return encoded; +} +bool ConnectionContext::CodecSwitch::canEncode() +{ + qpid::sys::ScopedLock<qpid::sys::Monitor> l(parent.lock); + if (parent.sasl.get()) { + if (parent.sasl->canEncode()) return true; + else if (!parent.sasl->authenticated()) return false; + else if (parent.sasl->getSecurityLayer()) return parent.sasl->getSecurityLayer()->canEncode(); + } + return parent.canEncode(); +} + + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/ConnectionContext.h b/cpp/src/qpid/messaging/amqp/ConnectionContext.h new file mode 100644 index 0000000000..3718184365 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/ConnectionContext.h @@ -0,0 +1,150 @@ +#ifndef QPID_MESSAGING_AMQP_CONNECTIONCONTEXT_H +#define QPID_MESSAGING_AMQP_CONNECTIONCONTEXT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <deque> +#include <map> +#include <memory> +#include <string> +#include <boost/shared_ptr.hpp> +#include "qpid/Url.h" +#include "qpid/messaging/ConnectionOptions.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/sys/Monitor.h" +#include "qpid/types/Variant.h" +#include "qpid/messaging/amqp/TransportContext.h" + +struct pn_connection_t; +struct pn_link_t; +struct pn_session_t; +struct pn_transport_t; + + +namespace qpid { +namespace framing { +class ProtocolVersion; +} +namespace messaging { +class Duration; +class Message; +namespace amqp { + +class DriverImpl; +class ReceiverContext; +class Sasl; +class SessionContext; +class SenderContext; +class Transport; + +/** + * + */ +class ConnectionContext : public qpid::sys::ConnectionCodec, public qpid::messaging::ConnectionOptions, public TransportContext +{ + public: + ConnectionContext(const std::string& url, const qpid::types::Variant::Map& options); + ~ConnectionContext(); + void open(); + bool isOpen() const; + void close(); + boost::shared_ptr<SessionContext> newSession(bool transactional, const std::string& name); + boost::shared_ptr<SessionContext> getSession(const std::string& name) const; + void endSession(boost::shared_ptr<SessionContext>); + void attach(boost::shared_ptr<SessionContext>, boost::shared_ptr<SenderContext>); + void attach(boost::shared_ptr<SessionContext>, boost::shared_ptr<ReceiverContext>); + void send(boost::shared_ptr<SenderContext> ctxt, const qpid::messaging::Message& message, bool sync); + bool fetch(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk, qpid::messaging::Message& message, qpid::messaging::Duration timeout); + bool get(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk, qpid::messaging::Message& message, qpid::messaging::Duration timeout); + void acknowledge(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Message* message, bool cumulative); + + void setOption(const std::string& name, const qpid::types::Variant& value); + std::string getAuthenticatedUsername(); + + void setCapacity(boost::shared_ptr<SenderContext>, uint32_t); + uint32_t getCapacity(boost::shared_ptr<SenderContext>); + uint32_t getUnsettled(boost::shared_ptr<SenderContext>); + + void setCapacity(boost::shared_ptr<ReceiverContext>, uint32_t); + uint32_t getCapacity(boost::shared_ptr<ReceiverContext>); + uint32_t getAvailable(boost::shared_ptr<ReceiverContext>); + uint32_t getUnsettled(boost::shared_ptr<ReceiverContext>); + + + void activateOutput(); + qpid::sys::Codec& getCodec(); + //ConnectionCodec interface: + std::size_t decode(const char* buffer, std::size_t size); + std::size_t encode(char* buffer, std::size_t size); + bool canEncode(); + void closed(); + bool isClosed() const; + framing::ProtocolVersion getVersion() const; + //additionally, Transport needs: + void opened();//signal successful connection + + private: + typedef std::map<std::string, boost::shared_ptr<SessionContext> > SessionMap; + qpid::Url url; + + boost::shared_ptr<DriverImpl> driver; + boost::shared_ptr<Transport> transport; + + pn_transport_t* engine; + pn_connection_t* connection; + SessionMap sessions; + mutable qpid::sys::Monitor lock; + bool writeHeader; + bool readHeader; + bool haveOutput; + std::string id; + enum { + DISCONNECTED, + CONNECTING, + CONNECTED + } state; + std::auto_ptr<Sasl> sasl; + class CodecSwitch : public qpid::sys::Codec + { + public: + CodecSwitch(ConnectionContext&); + std::size_t decode(const char* buffer, std::size_t size); + std::size_t encode(char* buffer, std::size_t size); + bool canEncode(); + private: + ConnectionContext& parent; + }; + CodecSwitch codecSwitch; + + void wait(); + void wakeupDriver(); + void attach(pn_session_t*, pn_link_t*, int credit=0); + + std::size_t readProtocolHeader(const char* buffer, std::size_t size); + std::size_t writeProtocolHeader(char* buffer, std::size_t size); + std::string getError(); + bool useSasl(); +}; + +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_CONNECTIONCONTEXT_H*/ diff --git a/cpp/src/qpid/messaging/amqp/ConnectionHandle.cpp b/cpp/src/qpid/messaging/amqp/ConnectionHandle.cpp new file mode 100644 index 0000000000..0c4ec2bfcb --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/ConnectionHandle.cpp @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "ConnectionHandle.h" +#include "ConnectionContext.h" +#include "SessionHandle.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/ProtocolRegistry.h" + +namespace qpid { +namespace messaging { +namespace amqp { +// Static constructor which registers this implementation in the ProtocolRegistry +namespace { +ConnectionImpl* create(const std::string& u, const qpid::types::Variant::Map& o) +{ + return new ConnectionHandle(u, o); +} + +struct StaticInit +{ + StaticInit() + { + ProtocolRegistry::add("amqp1.0", &create); + }; +} init; +} + +ConnectionHandle::ConnectionHandle(const std::string& url, const qpid::types::Variant::Map& options) : connection(new ConnectionContext(url, options)) {} +ConnectionHandle::ConnectionHandle(boost::shared_ptr<ConnectionContext> c) : connection(c) {} + +void ConnectionHandle::open() +{ + connection->open(); +} + +bool ConnectionHandle::isOpen() const +{ + return connection->isOpen(); +} + +void ConnectionHandle::close() +{ + connection->close(); +} + +Session ConnectionHandle::newSession(bool transactional, const std::string& name) +{ + return qpid::messaging::Session(new SessionHandle(connection, connection->newSession(transactional, name))); +} + +Session ConnectionHandle::getSession(const std::string& name) const +{ + return qpid::messaging::Session(new SessionHandle(connection, connection->getSession(name))); +} + +void ConnectionHandle::setOption(const std::string& name, const qpid::types::Variant& value) +{ + connection->setOption(name, value); +} + +std::string ConnectionHandle::getAuthenticatedUsername() +{ + return connection->getAuthenticatedUsername(); +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/ConnectionHandle.h b/cpp/src/qpid/messaging/amqp/ConnectionHandle.h new file mode 100644 index 0000000000..d1eb27f6de --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/ConnectionHandle.h @@ -0,0 +1,58 @@ +#ifndef QPID_MESSAGING_AMQP_CONNECTIONHANDLE_H +#define QPID_MESSAGING_AMQP_CONNECTIONHANDLE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <boost/shared_ptr.hpp> +#include "qpid/messaging/ConnectionImpl.h" +#include "qpid/types/Variant.h" + +namespace qpid { +namespace messaging { +namespace amqp { + +class ConnectionContext; +/** + * Handles are directly referenced by applications; Contexts are + * referenced by Handles. This allows a graph structure that + * remains intact as long as the application references any part + * of it, but that can be automatically reclaimed if the whole + * graph becomes unreferenced. + */ +class ConnectionHandle : public qpid::messaging::ConnectionImpl +{ + public: + ConnectionHandle(const std::string& url, const qpid::types::Variant::Map& options); + ConnectionHandle(boost::shared_ptr<ConnectionContext>); + void open(); + bool isOpen() const; + void close(); + Session newSession(bool transactional, const std::string& name); + Session getSession(const std::string& name) const; + void setOption(const std::string& name, const qpid::types::Variant& value); + std::string getAuthenticatedUsername(); + private: + boost::shared_ptr<ConnectionContext> connection; +}; + +}}} // namespace qpid::messaging::amqp_1.0 + +#endif /*!QPID_MESSAGING_AMQP_CONNECTIONHANDLE_H*/ diff --git a/cpp/src/qpid/messaging/amqp/DriverImpl.cpp b/cpp/src/qpid/messaging/amqp/DriverImpl.cpp new file mode 100644 index 0000000000..16307b3c22 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/DriverImpl.cpp @@ -0,0 +1,74 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "DriverImpl.h" +#include "Transport.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/sys/Poller.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace messaging { +namespace amqp { + +DriverImpl::DriverImpl() : poller(new qpid::sys::Poller) +{ + start(); +} +DriverImpl::~DriverImpl() +{ + stop(); +} + +void DriverImpl::start() +{ + thread = qpid::sys::Thread(*poller); + QPID_LOG(debug, "Driver started"); +} + +void DriverImpl::stop() +{ + QPID_LOG(debug, "Driver stopped"); + poller->shutdown(); + thread.join(); +} + +boost::shared_ptr<Transport> DriverImpl::getTransport(const std::string& protocol, TransportContext& connection) +{ + boost::shared_ptr<Transport> t(Transport::create(protocol, connection, poller)); + if (!t) throw qpid::messaging::ConnectionError("No such transport: " + protocol); + return t; +} + + +qpid::sys::Mutex DriverImpl::defaultLock; +boost::weak_ptr<DriverImpl> DriverImpl::theDefault; +boost::shared_ptr<DriverImpl> DriverImpl::getDefault() +{ + qpid::sys::Mutex::ScopedLock l(defaultLock); + boost::shared_ptr<DriverImpl> p = theDefault.lock(); + if (!p) { + p = boost::shared_ptr<DriverImpl>(new DriverImpl); + theDefault = p; + } + return p; +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/sys/ClusterSafe.cpp b/cpp/src/qpid/messaging/amqp/DriverImpl.h index dd37615145..354fa1ae35 100644 --- a/cpp/src/qpid/sys/ClusterSafe.cpp +++ b/cpp/src/qpid/messaging/amqp/DriverImpl.h @@ -1,3 +1,6 @@ +#ifndef QPID_MESSAGING_AMQP_DRIVERIMPL_H +#define QPID_MESSAGING_AMQP_DRIVERIMPL_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -18,49 +21,40 @@ * under the License. * */ - -#include "ClusterSafe.h" -#include "qpid/log/Statement.h" +#include "qpid/sys/Mutex.h" #include "qpid/sys/Thread.h" -#include <stdlib.h> +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> namespace qpid { namespace sys { - -namespace { -bool inCluster = false; -QPID_TSS bool inContext = false; -} - -bool isClusterSafe() { return !inCluster || inContext; } - -void assertClusterSafe() { - if (!isClusterSafe()) { - QPID_LOG(critical, "Modified cluster state outside of cluster context"); - ::abort(); - } -} - -ClusterSafeScope::ClusterSafeScope() { - save = inContext; - inContext = true; -} - -ClusterSafeScope::~ClusterSafeScope() { - assert(inContext); - inContext = save; -} - -ClusterUnsafeScope::ClusterUnsafeScope() { - save = inContext; - inContext = false; -} - -ClusterUnsafeScope::~ClusterUnsafeScope() { - assert(!inContext); - inContext = save; +class Poller; } - -void enableClusterSafe() { inCluster = true; } - -}} // namespace qpid::sys +namespace messaging { +namespace amqp { +class TransportContext; +class Transport; +/** + * + */ +class DriverImpl +{ + public: + DriverImpl(); + ~DriverImpl(); + + void start(); + void stop(); + + boost::shared_ptr<Transport> getTransport(const std::string& protocol, TransportContext& connection); + + static boost::shared_ptr<DriverImpl> getDefault(); + private: + boost::shared_ptr<qpid::sys::Poller> poller; + qpid::sys::Thread thread; + static qpid::sys::Mutex defaultLock; + static boost::weak_ptr<DriverImpl> theDefault; +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_DRIVERIMPL_H*/ diff --git a/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp b/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp new file mode 100644 index 0000000000..54de3eae45 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp @@ -0,0 +1,263 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/amqp/EncodedMessage.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/amqp/Decoder.h" +#include <boost/lexical_cast.hpp> +#include <string.h> + +namespace qpid { +namespace messaging { +namespace amqp { + +using namespace qpid::amqp; + +EncodedMessage::EncodedMessage(size_t s) : size(s), data(size ? new char[size] : 0) +{ + init(); +} + +EncodedMessage::EncodedMessage() : size(0), data(0) +{ + init(); +} + +EncodedMessage::EncodedMessage(const EncodedMessage& other) : size(other.size), data(size ? new char[size] : 0) +{ + init(); +} + +void EncodedMessage::init() +{ + //init all CharSequence members + deliveryAnnotations.init(); + messageAnnotations.init(); + userId.init(); + to.init(); + subject.init(); + replyTo.init(); + contentType.init(); + contentEncoding.init(); + groupId.init(); + replyToGroupId.init(); + applicationProperties.init(); + body.init(); + footer.init(); +} + +EncodedMessage::~EncodedMessage() +{ + delete[] data; +} + +size_t EncodedMessage::getSize() const +{ + return size; +} +void EncodedMessage::trim(size_t t) +{ + size = t; +} +void EncodedMessage::resize(size_t s) +{ + delete[] data; + size = s; + data = new char[size]; +} + +char* EncodedMessage::getData() +{ + return data; +} +const char* EncodedMessage::getData() const +{ + return data; +} + +void EncodedMessage::init(qpid::messaging::MessageImpl& impl) +{ + //initial scan of raw data + qpid::amqp::Decoder decoder(data, size); + InitialScan reader(*this, impl); + decoder.read(reader); + bareMessage = reader.getBareMessage(); + if (bareMessage.data && !bareMessage.size) { + bareMessage.size = (data + size) - bareMessage.data; + } + +} +void EncodedMessage::populate(qpid::types::Variant::Map& map) const +{ + //decode application properties + if (applicationProperties) { + qpid::amqp::Decoder decoder(applicationProperties.data, applicationProperties.size); + decoder.readMap(map); + } + //add in 'x-amqp-' prefixed values + if (!!firstAcquirer) { + map["x-amqp-first-acquirer"] = firstAcquirer.get(); + } + if (!!deliveryCount) { + map["x-amqp-delivery-count"] = deliveryCount.get(); + } + if (to) { + map["x-amqp-delivery-count"] = to.str(); + } + if (!!absoluteExpiryTime) { + map["x-amqp-absolute-expiry-time"] = absoluteExpiryTime.get(); + } + if (!!creationTime) { + map["x-amqp-creation-time"] = creationTime.get(); + } + if (groupId) { + map["x-amqp-group-id"] = groupId.str(); + } + if (!!groupSequence) { + map["x-amqp-qroup-sequence"] = groupSequence.get(); + } + if (replyToGroupId) { + map["x-amqp-reply-to-group-id"] = replyToGroupId.str(); + } + //add in any annotations + if (deliveryAnnotations) { + qpid::types::Variant::Map& annotations = map["x-amqp-delivery-annotations"].asMap(); + qpid::amqp::Decoder decoder(deliveryAnnotations.data, deliveryAnnotations.size); + decoder.readMap(annotations); + } + if (messageAnnotations) { + qpid::types::Variant::Map& annotations = map["x-amqp-message-annotations"].asMap(); + qpid::amqp::Decoder decoder(messageAnnotations.data, messageAnnotations.size); + decoder.readMap(annotations); + } +} +qpid::amqp::CharSequence EncodedMessage::getBareMessage() const +{ + return bareMessage; +} + +void EncodedMessage::getReplyTo(qpid::messaging::Address& a) const +{ + a = qpid::messaging::Address(replyTo.str()); +} +void EncodedMessage::getSubject(std::string& s) const +{ + s.assign(subject.data, subject.size); +} +void EncodedMessage::getContentType(std::string& s) const +{ + s.assign(contentType.data, contentType.size); +} +void EncodedMessage::getUserId(std::string& s) const +{ + s.assign(userId.data, userId.size); +} +void EncodedMessage::getMessageId(std::string& s) const +{ + messageId.assign(s); +} +void EncodedMessage::getCorrelationId(std::string& s) const +{ + correlationId.assign(s); +} +void EncodedMessage::getBody(std::string& s) const +{ + s.assign(body.data, body.size); +} + +qpid::amqp::CharSequence EncodedMessage::getBody() const +{ + return body; +} + +bool EncodedMessage::hasHeaderChanged(const qpid::messaging::MessageImpl& msg) const +{ + if (!durable) { + if (msg.isDurable()) return true; + } else { + if (durable.get() != msg.isDurable()) return true; + } + + if (!priority) { + if (msg.getPriority() != 4) return true; + } else { + if (priority.get() != msg.getPriority()) return true; + } + + if (msg.getTtl() && (!ttl || msg.getTtl() != ttl.get())) { + return true; + } + + //first-acquirer can't be changed via Message interface as yet + + if (msg.isRedelivered() && (!deliveryCount || deliveryCount.get() == 0)) { + return true; + } + + return false; +} + + +EncodedMessage::InitialScan::InitialScan(EncodedMessage& e, qpid::messaging::MessageImpl& m) : em(e), mi(m) +{ + //set up defaults as needed: + mi.setPriority(4); +} +//header: +void EncodedMessage::InitialScan::onDurable(bool b) { mi.setDurable(b); em.durable = b; } +void EncodedMessage::InitialScan::onPriority(uint8_t i) { mi.setPriority(i); em.priority = i; } +void EncodedMessage::InitialScan::onTtl(uint32_t i) { mi.setTtl(i); em.ttl = i; } +void EncodedMessage::InitialScan::onFirstAcquirer(bool b) { em.firstAcquirer = b; } +void EncodedMessage::InitialScan::onDeliveryCount(uint32_t i) +{ + mi.setRedelivered(i); + em.deliveryCount = i; +} + +//properties: +void EncodedMessage::InitialScan::onMessageId(uint64_t v) { em.messageId.set(v); } +void EncodedMessage::InitialScan::onMessageId(const qpid::amqp::CharSequence& v, qpid::types::VariantType t) { em.messageId.set(v, t); } +void EncodedMessage::InitialScan::onUserId(const qpid::amqp::CharSequence& v) { em.userId = v; } +void EncodedMessage::InitialScan::onTo(const qpid::amqp::CharSequence& v) { em.to = v; } +void EncodedMessage::InitialScan::onSubject(const qpid::amqp::CharSequence& v) { em.subject = v; } +void EncodedMessage::InitialScan::onReplyTo(const qpid::amqp::CharSequence& v) { em.replyTo = v;} +void EncodedMessage::InitialScan::onCorrelationId(uint64_t v) { em.correlationId.set(v); } +void EncodedMessage::InitialScan::onCorrelationId(const qpid::amqp::CharSequence& v, qpid::types::VariantType t) { em.correlationId.set(v, t); } +void EncodedMessage::InitialScan::onContentType(const qpid::amqp::CharSequence& v) { em.contentType = v; } +void EncodedMessage::InitialScan::onContentEncoding(const qpid::amqp::CharSequence& v) { em.contentEncoding = v; } +void EncodedMessage::InitialScan::onAbsoluteExpiryTime(int64_t i) { em.absoluteExpiryTime = i; } +void EncodedMessage::InitialScan::onCreationTime(int64_t i) { em.creationTime = i; } +void EncodedMessage::InitialScan::onGroupId(const qpid::amqp::CharSequence& v) { em.groupId = v; } +void EncodedMessage::InitialScan::onGroupSequence(uint32_t i) { em.groupSequence = i; } +void EncodedMessage::InitialScan::onReplyToGroupId(const qpid::amqp::CharSequence& v) { em.replyToGroupId = v; } + +void EncodedMessage::InitialScan::onApplicationProperties(const qpid::amqp::CharSequence& v) { em.applicationProperties = v; } +void EncodedMessage::InitialScan::onDeliveryAnnotations(const qpid::amqp::CharSequence& v) { em.deliveryAnnotations = v; } +void EncodedMessage::InitialScan::onMessageAnnotations(const qpid::amqp::CharSequence& v) { em.messageAnnotations = v; } +void EncodedMessage::InitialScan::onBody(const qpid::amqp::CharSequence& v, const qpid::amqp::Descriptor&) +{ + //TODO: how to communicate the type, i.e. descriptor? + em.body = v; +} +void EncodedMessage::InitialScan::onBody(const qpid::types::Variant&, const qpid::amqp::Descriptor&) {} +void EncodedMessage::InitialScan::onFooter(const qpid::amqp::CharSequence& v) { em.footer = v; } + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/EncodedMessage.h b/cpp/src/qpid/messaging/amqp/EncodedMessage.h new file mode 100644 index 0000000000..09a9d948d5 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/EncodedMessage.h @@ -0,0 +1,177 @@ +#ifndef QPID_MESSAGING_AMQP_ENCODEDMESSAGE_H +#define QPID_MESSAGING_AMQP_ENCODEDMESSAGE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/amqp/CharSequence.h" +#include "qpid/amqp/MessageId.h" +#include "qpid/amqp/MessageReader.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/types/Variant.h" +#include <boost/optional.hpp> + +namespace qpid { +namespace amqp { +struct Descriptor; +} +namespace messaging { +class Address; +class MessageImpl; +namespace amqp { + +/** + * Used to 'lazy-decode' an AMQP 1.0 message. + * + * There are four categories of data item: + * + * (i) simple, fixed width primitives - priority, ttl, durability, + * delivery count - for which lazy-decoding doesn't buy much. These + * are decoded unconditionally on an initial scan of the message. + * + * (ii) standard variable length string properties - subject, + * message-id, user-id etc - which require conversion to a std::string + * for returning to the application. By delaying the conversion of + * these to a std::string we can avoid allocation & copying until it + * is actually required. The initial scan of the message merely + * records the position of these strings within the raw message data. + * + * (iii) custom, application defined headers. These form a map, and + * again, delaying the creation of that map until it is actually + * required can be advantageous. The initial scan of the message merely + * records the position of this section within the raw message data. + * + * (iv) the body content. This may be retreived as a std::string, or + * as a char*. Avoiding conversion to the string until it is required + * is advantageous. The initial scan of the message merely records the + * position of this section within the raw message data. + * + * At present the Message class only explicitly exposes some of the + * standard property and headers defined by AMQP 1.0. The remainder + * will have to be accessed through the message 'headers' map, using + * the 'x-amqp-' prefix. + */ +class EncodedMessage +{ + public: + EncodedMessage(); + EncodedMessage(size_t); + EncodedMessage(const EncodedMessage&); + ~EncodedMessage(); + + + size_t getSize() const; + char* getData(); + const char* getData() const; + void trim(size_t); + void resize(size_t); + + void getReplyTo(qpid::messaging::Address&) const; + void getSubject(std::string&) const; + void getContentType(std::string&) const; + void getMessageId(std::string&) const; + void getUserId(std::string&) const; + void getCorrelationId(std::string&) const; + + void init(qpid::messaging::MessageImpl&); + void populate(qpid::types::Variant::Map&) const; + void getBody(std::string&) const; + qpid::amqp::CharSequence getBareMessage() const; + qpid::amqp::CharSequence getBody() const; + bool hasHeaderChanged(const qpid::messaging::MessageImpl&) const; + private: + size_t size; + char* data; + + class InitialScan : public qpid::amqp::MessageReader + { + public: + InitialScan(EncodedMessage& e, qpid::messaging::MessageImpl& m); + //header: + void onDurable(bool b); + void onPriority(uint8_t i); + void onTtl(uint32_t i); + void onFirstAcquirer(bool b); + void onDeliveryCount(uint32_t i); + //properties: + void onMessageId(uint64_t); + void onMessageId(const qpid::amqp::CharSequence&, qpid::types::VariantType); + void onUserId(const qpid::amqp::CharSequence& v); + void onTo(const qpid::amqp::CharSequence& v); + void onSubject(const qpid::amqp::CharSequence& v); + void onReplyTo(const qpid::amqp::CharSequence& v); + void onCorrelationId(uint64_t); + void onCorrelationId(const qpid::amqp::CharSequence&, qpid::types::VariantType); + void onContentType(const qpid::amqp::CharSequence& v); + void onContentEncoding(const qpid::amqp::CharSequence& v); + void onAbsoluteExpiryTime(int64_t i); + void onCreationTime(int64_t); + void onGroupId(const qpid::amqp::CharSequence&); + void onGroupSequence(uint32_t); + void onReplyToGroupId(const qpid::amqp::CharSequence&); + + void onApplicationProperties(const qpid::amqp::CharSequence&); + void onDeliveryAnnotations(const qpid::amqp::CharSequence&); + void onMessageAnnotations(const qpid::amqp::CharSequence&); + void onBody(const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor&); + void onBody(const qpid::types::Variant&, const qpid::amqp::Descriptor&); + void onFooter(const qpid::amqp::CharSequence&); + private: + EncodedMessage& em; + qpid::messaging::MessageImpl& mi; + }; + //header: + boost::optional<bool> durable; + boost::optional<uint8_t> priority; + boost::optional<uint32_t> ttl; + boost::optional<bool> firstAcquirer; + boost::optional<uint32_t> deliveryCount; + //annotations: + qpid::amqp::CharSequence deliveryAnnotations; + qpid::amqp::CharSequence messageAnnotations; + + qpid::amqp::CharSequence bareMessage;//properties, application-properties and content + //properties: + qpid::amqp::MessageId messageId; + qpid::amqp::CharSequence userId; + qpid::amqp::CharSequence to; + qpid::amqp::CharSequence subject; + qpid::amqp::CharSequence replyTo; + qpid::amqp::MessageId correlationId; + qpid::amqp::CharSequence contentType; + qpid::amqp::CharSequence contentEncoding; + boost::optional<int64_t> absoluteExpiryTime; + boost::optional<int64_t> creationTime; + qpid::amqp::CharSequence groupId; + boost::optional<uint32_t> groupSequence; + qpid::amqp::CharSequence replyToGroupId; + //application-properties: + qpid::amqp::CharSequence applicationProperties; + qpid::amqp::CharSequence body; + //footer: + qpid::amqp::CharSequence footer; + + void init(); + //not implemented: + EncodedMessage& operator=(const EncodedMessage&); +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_ENCODEDMESSAGE_H*/ diff --git a/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp b/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp new file mode 100644 index 0000000000..414793c7fd --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp @@ -0,0 +1,146 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/amqp/ReceiverContext.h" +#include "qpid/messaging/amqp/AddressHelper.h" +#include "qpid/messaging/Duration.h" +#include "qpid/messaging/Message.h" +#include "qpid/amqp/descriptors.h" +extern "C" { +#include <proton/engine.h> +} + +namespace qpid { +namespace messaging { +namespace amqp { +//TODO: proper conversion to wide string for address +ReceiverContext::ReceiverContext(pn_session_t* session, const std::string& n, const qpid::messaging::Address& a) + : name(n), + address(a), + receiver(pn_receiver(session, name.c_str())), + capacity(0) {} +ReceiverContext::~ReceiverContext() +{ + pn_link_free(receiver); +} + +void ReceiverContext::setCapacity(uint32_t c) +{ + if (c != capacity) { + //stop + capacity = c; + //reissue credit + } +} + +uint32_t ReceiverContext::getCapacity() +{ + return capacity; +} + +uint32_t ReceiverContext::getAvailable() +{ + uint32_t count(0); + for (pn_delivery_t* d = pn_unsettled_head(receiver); d; d = pn_unsettled_next(d)) { + ++count; + if (d == pn_link_current(receiver)) break; + } + return count; +} + +uint32_t ReceiverContext::getUnsettled() +{ + uint32_t count(0); + for (pn_delivery_t* d = pn_unsettled_head(receiver); d; d = pn_unsettled_next(d)) { + ++count; + } + return count; +} + +void ReceiverContext::close() +{ + +} + +const std::string& ReceiverContext::getName() const +{ + return name; +} + +const std::string& ReceiverContext::getSource() const +{ + return address.getName(); +} +namespace { +pn_bytes_t convert(const std::string& s) +{ + pn_bytes_t result; + result.start = const_cast<char*>(s.data()); + result.size = s.size(); + return result; +} +bool hasWildcards(const std::string& key) +{ + return key.find('*') != std::string::npos || key.find('#') != std::string::npos; +} + +uint64_t getFilterDescriptor(const std::string& key) +{ + return hasWildcards(key) ? qpid::amqp::filters::LEGACY_TOPIC_FILTER_CODE : qpid::amqp::filters::LEGACY_DIRECT_FILTER_CODE; +} +} + +void ReceiverContext::configure() const +{ + configure(pn_link_source(receiver)); +} +void ReceiverContext::configure(pn_terminus_t* source) const +{ + pn_terminus_set_address(source, address.getName().c_str()); + //dynamic create: + AddressHelper helper(address); + if (helper.createEnabled(AddressHelper::FOR_RECEIVER)) { + helper.setNodeProperties(source); + } + + if (!address.getSubject().empty()) { + //filter: + pn_data_t* filter = pn_terminus_filter(source); + pn_data_put_map(filter); + pn_data_enter(filter); + pn_data_put_symbol(filter, convert("subject")); + //TODO: At present inserting described values into the map doesn't seem to work; correct this once resolved + //pn_data_put_described(filter); + //pn_data_enter(filter); + //pn_data_put_ulong(filter, getFilterDescriptor(address.getSubject())); + pn_data_put_string(filter, convert(address.getSubject())); + //pn_data_exit(filter); + pn_data_exit(filter); + } +} + +bool ReceiverContext::isClosed() const +{ + return false;//TODO +} + + + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/ReceiverContext.h b/cpp/src/qpid/messaging/amqp/ReceiverContext.h new file mode 100644 index 0000000000..34ecdda6be --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/ReceiverContext.h @@ -0,0 +1,68 @@ +#ifndef QPID_MESSAGING_AMQP_RECEIVERCONTEXT_H +#define QPID_MESSAGING_AMQP_RECEIVERCONTEXT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/Address.h" +#include <string> +#include "qpid/sys/IntegerTypes.h" + +struct pn_link_t; +struct pn_session_t; +struct pn_terminus_t; + +namespace qpid { +namespace messaging { + +class Duration; +class Message; + +namespace amqp { + +/** + * + */ +class ReceiverContext +{ + public: + ReceiverContext(pn_session_t* session, const std::string& name, const qpid::messaging::Address& source); + ~ReceiverContext(); + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t getAvailable(); + uint32_t getUnsettled(); + void attach(); + void close(); + const std::string& getName() const; + const std::string& getSource() const; + bool isClosed() const; + void configure() const; + private: + friend class ConnectionContext; + const std::string name; + const Address address; + pn_link_t* receiver; + uint32_t capacity; + void configure(pn_terminus_t*) const; +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_RECEIVERCONTEXT_H*/ diff --git a/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp b/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp new file mode 100644 index 0000000000..9bf64ebb8d --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp @@ -0,0 +1,106 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "ReceiverHandle.h" +#include "ConnectionContext.h" +#include "SessionContext.h" +#include "SessionHandle.h" +#include "ReceiverContext.h" +#include "qpid/messaging/Duration.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace messaging { +namespace amqp { + +ReceiverHandle::ReceiverHandle(boost::shared_ptr<ConnectionContext> c, + boost::shared_ptr<SessionContext> s, + boost::shared_ptr<ReceiverContext> r +) : connection(c), session(s), receiver(r) {} + + +bool ReceiverHandle::get(qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + return connection->get(session, receiver, message, timeout); +} + +qpid::messaging::Message ReceiverHandle::get(qpid::messaging::Duration timeout) +{ + qpid::messaging::Message result; + if (!get(result, timeout)) throw qpid::messaging::NoMessageAvailable(); + return result; +} + +bool ReceiverHandle::fetch(qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + return connection->fetch(session, receiver, message, timeout); +} + +qpid::messaging::Message ReceiverHandle::fetch(qpid::messaging::Duration timeout) +{ + qpid::messaging::Message result; + if (!fetch(result, timeout)) throw qpid::messaging::NoMessageAvailable(); + return result; +} + +void ReceiverHandle::setCapacity(uint32_t capacity) +{ + connection->setCapacity(receiver, capacity); +} + +uint32_t ReceiverHandle::getCapacity() +{ + return connection->getCapacity(receiver); +} + +uint32_t ReceiverHandle::getAvailable() +{ + return connection->getAvailable(receiver); +} + +uint32_t ReceiverHandle::getUnsettled() +{ + return connection->getUnsettled(receiver); +} + +void ReceiverHandle::close() +{ + session->closeReceiver(getName()); +} + +const std::string& ReceiverHandle::getName() const +{ + return receiver->getName(); +} + +qpid::messaging::Session ReceiverHandle::getSession() const +{ + //create new SessionHandle instance; i.e. create new handle that shares the same context + return qpid::messaging::Session(new SessionHandle(connection, session)); +} + +bool ReceiverHandle::isClosed() const +{ + return receiver->isClosed(); +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/ReceiverHandle.h b/cpp/src/qpid/messaging/amqp/ReceiverHandle.h new file mode 100644 index 0000000000..a1a6f26025 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/ReceiverHandle.h @@ -0,0 +1,63 @@ +#ifndef QPID_MESSAGING_AMQP_RECEIVERHANDLE_H +#define QPID_MESSAGING_AMQP_RECEIVERHANDLE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <boost/shared_ptr.hpp> +#include "qpid/messaging/ReceiverImpl.h" + +namespace qpid { +namespace messaging { +namespace amqp { + +class ConnectionContext; +class SessionContext; +class ReceiverContext; +/** + * + */ +class ReceiverHandle : public qpid::messaging::ReceiverImpl +{ + public: + ReceiverHandle(boost::shared_ptr<ConnectionContext>, + boost::shared_ptr<SessionContext>, + boost::shared_ptr<ReceiverContext> + ); + bool get(Message& message, qpid::messaging::Duration timeout); + qpid::messaging::Message get(qpid::messaging::Duration timeout); + bool fetch(Message& message, qpid::messaging::Duration timeout); + qpid::messaging::Message fetch(qpid::messaging::Duration timeout); + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t getAvailable(); + uint32_t getUnsettled(); + void close(); + const std::string& getName() const; + qpid::messaging::Session getSession() const; + bool isClosed() const; + private: + boost::shared_ptr<ConnectionContext> connection; + boost::shared_ptr<SessionContext> session; + boost::shared_ptr<ReceiverContext> receiver; +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_RECEIVERHANDLE_H*/ diff --git a/cpp/src/qpid/messaging/amqp/Sasl.cpp b/cpp/src/qpid/messaging/amqp/Sasl.cpp new file mode 100644 index 0000000000..a8bae1adda --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/Sasl.cpp @@ -0,0 +1,157 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "ConnectionContext.h" +#include "qpid/messaging/amqp/Sasl.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/log/Statement.h" +#include "qpid/Sasl.h" +#include "qpid/SaslFactory.h" +#include "qpid/StringUtils.h" +#include <sstream> + +namespace qpid { +namespace messaging { +namespace amqp { + +Sasl::Sasl(const std::string& id, ConnectionContext& c, const std::string& hostname_) + : qpid::amqp::SaslClient(id), context(c), + sasl(qpid::SaslFactory::getInstance().create(c.username, c.password, c.service, hostname_, c.minSsf, c.maxSsf, false)), + hostname(hostname_), readHeader(true), writeHeader(true), haveOutput(false), state(NONE) {} + +std::size_t Sasl::decode(const char* buffer, std::size_t size) +{ + size_t decoded = 0; + if (readHeader) { + decoded += readProtocolHeader(buffer, size); + readHeader = !decoded; + } + if (state == NONE && decoded < size) { + decoded += read(buffer + decoded, size - decoded); + } + QPID_LOG(trace, id << " Sasl::decode(" << size << "): " << decoded); + return decoded; +} + +std::size_t Sasl::encode(char* buffer, std::size_t size) +{ + size_t encoded = 0; + if (writeHeader) { + encoded += writeProtocolHeader(buffer, size); + writeHeader = !encoded; + } + if (encoded < size) { + encoded += write(buffer + encoded, size - encoded); + } + haveOutput = (encoded == size); + QPID_LOG(trace, id << " Sasl::encode(" << size << "): " << encoded); + return encoded; +} + +bool Sasl::canEncode() +{ + QPID_LOG(trace, id << " Sasl::canEncode(): " << writeHeader << " || " << haveOutput); + return writeHeader || haveOutput; +} + +void Sasl::mechanisms(const std::string& offered) +{ + QPID_LOG_CAT(debug, protocol, id << " Received SASL-MECHANISMS(" << offered << ")"); + std::string response; + + std::string mechanisms; + if (context.mechanism.size()) { + std::vector<std::string> allowed = split(context.mechanism, " "); + std::vector<std::string> supported = split(offered, " "); + std::stringstream intersection; + for (std::vector<std::string>::const_iterator i = allowed.begin(); i != allowed.end(); ++i) { + if (std::find(supported.begin(), supported.end(), *i) != supported.end()) { + intersection << *i << " "; + } + } + mechanisms = intersection.str(); + } else { + mechanisms = offered; + } + + if (sasl->start(mechanisms, response)) { + init(sasl->getMechanism(), &response, hostname.size() ? &hostname : 0); + } else { + init(sasl->getMechanism(), 0, hostname.size() ? &hostname : 0); + } + haveOutput = true; + context.activateOutput(); +} +void Sasl::challenge(const std::string& challenge) +{ + QPID_LOG_CAT(debug, protocol, id << " Received SASL-CHALLENGE(" << challenge.size() << " bytes)"); + std::string r = sasl->step(challenge); + response(&r); + haveOutput = true; + context.activateOutput(); +} +namespace { +const std::string EMPTY; +} +void Sasl::challenge() +{ + QPID_LOG_CAT(debug, protocol, id << " Received SASL-CHALLENGE(null)"); + std::string r = sasl->step(EMPTY); + response(&r); +} +void Sasl::outcome(uint8_t result, const std::string& extra) +{ + QPID_LOG_CAT(debug, protocol, id << " Received SASL-OUTCOME(" << result << ", " << extra << ")"); + outcome(result); +} +void Sasl::outcome(uint8_t result) +{ + QPID_LOG_CAT(debug, protocol, id << " Received SASL-OUTCOME(" << result << ")"); + if (result) state = FAILED; + else state = SUCCEEDED; + + securityLayer = sasl->getSecurityLayer(context.maxFrameSize); + if (securityLayer.get()) { + securityLayer->init(&context); + } + context.activateOutput(); +} + +qpid::sys::Codec* Sasl::getSecurityLayer() +{ + return securityLayer.get(); +} + +bool Sasl::authenticated() +{ + switch (state) { + case SUCCEEDED: return true; + case FAILED: throw qpid::messaging::UnauthorizedAccess("Failed to authenticate"); + case NONE: default: return false; + } +} + +std::string Sasl::getAuthenticatedUsername() +{ + return sasl->getUserId(); +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/Sasl.h b/cpp/src/qpid/messaging/amqp/Sasl.h new file mode 100644 index 0000000000..6657779fdc --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/Sasl.h @@ -0,0 +1,72 @@ +#ifndef QPID_MESSAGING_AMQP_SASL_H +#define QPID_MESSAGING_AMQP_SASL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/sys/Codec.h" +#include "qpid/amqp/SaslClient.h" +#include <memory> + +namespace qpid { +class Sasl; +namespace sys { +class SecurityLayer; +} +namespace messaging { +class ConnectionOptions; +namespace amqp { +class ConnectionContext; + +/** + * + */ +class Sasl : public qpid::sys::Codec, qpid::amqp::SaslClient +{ + public: + Sasl(const std::string& id, ConnectionContext& context, const std::string& hostname); + std::size_t decode(const char* buffer, std::size_t size); + std::size_t encode(char* buffer, std::size_t size); + bool canEncode(); + + bool authenticated(); + qpid::sys::Codec* getSecurityLayer(); + std::string getAuthenticatedUsername(); + private: + ConnectionContext& context; + std::auto_ptr<qpid::Sasl> sasl; + std::string hostname; + bool readHeader; + bool writeHeader; + bool haveOutput; + enum { + NONE, FAILED, SUCCEEDED + } state; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + + void mechanisms(const std::string&); + void challenge(const std::string&); + void challenge(); //null != empty string + void outcome(uint8_t result, const std::string&); + void outcome(uint8_t result); +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_SASL_H*/ diff --git a/cpp/src/qpid/messaging/amqp/SenderContext.cpp b/cpp/src/qpid/messaging/amqp/SenderContext.cpp new file mode 100644 index 0000000000..96c4437b89 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SenderContext.cpp @@ -0,0 +1,363 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/amqp/SenderContext.h" +#include "qpid/messaging/amqp/EncodedMessage.h" +#include "qpid/messaging/amqp/AddressHelper.h" +#include "qpid/amqp/descriptors.h" +#include "qpid/amqp/MessageEncoder.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/log/Statement.h" +extern "C" { +#include <proton/engine.h> +} +#include <boost/shared_ptr.hpp> +#include <string.h> + +namespace qpid { +namespace messaging { +namespace amqp { +//TODO: proper conversion to wide string for address +SenderContext::SenderContext(pn_session_t* session, const std::string& n, const qpid::messaging::Address& a) + : name(n), + address(a), + sender(pn_sender(session, n.c_str())), capacity(1000) {} + +SenderContext::~SenderContext() +{ + pn_link_free(sender); +} + +void SenderContext::close() +{ + +} + +void SenderContext::setCapacity(uint32_t c) +{ + if (c < deliveries.size()) throw qpid::messaging::SenderError("Desired capacity is less than unsettled message count!"); + capacity = c; +} + +uint32_t SenderContext::getCapacity() +{ + return capacity; +} + +uint32_t SenderContext::getUnsettled() +{ + return processUnsettled(); +} + +const std::string& SenderContext::getName() const +{ + return name; +} + +const std::string& SenderContext::getTarget() const +{ + return address.getName(); +} + +SenderContext::Delivery* SenderContext::send(const qpid::messaging::Message& message) +{ + if (processUnsettled() < capacity && pn_link_credit(sender)) { + deliveries.push_back(Delivery(nextId++)); + Delivery& delivery = deliveries.back(); + delivery.encode(MessageImplAccess::get(message), address); + delivery.send(sender); + return &delivery; + } else { + return 0; + } +} + +uint32_t SenderContext::processUnsettled() +{ + //remove accepted messages from front of deque + while (!deliveries.empty() && deliveries.front().accepted()) { + deliveries.front().settle(); + deliveries.pop_front(); + } + return deliveries.size(); +} +namespace { +class HeaderAdapter : public qpid::amqp::MessageEncoder::Header +{ + public: + HeaderAdapter(const qpid::messaging::MessageImpl& impl) : msg(impl) {} + virtual bool isDurable() const + { + return msg.isDurable(); + } + virtual uint8_t getPriority() const + { + return msg.getPriority(); + } + virtual bool hasTtl() const + { + return msg.getTtl(); + } + virtual uint32_t getTtl() const + { + return msg.getTtl(); + } + virtual bool isFirstAcquirer() const + { + return false; + } + virtual uint32_t getDeliveryCount() const + { + return msg.isRedelivered() ? 1 : 0; + } + private: + const qpid::messaging::MessageImpl& msg; +}; +const std::string EMPTY; + +class PropertiesAdapter : public qpid::amqp::MessageEncoder::Properties +{ + public: + PropertiesAdapter(const qpid::messaging::MessageImpl& impl, const std::string& s) : msg(impl), subject(s) {} + bool hasMessageId() const + { + return getMessageId().size(); + } + std::string getMessageId() const + { + return msg.getMessageId(); + } + + bool hasUserId() const + { + return getUserId().size(); + } + + std::string getUserId() const + { + return msg.getUserId(); + } + + bool hasTo() const + { + return false;//not yet supported + } + + std::string getTo() const + { + return EMPTY;//not yet supported + } + + bool hasSubject() const + { + return subject.size() || getSubject().size(); + } + + std::string getSubject() const + { + return subject.size() ? subject : msg.getSubject(); + } + + bool hasReplyTo() const + { + return msg.getReplyTo(); + } + + std::string getReplyTo() const + { + return msg.getReplyTo().str(); + } + + bool hasCorrelationId() const + { + return getCorrelationId().size(); + } + + std::string getCorrelationId() const + { + return msg.getCorrelationId(); + } + + bool hasContentType() const + { + return getContentType().size(); + } + + std::string getContentType() const + { + return msg.getContentType(); + } + + bool hasContentEncoding() const + { + return false;//not yet supported + } + + std::string getContentEncoding() const + { + return EMPTY;//not yet supported + } + + bool hasAbsoluteExpiryTime() const + { + return false;//not yet supported + } + + int64_t getAbsoluteExpiryTime() const + { + return 0;//not yet supported + } + + bool hasCreationTime() const + { + return false;//not yet supported + } + + int64_t getCreationTime() const + { + return 0;//not yet supported + } + + bool hasGroupId() const + { + return false;//not yet supported + } + + std::string getGroupId() const + { + return EMPTY;//not yet supported + } + + bool hasGroupSequence() const + { + return false;//not yet supported + } + + uint32_t getGroupSequence() const + { + return 0;//not yet supported + } + + bool hasReplyToGroupId() const + { + return false;//not yet supported + } + + std::string getReplyToGroupId() const + { + return EMPTY;//not yet supported + } + private: + const qpid::messaging::MessageImpl& msg; + const std::string subject; +}; + +bool changedSubject(const qpid::messaging::MessageImpl& msg, const qpid::messaging::Address& address) +{ + return address.getSubject().size() && address.getSubject() != msg.getSubject(); +} + +} + +SenderContext::Delivery::Delivery(int32_t i) : id(i), token(0) {} + +void SenderContext::Delivery::encode(const qpid::messaging::MessageImpl& msg, const qpid::messaging::Address& address) +{ + boost::shared_ptr<const EncodedMessage> original = msg.getEncoded(); + + if (original && !changedSubject(msg, address)) { //still have the content as received, send at least the bare message unaltered + //do we need to alter the header? are durable, priority, ttl, first-acquirer, delivery-count different from what was received? + if (original->hasHeaderChanged(msg)) { + //since as yet have no annotations, just write the revised header then the rest of the message as received + encoded.resize(16/*max header size*/ + original->getBareMessage().size); + qpid::amqp::MessageEncoder encoder(encoded.getData(), encoded.getSize()); + HeaderAdapter header(msg); + encoder.writeHeader(header); + ::memcpy(encoded.getData() + encoder.getPosition(), original->getBareMessage().data, original->getBareMessage().size); + } else { + //since as yet have no annotations, if the header hasn't + //changed and we still have the original bare message, can + //send the entire content as is + encoded.resize(original->getSize()); + ::memcpy(encoded.getData(), original->getData(), original->getSize()); + } + } else { + HeaderAdapter header(msg); + PropertiesAdapter properties(msg, address.getSubject()); + //compute size: + encoded.resize(qpid::amqp::MessageEncoder::getEncodedSize(header, properties, msg.getHeaders(), msg.getBytes())); + QPID_LOG(debug, "Sending message, buffer is " << encoded.getSize() << " bytes") + qpid::amqp::MessageEncoder encoder(encoded.getData(), encoded.getSize()); + //write header: + encoder.writeHeader(header); + //write delivery-annotations, write message-annotations (none yet supported) + //write properties + encoder.writeProperties(properties); + //write application-properties + encoder.writeApplicationProperties(msg.getHeaders()); + //write body + if (msg.getBytes().size()) encoder.writeBinary(msg.getBytes(), &qpid::amqp::message::DATA);//structured content not yet directly supported + if (encoder.getPosition() < encoded.getSize()) { + QPID_LOG(debug, "Trimming buffer from " << encoded.getSize() << " to " << encoder.getPosition()); + encoded.trim(encoder.getPosition()); + } + //write footer (no annotations yet supported) + } +} +void SenderContext::Delivery::send(pn_link_t* sender) +{ + pn_delivery_tag_t tag; + tag.size = sizeof(id); + tag.bytes = reinterpret_cast<const char*>(&id); + token = pn_delivery(sender, tag); + pn_link_send(sender, encoded.getData(), encoded.getSize()); + pn_link_advance(sender); +} + +bool SenderContext::Delivery::accepted() +{ + return pn_delivery_remote_state(token) == PN_ACCEPTED; +} +void SenderContext::Delivery::settle() +{ + pn_delivery_settle(token); +} +void SenderContext::configure() const +{ + configure(pn_link_target(sender)); +} +void SenderContext::configure(pn_terminus_t* target) const +{ + pn_terminus_set_address(target, address.getName().c_str()); + //dynamic create: + AddressHelper helper(address); + if (helper.createEnabled(AddressHelper::FOR_SENDER)) { + helper.setNodeProperties(target); + } +} + +bool SenderContext::settled() +{ + return processUnsettled() == 0; +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/SenderContext.h b/cpp/src/qpid/messaging/amqp/SenderContext.h new file mode 100644 index 0000000000..3595379e70 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SenderContext.h @@ -0,0 +1,90 @@ +#ifndef QPID_MESSAGING_AMQP_SENDERCONTEXT_H +#define QPID_MESSAGING_AMQP_SENDERCONTEXT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <deque> +#include <string> +#include <vector> +#include "qpid/sys/IntegerTypes.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/amqp/EncodedMessage.h" + +struct pn_delivery_t; +struct pn_link_t; +struct pn_session_t; +struct pn_terminus_t; + +namespace qpid { +namespace messaging { + +class Message; +class MessageImpl; + +namespace amqp { +/** + * + */ +class SenderContext +{ + public: + class Delivery + { + public: + Delivery(int32_t id); + void encode(const qpid::messaging::MessageImpl& message, const qpid::messaging::Address&); + void send(pn_link_t*); + bool accepted(); + void settle(); + private: + int32_t id; + pn_delivery_t* token; + EncodedMessage encoded; + }; + + SenderContext(pn_session_t* session, const std::string& name, const qpid::messaging::Address& target); + ~SenderContext(); + void close(); + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t getUnsettled(); + const std::string& getName() const; + const std::string& getTarget() const; + Delivery* send(const qpid::messaging::Message& message); + void configure() const; + bool settled(); + private: + friend class ConnectionContext; + typedef std::deque<Delivery> Deliveries; + + const std::string name; + const qpid::messaging::Address address; + pn_link_t* sender; + int32_t nextId; + Deliveries deliveries; + uint32_t capacity; + + uint32_t processUnsettled(); + void configure(pn_terminus_t*) const; +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_SENDERCONTEXT_H*/ diff --git a/cpp/src/qpid/messaging/amqp/SenderHandle.cpp b/cpp/src/qpid/messaging/amqp/SenderHandle.cpp new file mode 100644 index 0000000000..b7168e5b31 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SenderHandle.cpp @@ -0,0 +1,75 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "SenderHandle.h" +#include "ConnectionContext.h" +#include "SessionContext.h" +#include "SessionHandle.h" +#include "SenderContext.h" +#include "qpid/messaging/Duration.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace messaging { +namespace amqp { + +SenderHandle::SenderHandle(boost::shared_ptr<ConnectionContext> c, + boost::shared_ptr<SessionContext> s, + boost::shared_ptr<SenderContext> sndr +) : connection(c), session(s), sender(sndr) {} + +void SenderHandle::send(const Message& message, bool sync) +{ + connection->send(sender, message, sync); +} + +void SenderHandle::close() +{ + session->closeSender(getName()); +} + +void SenderHandle::setCapacity(uint32_t capacity) +{ + connection->setCapacity(sender, capacity); +} + +uint32_t SenderHandle::getCapacity() +{ + return connection->getCapacity(sender); +} + +uint32_t SenderHandle::getUnsettled() +{ + return connection->getUnsettled(sender); +} + +const std::string& SenderHandle::getName() const +{ + return sender->getName(); +} + +qpid::messaging::Session SenderHandle::getSession() const +{ + return qpid::messaging::Session(new SessionHandle(connection, session)); +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/SenderHandle.h b/cpp/src/qpid/messaging/amqp/SenderHandle.h new file mode 100644 index 0000000000..3c6b666582 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SenderHandle.h @@ -0,0 +1,58 @@ +#ifndef QPID_MESSAGING_AMQP_SENDERHANDLE_H +#define QPID_MESSAGING_AMQP_SENDERHANDLE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <boost/shared_ptr.hpp> +#include "qpid/messaging/SenderImpl.h" + +namespace qpid { +namespace messaging { +namespace amqp { + +class ConnectionContext; +class SessionContext; +class SenderContext; +/** + * + */ +class SenderHandle : public qpid::messaging::SenderImpl +{ + public: + SenderHandle(boost::shared_ptr<ConnectionContext> connection, + boost::shared_ptr<SessionContext> session, + boost::shared_ptr<SenderContext> sender + ); + void send(const Message& message, bool sync); + void close(); + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t getUnsettled(); + const std::string& getName() const; + Session getSession() const; + private: + boost::shared_ptr<ConnectionContext> connection; + boost::shared_ptr<SessionContext> session; + boost::shared_ptr<SenderContext> sender; +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_SENDERHANDLE_H*/ diff --git a/cpp/src/qpid/messaging/amqp/SessionContext.cpp b/cpp/src/qpid/messaging/amqp/SessionContext.cpp new file mode 100644 index 0000000000..9bdc658bc7 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SessionContext.cpp @@ -0,0 +1,156 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "SessionContext.h" +#include "SenderContext.h" +#include "ReceiverContext.h" +#include <boost/format.hpp> +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Duration.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/log/Statement.h" +extern "C" { +#include <proton/engine.h> +} + +namespace qpid { +namespace messaging { +namespace amqp { + +SessionContext::SessionContext(pn_connection_t* connection) : session(pn_session(connection)) {} +SessionContext::~SessionContext() +{ + senders.clear(); receivers.clear(); + pn_session_free(session); +} + +boost::shared_ptr<SenderContext> SessionContext::createSender(const qpid::messaging::Address& address) +{ + std::string name = address.getName(); + + int count = 1; + for (SenderMap::const_iterator i = senders.find(name); i != senders.end(); i = senders.find(name)) { + name = (boost::format("%1%_%2%") % address.getName() % ++count).str(); + } + boost::shared_ptr<SenderContext> s(new SenderContext(session, name, address)); + senders[name] = s; + return s; +} + +boost::shared_ptr<ReceiverContext> SessionContext::createReceiver(const qpid::messaging::Address& address) +{ + std::string name = address.getName(); + + int count = 1; + for (ReceiverMap::const_iterator i = receivers.find(name); i != receivers.end(); i = receivers.find(name)) { + name = (boost::format("%1%_%2%") % address.getName() % ++count).str(); + } + boost::shared_ptr<ReceiverContext> r(new ReceiverContext(session, name, address)); + receivers[name] = r; + return r; +} + +boost::shared_ptr<SenderContext> SessionContext::getSender(const std::string& name) const +{ + SenderMap::const_iterator i = senders.find(name); + if (i == senders.end()) { + throw qpid::messaging::KeyError(std::string("No such sender") + name); + } else { + return i->second; + } +} + +boost::shared_ptr<ReceiverContext> SessionContext::getReceiver(const std::string& name) const +{ + ReceiverMap::const_iterator i = receivers.find(name); + if (i == receivers.end()) { + throw qpid::messaging::KeyError(std::string("No such receiver") + name); + } else { + return i->second; + } +} + +void SessionContext::closeReceiver(const std::string&) +{ + +} + +void SessionContext::closeSender(const std::string&) +{ + +} + +boost::shared_ptr<ReceiverContext> SessionContext::nextReceiver(qpid::messaging::Duration /*timeout*/) +{ + return boost::shared_ptr<ReceiverContext>(); +} + +uint32_t SessionContext::getReceivable() +{ + return 0;//TODO +} + +uint32_t SessionContext::getUnsettledAcks() +{ + return 0;//TODO +} + +qpid::framing::SequenceNumber SessionContext::record(pn_delivery_t* delivery) +{ + qpid::framing::SequenceNumber id = next++; + unacked[id] = delivery; + QPID_LOG(debug, "Recorded delivery " << id << " -> " << delivery); + return id; +} + +void SessionContext::acknowledge(DeliveryMap::iterator begin, DeliveryMap::iterator end) +{ + for (DeliveryMap::iterator i = begin; i != end; ++i) { + QPID_LOG(debug, "Setting disposition for delivery " << i->first << " -> " << i->second); + pn_delivery_update(i->second, PN_ACCEPTED); + pn_delivery_settle(i->second);//TODO: different settlement modes? + } + unacked.erase(begin, end); +} + +void SessionContext::acknowledge() +{ + QPID_LOG(debug, "acknowledging all " << unacked.size() << " messages"); + acknowledge(unacked.begin(), unacked.end()); +} + +void SessionContext::acknowledge(const qpid::framing::SequenceNumber& id, bool cumulative) +{ + DeliveryMap::iterator i = unacked.find(id); + if (i != unacked.end()) { + acknowledge(cumulative ? unacked.begin() : i, ++i); + } +} + +bool SessionContext::settled() +{ + bool result = true; + for (SenderMap::iterator i = senders.begin(); i != senders.end(); ++i) { + if (!i->second->settled()) result = false; + } + return result; +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/SessionContext.h b/cpp/src/qpid/messaging/amqp/SessionContext.h new file mode 100644 index 0000000000..eca30a0e97 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SessionContext.h @@ -0,0 +1,81 @@ +#ifndef QPID_MESSAGING_AMQP_SESSIONCONTEXT_H +#define QPID_MESSAGING_AMQP_SESSIONCONTEXT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <map> +#include <string> +#include <boost/shared_ptr.hpp> +#include "qpid/sys/IntegerTypes.h" +#include "qpid/framing/SequenceNumber.h" + +struct pn_connection_t; +struct pn_session_t; +struct pn_delivery_t; + +namespace qpid { +namespace messaging { + +class Address; +class Duration; + +namespace amqp { + +class ConnectionContext; +class SenderContext; +class ReceiverContext; +/** + * + */ +class SessionContext +{ + public: + SessionContext(pn_connection_t*); + ~SessionContext(); + boost::shared_ptr<SenderContext> createSender(const qpid::messaging::Address& address); + boost::shared_ptr<ReceiverContext> createReceiver(const qpid::messaging::Address& address); + boost::shared_ptr<SenderContext> getSender(const std::string& name) const; + boost::shared_ptr<ReceiverContext> getReceiver(const std::string& name) const; + void closeReceiver(const std::string&); + void closeSender(const std::string&); + boost::shared_ptr<ReceiverContext> nextReceiver(qpid::messaging::Duration timeout); + uint32_t getReceivable(); + uint32_t getUnsettledAcks(); + bool settled(); + private: + friend class ConnectionContext; + typedef std::map<std::string, boost::shared_ptr<SenderContext> > SenderMap; + typedef std::map<std::string, boost::shared_ptr<ReceiverContext> > ReceiverMap; + typedef std::map<qpid::framing::SequenceNumber, pn_delivery_t*> DeliveryMap; + pn_session_t* session; + SenderMap senders; + ReceiverMap receivers; + DeliveryMap unacked; + qpid::framing::SequenceNumber next; + + qpid::framing::SequenceNumber record(pn_delivery_t*); + void acknowledge(); + void acknowledge(const qpid::framing::SequenceNumber& id, bool cummulative); + void acknowledge(DeliveryMap::iterator begin, DeliveryMap::iterator end); +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_SESSIONCONTEXT_H*/ diff --git a/cpp/src/qpid/messaging/amqp/SessionHandle.cpp b/cpp/src/qpid/messaging/amqp/SessionHandle.cpp new file mode 100644 index 0000000000..bf79771ca4 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SessionHandle.cpp @@ -0,0 +1,148 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "SessionHandle.h" +#include "ConnectionContext.h" +#include "ConnectionHandle.h" +#include "ReceiverContext.h" +#include "ReceiverHandle.h" +#include "SenderContext.h" +#include "SenderHandle.h" +#include "SessionContext.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Duration.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Session.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace messaging { +namespace amqp { + +SessionHandle::SessionHandle(boost::shared_ptr<ConnectionContext> c, boost::shared_ptr<SessionContext> s) : connection(c), session(s) {} + +void SessionHandle::commit() +{ + +} + +void SessionHandle::rollback() +{ + +} + +void SessionHandle::acknowledge(bool /*sync*/) +{ + connection->acknowledge(session, 0, false); +} + +void SessionHandle::acknowledge(qpid::messaging::Message& msg, bool cumulative) +{ + //TODO: handle cumulative + connection->acknowledge(session, &msg, cumulative); +} + +void SessionHandle::reject(qpid::messaging::Message&) +{ + +} + +void SessionHandle::release(qpid::messaging::Message&) +{ + +} + +void SessionHandle::close() +{ + connection->endSession(session); +} + +void SessionHandle::sync(bool /*block*/) +{ + +} + +qpid::messaging::Sender SessionHandle::createSender(const qpid::messaging::Address& address) +{ + boost::shared_ptr<SenderContext> sender = session->createSender(address); + connection->attach(session, sender); + return qpid::messaging::Sender(new SenderHandle(connection, session, sender)); +} + +qpid::messaging::Receiver SessionHandle::createReceiver(const qpid::messaging::Address& address) +{ + boost::shared_ptr<ReceiverContext> receiver = session->createReceiver(address); + connection->attach(session, receiver); + return qpid::messaging::Receiver(new ReceiverHandle(connection, session, receiver)); +} + +bool SessionHandle::nextReceiver(Receiver& receiver, Duration timeout) +{ + boost::shared_ptr<ReceiverContext> r = session->nextReceiver(timeout); + if (r) { + //TODO: cache handles in this case to avoid frequent allocation + receiver = qpid::messaging::Receiver(new ReceiverHandle(connection, session, r)); + return true; + } else { + return false; + } +} + +qpid::messaging::Receiver SessionHandle::nextReceiver(Duration timeout) +{ + qpid::messaging::Receiver r; + if (nextReceiver(r, timeout)) return r; + else throw qpid::messaging::NoMessageAvailable(); +} + +uint32_t SessionHandle::getReceivable() +{ + return session->getReceivable(); +} + +uint32_t SessionHandle::getUnsettledAcks() +{ + return session->getUnsettledAcks(); +} + +Sender SessionHandle::getSender(const std::string& name) const +{ + return qpid::messaging::Sender(new SenderHandle(connection, session, session->getSender(name))); +} + +Receiver SessionHandle::getReceiver(const std::string& name) const +{ + return qpid::messaging::Receiver(new ReceiverHandle(connection, session, session->getReceiver(name))); +} + +Connection SessionHandle::getConnection() const +{ + return qpid::messaging::Connection(new ConnectionHandle(connection)); +} + +void SessionHandle::checkError() +{ + +} + + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/SessionHandle.h b/cpp/src/qpid/messaging/amqp/SessionHandle.h new file mode 100644 index 0000000000..5e843aaacc --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SessionHandle.h @@ -0,0 +1,64 @@ +#ifndef QPID_MESSAGING_AMQP_SESSIONIMPL_H +#define QPID_MESSAGING_AMQP_SESSIONIMPL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <boost/shared_ptr.hpp> +#include "qpid/messaging/SessionImpl.h" + +namespace qpid { +namespace messaging { +namespace amqp { + +class ConnectionContext; +class SessionContext; +/** + * + */ +class SessionHandle : public qpid::messaging::SessionImpl +{ + public: + SessionHandle(boost::shared_ptr<ConnectionContext>, boost::shared_ptr<SessionContext>); + void commit(); + void rollback(); + void acknowledge(bool sync); + void acknowledge(Message&, bool); + void reject(Message&); + void release(Message&); + void close(); + void sync(bool block); + qpid::messaging::Sender createSender(const Address& address); + qpid::messaging::Receiver createReceiver(const Address& address); + bool nextReceiver(Receiver& receiver, Duration timeout); + qpid::messaging::Receiver nextReceiver(Duration timeout); + uint32_t getReceivable(); + uint32_t getUnsettledAcks(); + qpid::messaging::Sender getSender(const std::string& name) const; + qpid::messaging::Receiver getReceiver(const std::string& name) const; + qpid::messaging::Connection getConnection() const; + void checkError(); + private: + boost::shared_ptr<ConnectionContext> connection; + boost::shared_ptr<SessionContext> session; +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_SESSIONIMPL_H*/ diff --git a/cpp/src/qpid/messaging/amqp/SslTransport.cpp b/cpp/src/qpid/messaging/amqp/SslTransport.cpp new file mode 100644 index 0000000000..ea2375cb26 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SslTransport.cpp @@ -0,0 +1,160 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "SslTransport.h" +#include "TransportContext.h" +#include "qpid/sys/ssl/SslSocket.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/sys/Poller.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> +#include <boost/format.hpp> + +using namespace qpid::sys; +using namespace qpid::sys::ssl; + +namespace qpid { +namespace messaging { +namespace amqp { + +// Static constructor which registers connector here +namespace { +Transport* create(TransportContext& c, Poller::shared_ptr p) +{ + return new SslTransport(c, p); +} + +struct StaticInit +{ + StaticInit() + { + Transport::add("ssl", &create); + }; +} init; +} + + +SslTransport::SslTransport(TransportContext& c, boost::shared_ptr<Poller> p) : context(c), connector(0), aio(0), poller(p) {} + +void SslTransport::connect(const std::string& host, const std::string& port) +{ + assert(!connector); + assert(!aio); + connector = AsynchConnector::create( + socket, + host, port, + boost::bind(&SslTransport::connected, this, _1), + boost::bind(&SslTransport::failed, this, _3)); + + connector->start(poller); +} + +void SslTransport::failed(const std::string& msg) +{ + QPID_LOG(debug, "Failed to connect: " << msg); + socket.close(); + context.closed(); +} + +void SslTransport::connected(const Socket&) +{ + context.opened(); + aio = AsynchIO::create(socket, + boost::bind(&SslTransport::read, this, _1, _2), + boost::bind(&SslTransport::eof, this, _1), + boost::bind(&SslTransport::disconnected, this, _1), + boost::bind(&SslTransport::socketClosed, this, _1, _2), + 0, // nobuffs + boost::bind(&SslTransport::write, this, _1)); + aio->createBuffers(std::numeric_limits<uint16_t>::max());//note: AMQP 1.0 _can_ handle large frame sizes + id = boost::str(boost::format("[%1%]") % socket.getFullAddress()); + aio->start(poller); +} + +void SslTransport::read(AsynchIO&, AsynchIO::BufferBase* buffer) +{ + int32_t decoded = context.getCodec().decode(buffer->bytes+buffer->dataStart, buffer->dataCount); + if (decoded < buffer->dataCount) { + // Adjust buffer for used bytes and then "unread them" + buffer->dataStart += decoded; + buffer->dataCount -= decoded; + aio->unread(buffer); + } else { + // Give whole buffer back to aio subsystem + aio->queueReadBuffer(buffer); + } +} + +void SslTransport::write(AsynchIO&) +{ + if (context.getCodec().canEncode()) { + AsynchIO::BufferBase* buffer = aio->getQueuedBuffer(); + if (buffer) { + size_t encoded = context.getCodec().encode(buffer->bytes, buffer->byteCount); + + buffer->dataStart = 0; + buffer->dataCount = encoded; + aio->queueWrite(buffer); + } + } + +} + +void SslTransport::close() +{ + QPID_LOG(debug, id << " SslTransport closing..."); + if (aio) + aio->queueWriteClose(); +} + +void SslTransport::eof(AsynchIO&) +{ + close(); +} + +void SslTransport::disconnected(AsynchIO&) +{ + close(); + socketClosed(*aio, socket); +} + +void SslTransport::socketClosed(AsynchIO&, const Socket&) +{ + if (aio) + aio->queueForDeletion(); + context.closed(); + QPID_LOG(debug, id << " Socket closed"); +} + +void SslTransport::abort() +{ + if (aio) { + // Established connection + aio->requestCallback(boost::bind(&SslTransport::eof, this, _1)); + } +} + +void SslTransport::activateOutput() +{ + if (aio) aio->notifyPendingWrite(); +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/SslTransport.h b/cpp/src/qpid/messaging/amqp/SslTransport.h new file mode 100644 index 0000000000..f67ab95673 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/SslTransport.h @@ -0,0 +1,74 @@ +#ifndef QPID_MESSAGING_AMQP_SSLTRANSPORT_H +#define QPID_MESSAGING_AMQP_SSLTRANSPORT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/amqp/Transport.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/ssl/SslSocket.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace sys { +class ConnectionCodec; +class Poller; +class AsynchConnector; +class AsynchIO; +class AsynchIOBufferBase; +} + +namespace messaging { +namespace amqp { +class TransportContext; + +class SslTransport : public Transport +{ + public: + SslTransport(TransportContext&, boost::shared_ptr<qpid::sys::Poller> p); + + void connect(const std::string& host, const std::string& port); + + void activateOutput(); + void abort(); + void close(); + + private: + qpid::sys::ssl::SslSocket socket; + TransportContext& context; + qpid::sys::AsynchConnector* connector; + qpid::sys::AsynchIO* aio; + boost::shared_ptr<qpid::sys::Poller> poller; + bool closed; + std::string id; + + void connected(const qpid::sys::Socket&); + void failed(const std::string& msg); + void read(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); + void write(qpid::sys::AsynchIO&); + void eof(qpid::sys::AsynchIO&); + void disconnected(qpid::sys::AsynchIO&); + void socketClosed(qpid::sys::AsynchIO&, const qpid::sys::Socket&); + + friend class DriverImpl; +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_SSLTRANSPORT_H*/ diff --git a/cpp/src/qpid/messaging/amqp/TcpTransport.cpp b/cpp/src/qpid/messaging/amqp/TcpTransport.cpp new file mode 100644 index 0000000000..98022d634c --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/TcpTransport.cpp @@ -0,0 +1,162 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "TcpTransport.h" +#include "ConnectionContext.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/sys/Poller.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> +#include <boost/format.hpp> + +using namespace qpid::sys; + +namespace qpid { +namespace messaging { +namespace amqp { +// Static constructor which registers connector here +namespace { +Transport* create(TransportContext& c, Poller::shared_ptr p) +{ + return new TcpTransport(c, p); +} + +struct StaticInit +{ + StaticInit() + { + Transport::add("tcp", &create); + }; +} init; +} + +TcpTransport::TcpTransport(TransportContext& c, boost::shared_ptr<Poller> p) : socket(createSocket()), context(c), connector(0), aio(0), poller(p) {} + +void TcpTransport::connect(const std::string& host, const std::string& port) +{ + assert(!connector); + assert(!aio); + connector = AsynchConnector::create( + *socket, + host, port, + boost::bind(&TcpTransport::connected, this, _1), + boost::bind(&TcpTransport::failed, this, _3)); + + connector->start(poller); +} + +void TcpTransport::failed(const std::string& msg) +{ + QPID_LOG(debug, "Failed to connect: " << msg); + connector = 0; + socket->close(); + context.closed(); +} + +void TcpTransport::connected(const Socket&) +{ + context.opened(); + connector = 0; + aio = AsynchIO::create(*socket, + boost::bind(&TcpTransport::read, this, _1, _2), + boost::bind(&TcpTransport::eof, this, _1), + boost::bind(&TcpTransport::disconnected, this, _1), + boost::bind(&TcpTransport::socketClosed, this, _1, _2), + 0, // nobuffs + boost::bind(&TcpTransport::write, this, _1)); + aio->createBuffers(std::numeric_limits<uint16_t>::max());//note: AMQP 1.0 _can_ handle large frame sizes + id = boost::str(boost::format("[%1%]") % socket->getFullAddress()); + aio->start(poller); +} + +void TcpTransport::read(AsynchIO&, AsynchIO::BufferBase* buffer) +{ + int32_t decoded = context.getCodec().decode(buffer->bytes+buffer->dataStart, buffer->dataCount); + if (decoded < buffer->dataCount) { + // Adjust buffer for used bytes and then "unread them" + buffer->dataStart += decoded; + buffer->dataCount -= decoded; + aio->unread(buffer); + } else { + // Give whole buffer back to aio subsystem + aio->queueReadBuffer(buffer); + } +} + +void TcpTransport::write(AsynchIO&) +{ + if (context.getCodec().canEncode()) { + AsynchIO::BufferBase* buffer = aio->getQueuedBuffer(); + if (buffer) { + size_t encoded = context.getCodec().encode(buffer->bytes, buffer->byteCount); + + buffer->dataStart = 0; + buffer->dataCount = encoded; + aio->queueWrite(buffer); + } + } + +} + +void TcpTransport::close() +{ + QPID_LOG(debug, id << " TcpTransport closing..."); + if (aio) + aio->queueWriteClose(); +} + +void TcpTransport::eof(AsynchIO&) +{ + close(); +} + +void TcpTransport::disconnected(AsynchIO&) +{ + close(); + socketClosed(*aio, *socket); +} + +void TcpTransport::socketClosed(AsynchIO&, const Socket&) +{ + if (aio) + aio->queueForDeletion(); + context.closed(); + QPID_LOG(debug, id << " Socket closed"); +} + +void TcpTransport::abort() +{ + if (aio) { + // Established connection + aio->requestCallback(boost::bind(&TcpTransport::eof, this, _1)); + } else if (connector) { + // We're still connecting + connector->stop(); + failed("Connection timedout"); + } +} + +void TcpTransport::activateOutput() +{ + if (aio) aio->notifyPendingWrite(); +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/TcpTransport.h b/cpp/src/qpid/messaging/amqp/TcpTransport.h new file mode 100644 index 0000000000..8c1087abb3 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/TcpTransport.h @@ -0,0 +1,71 @@ +#ifndef QPID_MESSAGING_AMQP_TCPTRANSPORT_H +#define QPID_MESSAGING_AMQP_TCPTRANSPORT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/amqp/Transport.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Socket.h" +#include <boost/scoped_ptr.hpp> +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace sys { +class ConnectionCodec; +class AsynchConnector; +class AsynchIO; +class AsynchIOBufferBase; +class Poller; +} +namespace messaging { +namespace amqp { +class TransportContext; + +class TcpTransport : public Transport +{ + public: + TcpTransport(TransportContext&, boost::shared_ptr<qpid::sys::Poller>); + + void connect(const std::string& host, const std::string& port); + + void activateOutput(); + void abort(); + void close(); + + private: + boost::scoped_ptr<qpid::sys::Socket> socket; + TransportContext& context; + qpid::sys::AsynchConnector* connector; + qpid::sys::AsynchIO* aio; + boost::shared_ptr<qpid::sys::Poller> poller; + std::string id; + + void connected(const qpid::sys::Socket&); + void failed(const std::string& msg); + void read(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); + void write(qpid::sys::AsynchIO&); + void eof(qpid::sys::AsynchIO&); + void disconnected(qpid::sys::AsynchIO&); + void socketClosed(qpid::sys::AsynchIO&, const qpid::sys::Socket&); +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_TCPTRANSPORT_H*/ diff --git a/cpp/src/qpid/messaging/amqp/Transport.cpp b/cpp/src/qpid/messaging/amqp/Transport.cpp new file mode 100644 index 0000000000..21f51046b1 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/Transport.cpp @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/amqp/Transport.h" +#include "qpid/messaging/amqp/TransportContext.h" +#include <map> +#include <string> + +namespace qpid { +namespace messaging { +namespace amqp { +namespace { +typedef std::map<std::string, Transport::Factory*> Registry; + +Registry& theRegistry() +{ + static Registry factories; + return factories; +} +} + +Transport* Transport::create(const std::string& name, TransportContext& context, boost::shared_ptr<qpid::sys::Poller> poller) +{ + Registry::const_iterator i = theRegistry().find(name); + if (i != theRegistry().end()) return (i->second)(context, poller); + else return 0; +} +void Transport::add(const std::string& name, Factory* factory) +{ + theRegistry()[name] = factory; +} + +}}} // namespace qpid::messaging::amqp diff --git a/cpp/src/qpid/messaging/amqp/Transport.h b/cpp/src/qpid/messaging/amqp/Transport.h new file mode 100644 index 0000000000..ee021f645b --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/Transport.h @@ -0,0 +1,48 @@ +#ifndef QPID_MESSAGING_AMQP_TRANSPORT_H +#define QPID_MESSAGING_AMQP_TRANSPORT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/sys/OutputControl.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace sys { +class Poller; +} +namespace messaging { +namespace amqp { +class TransportContext; + +class Transport : public qpid::sys::OutputControl +{ + public: + virtual ~Transport() {} + virtual void connect(const std::string& host, const std::string& port) = 0; + virtual void close() = 0; + + typedef Transport* Factory(TransportContext&, boost::shared_ptr<qpid::sys::Poller>); + static Transport* create(const std::string& name, TransportContext&, boost::shared_ptr<qpid::sys::Poller>); + static void add(const std::string& name, Factory* factory); +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_TRANSPORT_H*/ diff --git a/cpp/src/qpid/messaging/amqp/TransportContext.h b/cpp/src/qpid/messaging/amqp/TransportContext.h new file mode 100644 index 0000000000..57192b5976 --- /dev/null +++ b/cpp/src/qpid/messaging/amqp/TransportContext.h @@ -0,0 +1,47 @@ +#ifndef QPID_MESSAGING_AMQP_TRANSPORTCONTEXT_H +#define QPID_MESSAGING_AMQP_TRANSPORTCONTEXT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +namespace qpid { +namespace sys { +class Codec; +} +namespace messaging { +namespace amqp { + +/** + * Interface to be supplied by 'users' of Transport interface, in + * order to provide codec and handle callbaskc for opening and closing + * of connection. + */ +class TransportContext +{ + public: + virtual ~TransportContext() {} + virtual qpid::sys::Codec& getCodec() = 0; + virtual void closed() = 0; + virtual void opened() = 0; + private: +}; +}}} // namespace qpid::messaging::amqp + +#endif /*!QPID_MESSAGING_AMQP_TRANSPORTCONTEXT_H*/ diff --git a/cpp/src/qpid/store/CMakeLists.txt b/cpp/src/qpid/store/CMakeLists.txt index 9abdf0ae3d..31623f8e84 100644 --- a/cpp/src/qpid/store/CMakeLists.txt +++ b/cpp/src/qpid/store/CMakeLists.txt @@ -42,6 +42,7 @@ if (CMAKE_COMPILER_IS_GNUCXX) set_target_properties (store PROPERTIES PREFIX "" + COMPILE_DEFINITIONS _IN_QPID_BROKER LINK_FLAGS "${GCC_CATCH_UNDEFINED}") endif (CMAKE_COMPILER_IS_GNUCXX) @@ -54,7 +55,9 @@ if (CMAKE_SYSTEM_NAME STREQUAL Windows) endif (MSVC) endif (CMAKE_SYSTEM_NAME STREQUAL Windows) -set_target_properties (store PROPERTIES VERSION ${qpidc_version}) +set_target_properties (store PROPERTIES + COMPILE_DEFINITIONS _IN_QPID_BROKER + VERSION ${qpidc_version}) install (TARGETS store # RUNTIME DESTINATION ${QPIDD_MODULE_DIR} COMPONENT ${QPID_COMPONENT_BROKER}) @@ -81,6 +84,7 @@ if (BUILD_MSSQL) ms-sql/State.cpp ms-sql/TplRecordset.cpp ms-sql/VariantHelper.cpp) + set_target_properties (mssql_store PROPERTIES COMPILE_DEFINITIONS _IN_QPID_BROKER) target_link_libraries (mssql_store qpidbroker qpidcommon ${Boost_PROGRAM_OPTIONS_LIBRARY}) install (TARGETS mssql_store # RUNTIME DESTINATION ${QPIDD_MODULE_DIR} @@ -110,6 +114,7 @@ if (BUILD_MSCLFS) ms-sql/State.cpp ms-sql/VariantHelper.cpp) include_directories(ms-sql) + set_target_properties (msclfs_store PROPERTIES COMPILE_DEFINITIONS _IN_QPID_BROKER) target_link_libraries (msclfs_store qpidbroker qpidcommon ${Boost_PROGRAM_OPTIONS_LIBRARY} clfsw32.lib) install (TARGETS msclfs_store # RUNTIME DESTINATION ${QPIDD_MODULE_DIR} diff --git a/cpp/src/qpid/store/MessageStorePlugin.cpp b/cpp/src/qpid/store/MessageStorePlugin.cpp index d72200c2ba..b869ecadbc 100644 --- a/cpp/src/qpid/store/MessageStorePlugin.cpp +++ b/cpp/src/qpid/store/MessageStorePlugin.cpp @@ -137,12 +137,6 @@ MessageStorePlugin::providerAvailable(const std::string name, QPID_LOG(warning, "Storage provider " << name << " duplicate; ignored."); } -void -MessageStorePlugin::truncateInit(const bool /*saveStoreContent*/) -{ - QPID_LOG(info, "Store: truncateInit"); -} - /** * Record the existence of a durable queue diff --git a/cpp/src/qpid/store/MessageStorePlugin.h b/cpp/src/qpid/store/MessageStorePlugin.h index 4a9bb2aecb..1fcde6683d 100644 --- a/cpp/src/qpid/store/MessageStorePlugin.h +++ b/cpp/src/qpid/store/MessageStorePlugin.h @@ -24,18 +24,22 @@ #include "qpid/Plugin.h" #include "qpid/Options.h" -#include "qpid/broker/Broker.h" #include "qpid/broker/MessageStore.h" -#include "qpid/broker/PersistableExchange.h" -#include "qpid/broker/PersistableMessage.h" -#include "qpid/broker/PersistableQueue.h" -#include "qpid/management/Manageable.h" +//#include "qpid/management/Manageable.h" #include <string> using namespace qpid; namespace qpid { + +namespace broker { +class Broker; +class PersistableExchange; +class PersistableMessage; +class PersistableQueue; +} + namespace store { class StorageProvider; @@ -82,18 +86,6 @@ class MessageStorePlugin : /** * @name Methods inherited from qpid::broker::MessageStore */ - //@{ - /** - * If called before recovery, will discard the database and reinitialize - * using an empty store. This is used when cluster nodes recover and - * must get their content from a cluster sync rather than directly from - * the store. - * - * @param saveStoreContent If true, the store's contents should be - * saved to a backup location before - * reinitializing the store content. - */ - virtual void truncateInit(const bool saveStoreContent = false); /** * Record the existence of a durable queue diff --git a/cpp/src/qpid/store/StorageProvider.h b/cpp/src/qpid/store/StorageProvider.h index d162cc58ec..de12ffb869 100644 --- a/cpp/src/qpid/store/StorageProvider.h +++ b/cpp/src/qpid/store/StorageProvider.h @@ -143,20 +143,6 @@ public: /** * @name Methods inherited from qpid::broker::MessageStore */ - //@{ - /** - * If called after init() but before recovery, will discard the database - * and reinitialize using an empty store dir. If @a pushDownStoreFiles - * is true, the content of the store dir will be moved to a backup dir - * inside the store dir. This is used when cluster nodes recover and must - * get thier content from a cluster sync rather than directly fromt the - * store. - * - * @param pushDownStoreFiles If true, will move content of the store dir - * into a subdir, leaving the store dir - * otherwise empty. - */ - virtual void truncateInit(const bool pushDownStoreFiles = false) = 0; /** * Record the existence of a durable queue diff --git a/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp b/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp index 586aaaf980..90785263d3 100644 --- a/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp +++ b/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp @@ -26,6 +26,7 @@ #include <string> #include <windows.h> #include <clfsw32.h> +#include <qpid/broker/Broker.h> #include <qpid/broker/RecoverableQueue.h> #include <qpid/log/Statement.h> #include <qpid/store/MessageStorePlugin.h> @@ -108,20 +109,6 @@ public: /** * @name Methods inherited from qpid::broker::MessageStore */ - //@{ - /** - * If called after init() but before recovery, will discard the database - * and reinitialize using an empty store dir. If @a pushDownStoreFiles - * is true, the content of the store dir will be moved to a backup dir - * inside the store dir. This is used when cluster nodes recover and must - * get their content from a cluster sync rather than directly from the - * store. - * - * @param pushDownStoreFiles If true, will move content of the store dir - * into a subdir, leaving the store dir - * otherwise empty. - */ - virtual void truncateInit(const bool pushDownStoreFiles = false); /** * Record the existence of a durable queue @@ -467,11 +454,6 @@ MSSqlClfsProvider::activate(MessageStorePlugin &store) } void -MSSqlClfsProvider::truncateInit(const bool pushDownStoreFiles) -{ -} - -void MSSqlClfsProvider::create(PersistableQueue& queue, const qpid::framing::FieldTable& /*args needed for jrnl*/) { diff --git a/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp b/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp index 7f22db3d02..1432cc8fca 100644 --- a/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp +++ b/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp @@ -92,20 +92,6 @@ public: /** * @name Methods inherited from qpid::broker::MessageStore */ - //@{ - /** - * If called after init() but before recovery, will discard the database - * and reinitialize using an empty store dir. If @a pushDownStoreFiles - * is true, the content of the store dir will be moved to a backup dir - * inside the store dir. This is used when cluster nodes recover and must - * get thier content from a cluster sync rather than directly fromt the - * store. - * - * @param pushDownStoreFiles If true, will move content of the store dir - * into a subdir, leaving the store dir - * otherwise empty. - */ - virtual void truncateInit(const bool pushDownStoreFiles = false); /** * Record the existence of a durable queue @@ -392,11 +378,6 @@ MSSqlProvider::activate(MessageStorePlugin &store) } void -MSSqlProvider::truncateInit(const bool pushDownStoreFiles) -{ -} - -void MSSqlProvider::create(PersistableQueue& queue, const qpid::framing::FieldTable& /*args needed for jrnl*/) { diff --git a/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp b/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp index b62a333df6..495f1a08c2 100644 --- a/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp +++ b/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp @@ -147,7 +147,7 @@ MessageRecordset::recover(qpid::broker::RecoveryManager& recoverer, // Now, do we need the rest of the content? long contentLength = blobSize - headerFieldLength - headerSize; - if (msg->loadContent(contentLength)) { + if (contentLength > 0 && msg->loadContent(contentLength)) { BlobAdapter content(contentLength); content = rs->Fields->Item["fieldTableBlob"]->GetChunk(contentLength); diff --git a/cpp/src/qpid/sys/AggregateOutput.cpp b/cpp/src/qpid/sys/AggregateOutput.cpp index fc95f46fb9..ebc5689ce5 100644 --- a/cpp/src/qpid/sys/AggregateOutput.cpp +++ b/cpp/src/qpid/sys/AggregateOutput.cpp @@ -32,8 +32,6 @@ void AggregateOutput::abort() { control.abort(); } void AggregateOutput::activateOutput() { control.activateOutput(); } -void AggregateOutput::giveReadCredit(int32_t credit) { control.giveReadCredit(credit); } - namespace { // Clear the busy flag and notify waiting threads in destructor. struct ScopedBusy { @@ -51,6 +49,7 @@ bool AggregateOutput::doOutput() { while (!tasks.empty()) { OutputTask* t=tasks.front(); tasks.pop_front(); + taskSet.erase(t); bool didOutput; { // Allow concurrent call to addOutputTask. @@ -59,7 +58,9 @@ bool AggregateOutput::doOutput() { didOutput = t->doOutput(); } if (didOutput) { - tasks.push_back(t); + if (taskSet.insert(t).second) { + tasks.push_back(t); + } return true; } } @@ -68,12 +69,15 @@ bool AggregateOutput::doOutput() { void AggregateOutput::addOutputTask(OutputTask* task) { Mutex::ScopedLock l(lock); - tasks.push_back(task); + if (taskSet.insert(task).second) { + tasks.push_back(task); + } } void AggregateOutput::removeOutputTask(OutputTask* task) { Mutex::ScopedLock l(lock); while (busy) lock.wait(); + taskSet.erase(task); tasks.erase(std::remove(tasks.begin(), tasks.end(), task), tasks.end()); } @@ -81,6 +85,7 @@ void AggregateOutput::removeAll() { Mutex::ScopedLock l(lock); while (busy) lock.wait(); + taskSet.clear(); tasks.clear(); } diff --git a/cpp/src/qpid/sys/AggregateOutput.h b/cpp/src/qpid/sys/AggregateOutput.h index d7c0ff29e3..e9dbd5a4cc 100644 --- a/cpp/src/qpid/sys/AggregateOutput.h +++ b/cpp/src/qpid/sys/AggregateOutput.h @@ -28,6 +28,7 @@ #include <algorithm> #include <deque> +#include <set> namespace qpid { namespace sys { @@ -44,9 +45,11 @@ namespace sys { class QPID_COMMON_CLASS_EXTERN AggregateOutput : public OutputTask, public OutputControl { typedef std::deque<OutputTask*> TaskList; + typedef std::set<OutputTask*> TaskSet; Monitor lock; TaskList tasks; + TaskSet taskSet; bool busy; OutputControl& control; @@ -56,7 +59,6 @@ class QPID_COMMON_CLASS_EXTERN AggregateOutput : public OutputTask, public Outpu // These may be called concurrently with any function. QPID_COMMON_EXTERN void abort(); QPID_COMMON_EXTERN void activateOutput(); - QPID_COMMON_EXTERN void giveReadCredit(int32_t); QPID_COMMON_EXTERN void addOutputTask(OutputTask* t); // These functions must not be called concurrently with each other. diff --git a/cpp/src/qpid/sys/AsynchIO.h b/cpp/src/qpid/sys/AsynchIO.h index b2eaaac9de..679665f8ad 100644 --- a/cpp/src/qpid/sys/AsynchIO.h +++ b/cpp/src/qpid/sys/AsynchIO.h @@ -21,9 +21,11 @@ * */ -#include "qpid/sys/IntegerTypes.h" #include "qpid/CommonImportExport.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/sys/SecuritySettings.h" + #include <string.h> #include <boost/function.hpp> @@ -56,6 +58,7 @@ class AsynchConnector { public: typedef boost::function1<void, const Socket&> ConnectedCallback; typedef boost::function3<void, const Socket&, int, const std::string&> FailedCallback; + typedef boost::function1<void, AsynchConnector&> RequestCallback; // Call create() to allocate a new AsynchConnector object with the // specified poller, addressing, and callbacks. @@ -70,6 +73,7 @@ public: FailedCallback failCb); virtual void start(boost::shared_ptr<Poller> poller) = 0; virtual void stop() {}; + virtual void requestCallback(RequestCallback) = 0; protected: AsynchConnector() {} virtual ~AsynchConnector() {} @@ -155,11 +159,11 @@ public: virtual void notifyPendingWrite() = 0; virtual void queueWriteClose() = 0; virtual bool writeQueueEmpty() = 0; - virtual void startReading() = 0; - virtual void stopReading() = 0; virtual void requestCallback(RequestCallback) = 0; virtual BufferBase* getQueuedBuffer() = 0; + virtual SecuritySettings getSecuritySettings() = 0; + protected: // Derived class manages lifetime; must be constructed using the // static create() method. Deletes not allowed from outside. diff --git a/cpp/src/qpid/sys/AsynchIOHandler.cpp b/cpp/src/qpid/sys/AsynchIOHandler.cpp index 2e117a3fb7..cf08b482e6 100644 --- a/cpp/src/qpid/sys/AsynchIOHandler.cpp +++ b/cpp/src/qpid/sys/AsynchIOHandler.cpp @@ -51,15 +51,15 @@ struct ProtocolTimeoutTask : public sys::TimerTask { } }; -AsynchIOHandler::AsynchIOHandler(const std::string& id, ConnectionCodec::Factory* f) : +AsynchIOHandler::AsynchIOHandler(const std::string& id, ConnectionCodec::Factory* f, bool isClient0, bool nodict0) : identifier(id), aio(0), factory(f), codec(0), reads(0), readError(false), - isClient(false), - readCredit(InfiniteCredit) + isClient(isClient0), + nodict(nodict0) {} AsynchIOHandler::~AsynchIOHandler() { @@ -97,25 +97,20 @@ void AsynchIOHandler::abort() { if (!readError) { aio->requestCallback(boost::bind(&AsynchIOHandler::eof, this, _1)); } + aio->queueWriteClose(); } void AsynchIOHandler::activateOutput() { aio->notifyPendingWrite(); } -// Input side -void AsynchIOHandler::giveReadCredit(int32_t credit) { - // Check whether we started in the don't about credit state - if (readCredit.boolCompareAndSwap(InfiniteCredit, credit)) - return; - // TODO In theory should be able to use an atomic operation before taking the lock - // but in practice there seems to be an unexplained race in that case - ScopedLock<Mutex> l(creditLock); - if (readCredit.fetchAndAdd(credit) != 0) - return; - assert(readCredit.get() >= 0); - if (readCredit.get() != 0) - aio->startReading(); +namespace { + SecuritySettings getSecuritySettings(AsynchIO* aio, bool nodict) + { + SecuritySettings settings = aio->getSecuritySettings(); + settings.nodict = nodict; + return settings; + } } void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) { @@ -123,26 +118,6 @@ void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) { return; } - // Check here for read credit - if (readCredit.get() != InfiniteCredit) { - if (readCredit.get() == 0) { - // FIXME aconway 2009-10-01: Workaround to avoid "false wakeups". - // readbuff is sometimes called with no credit. - // This should be fixed somewhere else to avoid such calls. - aio->unread(buff); - return; - } - // TODO In theory should be able to use an atomic operation before taking the lock - // but in practice there seems to be an unexplained race in that case - ScopedLock<Mutex> l(creditLock); - if (--readCredit == 0) { - assert(readCredit.get() >= 0); - if (readCredit.get() == 0) { - aio->stopReading(); - } - } - } - ++reads; size_t decoded = 0; if (codec) { // Already initiated @@ -168,13 +143,16 @@ void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) { QPID_LOG(debug, "RECV [" << identifier << "]: INIT(" << protocolInit << ")"); try { - codec = factory->create(protocolInit.getVersion(), *this, identifier, SecuritySettings()); + codec = factory->create(protocolInit.getVersion(), *this, identifier, getSecuritySettings(aio, nodict)); if (!codec) { //TODO: may still want to revise this... //send valid version header & close connection. write(framing::ProtocolInitiation(framing::highestProtocolVersion)); readError = true; aio->queueWriteClose(); + } else { + //read any further data that may already have been sent + decoded += codec->decode(buff->bytes+buff->dataStart+in.getPosition(), buff->dataCount-in.getPosition()); } } catch (const std::exception& e) { QPID_LOG(error, e.what()); @@ -223,7 +201,7 @@ void AsynchIOHandler::nobuffs(AsynchIO&) { void AsynchIOHandler::idle(AsynchIO&){ if (isClient && codec == 0) { - codec = factory->create(*this, identifier, SecuritySettings()); + codec = factory->create(*this, identifier, getSecuritySettings(aio, nodict)); write(framing::ProtocolInitiation(codec->getVersion())); // We've just sent the protocol negotiation so we can cancel the timeout for that // This is not ideal, because we've not received anything yet, but heartbeats will diff --git a/cpp/src/qpid/sys/AsynchIOHandler.h b/cpp/src/qpid/sys/AsynchIOHandler.h index fd0bc140e5..d93e24fd4c 100644 --- a/cpp/src/qpid/sys/AsynchIOHandler.h +++ b/cpp/src/qpid/sys/AsynchIOHandler.h @@ -51,24 +51,19 @@ class AsynchIOHandler : public OutputControl { uint32_t reads; bool readError; bool isClient; - AtomicValue<int32_t> readCredit; - static const int32_t InfiniteCredit = -1; - Mutex creditLock; + bool nodict; boost::intrusive_ptr<sys::TimerTask> timeoutTimerTask; void write(const framing::ProtocolInitiation&); public: - QPID_COMMON_EXTERN AsynchIOHandler(const std::string& id, qpid::sys::ConnectionCodec::Factory* f ); + QPID_COMMON_EXTERN AsynchIOHandler(const std::string& id, qpid::sys::ConnectionCodec::Factory* f, bool isClient, bool nodict); QPID_COMMON_EXTERN ~AsynchIOHandler(); QPID_COMMON_EXTERN void init(AsynchIO* a, Timer& timer, uint32_t maxTime); - QPID_COMMON_INLINE_EXTERN void setClient() { isClient = true; } - // Output side QPID_COMMON_EXTERN void abort(); QPID_COMMON_EXTERN void activateOutput(); - QPID_COMMON_EXTERN void giveReadCredit(int32_t credit); // Input side QPID_COMMON_EXTERN void readbuff(AsynchIO& aio, AsynchIOBufferBase* buff); diff --git a/cpp/src/qpid/sys/ClusterSafe.h b/cpp/src/qpid/sys/ClusterSafe.h deleted file mode 100644 index 27e4eb46a5..0000000000 --- a/cpp/src/qpid/sys/ClusterSafe.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef QPID_SYS_CLUSTERSAFE_H -#define QPID_SYS_CLUSTERSAFE_H - -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "qpid/CommonImportExport.h" - -namespace qpid { -namespace sys { - -/** - * Assertion to add to code that modifies clustered state. - * - * In a non-clustered broker this is a no-op. - * - * In a clustered broker, checks that it is being called - * in a context where it is safe to modify clustered state. - * If not it aborts the process as this is a serious bug. - * - * This function is in the common library rather than the cluster - * library because it is called by code in the broker library. - */ -QPID_COMMON_EXTERN void assertClusterSafe(); - -/** - * In a non-clustered broker, returns true. - * - * In a clustered broker returns true if we are in a context where it - * is safe to modify cluster state. - * - * This function is in the common library rather than the cluster - * library because it is called by code in the broker library. - */ -QPID_COMMON_EXTERN bool isClusterSafe(); - -/** - * Mark a scope as cluster safe. Sets isClusterSafe in constructor and resets - * to previous value in destructor. - */ -class ClusterSafeScope { - public: - ClusterSafeScope(); - ~ClusterSafeScope(); - private: - bool save; -}; - -/** - * Mark a scope as cluster unsafe. Clears isClusterSafe in constructor and resets - * to previous value in destructor. - */ -class ClusterUnsafeScope { - public: - QPID_COMMON_EXTERN ClusterUnsafeScope(); - QPID_COMMON_EXTERN ~ClusterUnsafeScope(); - private: - bool save; -}; - -/** - * Enable cluster-safe assertions. By default they are no-ops. - * Called by cluster code. - */ -void enableClusterSafe(); - -}} // namespace qpid::sys - -#endif /*!QPID_SYS_CLUSTERSAFE_H*/ diff --git a/cpp/src/qpid/sys/Codec.h b/cpp/src/qpid/sys/Codec.h index ace721fbcc..e398403e47 100644 --- a/cpp/src/qpid/sys/Codec.h +++ b/cpp/src/qpid/sys/Codec.h @@ -42,7 +42,7 @@ class Codec /** Encode into buffer, return number of bytes encoded */ - virtual std::size_t encode(const char* buffer, std::size_t size) = 0; + virtual std::size_t encode(char* buffer, std::size_t size) = 0; /** Return true if we have data to encode */ virtual bool canEncode() = 0; diff --git a/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h b/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h index 95a08d15ae..53d56ad716 100644 --- a/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h +++ b/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h @@ -45,7 +45,6 @@ class ConnectionOutputHandlerPtr : public ConnectionOutputHandler size_t getBuffered() const { return next->getBuffered(); } void abort() { next->abort(); } void activateOutput() { next->activateOutput(); } - void giveReadCredit(int32_t credit) { next->giveReadCredit(credit); } void send(framing::AMQFrame& f) { next->send(f); } private: diff --git a/cpp/src/qpid/sys/FileSysDir.h b/cpp/src/qpid/sys/FileSysDir.h index ffe7823f0a..7432fe39c9 100755 --- a/cpp/src/qpid/sys/FileSysDir.h +++ b/cpp/src/qpid/sys/FileSysDir.h @@ -54,6 +54,15 @@ class FileSysDir void mkdir(void); + typedef void Callback(const std::string&); + + /** + * Call the Callback function for every regular file in the directory + * + * @param cb Callback function that receives the full path to the file + */ + void forEachFile(Callback cb) const; + std::string getPath () { return dirPath; } }; diff --git a/cpp/src/qpid/sys/OutputControl.h b/cpp/src/qpid/sys/OutputControl.h index eae99beb0f..0d801e9d16 100644 --- a/cpp/src/qpid/sys/OutputControl.h +++ b/cpp/src/qpid/sys/OutputControl.h @@ -1,3 +1,6 @@ +#ifndef QPID_SYS_OUTPUT_CONTROL_H +#define QPID_SYS_OUTPUT_CONTROL_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -21,9 +24,6 @@ #include "qpid/sys/IntegerTypes.h" -#ifndef _OutputControl_ -#define _OutputControl_ - namespace qpid { namespace sys { @@ -33,11 +33,10 @@ namespace sys { virtual ~OutputControl() {} virtual void abort() = 0; virtual void activateOutput() = 0; - virtual void giveReadCredit(int32_t credit) = 0; }; } } -#endif +#endif /*!QPID_SYS_OUTPUT_CONTROL_H*/ diff --git a/cpp/src/qpid/sys/ProtocolFactory.h b/cpp/src/qpid/sys/ProtocolFactory.h index 4d198a92da..236398c111 100644 --- a/cpp/src/qpid/sys/ProtocolFactory.h +++ b/cpp/src/qpid/sys/ProtocolFactory.h @@ -42,10 +42,10 @@ class ProtocolFactory : public qpid::SharedObject<ProtocolFactory> virtual void accept(boost::shared_ptr<Poller>, ConnectionCodec::Factory*) = 0; virtual void connect( boost::shared_ptr<Poller>, + const std::string& name, const std::string& host, const std::string& port, ConnectionCodec::Factory* codec, ConnectFailedCallback failed) = 0; - virtual bool supports(const std::string& /*capability*/) { return false; } }; inline ProtocolFactory::~ProtocolFactory() {} diff --git a/cpp/src/qpid/sys/RdmaIOPlugin.cpp b/cpp/src/qpid/sys/RdmaIOPlugin.cpp index b491d28d0a..51cc0ed109 100644 --- a/cpp/src/qpid/sys/RdmaIOPlugin.cpp +++ b/cpp/src/qpid/sys/RdmaIOPlugin.cpp @@ -23,6 +23,7 @@ #include "qpid/Plugin.h" #include "qpid/broker/Broker.h" +#include "qpid/broker/NameGenerator.h" #include "qpid/framing/AMQP_HighestVersion.h" #include "qpid/log/Statement.h" #include "qpid/sys/rdma/RdmaIO.h" @@ -67,7 +68,6 @@ class RdmaIOHandler : public OutputControl { void close(); void abort(); void activateOutput(); - void giveReadCredit(int32_t credit); void initProtocolOut(); // Input side @@ -83,7 +83,7 @@ class RdmaIOHandler : public OutputControl { }; RdmaIOHandler::RdmaIOHandler(Rdma::Connection::intrusive_ptr c, qpid::sys::ConnectionCodec::Factory* f) : - identifier(c->getFullName()), + identifier(broker::QPID_NAME_PREFIX+c->getFullName()), factory(f), codec(0), readError(false), @@ -199,10 +199,6 @@ void RdmaIOHandler::full(Rdma::AsynchIO&) { QPID_LOG(debug, "Rdma: buffer full [" << identifier << "]"); } -// TODO: Dummy implementation of read throttling -void RdmaIOHandler::giveReadCredit(int32_t) { -} - // The logic here is subtly different from TCP as RDMA is message oriented // so we define that an RDMA message is a frame - in this case there is no putting back // of any message remainder - there shouldn't be any. And what we read here can't be @@ -250,7 +246,7 @@ class RdmaIOProtocolFactory : public ProtocolFactory { public: RdmaIOProtocolFactory(int16_t port, int backlog); void accept(Poller::shared_ptr, ConnectionCodec::Factory*); - void connect(Poller::shared_ptr, const string& host, const std::string& port, ConnectionCodec::Factory*, ConnectFailedCallback); + void connect(Poller::shared_ptr, const std::string& name, const string& host, const std::string& port, ConnectionCodec::Factory*, ConnectFailedCallback); uint16_t getPort() const; @@ -371,6 +367,7 @@ void RdmaIOProtocolFactory::connected(Poller::shared_ptr poller, Rdma::Connectio void RdmaIOProtocolFactory::connect( Poller::shared_ptr poller, + const std::string& /*name*/, const std::string& host, const std::string& port, ConnectionCodec::Factory* f, ConnectFailedCallback failed) diff --git a/cpp/src/qpid/sys/SecurityLayer.h b/cpp/src/qpid/sys/SecurityLayer.h index 52bc40e352..317ada16de 100644 --- a/cpp/src/qpid/sys/SecurityLayer.h +++ b/cpp/src/qpid/sys/SecurityLayer.h @@ -33,8 +33,12 @@ namespace sys { class SecurityLayer : public Codec { public: + SecurityLayer(int ssf_) : ssf(ssf_) {} + int getSsf() const { return ssf; } virtual void init(Codec*) = 0; virtual ~SecurityLayer() {} + private: + int ssf; }; }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/SecuritySettings.h b/cpp/src/qpid/sys/SecuritySettings.h index bfcd08fd0f..d595cad660 100644 --- a/cpp/src/qpid/sys/SecuritySettings.h +++ b/cpp/src/qpid/sys/SecuritySettings.h @@ -21,6 +21,8 @@ * under the License. * */ +#include <string> + namespace qpid { namespace sys { diff --git a/cpp/src/qpid/sys/Socket.h b/cpp/src/qpid/sys/Socket.h index defec4879c..38183bd5fd 100644 --- a/cpp/src/qpid/sys/Socket.h +++ b/cpp/src/qpid/sys/Socket.h @@ -22,7 +22,6 @@ * */ -#include "qpid/sys/IOHandle.h" #include "qpid/sys/IntegerTypes.h" #include "qpid/CommonImportExport.h" #include <string> @@ -31,45 +30,43 @@ namespace qpid { namespace sys { class Duration; +class IOHandle; class SocketAddress; -class QPID_COMMON_CLASS_EXTERN Socket : public IOHandle +class Socket { public: - /** Create a socket wrapper for descriptor. */ - QPID_COMMON_EXTERN Socket(); + virtual ~Socket() {}; - /** Create a new Socket which is the same address family as this one */ - QPID_COMMON_EXTERN Socket* createSameTypeSocket() const; + virtual operator const IOHandle&() const = 0; /** Set socket non blocking */ - void setNonblocking() const; + virtual void setNonblocking() const = 0; - QPID_COMMON_EXTERN void setTcpNoDelay() const; + virtual void setTcpNoDelay() const = 0; - QPID_COMMON_EXTERN void connect(const std::string& host, const std::string& port) const; - QPID_COMMON_EXTERN void connect(const SocketAddress&) const; + virtual void connect(const SocketAddress&) const = 0; + virtual void finishConnect(const SocketAddress&) const = 0; - QPID_COMMON_EXTERN void close() const; + virtual void close() const = 0; /** Bind to a port and start listening. *@param port 0 means choose an available port. *@param backlog maximum number of pending connections. *@return The bound port. */ - QPID_COMMON_EXTERN int listen(const std::string& host = "", const std::string& port = "0", int backlog = 10) const; - QPID_COMMON_EXTERN int listen(const SocketAddress&, int backlog = 10) const; + virtual int listen(const SocketAddress&, int backlog = 10) const = 0; /** * Returns an address (host and port) for the remote end of the * socket */ - QPID_COMMON_EXTERN std::string getPeerAddress() const; + virtual std::string getPeerAddress() const = 0; /** * Returns an address (host and port) for the local end of the * socket */ - QPID_COMMON_EXTERN std::string getLocalAddress() const; + virtual std::string getLocalAddress() const = 0; /** * Returns the full address of the connection: local and remote host and port. @@ -80,31 +77,24 @@ public: * Returns the error code stored in the socket. This may be used * to determine the result of a non-blocking connect. */ - QPID_COMMON_EXTERN int getError() const; + virtual int getError() const = 0; /** Accept a connection from a socket that is already listening * and has an incoming connection */ - QPID_COMMON_EXTERN Socket* accept() const; + virtual Socket* accept() const = 0; - // TODO The following are raw operations, maybe they need better wrapping? - QPID_COMMON_EXTERN int read(void *buf, size_t count) const; - QPID_COMMON_EXTERN int write(const void *buf, size_t count) const; + virtual int read(void *buf, size_t count) const = 0; + virtual int write(const void *buf, size_t count) const = 0; -private: - /** Create socket */ - void createSocket(const SocketAddress&) const; - -public: - /** Construct socket with existing handle */ - Socket(IOHandlePrivate*); - -protected: - mutable std::string localname; - mutable std::string peername; - mutable bool nonblocking; - mutable bool nodelay; + /* Transport security related: */ + virtual int getKeyLen() const = 0; + virtual std::string getClientAuthId() const = 0; }; +/** Make the default socket for whatever platform we are executing on + */ +QPID_COMMON_EXTERN Socket* createSocket(); + }} #endif /*!_sys_Socket_h*/ diff --git a/cpp/src/qpid/sys/SocketAddress.h b/cpp/src/qpid/sys/SocketAddress.h index dcca109d94..a4da5cca79 100644 --- a/cpp/src/qpid/sys/SocketAddress.h +++ b/cpp/src/qpid/sys/SocketAddress.h @@ -44,11 +44,12 @@ public: QPID_COMMON_EXTERN bool nextAddress(); QPID_COMMON_EXTERN std::string asString(bool numeric=true) const; + QPID_COMMON_EXTERN std::string getHost() const; QPID_COMMON_EXTERN void setAddrInfoPort(uint16_t port); QPID_COMMON_EXTERN static std::string asString(::sockaddr const * const addr, size_t addrlen); QPID_COMMON_EXTERN static uint16_t getPort(::sockaddr const * const addr); - + private: std::string host; diff --git a/cpp/src/qpid/sys/SslPlugin.cpp b/cpp/src/qpid/sys/SslPlugin.cpp index 069e97758e..a40da24eb8 100644 --- a/cpp/src/qpid/sys/SslPlugin.cpp +++ b/cpp/src/qpid/sys/SslPlugin.cpp @@ -22,19 +22,19 @@ #include "qpid/sys/ProtocolFactory.h" #include "qpid/Plugin.h" -#include "qpid/sys/ssl/check.h" -#include "qpid/sys/ssl/util.h" -#include "qpid/sys/ssl/SslHandler.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/NameGenerator.h" +#include "qpid/log/Statement.h" #include "qpid/sys/AsynchIOHandler.h" #include "qpid/sys/AsynchIO.h" -#include "qpid/sys/ssl/SslIo.h" +#include "qpid/sys/ssl/util.h" #include "qpid/sys/ssl/SslSocket.h" -#include "qpid/broker/Broker.h" -#include "qpid/log/Statement.h" +#include "qpid/sys/SocketAddress.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/sys/Poller.h" #include <boost/bind.hpp> -#include <memory> - +#include <boost/ptr_container/ptr_vector.hpp> namespace qpid { namespace sys { @@ -64,38 +64,32 @@ struct SslServerOptions : ssl::SslOptions } }; -template <class T> -class SslProtocolFactoryTmpl : public ProtocolFactory { - private: - - typedef SslAcceptorTmpl<T> SslAcceptor; - +class SslProtocolFactory : public ProtocolFactory { + boost::ptr_vector<Socket> listeners; + boost::ptr_vector<AsynchAcceptor> acceptors; Timer& brokerTimer; uint32_t maxNegotiateTime; + uint16_t listeningPort; const bool tcpNoDelay; - T listener; - const uint16_t listeningPort; - std::auto_ptr<SslAcceptor> acceptor; bool nodict; public: - SslProtocolFactoryTmpl(const SslServerOptions&, int backlog, bool nodelay, Timer& timer, uint32_t maxTime); + SslProtocolFactory(const qpid::broker::Broker::Options& opts, const SslServerOptions& options, + Timer& timer); void accept(Poller::shared_ptr, ConnectionCodec::Factory*); - void connect(Poller::shared_ptr, const std::string& host, const std::string& port, + void connect(Poller::shared_ptr, const std::string& name, const std::string& host, const std::string& port, ConnectionCodec::Factory*, - boost::function2<void, int, std::string> failed); + ConnectFailedCallback); uint16_t getPort() const; - bool supports(const std::string& capability); private: - void established(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*, - bool isClient); + void establishedIncoming(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*); + void establishedOutgoing(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*, const std::string&); + void establishedCommon(AsynchIOHandler*, Poller::shared_ptr , const Socket&); + void connectFailed(const Socket&, int, const std::string&, ConnectFailedCallback); }; -typedef SslProtocolFactoryTmpl<SslSocket> SslProtocolFactory; -typedef SslProtocolFactoryTmpl<SslMuxSocket> SslMuxProtocolFactory; - // Static instance to initialise plugin static struct SslPlugin : public Plugin { @@ -124,7 +118,7 @@ static struct SslPlugin : public Plugin { } } } - + void initialize(Target& target) { QPID_LOG(trace, "Initialising SSL plugin"); broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); @@ -139,19 +133,16 @@ static struct SslPlugin : public Plugin { const broker::Broker::Options& opts = broker->getOptions(); - ProtocolFactory::shared_ptr protocol(options.multiplex ? - static_cast<ProtocolFactory*>(new SslMuxProtocolFactory(options, - opts.connectionBacklog, - opts.tcpNoDelay, - broker->getTimer(), opts.maxNegotiateTime)) : - static_cast<ProtocolFactory*>(new SslProtocolFactory(options, - opts.connectionBacklog, - opts.tcpNoDelay, - broker->getTimer(), opts.maxNegotiateTime))); - QPID_LOG(notice, "Listening for " << - (options.multiplex ? "SSL or TCP" : "SSL") << - " connections on TCP port " << - protocol->getPort()); + ProtocolFactory::shared_ptr protocol( + static_cast<ProtocolFactory*>(new SslProtocolFactory(opts, options, broker->getTimer()))); + + if (protocol->getPort()!=0 ) { + QPID_LOG(notice, "Listening for " << + (options.multiplex ? "SSL or TCP" : "SSL") << + " connections on TCP/TCP6 port " << + protocol->getPort()); + } + broker->registerProtocolFactory("ssl", protocol); } catch (const std::exception& e) { QPID_LOG(error, "Failed to initialise SSL plugin: " << e.what()); @@ -161,99 +152,133 @@ static struct SslPlugin : public Plugin { } } sslPlugin; -template <class T> -SslProtocolFactoryTmpl<T>::SslProtocolFactoryTmpl(const SslServerOptions& options, int backlog, bool nodelay, Timer& timer, uint32_t maxTime) : +namespace { + // Expand list of Interfaces and addresses to a list of addresses + std::vector<std::string> expandInterfaces(const std::vector<std::string>& interfaces) { + std::vector<std::string> addresses; + // If there are no specific interfaces listed use a single "" to listen on every interface + if (interfaces.empty()) { + addresses.push_back(""); + return addresses; + } + for (unsigned i = 0; i < interfaces.size(); ++i) { + const std::string& interface = interfaces[i]; + if (!(SystemInfo::getInterfaceAddresses(interface, addresses))) { + // We don't have an interface of that name - + // Check for IPv6 ('[' ']') brackets and remove them + // then pass to be looked up directly + if (interface[0]=='[' && interface[interface.size()-1]==']') { + addresses.push_back(interface.substr(1, interface.size()-2)); + } else { + addresses.push_back(interface); + } + } + } + return addresses; + } +} + +SslProtocolFactory::SslProtocolFactory(const qpid::broker::Broker::Options& opts, const SslServerOptions& options, + Timer& timer) : brokerTimer(timer), - maxNegotiateTime(maxTime), - tcpNoDelay(nodelay), listeningPort(listener.listen(options.port, backlog, options.certName, options.clientAuth)), + maxNegotiateTime(opts.maxNegotiateTime), + tcpNoDelay(opts.tcpNoDelay), nodict(options.nodict) -{} - -void SslEstablished(Poller::shared_ptr poller, const qpid::sys::SslSocket& s, - ConnectionCodec::Factory* f, bool isClient, - Timer& timer, uint32_t maxTime, bool tcpNoDelay, bool nodict) { - qpid::sys::ssl::SslHandler* async = new qpid::sys::ssl::SslHandler(s.getFullAddress(), f, nodict); - - if (tcpNoDelay) { - s.setTcpNoDelay(tcpNoDelay); - QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress()); +{ + std::vector<std::string> addresses = expandInterfaces(opts.listenInterfaces); + if (addresses.empty()) { + // We specified some interfaces, but couldn't find addresses for them + QPID_LOG(warning, "SSL: No specified network interfaces found: Not Listening"); + listeningPort = 0; } - if (isClient) { - async->setClient(); + for (unsigned i = 0; i<addresses.size(); ++i) { + QPID_LOG(debug, "Using interface: " << addresses[i]); + SocketAddress sa(addresses[i], boost::lexical_cast<std::string>(options.port)); + + // We must have at least one resolved address + QPID_LOG(info, "Listening to: " << sa.asString()) + Socket* s = options.multiplex ? + new SslMuxSocket(options.certName, options.clientAuth) : + new SslSocket(options.certName, options.clientAuth); + uint16_t lport = s->listen(sa, opts.connectionBacklog); + QPID_LOG(debug, "Listened to: " << lport); + listeners.push_back(s); + + listeningPort = lport; + + // Try any other resolved addresses + while (sa.nextAddress()) { + // Hack to ensure that all listening connections are on the same port + sa.setAddrInfoPort(listeningPort); + QPID_LOG(info, "Listening to: " << sa.asString()) + Socket* s = options.multiplex ? + new SslMuxSocket(options.certName, options.clientAuth) : + new SslSocket(options.certName, options.clientAuth); + uint16_t lport = s->listen(sa, opts.connectionBacklog); + QPID_LOG(debug, "Listened to: " << lport); + listeners.push_back(s); + } } - - qpid::sys::ssl::SslIO* aio = new qpid::sys::ssl::SslIO(s, - boost::bind(&qpid::sys::ssl::SslHandler::readbuff, async, _1, _2), - boost::bind(&qpid::sys::ssl::SslHandler::eof, async, _1), - boost::bind(&qpid::sys::ssl::SslHandler::disconnect, async, _1), - boost::bind(&qpid::sys::ssl::SslHandler::closedSocket, async, _1, _2), - boost::bind(&qpid::sys::ssl::SslHandler::nobuffs, async, _1), - boost::bind(&qpid::sys::ssl::SslHandler::idle, async, _1)); - - async->init(aio,timer, maxTime); - aio->start(poller); -} - -template <> -void SslProtocolFactory::established(Poller::shared_ptr poller, const Socket& s, - ConnectionCodec::Factory* f, bool isClient) { - const SslSocket *sslSock = dynamic_cast<const SslSocket*>(&s); - - SslEstablished(poller, *sslSock, f, isClient, brokerTimer, maxNegotiateTime, tcpNoDelay, nodict); } -template <class T> -uint16_t SslProtocolFactoryTmpl<T>::getPort() const { - return listeningPort; // Immutable no need for lock. +void SslProtocolFactory::establishedIncoming(Poller::shared_ptr poller, const Socket& s, + ConnectionCodec::Factory* f) { + AsynchIOHandler* async = new AsynchIOHandler(broker::QPID_NAME_PREFIX+s.getFullAddress(), f, false, false); + establishedCommon(async, poller, s); } -template <class T> -void SslProtocolFactoryTmpl<T>::accept(Poller::shared_ptr poller, - ConnectionCodec::Factory* fact) { - acceptor.reset( - new SslAcceptor(listener, - boost::bind(&SslProtocolFactoryTmpl<T>::established, - this, poller, _1, fact, false))); - acceptor->start(poller); +void SslProtocolFactory::establishedOutgoing(Poller::shared_ptr poller, const Socket& s, + ConnectionCodec::Factory* f, const std::string& name) { + AsynchIOHandler* async = new AsynchIOHandler(name, f, true, false); + establishedCommon(async, poller, s); } -template <> -void SslMuxProtocolFactory::established(Poller::shared_ptr poller, const Socket& s, - ConnectionCodec::Factory* f, bool isClient) { - const SslSocket *sslSock = dynamic_cast<const SslSocket*>(&s); - - if (sslSock) { - SslEstablished(poller, *sslSock, f, isClient, brokerTimer, maxNegotiateTime, tcpNoDelay, nodict); - return; - } - - AsynchIOHandler* async = new AsynchIOHandler(s.getFullAddress(), f); - +void SslProtocolFactory::establishedCommon(AsynchIOHandler* async, Poller::shared_ptr poller, const Socket& s) { if (tcpNoDelay) { s.setTcpNoDelay(); QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress()); } - if (isClient) { - async->setClient(); - } - AsynchIO* aio = AsynchIO::create - (s, - boost::bind(&AsynchIOHandler::readbuff, async, _1, _2), - boost::bind(&AsynchIOHandler::eof, async, _1), - boost::bind(&AsynchIOHandler::disconnect, async, _1), - boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2), - boost::bind(&AsynchIOHandler::nobuffs, async, _1), - boost::bind(&AsynchIOHandler::idle, async, _1)); + AsynchIO* aio = AsynchIO::create( + s, + boost::bind(&AsynchIOHandler::readbuff, async, _1, _2), + boost::bind(&AsynchIOHandler::eof, async, _1), + boost::bind(&AsynchIOHandler::disconnect, async, _1), + boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2), + boost::bind(&AsynchIOHandler::nobuffs, async, _1), + boost::bind(&AsynchIOHandler::idle, async, _1)); async->init(aio, brokerTimer, maxNegotiateTime); aio->start(poller); } -template <class T> -void SslProtocolFactoryTmpl<T>::connect( +uint16_t SslProtocolFactory::getPort() const { + return listeningPort; // Immutable no need for lock. +} + +void SslProtocolFactory::accept(Poller::shared_ptr poller, + ConnectionCodec::Factory* fact) { + for (unsigned i = 0; i<listeners.size(); ++i) { + acceptors.push_back( + AsynchAcceptor::create(listeners[i], + boost::bind(&SslProtocolFactory::establishedIncoming, this, poller, _1, fact))); + acceptors[i].start(poller); + } +} + +void SslProtocolFactory::connectFailed( + const Socket& s, int ec, const std::string& emsg, + ConnectFailedCallback failedCb) +{ + failedCb(ec, emsg); + s.close(); + delete &s; +} + +void SslProtocolFactory::connect( Poller::shared_ptr poller, + const std::string& name, const std::string& host, const std::string& port, ConnectionCodec::Factory* fact, ConnectFailedCallback failed) @@ -264,31 +289,23 @@ void SslProtocolFactoryTmpl<T>::connect( // shutdown. The allocated SslConnector frees itself when it // is no longer needed. - qpid::sys::ssl::SslSocket* socket = new qpid::sys::ssl::SslSocket(); - new SslConnector(*socket, poller, host, port, - boost::bind(&SslProtocolFactoryTmpl<T>::established, this, poller, _1, fact, true), - failed); -} - -namespace -{ -const std::string SSL = "ssl"; -} - -template <> -bool SslProtocolFactory::supports(const std::string& capability) -{ - std::string s = capability; - transform(s.begin(), s.end(), s.begin(), tolower); - return s == SSL; -} - -template <> -bool SslMuxProtocolFactory::supports(const std::string& capability) -{ - std::string s = capability; - transform(s.begin(), s.end(), s.begin(), tolower); - return s == SSL || s == "tcp"; + Socket* socket = new qpid::sys::ssl::SslSocket(); + try { + AsynchConnector* c = AsynchConnector::create( + *socket, + host, + port, + boost::bind(&SslProtocolFactory::establishedOutgoing, + this, poller, _1, fact, name), + boost::bind(&SslProtocolFactory::connectFailed, + this, _1, _2, _3, failed)); + c->start(poller); + } catch (std::exception&) { + // TODO: Design question - should we do the error callback and also throw? + int errCode = socket->getError(); + connectFailed(*socket, errCode, strError(errCode), failed); + throw; + } } }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/TCPIOPlugin.cpp b/cpp/src/qpid/sys/TCPIOPlugin.cpp index ed7cc3748d..1ef8708cd0 100644 --- a/cpp/src/qpid/sys/TCPIOPlugin.cpp +++ b/cpp/src/qpid/sys/TCPIOPlugin.cpp @@ -20,15 +20,17 @@ */ #include "qpid/sys/ProtocolFactory.h" -#include "qpid/sys/AsynchIOHandler.h" -#include "qpid/sys/AsynchIO.h" #include "qpid/Plugin.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/NameGenerator.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/AsynchIOHandler.h" +#include "qpid/sys/AsynchIO.h" #include "qpid/sys/Socket.h" #include "qpid/sys/SocketAddress.h" +#include "qpid/sys/SystemInfo.h" #include "qpid/sys/Poller.h" -#include "qpid/broker/Broker.h" -#include "qpid/log/Statement.h" #include <boost/bind.hpp> #include <boost/ptr_container/ptr_vector.hpp> @@ -47,20 +49,19 @@ class AsynchIOProtocolFactory : public ProtocolFactory { const bool tcpNoDelay; public: - AsynchIOProtocolFactory(const std::string& host, const std::string& port, - int backlog, bool nodelay, - Timer& timer, uint32_t maxTime, - bool shouldListen); + AsynchIOProtocolFactory(const qpid::broker::Broker::Options& opts, Timer& timer, bool shouldListen); void accept(Poller::shared_ptr, ConnectionCodec::Factory*); - void connect(Poller::shared_ptr, const std::string& host, const std::string& port, + void connect(Poller::shared_ptr, const std::string& name, + const std::string& host, const std::string& port, ConnectionCodec::Factory*, ConnectFailedCallback); uint16_t getPort() const; private: - void established(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*, - bool isClient); + void establishedIncoming(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*); + void establishedOutgoing(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*, const std::string&); + void establishedCommon(AsynchIOHandler*, Poller::shared_ptr , const Socket&); void connectFailed(const Socket&, int, const std::string&, ConnectFailedCallback); }; @@ -93,14 +94,9 @@ static class TCPIOPlugin : public Plugin { bool shouldListen = !sslMultiplexEnabled(); ProtocolFactory::shared_ptr protocolt( - new AsynchIOProtocolFactory( - "", boost::lexical_cast<std::string>(opts.port), - opts.connectionBacklog, - opts.tcpNoDelay, - broker->getTimer(), opts.maxNegotiateTime, - shouldListen)); - - if (shouldListen) { + new AsynchIOProtocolFactory(opts, broker->getTimer(),shouldListen)); + + if (shouldListen && protocolt->getPort()!=0 ) { QPID_LOG(notice, "Listening on TCP/TCP6 port " << protocolt->getPort()); } @@ -109,54 +105,93 @@ static class TCPIOPlugin : public Plugin { } } tcpPlugin; -AsynchIOProtocolFactory::AsynchIOProtocolFactory(const std::string& host, const std::string& port, - int backlog, bool nodelay, - Timer& timer, uint32_t maxTime, - bool shouldListen) : +namespace { + // Expand list of Interfaces and addresses to a list of addresses + std::vector<std::string> expandInterfaces(const std::vector<std::string>& interfaces) { + std::vector<std::string> addresses; + // If there are no specific interfaces listed use a single "" to listen on every interface + if (interfaces.empty()) { + addresses.push_back(""); + return addresses; + } + for (unsigned i = 0; i < interfaces.size(); ++i) { + const std::string& interface = interfaces[i]; + if (!(SystemInfo::getInterfaceAddresses(interface, addresses))) { + // We don't have an interface of that name - + // Check for IPv6 ('[' ']') brackets and remove them + // then pass to be looked up directly + if (interface[0]=='[' && interface[interface.size()-1]==']') { + addresses.push_back(interface.substr(1, interface.size()-2)); + } else { + addresses.push_back(interface); + } + } + } + return addresses; + } +} + +AsynchIOProtocolFactory::AsynchIOProtocolFactory(const qpid::broker::Broker::Options& opts, Timer& timer, bool shouldListen) : brokerTimer(timer), - maxNegotiateTime(maxTime), - tcpNoDelay(nodelay) + maxNegotiateTime(opts.maxNegotiateTime), + tcpNoDelay(opts.tcpNoDelay) { if (!shouldListen) { - listeningPort = boost::lexical_cast<uint16_t>(port); + listeningPort = boost::lexical_cast<uint16_t>(opts.port); return; } - SocketAddress sa(host, port); - - // We must have at least one resolved address - QPID_LOG(info, "Listening to: " << sa.asString()) - Socket* s = new Socket; - uint16_t lport = s->listen(sa, backlog); - QPID_LOG(debug, "Listened to: " << lport); - listeners.push_back(s); + std::vector<std::string> addresses = expandInterfaces(opts.listenInterfaces); + if (addresses.empty()) { + // We specified some interfaces, but couldn't find addresses for them + QPID_LOG(warning, "TCP/TCP6: No specified network interfaces found: Not Listening"); + listeningPort = 0; + } - listeningPort = lport; + for (unsigned i = 0; i<addresses.size(); ++i) { + QPID_LOG(debug, "Using interface: " << addresses[i]); + SocketAddress sa(addresses[i], boost::lexical_cast<std::string>(opts.port)); - // Try any other resolved addresses - while (sa.nextAddress()) { - // Hack to ensure that all listening connections are on the same port - sa.setAddrInfoPort(listeningPort); + // We must have at least one resolved address QPID_LOG(info, "Listening to: " << sa.asString()) - Socket* s = new Socket; - uint16_t lport = s->listen(sa, backlog); + Socket* s = createSocket(); + uint16_t lport = s->listen(sa, opts.connectionBacklog); QPID_LOG(debug, "Listened to: " << lport); listeners.push_back(s); + + listeningPort = lport; + + // Try any other resolved addresses + while (sa.nextAddress()) { + // Hack to ensure that all listening connections are on the same port + sa.setAddrInfoPort(listeningPort); + QPID_LOG(info, "Listening to: " << sa.asString()) + Socket* s = createSocket(); + uint16_t lport = s->listen(sa, opts.connectionBacklog); + QPID_LOG(debug, "Listened to: " << lport); + listeners.push_back(s); + } } +} +void AsynchIOProtocolFactory::establishedIncoming(Poller::shared_ptr poller, const Socket& s, + ConnectionCodec::Factory* f) { + AsynchIOHandler* async = new AsynchIOHandler(broker::QPID_NAME_PREFIX+s.getFullAddress(), f, false, false); + establishedCommon(async, poller, s); } -void AsynchIOProtocolFactory::established(Poller::shared_ptr poller, const Socket& s, - ConnectionCodec::Factory* f, bool isClient) { - AsynchIOHandler* async = new AsynchIOHandler(s.getFullAddress(), f); +void AsynchIOProtocolFactory::establishedOutgoing(Poller::shared_ptr poller, const Socket& s, + ConnectionCodec::Factory* f, const std::string& name) { + AsynchIOHandler* async = new AsynchIOHandler(name, f, true, false); + establishedCommon(async, poller, s); +} +void AsynchIOProtocolFactory::establishedCommon(AsynchIOHandler* async, Poller::shared_ptr poller, const Socket& s) { if (tcpNoDelay) { s.setTcpNoDelay(); QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress()); } - if (isClient) - async->setClient(); AsynchIO* aio = AsynchIO::create (s, boost::bind(&AsynchIOHandler::readbuff, async, _1, _2), @@ -179,7 +214,7 @@ void AsynchIOProtocolFactory::accept(Poller::shared_ptr poller, for (unsigned i = 0; i<listeners.size(); ++i) { acceptors.push_back( AsynchAcceptor::create(listeners[i], - boost::bind(&AsynchIOProtocolFactory::established, this, poller, _1, fact, false))); + boost::bind(&AsynchIOProtocolFactory::establishedIncoming, this, poller, _1, fact))); acceptors[i].start(poller); } } @@ -195,6 +230,7 @@ void AsynchIOProtocolFactory::connectFailed( void AsynchIOProtocolFactory::connect( Poller::shared_ptr poller, + const std::string& name, const std::string& host, const std::string& port, ConnectionCodec::Factory* fact, ConnectFailedCallback failed) @@ -204,14 +240,14 @@ void AsynchIOProtocolFactory::connect( // upon connection failure or by the AsynchIO upon connection // shutdown. The allocated AsynchConnector frees itself when it // is no longer needed. - Socket* socket = new Socket(); + Socket* socket = createSocket(); try { AsynchConnector* c = AsynchConnector::create( *socket, host, port, - boost::bind(&AsynchIOProtocolFactory::established, - this, poller, _1, fact, true), + boost::bind(&AsynchIOProtocolFactory::establishedOutgoing, + this, poller, _1, fact, name), boost::bind(&AsynchIOProtocolFactory::connectFailed, this, _1, _2, _3, failed)); c->start(poller); diff --git a/cpp/src/qpid/sys/Timer.cpp b/cpp/src/qpid/sys/Timer.cpp index 973c6bd8b7..f8eef2c9ec 100644 --- a/cpp/src/qpid/sys/Timer.cpp +++ b/cpp/src/qpid/sys/Timer.cpp @@ -96,18 +96,13 @@ void TimerTask::cancel() { state = CANCELLED; } -void TimerTask::setFired() { - // Set nextFireTime to just before now, making readyToFire() true. - nextFireTime = AbsTime(sys::now(), Duration(-1)); -} - - +// TODO AStitcher 21/08/09 The threshholds for emitting warnings are a little arbitrary Timer::Timer() : active(false), late(50 * TIME_MSEC), overran(2 * TIME_MSEC), lateCancel(500 * TIME_MSEC), - warn(5 * TIME_SEC) + warn(60 * TIME_SEC) { start(); } @@ -133,7 +128,6 @@ public: } }; -// TODO AStitcher 21/08/09 The threshholds for emitting warnings are a little arbitrary void Timer::run() { Monitor::ScopedLock l(monitor); @@ -151,10 +145,6 @@ void Timer::run() { TimerTaskCallbackScope s(*t); if (s) { - { - Monitor::ScopedUnlock u(monitor); - drop(t); - } if (delay > lateCancel) { QPID_LOG(debug, t->name << " cancelled timer woken up " << delay / TIME_MSEC << "ms late"); @@ -171,8 +161,8 @@ void Timer::run() if (!tasks.empty()) { overrun = Duration(tasks.top()->nextFireTime, end); } - bool warningsEnabled; - QPID_LOG_TEST(warning, warningsEnabled); + bool warningsEnabled; // TimerWarning enabled + QPID_LOG_TEST(debug, warningsEnabled); // TimerWarning emitted at debug level if (warningsEnabled) { if (overrun > overran) { if (delay > overran) // if delay is significant to an overrun. @@ -235,9 +225,6 @@ void Timer::fire(boost::intrusive_ptr<TimerTask> t) { } } -// Provided for subclasses: called when a task is droped. -void Timer::drop(boost::intrusive_ptr<TimerTask>) {} - bool operator<(const intrusive_ptr<TimerTask>& a, const intrusive_ptr<TimerTask>& b) { diff --git a/cpp/src/qpid/sys/Timer.h b/cpp/src/qpid/sys/Timer.h index 5731b8d977..5045009609 100644 --- a/cpp/src/qpid/sys/Timer.h +++ b/cpp/src/qpid/sys/Timer.h @@ -67,10 +67,6 @@ class TimerTask : public RefCounted { std::string getName() const { return name; } - // Move the nextFireTime so readyToFire is true. - // Used by the cluster, where tasks are fired on cluster events, not on local time. - QPID_COMMON_EXTERN void setFired(); - protected: // Must be overridden with callback virtual void fire() = 0; @@ -99,7 +95,7 @@ class Timer : private Runnable { protected: QPID_COMMON_EXTERN virtual void fire(boost::intrusive_ptr<TimerTask> task); - QPID_COMMON_EXTERN virtual void drop(boost::intrusive_ptr<TimerTask> task); + // Allow derived classes to change the late/overran thresholds. Duration late; Duration overran; diff --git a/cpp/src/qpid/sys/TimerWarnings.cpp b/cpp/src/qpid/sys/TimerWarnings.cpp index 85e26da54a..00fb0d9db6 100644 --- a/cpp/src/qpid/sys/TimerWarnings.cpp +++ b/cpp/src/qpid/sys/TimerWarnings.cpp @@ -56,18 +56,18 @@ void TimerWarnings::log() { std::string task = i->first; TaskStats& stats = i->second; if (stats.lateDelay.count) - QPID_LOG(info, task << " task late " + QPID_LOG(debug, task << " task late " << stats.lateDelay.count << " times by " << stats.lateDelay.average()/TIME_MSEC << "ms on average."); if (stats.overranOverrun.count) - QPID_LOG(info, task << " task overran " + QPID_LOG(debug, task << " task overran " << stats.overranOverrun.count << " times by " << stats.overranOverrun.average()/TIME_MSEC << "ms (taking " << stats.overranTime.average() << "ns) on average."); if (stats.lateAndOverranOverrun.count) - QPID_LOG(info, task << " task late and overran " + QPID_LOG(debug, task << " task late and overran " << stats.lateAndOverranOverrun.count << " times: late " << stats.lateAndOverranDelay.average()/TIME_MSEC << "ms, overran " << stats.lateAndOverranOverrun.average()/TIME_MSEC << "ms (taking " diff --git a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp index 29b91f3e7a..79d9d08a59 100644 --- a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp +++ b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp @@ -29,8 +29,8 @@ namespace qpid { namespace sys { namespace cyrus { -CyrusSecurityLayer::CyrusSecurityLayer(sasl_conn_t* c, uint16_t maxFrameSize) : - conn(c), decrypted(0), decryptedSize(0), encrypted(0), encryptedSize(0), codec(0), maxInputSize(0), +CyrusSecurityLayer::CyrusSecurityLayer(sasl_conn_t* c, uint16_t maxFrameSize, int ssf) : + SecurityLayer(ssf), conn(c), decrypted(0), decryptedSize(0), encrypted(0), encryptedSize(0), codec(0), maxInputSize(0), decodeBuffer(maxFrameSize), encodeBuffer(maxFrameSize), encoded(0) { const void* value(0); @@ -68,7 +68,7 @@ size_t CyrusSecurityLayer::decode(const char* input, size_t size) return size; } -size_t CyrusSecurityLayer::encode(const char* buffer, size_t size) +size_t CyrusSecurityLayer::encode(char* buffer, size_t size) { size_t processed = 0;//records how many bytes have been written to buffer do { @@ -92,12 +92,12 @@ size_t CyrusSecurityLayer::encode(const char* buffer, size_t size) //can't fit all encrypted data in the buffer we've //been given, copy in what we can and hold on to the //rest until the next call - ::memcpy(const_cast<char*>(buffer + processed), encrypted, remaining); + ::memcpy(buffer + processed, encrypted, remaining); processed += remaining; encrypted += remaining; encryptedSize -= remaining; } else { - ::memcpy(const_cast<char*>(buffer + processed), encrypted, encryptedSize); + ::memcpy(buffer + processed, encrypted, encryptedSize); processed += encryptedSize; encrypted = 0; encryptedSize = 0; diff --git a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h index 1645cf1a58..ae86ba5569 100644 --- a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h +++ b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h @@ -37,9 +37,9 @@ namespace cyrus { class CyrusSecurityLayer : public qpid::sys::SecurityLayer { public: - CyrusSecurityLayer(sasl_conn_t*, uint16_t maxFrameSize); + CyrusSecurityLayer(sasl_conn_t*, uint16_t maxFrameSize, int ssf); size_t decode(const char* buffer, size_t size); - size_t encode(const char* buffer, size_t size); + size_t encode(char* buffer, size_t size); bool canEncode(); void init(qpid::sys::Codec*); private: diff --git a/cpp/src/qpid/sys/epoll/EpollPoller.cpp b/cpp/src/qpid/sys/epoll/EpollPoller.cpp index c23403c66d..6fdf99637f 100644 --- a/cpp/src/qpid/sys/epoll/EpollPoller.cpp +++ b/cpp/src/qpid/sys/epoll/EpollPoller.cpp @@ -20,7 +20,6 @@ */ #include "qpid/sys/Poller.h" -#include "qpid/sys/IOHandle.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/AtomicCount.h" #include "qpid/sys/DeletionManager.h" @@ -64,12 +63,12 @@ class PollerHandlePrivate { }; ::__uint32_t events; - const IOHandlePrivate* ioHandle; + const IOHandle* ioHandle; PollerHandle* pollerHandle; FDStat stat; Mutex lock; - PollerHandlePrivate(const IOHandlePrivate* h, PollerHandle* p) : + PollerHandlePrivate(const IOHandle* h, PollerHandle* p) : events(0), ioHandle(h), pollerHandle(p), @@ -77,7 +76,7 @@ class PollerHandlePrivate { } int fd() const { - return toFd(ioHandle); + return ioHandle->fd; } bool isActive() const { @@ -138,7 +137,7 @@ class PollerHandlePrivate { }; PollerHandle::PollerHandle(const IOHandle& h) : - impl(new PollerHandlePrivate(h.impl, this)) + impl(new PollerHandlePrivate(&h, this)) {} PollerHandle::~PollerHandle() { @@ -385,6 +384,7 @@ void PollerPrivate::resetMode(PollerHandlePrivate& eh) { int rc = ::epoll_ctl(epollFd, EPOLL_CTL_MOD, eh.fd(), &epe); // If something has closed the fd in the meantime try adding it back if (rc ==-1 && errno == ENOENT) { + eh.setIdle(); // Reset our handle as if starting from scratch rc = ::epoll_ctl(epollFd, EPOLL_CTL_ADD, eh.fd(), &epe); } QPID_POSIX_CHECK(rc); diff --git a/cpp/src/qpid/sys/posix/AsynchIO.cpp b/cpp/src/qpid/sys/posix/AsynchIO.cpp index 31355627cd..353a55f50c 100644 --- a/cpp/src/qpid/sys/posix/AsynchIO.cpp +++ b/cpp/src/qpid/sys/posix/AsynchIO.cpp @@ -143,6 +143,7 @@ class AsynchConnector : public qpid::sys::AsynchConnector, private: void connComplete(DispatchHandle& handle); + void requestedCall(RequestCallback rCb); private: ConnectedCallback connCallback; @@ -158,6 +159,7 @@ public: FailedCallback failCb); void start(Poller::shared_ptr poller); void stop(); + void requestCallback(RequestCallback rCb); }; AsynchConnector::AsynchConnector(const Socket& s, @@ -191,11 +193,30 @@ void AsynchConnector::stop() stopWatch(); } +void AsynchConnector::requestCallback(RequestCallback callback) { + // TODO creating a function object every time isn't all that + // efficient - if this becomes heavily used do something better (what?) + assert(callback); + DispatchHandle::call(boost::bind(&AsynchConnector::requestedCall, this, callback)); +} + +void AsynchConnector::requestedCall(RequestCallback callback) { + assert(callback); + callback(*this); +} + void AsynchConnector::connComplete(DispatchHandle& h) { int errCode = socket.getError(); if (errCode == 0) { h.stopWatch(); + try { + socket.finishConnect(sa); + } catch (const std::exception& e) { + failCallback(socket, 0, e.what()); + DispatchHandle::doDelete(); + return; + } connCallback(socket); } else { // Retry while we cause an immediate exception @@ -247,10 +268,9 @@ public: virtual void notifyPendingWrite(); virtual void queueWriteClose(); virtual bool writeQueueEmpty(); - virtual void startReading(); - virtual void stopReading(); virtual void requestCallback(RequestCallback); virtual BufferBase* getQueuedBuffer(); + virtual SecuritySettings getSecuritySettings(); private: ~AsynchIO(); @@ -282,13 +302,6 @@ private: * thread processing this handle. */ volatile bool writePending; - /** - * This records whether we've been reading is flow controlled: - * it's safe as a simple boolean as the only way to be stopped - * is in calls only allowed in the callback context, the only calls - * checking it are also in calls only allowed in callback context. - */ - volatile bool readingStopped; }; AsynchIO::AsynchIO(const Socket& s, @@ -307,8 +320,7 @@ AsynchIO::AsynchIO(const Socket& s, idleCallback(iCb), socket(s), queuedClose(false), - writePending(false), - readingStopped(false) { + writePending(false) { s.setNonblocking(); } @@ -344,7 +356,7 @@ void AsynchIO::queueReadBuffer(BufferBase* buff) { bool queueWasEmpty = bufferQueue.empty(); bufferQueue.push_back(buff); - if (queueWasEmpty && !readingStopped) + if (queueWasEmpty) DispatchHandle::rewatchRead(); } @@ -354,7 +366,7 @@ void AsynchIO::unread(BufferBase* buff) { bool queueWasEmpty = bufferQueue.empty(); bufferQueue.push_front(buff); - if (queueWasEmpty && !readingStopped) + if (queueWasEmpty) DispatchHandle::rewatchRead(); } @@ -386,17 +398,6 @@ bool AsynchIO::writeQueueEmpty() { return writeQueue.empty(); } -// This can happen outside the callback context -void AsynchIO::startReading() { - readingStopped = false; - DispatchHandle::rewatchRead(); -} - -void AsynchIO::stopReading() { - readingStopped = true; - DispatchHandle::unwatchRead(); -} - void AsynchIO::requestCallback(RequestCallback callback) { // TODO creating a function object every time isn't all that // efficient - if this becomes heavily used do something better (what?) @@ -429,11 +430,6 @@ AsynchIO::BufferBase* AsynchIO::getQueuedBuffer() { * to put it in and reading is not stopped by flow control. */ void AsynchIO::readable(DispatchHandle& h) { - if (readingStopped) { - // We have been flow controlled. - QPID_PROBE1(asynchio_read_flowcontrolled, &h); - return; - } AbsTime readStartTime = AbsTime::now(); size_t total = 0; int readCalls = 0; @@ -455,12 +451,6 @@ void AsynchIO::readable(DispatchHandle& h) { total += rc; readCallback(*this, buff); - if (readingStopped) { - // We have been flow controlled. - QPID_PROBE4(asynchio_read_finished_flowcontrolled, &h, duration, total, readCalls); - break; - } - if (rc != readCount) { // If we didn't fill the read buffer then time to stop reading QPID_PROBE4(asynchio_read_finished_done, &h, duration, total, readCalls); @@ -626,6 +616,13 @@ void AsynchIO::close(DispatchHandle& h) { } } +SecuritySettings AsynchIO::getSecuritySettings() { + SecuritySettings settings; + settings.ssf = socket.getKeyLen(); + settings.authid = socket.getClientAuthId(); + return settings; +} + } // namespace posix AsynchAcceptor* AsynchAcceptor::create(const Socket& s, diff --git a/cpp/src/qpid/sys/posix/Socket.cpp b/cpp/src/qpid/sys/posix/BSDSocket.cpp index 77ae1af60c..7c31b13ae9 100644 --- a/cpp/src/qpid/sys/posix/Socket.cpp +++ b/cpp/src/qpid/sys/posix/BSDSocket.cpp @@ -19,7 +19,7 @@ * */ -#include "qpid/sys/Socket.h" +#include "qpid/sys/posix/BSDSocket.h" #include "qpid/sys/SocketAddress.h" #include "qpid/sys/posix/check.h" @@ -67,25 +67,41 @@ uint16_t getLocalPort(int fd) } } -Socket::Socket() : - IOHandle(new IOHandlePrivate), +BSDSocket::BSDSocket() : + fd(-1), + handle(new IOHandle), nonblocking(false), nodelay(false) {} -Socket::Socket(IOHandlePrivate* h) : - IOHandle(h), +Socket* createSocket() +{ + return new BSDSocket; +} + +BSDSocket::BSDSocket(int fd0) : + fd(fd0), + handle(new IOHandle(fd)), nonblocking(false), nodelay(false) {} -void Socket::createSocket(const SocketAddress& sa) const +BSDSocket::~BSDSocket() +{} + +BSDSocket::operator const IOHandle&() const +{ + return *handle; +} + +void BSDSocket::createSocket(const SocketAddress& sa) const { - int& socket = impl->fd; - if (socket != -1) Socket::close(); + int& socket = fd; + if (socket != -1) BSDSocket::close(); int s = ::socket(getAddrInfo(sa).ai_family, getAddrInfo(sa).ai_socktype, 0); if (s < 0) throw QPID_POSIX_ERROR(errno); socket = s; + *handle = IOHandle(s); try { if (nonblocking) setNonblocking(); @@ -98,50 +114,31 @@ void Socket::createSocket(const SocketAddress& sa) const } catch (std::exception&) { ::close(s); socket = -1; + *handle = IOHandle(); throw; } } -Socket* Socket::createSameTypeSocket() const { - int& socket = impl->fd; - // Socket currently has no actual socket attached - if (socket == -1) - return new Socket; - - ::sockaddr_storage sa; - ::socklen_t salen = sizeof(sa); - QPID_POSIX_CHECK(::getsockname(socket, (::sockaddr*)&sa, &salen)); - int s = ::socket(sa.ss_family, SOCK_STREAM, 0); // Currently only work with SOCK_STREAM - if (s < 0) throw QPID_POSIX_ERROR(errno); - return new Socket(new IOHandlePrivate(s)); -} - -void Socket::setNonblocking() const { - int& socket = impl->fd; +void BSDSocket::setNonblocking() const { + int& socket = fd; nonblocking = true; if (socket != -1) { QPID_POSIX_CHECK(::fcntl(socket, F_SETFL, O_NONBLOCK)); } } -void Socket::setTcpNoDelay() const +void BSDSocket::setTcpNoDelay() const { - int& socket = impl->fd; + int& socket = fd; nodelay = true; if (socket != -1) { int flag = 1; - int result = ::setsockopt(impl->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); + int result = ::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); QPID_POSIX_CHECK(result); } } -void Socket::connect(const std::string& host, const std::string& port) const -{ - SocketAddress sa(host, port); - connect(sa); -} - -void Socket::connect(const SocketAddress& addr) const +void BSDSocket::connect(const SocketAddress& addr) const { // The display name for an outbound connection needs to be the name that was specified // for the address rather than a resolved IP address as we don't know which of @@ -154,7 +151,7 @@ void Socket::connect(const SocketAddress& addr) const createSocket(addr); - const int& socket = impl->fd; + const int& socket = fd; // TODO the correct thing to do here is loop on failure until you've used all the returned addresses if ((::connect(socket, getAddrInfo(addr).ai_addr, getAddrInfo(addr).ai_addrlen) < 0) && (errno != EINPROGRESS)) { @@ -165,11 +162,6 @@ void Socket::connect(const SocketAddress& addr) const // remote port (which is unoccupied) as the port to bind the local // end of the socket, resulting in a "circular" connection. // - // This seems like something the OS should prevent but I have - // confirmed that sporadic hangs in - // cluster_tests.LongTests.test_failover on RHEL5 are caused by - // such a circular connection. - // // Raise an error if we see such a connection, since we know there is // no listener on the peer address. // @@ -179,26 +171,25 @@ void Socket::connect(const SocketAddress& addr) const } } +void BSDSocket::finishConnect(const SocketAddress&) const +{ +} + void -Socket::close() const +BSDSocket::close() const { - int& socket = impl->fd; + int& socket = fd; if (socket == -1) return; if (::close(socket) < 0) throw QPID_POSIX_ERROR(errno); socket = -1; + *handle = IOHandle(); } -int Socket::listen(const std::string& host, const std::string& port, int backlog) const -{ - SocketAddress sa(host, port); - return listen(sa, backlog); -} - -int Socket::listen(const SocketAddress& sa, int backlog) const +int BSDSocket::listen(const SocketAddress& sa, int backlog) const { createSocket(sa); - const int& socket = impl->fd; + const int& socket = fd; int yes=1; QPID_POSIX_CHECK(::setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes))); @@ -210,11 +201,11 @@ int Socket::listen(const SocketAddress& sa, int backlog) const return getLocalPort(socket); } -Socket* Socket::accept() const +Socket* BSDSocket::accept() const { - int afd = ::accept(impl->fd, 0, 0); + int afd = ::accept(fd, 0, 0); if ( afd >= 0) { - Socket* s = new Socket(new IOHandlePrivate(afd)); + BSDSocket* s = new BSDSocket(afd); s->localname = localname; return s; } @@ -223,41 +214,51 @@ Socket* Socket::accept() const else throw QPID_POSIX_ERROR(errno); } -int Socket::read(void *buf, size_t count) const +int BSDSocket::read(void *buf, size_t count) const { - return ::read(impl->fd, buf, count); + return ::read(fd, buf, count); } -int Socket::write(const void *buf, size_t count) const +int BSDSocket::write(const void *buf, size_t count) const { - return ::write(impl->fd, buf, count); + return ::write(fd, buf, count); } -std::string Socket::getPeerAddress() const +std::string BSDSocket::getPeerAddress() const { if (peername.empty()) { - peername = getName(impl->fd, false); + peername = getName(fd, false); } return peername; } -std::string Socket::getLocalAddress() const +std::string BSDSocket::getLocalAddress() const { if (localname.empty()) { - localname = getName(impl->fd, true); + localname = getName(fd, true); } return localname; } -int Socket::getError() const +int BSDSocket::getError() const { int result; socklen_t rSize = sizeof (result); - if (::getsockopt(impl->fd, SOL_SOCKET, SO_ERROR, &result, &rSize) < 0) + if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &result, &rSize) < 0) throw QPID_POSIX_ERROR(errno); return result; } +int BSDSocket::getKeyLen() const +{ + return 0; +} + +std::string BSDSocket::getClientAuthId() const +{ + return std::string(); +} + }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/posix/BSDSocket.h b/cpp/src/qpid/sys/posix/BSDSocket.h new file mode 100644 index 0000000000..862d36c1b9 --- /dev/null +++ b/cpp/src/qpid/sys/posix/BSDSocket.h @@ -0,0 +1,113 @@ +#ifndef QPID_SYS_BSDSOCKET_H +#define QPID_SYS_BSDSOCKET_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/sys/Socket.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/CommonImportExport.h" +#include <string> + +#include <boost/scoped_ptr.hpp> + +namespace qpid { +namespace sys { + +class Duration; +class IOHandle; +class SocketAddress; + +namespace ssl { +class SslMuxSocket; +} + +class QPID_COMMON_CLASS_EXTERN BSDSocket : public Socket +{ +public: + /** Create a socket wrapper for descriptor. */ + QPID_COMMON_EXTERN BSDSocket(); + QPID_COMMON_EXTERN ~BSDSocket(); + + QPID_COMMON_EXTERN operator const IOHandle&() const; + + /** Set socket non blocking */ + QPID_COMMON_EXTERN virtual void setNonblocking() const; + + QPID_COMMON_EXTERN virtual void setTcpNoDelay() const; + + QPID_COMMON_EXTERN virtual void connect(const SocketAddress&) const; + QPID_COMMON_EXTERN virtual void finishConnect(const SocketAddress&) const; + + QPID_COMMON_EXTERN virtual void close() const; + + /** Bind to a port and start listening. + *@return The bound port number + */ + QPID_COMMON_EXTERN virtual int listen(const SocketAddress&, int backlog = 10) const; + + /** + * Returns an address (host and port) for the remote end of the + * socket + */ + QPID_COMMON_EXTERN std::string getPeerAddress() const; + /** + * Returns an address (host and port) for the local end of the + * socket + */ + QPID_COMMON_EXTERN std::string getLocalAddress() const; + + /** + * Returns the error code stored in the socket. This may be used + * to determine the result of a non-blocking connect. + */ + QPID_COMMON_EXTERN int getError() const; + + /** Accept a connection from a socket that is already listening + * and has an incoming connection + */ + QPID_COMMON_EXTERN virtual Socket* accept() const; + + // TODO The following are raw operations, maybe they need better wrapping? + QPID_COMMON_EXTERN virtual int read(void *buf, size_t count) const; + QPID_COMMON_EXTERN virtual int write(const void *buf, size_t count) const; + + QPID_COMMON_EXTERN int getKeyLen() const; + QPID_COMMON_EXTERN std::string getClientAuthId() const; + +protected: + /** Create socket */ + void createSocket(const SocketAddress&) const; + + mutable int fd; + mutable boost::scoped_ptr<IOHandle> handle; + mutable std::string localname; + mutable std::string peername; + mutable bool nonblocking; + mutable bool nodelay; + + /** Construct socket with existing handle */ + BSDSocket(int fd); + friend class qpid::sys::ssl::SslMuxSocket; // Needed for this constructor +}; + +}} +#endif /*!QPID_SYS_BSDSOCKET_H*/ diff --git a/cpp/src/qpid/sys/posix/FileSysDir.cpp b/cpp/src/qpid/sys/posix/FileSysDir.cpp index 22dc487e74..cec580164d 100755 --- a/cpp/src/qpid/sys/posix/FileSysDir.cpp +++ b/cpp/src/qpid/sys/posix/FileSysDir.cpp @@ -18,6 +18,7 @@ #include "qpid/sys/FileSysDir.h" #include "qpid/sys/StrError.h" +#include "qpid/log/Statement.h" #include "qpid/Exception.h" #include <sys/types.h> @@ -25,6 +26,8 @@ #include <fcntl.h> #include <cerrno> #include <unistd.h> +#include <dirent.h> +#include <stdlib.h> namespace qpid { namespace sys { @@ -51,4 +54,27 @@ void FileSysDir::mkdir(void) throw Exception ("Can't create directory: " + dirPath); } +void FileSysDir::forEachFile(Callback cb) const { + + ::dirent** namelist; + + int n = scandir(dirPath.c_str(), &namelist, 0, alphasort); + if (n == -1) throw Exception (strError(errno) + ": Can't scan directory: " + dirPath); + + for (int i = 0; i<n; ++i) { + std::string fullpath = dirPath + "/" + namelist[i]->d_name; + // Filter out non files/stat problems etc. + struct ::stat s; + // Can't throw here without leaking memory, so just do nothing with + // entries for which stat() fails. + if (!::stat(fullpath.c_str(), &s)) { + if (S_ISREG(s.st_mode)) { + cb(fullpath); + } + } + ::free(namelist[i]); + } + ::free(namelist); +} + }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/posix/IOHandle.cpp b/cpp/src/qpid/sys/posix/IOHandle.cpp index 9c049ee1de..d3f502a63c 100644 --- a/cpp/src/qpid/sys/posix/IOHandle.cpp +++ b/cpp/src/qpid/sys/posix/IOHandle.cpp @@ -19,26 +19,11 @@ * */ -#include "qpid/sys/IOHandle.h" - #include "qpid/sys/posix/PrivatePosix.h" namespace qpid { namespace sys { -int toFd(const IOHandlePrivate* h) -{ - return h->fd; -} - NullIOHandle DummyIOHandle; -IOHandle::IOHandle(IOHandlePrivate* h) : - impl(h) -{} - -IOHandle::~IOHandle() { - delete impl; -} - }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/posix/PollableCondition.cpp b/cpp/src/qpid/sys/posix/PollableCondition.cpp index abff8a5be8..aa129faf20 100644 --- a/cpp/src/qpid/sys/posix/PollableCondition.cpp +++ b/cpp/src/qpid/sys/posix/PollableCondition.cpp @@ -21,7 +21,6 @@ #include "qpid/sys/PollableCondition.h" #include "qpid/sys/DispatchHandle.h" -#include "qpid/sys/IOHandle.h" #include "qpid/sys/posix/PrivatePosix.h" #include "qpid/Exception.h" @@ -58,14 +57,14 @@ PollableConditionPrivate::PollableConditionPrivate( const sys::PollableCondition::Callback& cb, sys::PollableCondition& parent, const boost::shared_ptr<sys::Poller>& poller -) : IOHandle(new sys::IOHandlePrivate), cb(cb), parent(parent) +) : cb(cb), parent(parent) { int fds[2]; if (::pipe(fds) == -1) throw ErrnoException(QPID_MSG("Can't create PollableCondition")); - impl->fd = fds[0]; + fd = fds[0]; writeFd = fds[1]; - if (::fcntl(impl->fd, F_SETFL, O_NONBLOCK) == -1) + if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) throw ErrnoException(QPID_MSG("Can't create PollableCondition")); if (::fcntl(writeFd, F_SETFL, O_NONBLOCK) == -1) throw ErrnoException(QPID_MSG("Can't create PollableCondition")); diff --git a/cpp/src/qpid/sys/posix/PosixPoller.cpp b/cpp/src/qpid/sys/posix/PosixPoller.cpp index eb0c3384d1..ae839b2e20 100644 --- a/cpp/src/qpid/sys/posix/PosixPoller.cpp +++ b/cpp/src/qpid/sys/posix/PosixPoller.cpp @@ -88,12 +88,12 @@ class PollerHandlePrivate { }; short events; - const IOHandlePrivate* ioHandle; + const IOHandle* ioHandle; PollerHandle* pollerHandle; FDStat stat; Mutex lock; - PollerHandlePrivate(const IOHandlePrivate* h, PollerHandle* p) : + PollerHandlePrivate(const IOHandle* h, PollerHandle* p) : events(0), ioHandle(h), pollerHandle(p), @@ -101,7 +101,7 @@ class PollerHandlePrivate { } int fd() const { - return toFd(ioHandle); + return ioHandle->fd; } bool isActive() const { @@ -162,7 +162,7 @@ class PollerHandlePrivate { }; PollerHandle::PollerHandle(const IOHandle& h) : - impl(new PollerHandlePrivate(h.impl, this)) + impl(new PollerHandlePrivate(&h, this)) {} PollerHandle::~PollerHandle() { diff --git a/cpp/src/qpid/sys/posix/SocketAddress.cpp b/cpp/src/qpid/sys/posix/SocketAddress.cpp index 344bd28669..cd23442226 100644 --- a/cpp/src/qpid/sys/posix/SocketAddress.cpp +++ b/cpp/src/qpid/sys/posix/SocketAddress.cpp @@ -102,6 +102,11 @@ std::string SocketAddress::asString(bool numeric) const return asString(ai.ai_addr, ai.ai_addrlen); } +std::string SocketAddress::getHost() const +{ + return host; +} + bool SocketAddress::nextAddress() { bool r = currentAddrInfo->ai_next != 0; if (r) diff --git a/cpp/src/qpid/sys/posix/SystemInfo.cpp b/cpp/src/qpid/sys/posix/SystemInfo.cpp index cfd2c64aee..ea7f521f2b 100755 --- a/cpp/src/qpid/sys/posix/SystemInfo.cpp +++ b/cpp/src/qpid/sys/posix/SystemInfo.cpp @@ -21,7 +21,6 @@ #include "qpid/log/Statement.h" #include "qpid/sys/SystemInfo.h" #include "qpid/sys/posix/check.h" -#include <set> #include <arpa/inet.h> #include <sys/ioctl.h> #include <sys/utsname.h> @@ -33,6 +32,7 @@ #include <iostream> #include <fstream> #include <sstream> +#include <map> #include <netdb.h> #include <string.h> @@ -77,84 +77,70 @@ inline bool isLoopback(const ::sockaddr* addr) { } } -void SystemInfo::getLocalIpAddresses (uint16_t port, - std::vector<Address> &addrList) { - ::ifaddrs* ifaddr = 0; - QPID_POSIX_CHECK(::getifaddrs(&ifaddr)); - for (::ifaddrs* ifap = ifaddr; ifap != 0; ifap = ifap->ifa_next) { - if (ifap->ifa_addr == 0) continue; - if (isLoopback(ifap->ifa_addr)) continue; - int family = ifap->ifa_addr->sa_family; - switch (family) { - case AF_INET6: { - // Ignore link local addresses as: - // * The scope id is illegal in URL syntax - // * Clients won't be able to use a link local address - // without adding their own (potentially different) scope id - sockaddr_in6* sa6 = (sockaddr_in6*)((void*)ifap->ifa_addr); - if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) break; - // Fallthrough - } - case AF_INET: { - char dispName[NI_MAXHOST]; - int rc = ::getnameinfo( - ifap->ifa_addr, - (family == AF_INET) - ? sizeof(struct sockaddr_in) - : sizeof(struct sockaddr_in6), - dispName, sizeof(dispName), - 0, 0, NI_NUMERICHOST); - if (rc != 0) { - throw QPID_POSIX_ERROR(rc); - } - string addr(dispName); - addrList.push_back(Address(TCP, addr, port)); - break; - } - default: - continue; +namespace { + inline socklen_t sa_len(::sockaddr* sa) + { + switch (sa->sa_family) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + default: + return sizeof(struct sockaddr_storage); } } - ::freeifaddrs(ifaddr); - if (addrList.empty()) { - addrList.push_back(Address(TCP, LOOPBACK, port)); + inline bool isInetOrInet6(::sockaddr* sa) { + switch (sa->sa_family) { + case AF_INET: + case AF_INET6: + return true; + default: + return false; + } + } + typedef std::map<std::string, std::vector<std::string> > InterfaceInfo; + std::map<std::string, std::vector<std::string> > cachedInterfaces; + + void cacheInterfaceInfo() { + // Get interface info + ::ifaddrs* interfaceInfo; + QPID_POSIX_CHECK( ::getifaddrs(&interfaceInfo) ); + + char name[NI_MAXHOST]; + for (::ifaddrs* info = interfaceInfo; info != 0; info = info->ifa_next) { + + // Only use IPv4/IPv6 interfaces + if (!isInetOrInet6(info->ifa_addr)) continue; + + int rc=::getnameinfo(info->ifa_addr, sa_len(info->ifa_addr), + name, sizeof(name), 0, 0, + NI_NUMERICHOST); + if (rc >= 0) { + std::string address(name); + cachedInterfaces[info->ifa_name].push_back(address); + } else { + throw qpid::Exception(QPID_MSG(gai_strerror(rc))); + } + } + ::freeifaddrs(interfaceInfo); } } -namespace { -struct AddrInfo { - struct addrinfo* ptr; - AddrInfo(const std::string& host) : ptr(0) { - ::addrinfo hints; - ::memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; // Allow both IPv4 and IPv6 - if (::getaddrinfo(host.c_str(), NULL, &hints, &ptr) != 0) - ptr = 0; - } - ~AddrInfo() { if (ptr) ::freeaddrinfo(ptr); } -}; +bool SystemInfo::getInterfaceAddresses(const std::string& interface, std::vector<std::string>& addresses) { + if ( cachedInterfaces.empty() ) cacheInterfaceInfo(); + InterfaceInfo::iterator i = cachedInterfaces.find(interface); + if ( i==cachedInterfaces.end() ) return false; + std::copy(i->second.begin(), i->second.end(), std::back_inserter(addresses)); + return true; } -bool SystemInfo::isLocalHost(const std::string& host) { - std::vector<Address> myAddrs; - getLocalIpAddresses(0, myAddrs); - std::set<string> localHosts; - for (std::vector<Address>::const_iterator i = myAddrs.begin(); i != myAddrs.end(); ++i) - localHosts.insert(i->host); - // Resolve host - AddrInfo ai(host); - if (!ai.ptr) return false; - for (struct addrinfo *res = ai.ptr; res != NULL; res = res->ai_next) { - if (isLoopback(res->ai_addr)) return true; - // Get string form of IP addr - char addr[NI_MAXHOST] = ""; - int error = ::getnameinfo(res->ai_addr, res->ai_addrlen, addr, NI_MAXHOST, NULL, 0, - NI_NUMERICHOST | NI_NUMERICSERV); - if (error) return false; - if (localHosts.find(addr) != localHosts.end()) return true; +void SystemInfo::getInterfaceNames(std::vector<std::string>& names ) { + if ( cachedInterfaces.empty() ) cacheInterfaceInfo(); + + for (InterfaceInfo::const_iterator i = cachedInterfaces.begin(); i!=cachedInterfaces.end(); ++i) { + names.push_back(i->first); } - return false; } void SystemInfo::getSystemId (std::string &osName, @@ -205,4 +191,11 @@ string SystemInfo::getProcessName() return value; } +// Always true. Only Windows has exception cases. +bool SystemInfo::threadSafeShutdown() +{ + return true; +} + + }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/rdma/rdma_wrap.cpp b/cpp/src/qpid/sys/rdma/rdma_wrap.cpp index efe454c5be..889ee9ff75 100644 --- a/cpp/src/qpid/sys/rdma/rdma_wrap.cpp +++ b/cpp/src/qpid/sys/rdma/rdma_wrap.cpp @@ -105,7 +105,7 @@ namespace Rdma { } QueuePair::QueuePair(boost::shared_ptr< ::rdma_cm_id > i) : - qpid::sys::IOHandle(new qpid::sys::IOHandlePrivate), + handle(new qpid::sys::IOHandle), pd(allocPd(i->verbs)), cchannel(mkCChannel(i->verbs)), scq(mkCq(i->verbs, DEFAULT_CQ_ENTRIES, 0, cchannel.get())), @@ -113,7 +113,7 @@ namespace Rdma { outstandingSendEvents(0), outstandingRecvEvents(0) { - impl->fd = cchannel->fd; + handle->fd = cchannel->fd; // Set cq context to this QueuePair object so we can find // ourselves again @@ -163,6 +163,11 @@ namespace Rdma { // The buffers vectors automatically deletes all the buffers we've allocated } + QueuePair::operator qpid::sys::IOHandle&() const + { + return *handle; + } + // Create buffers to use for writing void QueuePair::createSendBuffers(int sendBufferCount, int bufferSize, int reserved) { @@ -359,11 +364,11 @@ namespace Rdma { // Wrap the passed in rdma_cm_id with a Connection // this basically happens only on connection request Connection::Connection(::rdma_cm_id* i) : - qpid::sys::IOHandle(new qpid::sys::IOHandlePrivate), + handle(new qpid::sys::IOHandle), id(mkId(i)), context(0) { - impl->fd = id->channel->fd; + handle->fd = id->channel->fd; // Just overwrite the previous context as it will // have come from the listening connection @@ -372,12 +377,12 @@ namespace Rdma { } Connection::Connection() : - qpid::sys::IOHandle(new qpid::sys::IOHandlePrivate), + handle(new qpid::sys::IOHandle), channel(mkEChannel()), id(mkId(channel.get(), this, RDMA_PS_TCP)), context(0) { - impl->fd = channel->fd; + handle->fd = channel->fd; } Connection::~Connection() { @@ -385,6 +390,11 @@ namespace Rdma { id->context = 0; } + Connection::operator qpid::sys::IOHandle&() const + { + return *handle; + } + void Connection::ensureQueuePair() { assert(id.get()); diff --git a/cpp/src/qpid/sys/rdma/rdma_wrap.h b/cpp/src/qpid/sys/rdma/rdma_wrap.h index 8e3429027b..5f84793a5b 100644 --- a/cpp/src/qpid/sys/rdma/rdma_wrap.h +++ b/cpp/src/qpid/sys/rdma/rdma_wrap.h @@ -28,6 +28,7 @@ #include "qpid/sys/Mutex.h" #include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> #include <boost/intrusive_ptr.hpp> #include <boost/ptr_container/ptr_deque.hpp> @@ -116,9 +117,10 @@ namespace Rdma { // Wrapper for a queue pair - this has the functionality for // putting buffers on the receive queue and for sending buffers // to the other end of the connection. - class QueuePair : public qpid::sys::IOHandle, public qpid::RefCounted { + class QueuePair : public qpid::RefCounted { friend class Connection; + boost::scoped_ptr< qpid::sys::IOHandle > handle; boost::shared_ptr< ::ibv_pd > pd; boost::shared_ptr< ::ibv_mr > smr; boost::shared_ptr< ::ibv_mr > rmr; @@ -139,6 +141,8 @@ namespace Rdma { public: typedef boost::intrusive_ptr<QueuePair> intrusive_ptr; + operator qpid::sys::IOHandle&() const; + // Create a buffers to use for writing void createSendBuffers(int sendBufferCount, int dataSize, int headerSize); @@ -195,7 +199,8 @@ namespace Rdma { // registered buffers can't be shared between different connections // (this can only happen between connections on the same controller in any case, // so needs careful management if used) - class Connection : public qpid::sys::IOHandle, public qpid::RefCounted { + class Connection : public qpid::RefCounted { + boost::scoped_ptr< qpid::sys::IOHandle > handle; boost::shared_ptr< ::rdma_event_channel > channel; boost::shared_ptr< ::rdma_cm_id > id; QueuePair::intrusive_ptr qp; @@ -216,6 +221,8 @@ namespace Rdma { public: typedef boost::intrusive_ptr<Connection> intrusive_ptr; + operator qpid::sys::IOHandle&() const; + static intrusive_ptr make(); static intrusive_ptr find(::rdma_cm_id* i); diff --git a/cpp/src/qpid/sys/solaris/SystemInfo.cpp b/cpp/src/qpid/sys/solaris/SystemInfo.cpp index e5856f55e6..0e754e048b 100755 --- a/cpp/src/qpid/sys/solaris/SystemInfo.cpp +++ b/cpp/src/qpid/sys/solaris/SystemInfo.cpp @@ -60,31 +60,6 @@ bool SystemInfo::getLocalHostname(Address &address) { static const string LOCALHOST("127.0.0.1"); static const string TCP("tcp"); -void SystemInfo::getLocalIpAddresses(uint16_t port, - std::vector<Address> &addrList) { - int s = socket(PF_INET, SOCK_STREAM, 0); - for (int i=1;;i++) { - struct lifreq ifr; - ifr.lifr_index = i; - if (::ioctl(s, SIOCGIFADDR, &ifr) < 0) { - break; - } - struct sockaddr *sa = static_cast<struct sockaddr *>((void *) &ifr.lifr_addr); - if (sa->sa_family != AF_INET) { - // TODO: Url parsing currently can't cope with IPv6 addresses, defer for now - break; - } - struct sockaddr_in *sin = static_cast<struct sockaddr_in *>((void *)sa); - std::string addr(inet_ntoa(sin->sin_addr)); - if (addr != LOCALHOST) - addrList.push_back(Address(TCP, addr, port)); - } - if (addrList.empty()) { - addrList.push_back(Address(TCP, LOCALHOST, port)); - } - close (s); -} - void SystemInfo::getSystemId(std::string &osName, std::string &nodeName, std::string &release, @@ -126,4 +101,10 @@ string SystemInfo::getProcessName() return value; } +// Always true. Only Windows has exception cases. +bool SystemInfo::threadSafeShutdown() +{ + return true; +} + }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/ssl/SslHandler.cpp b/cpp/src/qpid/sys/ssl/SslHandler.cpp deleted file mode 100644 index eeb8c26a76..0000000000 --- a/cpp/src/qpid/sys/ssl/SslHandler.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -#include "qpid/sys/ssl/SslHandler.h" -#include "qpid/sys/ssl/SslIo.h" -#include "qpid/sys/ssl/SslSocket.h" -#include "qpid/sys/Timer.h" -#include "qpid/framing/AMQP_HighestVersion.h" -#include "qpid/framing/ProtocolInitiation.h" -#include "qpid/log/Statement.h" - -#include <boost/bind.hpp> - -namespace qpid { -namespace sys { -namespace ssl { - - -struct ProtocolTimeoutTask : public sys::TimerTask { - SslHandler& handler; - std::string id; - - ProtocolTimeoutTask(const std::string& i, const Duration& timeout, SslHandler& h) : - TimerTask(timeout, "ProtocolTimeout"), - handler(h), - id(i) - {} - - void fire() { - // If this fires it means that we didn't negotiate the connection in the timeout period - // Schedule closing the connection for the io thread - QPID_LOG(error, "Connection " << id << " No protocol received closing"); - handler.abort(); - } -}; - -SslHandler::SslHandler(std::string id, ConnectionCodec::Factory* f, bool _nodict) : - identifier(id), - aio(0), - factory(f), - codec(0), - readError(false), - isClient(false), - nodict(_nodict) -{} - -SslHandler::~SslHandler() { - if (codec) - codec->closed(); - if (timeoutTimerTask) - timeoutTimerTask->cancel(); - delete codec; -} - -void SslHandler::init(SslIO* a, Timer& timer, uint32_t maxTime) { - aio = a; - - // Start timer for this connection - timeoutTimerTask = new ProtocolTimeoutTask(identifier, maxTime*TIME_MSEC, *this); - timer.add(timeoutTimerTask); - - // Give connection some buffers to use - aio->createBuffers(); -} - -void SslHandler::write(const framing::ProtocolInitiation& data) -{ - QPID_LOG(debug, "SENT [" << identifier << "]: INIT(" << data << ")"); - SslIO::BufferBase* buff = aio->getQueuedBuffer(); - assert(buff); - framing::Buffer out(buff->bytes, buff->byteCount); - data.encode(out); - buff->dataCount = data.encodedSize(); - aio->queueWrite(buff); -} - -void SslHandler::abort() { - // Don't disconnect if we're already disconnecting - if (!readError) { - aio->requestCallback(boost::bind(&SslHandler::eof, this, _1)); - } -} -void SslHandler::activateOutput() { - aio->notifyPendingWrite(); -} - -void SslHandler::giveReadCredit(int32_t) { - // FIXME aconway 2008-12-05: not yet implemented. -} - -// Input side -void SslHandler::readbuff(SslIO& , SslIO::BufferBase* buff) { - if (readError) { - return; - } - size_t decoded = 0; - if (codec) { // Already initiated - try { - decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount); - }catch(const std::exception& e){ - QPID_LOG(error, e.what()); - readError = true; - aio->queueWriteClose(); - } - }else{ - framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); - framing::ProtocolInitiation protocolInit; - if (protocolInit.decode(in)) { - // We've just got the protocol negotiation so we can cancel the timeout for that - timeoutTimerTask->cancel(); - - decoded = in.getPosition(); - QPID_LOG(debug, "RECV [" << identifier << "]: INIT(" << protocolInit << ")"); - try { - codec = factory->create(protocolInit.getVersion(), *this, identifier, getSecuritySettings(aio)); - if (!codec) { - //TODO: may still want to revise this... - //send valid version header & close connection. - write(framing::ProtocolInitiation(framing::highestProtocolVersion)); - readError = true; - aio->queueWriteClose(); - } - } catch (const std::exception& e) { - QPID_LOG(error, e.what()); - readError = true; - aio->queueWriteClose(); - } - } - } - // TODO: unreading needs to go away, and when we can cope - // with multiple sub-buffers in the general buffer scheme, it will - if (decoded != size_t(buff->dataCount)) { - // Adjust buffer for used bytes and then "unread them" - buff->dataStart += decoded; - buff->dataCount -= decoded; - aio->unread(buff); - } else { - // Give whole buffer back to aio subsystem - aio->queueReadBuffer(buff); - } -} - -void SslHandler::eof(SslIO&) { - QPID_LOG(debug, "DISCONNECTED [" << identifier << "]"); - if (codec) codec->closed(); - aio->queueWriteClose(); -} - -void SslHandler::closedSocket(SslIO&, const SslSocket& s) { - // If we closed with data still to send log a warning - if (!aio->writeQueueEmpty()) { - QPID_LOG(warning, "CLOSING [" << identifier << "] unsent data (probably due to client disconnect)"); - } - delete &s; - aio->queueForDeletion(); - delete this; -} - -void SslHandler::disconnect(SslIO& a) { - // treat the same as eof - eof(a); -} - -// Notifications -void SslHandler::nobuffs(SslIO&) { -} - -void SslHandler::idle(SslIO&){ - if (isClient && codec == 0) { - codec = factory->create(*this, identifier, getSecuritySettings(aio)); - write(framing::ProtocolInitiation(codec->getVersion())); - // We've just sent the protocol negotiation so we can cancel the timeout for that - // This is not ideal, because we've not received anything yet, but heartbeats will - // be active soon - timeoutTimerTask->cancel(); - return; - } - if (codec == 0) return; - if (!codec->canEncode()) { - return; - } - SslIO::BufferBase* buff = aio->getQueuedBuffer(); - if (buff) { - size_t encoded=codec->encode(buff->bytes, buff->byteCount); - buff->dataCount = encoded; - aio->queueWrite(buff); - } - if (codec->isClosed()) - aio->queueWriteClose(); -} - -SecuritySettings SslHandler::getSecuritySettings(SslIO* aio) -{ - SecuritySettings settings = aio->getSecuritySettings(); - settings.nodict = nodict; - return settings; -} - - -}}} // namespace qpid::sys::ssl diff --git a/cpp/src/qpid/sys/ssl/SslHandler.h b/cpp/src/qpid/sys/ssl/SslHandler.h deleted file mode 100644 index 14814b0281..0000000000 --- a/cpp/src/qpid/sys/ssl/SslHandler.h +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef QPID_SYS_SSL_SSLHANDLER_H -#define QPID_SYS_SSL_SSLHANDLER_H - -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "qpid/sys/ConnectionCodec.h" -#include "qpid/sys/OutputControl.h" - -#include <boost/intrusive_ptr.hpp> - -namespace qpid { - -namespace framing { - class ProtocolInitiation; -} - -namespace sys { - -class Timer; -class TimerTask; - -namespace ssl { - -class SslIO; -struct SslIOBufferBase; -class SslSocket; - -class SslHandler : public OutputControl { - std::string identifier; - SslIO* aio; - ConnectionCodec::Factory* factory; - ConnectionCodec* codec; - bool readError; - bool isClient; - bool nodict; - boost::intrusive_ptr<sys::TimerTask> timeoutTimerTask; - - void write(const framing::ProtocolInitiation&); - qpid::sys::SecuritySettings getSecuritySettings(SslIO* aio); - - public: - SslHandler(std::string id, ConnectionCodec::Factory* f, bool nodict); - ~SslHandler(); - void init(SslIO* a, Timer& timer, uint32_t maxTime); - - void setClient() { isClient = true; } - - // Output side - void abort(); - void activateOutput(); - void giveReadCredit(int32_t); - - // Input side - void readbuff(SslIO& aio, SslIOBufferBase* buff); - void eof(SslIO& aio); - void disconnect(SslIO& aio); - - // Notifications - void nobuffs(SslIO& aio); - void idle(SslIO& aio); - void closedSocket(SslIO& aio, const SslSocket& s); -}; - -}}} // namespace qpid::sys::ssl - -#endif /*!QPID_SYS_SSL_SSLHANDLER_H*/ diff --git a/cpp/src/qpid/sys/ssl/SslIo.cpp b/cpp/src/qpid/sys/ssl/SslIo.cpp deleted file mode 100644 index bbfb703170..0000000000 --- a/cpp/src/qpid/sys/ssl/SslIo.cpp +++ /dev/null @@ -1,470 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "qpid/sys/ssl/SslIo.h" -#include "qpid/sys/ssl/SslSocket.h" -#include "qpid/sys/ssl/check.h" - -#include "qpid/sys/Time.h" -#include "qpid/sys/posix/check.h" -#include "qpid/log/Statement.h" - -// TODO The basic algorithm here is not really POSIX specific and with a bit more abstraction -// could (should) be promoted to be platform portable -#include <unistd.h> -#include <sys/socket.h> -#include <signal.h> -#include <errno.h> -#include <string.h> - -#include <boost/bind.hpp> - -namespace qpid { -namespace sys { -namespace ssl { - -namespace { - -/* - * Make *process* not generate SIGPIPE when writing to closed - * pipe/socket (necessary as default action is to terminate process) - */ -void ignoreSigpipe() { - ::signal(SIGPIPE, SIG_IGN); -} - -/* - * We keep per thread state to avoid locking overhead. The assumption is that - * on average all the connections are serviced by all the threads so the state - * recorded in each thread is about the same. If this turns out not to be the - * case we could rebalance the info occasionally. - */ -__thread int threadReadTotal = 0; -__thread int threadReadCount = 0; -__thread int threadWriteTotal = 0; -__thread int threadWriteCount = 0; -__thread int64_t threadMaxIoTimeNs = 2 * 1000000; // start at 2ms -} - -/* - * Asynch Acceptor - */ - -template <class T> -SslAcceptorTmpl<T>::SslAcceptorTmpl(const T& s, Callback callback) : - acceptedCallback(callback), - handle(s, boost::bind(&SslAcceptorTmpl<T>::readable, this, _1), 0, 0), - socket(s) { - - s.setNonblocking(); - ignoreSigpipe(); -} - -template <class T> -SslAcceptorTmpl<T>::~SslAcceptorTmpl() -{ - handle.stopWatch(); -} - -template <class T> -void SslAcceptorTmpl<T>::start(Poller::shared_ptr poller) { - handle.startWatch(poller); -} - -/* - * We keep on accepting as long as there is something to accept - */ -template <class T> -void SslAcceptorTmpl<T>::readable(DispatchHandle& h) { - Socket* s; - do { - errno = 0; - // TODO: Currently we ignore the peers address, perhaps we should - // log it or use it for connection acceptance. - try { - s = socket.accept(); - if (s) { - acceptedCallback(*s); - } else { - break; - } - } catch (const std::exception& e) { - QPID_LOG(error, "Could not accept socket: " << e.what()); - } - } while (true); - - h.rewatch(); -} - -// Explicitly instantiate the templates we need -template class SslAcceptorTmpl<SslSocket>; -template class SslAcceptorTmpl<SslMuxSocket>; - -/* - * Asynch Connector - */ - -SslConnector::SslConnector(const SslSocket& s, - Poller::shared_ptr poller, - std::string hostname, - std::string port, - ConnectedCallback connCb, - FailedCallback failCb) : - DispatchHandle(s, - 0, - boost::bind(&SslConnector::connComplete, this, _1), - boost::bind(&SslConnector::connComplete, this, _1)), - connCallback(connCb), - failCallback(failCb), - socket(s) -{ - //TODO: would be better for connect to be performed on a - //non-blocking socket, but that doesn't work at present so connect - //blocks until complete - try { - socket.connect(hostname, port); - socket.setNonblocking(); - startWatch(poller); - } catch(std::exception& e) { - failure(-1, std::string(e.what())); - } -} - -void SslConnector::connComplete(DispatchHandle& h) -{ - int errCode = socket.getError(); - - h.stopWatch(); - if (errCode == 0) { - connCallback(socket); - DispatchHandle::doDelete(); - } else { - // TODO: This need to be fixed as strerror isn't thread safe - failure(errCode, std::string(::strerror(errCode))); - } -} - -void SslConnector::failure(int errCode, std::string message) -{ - if (failCallback) - failCallback(errCode, message); - - socket.close(); - delete &socket; - - DispatchHandle::doDelete(); -} - -/* - * Asynch reader/writer - */ -SslIO::SslIO(const SslSocket& s, - ReadCallback rCb, EofCallback eofCb, DisconnectCallback disCb, - ClosedCallback cCb, BuffersEmptyCallback eCb, IdleCallback iCb) : - - DispatchHandle(s, - boost::bind(&SslIO::readable, this, _1), - boost::bind(&SslIO::writeable, this, _1), - boost::bind(&SslIO::disconnected, this, _1)), - readCallback(rCb), - eofCallback(eofCb), - disCallback(disCb), - closedCallback(cCb), - emptyCallback(eCb), - idleCallback(iCb), - socket(s), - queuedClose(false), - writePending(false) { - - s.setNonblocking(); -} - -SslIO::~SslIO() { -} - -void SslIO::queueForDeletion() { - DispatchHandle::doDelete(); -} - -void SslIO::start(Poller::shared_ptr poller) { - DispatchHandle::startWatch(poller); -} - -void SslIO::createBuffers(uint32_t size) { - // Allocate all the buffer memory at once - bufferMemory.reset(new char[size*BufferCount]); - - // Create the Buffer structs in a vector - // And push into the buffer queue - buffers.reserve(BufferCount); - for (uint32_t i = 0; i < BufferCount; i++) { - buffers.push_back(BufferBase(&bufferMemory[i*size], size)); - queueReadBuffer(&buffers[i]); - } -} - -void SslIO::queueReadBuffer(BufferBase* buff) { - assert(buff); - buff->dataStart = 0; - buff->dataCount = 0; - bufferQueue.push_back(buff); - DispatchHandle::rewatchRead(); -} - -void SslIO::unread(BufferBase* buff) { - assert(buff); - if (buff->dataStart != 0) { - memmove(buff->bytes, buff->bytes+buff->dataStart, buff->dataCount); - buff->dataStart = 0; - } - bufferQueue.push_front(buff); - DispatchHandle::rewatchRead(); -} - -void SslIO::queueWrite(BufferBase* buff) { - assert(buff); - // If we've already closed the socket then throw the write away - if (queuedClose) { - bufferQueue.push_front(buff); - return; - } else { - writeQueue.push_front(buff); - } - writePending = false; - DispatchHandle::rewatchWrite(); -} - -void SslIO::notifyPendingWrite() { - writePending = true; - DispatchHandle::rewatchWrite(); -} - -void SslIO::queueWriteClose() { - queuedClose = true; - DispatchHandle::rewatchWrite(); -} - -void SslIO::requestCallback(RequestCallback callback) { - // TODO creating a function object every time isn't all that - // efficient - if this becomes heavily used do something better (what?) - assert(callback); - DispatchHandle::call(boost::bind(&SslIO::requestedCall, this, callback)); -} - -void SslIO::requestedCall(RequestCallback callback) { - assert(callback); - callback(*this); -} - -/** Return a queued buffer if there are enough - * to spare - */ -SslIO::BufferBase* SslIO::getQueuedBuffer() { - // Always keep at least one buffer (it might have data that was "unread" in it) - if (bufferQueue.size()<=1) - return 0; - BufferBase* buff = bufferQueue.back(); - assert(buff); - buff->dataStart = 0; - buff->dataCount = 0; - bufferQueue.pop_back(); - return buff; -} - -/* - * We keep on reading as long as we have something to read and a buffer to put - * it in - */ -void SslIO::readable(DispatchHandle& h) { - AbsTime readStartTime = AbsTime::now(); - do { - // (Try to) get a buffer - if (!bufferQueue.empty()) { - // Read into buffer - BufferBase* buff = bufferQueue.front(); - assert(buff); - bufferQueue.pop_front(); - errno = 0; - int readCount = buff->byteCount-buff->dataCount; - int rc = socket.read(buff->bytes + buff->dataCount, readCount); - if (rc > 0) { - buff->dataCount += rc; - threadReadTotal += rc; - - readCallback(*this, buff); - if (rc != readCount) { - // If we didn't fill the read buffer then time to stop reading - break; - } - - // Stop reading if we've overrun our timeslot - if (Duration(readStartTime, AbsTime::now()) > threadMaxIoTimeNs) { - break; - } - - } else { - // Put buffer back (at front so it doesn't interfere with unread buffers) - bufferQueue.push_front(buff); - assert(buff); - - // Eof or other side has gone away - if (rc == 0 || errno == ECONNRESET) { - eofCallback(*this); - h.unwatchRead(); - break; - } else if (errno == EAGAIN) { - // We have just put a buffer back so we know - // we can carry on watching for reads - break; - } else { - // Report error then just treat as a socket disconnect - QPID_LOG(error, "Error reading socket: " << getErrorString(PR_GetError())); - eofCallback(*this); - h.unwatchRead(); - break; - } - } - } else { - // Something to read but no buffer - if (emptyCallback) { - emptyCallback(*this); - } - // If we still have no buffers we can't do anything more - if (bufferQueue.empty()) { - h.unwatchRead(); - break; - } - - } - } while (true); - - ++threadReadCount; - return; -} - -/* - * We carry on writing whilst we have data to write and we can write - */ -void SslIO::writeable(DispatchHandle& h) { - AbsTime writeStartTime = AbsTime::now(); - do { - // See if we've got something to write - if (!writeQueue.empty()) { - // Write buffer - BufferBase* buff = writeQueue.back(); - writeQueue.pop_back(); - errno = 0; - assert(buff->dataStart+buff->dataCount <= buff->byteCount); - int rc = socket.write(buff->bytes+buff->dataStart, buff->dataCount); - if (rc >= 0) { - threadWriteTotal += rc; - - // If we didn't write full buffer put rest back - if (rc != buff->dataCount) { - buff->dataStart += rc; - buff->dataCount -= rc; - writeQueue.push_back(buff); - break; - } - - // Recycle the buffer - queueReadBuffer(buff); - - // Stop writing if we've overrun our timeslot - if (Duration(writeStartTime, AbsTime::now()) > threadMaxIoTimeNs) { - break; - } - } else { - // Put buffer back - writeQueue.push_back(buff); - if (errno == ECONNRESET || errno == EPIPE) { - // Just stop watching for write here - we'll get a - // disconnect callback soon enough - h.unwatchWrite(); - break; - } else if (errno == EAGAIN) { - // We have just put a buffer back so we know - // we can carry on watching for writes - break; - } else { - QPID_LOG(error, "Error writing to socket: " << getErrorString(PR_GetError())); - h.unwatchWrite(); - break; - } - } - } else { - // If we're waiting to close the socket then can do it now as there is nothing to write - if (queuedClose) { - close(h); - break; - } - // Fd is writable, but nothing to write - if (idleCallback) { - writePending = false; - idleCallback(*this); - } - // If we still have no buffers to write we can't do anything more - if (writeQueue.empty() && !writePending && !queuedClose) { - h.unwatchWrite(); - // The following handles the case where writePending is - // set to true after the test above; in this case its - // possible that the unwatchWrite overwrites the - // desired rewatchWrite so we correct that here - if (writePending) - h.rewatchWrite(); - break; - } - } - } while (true); - - ++threadWriteCount; - return; -} - -void SslIO::disconnected(DispatchHandle& h) { - // If we've already queued close do it instead of disconnected callback - if (queuedClose) { - close(h); - } else if (disCallback) { - disCallback(*this); - h.unwatch(); - } -} - -/* - * Close the socket and callback to say we've done it - */ -void SslIO::close(DispatchHandle& h) { - h.stopWatch(); - socket.close(); - if (closedCallback) { - closedCallback(*this, socket); - } -} - -SecuritySettings SslIO::getSecuritySettings() { - SecuritySettings settings; - settings.ssf = socket.getKeyLen(); - settings.authid = socket.getClientAuthId(); - return settings; -} - -}}} diff --git a/cpp/src/qpid/sys/ssl/SslIo.h b/cpp/src/qpid/sys/ssl/SslIo.h deleted file mode 100644 index f3112bfa65..0000000000 --- a/cpp/src/qpid/sys/ssl/SslIo.h +++ /dev/null @@ -1,193 +0,0 @@ -#ifndef _sys_ssl_SslIO -#define _sys_ssl_SslIO -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "qpid/sys/DispatchHandle.h" -#include "qpid/sys/SecuritySettings.h" - -#include <boost/function.hpp> -#include <boost/shared_array.hpp> -#include <deque> - -namespace qpid { -namespace sys { - -class Socket; - -namespace ssl { - -class SslSocket; - -/* - * Asynchronous ssl acceptor: accepts connections then does a callback - * with the accepted fd - */ -template <class T> -class SslAcceptorTmpl { -public: - typedef boost::function1<void, const Socket&> Callback; - -private: - Callback acceptedCallback; - qpid::sys::DispatchHandle handle; - const T& socket; - -public: - SslAcceptorTmpl(const T& s, Callback callback); - ~SslAcceptorTmpl(); - void start(qpid::sys::Poller::shared_ptr poller); - -private: - void readable(qpid::sys::DispatchHandle& handle); -}; - -/* - * Asynchronous ssl connector: starts the process of initiating a - * connection and invokes a callback when completed or failed. - */ -class SslConnector : private qpid::sys::DispatchHandle { -public: - typedef boost::function1<void, const SslSocket&> ConnectedCallback; - typedef boost::function2<void, int, std::string> FailedCallback; - -private: - ConnectedCallback connCallback; - FailedCallback failCallback; - const SslSocket& socket; - -public: - SslConnector(const SslSocket& socket, - Poller::shared_ptr poller, - std::string hostname, - std::string port, - ConnectedCallback connCb, - FailedCallback failCb = 0); - -private: - void connComplete(DispatchHandle& handle); - void failure(int, std::string); -}; - -struct SslIOBufferBase { - char* bytes; - int32_t byteCount; - int32_t dataStart; - int32_t dataCount; - - SslIOBufferBase(char* const b, const int32_t s) : - bytes(b), - byteCount(s), - dataStart(0), - dataCount(0) - {} - - virtual ~SslIOBufferBase() - {} -}; - -/* - * Asychronous reader/writer: - * Reader accepts buffers to read into; reads into the provided buffers - * and then does a callback with the buffer and amount read. Optionally it can callback - * when there is something to read but no buffer to read it into. - * - * Writer accepts a buffer and queues it for writing; can also be given - * a callback for when writing is "idle" (ie fd is writable, but nothing to write) - * - * The class is implemented in terms of DispatchHandle to allow it to be deleted by deleting - * the contained DispatchHandle - */ -class SslIO : private qpid::sys::DispatchHandle { -public: - typedef SslIOBufferBase BufferBase; - - typedef boost::function2<void, SslIO&, BufferBase*> ReadCallback; - typedef boost::function1<void, SslIO&> EofCallback; - typedef boost::function1<void, SslIO&> DisconnectCallback; - typedef boost::function2<void, SslIO&, const SslSocket&> ClosedCallback; - typedef boost::function1<void, SslIO&> BuffersEmptyCallback; - typedef boost::function1<void, SslIO&> IdleCallback; - typedef boost::function1<void, SslIO&> RequestCallback; - - SslIO(const SslSocket& s, - ReadCallback rCb, EofCallback eofCb, DisconnectCallback disCb, - ClosedCallback cCb = 0, BuffersEmptyCallback eCb = 0, IdleCallback iCb = 0); -private: - ReadCallback readCallback; - EofCallback eofCallback; - DisconnectCallback disCallback; - ClosedCallback closedCallback; - BuffersEmptyCallback emptyCallback; - IdleCallback idleCallback; - const SslSocket& socket; - std::deque<BufferBase*> bufferQueue; - std::deque<BufferBase*> writeQueue; - std::vector<BufferBase> buffers; - boost::shared_array<char> bufferMemory; - bool queuedClose; - /** - * This flag is used to detect and handle concurrency between - * calls to notifyPendingWrite() (which can be made from any thread) and - * the execution of the writeable() method (which is always on the - * thread processing this handle. - */ - volatile bool writePending; - -public: - /* - * Size of IO buffers - this is the maximum possible frame size + 1 - */ - const static uint32_t MaxBufferSize = 65536; - - /* - * Number of IO buffers allocated - I think the code can only use 2 - - * 1 for reading and 1 for writing, allocate 4 for safety - */ - const static uint32_t BufferCount = 4; - - void queueForDeletion(); - - void start(qpid::sys::Poller::shared_ptr poller); - void createBuffers(uint32_t size = MaxBufferSize); - void queueReadBuffer(BufferBase* buff); - void unread(BufferBase* buff); - void queueWrite(BufferBase* buff); - void notifyPendingWrite(); - void queueWriteClose(); - bool writeQueueEmpty() { return writeQueue.empty(); } - void requestCallback(RequestCallback); - BufferBase* getQueuedBuffer(); - - qpid::sys::SecuritySettings getSecuritySettings(); - -private: - ~SslIO(); - void readable(qpid::sys::DispatchHandle& handle); - void writeable(qpid::sys::DispatchHandle& handle); - void disconnected(qpid::sys::DispatchHandle& handle); - void requestedCall(RequestCallback); - void close(qpid::sys::DispatchHandle& handle); -}; - -}}} - -#endif // _sys_ssl_SslIO diff --git a/cpp/src/qpid/sys/ssl/SslSocket.cpp b/cpp/src/qpid/sys/ssl/SslSocket.cpp index 30234bb686..a328e49c13 100644 --- a/cpp/src/qpid/sys/ssl/SslSocket.cpp +++ b/cpp/src/qpid/sys/ssl/SslSocket.cpp @@ -20,6 +20,7 @@ */ #include "qpid/sys/ssl/SslSocket.h" +#include "qpid/sys/SocketAddress.h" #include "qpid/sys/ssl/check.h" #include "qpid/sys/ssl/util.h" #include "qpid/Exception.h" @@ -52,28 +53,6 @@ namespace sys { namespace ssl { namespace { -std::string getService(int fd, bool local) -{ - ::sockaddr_storage name; // big enough for any socket address - ::socklen_t namelen = sizeof(name); - - int result = -1; - if (local) { - result = ::getsockname(fd, (::sockaddr*)&name, &namelen); - } else { - result = ::getpeername(fd, (::sockaddr*)&name, &namelen); - } - - QPID_POSIX_CHECK(result); - - char servName[NI_MAXSERV]; - if (int rc=::getnameinfo((::sockaddr*)&name, namelen, 0, 0, - servName, sizeof(servName), - NI_NUMERICHOST | NI_NUMERICSERV) != 0) - throw QPID_POSIX_ERROR(rc); - return servName; -} - const std::string DOMAIN_SEPARATOR("@"); const std::string DC_SEPARATOR("."); const std::string DC("DC"); @@ -101,14 +80,18 @@ std::string getDomainFromSubject(std::string subject) } return domain; } - } -SslSocket::SslSocket() : socket(0), prototype(0) +SslSocket::SslSocket(const std::string& certName, bool clientAuth) : + nssSocket(0), certname(certName), prototype(0) { - impl->fd = ::socket (PF_INET, SOCK_STREAM, 0); - if (impl->fd < 0) throw QPID_POSIX_ERROR(errno); - socket = SSL_ImportFD(0, PR_ImportTCPSocket(impl->fd)); + //configure prototype socket: + prototype = SSL_ImportFD(0, PR_NewTCPSocket()); + + if (clientAuth) { + NSS_CHECK(SSL_OptionSet(prototype, SSL_REQUEST_CERTIFICATE, PR_TRUE)); + NSS_CHECK(SSL_OptionSet(prototype, SSL_REQUIRE_CERTIFICATE, PR_TRUE)); + } } /** @@ -116,25 +99,44 @@ SslSocket::SslSocket() : socket(0), prototype(0) * returned from accept. Because we use posix accept rather than * PR_Accept, we have to reset the handshake. */ -SslSocket::SslSocket(IOHandlePrivate* ioph, PRFileDesc* model) : Socket(ioph), socket(0), prototype(0) +SslSocket::SslSocket(int fd, PRFileDesc* model) : BSDSocket(fd), nssSocket(0), prototype(0) { - socket = SSL_ImportFD(model, PR_ImportTCPSocket(impl->fd)); - NSS_CHECK(SSL_ResetHandshake(socket, true)); + nssSocket = SSL_ImportFD(model, PR_ImportTCPSocket(fd)); + NSS_CHECK(SSL_ResetHandshake(nssSocket, PR_TRUE)); } void SslSocket::setNonblocking() const { + if (!nssSocket) { + BSDSocket::setNonblocking(); + return; + } PRSocketOptionData option; option.option = PR_SockOpt_Nonblocking; option.value.non_blocking = true; - PR_SetSocketOption(socket, &option); + PR_SetSocketOption(nssSocket, &option); } -void SslSocket::connect(const std::string& host, const std::string& port) const +void SslSocket::setTcpNoDelay() const { - std::stringstream namestream; - namestream << host << ":" << port; - connectname = namestream.str(); + if (!nssSocket) { + BSDSocket::setTcpNoDelay(); + return; + } + PRSocketOptionData option; + option.option = PR_SockOpt_NoDelay; + option.value.no_delay = true; + PR_SetSocketOption(nssSocket, &option); +} + +void SslSocket::connect(const SocketAddress& addr) const +{ + BSDSocket::connect(addr); +} + +void SslSocket::finishConnect(const SocketAddress& addr) const +{ + nssSocket = SSL_ImportFD(0, PR_ImportTCPSocket(fd)); void* arg; // Use the connection's cert-name if it has one; else use global cert-name @@ -145,75 +147,48 @@ void SslSocket::connect(const std::string& host, const std::string& port) const } else { arg = const_cast<char*>(SslOptions::global.certName.c_str()); } - NSS_CHECK(SSL_GetClientAuthDataHook(socket, NSS_GetClientAuthData, arg)); - NSS_CHECK(SSL_SetURL(socket, host.data())); - - char hostBuffer[PR_NETDB_BUF_SIZE]; - PRHostEnt hostEntry; - PR_CHECK(PR_GetHostByName(host.data(), hostBuffer, PR_NETDB_BUF_SIZE, &hostEntry)); - PRNetAddr address; - int value = PR_EnumerateHostEnt(0, &hostEntry, boost::lexical_cast<PRUint16>(port), &address); - if (value < 0) { - throw Exception(QPID_MSG("Error getting address for host: " << ErrorString())); - } else if (value == 0) { - throw Exception(QPID_MSG("Could not resolve address for host.")); - } - PR_CHECK(PR_Connect(socket, &address, PR_INTERVAL_NO_TIMEOUT)); - NSS_CHECK(SSL_ForceHandshake(socket)); + NSS_CHECK(SSL_GetClientAuthDataHook(nssSocket, NSS_GetClientAuthData, arg)); + + url = addr.getHost(); + NSS_CHECK(SSL_SetURL(nssSocket, url.data())); + + NSS_CHECK(SSL_ResetHandshake(nssSocket, PR_FALSE)); + NSS_CHECK(SSL_ForceHandshake(nssSocket)); } void SslSocket::close() const { - if (impl->fd > 0) { - PR_Close(socket); - impl->fd = -1; + if (!nssSocket) { + BSDSocket::close(); + return; + } + if (fd > 0) { + PR_Close(nssSocket); + fd = -1; } } -int SslSocket::listen(uint16_t port, int backlog, const std::string& certName, bool clientAuth) const +int SslSocket::listen(const SocketAddress& sa, int backlog) const { - //configure prototype socket: - prototype = SSL_ImportFD(0, PR_NewTCPSocket()); - if (clientAuth) { - NSS_CHECK(SSL_OptionSet(prototype, SSL_REQUEST_CERTIFICATE, PR_TRUE)); - NSS_CHECK(SSL_OptionSet(prototype, SSL_REQUIRE_CERTIFICATE, PR_TRUE)); - } - //get certificate and key (is this the correct way?) - CERTCertificate *cert = PK11_FindCertFromNickname(const_cast<char*>(certName.c_str()), 0); - if (!cert) throw Exception(QPID_MSG("Failed to load certificate '" << certName << "'")); + std::string cName( (certname == "") ? "localhost.localdomain" : certname); + CERTCertificate *cert = PK11_FindCertFromNickname(const_cast<char*>(cName.c_str()), 0); + if (!cert) throw Exception(QPID_MSG("Failed to load certificate '" << cName << "'")); SECKEYPrivateKey *key = PK11_FindKeyByAnyCert(cert, 0); if (!key) throw Exception(QPID_MSG("Failed to retrieve private key from certificate")); NSS_CHECK(SSL_ConfigSecureServer(prototype, cert, key, NSS_FindCertKEAType(cert))); SECKEY_DestroyPrivateKey(key); CERT_DestroyCertificate(cert); - //bind and listen - const int& socket = impl->fd; - int yes=1; - QPID_POSIX_CHECK(setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes))); - struct sockaddr_in name; - name.sin_family = AF_INET; - name.sin_port = htons(port); - name.sin_addr.s_addr = 0; - if (::bind(socket, (struct sockaddr*)&name, sizeof(name)) < 0) - throw Exception(QPID_MSG("Can't bind to port " << port << ": " << strError(errno))); - if (::listen(socket, backlog) < 0) - throw Exception(QPID_MSG("Can't listen on port " << port << ": " << strError(errno))); - - socklen_t namelen = sizeof(name); - if (::getsockname(socket, (struct sockaddr*)&name, &namelen) < 0) - throw QPID_POSIX_ERROR(errno); - - return ntohs(name.sin_port); + return BSDSocket::listen(sa, backlog); } -SslSocket* SslSocket::accept() const +Socket* SslSocket::accept() const { QPID_LOG(trace, "Accepting SSL connection."); - int afd = ::accept(impl->fd, 0, 0); + int afd = ::accept(fd, 0, 0); if ( afd >= 0) { - return new SslSocket(new IOHandlePrivate(afd), prototype); + return new SslSocket(afd, prototype); } else if (errno == EAGAIN) { return 0; } else { @@ -297,17 +272,22 @@ static bool isSslStream(int afd) { return isSSL2Handshake || isSSL3Handshake; } +SslMuxSocket::SslMuxSocket(const std::string& certName, bool clientAuth) : + SslSocket(certName, clientAuth) +{ +} + Socket* SslMuxSocket::accept() const { - int afd = ::accept(impl->fd, 0, 0); + int afd = ::accept(fd, 0, 0); if (afd >= 0) { QPID_LOG(trace, "Accepting connection with optional SSL wrapper."); if (isSslStream(afd)) { QPID_LOG(trace, "Accepted SSL connection."); - return new SslSocket(new IOHandlePrivate(afd), prototype); + return new SslSocket(afd, prototype); } else { QPID_LOG(trace, "Accepted Plaintext connection."); - return new Socket(new IOHandlePrivate(afd)); + return new BSDSocket(afd); } } else if (errno == EAGAIN) { return 0; @@ -318,32 +298,12 @@ Socket* SslMuxSocket::accept() const int SslSocket::read(void *buf, size_t count) const { - return PR_Read(socket, buf, count); + return PR_Read(nssSocket, buf, count); } int SslSocket::write(const void *buf, size_t count) const { - return PR_Write(socket, buf, count); -} - -uint16_t SslSocket::getLocalPort() const -{ - return std::atoi(getService(impl->fd, true).c_str()); -} - -uint16_t SslSocket::getRemotePort() const -{ - return atoi(getService(impl->fd, true).c_str()); -} - -void SslSocket::setTcpNoDelay(bool nodelay) const -{ - if (nodelay) { - PRSocketOptionData option; - option.option = PR_SockOpt_NoDelay; - option.value.no_delay = true; - PR_SetSocketOption(socket, &option); - } + return PR_Write(nssSocket, buf, count); } void SslSocket::setCertName(const std::string& name) @@ -359,7 +319,7 @@ int SslSocket::getKeyLen() const int keySize = 0; SECStatus rc; - rc = SSL_SecurityStatus( socket, + rc = SSL_SecurityStatus( nssSocket, &enabled, NULL, NULL, @@ -374,7 +334,7 @@ int SslSocket::getKeyLen() const std::string SslSocket::getClientAuthId() const { std::string authId; - CERTCertificate* cert = SSL_PeerCertificate(socket); + CERTCertificate* cert = SSL_PeerCertificate(nssSocket); if (cert) { authId = CERT_GetCommonName(&(cert->subject)); /* diff --git a/cpp/src/qpid/sys/ssl/SslSocket.h b/cpp/src/qpid/sys/ssl/SslSocket.h index eabadcbe23..fc97059cfd 100644 --- a/cpp/src/qpid/sys/ssl/SslSocket.h +++ b/cpp/src/qpid/sys/ssl/SslSocket.h @@ -23,7 +23,7 @@ */ #include "qpid/sys/IOHandle.h" -#include "qpid/sys/Socket.h" +#include "qpid/sys/posix/BSDSocket.h" #include <nspr.h> #include <string> @@ -37,55 +37,54 @@ class Duration; namespace ssl { -class SslSocket : public qpid::sys::Socket +class SslSocket : public qpid::sys::BSDSocket { public: - /** Create a socket wrapper for descriptor. */ - SslSocket(); + /** Create a socket wrapper for descriptor. + *@param certName name of certificate to use to identify the socket + */ + SslSocket(const std::string& certName = "", bool clientAuth = false); /** Set socket non blocking */ void setNonblocking() const; /** Set tcp-nodelay */ - void setTcpNoDelay(bool nodelay) const; + void setTcpNoDelay() const; /** Set SSL cert-name. Allows the cert-name to be set per * connection, overriding global cert-name settings from * NSSInit().*/ void setCertName(const std::string& certName); - void connect(const std::string& host, const std::string& port) const; + void connect(const SocketAddress&) const; + void finishConnect(const SocketAddress&) const; void close() const; /** Bind to a port and start listening. *@param port 0 means choose an available port. *@param backlog maximum number of pending connections. - *@param certName name of certificate to use to identify the server *@return The bound port. */ - int listen(uint16_t port = 0, int backlog = 10, const std::string& certName = "localhost.localdomain", bool clientAuth = false) const; + int listen(const SocketAddress&, int backlog = 10) const; /** * Accept a connection from a socket that is already listening * and has an incoming connection */ - SslSocket* accept() const; + virtual Socket* accept() const; // TODO The following are raw operations, maybe they need better wrapping? int read(void *buf, size_t count) const; int write(const void *buf, size_t count) const; - uint16_t getLocalPort() const; - uint16_t getRemotePort() const; - int getKeyLen() const; std::string getClientAuthId() const; protected: - mutable std::string connectname; - mutable PRFileDesc* socket; + mutable PRFileDesc* nssSocket; std::string certname; + mutable std::string url; /** * 'model' socket, with configuration to use when importing @@ -94,13 +93,14 @@ protected: */ mutable PRFileDesc* prototype; - SslSocket(IOHandlePrivate* ioph, PRFileDesc* model); - friend class SslMuxSocket; + SslSocket(int fd, PRFileDesc* model); + friend class SslMuxSocket; // Needed for this constructor }; class SslMuxSocket : public SslSocket { public: + SslMuxSocket(const std::string& certName = "", bool clientAuth = false); Socket* accept() const; }; diff --git a/cpp/src/qpid/sys/ssl/util.cpp b/cpp/src/qpid/sys/ssl/util.cpp index 3078e894df..de5d638b09 100644 --- a/cpp/src/qpid/sys/ssl/util.cpp +++ b/cpp/src/qpid/sys/ssl/util.cpp @@ -31,8 +31,6 @@ #include <iostream> #include <fstream> -#include <boost/filesystem/operations.hpp> -#include <boost/filesystem/path.hpp> namespace qpid { namespace sys { @@ -82,15 +80,14 @@ SslOptions SslOptions::global; char* readPasswordFromFile(PK11SlotInfo*, PRBool retry, void*) { const std::string& passwordFile = SslOptions::global.certPasswordFile; - if (retry || passwordFile.empty() || !boost::filesystem::exists(passwordFile)) { - return 0; - } else { - std::ifstream file(passwordFile.c_str()); - std::string password; - file >> password; - return PL_strdup(password.c_str()); - } -} + if (retry || passwordFile.empty()) return 0; + std::ifstream file(passwordFile.c_str()); + if (!file) return 0; + + std::string password; + file >> password; + return PL_strdup(password.c_str()); +} void initNSS(const SslOptions& options, bool server) { diff --git a/cpp/src/qpid/sys/windows/AsynchIO.cpp b/cpp/src/qpid/sys/windows/AsynchIO.cpp index 355acbe0e6..b36ee9f941 100644 --- a/cpp/src/qpid/sys/windows/AsynchIO.cpp +++ b/cpp/src/qpid/sys/windows/AsynchIO.cpp @@ -24,6 +24,8 @@ #include "qpid/sys/AsynchIO.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/Socket.h" +#include "qpid/sys/windows/WinSocket.h" +#include "qpid/sys/SocketAddress.h" #include "qpid/sys/Poller.h" #include "qpid/sys/Thread.h" #include "qpid/sys/Time.h" @@ -50,8 +52,8 @@ namespace { * The function pointers for AcceptEx and ConnectEx need to be looked up * at run time. */ -const LPFN_ACCEPTEX lookUpAcceptEx(const qpid::sys::Socket& s) { - SOCKET h = toSocketHandle(s); +const LPFN_ACCEPTEX lookUpAcceptEx(const qpid::sys::IOHandle& io) { + SOCKET h = io.fd; GUID guidAcceptEx = WSAID_ACCEPTEX; DWORD dwBytes = 0; LPFN_ACCEPTEX fnAcceptEx; @@ -93,12 +95,14 @@ private: AsynchAcceptor::Callback acceptedCallback; const Socket& socket; + const SOCKET wSocket; const LPFN_ACCEPTEX fnAcceptEx; }; AsynchAcceptor::AsynchAcceptor(const Socket& s, Callback callback) : acceptedCallback(callback), socket(s), + wSocket(IOHandle(s).fd), fnAcceptEx(lookUpAcceptEx(s)) { s.setNonblocking(); @@ -121,8 +125,8 @@ void AsynchAcceptor::restart(void) { this, socket); BOOL status; - status = fnAcceptEx(toSocketHandle(socket), - toSocketHandle(*result->newSocket), + status = fnAcceptEx(wSocket, + IOHandle(*result->newSocket).fd, result->addressBuffer, 0, AsynchAcceptResult::SOCKADDRMAXLEN, @@ -133,16 +137,30 @@ void AsynchAcceptor::restart(void) { } +Socket* createSameTypeSocket(const Socket& sock) { + SOCKET socket = IOHandle(sock).fd; + // Socket currently has no actual socket attached + if (socket == INVALID_SOCKET) + return new WinSocket; + + ::sockaddr_storage sa; + ::socklen_t salen = sizeof(sa); + QPID_WINSOCK_CHECK(::getsockname(socket, (::sockaddr*)&sa, &salen)); + SOCKET s = ::socket(sa.ss_family, SOCK_STREAM, 0); // Currently only work with SOCK_STREAM + if (s == INVALID_SOCKET) throw QPID_WINDOWS_ERROR(WSAGetLastError()); + return new WinSocket(s); +} + AsynchAcceptResult::AsynchAcceptResult(AsynchAcceptor::Callback cb, AsynchAcceptor *acceptor, - const Socket& listener) + const Socket& lsocket) : callback(cb), acceptor(acceptor), - listener(toSocketHandle(listener)), - newSocket(listener.createSameTypeSocket()) { + listener(IOHandle(lsocket).fd), + newSocket(createSameTypeSocket(lsocket)) { } void AsynchAcceptResult::success(size_t /*bytesTransferred*/) { - ::setsockopt (toSocketHandle(*newSocket), + ::setsockopt (IOHandle(*newSocket).fd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char*)&listener, @@ -180,6 +198,7 @@ public: ConnectedCallback connCb, FailedCallback failCb = 0); void start(Poller::shared_ptr poller); + void requestCallback(RequestCallback rCb); }; AsynchConnector::AsynchConnector(const Socket& sock, @@ -195,7 +214,7 @@ AsynchConnector::AsynchConnector(const Socket& sock, void AsynchConnector::start(Poller::shared_ptr) { try { - socket.connect(hostname, port); + socket.connect(SocketAddress(hostname, port)); socket.setNonblocking(); connCallback(socket); } catch(std::exception& e) { @@ -205,6 +224,13 @@ void AsynchConnector::start(Poller::shared_ptr) } } +// This can never be called in the current windows code as connect +// is blocking and requestCallback only makes sense if connect is +// non-blocking with the results returned via a poller callback. +void AsynchConnector::requestCallback(RequestCallback rCb) +{ +} + } // namespace windows AsynchAcceptor* AsynchAcceptor::create(const Socket& s, @@ -260,8 +286,6 @@ public: virtual void notifyPendingWrite(); virtual void queueWriteClose(); virtual bool writeQueueEmpty(); - virtual void startReading(); - virtual void stopReading(); virtual void requestCallback(RequestCallback); /** @@ -272,6 +296,8 @@ public: */ virtual BufferBase* getQueuedBuffer(); + virtual SecuritySettings getSecuritySettings(void); + private: ReadCallback readCallback; EofCallback eofCallback; @@ -319,6 +345,12 @@ private: void close(void); /** + * startReading initiates reading, readComplete() is + * called when the read completes. + */ + void startReading(); + + /** * readComplete is called when a read request is complete. * * @param result Results of the operation. @@ -362,7 +394,7 @@ class CallbackHandle : public IOHandle { public: CallbackHandle(AsynchIoResult::Completer completeCb, AsynchIO::RequestCallback reqCb = 0) : - IOHandle(new IOHandlePrivate (INVALID_SOCKET, completeCb, reqCb)) + IOHandle(INVALID_SOCKET, completeCb, reqCb) {} }; @@ -515,7 +547,7 @@ void AsynchIO::startReading() { DWORD bytesReceived = 0, flags = 0; InterlockedIncrement(&opsInProgress); readInProgress = true; - int status = WSARecv(toSocketHandle(socket), + int status = WSARecv(IOHandle(socket).fd, const_cast<LPWSABUF>(result->getWSABUF()), 1, &bytesReceived, &flags, @@ -537,15 +569,6 @@ void AsynchIO::startReading() { return; } -// stopReading was added to prevent a race condition with read-credit on Linux. -// It may or may not be required on windows. -// -// AsynchIOHandler::readbuff() calls stopReading() inside the same -// critical section that protects startReading() in -// AsynchIOHandler::giveReadCredit(). -// -void AsynchIO::stopReading() {} - // Queue the specified callback for invocation from an I/O thread. void AsynchIO::requestCallback(RequestCallback callback) { // This method is generally called from a processing thread; transfer @@ -613,7 +636,7 @@ void AsynchIO::startWrite(AsynchIO::BufferBase* buff) { buff, buff->dataCount); DWORD bytesSent = 0; - int status = WSASend(toSocketHandle(socket), + int status = WSASend(IOHandle(socket).fd, const_cast<LPWSABUF>(result->getWSABUF()), 1, &bytesSent, 0, @@ -639,6 +662,13 @@ void AsynchIO::close(void) { notifyClosed(); } +SecuritySettings AsynchIO::getSecuritySettings() { + SecuritySettings settings; + settings.ssf = socket.getKeyLen(); + settings.authid = socket.getClientAuthId(); + return settings; +} + void AsynchIO::readComplete(AsynchReadResult *result) { int status = result->getStatus(); size_t bytes = result->getTransferred(); @@ -683,7 +713,8 @@ void AsynchIO::writeComplete(AsynchWriteResult *result) { else { // An error... if it's a connection close, ignore it - it will be // noticed and handled on a read completion any moment now. - // What to do with real error??? Save the Buffer? + // What to do with real error??? Save the Buffer? TBD. + queueReadBuffer(buff); // All done; back to the pool } } diff --git a/cpp/src/qpid/sys/windows/FileSysDir.cpp b/cpp/src/qpid/sys/windows/FileSysDir.cpp index 88f1637d48..e090747715 100644 --- a/cpp/src/qpid/sys/windows/FileSysDir.cpp +++ b/cpp/src/qpid/sys/windows/FileSysDir.cpp @@ -24,6 +24,9 @@ #include <sys/stat.h> #include <direct.h> #include <errno.h> +#include <windows.h> +#include <strsafe.h> + namespace qpid { namespace sys { @@ -50,4 +53,36 @@ void FileSysDir::mkdir(void) throw Exception ("Can't create directory: " + dirPath); } +void FileSysDir::forEachFile(Callback cb) const { + + WIN32_FIND_DATAA findFileData; + char szDir[MAX_PATH]; + size_t dirPathLength; + HANDLE hFind = INVALID_HANDLE_VALUE; + + // create dirPath+"\*" in szDir + StringCchLength (dirPath.c_str(), MAX_PATH, &dirPathLength); + + if (dirPathLength > (MAX_PATH - 3)) { + throw Exception ("Directory path is too long: " + dirPath); + } + + StringCchCopy(szDir, MAX_PATH, dirPath.c_str()); + StringCchCat(szDir, MAX_PATH, TEXT("\\*")); + + // Special work for first file + hFind = FindFirstFileA(szDir, &findFileData); + if (INVALID_HANDLE_VALUE == hFind) { + return; + } + + // process everything that isn't a directory + do { + if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + std::string fileName(findFileData.cFileName); + cb(fileName); + } + } while (FindNextFile(hFind, &findFileData) != 0); +} + }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/windows/IOHandle.cpp b/cpp/src/qpid/sys/windows/IOHandle.cpp index 250737cb99..19a1c44875 100755 --- a/cpp/src/qpid/sys/windows/IOHandle.cpp +++ b/cpp/src/qpid/sys/windows/IOHandle.cpp @@ -19,24 +19,11 @@ * */ -#include "qpid/sys/IOHandle.h" #include "qpid/sys/windows/IoHandlePrivate.h" #include <windows.h> namespace qpid { namespace sys { -SOCKET toFd(const IOHandlePrivate* h) -{ - return h->fd; -} - -IOHandle::IOHandle(IOHandlePrivate* h) : - impl(h) -{} - -IOHandle::~IOHandle() { - delete impl; -} }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/windows/IoHandlePrivate.h b/cpp/src/qpid/sys/windows/IoHandlePrivate.h index 5943db5cc7..4529ad93ec 100755 --- a/cpp/src/qpid/sys/windows/IoHandlePrivate.h +++ b/cpp/src/qpid/sys/windows/IoHandlePrivate.h @@ -38,15 +38,14 @@ namespace sys { // completer from an I/O thread. If the callback mechanism is used, there // can be a RequestCallback set - this carries the callback object through // from AsynchIO::requestCallback() through to the I/O completion processing. -class IOHandlePrivate { - friend QPID_COMMON_EXTERN SOCKET toSocketHandle(const Socket& s); - static IOHandlePrivate* getImpl(const IOHandle& h); - +class IOHandle { public: - IOHandlePrivate(SOCKET f = INVALID_SOCKET, - windows::AsynchIoResult::Completer cb = 0, - AsynchIO::RequestCallback reqCallback = 0) : - fd(f), event(cb), cbRequest(reqCallback) + IOHandle(SOCKET f = INVALID_SOCKET, + windows::AsynchIoResult::Completer cb = 0, + AsynchIO::RequestCallback reqCallback = 0) : + fd(f), + event(cb), + cbRequest(reqCallback) {} SOCKET fd; @@ -54,8 +53,6 @@ public: AsynchIO::RequestCallback cbRequest; }; -QPID_COMMON_EXTERN SOCKET toSocketHandle(const Socket& s); - }} #endif /* _sys_windows_IoHandlePrivate_h */ diff --git a/cpp/src/qpid/sys/windows/IocpPoller.cpp b/cpp/src/qpid/sys/windows/IocpPoller.cpp index c81cef87b0..ecb33c5517 100755 --- a/cpp/src/qpid/sys/windows/IocpPoller.cpp +++ b/cpp/src/qpid/sys/windows/IocpPoller.cpp @@ -22,7 +22,7 @@ #include "qpid/sys/Poller.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/Dispatcher.h" - +#include "qpid/sys/IOHandle.h" #include "qpid/sys/windows/AsynchIoResult.h" #include "qpid/sys/windows/IoHandlePrivate.h" #include "qpid/sys/windows/check.h" @@ -55,7 +55,7 @@ class PollerHandlePrivate { }; PollerHandle::PollerHandle(const IOHandle& h) : - impl(new PollerHandlePrivate(toSocketHandle(static_cast<const Socket&>(h)), h.impl->event, h.impl->cbRequest)) + impl(new PollerHandlePrivate(h.fd, h.event, h.cbRequest)) {} PollerHandle::~PollerHandle() { diff --git a/cpp/src/qpid/sys/windows/PollableCondition.cpp b/cpp/src/qpid/sys/windows/PollableCondition.cpp index bb637be0a6..3e2a5fb36c 100644 --- a/cpp/src/qpid/sys/windows/PollableCondition.cpp +++ b/cpp/src/qpid/sys/windows/PollableCondition.cpp @@ -52,14 +52,14 @@ private: PollableCondition& parent; boost::shared_ptr<sys::Poller> poller; LONG isSet; + LONG isDispatching; }; PollableConditionPrivate::PollableConditionPrivate(const sys::PollableCondition::Callback& cb, sys::PollableCondition& parent, const boost::shared_ptr<sys::Poller>& poller) - : IOHandle(new sys::IOHandlePrivate(INVALID_SOCKET, - boost::bind(&PollableConditionPrivate::dispatch, this, _1))), - cb(cb), parent(parent), poller(poller), isSet(0) + : IOHandle(INVALID_SOCKET, boost::bind(&PollableConditionPrivate::dispatch, this, _1)), + cb(cb), parent(parent), poller(poller), isSet(0), isDispatching(0) { } @@ -78,7 +78,12 @@ void PollableConditionPrivate::poke() void PollableConditionPrivate::dispatch(windows::AsynchIoResult *result) { delete result; // Poller::monitorHandle() allocates this + // If isDispatching is already set, just return. Else, enter. + if (::InterlockedCompareExchange(&isDispatching, 1, 0) == 1) + return; cb(parent); + LONG oops = ::InterlockedDecrement(&isDispatching); // Result must be 0 + assert(!oops); if (isSet) poke(); } diff --git a/cpp/src/qpid/sys/windows/QpidDllMain.h b/cpp/src/qpid/sys/windows/QpidDllMain.h new file mode 100644 index 0000000000..74eaf0256a --- /dev/null +++ b/cpp/src/qpid/sys/windows/QpidDllMain.h @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Include this file once in each DLL that relies on SystemInfo.h: + * threadSafeShutdown(). Note that Thread.cpp has a more elaborate + * DllMain, that also provides this functionality separately. + * + * Teardown is in the reverse order of the DLL dependencies used + * during the load phase. The calls to DllMain and the static + * destructors are from the same thread, so no locking is necessary + * and there is no downside to an invocation of DllMain by multiple + * Qpid DLLs. + */ + +#ifdef _DLL + +#include <qpid/ImportExport.h> +#include <windows.h> + +namespace qpid { +namespace sys { +namespace windows { + +QPID_IMPORT bool processExiting; +QPID_IMPORT bool libraryUnloading; + +}}} // namespace qpid::sys::SystemInfo + + +BOOL APIENTRY DllMain(HMODULE hm, DWORD reason, LPVOID reserved) { + switch (reason) { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + // Remember how the process is terminating this DLL. + if (reserved != NULL) { + qpid::sys::windows::processExiting = true; + // Danger: all threading suspect, including indirect use of malloc or locks. + // Think twice before adding more functionality here. + return TRUE; + } + else { + qpid::sys::windows::libraryUnloading = true; + } + break; + } + return TRUE; +} + + +#endif diff --git a/cpp/src/qpid/sys/windows/SslAsynchIO.cpp b/cpp/src/qpid/sys/windows/SslAsynchIO.cpp index d263f00ab3..e48c799b29 100644 --- a/cpp/src/qpid/sys/windows/SslAsynchIO.cpp +++ b/cpp/src/qpid/sys/windows/SslAsynchIO.cpp @@ -209,18 +209,6 @@ bool SslAsynchIO::writeQueueEmpty() { return aio->writeQueueEmpty(); } -/* - * Initiate a read operation. AsynchIO::readComplete() will be - * called when the read is complete and data is available. - */ -void SslAsynchIO::startReading() { - aio->startReading(); -} - -void SslAsynchIO::stopReading() { - aio->stopReading(); -} - // Queue the specified callback for invocation from an I/O thread. void SslAsynchIO::requestCallback(RequestCallback callback) { aio->requestCallback(callback); @@ -241,11 +229,15 @@ AsynchIO::BufferBase* SslAsynchIO::getQueuedBuffer() { return sslBuff; } -unsigned int SslAsynchIO::getSslKeySize() { +SecuritySettings SslAsynchIO::getSecuritySettings() { SecPkgContext_KeyInfo info; memset(&info, 0, sizeof(info)); ::QueryContextAttributes(&ctxtHandle, SECPKG_ATTR_KEY_INFO, &info); - return info.KeySize; + + SecuritySettings settings; + settings.ssf = info.KeySize; + settings.authid = std::string(); + return settings; } void SslAsynchIO::negotiationDone() { diff --git a/cpp/src/qpid/sys/windows/SslAsynchIO.h b/cpp/src/qpid/sys/windows/SslAsynchIO.h index e9d9e8d629..2f6842b135 100644 --- a/cpp/src/qpid/sys/windows/SslAsynchIO.h +++ b/cpp/src/qpid/sys/windows/SslAsynchIO.h @@ -77,12 +77,9 @@ public: virtual void notifyPendingWrite(); virtual void queueWriteClose(); virtual bool writeQueueEmpty(); - virtual void startReading(); - virtual void stopReading(); virtual void requestCallback(RequestCallback); virtual BufferBase* getQueuedBuffer(); - - QPID_COMMON_EXTERN unsigned int getSslKeySize(); + virtual SecuritySettings getSecuritySettings(void); protected: CredHandle credHandle; diff --git a/cpp/src/qpid/sys/windows/SystemInfo.cpp b/cpp/src/qpid/sys/windows/SystemInfo.cpp index cef78dcc60..fb58d53b81 100755 --- a/cpp/src/qpid/sys/windows/SystemInfo.cpp +++ b/cpp/src/qpid/sys/windows/SystemInfo.cpp @@ -25,7 +25,8 @@ #include "qpid/sys/SystemInfo.h" #include "qpid/sys/IntegerTypes.h" -#include "qpid/Exception.h"
+#include "qpid/Exception.h" +#include "qpid/log/Statement.h" #include <assert.h> #include <winsock2.h> @@ -66,39 +67,10 @@ bool SystemInfo::getLocalHostname (Address &address) { static const std::string LOCALHOST("127.0.0.1"); static const std::string TCP("tcp"); -void SystemInfo::getLocalIpAddresses (uint16_t port, - std::vector<Address> &addrList) { - enum { MAX_URL_INTERFACES = 100 }; - - SOCKET s = socket (PF_INET, SOCK_STREAM, 0); - if (s != INVALID_SOCKET) { - INTERFACE_INFO interfaces[MAX_URL_INTERFACES]; - DWORD filledBytes = 0; - WSAIoctl (s, - SIO_GET_INTERFACE_LIST, - 0, - 0, - interfaces, - sizeof (interfaces), - &filledBytes, - 0, - 0); - unsigned int interfaceCount = filledBytes / sizeof (INTERFACE_INFO); - for (unsigned int i = 0; i < interfaceCount; ++i) { - if (interfaces[i].iiFlags & IFF_UP) { - std::string addr(inet_ntoa(interfaces[i].iiAddress.AddressIn.sin_addr)); - if (addr != LOCALHOST) - addrList.push_back(Address(TCP, addr, port)); - } - } - closesocket (s); - } -} - -bool SystemInfo::isLocalHost(const std::string& candidateHost) { - // FIXME aconway 2012-05-03: not implemented. - assert(0); - throw Exception("Not implemented: isLocalHost"); +// Null function which always fails to find an network interface name +bool SystemInfo::getInterfaceAddresses(const std::string&, std::vector<std::string>&) +{ + return false; } void SystemInfo::getSystemId (std::string &osName, @@ -208,4 +180,29 @@ std::string SystemInfo::getProcessName() return name; } + +#ifdef _DLL +namespace windows { +// set from one or more Qpid DLLs: i.e. in DllMain with DLL_PROCESS_DETACH +QPID_EXPORT bool processExiting = false; +QPID_EXPORT bool libraryUnloading = false; +} +#endif + +bool SystemInfo::threadSafeShutdown() +{ +#ifdef _DLL + if (!windows::processExiting && !windows::libraryUnloading) { + // called before exit() or FreeLibrary(), or by a DLL without + // a participating DllMain. + QPID_LOG(warning, "invalid query for shutdown state"); + throw qpid::Exception(QPID_MSG("Unable to determine shutdown state.")); + } + return !windows::processExiting; +#else + // Not a DLL: shutdown can only be by exit() or return from main(). + return false; +#endif +} + }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/windows/Thread.cpp b/cpp/src/qpid/sys/windows/Thread.cpp index 23b0033be4..b342c9da1d 100755 --- a/cpp/src/qpid/sys/windows/Thread.cpp +++ b/cpp/src/qpid/sys/windows/Thread.cpp @@ -27,6 +27,7 @@ #include "qpid/sys/Thread.h" #include "qpid/sys/Runnable.h" #include "qpid/sys/windows/check.h" +#include "qpid/sys/SystemInfo.h" #include <process.h> #include <windows.h> @@ -274,8 +275,17 @@ Thread Thread::current() { #ifdef _DLL +namespace qpid { +namespace sys { +namespace windows { + +extern bool processExiting; +extern bool libraryUnloading; + +}}} // namespace qpid::sys::SystemInfo + // DllMain: called possibly many times in a process lifetime if dll -// loaded and freed repeatedly . Be mindful of Windows loader lock +// loaded and freed repeatedly. Be mindful of Windows loader lock // and other DllMain restrictions. BOOL APIENTRY DllMain(HMODULE hm, DWORD reason, LPVOID reserved) { @@ -290,10 +300,12 @@ BOOL APIENTRY DllMain(HMODULE hm, DWORD reason, LPVOID reserved) { if (reserved != NULL) { // process exit(): threads are stopped arbitrarily and // possibly in an inconsistent state. Not even threadLock - // can be trusted. All static destructors have been - // called at this point and any resources this unit knows - // about will be released as part of process tear down by - // the OS. Accordingly, do nothing. + // can be trusted. All static destructors for this unit + // are pending and face the same unsafe environment. + // Any resources this unit knows about will be released as + // part of process tear down by the OS. Accordingly, skip + // any clean up tasks. + qpid::sys::windows::processExiting = true; return TRUE; } else { @@ -301,6 +313,7 @@ BOOL APIENTRY DllMain(HMODULE hm, DWORD reason, LPVOID reserved) { // encouraged to clean up to avoid leaks. Mostly we just // want any straggler threads to finish and notify // threadsDone as the last thing they do. + qpid::sys::windows::libraryUnloading = true; while (1) { { ScopedCriticalSection l(threadLock); diff --git a/cpp/src/qpid/sys/windows/Socket.cpp b/cpp/src/qpid/sys/windows/WinSocket.cpp index a4374260cc..b2d2d79c63 100644 --- a/cpp/src/qpid/sys/windows/Socket.cpp +++ b/cpp/src/qpid/sys/windows/WinSocket.cpp @@ -19,18 +19,12 @@ * */ -#include "qpid/sys/Socket.h" +#include "qpid/sys/windows/WinSocket.h" #include "qpid/sys/SocketAddress.h" #include "qpid/sys/windows/check.h" #include "qpid/sys/windows/IoHandlePrivate.h" - -// Ensure we get all of winsock2.h -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0501 -#endif - -#include <winsock2.h> +#include "qpid/sys/SystemInfo.h" namespace qpid { namespace sys { @@ -67,7 +61,8 @@ public: } ~WinSockSetup() { - WSACleanup(); + if (SystemInfo::threadSafeShutdown()) + WSACleanup(); } public: @@ -106,22 +101,32 @@ uint16_t getLocalPort(int fd) } } // namespace -Socket::Socket() : - IOHandle(new IOHandlePrivate), +WinSocket::WinSocket() : + handle(new IOHandle), nonblocking(false), nodelay(false) {} -Socket::Socket(IOHandlePrivate* h) : - IOHandle(h), +Socket* createSocket() +{ + return new WinSocket; +} + +WinSocket::WinSocket(SOCKET fd) : + handle(new IOHandle(fd)), nonblocking(false), nodelay(false) {} -void Socket::createSocket(const SocketAddress& sa) const +WinSocket::operator const IOHandle&() const { - SOCKET& socket = impl->fd; - if (socket != INVALID_SOCKET) Socket::close(); + return *handle; +} + +void WinSocket::createSocket(const SocketAddress& sa) const +{ + SOCKET& socket = handle->fd; + if (socket != INVALID_SOCKET) WinSocket::close(); SOCKET s = ::socket (getAddrInfo(sa).ai_family, getAddrInfo(sa).ai_socktype, @@ -139,39 +144,19 @@ void Socket::createSocket(const SocketAddress& sa) const } } -Socket* Socket::createSameTypeSocket() const { - SOCKET& socket = impl->fd; - // Socket currently has no actual socket attached - if (socket == INVALID_SOCKET) - return new Socket; - - ::sockaddr_storage sa; - ::socklen_t salen = sizeof(sa); - QPID_WINSOCK_CHECK(::getsockname(socket, (::sockaddr*)&sa, &salen)); - SOCKET s = ::socket(sa.ss_family, SOCK_STREAM, 0); // Currently only work with SOCK_STREAM - if (s == INVALID_SOCKET) throw QPID_WINDOWS_ERROR(WSAGetLastError()); - return new Socket(new IOHandlePrivate(s)); -} - -void Socket::setNonblocking() const { +void WinSocket::setNonblocking() const { u_long nonblock = 1; - QPID_WINSOCK_CHECK(ioctlsocket(impl->fd, FIONBIO, &nonblock)); -} - -void Socket::connect(const std::string& host, const std::string& port) const -{ - SocketAddress sa(host, port); - connect(sa); + QPID_WINSOCK_CHECK(ioctlsocket(handle->fd, FIONBIO, &nonblock)); } void -Socket::connect(const SocketAddress& addr) const +WinSocket::connect(const SocketAddress& addr) const { peername = addr.asString(false); createSocket(addr); - const SOCKET& socket = impl->fd; + const SOCKET& socket = handle->fd; int err; WSASetLastError(0); if ((::connect(socket, getAddrInfo(addr).ai_addr, getAddrInfo(addr).ai_addrlen) != 0) && @@ -180,44 +165,43 @@ Socket::connect(const SocketAddress& addr) const } void -Socket::close() const +WinSocket::finishConnect(const SocketAddress&) const { - SOCKET& socket = impl->fd; +} + +void +WinSocket::close() const +{ + SOCKET& socket = handle->fd; if (socket == INVALID_SOCKET) return; QPID_WINSOCK_CHECK(closesocket(socket)); socket = INVALID_SOCKET; } -int Socket::write(const void *buf, size_t count) const +int WinSocket::write(const void *buf, size_t count) const { - const SOCKET& socket = impl->fd; + const SOCKET& socket = handle->fd; int sent = ::send(socket, (const char *)buf, count, 0); if (sent == SOCKET_ERROR) return -1; return sent; } -int Socket::read(void *buf, size_t count) const +int WinSocket::read(void *buf, size_t count) const { - const SOCKET& socket = impl->fd; + const SOCKET& socket = handle->fd; int received = ::recv(socket, (char *)buf, count, 0); if (received == SOCKET_ERROR) return -1; return received; } -int Socket::listen(const std::string& host, const std::string& port, int backlog) const -{ - SocketAddress sa(host, port); - return listen(sa, backlog); -} - -int Socket::listen(const SocketAddress& addr, int backlog) const +int WinSocket::listen(const SocketAddress& addr, int backlog) const { createSocket(addr); - const SOCKET& socket = impl->fd; + const SOCKET& socket = handle->fd; BOOL yes=1; QPID_WINSOCK_CHECK(setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes))); @@ -229,48 +213,48 @@ int Socket::listen(const SocketAddress& addr, int backlog) const return getLocalPort(socket); } -Socket* Socket::accept() const +Socket* WinSocket::accept() const { - SOCKET afd = ::accept(impl->fd, 0, 0); + SOCKET afd = ::accept(handle->fd, 0, 0); if (afd != INVALID_SOCKET) - return new Socket(new IOHandlePrivate(afd)); + return new WinSocket(afd); else if (WSAGetLastError() == EAGAIN) return 0; else throw QPID_WINDOWS_ERROR(WSAGetLastError()); } -std::string Socket::getPeerAddress() const +std::string WinSocket::getPeerAddress() const { if (peername.empty()) { - peername = getName(impl->fd, false); + peername = getName(handle->fd, false); } return peername; } -std::string Socket::getLocalAddress() const +std::string WinSocket::getLocalAddress() const { if (localname.empty()) { - localname = getName(impl->fd, true); + localname = getName(handle->fd, true); } return localname; } -int Socket::getError() const +int WinSocket::getError() const { int result; socklen_t rSize = sizeof (result); - QPID_WINSOCK_CHECK(::getsockopt(impl->fd, SOL_SOCKET, SO_ERROR, (char *)&result, &rSize)); + QPID_WINSOCK_CHECK(::getsockopt(handle->fd, SOL_SOCKET, SO_ERROR, (char *)&result, &rSize)); return result; } -void Socket::setTcpNoDelay() const +void WinSocket::setTcpNoDelay() const { - SOCKET& socket = impl->fd; + SOCKET& socket = handle->fd; nodelay = true; if (socket != INVALID_SOCKET) { int flag = 1; - int result = setsockopt(impl->fd, + int result = setsockopt(handle->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, @@ -279,14 +263,14 @@ void Socket::setTcpNoDelay() const } } -inline IOHandlePrivate* IOHandlePrivate::getImpl(const qpid::sys::IOHandle &h) +int WinSocket::getKeyLen() const { - return h.impl; + return 0; } -SOCKET toSocketHandle(const Socket& s) +std::string WinSocket::getClientAuthId() const { - return IOHandlePrivate::getImpl(s)->fd; + return std::string(); } }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/windows/WinSocket.h b/cpp/src/qpid/sys/windows/WinSocket.h new file mode 100644 index 0000000000..bee6a58e7a --- /dev/null +++ b/cpp/src/qpid/sys/windows/WinSocket.h @@ -0,0 +1,118 @@ +#ifndef QPID_SYS_WINDOWS_BSDSOCKET_H +#define QPID_SYS_WINDOWS_BSDSOCKET_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/sys/Socket.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/CommonImportExport.h" +#include <string> + +#include <boost/scoped_ptr.hpp> + +// Ensure we get all of winsock2.h +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif + +#include <winsock2.h> + +namespace qpid { +namespace sys { + +namespace windows { +Socket* createSameTypeSocket(const Socket&); +} + +class Duration; +class IOHandle; +class SocketAddress; + +class QPID_COMMON_CLASS_EXTERN WinSocket : public Socket +{ +public: + /** Create a socket wrapper for descriptor. */ + QPID_COMMON_EXTERN WinSocket(); + + QPID_COMMON_EXTERN operator const IOHandle&() const; + + /** Set socket non blocking */ + QPID_COMMON_EXTERN virtual void setNonblocking() const; + + QPID_COMMON_EXTERN virtual void setTcpNoDelay() const; + + QPID_COMMON_EXTERN virtual void connect(const SocketAddress&) const; + QPID_COMMON_EXTERN virtual void finishConnect(const SocketAddress&) const; + + QPID_COMMON_EXTERN virtual void close() const; + + /** Bind to a port and start listening. + *@return The bound port number + */ + QPID_COMMON_EXTERN virtual int listen(const SocketAddress&, int backlog = 10) const; + + /** + * Returns an address (host and port) for the remote end of the + * socket + */ + QPID_COMMON_EXTERN std::string getPeerAddress() const; + /** + * Returns an address (host and port) for the local end of the + * socket + */ + QPID_COMMON_EXTERN std::string getLocalAddress() const; + + /** + * Returns the error code stored in the socket. This may be used + * to determine the result of a non-blocking connect. + */ + QPID_COMMON_EXTERN int getError() const; + + /** Accept a connection from a socket that is already listening + * and has an incoming connection + */ + QPID_COMMON_EXTERN virtual Socket* accept() const; + + // TODO The following are raw operations, maybe they need better wrapping? + QPID_COMMON_EXTERN virtual int read(void *buf, size_t count) const; + QPID_COMMON_EXTERN virtual int write(const void *buf, size_t count) const; + + QPID_COMMON_EXTERN int getKeyLen() const; + QPID_COMMON_EXTERN std::string getClientAuthId() const; + +protected: + /** Create socket */ + void createSocket(const SocketAddress&) const; + + mutable boost::scoped_ptr<IOHandle> handle; + mutable std::string localname; + mutable std::string peername; + mutable bool nonblocking; + mutable bool nodelay; + + /** Construct socket with existing handle */ + friend Socket* qpid::sys::windows::createSameTypeSocket(const Socket&); + WinSocket(SOCKET fd); +}; + +}} +#endif /*!QPID_SYS_WINDOWS_BSDSOCKET_H*/ diff --git a/cpp/src/qpid/types/Variant.cpp b/cpp/src/qpid/types/Variant.cpp index d332fffa5e..8c9837e765 100644 --- a/cpp/src/qpid/types/Variant.cpp +++ b/cpp/src/qpid/types/Variant.cpp @@ -110,21 +110,28 @@ class VariantImpl } value; std::string encoding;//optional encoding for variable length data - template<class T> T convertFromString() const + template<class T> T convertFromString() const { const std::string& s = *value.string; + try { - T r = boost::lexical_cast<T>(s); - //lexical_cast won't fail if string is a negative number and T is unsigned - //So check that and allow special case of negative zero - //else its a non-zero negative number so throw exception at end of function - if (std::numeric_limits<T>::is_signed || s.find('-') != 0 || r == 0) { - return r; + // Extra shenanigans to work around negative zero + // conversion error in older GCC libs. + if ( s[0] != '-' ) { + return boost::lexical_cast<T>(s); + } else { + T r = boost::lexical_cast<T>(s.substr(1)); + if (std::numeric_limits<T>::is_signed) { + return -r; + } else { + if (r==0) return 0; + } } } catch(const boost::bad_lexical_cast&) { } throw InvalidConversion(QPID_MSG("Cannot convert " << s)); } + }; @@ -650,7 +657,7 @@ VariantImpl* VariantImpl::create(const Variant& v) } } -Variant::Variant() : impl(0) {} +Variant::Variant() : impl(new VariantImpl()) {} Variant::Variant(bool b) : impl(new VariantImpl(b)) {} Variant::Variant(uint8_t i) : impl(new VariantImpl(i)) {} Variant::Variant(uint16_t i) : impl(new VariantImpl(i)) {} @@ -893,6 +900,8 @@ bool operator==(const Variant& a, const Variant& b) return a.isEqualTo(b); } +bool operator!=(const Variant& a, const Variant& b) { return !(a == b); } + bool Variant::isEqualTo(const Variant& other) const { return impl && impl->isEqualTo(*other.impl); |