diff options
| author | Rafael H. Schloming <rhs@apache.org> | 2009-12-26 12:42:57 +0000 |
|---|---|---|
| committer | Rafael H. Schloming <rhs@apache.org> | 2009-12-26 12:42:57 +0000 |
| commit | 248f1fe188fe2307b9dcf2c87a83b653eaa1920c (patch) | |
| tree | d5d0959a70218946ff72e107a6c106e32479a398 /cpp/src/qpid | |
| parent | 3c83a0e3ec7cf4dc23e83a340b25f5fc1676f937 (diff) | |
| download | qpid-python-248f1fe188fe2307b9dcf2c87a83b653eaa1920c.tar.gz | |
synchronized with trunk except for ruby dir
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/qpid.rnr@893970 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp/src/qpid')
624 files changed, 47441 insertions, 14603 deletions
diff --git a/cpp/src/qpid/Address.cpp b/cpp/src/qpid/Address.cpp new file mode 100644 index 0000000000..c3f6829fd3 --- /dev/null +++ b/cpp/src/qpid/Address.cpp @@ -0,0 +1,58 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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/Address.h" + +#include <ostream> + +using namespace std; + +namespace qpid { + +TcpAddress::TcpAddress(const std::string& h, uint16_t p): host(h), port(p) {} + +struct AddressOstreamVisitor : public boost::static_visitor<ostream&> { + ostream& out; + AddressOstreamVisitor(ostream& o) : out(o) {} + template <class T> ostream& operator()(const T& data) { return out << data; } +}; + +ostream& operator<<(ostream& os, const Address& addr) { + AddressOstreamVisitor visitor(os); + return boost::apply_visitor(visitor, addr.value); +} + +bool operator==(const TcpAddress& x, const TcpAddress& y) { + return y.host==x.host && y.port == x.port; +} + +ostream& operator<<(ostream& os, const TcpAddress& a) { + return os << "tcp:" << a.host << ":" << a.port; +} + +ExampleAddress::ExampleAddress(const char c) : data(c) {} + +bool operator==(const ExampleAddress& x, const ExampleAddress& y) { + return x.data == y.data; +} + +ostream& operator<<(ostream& os, const ExampleAddress& ex) { + return os << "example:" << ex.data; +} + +} // namespace qpid diff --git a/cpp/src/qpid/DataDir.cpp b/cpp/src/qpid/DataDir.cpp index 18b52b9b8f..ad732052ab 100644 --- a/cpp/src/qpid/DataDir.cpp +++ b/cpp/src/qpid/DataDir.cpp @@ -18,15 +18,11 @@ * */ -#include "Exception.h" -#include "DataDir.h" +#include "qpid/Exception.h" +#include "qpid/DataDir.h" #include "qpid/log/Statement.h" -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/file.h> -#include <fcntl.h> -#include <cerrno> -#include <unistd.h> +#include "qpid/sys/FileSysDir.h" +#include "qpid/sys/LockFile.h" namespace qpid { @@ -40,16 +36,9 @@ DataDir::DataDir (std::string path) : return; } - const char *cpath = dirPath.c_str (); - struct stat s; - if (::stat(cpath, &s)) { - if (errno == ENOENT) { - if (::mkdir(cpath, 0755)) - throw Exception ("Can't create data directory: " + path); - } - else - throw Exception ("Data directory not found: " + path); - } + sys::FileSysDir dir(dirPath); + if (!dir.exists()) + dir.mkdir(); std::string lockFileName(path); lockFileName += "/lock"; lockFile = std::auto_ptr<sys::LockFile>(new sys::LockFile(lockFileName, true)); diff --git a/cpp/src/qpid/DataDir.h b/cpp/src/qpid/DataDir.h index 7de5ebf62d..828299f3ba 100644 --- a/cpp/src/qpid/DataDir.h +++ b/cpp/src/qpid/DataDir.h @@ -23,10 +23,14 @@ #include <string> #include <memory> -#include "qpid/sys/LockFile.h" +#include "qpid/CommonImportExport.h" namespace qpid { + namespace sys { + class LockFile; + } + /** * DataDir class. */ @@ -38,11 +42,11 @@ class DataDir public: - DataDir (std::string path); - ~DataDir (); + QPID_COMMON_EXTERN DataDir (std::string path); + QPID_COMMON_EXTERN ~DataDir (); - bool isEnabled () { return enabled; } - std::string getPath () { return dirPath; } + bool isEnabled() { return enabled; } + const std::string& getPath() { return dirPath; } }; } // namespace qpid diff --git a/cpp/src/qpid/Exception.cpp b/cpp/src/qpid/Exception.cpp index 9e884efec0..16a3a13d17 100644 --- a/cpp/src/qpid/Exception.cpp +++ b/cpp/src/qpid/Exception.cpp @@ -20,7 +20,7 @@ */ #include "qpid/log/Statement.h" -#include "Exception.h" +#include "qpid/Exception.h" #include <typeinfo> #include <assert.h> #include <string.h> @@ -28,7 +28,7 @@ namespace qpid { Exception::Exception(const std::string& msg) throw() : message(msg) { - QPID_LOG(debug, "Exception constructed: " << message); + QPID_LOG_IF(debug, !msg.empty(), "Exception constructed: " << message); } Exception::~Exception() throw() {} diff --git a/cpp/src/qpid/Exception.h b/cpp/src/qpid/Exception.h deleted file mode 100644 index 9cf564104d..0000000000 --- a/cpp/src/qpid/Exception.h +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef _Exception_ -#define _Exception_ - -/* - * - * 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/framing/amqp_types.h" -#include "qpid/framing/constants.h" -#include "qpid/sys/StrError.h" -#include "qpid/Msg.h" - -#include <memory> -#include <string> -#include <errno.h> - -namespace qpid -{ - -/** - * Base class for Qpid runtime exceptions. - */ -class Exception : public std::exception -{ - public: - explicit Exception(const std::string& message=std::string()) throw(); - virtual ~Exception() throw(); - virtual const char* what() const throw(); // prefix: message - virtual std::string getMessage() const; // Unprefixed message - virtual std::string getPrefix() const; // Prefix - - private: - std::string message; - mutable std::string whatStr; -}; - -/** Exception that includes an errno message. */ -struct ErrnoException : public Exception { - ErrnoException(const std::string& msg, int err) : Exception(msg+": "+qpid::sys::strError(err)) {} - ErrnoException(const std::string& msg) : Exception(msg+": "+qpid::sys::strError(errno)) {} -}; - -struct SessionException : public Exception { - const framing::ReplyCode code; - SessionException(framing::ReplyCode code_, const std::string& message) - : Exception(message), code(code_) {} -}; - -struct ChannelException : public Exception { - const framing::ReplyCode code; - ChannelException(framing::ReplyCode _code, const std::string& message) - : Exception(message), code(_code) {} -}; - -struct ConnectionException : public Exception { - const framing::ReplyCode code; - ConnectionException(framing::ReplyCode _code, const std::string& message) - : Exception(message), code(_code) {} -}; - -struct ClosedException : public Exception { - ClosedException(const std::string& msg=std::string()); - std::string getPrefix() const; -}; - -} // namespace qpid - -#endif /*!_Exception_*/ diff --git a/cpp/src/qpid/InlineAllocator.h b/cpp/src/qpid/InlineAllocator.h deleted file mode 100644 index bf2f570599..0000000000 --- a/cpp/src/qpid/InlineAllocator.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef QPID_INLINEALLOCATOR_H -#define QPID_INLINEALLOCATOR_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 <memory> -#include <assert.h> -#include <boost/type_traits/type_with_alignment.hpp> -#include <boost/type_traits/alignment_of.hpp> - -namespace qpid { - -/** - * An allocator that has inline storage for up to Max objects - * of type BaseAllocator::value_type. - */ -template <class BaseAllocator, size_t Max> -class InlineAllocator : public BaseAllocator { - public: - typedef typename BaseAllocator::pointer pointer; - typedef typename BaseAllocator::size_type size_type; - typedef typename BaseAllocator::value_type value_type; - - InlineAllocator() : allocated(false) {} - InlineAllocator(const InlineAllocator& x) : BaseAllocator(x), allocated(false) {} - - pointer allocate(size_type n) { - if (n <= Max && !allocated) { - allocated=true; - return reinterpret_cast<value_type*>(address()); - } - else - return BaseAllocator::allocate(n, 0); - } - - void deallocate(pointer p, size_type n) { - if (p == address()) { - assert(allocated); - allocated=false; - } - else - BaseAllocator::deallocate(p, n); - } - - template<typename T1> - struct rebind { - typedef typename BaseAllocator::template rebind<T1>::other BaseOther; - typedef InlineAllocator<BaseOther, Max> other; - }; - - private: - // POD object with alignment and size to hold Max value_types. - static const size_t ALIGNMENT=boost::alignment_of<value_type>::value; - typedef typename boost::type_with_alignment<ALIGNMENT>::type Aligner; - union Store { - Aligner aligner_; - char sizer_[sizeof(value_type)*Max]; - } store; - value_type* address() { return reinterpret_cast<value_type*>(&store); } - bool allocated; -}; - -} // namespace qpid - -#endif /*!QPID_INLINEALLOCATOR_H*/ diff --git a/cpp/src/qpid/InlineVector.h b/cpp/src/qpid/InlineVector.h deleted file mode 100644 index 551b9912e7..0000000000 --- a/cpp/src/qpid/InlineVector.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef QPID_INLINEVECTOR_H -#define QPID_INLINEVECTOR_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 "InlineAllocator.h" -#include <vector> - -namespace qpid { - -/** - * A vector that stores up to Max elements in inline storage, - * otherwise uses normal vector allocation. - * - * NOTE: depends on some non-standard but highly probably assumptions - * about how std::vector uses its allocator, they are true for g++. - * - default constructor does not allocate. - * - reserve(N) does not allocate more than N elements. - * - vector never re-allocates when size() < capacity() - */ -template <class T, size_t Max, class Alloc=std::allocator<T> > -class InlineVector : public std::vector<T, InlineAllocator<Alloc, Max> > -{ - typedef std::vector<T, InlineAllocator<Alloc, Max> > Base; - public: - typedef typename Base::allocator_type allocator_type; - typedef typename Base::value_type value_type; - typedef typename Base::size_type size_type; - - explicit InlineVector(const allocator_type& a=allocator_type()) : Base(a) { - this->reserve(Max); - } - - explicit InlineVector(size_type n, const value_type& x = value_type(), - const allocator_type& a=allocator_type()) : Base(a) - { - this->reserve(std::max(n, Max)); - this->insert(this->end(), n, x); - } - - InlineVector(const InlineVector& x) : Base() { - this->reserve(std::max(x.size(), Max)); - *this = x; - } -}; - -} // namespace qpid - -#endif /*!QPID_INLINEVECTOR_H*/ diff --git a/cpp/src/qpid/Modules.cpp b/cpp/src/qpid/Modules.cpp new file mode 100644 index 0000000000..70549bd553 --- /dev/null +++ b/cpp/src/qpid/Modules.cpp @@ -0,0 +1,95 @@ +/* + * + * 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 "config.h" +#include "qpid/Modules.h" +#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; + +namespace { + +// CMake sets QPID_MODULE_SUFFIX; Autoconf doesn't, so assume Linux .so +#if defined (QPID_MODULE_SUFFIX) + std::string suffix(QPID_MODULE_SUFFIX); +#else + std::string suffix(".so"); +#endif + +bool isShlibName(const std::string& name) { + return name.find (suffix) == name.length() - suffix.length(); +} + +} + +namespace qpid { + +ModuleOptions::ModuleOptions(const std::string& defaultModuleDir) + : qpid::Options("Module options"), loadDir(defaultModuleDir), noLoad(false) +{ + addOptions() + ("module-dir", optValue(loadDir, "DIR"), "Load all shareable modules in this directory") + ("load-module", optValue(load, "FILE"), "Specifies additional module(s) to be loaded") + ("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; + try { + sys::Shlib shlib(libname); + QPID_LOG (info, "Loaded Module: " << 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)) + { + if (isDefault) + return; + throw Exception ("Directory not found: " + dirname); + } + if (!fs::is_directory(dirPath)) + { + throw Exception ("Invalid value for module-dir: " + dirname + " is not a directory"); + } + + 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); + } +} + +} // namespace qpid diff --git a/cpp/src/qpid/Modules.h b/cpp/src/qpid/Modules.h new file mode 100644 index 0000000000..159dd156c1 --- /dev/null +++ b/cpp/src/qpid/Modules.h @@ -0,0 +1,44 @@ +#ifndef QPID_MODULES_H +#define QPID_MODULES_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/Options.h" +#include <string> +#include <vector> +#include "qpid/CommonImportExport.h" + +namespace qpid { + +struct ModuleOptions : public qpid::Options { + std::string loadDir; + std::vector<std::string> load; + bool noLoad; + QPID_COMMON_EXTERN ModuleOptions(const std::string& defaultModuleDir); +}; + +QPID_COMMON_EXTERN void tryShlib(const char* libname, bool noThrow); +QPID_COMMON_EXTERN void loadModuleDir (std::string dirname, bool isDefault); + +} // namespace qpid + +#endif /*!QPID_MODULES_H*/ diff --git a/cpp/src/qpid/Msg.h b/cpp/src/qpid/Msg.h deleted file mode 100644 index 7214db611f..0000000000 --- a/cpp/src/qpid/Msg.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef QPID_MSG_H -#define QPID_MSG_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 <sstream> -#include <iostream> - -namespace qpid { - -/** A simple wrapper for std::ostringstream that allows - * in place construction of a message and automatic conversion - * to string. - * E.g. - *@code - * void foo(const std::string&); - * foo(Msg() << "hello " << 32); - *@endcode - * Will construct the string "hello 32" and pass it to foo() - */ -struct Msg { - std::ostringstream os; - Msg() {} - Msg(const Msg& m) : os(m.str()) {} - std::string str() const { return os.str(); } - operator std::string() const { return str(); } -}; - -template <class T> const Msg& operator<<(const Msg& m, const T& t) { - const_cast<std::ostringstream&>(m.os)<<t; return m; -} - -inline std::ostream& operator<<(std::ostream& o, const Msg& m) { - return o<<m.str(); -} - -/** Construct a message using operator << and append (file:line) */ -#define QPID_MSG(message) ::qpid::Msg() << message << " (" << __FILE__ << ":" << __LINE__ << ")" - -} // namespace qpid - -#endif /*!QPID_MSG_H*/ diff --git a/cpp/src/qpid/Options.cpp b/cpp/src/qpid/Options.cpp index e521b1220a..499fb71bc3 100644 --- a/cpp/src/qpid/Options.cpp +++ b/cpp/src/qpid/Options.cpp @@ -16,7 +16,7 @@ * */ -#include "Options.h" +#include "qpid/Options.h" #include "qpid/Exception.h" #include <boost/bind.hpp> @@ -55,7 +55,8 @@ struct EnvOptMapper { } static bool matchStr(const string& env, boost::shared_ptr<po::option_description> desc) { - return std::equal(env.begin(), env.end(), desc->long_name().begin(), &matchChar); + 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) { diff --git a/cpp/src/qpid/Options.h b/cpp/src/qpid/Options.h deleted file mode 100644 index cb86d27241..0000000000 --- a/cpp/src/qpid/Options.h +++ /dev/null @@ -1,257 +0,0 @@ -#ifndef QPID_COMMONOPTIONS_H -#define QPID_COMMONOPTIONS_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/Exception.h" -#include <boost/program_options.hpp> -#include <boost/format.hpp> -#include <sstream> -#include <iterator> -#include <algorithm> -#include <string> - - -namespace qpid { -namespace po=boost::program_options; - - - -///@internal -std::string prettyArg(const std::string&, const std::string&); - -/** @internal Normally only constructed by optValue() */ -template <class T> -class OptionValue : public po::typed_value<T> { - public: - OptionValue(T& value, const std::string& arg) - : po::typed_value<T>(&value), argName(arg) {} - std::string name() const { return argName; } - - private: - std::string argName; -}; - - -/** Create an option value. - * name, value appear after the option name in help like this: - * <name> (=<value>) - * T must support operator <<. - *@see Options for example of use. - */ -template<class T> -po::value_semantic* optValue(T& value, const char* name) { - std::string valstr(boost::lexical_cast<std::string>(value)); - return new OptionValue<T>(value, prettyArg(name, valstr)); -} - -/** Create a vector value. Multiple occurences of the option are - * accumulated into the vector - */ -template <class T> -po::value_semantic* optValue(std::vector<T>& value, const char* name) { - using namespace std; - ostringstream os; - copy(value.begin(), value.end(), ostream_iterator<T>(os, " ")); - string val=os.str(); - if (!val.empty()) - val.erase(val.end()-1); // Remove trailing " " - return (new OptionValue<vector<T> >(value, prettyArg(name, val))); -} - -/** Create a boolean switch value. Presence of the option sets the value. */ -inline po::value_semantic* optValue(bool& value) { return po::bool_switch(&value); } - -/** - * Base class for options. - * Example of use: - @code - struct MySubOptions : public Options { - int x; - string y; - MySubOptions() : Options("Sub options") { - addOptions() - ("x", optValue(x,"XUNIT"), "Option X") - ("y", optValue(y, "YUNIT"), "Option Y"); - } - }; - - struct MyOptions : public Options { - bool z; - vector<string> foo; - MySubOptions subOptions; - MyOptions() : Options("My Options") { - addOptions() - ("z", boolSwitch(z), "Option Z") - ("foo", optValue(foo), "Multiple option foo"); - add(subOptions); - } - - main(int argc, char** argv) { - Options opts; - opts.parse(argc, char** argv); - // Use values - dosomething(opts.subOptions.x); - if (error) - cout << opts << end; // Help message. - } - - @endcode - */ - - - - -/* - * --------------------------------------------- - * Explanation for Boost 103200 conditional code - * --------------------------------------------- - * - * This boost version has an implementation of the program_options library - * that has no provision for allowing unregistered options to pass by. - * - * But that means that, if you have a program that loads optional modules - * after start-up, and those modules each have their own set of options, - * then if you parse the command line too soon, you will get spurious - * reports of unrecognized options -- and the program will exit! - * - * And we must process the command-line before module-loading, because we - * need to look at the "bootstrap" options. - * - * This conditional code: - * - * 1. implements it's own functor class, derived from the Boost - * "options_description_easy_init" class. This functor is used - * to process added options and do the functor chaining, so that - * I can snoop on the arguments before doing an explicit call - * to its parent. - * - * 2. It implements two static vectors, one to hold long names, and - * one for short names, so that options declared by modules are - * not forgotten when their options_description goes out of scope. - * - * I will be thrilled to personally delete this code if we ever decide - * that qpid doesn't really need to support this antique version of Boost. - * - */ - -#if ( BOOST_VERSION == 103200 ) -struct Options; - - -struct -options_description_less_easy_init - : public po::options_description_easy_init -{ - options_description_less_easy_init ( Options * my_owner, - po::options_description * my_parents_owner - ) - : po::options_description_easy_init(my_parents_owner) - { - owner = my_owner; - } - - - options_description_less_easy_init& - operator()(char const * name, - char const * description); - - - options_description_less_easy_init& - operator()(char const * name, - const po::value_semantic* s); - - - options_description_less_easy_init& - operator()(const char* name, - const po::value_semantic* s, - const char* description); - - - Options * owner; -}; -#endif - - - - - - -struct Options : public po::options_description { - - struct Exception : public qpid::Exception { - Exception(const std::string& msg) : qpid::Exception(msg) {} - }; - - Options(const std::string& name=std::string()); - - /** - * Parses options from argc/argv, environment variables and config file. - * Note the filename argument can reference an options variable that - * is updated by argc/argv or environment variable parsing. - */ - void parse(int argc, char const* const* argv, - const std::string& configfile=std::string(), - bool allowUnknown = false); - - - #if ( BOOST_VERSION == 103200 ) - options_description_less_easy_init m_less_easy; - - options_description_less_easy_init addOptions() { - return m_less_easy; - } - - bool - is_registered_option ( std::string s ); - - void - register_names ( std::string s ); - - static std::vector<std::string> long_names; - static std::vector<std::string> short_names; - #else - boost::program_options::options_description_easy_init addOptions() { - return add_options(); - } - #endif -}; - - - -/** - * Standard options for configuration - */ -struct CommonOptions : public Options { - CommonOptions(const std::string& name=std::string(), - const std::string& configfile=std::string()); - bool help; - bool version; - std::string config; -}; - - - - -} // namespace qpid - -#endif /*!QPID_COMMONOPTIONS_H*/ diff --git a/cpp/src/qpid/Plugin.cpp b/cpp/src/qpid/Plugin.cpp index e4b76db28a..196b5c2333 100644 --- a/cpp/src/qpid/Plugin.cpp +++ b/cpp/src/qpid/Plugin.cpp @@ -18,7 +18,7 @@ * */ -#include "Plugin.h" +#include "qpid/Plugin.h" #include "qpid/Options.h" #include <boost/bind.hpp> #include <algorithm> @@ -42,7 +42,7 @@ void invoke(boost::function<void()> f) { f(); } Plugin::Target::~Target() { finalize(); } void Plugin::Target::finalize() { - for_each(finalizers.begin(), finalizers.end(), invoke); + std::for_each(finalizers.begin(), finalizers.end(), invoke); finalizers.clear(); } @@ -50,9 +50,16 @@ void Plugin::Target::addFinalizer(const boost::function<void()>& f) { finalizers.push_back(f); } +namespace { +bool initBefore(const Plugin* a, const Plugin* b) { + return a->initOrder() < b->initOrder(); +} +} + Plugin::Plugin() { // Register myself. thePlugins().push_back(this); + std::sort(thePlugins().begin(), thePlugins().end(), &initBefore); } Plugin::~Plugin() {} @@ -74,7 +81,14 @@ void Plugin::addOptions(Options& opts) { } } -void Plugin::earlyInitAll(Target& t) { each_plugin(boost::bind(&Plugin::earlyInitialize, _1, boost::ref(t))); } -void Plugin::initializeAll(Target& t) { each_plugin(boost::bind(&Plugin::initialize, _1, boost::ref(t))); } +int Plugin::initOrder() const { return DEFAULT_INIT_ORDER; } + +void Plugin::earlyInitAll(Target& t) { + each_plugin(boost::bind(&Plugin::earlyInitialize, _1, boost::ref(t))); +} + +void Plugin::initializeAll(Target& t) { + each_plugin(boost::bind(&Plugin::initialize, _1, boost::ref(t))); +} } // namespace qpid diff --git a/cpp/src/qpid/Plugin.h b/cpp/src/qpid/Plugin.h index 3c7c8031bb..4e057872b9 100644 --- a/cpp/src/qpid/Plugin.h +++ b/cpp/src/qpid/Plugin.h @@ -24,11 +24,12 @@ #include <boost/noncopyable.hpp> #include <boost/function.hpp> #include <vector> +#include "qpid/CommonImportExport.h" /**@file Generic plug-in framework. */ namespace qpid { -class Options; +struct Options; /** * Plug-in base class. @@ -36,6 +37,8 @@ class Options; class Plugin : private boost::noncopyable { public: typedef std::vector<Plugin*> Plugins; + /** Default value returned by initOrder() */ + static const int DEFAULT_INIT_ORDER=1000; /** * Base interface for targets that can receive plug-ins. @@ -46,13 +49,13 @@ class Plugin : private boost::noncopyable { { public: /** Calls finalize() if not already called. */ - virtual ~Target(); + QPID_COMMON_EXTERN virtual ~Target(); /** Run all the finalizers */ - void finalize(); + QPID_COMMON_EXTERN void finalize(); /** Add a function to run when finalize() is called */ - void addFinalizer(const boost::function<void()>&); + QPID_COMMON_EXTERN void addFinalizer(const boost::function<void()>&); private: std::vector<boost::function<void()> > finalizers; @@ -65,9 +68,9 @@ class Plugin : private boost::noncopyable { * member variable in a library so it is registered during * initialization when the library is loaded. */ - Plugin(); + QPID_COMMON_EXTERN Plugin(); - virtual ~Plugin(); + QPID_COMMON_EXTERN virtual ~Plugin(); /** * Configuration options for the plugin. @@ -76,7 +79,7 @@ class Plugin : private boost::noncopyable { * @return An options group or 0 for no options. Default returns 0. * Plugin retains ownership of return value. */ - virtual Options* getOptions(); + QPID_COMMON_EXTERN virtual Options* getOptions(); /** * Initialize Plugin functionality on a Target, called before @@ -98,19 +101,27 @@ class Plugin : private boost::noncopyable { */ virtual void initialize(Target&) = 0; + /** + * Initialization order. If a plugin does not override this, it + * returns DEFAULT_INIT_ORDER. Plugins that need to be initialized + * earlier/later than normal can override initOrder to return + * a lower/higher value than DEFAULT_INIT_ORDER. + */ + QPID_COMMON_EXTERN virtual int initOrder() const; + /** List of registered Plugin objects. * Caller must not delete plugin pointers. */ - static const Plugins& getPlugins(); + QPID_COMMON_EXTERN static const Plugins& getPlugins(); /** Call earlyInitialize() on all registered plugins */ - static void earlyInitAll(Target&); + QPID_COMMON_EXTERN static void earlyInitAll(Target&); /** Call initialize() on all registered plugins */ - static void initializeAll(Target&); + QPID_COMMON_EXTERN static void initializeAll(Target&); /** For each registered plugin, add plugin.getOptions() to opts. */ - static void addOptions(Options& opts); + QPID_COMMON_EXTERN static void addOptions(Options& opts); }; } // namespace qpid diff --git a/cpp/src/qpid/RangeSet.h b/cpp/src/qpid/RangeSet.h deleted file mode 100644 index 2a88426f17..0000000000 --- a/cpp/src/qpid/RangeSet.h +++ /dev/null @@ -1,324 +0,0 @@ -#ifndef QPID_RANGESET_H -#define QPID_RANGESET_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/InlineVector.h" -#include <boost/iterator/iterator_facade.hpp> -#include <boost/operators.hpp> -#include <boost/bind.hpp> -#include <algorithm> - -namespace qpid { - -/** A range of values, used in RangeSet. - * Range(begin, end) includes begin but excludes end. - * Range::makeClosed(first,last) includes both first and last. - */ -template <class T> -class Range { - public: - static Range makeClosed(const T& first, T last) { return Range(first, ++last); } - - Range() : begin_(), end_() {} - explicit Range(const T& t) : begin_(t), end_(t) { ++end_; } - Range(const T& b, const T& e) : begin_(b), end_(e) { assert(b <= e); } - - T begin() const { return begin_; } - /** End of _open_ range, i.e. !contains(end()) */ - T end() const { return end_; } - - T first() const { assert(!empty()); return begin_; } - /** Last in closed range, i.e. contains(end()) */ - T last() const { assert(!empty()); T ret=end_; return --ret; } - - void begin(const T& t) { begin_ = t; } - void end(const T& t) { end_ = t; } - - bool empty() const { return begin_ == end_; } - - bool contains(const T& x) const { return begin_ <= x && x < end_; } - bool contains(const Range& r) const { return begin_ <= r.begin_ && r.end_ <= end_; } - bool strictContains(const Range& r) const { return begin_ < r.begin_ && r.end_ < end_; } - - bool operator==(const Range& x) { return begin_ == x.begin_ && end_== x.end_; } - - bool operator<(const T& t) const { return end_ < t; } - bool operator<(const Range<T>& r) const { return end_ < r.begin_; } - - /** touching ranges can be merged into a single range. */ - bool touching(const Range& r) const { - return std::max(begin_, r.begin_) <= std::min(end_, r.end_); - } - - /** @pre touching */ - void merge(const Range& r) { - assert(touching(r)); - begin_ = std::min(begin_, r.begin_); - end_ = std::max(end_, r.end_); - } - - operator bool() const { return !empty(); } - - template <class S> void serialize(S& s) { s(begin_)(end_); } - - private: - T begin_, end_; -}; - - -/** - * A set implemented as a list of [begin, end) ranges. - * T must be LessThanComparable and Incrementable. - * RangeSet only provides const iterators. - */ -template <class T> -class RangeSet - : boost::additive1<RangeSet<T>, - boost::additive2<RangeSet<T>, Range<T>, - boost::additive2<RangeSet<T>, T> > > -{ - typedef InlineVector<Range<T>, 3> Ranges; // TODO aconway 2008-04-21: what's the optimial inlined value? - - public: - - class iterator : public boost::iterator_facade< - iterator, - const T, - boost::forward_traversal_tag> - { - public: - iterator() : ranges(), iter(), value() {} - - private: - typedef typename Ranges::const_iterator RangesIter; - iterator(const Ranges& r, const RangesIter& i, const T& t) - : ranges(&r), iter(i), value(t) {} - - void increment(); - bool equal(const iterator& i) const; - const T& dereference() const { return value; } - - const Ranges* ranges; - RangesIter iter; - T value; - - friend class RangeSet<T>; - friend class boost::iterator_core_access; - }; - - typedef iterator const_iterator; - - RangeSet() {} - explicit RangeSet(const Range<T>& r) { *this += r; } - RangeSet(const T& a, const T& b) { *this += Range<T>(a,b); } - - bool contiguous() const { return ranges.size() <= 1; } - - bool contains(const T& t) const; - bool contains(const Range<T>&) const; - - /**@pre contiguous() */ - Range<T> toRange() const; - - bool operator==(const RangeSet<T>&) const; - - void addRange (const Range<T>&); - void addSet (const RangeSet<T>&); - - RangeSet<T>& operator+=(const T& t) { return *this += Range<T>(t); } - RangeSet<T>& operator+=(const Range<T>& r) { addRange(r); return *this; } - RangeSet<T>& operator+=(const RangeSet<T>& s) { addSet(s); return *this; } - - void removeRange (const Range<T>&); - void removeSet (const RangeSet<T>&); - - RangeSet<T>& operator-=(const T& t) { return *this -= Range<T>(t); } - RangeSet<T>& operator-=(const Range<T>& r) { removeRange(r); return *this; } - RangeSet<T>& operator-=(const RangeSet<T>& s) { removeSet(s); return *this; } - - T front() const { return ranges.front().begin(); } - T back() const { return ranges.back().end(); } - - // Iterate over elements in the set. - iterator begin() const; - iterator end() const; - - // Iterate over ranges in the set. - typedef typename Ranges::const_iterator RangeIterator; - RangeIterator rangesBegin() const { return ranges.begin(); } - RangeIterator rangesEnd() const { return ranges.end(); } - size_t rangesSize() const { return ranges.size(); } - - // The difference between the start and end of this range set - uint32_t span() const; - - bool empty() const { return ranges.empty(); } - void clear() { ranges.clear(); } - - /** Return the largest contiguous range containing x. - * Returns the empty range [x,x) if x is not in the set. - */ - Range<T> rangeContaining(const T&) const; - - template <class S> void serialize(S& s) { s.split(*this); s(ranges.begin(), ranges.end()); } - template <class S> void encode(S& s) const { s(uint16_t(ranges.size()*sizeof(Range<T>))); } - template <class S> void decode(S& s) { uint16_t sz; s(sz); ranges.resize(sz/sizeof(Range<T>)); } - - private: - Ranges ranges; - - template <class U> friend std::ostream& operator<<(std::ostream& o, const RangeSet<U>& r); - - friend class iterator; -}; - -template <class T> -std::ostream& operator<<(std::ostream& o, const Range<T>& r) { - return o << "[" << r.begin() << "," << r.end() << ")"; -} - -template <class T> -std::ostream& operator<<(std::ostream& o, const RangeSet<T>& rs) { - std::ostream_iterator<Range<T> > i(o, " "); - o << "{ "; - std::copy(rs.ranges.begin(), rs.ranges.end(), i); - return o << "}"; -} - -template <class T> -bool RangeSet<T>::contains(const T& t) const { - typename Ranges::const_iterator i = - std::lower_bound(ranges.begin(), ranges.end(), Range<T>(t)); - return i != ranges.end() && i->contains(t); -} - -template <class T> -bool RangeSet<T>::contains(const Range<T>& r) const { - typename Ranges::const_iterator i = - std::lower_bound(ranges.begin(), ranges.end(), r); - return i != ranges.end() && i->contains(r); -} - -template <class T> void RangeSet<T>::addRange(const Range<T>& r) { - if (r.empty()) return; - typename Ranges::iterator i = - std::lower_bound(ranges.begin(), ranges.end(), r); - if (i == ranges.end() || !i->touching(r)) - ranges.insert(i, r); - else { - i->merge(r); - typename Ranges::iterator j = i; - if (++j != ranges.end() && i->touching(*j)) { - i->merge(*j); - ranges.erase(j); - } - } -} - - -template <class T> void RangeSet<T>::addSet(const RangeSet<T>& s) { - typedef RangeSet<T>& (RangeSet<T>::*RangeSetRangeOp)(const Range<T>&); - std::for_each(s.ranges.begin(), s.ranges.end(), - boost::bind((RangeSetRangeOp)&RangeSet<T>::operator+=, this, _1)); -} - -template <class T> void RangeSet<T>::removeRange(const Range<T>& r) { - if (r.empty()) return; - typename Ranges::iterator i,j; - i = std::lower_bound(ranges.begin(), ranges.end(), r); - if (i == ranges.end() || i->begin() >= r.end()) - return; // Outside of set - if (*i == r) // Erase i - ranges.erase(i); - else if (i->strictContains(r)) { // Split i - Range<T> i1(i->begin(), r.begin()); - Range<T> i2(r.end(), i->end()); - *i = i2; - ranges.insert(i, i1); - } else { - if (i->begin() < r.begin()) { // Truncate i - i->end(r.begin()); - ++i; - } - for (j = i; j != ranges.end() && r.contains(*j); ++j) - ; // Ranges to erase. - if (j != ranges.end() && j->end() > r.end()) - j->begin(r.end()); // Truncate j - ranges.erase(i,j); - } -} - -template <class T> void RangeSet<T>::removeSet(const RangeSet<T>& r) { - std::for_each( - r.ranges.begin(), r.ranges.end(), - boost::bind(&RangeSet<T>::removeRange, this, _1)); -} - -template <class T> Range<T> RangeSet<T>::toRange() const { - assert(contiguous()); - return empty() ? Range<T>() : ranges.front(); -} - -template <class T> void RangeSet<T>::iterator::increment() { - assert(ranges && iter != ranges->end()); - if (!iter->contains(++value)) { - ++iter; - if (iter == ranges->end()) - *this=iterator(); // end() iterator - else - value=iter->begin(); - } -} - -template <class T> bool RangeSet<T>::operator==(const RangeSet<T>& r) const { - return ranges.size() == r.ranges.size() && std::equal(ranges.begin(), ranges.end(), r.ranges.begin()); -} - -template <class T> typename RangeSet<T>::iterator RangeSet<T>::begin() const { - return empty() ? end() : iterator(ranges, ranges.begin(), front()); -} - -template <class T> typename RangeSet<T>::iterator RangeSet<T>::end() const { - return iterator(); -} - -template <class T> bool RangeSet<T>::iterator::equal(const iterator& i) const { - return ranges==i.ranges && (ranges==0 || value==i.value); -} - -template <class T> Range<T> RangeSet<T>::rangeContaining(const T& t) const { - typename Ranges::const_iterator i = - std::lower_bound(ranges.begin(), ranges.end(), Range<T>(t)); - return (i != ranges.end() && i->contains(t)) ? *i : Range<T>(t,t); -} - -template <class T> uint32_t RangeSet<T>::span() const { - if (ranges.empty()) return 0; - return ranges.back().last() - ranges.front().first(); -} - - -} // namespace qpid - - -#endif /*!QPID_RANGESET_H*/ diff --git a/cpp/src/qpid/RefCounted.h b/cpp/src/qpid/RefCounted.h index 10b5e4afcc..e7a9bf31c1 100644 --- a/cpp/src/qpid/RefCounted.h +++ b/cpp/src/qpid/RefCounted.h @@ -39,11 +39,13 @@ class RefCounted : boost::noncopyable { public: RefCounted() : count(0) {} void addRef() const { ++count; } - void release() const { if (--count==0) delete this; } + void release() const { if (--count==0) released(); } long refCount() { return count; } protected: virtual ~RefCounted() {}; + // Allow subclasses to over-ride behavior when refcount reaches 0. + virtual void released() const { delete this; } }; diff --git a/cpp/src/qpid/RefCountedBuffer.cpp b/cpp/src/qpid/RefCountedBuffer.cpp new file mode 100644 index 0000000000..9b8f1ebd5e --- /dev/null +++ b/cpp/src/qpid/RefCountedBuffer.cpp @@ -0,0 +1,53 @@ +/* + * + * 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/RefCountedBuffer.h" +#include <new> + +namespace qpid { + +RefCountedBuffer::RefCountedBuffer() : count(0) {} + +void RefCountedBuffer::destroy() const { + this->~RefCountedBuffer(); + ::delete[] reinterpret_cast<const char*>(this); +} + +char* RefCountedBuffer::addr() const { + return const_cast<char*>(reinterpret_cast<const char*>(this)+sizeof(RefCountedBuffer)); +} + +RefCountedBuffer::pointer RefCountedBuffer::create(size_t n) { + char* store=::new char[n+sizeof(RefCountedBuffer)]; + new(store) RefCountedBuffer; + return pointer(reinterpret_cast<RefCountedBuffer*>(store)); +} + +RefCountedBuffer::pointer::pointer() {} +RefCountedBuffer::pointer::pointer(RefCountedBuffer* x) : p(x) {} +RefCountedBuffer::pointer::pointer(const pointer& x) : p(x.p) {} +RefCountedBuffer::pointer::~pointer() {} +RefCountedBuffer::pointer& RefCountedBuffer::pointer::operator=(const RefCountedBuffer::pointer& x) { p = x.p; return *this; } + +char* RefCountedBuffer::pointer::cp() const { return p ? p->get() : 0; } +} // namespace qpid + + diff --git a/cpp/src/qpid/RefCountedBuffer.h b/cpp/src/qpid/RefCountedBuffer.h new file mode 100644 index 0000000000..c332325378 --- /dev/null +++ b/cpp/src/qpid/RefCountedBuffer.h @@ -0,0 +1,89 @@ +#ifndef QPID_REFCOUNTEDBUFFER_H +#define QPID_REFCOUNTEDBUFFER_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/utility.hpp> +#include <boost/detail/atomic_count.hpp> +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +// FIXME aconway 2008-09-06: easy to add alignment +/** + * Reference-counted byte buffer. + * No alignment guarantees. + */ +class RefCountedBuffer : boost::noncopyable { + mutable boost::detail::atomic_count count; + RefCountedBuffer(); + void destroy() const; + char* addr() const; + +public: + /** Smart char pointer to a reference counted buffer */ + class pointer { + boost::intrusive_ptr<RefCountedBuffer> p; + char* cp() const; + pointer(RefCountedBuffer* x); + friend class RefCountedBuffer; + + public: + pointer(); + pointer(const pointer&); + ~pointer(); + pointer& operator=(const pointer&); + + char* get() { return cp(); } + operator char*() { return cp(); } + char& operator*() { return *cp(); } + char& operator[](size_t i) { return cp()[i]; } + + const char* get() const { return cp(); } + operator const char*() const { return cp(); } + const char& operator*() const { return *cp(); } + const char& operator[](size_t i) const { return cp()[i]; } + }; + + /** Create a reference counted buffer of size n */ + static pointer create(size_t n); + + /** Get a pointer to the start of the buffer. */ + char* get() { return addr(); } + const char* get() const { return addr(); } + char& operator[](size_t i) { return get()[i]; } + const char& operator[](size_t i) const { return get()[i]; } + + void addRef() const { ++count; } + void release() const { if (--count==0) destroy(); } + long refCount() { return count; } +}; + +} // namespace qpid + +// intrusive_ptr support. +namespace boost { +inline void intrusive_ptr_add_ref(const qpid::RefCountedBuffer* p) { p->addRef(); } +inline void intrusive_ptr_release(const qpid::RefCountedBuffer* p) { p->release(); } +} + + +#endif /*!QPID_REFCOUNTEDBUFFER_H*/ diff --git a/cpp/src/qpid/SessionId.cpp b/cpp/src/qpid/SessionId.cpp index fce6619f5d..c7e83f83d7 100644 --- a/cpp/src/qpid/SessionId.cpp +++ b/cpp/src/qpid/SessionId.cpp @@ -19,7 +19,7 @@ * */ -#include "SessionId.h" +#include "qpid/SessionId.h" #include <sstream> namespace qpid { @@ -35,7 +35,7 @@ bool SessionId::operator==(const SessionId& id) const { } std::ostream& operator<<(std::ostream& o, const SessionId& id) { - return o << id.getName() << "@" << id.getUserId(); + return o << id.getUserId() << "." << id.getName(); } std::string SessionId::str() const { diff --git a/cpp/src/qpid/SessionId.h b/cpp/src/qpid/SessionId.h deleted file mode 100644 index 291c42a2bb..0000000000 --- a/cpp/src/qpid/SessionId.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef QPID_SESSIONID_H -#define QPID_SESSIONID_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/operators.hpp> -#include <string> - -namespace qpid { - -/** Identifier for a session. - * There are two parts to a session identifier: - * - * getUserId() returns the authentication principal associated with - * the session's connection. - * - * getName() returns the session name. - * - * The name must be unique among sessions with the same authentication - * principal. - */ -class SessionId : boost::totally_ordered1<SessionId> { - std::string userId; - std::string name; - public: - SessionId(const std::string& userId=std::string(), const std::string& name=std::string()); - std::string getUserId() const { return userId; } - std::string getName() const { return name; } - bool operator<(const SessionId&) const ; - bool operator==(const SessionId& id) const; - // Convert to a string - std::string str() const; -}; - -std::ostream& operator<<(std::ostream&, const SessionId&); - - -} // namespace qpid - -#endif /*!QPID_SESSIONID_H*/ diff --git a/cpp/src/qpid/SessionState.cpp b/cpp/src/qpid/SessionState.cpp index 1be0111489..4f370c6765 100644 --- a/cpp/src/qpid/SessionState.cpp +++ b/cpp/src/qpid/SessionState.cpp @@ -19,9 +19,10 @@ * */ -#include "SessionState.h" +#include "qpid/SessionState.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/enum.h" #include "qpid/log/Statement.h" #include <boost/bind.hpp> #include <numeric> @@ -37,10 +38,10 @@ using framing::FramingErrorException; namespace { bool isControl(const AMQFrame& f) { - return f.getMethod() && f.getMethod()->type() == framing::CONTROL; + return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_CONTROL; } bool isCommand(const AMQFrame& f) { - return f.getMethod() && f.getMethod()->type() == framing::COMMAND; + return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_COMMAND; } } // namespace @@ -60,7 +61,7 @@ void SessionPoint::advance(const AMQFrame& f) { if (f.isLastSegment() && f.isLastFrame()) ++command; // Single-frame command. else - offset += f.size(); + offset += f.encodedSize(); } else { // continuation frame for partial command if (offset == 0) @@ -76,7 +77,7 @@ void SessionPoint::advance(const AMQFrame& f) { // that the relationship of fragment offsets to the replay // list can be computed more easily. // - offset += f.size(); + offset += f.encodedSize(); } } } @@ -112,12 +113,13 @@ SessionState::ReplayRange SessionState::senderExpected(const SessionPoint& expec void SessionState::senderRecord(const AMQFrame& f) { if (isControl(f)) return; // Ignore control frames. - QPID_LOG_IF(debug, f.getMethod(), getId() << ": sent cmd " << sender.sendPoint.command << ": " << *f.getMethod()); + QPID_LOG(trace, getId() << ": sent cmd " << sender.sendPoint.command << ": " << *f.getBody()); + stateful = true; if (timeout) sender.replayList.push_back(f); - sender.unflushedSize += f.size(); - sender.bytesSinceKnownCompleted += f.size(); - sender.replaySize += f.size(); + sender.unflushedSize += f.encodedSize(); + sender.bytesSinceKnownCompleted += f.encodedSize(); + sender.replaySize += f.encodedSize(); sender.incomplete += sender.sendPoint.command; sender.sendPoint.advance(f); if (config.replayHardLimit && config.replayHardLimit < sender.replaySize) @@ -146,15 +148,15 @@ void SessionState::senderRecordKnownCompleted() { void SessionState::senderConfirmed(const SessionPoint& confirmed) { if (confirmed > sender.sendPoint) - throw InvalidArgumentException(QPID_MSG(getId() << "Confirmed commands not yet sent.")); + throw InvalidArgumentException(QPID_MSG(getId() << ": confirmed < " << confirmed << " but only sent < " << sender.sendPoint)); QPID_LOG(debug, getId() << ": sender confirmed point moved to " << confirmed); ReplayList::iterator i = sender.replayList.begin(); while (i != sender.replayList.end() && sender.replayPoint.command < confirmed.command) { sender.replayPoint.advance(*i); assert(sender.replayPoint <= sender.sendPoint); - sender.replaySize -= i->size(); + sender.replaySize -= i->encodedSize(); if (sender.replayPoint > sender.flushPoint) - sender.unflushedSize -= i->size(); + sender.unflushedSize -= i->encodedSize(); ++i; } if (sender.replayPoint > sender.flushPoint) @@ -168,7 +170,7 @@ void SessionState::senderCompleted(const SequenceSet& commands) { QPID_LOG(debug, getId() << ": sender marked completed: " << commands); sender.incomplete -= commands; // Completion implies confirmation but we don't handle out-of-order - // confirmation, so confirm only the first contiguous range of commands. + // confirmation, so confirm up to the end of the first contiguous range of commands. senderConfirmed(SessionPoint(commands.rangesBegin()->end())); } @@ -182,21 +184,23 @@ void SessionState::receiverSetCommandPoint(const SessionPoint& point) { } bool SessionState::receiverRecord(const AMQFrame& f) { + if (receiverTrackingDisabled) return true; //Very nasty hack for push bridges if (isControl(f)) return true; // Ignore control frames. stateful = true; receiver.expected.advance(f); - receiver.bytesSinceKnownCompleted += f.size(); + receiver.bytesSinceKnownCompleted += f.encodedSize(); bool firstTime = receiver.expected > receiver.received; if (firstTime) { receiver.received = receiver.expected; receiver.incomplete += receiverGetCurrent(); } - QPID_LOG_IF(debug, f.getMethod(), getId() << ": recv cmd " << receiverGetCurrent() << ": " << *f.getMethod()); - QPID_LOG_IF(debug, !firstTime, "Ignoring duplicate frame: " << receiverGetCurrent() << ": " << f); + QPID_LOG(trace, getId() << ": recv cmd " << receiverGetCurrent() << ": " << *f.getBody()); + if (!firstTime) QPID_LOG(trace, "Ignoring duplicate frame."); return firstTime; } void SessionState::receiverCompleted(SequenceNumber command, bool cumulative) { + if (receiverTrackingDisabled) return; //Very nasty hack for push bridges assert(receiver.incomplete.contains(command)); // Internal error to complete command twice. SequenceNumber first =cumulative ? receiver.incomplete.front() : command; SequenceNumber last = command; @@ -236,7 +240,7 @@ SessionState::Configuration::Configuration(size_t flush, size_t hard) : replayFlushLimit(flush), replayHardLimit(hard) {} SessionState::SessionState(const SessionId& i, const Configuration& c) - : id(i), timeout(), config(c), stateful() + : id(i), timeout(), config(c), stateful(), receiverTrackingDisabled(false) { QPID_LOG(debug, "SessionState::SessionState " << id << ": " << this); } @@ -249,4 +253,32 @@ std::ostream& operator<<(std::ostream& o, const SessionPoint& p) { return o << "(" << p.command.getValue() << "+" << p.offset << ")"; } +void SessionState::setState( + const SequenceNumber& replayStart, + const SequenceNumber& sendCommandPoint, + const SequenceSet& sentIncomplete, + const SequenceNumber& expected, + const SequenceNumber& received, + const SequenceSet& unknownCompleted, + const SequenceSet& receivedIncomplete +) +{ + sender.replayPoint = replayStart; + sender.flushPoint = sendCommandPoint; + sender.sendPoint = sendCommandPoint; + sender.unflushedSize = 0; + sender.replaySize = 0; // Replay list will be updated separately. + sender.incomplete = sentIncomplete; + sender.bytesSinceKnownCompleted = 0; + + receiver.expected = expected; + receiver.received = received; + receiver.unknownCompleted = unknownCompleted; + receiver.incomplete = receivedIncomplete; + receiver.bytesSinceKnownCompleted = 0; +} + +void SessionState::disableReceiverTracking() { receiverTrackingDisabled = true; } +void SessionState::enableReceiverTracking() { receiverTrackingDisabled = false; } + } // namespace qpid diff --git a/cpp/src/qpid/SessionState.h b/cpp/src/qpid/SessionState.h index 10937b7a1e..da28738546 100644 --- a/cpp/src/qpid/SessionState.h +++ b/cpp/src/qpid/SessionState.h @@ -31,6 +31,7 @@ #include <boost/range/iterator_range.hpp> #include <vector> #include <iosfwd> +#include <qpid/CommonImportExport.h> namespace qpid { using framing::SequenceNumber; @@ -38,19 +39,19 @@ using framing::SequenceSet; /** A point in the session. Points to command id + offset */ struct SessionPoint : boost::totally_ordered1<SessionPoint> { - SessionPoint(SequenceNumber command = 0, uint64_t offset = 0); + QPID_COMMON_EXTERN SessionPoint(SequenceNumber command = 0, uint64_t offset = 0); SequenceNumber command; uint64_t offset; /** Advance past frame f */ - void advance(const framing::AMQFrame& f); + QPID_COMMON_EXTERN void advance(const framing::AMQFrame& f); - bool operator<(const SessionPoint&) const; - bool operator==(const SessionPoint&) const; + QPID_COMMON_EXTERN bool operator<(const SessionPoint&) const; + QPID_COMMON_EXTERN bool operator==(const SessionPoint&) const; }; -std::ostream& operator<<(std::ostream&, const SessionPoint&); +QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const SessionPoint&); /** * Support for session idempotence barrier and resume as defined in @@ -78,14 +79,14 @@ class SessionState { typedef boost::iterator_range<ReplayList::iterator> ReplayRange; struct Configuration { - Configuration(size_t flush=1024*1024, size_t hard=0); + QPID_COMMON_EXTERN Configuration(size_t flush=1024*1024, size_t hard=0); size_t replayFlushLimit; // Flush when the replay list >= N bytes. 0 disables. size_t replayHardLimit; // Kill session if replay list > N bytes. 0 disables. }; - SessionState(const SessionId& =SessionId(), const Configuration& =Configuration()); + QPID_COMMON_EXTERN SessionState(const SessionId& =SessionId(), const Configuration& =Configuration()); - virtual ~SessionState(); + QPID_COMMON_EXTERN virtual ~SessionState(); bool hasState() const; @@ -100,73 +101,100 @@ class SessionState { // ==== Functions for sender state. /** Record frame f for replay. Should not be called during replay. */ - virtual void senderRecord(const framing::AMQFrame& f); + QPID_COMMON_EXTERN virtual void senderRecord(const framing::AMQFrame& f); /** @return true if we should send flush for confirmed and completed commands. */ - virtual bool senderNeedFlush() const; + QPID_COMMON_EXTERN virtual bool senderNeedFlush() const; /** Called when flush for confirmed and completed commands is sent to peer. */ - virtual void senderRecordFlush(); + QPID_COMMON_EXTERN virtual void senderRecordFlush(); /** True if we should reply to the next incoming completed command */ - virtual bool senderNeedKnownCompleted() const; + QPID_COMMON_EXTERN virtual bool senderNeedKnownCompleted() const; /** Called when knownCompleted is sent to peer. */ - virtual void senderRecordKnownCompleted(); + QPID_COMMON_EXTERN virtual void senderRecordKnownCompleted(); /** Called when the peer confirms up to comfirmed. */ - virtual void senderConfirmed(const SessionPoint& confirmed); + QPID_COMMON_EXTERN virtual void senderConfirmed(const SessionPoint& confirmed); /** Called when the peer indicates commands completed */ - virtual void senderCompleted(const SequenceSet& commands); + QPID_COMMON_EXTERN virtual void senderCompleted(const SequenceSet& commands); /** Point from which the next new (not replayed) data will be sent. */ - virtual SessionPoint senderGetCommandPoint(); + QPID_COMMON_EXTERN virtual SessionPoint senderGetCommandPoint(); /** Set of outstanding incomplete commands */ - virtual SequenceSet senderGetIncomplete() const; + QPID_COMMON_EXTERN virtual SequenceSet senderGetIncomplete() const; /** Point from which we can replay. */ - virtual SessionPoint senderGetReplayPoint() const; + QPID_COMMON_EXTERN virtual SessionPoint senderGetReplayPoint() const; /** Peer expecting commands from this point. - virtual *@return Range of frames to be replayed. - */ - virtual ReplayRange senderExpected(const SessionPoint& expected); + *@return Range of frames to be replayed. + */ + QPID_COMMON_EXTERN virtual ReplayRange senderExpected(const SessionPoint& expected); // ==== Functions for receiver state /** Set the command point. */ - virtual void receiverSetCommandPoint(const SessionPoint& point); + QPID_COMMON_EXTERN virtual void receiverSetCommandPoint(const SessionPoint& point); /** Returns true if frame should be be processed, false if it is a duplicate. */ - virtual bool receiverRecord(const framing::AMQFrame& f); + QPID_COMMON_EXTERN virtual bool receiverRecord(const framing::AMQFrame& f); /** Command completed locally */ - virtual void receiverCompleted(SequenceNumber command, bool cumulative=false); + QPID_COMMON_EXTERN virtual void receiverCompleted(SequenceNumber command, bool cumulative=false); /** Peer has indicated commands are known completed */ - virtual void receiverKnownCompleted(const SequenceSet& commands); + QPID_COMMON_EXTERN virtual void receiverKnownCompleted(const SequenceSet& commands); /** True if the next completed control should set the timely-reply argument * to request a knonw-completed response. */ - virtual bool receiverNeedKnownCompleted() const; + QPID_COMMON_EXTERN virtual bool receiverNeedKnownCompleted() const; /** Get the incoming command point */ - virtual const SessionPoint& receiverGetExpected() const; + QPID_COMMON_EXTERN virtual const SessionPoint& receiverGetExpected() const; /** Get the received high-water-mark, may be > getExpected() during replay */ - virtual const SessionPoint& receiverGetReceived() const; + QPID_COMMON_EXTERN virtual const SessionPoint& receiverGetReceived() const; /** Completed received commands that the peer may not know about. */ - virtual const SequenceSet& receiverGetUnknownComplete() const; + QPID_COMMON_EXTERN virtual const SequenceSet& receiverGetUnknownComplete() const; /** Incomplete received commands. */ - virtual const SequenceSet& receiverGetIncomplete() const; + QPID_COMMON_EXTERN virtual const SequenceSet& receiverGetIncomplete() const; /** ID of the command currently being handled. */ - virtual SequenceNumber receiverGetCurrent() const; + QPID_COMMON_EXTERN virtual SequenceNumber receiverGetCurrent() const; + + /** Set the state variables, used to create a session that will resume + * from some previously established point. + */ + QPID_COMMON_EXTERN virtual void setState( + const SequenceNumber& replayStart, + const SequenceNumber& sendCommandPoint, + const SequenceSet& sentIncomplete, + const SequenceNumber& expected, + const SequenceNumber& received, + const SequenceSet& unknownCompleted, + const SequenceSet& receivedIncomplete + ); + + /** + * So called 'push' bridges work by faking a subscribe request + * (and the accompanying flows etc) to the local broker to initiate + * the outflow of messages for the bridge. + * + * As the peer doesn't send these it cannot include them in its + * session state. To keep the session state on either side of the + * bridge in sync, this hack allows the tracking of state for + * received messages to be disabled for the faked commands and + * subsequently re-enabled. + */ + QPID_COMMON_EXTERN void disableReceiverTracking(); + QPID_COMMON_EXTERN void enableReceiverTracking(); private: @@ -196,6 +224,7 @@ class SessionState { uint32_t timeout; Configuration config; bool stateful; + bool receiverTrackingDisabled;//very nasty hack for 'push' bridges }; inline bool operator==(const SessionId& id, const SessionState& s) { return s == id; } diff --git a/cpp/src/qpid/StringUtils.cpp b/cpp/src/qpid/StringUtils.cpp index 17eb141e12..c436441c56 100644 --- a/cpp/src/qpid/StringUtils.cpp +++ b/cpp/src/qpid/StringUtils.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "StringUtils.h" +#include "qpid/StringUtils.h" namespace qpid { diff --git a/cpp/src/qpid/StringUtils.h b/cpp/src/qpid/StringUtils.h index 3120e43334..4130fae017 100644 --- a/cpp/src/qpid/StringUtils.h +++ b/cpp/src/qpid/StringUtils.h @@ -22,6 +22,8 @@ * */ +#include "qpid/CommonImportExport.h" + #include <string> #include <vector> @@ -31,12 +33,12 @@ namespace qpid { * Split 'in' into words using delimiters in 'delims' and put * resulting strings into 'out' vector. */ -void split(std::vector<std::string>& out, const std::string& in, const std::string& delims); +QPID_COMMON_EXTERN void split(std::vector<std::string>& out, const std::string& in, const std::string& delims); /** * Split 'in' into words using delimiters in 'delims' and return the * resulting strings in a vector. */ -std::vector<std::string> split(const std::string& in, const std::string& delims); +QPID_COMMON_EXTERN std::vector<std::string> split(const std::string& in, const std::string& delims); } // namespace qpid diff --git a/cpp/src/qpid/Url.cpp b/cpp/src/qpid/Url.cpp index 95d6a34136..f831167dd8 100644 --- a/cpp/src/qpid/Url.cpp +++ b/cpp/src/qpid/Url.cpp @@ -19,57 +19,32 @@ #include "qpid/Url.h" #include "qpid/Exception.h" #include "qpid/Msg.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/sys/StrError.h" -#include <limits.h> // NB: must be before boost/spirit headers. -#include <boost/spirit.hpp> -#include <boost/spirit/actor.hpp> +#include <boost/lexical_cast.hpp> -#include <sstream> +#include <algorithm> -#include <sys/ioctl.h> -#include <net/if.h> -#include <unistd.h> -#include <arpa/inet.h> -#include <stdio.h> -#include <errno.h> +#include <string.h> -using namespace boost::spirit; using namespace std; +using boost::lexical_cast; namespace qpid { -std::ostream& operator<<(std::ostream& os, const TcpAddress& a) { - return os << "tcp:" << a.host << ":" << a.port; -} - -std::istream& operator>>(std::istream&, const TcpAddress&); +Url::Invalid::Invalid(const string& s) : Exception(s) {} Url Url::getHostNameUrl(uint16_t port) { - char name[HOST_NAME_MAX]; - if (::gethostname(name, sizeof(name)) != 0) - throw InvalidUrl(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno))); - return Url(TcpAddress(name, port)); + TcpAddress address(std::string(), port); + if (!sys::SystemInfo::getLocalHostname(address)) + throw Url::Invalid(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno))); + return Url(address); } -static const string LOCALHOST("127.0.0.1"); - Url Url::getIpAddressesUrl(uint16_t port) { Url url; - int s = socket (PF_INET, SOCK_STREAM, 0); - for (int i=1;;i++) { - struct ifreq ifr; - ifr.ifr_ifindex = i; - if (::ioctl (s, SIOCGIFNAME, &ifr) < 0) - break; - /* now ifr.ifr_name is set */ - if (::ioctl (s, SIOCGIFADDR, &ifr) < 0) - continue; - struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr; - string addr(inet_ntoa(sin->sin_addr)); - if (addr != LOCALHOST) - url.push_back(TcpAddress(addr, port)); - } - close (s); + sys::SystemInfo::getLocalIpAddresses(port, url); return url; } @@ -93,78 +68,138 @@ ostream& operator<<(ostream& os, const Url& url) { return os; } -// Addition to boost::spirit parsers: accept any character from a -// string. Vastly more compile-time-efficient than long rules of the -// form: ch_p('x') | ch_p('y') |... -// -struct ch_in : public char_parser<ch_in> { - ch_in(const string& chars_) : chars(chars_) {} - bool test(char ch_) const { - return chars.find(ch_) != string::npos; - } - string chars; -}; -inline ch_in ch_in_p(const string& chars) { - return ch_in(chars); -} +/** Simple recursive-descent parser for url grammar in AMQP 0-10 spec: + + amqp_url = "amqp:" prot_addr_list + prot_addr_list = [prot_addr ","]* prot_addr + prot_addr = tcp_prot_addr | tls_prot_addr + + tcp_prot_addr = tcp_id tcp_addr + tcp_id = "tcp:" | "" + tcp_addr = [host [":" port] ] + host = <as per http://www.ietf.org/rfc/rfc3986.txt> + port = number]]> +*/ +class UrlParser { + public: + UrlParser(Url& u, const char* s) : url(u), text(s), end(s+strlen(s)), i(s) {} + bool parse() { return literal("amqp:") && list(&UrlParser::protAddr, &UrlParser::comma) && i == end; } -/** Grammar for AMQP URLs. */ -struct UrlGrammar : public grammar<UrlGrammar> -{ - Url& addr; + private: + typedef bool (UrlParser::*Rule)(); + + bool comma() { return literal(","); } + + // NOTE: tcpAddr must be last since it is allowed to omit it's tcp: tag. + bool protAddr() { return exampleAddr() || tcpAddr(); } + + bool tcpAddr() { + TcpAddress addr; + literal("tcp:"); // Don't check result, allowed to be absent. + return addIf(host(addr.host) && (literal(":") ? port(addr.port) : true), addr); + } - UrlGrammar(Url& addr_) : addr(addr_) {} - - template <class ScannerT> - struct definition { - TcpAddress tcp; - - definition(const UrlGrammar& self) - { - first = eps_p[clear_a(self.addr)] >> amqp_url; - amqp_url = str_p("amqp:") >> prot_addr_list >> - !(str_p("/") >> !parameters); - prot_addr_list = prot_addr % ','; - prot_addr = tcp_prot_addr; // Extend for TLS etc. - - // TCP addresses - tcp_prot_addr = tcp_id >> tcp_addr[push_back_a(self.addr, tcp)]; - tcp_id = !str_p("tcp:"); - tcp_addr = !(host[assign_a(tcp.host)] >> !(':' >> port)); - - // See http://www.apps.ietf.org/rfc/rfc3986.html#sec-A - // for real host grammar. Shortcut: - port = uint_parser<uint16_t>()[assign_a(tcp.port)]; - host = *( unreserved | pct_encoded ); - unreserved = alnum_p | ch_in_p("-._~"); - pct_encoded = "%" >> xdigit_p >> xdigit_p; - parameters = *anychar_p >> end_p; // Ignore, not used yet. + // Placeholder address type till we have multiple address types. Address is a single char. + bool exampleAddr () { + if (literal("example:") && i < end) { + ExampleAddress ex(*i++); + url.push_back(ex); + return true; } + return false; + } + + // FIXME aconway 2008-11-20: this does not implement http://www.ietf.org/rfc/rfc3986.txt. + // Works for DNS names and ipv4 literals but won't handle ipv6. + bool host(string& h) { + const char* start=i; + while (unreserved() || pctEncoded()) + ; + if (start == i) h = LOCALHOST; // Default + else h.assign(start, i); + return true; + } + + bool unreserved() { return (::isalnum(*i) || ::strchr("-._~", *i)) && advance(); } - const rule<ScannerT>& start() const { return first; } + bool pctEncoded() { return literal("%") && hexDigit() && hexDigit(); } - rule<ScannerT> first, amqp_url, prot_addr_list, prot_addr, - tcp_prot_addr, tcp_id, tcp_addr, host, port, - unreserved, pct_encoded, parameters; + bool hexDigit() { return i < end && ::strchr("01234567890abcdefABCDEF", *i) && advance(); } + + bool port(uint16_t& p) { return decimalInt(p); } + + template <class AddrType> bool addIf(bool ok, const AddrType& addr) { if (ok) url.push_back(addr); return ok; } + + template <class IntType> bool decimalInt(IntType& n) { + const char* start = i; + while (decDigit()) + ; + try { + n = lexical_cast<IntType>(string(start, i)); + return true; + } catch(...) { return false; } + } + + bool decDigit() { return i < end && ::isdigit(*i) && advance(); } + + bool literal(const char* s) { + int n = ::strlen(s); + if (n <= end-i && equal(s, s+n, i)) return advance(n); + return false; }; + + bool noop() { return true; } + + /** List of item, separated by separator, with min & max bounds. */ + bool list(Rule item, Rule separator, size_t min=0, size_t max=UNLIMITED) { + assert(max > 0); + assert(max >= min); + if (!(this->*item)()) return min == 0; // Empty list. + size_t n = 1; + while (n < max && i < end) { + if (!(this->*separator)()) break; + if (i == end || !(this->*item)()) return false; // Separator with no item. + ++n; + } + return n >= min; + } + + /** List of items with no separator */ + bool list(Rule item, size_t min=0, size_t max=UNLIMITED) { return list(item, &UrlParser::noop, min, max); } + + bool advance(size_t n=1) { + if (i+n > end) return false; + i += n; + return true; + } + + static const size_t UNLIMITED = size_t(~1); + static const std::string LOCALHOST; + + Url& url; + const char* text; + const char* end; + const char* i; }; +const string UrlParser::LOCALHOST("127.0.0.1"); + void Url::parse(const char* url) { - cache.clear(); - if (!boost::spirit::parse(url, UrlGrammar(*this)).full) - throw InvalidUrl(string("Invalid AMQP url: ")+url); + parseNoThrow(url); + if (empty()) + throw Url::Invalid(QPID_MSG("Invalid URL: " << url)); } void Url::parseNoThrow(const char* url) { cache.clear(); - if (!boost::spirit::parse(url, UrlGrammar(*this)).full) + if (!UrlParser(*this, url).parse()) clear(); } void Url::throwIfEmpty() const { if (empty()) - throw InvalidUrl("URL contains no addresses"); + throw Url::Invalid("URL contains no addresses"); } std::istream& operator>>(std::istream& is, Url& url) { diff --git a/cpp/src/qpid/Url.h b/cpp/src/qpid/Url.h deleted file mode 100644 index 20f42db0ad..0000000000 --- a/cpp/src/qpid/Url.h +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef QPID_URL_H -#define QPID_URL_H - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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/Exception.h" -#include <boost/variant.hpp> -#include <string> -#include <vector> -#include <new> -#include <ostream> - -namespace qpid { - -/** TCP address of a broker - host:port */ -struct TcpAddress { - static const uint16_t DEFAULT_PORT=5672; - explicit TcpAddress(const std::string& host_=std::string(), - uint16_t port_=DEFAULT_PORT) - : host(host_), port(port_) {} - std::string host; - uint16_t port; -}; - -inline bool operator==(const TcpAddress& x, const TcpAddress& y) { - return y.host==x.host && y.port == x.port; -} - -std::ostream& operator<<(std::ostream& os, const TcpAddress& a); - -/** Address is a variant of all address types, more coming in future. */ -typedef boost::variant<TcpAddress> Address; - -/** An AMQP URL contains a list of addresses */ -struct Url : public std::vector<Address> { - - /** Url with the hostname as returned by gethostname(2) */ - static Url getHostNameUrl(uint16_t port); - - /** Url with local IP address(es), may be more than one address - * on a multi-homed host. */ - static Url getIpAddressesUrl(uint16_t port); - - struct InvalidUrl : public Exception { - InvalidUrl(const std::string& s) : Exception(s) {} - }; - - /** Convert to string form. */ - std::string str() const; - - /** Empty URL. */ - Url() {} - - /** URL containing a single address */ - explicit Url(const Address& addr) { push_back(addr); } - - /** Parse url, throw InvalidUrl if invalid. */ - explicit Url(const std::string& url) { parse(url.c_str()); } - - /** Parse url, throw InvalidUrl if invalid. */ - explicit Url(const char* url) { parse(url); } - - template<class T> Url& operator=(T s) { parse(s); return *this; } - - /** Throw InvalidUrl if the URL does not contain any addresses. */ - void throwIfEmpty() const; - - /** Replace contents with parsed URL as defined in - * https://wiki.108.redhat.com/jira/browse/AMQP-95 - *@exception InvalidUrl if the url is invalid. - */ - void parse(const char* url); - void parse(const std::string& url) { parse(url.c_str()); } - - /** Replace contesnts with parsed URL as defined in - * https://wiki.108.redhat.com/jira/browse/AMQP-95 - * url.empty() will be true if url is invalid. - */ - void parseNoThrow(const char* url); - - private: - mutable std::string cache; // cache string form for efficiency. -}; - -inline bool operator==(const Url& a, const Url& b) { return a.str()==b.str(); } -inline bool operator!=(const Url& a, const Url& b) { return a.str()!=b.str(); } - -std::ostream& operator<<(std::ostream& os, const Url& url); -std::istream& operator>>(std::istream& is, Url& url); - -} // namespace qpid - -#endif /*!QPID_URL_H*/ diff --git a/cpp/src/qpid/Version.h b/cpp/src/qpid/Version.h new file mode 100755 index 0000000000..67c0281ac5 --- /dev/null +++ b/cpp/src/qpid/Version.h @@ -0,0 +1,44 @@ +#ifndef QPID_VERSION_H +#define QPID_VERSION_H + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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> + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +namespace qpid { +#ifdef HAVE_CONFIG_H + const std::string product = PACKAGE_NAME; + const std::string version = PACKAGE_VERSION; +# if HAVE_SASL + const std::string saslName = BROKER_SASL_NAME; +# else + const std::string saslName = "qpidd-no-sasl"; +# endif +#else + const std::string product = "qpidc"; + const std::string version = "0.6"; + const std::string saslName = "qpid-broker"; +#endif +} + +#endif /*!QPID_VERSION_H*/ diff --git a/cpp/src/qpid/acl/Acl.cpp b/cpp/src/qpid/acl/Acl.cpp index 79e4af57ee..fe2644c136 100644 --- a/cpp/src/qpid/acl/Acl.cpp +++ b/cpp/src/qpid/acl/Acl.cpp @@ -16,126 +16,150 @@ * */ -#include "Acl.h" - +#include "qpid/acl/Acl.h" +#include "qpid/acl/AclData.h" #include "qpid/broker/Broker.h" #include "qpid/Plugin.h" #include "qpid/Options.h" -#include "qpid/shared_ptr.h" #include "qpid/log/Logger.h" +#include "qmf/org/apache/qpid/acl/Package.h" +#include "qmf/org/apache/qpid/acl/EventAllow.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" #include <map> +#include <boost/shared_ptr.hpp> #include <boost/utility/in_place_factory.hpp> -namespace qpid { -namespace acl { - using namespace std; - - Acl::Acl (AclValues& av, broker::Broker& b): aclValues(av), broker(&b), transferAcl(false) +using namespace qpid::acl; +using qpid::broker::Broker; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +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) +{ + + agent = broker->getManagementAgent(); + + if (agent != 0){ + _qmf::Package packageInit(agent); + mgmtObject = new _qmf::Acl (agent, this, broker); + agent->addObject (mgmtObject); + } + + std::string errorString; + if (!readAclFile(errorString)){ + throw Exception("Could not read ACL file " + errorString); + if (mgmtObject!=0) mgmtObject->set_enforcingAcl(0); + } + QPID_LOG(info, "ACL Plugin loaded"); + if (mgmtObject!=0) mgmtObject->set_enforcingAcl(1); +} + + bool Acl::authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params) { - if (!readAclFile()) throw Exception("Could not read ACL file"); - QPID_LOG(info, "ACL Plugin loaded"); + boost::shared_ptr<AclData> dataLocal = data; //rcu copy - } + // add real ACL check here... + AclResult aclreslt = dataLocal->lookup(id,action,objType,name,params); - std::string Acl::printAction(acl::Action action) - { - switch (action) - { - case CONSUME: return "Consume"; - case PUBLISH: return "Publish"; - case CREATE: return "Create"; - case ACCESS: return "Access"; - case BIND: return "Bind"; - case UNBIND: return "Unbind"; - case DELETE: return "Delete"; - case PURGE: return "Purge"; - case UPDATE: return "Update"; - default: return "Unknown"; - } - } - - std::string Acl::printObjType(acl::ObjectType objType) - { - switch (objType) - { - case QUEUE: return "Queue"; - case EXCHANGE: return "Exchnage"; - case BROKER: return "Broker"; - case LINK: return "Link"; - case ROUTE: return "Route"; - default: return "Unknown"; - } - } - bool Acl::authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string name, std::map<std::string, std::string>* - /*params*/) - { - if (aclValues.noEnforce) return true; - boost::shared_ptr<AclData> dataLocal = data; //rcu copy - - // only use dataLocal here... - - // add real ACL check here... - AclResult aclreslt = ALLOWLOG; // hack to test, set based on real decision. - - - return result(aclreslt, id, action, objType, name); + return result(aclreslt, id, action, objType, name); } - bool Acl::authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string ExchangeName, std::string /*RoutingKey*/) + bool Acl::authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& ExchangeName, const std::string& RoutingKey) { - if (aclValues.noEnforce) return true; boost::shared_ptr<AclData> dataLocal = data; //rcu copy - + // only use dataLocal here... - - // add real ACL check here... - AclResult aclreslt = ALLOWLOG; // hack to test, set based on real decision. - - - return result(aclreslt, id, action, objType, ExchangeName); + AclResult aclreslt = dataLocal->lookup(id,action,objType,ExchangeName,RoutingKey); + + return result(aclreslt, id, action, objType, ExchangeName); } - - bool Acl::result(AclResult aclreslt, std::string id, acl::Action action, acl::ObjectType objType, std::string name) + + bool Acl::result(const AclResult& aclreslt, const std::string& id, const Action& action, const ObjectType& objType, const std::string& name) { switch (aclreslt) { case ALLOWLOG: - QPID_LOG(info, "ACL Allow id:" << id <<" action:" << printAction(action) << " ObjectType:" << printObjType(objType) << " Name:" << name ); + QPID_LOG(info, "ACL Allow id:" << id <<" action:" << AclHelper::getActionStr(action) << + " ObjectType:" << AclHelper::getObjectTypeStr(objType) << " Name:" << name ); + agent->raiseEvent(_qmf::EventAllow(id, AclHelper::getActionStr(action), + AclHelper::getObjectTypeStr(objType), + name, framing::FieldTable())); case ALLOW: return true; - case DENYNOLOG: - return false; case DENY: - default: - QPID_LOG(info, "ACL Deny id:" << id << " action:" << printAction(action) << " ObjectType:" << printObjType(objType) << " Name:" << name); + if (mgmtObject!=0) mgmtObject->inc_aclDenyCount(); return false; + case DENYLOG: + if (mgmtObject!=0) mgmtObject->inc_aclDenyCount(); + default: + QPID_LOG(info, "ACL Deny id:" << id << " action:" << AclHelper::getActionStr(action) << " ObjectType:" << AclHelper::getObjectTypeStr(objType) << " Name:" << name); + agent->raiseEvent(_qmf::EventDeny(id, AclHelper::getActionStr(action), + AclHelper::getObjectTypeStr(objType), + name, framing::FieldTable())); + return false; } - return false; + return false; } - - bool Acl::readAclFile() + + bool Acl::readAclFile(std::string& errorText) { // only set transferAcl = true if a rule implies the use of ACL on transfer, else keep false for permormance reasons. - return readAclFile(aclValues.aclFile); + return readAclFile(aclValues.aclFile, errorText); } - bool Acl::readAclFile(std::string aclFile) { + bool Acl::readAclFile(std::string& aclFile, std::string& errorText) { boost::shared_ptr<AclData> d(new AclData); - if (AclReader::read(aclFile, d)) + AclReader ar; + if (ar.read(aclFile, d)){ + agent->raiseEvent(_qmf::EventFileLoadFailed("", ar.getError())); + errorText = ar.getError(); + QPID_LOG(error,ar.getError()); return false; - + } + data = d; + transferAcl = data->transferAcl; // any transfer ACL + if (mgmtObject!=0){ + mgmtObject->set_transferAcl(transferAcl?1:0); + mgmtObject->set_policyFile(aclFile); + sys::AbsTime now = sys::AbsTime::now(); + int64_t ns = sys::Duration(now); + mgmtObject->set_lastAclLoad(ns); + agent->raiseEvent(_qmf::EventFileLoaded("")); + } return true; } Acl::~Acl(){} + ManagementObject* Acl::GetManagementObject(void) const + { + return (ManagementObject*) mgmtObject; + } - -}} // namespace qpid::acl + Manageable::status_t Acl::ManagementMethod (uint32_t methodId, Args& /*args*/, string& text) + { + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + QPID_LOG (debug, "Queue::ManagementMethod [id=" << methodId << "]"); + + switch (methodId) + { + case _qmf::Acl::METHOD_RELOADACLFILE : + readAclFile(text); + status = Manageable::STATUS_USER; + break; + } + + return status; +} diff --git a/cpp/src/qpid/acl/Acl.h b/cpp/src/qpid/acl/Acl.h index a82add556c..e153187b3d 100644 --- a/cpp/src/qpid/acl/Acl.h +++ b/cpp/src/qpid/acl/Acl.h @@ -23,9 +23,12 @@ #include "qpid/acl/AclReader.h" -#include "qpid/shared_ptr.h" #include "qpid/RefCounted.h" #include "qpid/broker/AclModule.h" +#include "qpid/management/Manageable.h" +#include "qpid/management/ManagementAgent.h" +#include "qmf/org/apache/qpid/acl/Acl.h" + #include <map> #include <string> @@ -38,14 +41,11 @@ class Broker; namespace acl { struct AclValues { - bool noEnforce; - std::string aclFile; - - AclValues() {noEnforce = false; aclFile = "policy.acl"; } + std::string aclFile; }; -class Acl : public broker::AclModule, public RefCounted +class Acl : public broker::AclModule, public RefCounted, public management::Manageable { private: @@ -53,7 +53,8 @@ private: broker::Broker* broker; bool transferAcl; boost::shared_ptr<AclData> data; - + qmf::org::apache::qpid::acl::Acl* mgmtObject; // mgnt owns lifecycle + qpid::management::ManagementAgent* agent; public: Acl (AclValues& av, broker::Broker& b); @@ -63,20 +64,21 @@ public: inline virtual bool doTransferAcl() {return transferAcl;}; // create specilied authorise methods for cases that need faster matching as needed. - virtual bool authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string name, std::map<std::string, std::string>* params); - virtual bool authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string ExchangeName, std::string RoutingKey); + virtual bool authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params=0); + virtual bool authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& ExchangeName,const std::string& RoutingKey); virtual ~Acl(); private: - std::string printAction(acl::Action action); - std::string printObjType(acl::ObjectType objType); - bool result(AclResult aclreslt, std::string id, acl::Action action, acl::ObjectType objType, std::string name); - bool readAclFile(); - bool readAclFile(std::string aclFile); + bool result(const AclResult& aclreslt, const std::string& id, const Action& action, const ObjectType& objType, const std::string& name); + bool readAclFile(std::string& errorText); + bool readAclFile(std::string& aclFile, std::string& errorText); + virtual qpid::management::ManagementObject* GetManagementObject(void) const; + virtual management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + }; - + }} // namespace qpid::acl #endif // QPID_ACL_ACL_H diff --git a/cpp/src/qpid/acl/AclData.cpp b/cpp/src/qpid/acl/AclData.cpp new file mode 100644 index 0000000000..5d7a028736 --- /dev/null +++ b/cpp/src/qpid/acl/AclData.cpp @@ -0,0 +1,236 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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/acl/AclData.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/IntegerTypes.h" +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace acl { + +AclData::AclData():decisionMode(qpid::acl::DENY),transferAcl(false) +{ + for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){ + actionList[cnt]=0; + } + +} + +void AclData::clear () +{ + for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){ + if (actionList[cnt]){ + for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++) + delete actionList[cnt][cnt1]; + } + delete[] actionList[cnt]; + } + +} + +bool AclData::matchProp(const std::string & src, const std::string& src1) +{ + // allow wildcard on the end of strings... + if (src.data()[src.size()-1]=='*') { + return (src.compare(0, src.size()-1, src1, 0,src.size()-1 ) == 0); + } else { + return (src.compare(src1)==0) ; + } +} + +AclResult AclData::lookup(const std::string& id, const Action& action, const ObjectType& objType, + const std::string& name, std::map<Property, std::string>* params) { + + QPID_LOG(debug, "ACL: Lookup for id:" << id << " action:" << AclHelper::getActionStr((Action) action) + << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType) << " name:" << name + << " with params " << AclHelper::propertyMapToString(params)); + + AclResult aclresult = decisionMode; + if (actionList[action] && actionList[action][objType]) { + AclData::actObjItr itrRule = actionList[action][objType]->find(id); + if (itrRule == actionList[action][objType]->end()) + itrRule = actionList[action][objType]->find("*"); + + if (itrRule != actionList[action][objType]->end()) { + + QPID_LOG(debug, "ACL: checking the following rules for : " << itrRule->first ); + + //loop the vector + for (ruleSetItr i = itrRule->second.begin(); i < itrRule->second.end(); i++) { + QPID_LOG(debug, "ACL: checking rule " << i->toString()); + // loop the names looking for match + bool match = true; + for (propertyMapItr pMItr = i->props.begin(); (pMItr != i->props.end()) && match; pMItr++) { + //match name is exists first + if (pMItr->first == acl::PROP_NAME) { + if (matchProp(pMItr->second, name)){ + QPID_LOG(debug, "ACL: name '" << name << "' matched with name '" + << pMItr->second << "' given in the rule"); + }else{ + match = false; + QPID_LOG(debug, "ACL: name '" << name << "' didn't match with name '" + << pMItr->second << "' given in the rule"); + } + } else if (params) { //match pMItr against params + propertyMapItr paramItr = params->find(pMItr->first); + if (paramItr == params->end()) { + match = false; + QPID_LOG(debug, "ACL: the given parameter map in lookup doesn't contain the property '" + << AclHelper::getPropertyStr(pMItr->first) << "'"); + }else if ( pMItr->first == acl::PROP_MAXQUEUECOUNT || pMItr->first == acl::PROP_MAXQUEUESIZE ) { + if ( pMItr->first == paramItr->first ) { + uint64_t aclMax = boost::lexical_cast<uint64_t>(pMItr->second); + uint64_t paramMax = boost::lexical_cast<uint64_t>(paramItr->second); + QPID_LOG(debug, "ACL: Numeric comparison for property " << + AclHelper::getPropertyStr(paramItr->first) << + " (value given in lookup = " << + boost::lexical_cast<std::string>(paramItr->second) << + ", value give in rule = " << + boost::lexical_cast<std::string>(pMItr->second) << " )"); + if (( aclMax ) && ( paramMax == 0 || paramMax > aclMax)){ + match = decisionMode == qpid::acl::ALLOW ; + QPID_LOG(debug, "ACL: Limit exceeded and match=" << + (match ? "true": "false") << + " as decision mode is " << AclHelper::getAclResultStr(decisionMode)); + } + } + }else if (matchProp(pMItr->second, paramItr->second)) { + QPID_LOG(debug, "ACL: the pair(" + << AclHelper::getPropertyStr(paramItr->first) << "," << paramItr->second + << ") given in lookup matched the pair(" + << AclHelper::getPropertyStr(pMItr->first) << "," << pMItr->second << ") given in the rule"); + } else { + QPID_LOG(debug, "ACL: the pair(" + << AclHelper::getPropertyStr(paramItr->first) << "," << paramItr->second + << ") given in lookup doesn't match the pair(" + << AclHelper::getPropertyStr(pMItr->first) << "," << pMItr->second << ") given in the rule"); + match = false; + + } + } + } + if (match) + { + aclresult = getACLResult(i->logOnly, i->log); + QPID_LOG(debug,"Successful match, the decision is:" << AclHelper::getAclResultStr(aclresult)); + return aclresult; + } + } + } + } + + QPID_LOG(debug,"No successful match, defaulting to the decision mode " << AclHelper::getAclResultStr(aclresult)); + return aclresult; +} + +AclResult AclData::lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& /*Exchange*/ name, const std::string& RoutingKey) +{ + + QPID_LOG(debug, "ACL: Lookup for id:" << id << " action:" << AclHelper::getActionStr((Action) action) + << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType) << " exchange name:" << name + << " with routing key " << RoutingKey); + + AclResult aclresult = decisionMode; + + if (actionList[action] && actionList[action][objType]){ + AclData::actObjItr itrRule = actionList[action][objType]->find(id); + + if (itrRule == actionList[action][objType]->end()) + itrRule = actionList[action][objType]->find("*"); + + if (itrRule != actionList[action][objType]->end() ) { + + QPID_LOG(debug, "ACL: checking the following rules for : " << itrRule->first ); + + //loop the vector + for (ruleSetItr i=itrRule->second.begin(); i<itrRule->second.end(); i++) { + QPID_LOG(debug, "ACL: checking rule " << i->toString()); + + // loop the names looking for match + bool match =true; + for (propertyMapItr pMItr = i->props.begin(); (pMItr != i->props.end()) && match; pMItr++) + { + //match name is exists first + if (pMItr->first == acl::PROP_NAME){ + if (matchProp(pMItr->second, name)){ + QPID_LOG(debug, "ACL: name '" << name << "' matched with name '" + << pMItr->second << "' given in the rule"); + + }else{ + match= false; + QPID_LOG(debug, "ACL: name '" << name << "' didn't match with name '" + << pMItr->second << "' given in the rule"); + } + }else if (pMItr->first == acl::PROP_ROUTINGKEY){ + if (matchProp(pMItr->second, RoutingKey)){ + QPID_LOG(debug, "ACL: name '" << name << "' matched with routing_key '" + << pMItr->second << "' given in the rule"); + }else{ + match= false; + QPID_LOG(debug, "ACL: name '" << name << "' didn't match with routing_key '" + << pMItr->second << "' given in the rule"); + } + } + } + if (match){ + aclresult = getACLResult(i->logOnly, i->log); + QPID_LOG(debug,"Successful match, the decision is:" << AclHelper::getAclResultStr(aclresult)); + return aclresult; + } + } + } + } + QPID_LOG(debug,"No successful match, defaulting to the decision mode " << AclHelper::getAclResultStr(aclresult)); + return aclresult; + +} + + +AclResult AclData::getACLResult(bool logOnly, bool log) +{ + switch (decisionMode) + { + case qpid::acl::ALLOWLOG: + case qpid::acl::ALLOW: + if (logOnly) return qpid::acl::ALLOWLOG; + if (log) + return qpid::acl::DENYLOG; + else + return qpid::acl::DENY; + + + case qpid::acl::DENYLOG: + case qpid::acl::DENY: + if (logOnly) return qpid::acl::DENYLOG; + if (log) + return qpid::acl::ALLOWLOG; + else + return qpid::acl::ALLOW; + } + + QPID_LOG(error, "ACL Decision Failed, setting DENY"); + return qpid::acl::DENY; +} + +AclData::~AclData() +{ + clear(); +} + +}} diff --git a/cpp/src/qpid/acl/AclData.h b/cpp/src/qpid/acl/AclData.h new file mode 100644 index 0000000000..a63afab12b --- /dev/null +++ b/cpp/src/qpid/acl/AclData.h @@ -0,0 +1,83 @@ +#ifndef QPID_ACL_ACLDATA_H +#define QPID_ACL_ACLDATA_H + + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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/AclModule.h" +#include <vector> +#include <sstream> + +namespace qpid { +namespace acl { + +class AclData { + + +public: + + typedef std::map<qpid::acl::Property, std::string> propertyMap; + typedef propertyMap::const_iterator propertyMapItr; + struct rule { + + bool log; + bool logOnly; // this is a rule is to log only + + // key value map + //?? + propertyMap props; + + + rule (propertyMap& p):log(false),logOnly(false),props(p) {}; + + std::string toString () const { + std::ostringstream ruleStr; + ruleStr << "[log=" << log << ", logOnly=" << logOnly << " props{"; + for (propertyMapItr pMItr = props.begin(); pMItr != props.end(); pMItr++) { + ruleStr << " " << AclHelper::getPropertyStr((Property) pMItr-> first) << "=" << pMItr->second; + } + ruleStr << " }]"; + return ruleStr.str(); + } + }; + typedef std::vector<rule> ruleSet; + typedef ruleSet::const_iterator ruleSetItr; + typedef std::map<std::string, ruleSet > actionObject; // user + typedef actionObject::iterator actObjItr; + typedef actionObject* aclAction; + + // Action*[] -> Object*[] -> map<user -> set<Rule> > + aclAction* actionList[qpid::acl::ACTIONSIZE]; + qpid::acl::AclResult decisionMode; // determines if the rule set is an deny or accept basis. + bool transferAcl; + + AclResult lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params=0); + AclResult lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& ExchangeName, const std::string& RoutingKey); + AclResult getACLResult(bool logOnly, bool log); + + bool matchProp(const std::string & src, const std::string& src1); + void clear (); + + AclData(); + virtual ~AclData(); +}; + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACLDATA_H diff --git a/cpp/src/qpid/acl/AclPlugin.cpp b/cpp/src/qpid/acl/AclPlugin.cpp index a025354f13..e4d721ea44 100644 --- a/cpp/src/qpid/acl/AclPlugin.cpp +++ b/cpp/src/qpid/acl/AclPlugin.cpp @@ -21,9 +21,9 @@ #include "qpid/broker/Broker.h" #include "qpid/Plugin.h" #include "qpid/Options.h" -#include "qpid/shared_ptr.h" #include "qpid/log/Statement.h" +#include <boost/shared_ptr.hpp> #include <boost/utility/in_place_factory.hpp> namespace qpid { @@ -36,13 +36,11 @@ using namespace std; * New boost allows a shared_ptr but that's not compatible with old boost. */ struct AclOptions : public Options { - AclValues& values; + AclValues& values; AclOptions(AclValues& v) : Options("ACL Options"), values(v) { addOptions() - ("no-enforce-acl", optValue(values.noEnforce), "Do not enforce ACL") - ("acl-file", optValue(values.aclFile, "FILE"), "The policy file to load from, loaded from data dir") - ; + ("acl-file", optValue(values.aclFile, "FILE"), "The policy file to load from, loaded from data dir"); } }; @@ -51,20 +49,25 @@ struct AclPlugin : public Plugin { AclValues values; AclOptions options; boost::intrusive_ptr<Acl> acl; - + AclPlugin() : options(values) {} Options* getOptions() { return &options; } void init(broker::Broker& b) { - if (values.noEnforce){ - QPID_LOG(info, "ACL Disabled, no ACL checking being done."); - return; - } - if (acl) throw Exception("ACL plugin cannot be initialized twice in one process."); - std::ostringstream oss; - oss << b.getDataDir().getPath() << "/" << values.aclFile; - values.aclFile = oss.str(); + if (values.aclFile.empty()){ + QPID_LOG(info, "Policy file not specified. ACL Disabled, no ACL checking being done!"); + return; + } + + if (acl) throw Exception("ACL plugin cannot be initialized twice in one process."); + + if (values.aclFile.at(0) != '/' && !b.getDataDir().getPath().empty()) { + std::ostringstream oss; + oss << b.getDataDir().getPath() << "/" << values.aclFile; + values.aclFile = oss.str(); + } + acl = new Acl(values, b); b.setAcl(acl.get()); b.addFinalizer(boost::bind(&AclPlugin::shutdown, this)); @@ -89,5 +92,5 @@ static AclPlugin instance; // Static initialization. // For test purposes. boost::intrusive_ptr<Acl> getGlobalAcl() { return instance.acl; } - + }} // namespace qpid::acl diff --git a/cpp/src/qpid/acl/AclReader.cpp b/cpp/src/qpid/acl/AclReader.cpp index 0a9517bc76..8f419a6f50 100644 --- a/cpp/src/qpid/acl/AclReader.cpp +++ b/cpp/src/qpid/acl/AclReader.cpp @@ -18,47 +18,306 @@ #include "qpid/acl/AclReader.h" +#include <cctype> #include <cstring> -//#include <iostream> // debug #include <fstream> +#include <sstream> +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" + +#include <iomanip> // degug +#include <iostream> // debug + +#define ACL_FORMAT_ERR_LOG_PREFIX "ACL format error: " << fileName << ":" << lineNumber << ": " namespace qpid { namespace acl { +AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups) : res(r), actionAll(true), objStatus(NONE) { + processName(n, groups); +} +AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a) : res(r), actionAll(false), action(a), objStatus(NONE) { + processName(n, groups); +} + +void AclReader::aclRule::setObjectType(const ObjectType o) { + objStatus = VALUE; + object = o; +} + +void AclReader::aclRule::setObjectTypeAll() { + objStatus = ALL; +} + +bool AclReader::aclRule::addProperty(const Property p, const std::string v) { + return props.insert(propNvPair(p, v)).second; +} + +bool AclReader::aclRule::validate(const AclHelper::objectMapPtr& /*validationMap*/) { + // TODO - invalid rules won't ever be called in real life... + return true; +} + +// Debug aid +std::string AclReader::aclRule::toString() { + std::ostringstream oss; + oss << AclHelper::getAclResultStr(res) << " ["; + for (nsCitr itr = names.begin(); itr != names.end(); itr++) { + if (itr != names.begin()) oss << ", "; + oss << *itr; + } + oss << "]"; + if (actionAll) { + oss << " *"; + } else { + oss << " " << AclHelper::getActionStr(action); + } + if (objStatus == ALL) { + oss << " *"; + } else if (objStatus == VALUE) { + oss << " " << AclHelper::getObjectTypeStr(object); + } + for (pmCitr i=props.begin(); i!=props.end(); i++) { + oss << " " << AclHelper::getPropertyStr(i->first) << "=" << i->second; + } + return oss.str(); +} + +void AclReader::loadDecisionData(boost::shared_ptr<AclData> d) { + d->clear(); + QPID_LOG(debug, "ACL Load Rules"); + int cnt = rules.size(); + bool foundmode = false; + + for (rlCitr i = rules.end() - 1; cnt; i--, cnt--) { + QPID_LOG(debug, "ACL Processing " << std::setfill(' ') << std::setw(2) + << cnt << " " << (*i)->toString()); + + if (!foundmode && (*i)->actionAll && (*i)->names.size() == 1 + && (*((*i)->names.begin())).compare("*") == 0) { + d->decisionMode = (*i)->res; + QPID_LOG(debug, "ACL FoundMode " << AclHelper::getAclResultStr( + d->decisionMode)); + foundmode = true; + } else { + AclData::rule rule((*i)->props); + bool addrule = true; + + switch ((*i)->res) { + case qpid::acl::ALLOWLOG: + rule.log = true; + if (d->decisionMode == qpid::acl::ALLOW || d->decisionMode + == qpid::acl::ALLOWLOG) + rule.logOnly = true; + break; + case qpid::acl::ALLOW: + if (d->decisionMode == qpid::acl::ALLOW || d->decisionMode + == qpid::acl::ALLOWLOG) + addrule = false; + break; + case qpid::acl::DENYLOG: + rule.log = true; + if (d->decisionMode == qpid::acl::DENY || d->decisionMode + == qpid::acl::DENYLOG) + rule.logOnly = true; + break; + case qpid::acl::DENY: + if (d->decisionMode == qpid::acl::DENY || d->decisionMode + == qpid::acl::DENYLOG) + addrule = false; + break; + default: + throw Exception("Invalid ACL Result loading rules."); + } + + // Action -> Object -> map<user -> set<Rule> > + if (addrule) { + std::ostringstream actionstr; + for (int acnt = ((*i)->actionAll ? 0 : (*i)->action); acnt + < acl::ACTIONSIZE; (*i)->actionAll ? acnt++ : acnt + = acl::ACTIONSIZE) { + + if (acnt == acl::ACT_PUBLISH) + d->transferAcl = true; // we have transfer ACL + + actionstr << AclHelper::getActionStr((Action) acnt) << ","; + + //find the Action, create if not exist + if (d->actionList[acnt] == NULL) { + d->actionList[acnt] + = new AclData::aclAction[qpid::acl::OBJECTSIZE]; + for (int j = 0; j < qpid::acl::OBJECTSIZE; j++) + d->actionList[acnt][j] = NULL; + } + + // optimize this loop to limit to valid options only!! + for (int ocnt = ((*i)->objStatus != aclRule::VALUE ? 0 + : (*i)->object); ocnt < acl::OBJECTSIZE; (*i)->objStatus + != aclRule::VALUE ? ocnt++ : ocnt = acl::OBJECTSIZE) { + + //find the Object, create if not exist + if (d->actionList[acnt][ocnt] == NULL) + d->actionList[acnt][ocnt] + = new AclData::actionObject; + + // add users and Rule to object set + bool allNames = false; + // check to see if names.begin is '*' + if ((*(*i)->names.begin()).compare("*") == 0) + allNames = true; + + for (nsCitr itr = (allNames ? names.begin() + : (*i)->names.begin()); itr + != (allNames ? names.end() : (*i)->names.end()); itr++) { + + AclData::actObjItr itrRule = + d->actionList[acnt][ocnt]->find(*itr); + + if (itrRule == d->actionList[acnt][ocnt]->end()) { + AclData::ruleSet rSet; + rSet.push_back(rule); + d->actionList[acnt][ocnt]->insert(make_pair( + std::string(*itr), rSet)); + } else { + + // TODO add code to check for dead rules + // allow peter create queue name=tmp <-- dead rule!! + // allow peter create queue + + itrRule->second.push_back(rule); + } + } + + } + } + + std::ostringstream objstr; + for (int ocnt = ((*i)->objStatus != aclRule::VALUE ? 0 : (*i)->object); ocnt < acl::OBJECTSIZE; + (*i)->objStatus != aclRule::VALUE ? ocnt++ : ocnt = acl::OBJECTSIZE) { + objstr << AclHelper::getObjectTypeStr((ObjectType) ocnt) << ","; + } + + bool allNames = ((*(*i)->names.begin()).compare("*") == 0); + std::ostringstream userstr; + for (nsCitr itr = (allNames ? names.begin() : (*i)->names.begin()); + itr != (allNames ? names.end() : (*i)->names.end()); itr++) { + userstr << *itr << ","; + } + + QPID_LOG(debug,"ACL: Adding actions {" << actionstr.str().substr(0,actionstr.str().length()-1) + << "} to objects {" << objstr.str().substr(0,objstr.str().length()-1) + << "} with props " << AclHelper::propertyMapToString(&rule.props) + << " for users {" << userstr.str().substr(0,userstr.str().length()-1) << "}" ); + } else { + QPID_LOG(debug, "ACL Skipping based on Mode:" + << AclHelper::getAclResultStr(d->decisionMode)); + } + } + + } + +} + + +void AclReader::aclRule::processName(const std::string& name, const groupMap& groups) { + if (name.compare("all") == 0) { + names.insert("*"); + } else { + gmCitr itr = groups.find(name); + if (itr == groups.end()) { + names.insert(name); + } else { + names.insert(itr->second->begin(), itr->second->end()); + } + } +} + +AclReader::AclReader() : lineNumber(0), contFlag(false), validationMap(new AclHelper::objectMap) { + AclHelper::loadValidationMap(validationMap); + names.insert("*"); +} + +AclReader::~AclReader() {} + +std::string AclReader::getError() { + return errorStream.str(); +} + int AclReader::read(const std::string& fn, boost::shared_ptr<AclData> d) { -//std::cout << "AclReader::read(" << fn << ")" << std::endl << std::flush; + fileName = fn; + lineNumber = 0; char buff[1024]; std::ifstream ifs(fn.c_str(), std::ios_base::in); if (!ifs.good()) { - // error/exception - file open error + errorStream << "Unable to open ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F"); return -1; } try { + bool err = false; while (ifs.good()) { ifs.getline(buff, 1024); - processLine(buff, d); + lineNumber++; + if (std::strlen(buff) > 0 && buff[0] != '#') // Ignore blank lines and comments + err |= !processLine(buff); } + if (!ifs.eof()) + { + errorStream << "Unable to read ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F"); + ifs.close(); + return -2; + } + ifs.close(); + if (err) return -3; + QPID_LOG(notice, "Read ACL file \"" << fn << "\""); + } catch (const std::exception& e) { + errorStream << "Unable to read ACL file \"" << fn << "\": " << e.what(); ifs.close(); + return -4; } catch (...) { - // error/exception - file read/processing error + errorStream << "Unable to read ACL file \"" << fn << "\": Unknown exception"; ifs.close(); - return -2; + return -5; } + printNames(); + printRules(); + loadDecisionData(d); + return 0; } +bool AclReader::processLine(char* line) { + bool ret = false; + std::vector<std::string> toks; + + // Check for continuation + char* contCharPtr = std::strrchr(line, '\\'); + bool cont = contCharPtr != 0; + if (cont) *contCharPtr = 0; -void AclReader::processLine(char* line, boost::shared_ptr<AclData> /*d*/) { - std::vector<std::string> toks; - int numToks = tokenizeLine(line, toks); - for (int i=0; i<numToks; i++) { -// DO MAGIC STUFF HERE -//std::cout << "tok " << i << ": " << toks[i] << std::endl << std::flush; - } + int numToks = tokenize(line, toks); + if (numToks && (toks[0].compare("group") == 0 || contFlag)) { + ret = processGroupLine(toks, cont); + } else if (numToks && toks[0].compare("acl") == 0) { + ret = processAclLine(toks); + } else { + // Check for whitespace only line, ignore these + bool ws = true; + for (unsigned i=0; i<std::strlen(line) && ws; i++) { + if (!std::isspace(line[i])) ws = false; + } + if (ws) { + ret = true; + } else { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Non-continuation line must start with \"group\" or \"acl\"."; + ret = false; + } + } + contFlag = cont; + return ret; } -int AclReader::tokenizeLine(char* line, std::vector<std::string>& toks) { - const char* tokChars = " \t\n"; +int AclReader::tokenize(char* line, std::vector<std::string>& toks) { + const char* tokChars = " \t\n\f\v\r"; int cnt = 0; char* cp = std::strtok(line, tokChars); while (cp != 0) { @@ -69,5 +328,223 @@ int AclReader::tokenizeLine(char* line, std::vector<std::string>& toks) { return cnt; } +// 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) { + const unsigned toksSize = toks.size(); + if (contFlag) { + gmCitr citr = groups.find(groupName); + for (unsigned i = 0; i < toksSize; i++) { + if (!checkName(toks[i])) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Name \"" << toks[i] << "\" contains illegal characters."; + return false; + } + if (!isValidUserName(toks[i])) return false; + addName(toks[i], citr->second); + } + } else { + if (toksSize < (cont ? 2 : 3)) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Insufficient tokens for group definition."; + return false; + } + if (!checkName(toks[1])) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Group name \"" << toks[1] << "\" contains illegal characters."; + return false; + } + gmCitr citr = addGroup(toks[1]); + if (citr == groups.end()) return false; + for (unsigned i = 2; i < toksSize; i++) { + if (!checkName(toks[i])) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Name \"" << toks[i] << "\" contains illegal characters."; + return false; + } + if (!isValidUserName(toks[i])) return false; + addName(toks[i], citr->second); + } + } + return true; +} + +// Return true if sucessfully added group +AclReader::gmCitr AclReader::addGroup(const std::string& newGroupName) { + gmCitr citr = groups.find(newGroupName); + if (citr != groups.end()) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Duplicate group name \"" << newGroupName << "\"."; + return groups.end(); + } + groupPair p(newGroupName, nameSetPtr(new nameSet)); + gmRes res = groups.insert(p); + assert(res.second); + groupName = newGroupName; + return res.first; +} + +void AclReader::addName(const std::string& name, nameSetPtr groupNameSet) { + gmCitr citr = groups.find(name); + if (citr != groups.end() && citr->first != name){ + // This is a previously defined group: add all the names in that group to this group + groupNameSet->insert(citr->second->begin(), citr->second->end()); + } else { + // Not a known group name + groupNameSet->insert(name); + addName(name); + } +} + +void AclReader::addName(const std::string& name) { + names.insert(name); +} + +// Debug aid +void AclReader::printNames() const { + QPID_LOG(debug, "Group list: " << groups.size() << " groups found:" ); + std::string tmp; + for (gmCitr i=groups.begin(); i!= groups.end(); i++) { + tmp += " \""; + tmp += i->first; + tmp += "\":"; + for (nsCitr j=i->second->begin(); j!=i->second->end(); j++) { + tmp += " "; + tmp += *j; + } + QPID_LOG(debug, tmp); + tmp.clear(); + } + QPID_LOG(debug, "Name list: " << names.size() << " names found:" ); + tmp.clear(); + for (nsCitr k=names.begin(); k!=names.end(); k++) { + tmp += " "; + tmp += *k; + } + QPID_LOG(debug, tmp); +} + +bool AclReader::processAclLine(tokList& toks) { + const unsigned toksSize = toks.size(); + if (toksSize < 4) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Insufficient tokens for acl definition."; + return false; + } + + AclResult res; + try { + res = AclHelper::getAclResult(toks[1]); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Unknown ACL permission \"" << toks[1] << "\"."; + return false; + } + + bool actionAllFlag = toks[3].compare("all") == 0; + bool userAllFlag = toks[2].compare("all") == 0; + Action action; + if (actionAllFlag) { + + if (userAllFlag && toksSize > 4) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Tokens found after action \"all\"."; + return false; + } + action = ACT_CONSUME; // dummy; compiler must initialize action for this code path + } else { + try { + action = AclHelper::getAction(toks[3]); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Unknown action \"" << toks[3] << "\"."; + return false; + } + } + + // Create rule obj; then add object (if any) and properties (if any) + aclRulePtr rule; + if (actionAllFlag) { + rule.reset(new aclRule(res, toks[2], groups)); + } else { + rule.reset(new aclRule(res, toks[2], groups, action)); + } + + if (toksSize >= 5) { // object name-value pair + if (toks[4].compare("all") == 0) { + rule->setObjectTypeAll(); + } else { + try { + rule->setObjectType(AclHelper::getObjectType(toks[4])); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Unknown object \"" << toks[4] << "\"."; + return false; + } + } + } + + if (toksSize >= 6) { // property name-value pair(s) + for (unsigned i=5; i<toksSize; i++) { + nvPair propNvp = splitNameValuePair(toks[i]); + if (propNvp.second.size() == 0) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Badly formed property name-value pair \"" << propNvp.first << "\". (Must be name=value)"; + return false; + } + Property prop; + try { + prop = AclHelper::getProperty(propNvp.first); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Unknown property \"" << propNvp.first << "\"."; + return false; + } + rule->addProperty(prop, propNvp.second); + } + } + // Check if name (toks[2]) is group; if not, add as name of individual + if (toks[2].compare("all") != 0) { + if (groups.find(toks[2]) == groups.end()) { + addName(toks[2]); + } + } + + // If rule validates, add to rule list + if (!rule->validate(validationMap)) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Invalid object/action/property combination."; + return false; + } + rules.push_back(rule); + + return true; +} + +// Debug aid +void AclReader::printRules() const { + QPID_LOG(debug, "Rule list: " << rules.size() << " ACL rules found:"); + int cnt = 0; + for (rlCitr i=rules.begin(); i<rules.end(); i++,cnt++) { + QPID_LOG(debug, " " << std::setfill(' ') << std::setw(2) << cnt << " " << (*i)->toString()); + } +} + +// Static function +// Return true if the name is well-formed (ie contains legal characters) +bool AclReader::checkName(const std::string& name) { + for (unsigned i=0; i<name.size(); i++) { + const char ch = name.at(i); + if (!std::isalnum(ch) && ch != '-' && ch != '_' && ch != '@') return false; + } + return true; +} + +// Static function +// Split name-value pair around '=' char of the form "name=value" +AclReader::nvPair AclReader::splitNameValuePair(const std::string& nvpString) { + std::size_t pos = nvpString.find("="); + if (pos == std::string::npos || pos == nvpString.size() - 1) { + return nvPair(nvpString, ""); + } + return nvPair(nvpString.substr(0, pos), nvpString.substr(pos+1)); +} + +// Returns true if a username has the name@realm format +bool AclReader::isValidUserName(const std::string& name){ + size_t pos = name.find('@'); + if ( pos == std::string::npos || pos == name.length() -1){ + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Username '" << name << "' must contain a realm"; + return false; + } + return true; +} }} // namespace qpid::acl diff --git a/cpp/src/qpid/acl/AclReader.h b/cpp/src/qpid/acl/AclReader.h index 783b70d98a..dccb450192 100644 --- a/cpp/src/qpid/acl/AclReader.h +++ b/cpp/src/qpid/acl/AclReader.h @@ -21,25 +21,98 @@ */ #include <boost/shared_ptr.hpp> +#include <map> +#include <set> #include <string> #include <vector> +#include <sstream> +#include "qpid/acl/AclData.h" +#include "qpid/broker/AclModule.h" namespace qpid { namespace acl { -struct AclData { - bool lc; // Line continue flag - AclData() : lc(false) {} -}; - class AclReader { -public: - static int read(const std::string& fn, boost::shared_ptr<AclData> d); -private: - static void processLine(char* line, boost::shared_ptr<AclData> d); - static int tokenizeLine(char* line, std::vector<std::string>& toks); + typedef std::set<std::string> nameSet; + typedef nameSet::const_iterator nsCitr; + typedef boost::shared_ptr<nameSet> nameSetPtr; + + typedef std::pair<std::string, nameSetPtr> groupPair; + typedef std::map<std::string, nameSetPtr> groupMap; + typedef groupMap::const_iterator gmCitr; + typedef std::pair<gmCitr, bool> gmRes; + + typedef std::pair<Property, std::string> propNvPair; + typedef std::map<Property, std::string> propMap; + typedef propMap::const_iterator pmCitr; + + class aclRule { + public: + enum objectStatus {NONE, VALUE, ALL}; + AclResult res; + nameSet names; + bool actionAll; // True if action is set to keyword "all" + Action action; // Ignored if action is set to keyword "all" + objectStatus objStatus; + ObjectType object; // Ignored for all status values except VALUE + propMap props; + public: + aclRule(const AclResult r, const std::string n, const groupMap& groups); // action = "all" + aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a); + void setObjectType(const ObjectType o); + void setObjectTypeAll(); + bool addProperty(const Property p, const std::string v); + bool validate(const AclHelper::objectMapPtr& validationMap); + std::string toString(); // debug aid + private: + void processName(const std::string& name, const groupMap& groups); + }; + typedef boost::shared_ptr<aclRule> aclRulePtr; + typedef std::vector<aclRulePtr> ruleList; + typedef ruleList::const_iterator rlCitr; + + typedef std::vector<std::string> tokList; + typedef tokList::const_iterator tlCitr; + + typedef std::set<std::string> keywordSet; + typedef keywordSet::const_iterator ksCitr; + typedef std::pair<std::string, std::string> nvPair; // Name-Value pair + + std::string fileName; + int lineNumber; + bool contFlag; + std::string groupName; + nameSet names; + groupMap groups; + ruleList rules; + AclHelper::objectMapPtr validationMap; + std::ostringstream errorStream; + + public: + AclReader(); + virtual ~AclReader(); + int read(const std::string& fn, boost::shared_ptr<AclData> d); + std::string getError(); + + private: + bool processLine(char* line); + void loadDecisionData( boost::shared_ptr<AclData> d); + int tokenize(char* line, tokList& toks); + + bool processGroupLine(tokList& toks, const bool cont); + gmCitr addGroup(const std::string& groupName); + void addName(const std::string& name, nameSetPtr groupNameSet); + void addName(const std::string& name); + void printNames() const; // debug aid + + bool processAclLine(tokList& toks); + void printRules() const; // debug aid + bool isValidUserName(const std::string& name); + + static bool checkName(const std::string& name); + static nvPair splitNameValuePair(const std::string& nvpString); }; - + }} // namespace qpid::acl #endif // QPID_ACL_ACLREADER_H diff --git a/cpp/src/qpid/acl/management-schema.xml b/cpp/src/qpid/acl/management-schema.xml new file mode 100644 index 0000000000..f4637253d0 --- /dev/null +++ b/cpp/src/qpid/acl/management-schema.xml @@ -0,0 +1,44 @@ +<schema package="org.apache.qpid.acl"> + +<!-- + * Copyright (c) 2008 The Apache Software Foundation + * + * Licensed 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="Acl"> + <property name="brokerRef" type="objId" references="org.apache.qpid.broker:Broker" access="RO" index="y" parentRef="y"/> + <property name="policyFile" type="sstr" access="RO" desc="Name of the policy file"/> + <property name="enforcingAcl" type="bool" access="RO" desc="Currently Enforcing ACL"/> + <property name="transferAcl" type="bool" access="RO" desc="Any transfer ACL rules in force"/> + <property name="lastAclLoad" type="absTime" access="RO" desc="Timestamp of last successful load of ACL"/> + <statistic name="aclDenyCount" type="count64" unit="request" desc="Number of ACL requests denied"/> + + <method name="reloadACLFile" desc="Reload the ACL file"/> + </class> + + <eventArguments> + <arg name="action" type="sstr"/> + <arg name="arguments" type="map"/> + <arg name="objectName" type="sstr"/> + <arg name="objectType" type="sstr"/> + <arg name="reason" type="sstr"/> + <arg name="userId" 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="fileLoaded" sev="inform" args="userId"/> + <event name="fileLoadFailed" sev="error" args="userId, reason"/> + +</schema> diff --git a/cpp/src/qpid/agent/ManagementAgent.h b/cpp/src/qpid/agent/ManagementAgent.h deleted file mode 100644 index e7379e6c94..0000000000 --- a/cpp/src/qpid/agent/ManagementAgent.h +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef _qpid_agent_ManagementAgent_ -#define _qpid_agent_ManagementAgent_ - -// -// 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/ManagementObject.h" -#include "qpid/management/Manageable.h" -#include "qpid/sys/Mutex.h" - -namespace qpid { -namespace management { - -class ManagementAgent -{ - public: - - class Singleton { - public: - Singleton(bool disableManagement = false); - ~Singleton(); - static ManagementAgent* getInstance(); - private: - static sys::Mutex lock; - static bool disabled; - static int refCount; - static ManagementAgent* agent; - }; - - ManagementAgent () {} - virtual ~ManagementAgent () {} - - virtual int getMaxThreads() = 0; - - // Connect to a management broker - // - // brokerHost - Hostname or IP address (dotted-quad) of broker. - // - // brokerPort - TCP port of broker. - // - // intervalSeconds - The interval (in seconds) that this agent shall use - // between broadcast updates to the broker. - // - // useExternalThread - If true, the thread of control used for callbacks - // must be supplied by the user of the object (via the - // pollCallbacks method). - // - // If false, callbacks shall be invoked on the management - // agent's thread. In this case, the callback implementations - // MUST be thread safe. - // - virtual void init (std::string brokerHost = "localhost", - uint16_t brokerPort = 5672, - uint16_t intervalSeconds = 10, - bool useExternalThread = false) = 0; - - // Register a schema with the management agent. This is normally called by the - // package initializer generated by the management code generator. - // - virtual void - RegisterClass (std::string packageName, - std::string className, - uint8_t* md5Sum, - management::ManagementObject::writeSchemaCall_t schemaCall) = 0; - - // Add a management object to the agent. Once added, this object shall be visible - // in the greater management context. - // - // Please note that ManagementObject instances are not explicitly deleted from - // the management agent. When the core object represented by a management object - // is deleted, the "resourceDestroy" method on the management object must be called. - // It will then be reclaimed in due course by the management agent. - // - // Once a ManagementObject instance is added to the agent, the agent then owns the - // instance. The caller MUST NOT free the resources of the instance at any time. - // When it is no longer needed, invoke its "resourceDestroy" method and discard the - // pointer. This allows the management agent to report the deletion of the object - // in an orderly way. - // - virtual uint64_t addObject (ManagementObject* objectPtr, - uint32_t persistId = 0, - uint32_t persistBank = 4) = 0; - - // If "useExternalThread" was set to true in init, this method must - // be called to provide a thread for any pending method calls that have arrived. - // The method calls for ManagementObject instances shall be invoked synchronously - // during the execution of this method. - // - // callLimit may optionally be used to limit the number of callbacks invoked. - // if 0, no limit is imposed. - // - // The return value is the number of callbacks that remain queued after this - // call is complete. It can be used to determine whether or not further calls - // to pollCallbacks are necessary to clear the backlog. If callLimit is zero, - // the return value will also be zero. - // - virtual uint32_t pollCallbacks (uint32_t callLimit = 0) = 0; - - // If "useExternalThread" was set to true in the constructor, this method provides - // a standard file descriptor that can be used in a select statement to signal that - // there are method callbacks ready (i.e. that "pollCallbacks" will result in at - // least one method call). When this fd is ready-for-read, pollCallbacks may be - // invoked. Calling pollCallbacks shall reset the ready-to-read state of the fd. - // - virtual int getSignalFd (void) = 0; - -}; - -}} - -#endif /*!_qpid_agent_ManagementAgent_*/ diff --git a/cpp/src/qpid/agent/ManagementAgentImpl.cpp b/cpp/src/qpid/agent/ManagementAgentImpl.cpp index ebdc71e3b1..f84e158154 100644 --- a/cpp/src/qpid/agent/ManagementAgentImpl.cpp +++ b/cpp/src/qpid/agent/ManagementAgentImpl.cpp @@ -20,16 +20,25 @@ #include "qpid/management/Manageable.h" #include "qpid/management/ManagementObject.h" -#include "ManagementAgentImpl.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/PipeHandle.h" +#include "qpid/agent/ManagementAgentImpl.h" #include <list> -#include <unistd.h> #include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <iostream> +#include <fstream> + using namespace qpid::client; using namespace qpid::framing; using namespace qpid::management; using namespace qpid::sys; +using namespace std; using std::stringstream; +using std::ofstream; +using std::ifstream; using std::string; using std::cout; using std::endl; @@ -66,128 +75,274 @@ ManagementAgent* ManagementAgent::Singleton::getInstance() return agent; } +const string ManagementAgentImpl::storeMagicNumber("MA02"); + ManagementAgentImpl::ManagementAgentImpl() : - clientWasAdded(true), objIdPrefix(0), bgThread(*this), thread(bgThread), startupWait(false) + interval(10), extThread(false), pipeHandle(0), + initialized(false), connected(false), lastFailure("never connected"), + clientWasAdded(true), requestedBrokerBank(0), requestedAgentBank(0), + assignedBrokerBank(0), assignedAgentBank(0), bootSequence(0), + connThreadBody(*this), connThread(connThreadBody), + pubThreadBody(*this), pubThread(pubThreadBody) { - // TODO: Establish system ID } -void ManagementAgentImpl::init(std::string brokerHost, - uint16_t brokerPort, - uint16_t intervalSeconds, - bool useExternalThread) +ManagementAgentImpl::~ManagementAgentImpl() { + // shutdown & cleanup all threads + connThreadBody.close(); + pubThreadBody.close(); + + connThread.join(); + pubThread.join(); + + // Release the memory associated with stored management objects. { Mutex::ScopedLock lock(agentLock); - startupWait = true; + + moveNewObjectsLH(); + for (ManagementObjectMap::iterator iter = managementObjects.begin (); + iter != managementObjects.end (); + iter++) { + ManagementObject* object = iter->second; + delete object; + } + managementObjects.clear(); + } + if (pipeHandle) { + delete pipeHandle; + pipeHandle = 0; } +} +void ManagementAgentImpl::init(const string& brokerHost, + uint16_t brokerPort, + uint16_t intervalSeconds, + bool useExternalThread, + const string& _storeFile, + const string& uid, + const string& pwd, + const string& mech, + const string& proto) +{ + client::ConnectionSettings settings; + settings.protocol = proto; + settings.host = brokerHost; + settings.port = brokerPort; + settings.username = uid; + settings.password = pwd; + settings.mechanism = mech; + init(settings, intervalSeconds, useExternalThread, _storeFile); +} + +void ManagementAgentImpl::init(const qpid::client::ConnectionSettings& settings, + uint16_t intervalSeconds, + bool useExternalThread, + const std::string& _storeFile) +{ interval = intervalSeconds; extThread = useExternalThread; + storeFile = _storeFile; nextObjectId = 1; - sessionId.generate(); - queueName << "qmfagent-" << sessionId; - string dest = "qmfagent"; + QPID_LOG(info, "QMF Agent Initialized: broker=" << settings.host << ":" << settings.port << + " interval=" << intervalSeconds << " storeFile=" << _storeFile); + connectionSettings = settings; - connection.open(brokerHost.c_str(), brokerPort); - session = connection.newSession (queueName.str()); - dispatcher = new client::Dispatcher(session); + // TODO: Abstract the socket calls for portability + // qpid::sys::PipeHandle to create a pipe + if (extThread) { + pipeHandle = new PipeHandle(true); + } + retrieveData(); + bootSequence++; + if ((bootSequence & 0xF000) != 0) + bootSequence = 1; + storeData(true); - session.queueDeclare (arg::queue=queueName.str()); - session.exchangeBind (arg::exchange="amq.direct", arg::queue=queueName.str(), - arg::bindingKey=queueName.str()); - session.messageSubscribe (arg::queue=queueName.str(), - arg::destination=dest); - session.messageFlow (arg::destination=dest, arg::unit=0, arg::value=0xFFFFFFFF); - session.messageFlow (arg::destination=dest, arg::unit=1, arg::value=0xFFFFFFFF); + initialized = true; +} - Message attachRequest; - char rawbuffer[512]; - Buffer buffer (rawbuffer, 512); +void ManagementAgentImpl::registerClass(const string& packageName, + const string& className, + uint8_t* md5Sum, + qpid::management::ManagementObject::writeSchemaCall_t schemaCall) +{ + Mutex::ScopedLock lock(agentLock); + PackageMap::iterator pIter = findOrAddPackage(packageName); + addClassLocal(ManagementItem::CLASS_KIND_TABLE, pIter, className, md5Sum, schemaCall); +} - attachRequest.getDeliveryProperties().setRoutingKey("broker"); - attachRequest.getMessageProperties().setReplyTo(ReplyTo("amq.direct", queueName.str())); +void ManagementAgentImpl::registerEvent(const string& packageName, + const string& eventName, + uint8_t* md5Sum, + qpid::management::ManagementObject::writeSchemaCall_t schemaCall) +{ + Mutex::ScopedLock lock(agentLock); + PackageMap::iterator pIter = findOrAddPackage(packageName); + addClassLocal(ManagementItem::CLASS_KIND_EVENT, pIter, eventName, md5Sum, schemaCall); +} - EncodeHeader (buffer, 'A'); - buffer.putShortString ("RemoteAgent [C++]"); - systemId.encode (buffer); - buffer.putLong (11); +ObjectId ManagementAgentImpl::addObject(ManagementObject* object, + uint64_t persistId) +{ + Mutex::ScopedLock lock(addLock); + uint16_t sequence = persistId ? 0 : bootSequence; + uint64_t objectNum = persistId ? persistId : nextObjectId++; - size_t length = 512 - buffer.available (); - string stringBuffer (rawbuffer, length); - attachRequest.setData (stringBuffer); + ObjectId objectId(&attachment, 0, sequence, objectNum); - session.messageTransfer(arg::content=attachRequest, arg::destination="qpid.management"); + // TODO: fix object-id handling + object->setObjectId(objectId); + newManagementObjects[objectId] = object; + return objectId; +} - dispatcher->listen(dest, this); - dispatcher->start(); +void ManagementAgentImpl::raiseEvent(const ManagementEvent& event, severity_t severity) +{ + Mutex::ScopedLock lock(agentLock); + Buffer outBuffer(eventBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + uint8_t sev = (severity == SEV_DEFAULT) ? event.getSeverity() : (uint8_t) severity; + stringstream key; - { - Mutex::ScopedLock lock(agentLock); - if (startupWait) - startupCond.wait(agentLock); - } + key << "console.event." << assignedBrokerBank << "." << assignedAgentBank << "." << + event.getPackageName() << "." << event.getEventName(); + + encodeHeader(outBuffer, 'e'); + outBuffer.putShortString(event.getPackageName()); + outBuffer.putShortString(event.getEventName()); + outBuffer.putBin128(event.getMd5Sum()); + outBuffer.putLongLong(uint64_t(Duration(now()))); + outBuffer.putOctet(sev); + event.encode(outBuffer); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + connThreadBody.sendBuffer(outBuffer, outLen, "qpid.management", key.str()); } -ManagementAgentImpl::~ManagementAgentImpl() +uint32_t ManagementAgentImpl::pollCallbacks(uint32_t callLimit) { - dispatcher->stop(); - session.close(); - delete dispatcher; + Mutex::ScopedLock lock(agentLock); + + for (uint32_t idx = 0; callLimit == 0 || idx < callLimit; idx++) { + if (methodQueue.empty()) + break; + + QueuedMethod* item = methodQueue.front(); + methodQueue.pop_front(); + { + Mutex::ScopedUnlock unlock(agentLock); + Buffer inBuffer(const_cast<char*>(item->body.c_str()), item->body.size()); + invokeMethodRequest(inBuffer, item->sequence, item->replyTo); + delete item; + } + } + + char rbuf[100]; + while (pipeHandle->read(rbuf, 100) > 0) ; // Consume all signaling bytes + return methodQueue.size(); } -void ManagementAgentImpl::RegisterClass (std::string packageName, - std::string className, - uint8_t* md5Sum, - management::ManagementObject::writeSchemaCall_t schemaCall) -{ - Mutex::ScopedLock lock(agentLock); - PackageMap::iterator pIter = FindOrAddPackage (packageName); - AddClassLocal (pIter, className, md5Sum, schemaCall); +int ManagementAgentImpl::getSignalFd(void) +{ + return pipeHandle->getReadHandle(); } -uint64_t ManagementAgentImpl::addObject (ManagementObject* object, - uint32_t /*persistId*/, - uint32_t /*persistBank*/) +void ManagementAgentImpl::startProtocol() { - Mutex::ScopedLock lock(addLock); - uint64_t objectId; + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + connected = true; + encodeHeader(buffer, 'A'); + buffer.putShortString("RemoteAgent [C++]"); + systemId.encode (buffer); + buffer.putLong(requestedBrokerBank); + buffer.putLong(requestedAgentBank); + uint32_t length = buffer.getPosition(); + buffer.reset(); + connThreadBody.sendBuffer(buffer, length, "qpid.management", "broker"); + QPID_LOG(trace, "SENT AttachRequest: reqBroker=" << requestedBrokerBank << + " reqAgent=" << requestedAgentBank); +} - // TODO: fix object-id handling - objectId = objIdPrefix | ((nextObjectId++) & 0x00FFFFFF); - object->setObjectId (objectId); - newManagementObjects[objectId] = object; - return objectId; +void ManagementAgentImpl::storeData(bool requested) +{ + if (!storeFile.empty()) { + ofstream outFile(storeFile.c_str()); + uint32_t brokerBankToWrite = requested ? requestedBrokerBank : assignedBrokerBank; + uint32_t agentBankToWrite = requested ? requestedAgentBank : assignedAgentBank; + + if (outFile.good()) { + outFile << storeMagicNumber << " " << brokerBankToWrite << " " << + agentBankToWrite << " " << bootSequence << endl; + outFile.close(); + } + } } -uint32_t ManagementAgentImpl::pollCallbacks(uint32_t /*callLimit*/) +void ManagementAgentImpl::retrieveData() { - return 0; + if (!storeFile.empty()) { + ifstream inFile(storeFile.c_str()); + string mn; + + if (inFile.good()) { + inFile >> mn; + if (mn == storeMagicNumber) { + inFile >> requestedBrokerBank; + inFile >> requestedAgentBank; + inFile >> bootSequence; + } + inFile.close(); + } + } } -int ManagementAgentImpl::getSignalFd(void) +void ManagementAgentImpl::sendCommandComplete(string replyToKey, uint32_t sequence, + uint32_t code, string text) { - return -1; + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader(outBuffer, 'z', sequence); + outBuffer.putLong(code); + outBuffer.putShortString(text); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + connThreadBody.sendBuffer(outBuffer, outLen, "amq.direct", replyToKey); + QPID_LOG(trace, "SENT CommandComplete: seq=" << sequence << " code=" << code << " text=" << text); } void ManagementAgentImpl::handleAttachResponse(Buffer& inBuffer) { Mutex::ScopedLock lock(agentLock); - uint32_t assigned; - stringstream key; - assigned = inBuffer.getLong(); - objIdPrefix = ((uint64_t) assigned) << 24; + assignedBrokerBank = inBuffer.getLong(); + assignedAgentBank = inBuffer.getLong(); - startupWait = false; - startupCond.notify(); + QPID_LOG(trace, "RCVD AttachResponse: broker=" << assignedBrokerBank << " agent=" << assignedAgentBank); + + if ((assignedBrokerBank != requestedBrokerBank) || + (assignedAgentBank != requestedAgentBank)) { + if (requestedAgentBank == 0) { + QPID_LOG(notice, "Initial object-id bank assigned: " << assignedBrokerBank << "." << + assignedAgentBank); + } else { + QPID_LOG(warning, "Collision in object-id! New bank assigned: " << assignedBrokerBank << + "." << assignedAgentBank); + } + storeData(); + requestedBrokerBank = assignedBrokerBank; + requestedAgentBank = assignedAgentBank; + } + + attachment.setBanks(assignedBrokerBank, assignedAgentBank); // Bind to qpid.management to receive commands - key << "agent." << assigned; - session.exchangeBind (arg::exchange="qpid.management", arg::queue=queueName.str(), - arg::bindingKey=key.str()); + connThreadBody.bindToBank(assignedBrokerBank, assignedAgentBank); // Send package indications for all local packages for (PackageMap::iterator pIter = packages.begin(); @@ -196,21 +351,21 @@ void ManagementAgentImpl::handleAttachResponse(Buffer& inBuffer) Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); uint32_t outLen; - EncodeHeader(outBuffer, 'p'); - EncodePackageIndication(outBuffer, pIter); - outLen = MA_BUFFER_SIZE - outBuffer.available (); + encodeHeader(outBuffer, 'p'); + encodePackageIndication(outBuffer, pIter); + outLen = MA_BUFFER_SIZE - outBuffer.available(); outBuffer.reset(); - SendBuffer(outBuffer, outLen, "qpid.management", "broker"); + connThreadBody.sendBuffer(outBuffer, outLen, "qpid.management", "broker"); // Send class indications for all local classes ClassMap cMap = pIter->second; for (ClassMap::iterator cIter = cMap.begin(); cIter != cMap.end(); cIter++) { outBuffer.reset(); - EncodeHeader(outBuffer, 'q'); - EncodeClassIndication(outBuffer, pIter, cIter); - outLen = MA_BUFFER_SIZE - outBuffer.available (); + encodeHeader(outBuffer, 'q'); + encodeClassIndication(outBuffer, pIter, cIter); + outLen = MA_BUFFER_SIZE - outBuffer.available(); outBuffer.reset(); - SendBuffer(outBuffer, outLen, "qpid.management", "broker"); + connThreadBody.sendBuffer(outBuffer, outLen, "qpid.management", "broker"); } } } @@ -225,20 +380,24 @@ void ManagementAgentImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequenc inBuffer.getShortString(key.name); inBuffer.getBin128(key.hash); + QPID_LOG(trace, "RCVD SchemaRequest: package=" << packageName << " class=" << key.name); + PackageMap::iterator pIter = packages.find(packageName); if (pIter != packages.end()) { - ClassMap cMap = pIter->second; + ClassMap& cMap = pIter->second; ClassMap::iterator cIter = cMap.find(key); if (cIter != cMap.end()) { - SchemaClass schema = cIter->second; - Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader(outBuffer, 's', sequence); - schema.writeSchemaCall(outBuffer); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset(); - SendBuffer(outBuffer, outLen, "qpid.management", "broker"); + SchemaClass& schema = cIter->second; + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader(outBuffer, 's', sequence); + schema.writeSchemaCall(outBuffer); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + connThreadBody.sendBuffer(outBuffer, outLen, "qpid.management", "broker"); + + QPID_LOG(trace, "SENT SchemaInd: package=" << packageName << " class=" << key.name); } } } @@ -247,30 +406,134 @@ void ManagementAgentImpl::handleConsoleAddedIndication() { Mutex::ScopedLock lock(agentLock); clientWasAdded = true; + + QPID_LOG(trace, "RCVD ConsoleAddedInd"); } -void ManagementAgentImpl::handleMethodRequest(Buffer& inBuffer, uint32_t sequence, string replyTo) +void ManagementAgentImpl::invokeMethodRequest(Buffer& inBuffer, uint32_t sequence, string replyTo) { string methodName; - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + string packageName; + string className; + uint8_t hash[16]; + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); uint32_t outLen; - uint64_t objId = inBuffer.getLongLong(); + ObjectId objId(inBuffer); + inBuffer.getShortString(packageName); + inBuffer.getShortString(className); + inBuffer.getBin128(hash); inBuffer.getShortString(methodName); - EncodeHeader(outBuffer, 'm', sequence); + encodeHeader(outBuffer, 'm', sequence); ManagementObjectMap::iterator iter = managementObjects.find(objId); if (iter == managementObjects.end() || iter->second->isDeleted()) { outBuffer.putLong (Manageable::STATUS_UNKNOWN_OBJECT); - outBuffer.putShortString (Manageable::StatusText (Manageable::STATUS_UNKNOWN_OBJECT)); + outBuffer.putMediumString(Manageable::StatusText(Manageable::STATUS_UNKNOWN_OBJECT)); } else { - iter->second->doMethod(methodName, inBuffer, outBuffer); + if ((iter->second->getPackageName() != packageName) || + (iter->second->getClassName() != className)) { + outBuffer.putLong (Manageable::STATUS_PARAMETER_INVALID); + outBuffer.putMediumString(Manageable::StatusText (Manageable::STATUS_PARAMETER_INVALID)); + } + else + try { + outBuffer.record(); + iter->second->doMethod(methodName, inBuffer, outBuffer); + } catch(exception& e) { + outBuffer.restore(); + outBuffer.putLong(Manageable::STATUS_EXCEPTION); + outBuffer.putMediumString(e.what()); + } } outLen = MA_BUFFER_SIZE - outBuffer.available(); outBuffer.reset(); - SendBuffer(outBuffer, outLen, "amq.direct", replyTo); + connThreadBody.sendBuffer(outBuffer, outLen, "amq.direct", replyTo); +} + +void ManagementAgentImpl::handleGetQuery(Buffer& inBuffer, uint32_t sequence, string replyTo) +{ + FieldTable ft; + FieldTable::ValuePtr value; + + moveNewObjectsLH(); + + ft.decode(inBuffer); + + QPID_LOG(trace, "RCVD GetQuery: map=" << ft); + + value = ft.get("_class"); + if (value.get() == 0 || !value->convertsTo<string>()) { + value = ft.get("_objectid"); + if (value.get() == 0 || !value->convertsTo<string>()) + return; + + ObjectId selector(value->get<string>()); + ManagementObjectMap::iterator iter = managementObjects.find(selector); + if (iter != managementObjects.end()) { + ManagementObject* object = iter->second; + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + encodeHeader(outBuffer, 'g', sequence); + object->writeProperties(outBuffer); + object->writeStatistics(outBuffer, true); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + connThreadBody.sendBuffer(outBuffer, outLen, "amq.direct", replyTo); + + QPID_LOG(trace, "SENT ObjectInd"); + } + sendCommandComplete(replyTo, sequence); + return; + } + + string className(value->get<string>()); + + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second; + if (object->getClassName() == className) { + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + encodeHeader(outBuffer, 'g', sequence); + object->writeProperties(outBuffer); + object->writeStatistics(outBuffer, true); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + connThreadBody.sendBuffer(outBuffer, outLen, "amq.direct", replyTo); + + QPID_LOG(trace, "SENT ObjectInd"); + } + } + + sendCommandComplete(replyTo, sequence); +} + +void ManagementAgentImpl::handleMethodRequest(Buffer& inBuffer, uint32_t sequence, string replyTo) +{ + if (extThread) { + Mutex::ScopedLock lock(agentLock); + string body; + + inBuffer.getRawData(body, inBuffer.available()); + methodQueue.push_back(new QueuedMethod(sequence, replyTo, body)); + pipeHandle->write("X", 1); + } else { + invokeMethodRequest(inBuffer, sequence, replyTo); + } + + QPID_LOG(trace, "RCVD MethodRequest"); } void ManagementAgentImpl::received(Message& msg) @@ -287,215 +550,371 @@ void ManagementAgentImpl::received(Message& msg) replyToKey = rt.getRoutingKey(); } - if (CheckHeader (inBuffer, &opcode, &sequence)) + if (checkHeader(inBuffer, &opcode, &sequence)) { if (opcode == 'a') handleAttachResponse(inBuffer); else if (opcode == 'S') handleSchemaRequest(inBuffer, sequence); else if (opcode == 'x') handleConsoleAddedIndication(); + else if (opcode == 'G') handleGetQuery(inBuffer, sequence, replyToKey); else if (opcode == 'M') handleMethodRequest(inBuffer, sequence, replyToKey); } } -void ManagementAgentImpl::EncodeHeader (Buffer& buf, uint8_t opcode, uint32_t seq) +void ManagementAgentImpl::encodeHeader(Buffer& buf, uint8_t opcode, uint32_t seq) { - buf.putOctet ('A'); - buf.putOctet ('M'); - buf.putOctet ('1'); - buf.putOctet (opcode); - buf.putLong (seq); + buf.putOctet('A'); + buf.putOctet('M'); + buf.putOctet('2'); + buf.putOctet(opcode); + buf.putLong (seq); } -bool ManagementAgentImpl::CheckHeader (Buffer& buf, uint8_t *opcode, uint32_t *seq) +bool ManagementAgentImpl::checkHeader(Buffer& buf, uint8_t *opcode, uint32_t *seq) { if (buf.getSize() < 8) return false; - uint8_t h1 = buf.getOctet (); - uint8_t h2 = buf.getOctet (); - uint8_t h3 = buf.getOctet (); + uint8_t h1 = buf.getOctet(); + uint8_t h2 = buf.getOctet(); + uint8_t h3 = buf.getOctet(); - *opcode = buf.getOctet (); - *seq = buf.getLong (); + *opcode = buf.getOctet(); + *seq = buf.getLong(); - return h1 == 'A' && h2 == 'M' && h3 == '1'; + return h1 == 'A' && h2 == 'M' && h3 == '2'; } -void ManagementAgentImpl::SendBuffer (Buffer& buf, - uint32_t length, - string exchange, - string routingKey) +ManagementAgentImpl::PackageMap::iterator ManagementAgentImpl::findOrAddPackage(const string& name) { - Message msg; - string data; - - if (objIdPrefix == 0) - return; - - buf.getRawData(data, length); - msg.getDeliveryProperties().setRoutingKey(routingKey); - msg.getMessageProperties().setReplyTo(ReplyTo("amq.direct", queueName.str())); - msg.setData (data); - session.messageTransfer (arg::content=msg, arg::destination=exchange); -} - -ManagementAgentImpl::PackageMap::iterator ManagementAgentImpl::FindOrAddPackage (std::string name) -{ - PackageMap::iterator pIter = packages.find (name); - if (pIter != packages.end ()) + PackageMap::iterator pIter = packages.find(name); + if (pIter != packages.end()) return pIter; // No such package found, create a new map entry. - std::pair<PackageMap::iterator, bool> result = - packages.insert (std::pair<string, ClassMap> (name, ClassMap ())); + pair<PackageMap::iterator, bool> result = + packages.insert(pair<string, ClassMap>(name, ClassMap())); - // Publish a package-indication message - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; + if (connected) { + // Publish a package-indication message + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; - EncodeHeader (outBuffer, 'p'); - EncodePackageIndication (outBuffer, result.first); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, "qpid.management", "mgmt.schema.package"); + encodeHeader(outBuffer, 'p'); + encodePackageIndication(outBuffer, result.first); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + connThreadBody.sendBuffer(outBuffer, outLen, "qpid.management", "schema.package"); + } return result.first; } void ManagementAgentImpl::moveNewObjectsLH() { - Mutex::ScopedLock lock (addLock); - for (ManagementObjectMap::iterator iter = newManagementObjects.begin (); - iter != newManagementObjects.end (); + Mutex::ScopedLock lock(addLock); + for (ManagementObjectMap::iterator iter = newManagementObjects.begin(); + iter != newManagementObjects.end(); iter++) managementObjects[iter->first] = iter->second; newManagementObjects.clear(); } -void ManagementAgentImpl::AddClassLocal (PackageMap::iterator pIter, - string className, - uint8_t* md5Sum, - management::ManagementObject::writeSchemaCall_t schemaCall) +void ManagementAgentImpl::addClassLocal(uint8_t classKind, + PackageMap::iterator pIter, + const string& className, + uint8_t* md5Sum, + qpid::management::ManagementObject::writeSchemaCall_t schemaCall) { SchemaClassKey key; ClassMap& cMap = pIter->second; key.name = className; - memcpy (&key.hash, md5Sum, 16); + memcpy(&key.hash, md5Sum, 16); - ClassMap::iterator cIter = cMap.find (key); - if (cIter != cMap.end ()) + ClassMap::iterator cIter = cMap.find(key); + if (cIter != cMap.end()) return; // No such class found, create a new class with local information. - SchemaClass classInfo; - - classInfo.writeSchemaCall = schemaCall; - cMap[key] = classInfo; - - // TODO: Publish a class-indication message + cMap.insert(pair<SchemaClassKey, SchemaClass>(key, SchemaClass(schemaCall, classKind))); } -void ManagementAgentImpl::EncodePackageIndication (Buffer& buf, - PackageMap::iterator pIter) +void ManagementAgentImpl::encodePackageIndication(Buffer& buf, + PackageMap::iterator pIter) { - buf.putShortString ((*pIter).first); + buf.putShortString((*pIter).first); + + QPID_LOG(trace, "SENT PackageInd: package=" << (*pIter).first); } -void ManagementAgentImpl::EncodeClassIndication (Buffer& buf, - PackageMap::iterator pIter, - ClassMap::iterator cIter) +void ManagementAgentImpl::encodeClassIndication(Buffer& buf, + PackageMap::iterator pIter, + ClassMap::iterator cIter) { SchemaClassKey key = (*cIter).first; - buf.putShortString ((*pIter).first); - buf.putShortString (key.name); - buf.putBin128 (key.hash); + buf.putOctet((*cIter).second.kind); + buf.putShortString((*pIter).first); + buf.putShortString(key.name); + buf.putBin128(key.hash); + + QPID_LOG(trace, "SENT ClassInd: package=" << (*pIter).first << " class=" << key.name); } -void ManagementAgentImpl::PeriodicProcessing() +void ManagementAgentImpl::periodicProcessing() { #define BUFSIZE 65536 Mutex::ScopedLock lock(agentLock); char msgChars[BUFSIZE]; uint32_t contentSize; - string routingKey; - std::list<uint64_t> deleteList; + list<pair<ObjectId, ManagementObject*> > deleteList; - { - Buffer msgBuffer(msgChars, BUFSIZE); - EncodeHeader(msgBuffer, 'h'); - msgBuffer.putLongLong(uint64_t(Duration(now()))); + if (!connected) + return; - contentSize = BUFSIZE - msgBuffer.available (); - msgBuffer.reset (); - routingKey = "mgmt." + systemId.str() + ".heartbeat"; - SendBuffer (msgBuffer, contentSize, "qpid.management", routingKey); + moveNewObjectsLH(); + + // + // Clear the been-here flag on all objects in the map. + // + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second; + object->setFlags(0); + if (clientWasAdded) { + object->setForcePublish(true); + } } - moveNewObjectsLH(); + clientWasAdded = false; + + // + // Process the entire object map. + // + for (ManagementObjectMap::iterator baseIter = managementObjects.begin(); + baseIter != managementObjects.end(); + baseIter++) { + ManagementObject* baseObject = baseIter->second; + + // + // Skip until we find a base object requiring a sent message. + // + if (baseObject->getFlags() == 1 || + (!baseObject->getConfigChanged() && + !baseObject->getInstChanged() && + !baseObject->getForcePublish() && + !baseObject->isDeleted())) + continue; - if (clientWasAdded) - { - clientWasAdded = false; - for (ManagementObjectMap::iterator iter = managementObjects.begin (); - iter != managementObjects.end (); - iter++) - { + Buffer msgBuffer(msgChars, BUFSIZE); + for (ManagementObjectMap::iterator iter = baseIter; + iter != managementObjects.end(); + iter++) { ManagementObject* object = iter->second; - object->setAllChanged (); + if (baseObject->isSameClass(*object) && object->getFlags() == 0) { + object->setFlags(1); + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + if (object->getConfigChanged() || object->getForcePublish() || object->isDeleted()) { + encodeHeader(msgBuffer, 'c'); + object->writeProperties(msgBuffer); + } + + if (object->hasInst() && (object->getInstChanged() || object->getForcePublish())) { + encodeHeader(msgBuffer, 'i'); + object->writeStatistics(msgBuffer); + } + + if (object->isDeleted()) + deleteList.push_back(pair<ObjectId, ManagementObject*>(iter->first, object)); + object->setForcePublish(false); + + if (msgBuffer.available() < (BUFSIZE / 2)) + break; + } + } + + contentSize = BUFSIZE - msgBuffer.available(); + if (contentSize > 0) { + msgBuffer.reset(); + stringstream key; + key << "console.obj." << assignedBrokerBank << "." << assignedAgentBank << "." << + baseObject->getPackageName() << "." << baseObject->getClassName(); + connThreadBody.sendBuffer(msgBuffer, contentSize, "qpid.management", key.str()); } } - if (managementObjects.empty ()) - return; - - for (ManagementObjectMap::iterator iter = managementObjects.begin (); - iter != managementObjects.end (); - iter++) + // Delete flagged objects + for (list<pair<ObjectId, ManagementObject*> >::reverse_iterator iter = deleteList.rbegin(); + iter != deleteList.rend(); + iter++) { + delete iter->second; + managementObjects.erase(iter->first); + } + + deleteList.clear(); + { - ManagementObject* object = iter->second; + Buffer msgBuffer(msgChars, BUFSIZE); + encodeHeader(msgBuffer, 'h'); + msgBuffer.putLongLong(uint64_t(Duration(now()))); + stringstream key; + key << "console.heartbeat." << assignedBrokerBank << "." << assignedAgentBank; - if (object->getConfigChanged () || object->isDeleted ()) - { - Buffer msgBuffer (msgChars, BUFSIZE); - EncodeHeader (msgBuffer, 'c'); - object->writeProperties(msgBuffer); - - contentSize = BUFSIZE - msgBuffer.available (); - msgBuffer.reset (); - routingKey = "mgmt." + systemId.str() + ".prop." + object->getClassName (); - SendBuffer (msgBuffer, contentSize, "qpid.management", routingKey); + contentSize = BUFSIZE - msgBuffer.available(); + msgBuffer.reset(); + connThreadBody.sendBuffer(msgBuffer, contentSize, "qpid.management", key.str()); + } +} + +void ManagementAgentImpl::ConnectionThread::run() +{ + static const int delayMin(1); + static const int delayMax(128); + static const int delayFactor(2); + int delay(delayMin); + string dest("qmfagent"); + ConnectionThread::shared_ptr tmp; + + sessionId.generate(); + queueName << "qmfagent-" << sessionId; + + while (true) { + try { + if (agent.initialized) { + QPID_LOG(debug, "QMF Agent attempting to connect to the broker..."); + connection.open(agent.connectionSettings); + session = connection.newSession(queueName.str()); + subscriptions.reset(new client::SubscriptionManager(session)); + + session.queueDeclare(arg::queue=queueName.str(), arg::autoDelete=true, + arg::exclusive=true); + session.exchangeBind(arg::exchange="amq.direct", arg::queue=queueName.str(), + arg::bindingKey=queueName.str()); + + subscriptions->subscribe(agent, queueName.str(), dest); + QPID_LOG(info, "Connection established with broker"); + { + Mutex::ScopedLock _lock(connLock); + if (shutdown) + return; + operational = true; + agent.startProtocol(); + try { + Mutex::ScopedUnlock _unlock(connLock); + subscriptions->run(); + } catch (exception) {} + + QPID_LOG(warning, "Connection to the broker has been lost"); + + operational = false; + agent.connected = false; + tmp = subscriptions; + subscriptions.reset(); + } + tmp.reset(); // frees the subscription outside the lock + delay = delayMin; + connection.close(); + } + } catch (exception &e) { + if (delay < delayMax) + delay *= delayFactor; + QPID_LOG(debug, "Connection failed: exception=" << e.what()); } - - if (object->getInstChanged ()) + { - Buffer msgBuffer (msgChars, BUFSIZE); - EncodeHeader (msgBuffer, 'i'); - object->writeStatistics(msgBuffer); - - contentSize = BUFSIZE - msgBuffer.available (); - msgBuffer.reset (); - routingKey = "mgmt." + systemId.str () + ".stat." + object->getClassName (); - SendBuffer (msgBuffer, contentSize, "qpid.management", routingKey); + // sleep for "delay" seconds, but peridically check if the + // agent is shutting down so we don't hang for up to delayMax + // seconds during agent shutdown + Mutex::ScopedLock _lock(connLock); + if (shutdown) + return; + sleeping = true; + int totalSleep = 0; + do { + Mutex::ScopedUnlock _unlock(connLock); + ::sleep(delayMin); + totalSleep += delayMin; + } while (totalSleep < delay && !shutdown); + sleeping = false; + if (shutdown) + return; } + } +} + +ManagementAgentImpl::ConnectionThread::~ConnectionThread() +{ +} - if (object->isDeleted ()) - deleteList.push_back (iter->first); +void ManagementAgentImpl::ConnectionThread::sendBuffer(Buffer& buf, + uint32_t length, + const string& exchange, + const string& routingKey) +{ + ConnectionThread::shared_ptr s; + { + Mutex::ScopedLock _lock(connLock); + if (!operational) + return; + s = subscriptions; } - // Delete flagged objects - for (std::list<uint64_t>::reverse_iterator iter = deleteList.rbegin (); - iter != deleteList.rend (); - iter++) - managementObjects.erase (*iter); + Message msg; + string data; - deleteList.clear (); + buf.getRawData(data, length); + msg.getDeliveryProperties().setRoutingKey(routingKey); + msg.getMessageProperties().setReplyTo(ReplyTo("amq.direct", queueName.str())); + msg.setData(data); + try { + session.messageTransfer(arg::content=msg, arg::destination=exchange); + } catch(exception& e) { + QPID_LOG(error, "Exception caught in sendBuffer: " << e.what()); + // Bounce the connection + if (s) + s->stop(); + } } -void ManagementAgentImpl::BackgroundThread::run() +void ManagementAgentImpl::ConnectionThread::bindToBank(uint32_t brokerBank, uint32_t agentBank) { - while (true) { - ::sleep(5); - agent.PeriodicProcessing(); + stringstream key; + key << "agent." << brokerBank << "." << agentBank; + session.exchangeBind(arg::exchange="qpid.management", arg::queue=queueName.str(), + arg::bindingKey=key.str()); +} + +void ManagementAgentImpl::ConnectionThread::close() +{ + ConnectionThread::shared_ptr s; + { + Mutex::ScopedLock _lock(connLock); + shutdown = true; + s = subscriptions; + } + if (s) + s->stop(); +} + +bool ManagementAgentImpl::ConnectionThread::isSleeping() const +{ + Mutex::ScopedLock _lock(connLock); + return sleeping; +} + + +void ManagementAgentImpl::PublishThread::run() +{ + uint16_t totalSleep; + + while (!shutdown) { + agent.periodicProcessing(); + totalSleep = 0; + while (totalSleep++ < agent.getInterval() && !shutdown) { + ::sleep(1); + } } } diff --git a/cpp/src/qpid/agent/ManagementAgentImpl.h b/cpp/src/qpid/agent/ManagementAgentImpl.h index f7f19e145d..a876496e98 100644 --- a/cpp/src/qpid/agent/ManagementAgentImpl.h +++ b/cpp/src/qpid/agent/ManagementAgentImpl.h @@ -20,9 +20,10 @@ // under the License. // -#include "ManagementAgent.h" +#include "qpid/agent/ManagementAgent.h" #include "qpid/client/Connection.h" -#include "qpid/client/Dispatcher.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/SubscriptionManager.h" #include "qpid/client/Session.h" #include "qpid/client/AsyncSession.h" #include "qpid/client/Message.h" @@ -30,10 +31,11 @@ #include "qpid/sys/Thread.h" #include "qpid/sys/Runnable.h" #include "qpid/sys/Mutex.h" -#include "qpid/sys/Condition.h" +#include "qpid/sys/PipeHandle.h" #include "qpid/framing/Uuid.h" #include <iostream> #include <sstream> +#include <deque> namespace qpid { namespace management { @@ -45,33 +47,49 @@ class ManagementAgentImpl : public ManagementAgent, public client::MessageListen ManagementAgentImpl(); virtual ~ManagementAgentImpl(); + // + // Methods from ManagementAgent + // int getMaxThreads() { return 1; } - void init(std::string brokerHost = "localhost", - uint16_t brokerPort = 5672, - uint16_t intervalSeconds = 10, - bool useExternalThread = false); - void RegisterClass(std::string packageName, - std::string className, - uint8_t* md5Sum, + void init(const std::string& brokerHost = "localhost", + uint16_t brokerPort = 5672, + uint16_t intervalSeconds = 10, + bool useExternalThread = false, + const std::string& storeFile = "", + const std::string& uid = "guest", + const std::string& pwd = "guest", + const std::string& mech = "PLAIN", + const std::string& proto = "tcp"); + void init(const client::ConnectionSettings& settings, + uint16_t intervalSeconds = 10, + bool useExternalThread = false, + const std::string& storeFile = ""); + bool isConnected() { return connected; } + std::string& getLastFailure() { return lastFailure; } + void registerClass(const std::string& packageName, + const std::string& className, + uint8_t* md5Sum, management::ManagementObject::writeSchemaCall_t schemaCall); - uint64_t addObject (management::ManagementObject* objectPtr, - uint32_t persistId = 0, - uint32_t persistBank = 4); - uint32_t pollCallbacks (uint32_t callLimit = 0); - int getSignalFd (void); + void registerEvent(const std::string& packageName, + const std::string& eventName, + uint8_t* md5Sum, + management::ManagementObject::writeSchemaCall_t schemaCall); + ObjectId addObject(management::ManagementObject* objectPtr, uint64_t persistId = 0); + void raiseEvent(const management::ManagementEvent& event, severity_t severity = SEV_DEFAULT); + uint32_t pollCallbacks(uint32_t callLimit = 0); + int getSignalFd(); - void PeriodicProcessing(); + uint16_t getInterval() { return interval; } + void periodicProcessing(); private: - struct SchemaClassKey - { + struct SchemaClassKey { std::string name; uint8_t hash[16]; }; - struct SchemaClassKeyComp - { + struct SchemaClassKeyComp { bool operator() (const SchemaClassKey& lhs, const SchemaClassKey& rhs) const { if (lhs.name != rhs.name) @@ -84,74 +102,137 @@ class ManagementAgentImpl : public ManagementAgent, public client::MessageListen } }; - struct SchemaClass - { + struct SchemaClass { management::ManagementObject::writeSchemaCall_t writeSchemaCall; + uint8_t kind; - SchemaClass () : writeSchemaCall(0) {} + SchemaClass(const management::ManagementObject::writeSchemaCall_t call, + const uint8_t _kind) : writeSchemaCall(call), kind(_kind) {} }; + struct QueuedMethod { + QueuedMethod(uint32_t _seq, std::string _reply, std::string _body) : + sequence(_seq), replyTo(_reply), body(_body) {} + + uint32_t sequence; + std::string replyTo; + std::string body; + }; + + typedef std::deque<QueuedMethod*> MethodQueue; typedef std::map<SchemaClassKey, SchemaClass, SchemaClassKeyComp> ClassMap; typedef std::map<std::string, ClassMap> PackageMap; PackageMap packages; + AgentAttachment attachment; management::ManagementObjectMap managementObjects; management::ManagementObjectMap newManagementObjects; + MethodQueue methodQueue; void received (client::Message& msg); uint16_t interval; bool extThread; + sys::PipeHandle* pipeHandle; uint64_t nextObjectId; + std::string storeFile; sys::Mutex agentLock; sys::Mutex addLock; - framing::Uuid sessionId; framing::Uuid systemId; + client::ConnectionSettings connectionSettings; + bool initialized; + bool connected; + std::string lastFailure; + + bool clientWasAdded; + uint32_t requestedBrokerBank; + uint32_t requestedAgentBank; + uint32_t assignedBrokerBank; + uint32_t assignedAgentBank; + uint16_t bootSequence; + + static const uint8_t DEBUG_OFF = 0; + static const uint8_t DEBUG_CONN = 1; + static const uint8_t DEBUG_PROTO = 2; + static const uint8_t DEBUG_PUBLISH = 3; - int signalFdIn, signalFdOut; - client::Connection connection; - client::Session session; - client::Dispatcher* dispatcher; - bool clientWasAdded; - uint64_t objIdPrefix; - std::stringstream queueName; # define MA_BUFFER_SIZE 65536 char outputBuffer[MA_BUFFER_SIZE]; + char eventBuffer[MA_BUFFER_SIZE]; - class BackgroundThread : public sys::Runnable + friend class ConnectionThread; + class ConnectionThread : public sys::Runnable { + typedef boost::shared_ptr<client::SubscriptionManager> shared_ptr; + + bool operational; ManagementAgentImpl& agent; + framing::Uuid sessionId; + client::Connection connection; + client::Session session; + ConnectionThread::shared_ptr subscriptions; + std::stringstream queueName; + mutable sys::Mutex connLock; + bool shutdown; + bool sleeping; void run(); public: - BackgroundThread(ManagementAgentImpl& _agent) : agent(_agent) {} + ConnectionThread(ManagementAgentImpl& _agent) : + operational(false), agent(_agent), + shutdown(false), sleeping(false) {} + ~ConnectionThread(); + void sendBuffer(qpid::framing::Buffer& buf, + uint32_t length, + const std::string& exchange, + const std::string& routingKey); + void bindToBank(uint32_t brokerBank, uint32_t agentBank); + void close(); + bool isSleeping() const; }; - BackgroundThread bgThread; - sys::Thread thread; - sys::Condition startupCond; - bool startupWait; + class PublishThread : public sys::Runnable + { + ManagementAgentImpl& agent; + void run(); + bool shutdown; + public: + PublishThread(ManagementAgentImpl& _agent) : + agent(_agent), shutdown(false) {} + void close() { shutdown = true; } + }; + + ConnectionThread connThreadBody; + sys::Thread connThread; + PublishThread pubThreadBody; + sys::Thread pubThread; + + static const std::string storeMagicNumber; - PackageMap::iterator FindOrAddPackage (std::string name); + void startProtocol(); + void storeData(bool requested=false); + void retrieveData(); + PackageMap::iterator findOrAddPackage(const std::string& name); void moveNewObjectsLH(); - void AddClassLocal (PackageMap::iterator pIter, - std::string className, + void addClassLocal (uint8_t classKind, + PackageMap::iterator pIter, + const std::string& className, uint8_t* md5Sum, management::ManagementObject::writeSchemaCall_t schemaCall); - void EncodePackageIndication (qpid::framing::Buffer& buf, + void encodePackageIndication (framing::Buffer& buf, PackageMap::iterator pIter); - void EncodeClassIndication (qpid::framing::Buffer& buf, + void encodeClassIndication (framing::Buffer& buf, PackageMap::iterator pIter, ClassMap::iterator cIter); - void EncodeHeader (qpid::framing::Buffer& buf, uint8_t opcode, uint32_t seq = 0); - bool CheckHeader (qpid::framing::Buffer& buf, uint8_t *opcode, uint32_t *seq); - void SendBuffer (qpid::framing::Buffer& buf, - uint32_t length, - std::string exchange, - std::string routingKey); + void encodeHeader (framing::Buffer& buf, uint8_t opcode, uint32_t seq = 0); + bool checkHeader (framing::Buffer& buf, uint8_t *opcode, uint32_t *seq); + void sendCommandComplete (std::string replyToKey, uint32_t sequence, + uint32_t code = 0, std::string text = std::string("OK")); void handleAttachResponse (qpid::framing::Buffer& inBuffer); void handlePackageRequest (qpid::framing::Buffer& inBuffer); void handleClassQuery (qpid::framing::Buffer& inBuffer); void handleSchemaRequest (qpid::framing::Buffer& inBuffer, uint32_t sequence); + void invokeMethodRequest (qpid::framing::Buffer& inBuffer, uint32_t sequence, std::string replyTo); + void handleGetQuery (qpid::framing::Buffer& inBuffer, uint32_t sequence, std::string replyTo); void handleMethodRequest (qpid::framing::Buffer& inBuffer, uint32_t sequence, std::string replyTo); void handleConsoleAddedIndication(); }; diff --git a/cpp/src/qpid/amqp_0_10/Array.cpp b/cpp/src/qpid/amqp_0_10/Array.cpp index 380e0f1f36..2ee47546f2 100644 --- a/cpp/src/qpid/amqp_0_10/Array.cpp +++ b/cpp/src/qpid/amqp_0_10/Array.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "Array.h" +#include "qpid/amqp_0_10/Array.h" namespace qpid { namespace amqp_0_10 { diff --git a/cpp/src/qpid/amqp_0_10/Codec.h b/cpp/src/qpid/amqp_0_10/Codec.h index 5cad5cf4ed..fd006a348e 100644 --- a/cpp/src/qpid/amqp_0_10/Codec.h +++ b/cpp/src/qpid/amqp_0_10/Codec.h @@ -22,7 +22,7 @@ * */ -#include "built_in_types.h" +#include "qpid/amqp_0_10/built_in_types.h" #include "qpid/Serializer.h" #include <boost/type_traits/is_integral.hpp> #include <boost/type_traits/is_float.hpp> diff --git a/cpp/src/qpid/amqp_0_10/Command.h b/cpp/src/qpid/amqp_0_10/Command.h index 0fe023e520..b1d3607a84 100644 --- a/cpp/src/qpid/amqp_0_10/Command.h +++ b/cpp/src/qpid/amqp_0_10/Command.h @@ -22,7 +22,7 @@ * */ -#include "Control.h" +#include "qpid/amqp_0_10/Control.h" #include "qpid/amqp_0_10/structs.h" namespace qpid { diff --git a/cpp/src/qpid/amqp_0_10/Connection.cpp b/cpp/src/qpid/amqp_0_10/Connection.cpp index a3692911b2..bf2e7d5713 100644 --- a/cpp/src/qpid/amqp_0_10/Connection.cpp +++ b/cpp/src/qpid/amqp_0_10/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 @@ -18,19 +18,26 @@ * under the License. * */ -#include "Connection.h" +#include "qpid/amqp_0_10/Connection.h" #include "qpid/log/Statement.h" #include "qpid/amqp_0_10/exceptions.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/ProtocolInitiation.h" namespace qpid { namespace amqp_0_10 { using sys::Mutex; -Connection::Connection(sys::OutputControl& o, broker::Broker& broker, const std::string& id, bool _isClient) - : frameQueueClosed(false), output(o), - connection(new broker::Connection(this, broker, id, _isClient)), - identifier(id), initialized(false), isClient(_isClient) {} +Connection::Connection(sys::OutputControl& o, const std::string& id, bool _isClient) + : pushClosed(false), popClosed(false), output(o), identifier(id), initialized(false), + isClient(_isClient), buffered(0), version(0,10) +{} + +void Connection::setInputHandler(std::auto_ptr<sys::ConnectionInputHandler> c) { + connection = c; +} size_t Connection::decode(const char* buffer, size_t size) { framing::Buffer in(const_cast<char*>(buffer), size); @@ -38,7 +45,9 @@ size_t Connection::decode(const char* buffer, size_t size) { //read in protocol header framing::ProtocolInitiation pi; if (pi.decode(in)) { - //TODO: check the version is correct + if(!(pi==version)) + throw Exception(QPID_MSG("Unsupported version: " << pi + << " supported version " << version)); QPID_LOG(trace, "RECV " << identifier << " INIT(" << pi << ")"); } initialized = true; @@ -52,18 +61,26 @@ size_t Connection::decode(const char* buffer, size_t size) { } bool Connection::canEncode() { - if (!frameQueueClosed) connection->doOutput(); Mutex::ScopedLock l(frameQueueLock); - return (!isClient && !initialized) || !frameQueue.empty(); + if (!popClosed) { + Mutex::ScopedUnlock u(frameQueueLock); + connection->doOutput(); + } + return !popClosed && ((!isClient && !initialized) || !frameQueue.empty()); } bool Connection::isClosed() const { Mutex::ScopedLock l(frameQueueLock); - return frameQueueClosed; + return pushClosed && popClosed; } size_t Connection::encode(const char* buffer, size_t size) { - Mutex::ScopedLock l(frameQueueLock); + { // 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); if (!isClient && !initialized) { framing::ProtocolInitiation pi(getVersion()); @@ -71,23 +88,39 @@ size_t Connection::encode(const char* buffer, size_t size) { initialized = true; QPID_LOG(trace, "SENT " << identifier << " INIT(" << pi << ")"); } - while (!frameQueue.empty() && (frameQueue.front().size() <= out.available())) { - frameQueue.front().encode(out); - QPID_LOG(trace, "SENT [" << identifier << "]: " << frameQueue.front()); - frameQueue.pop(); + size_t frameSize=0; + size_t encoded=0; + while (!workQueue.empty() && ((frameSize=workQueue.front().encodedSize()) <= out.available())) { + workQueue.front().encode(out); + QPID_LOG(trace, "SENT [" << identifier << "]: " << workQueue.front()); + workQueue.pop_front(); + encoded += frameSize; + if (workQueue.empty() && out.available() > 0) connection->doOutput(); + } + assert(workQueue.empty() || workQueue.front().encodedSize() <= size); + if (!workQueue.empty() && workQueue.front().encodedSize() > size) + throw InternalErrorException(QPID_MSG("Frame too large for buffer.")); + { + Mutex::ScopedLock l(frameQueueLock); + buffered -= encoded; + // Put back any frames we did not encode. + frameQueue.insert(frameQueue.begin(), workQueue.begin(), workQueue.end()); + workQueue.clear(); + if (frameQueue.empty() && pushClosed) + popClosed = true; } - assert(frameQueue.empty() || frameQueue.front().size() <= size); - if (!frameQueue.empty() && frameQueue.front().size() > size) - throw InternalErrorException(QPID_MSG("Could not write frame, too large for buffer.")); return out.getPosition(); } -void Connection::activateOutput() { output.activateOutput(); } +void Connection::abort() { output.abort(); } +void Connection::activateOutput() { output.activateOutput(); } +void Connection::giveReadCredit(int32_t credit) { output.giveReadCredit(credit); } void Connection::close() { - // Close the output queue. + // No more frames can be pushed onto the queue. + // Frames aleady on the queue can be popped. Mutex::ScopedLock l(frameQueueLock); - frameQueueClosed = true; + pushClosed = true; } void Connection::closed() { @@ -97,14 +130,24 @@ void Connection::closed() { void Connection::send(framing::AMQFrame& f) { { Mutex::ScopedLock l(frameQueueLock); - if (!frameQueueClosed) - frameQueue.push(f); + if (!pushClosed) + frameQueue.push_back(f); + buffered += f.encodedSize(); } activateOutput(); } framing::ProtocolVersion Connection::getVersion() const { - return framing::ProtocolVersion(0,10); + return version; +} + +void Connection::setVersion(const framing::ProtocolVersion& v) { + version = v; +} + +size_t Connection::getBuffered() const { + Mutex::ScopedLock l(frameQueueLock); + return buffered; } }} // namespace qpid::amqp_0_10 diff --git a/cpp/src/qpid/amqp_0_10/Connection.h b/cpp/src/qpid/amqp_0_10/Connection.h index b707031789..995d824796 100644 --- a/cpp/src/qpid/amqp_0_10/Connection.h +++ b/cpp/src/qpid/amqp_0_10/Connection.h @@ -10,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 @@ -21,41 +21,61 @@ * under the License. * */ + +#include "qpid/framing/AMQFrame.h" #include "qpid/sys/ConnectionCodec.h" +#include "qpid/sys/ConnectionInputHandler.h" #include "qpid/sys/ConnectionOutputHandler.h" #include "qpid/sys/Mutex.h" -#include "qpid/broker/Connection.h" +#include "qpid/broker/BrokerImportExport.h" #include <boost/intrusive_ptr.hpp> -#include <queue> #include <memory> +#include <deque> namespace qpid { -namespace broker { class Broker; } + +namespace sys { +class ConnectionInputHandlerFactory; +} + namespace amqp_0_10 { class Connection : public sys::ConnectionCodec, public sys::ConnectionOutputHandler { - std::queue<framing::AMQFrame> frameQueue; - bool frameQueueClosed; + typedef std::deque<framing::AMQFrame> FrameQueue; + + FrameQueue frameQueue; + FrameQueue workQueue; + bool pushClosed, popClosed; mutable sys::Mutex frameQueueLock; sys::OutputControl& output; - boost::intrusive_ptr<broker::Connection> connection; + std::auto_ptr<sys::ConnectionInputHandler> connection; std::string identifier; bool initialized; bool isClient; - + size_t buffered; + framing::ProtocolVersion version; + public: - Connection(sys::OutputControl&, broker::Broker&, const std::string& id, bool isClient = false); + 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); 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/amqp_0_10/Control.h b/cpp/src/qpid/amqp_0_10/Control.h index 226f6f92a6..ce188ae6d8 100644 --- a/cpp/src/qpid/amqp_0_10/Control.h +++ b/cpp/src/qpid/amqp_0_10/Control.h @@ -22,7 +22,7 @@ * */ -#include "Struct.h" +#include "qpid/amqp_0_10/Struct.h" namespace qpid { namespace amqp_0_10 { diff --git a/cpp/src/qpid/amqp_0_10/Exception.h b/cpp/src/qpid/amqp_0_10/Exception.h index 4841d91215..6d526c1706 100644 --- a/cpp/src/qpid/amqp_0_10/Exception.h +++ b/cpp/src/qpid/amqp_0_10/Exception.h @@ -32,12 +32,12 @@ namespace amqp_0_10 { * Raised when the connection is unexpectedly closed. Sessions with * non-0 timeout may be available for re-attachment on another connection. */ -struct ConnectionException : public qpid::ConnectionException { +struct ConnectionException : public qpid::Exception { // FIXME aconway 2008-04-04: Merge qpid::ConnectionException // into this when the old code is removed. typedef connection::CloseCode Code; ConnectionException(Code c, const std::string m) - : qpid::ConnectionException(c,m), code(c) {} + : qpid::Exception(m), code(c) {} Code code; }; @@ -45,10 +45,10 @@ struct ConnectionException : public qpid::ConnectionException { * Raised when a session is unexpectedly detached for any reason, or * if an attempt is made to use a session that is not attached. */ -struct SessionException : public qpid::SessionException { +struct SessionException : public qpid::Exception { // FIXME aconway 2008-04-04: should not have a code at this level. // Leave in place till old preview code is gone. - SessionException(int code, const std::string& msg) : qpid::SessionException(code, msg) {} + SessionException(int /*code*/, const std::string& msg) : qpid::Exception(msg) {} }; /** Raised when the state of a session has been destroyed */ @@ -94,93 +94,3 @@ struct SessionDetachedException : public SessionException { }} // namespace qpid::amqp_0_10 #endif /*!QPID_AMQP_0_10_EXCEPTION_H*/ -#ifndef QPID_AMQP_0_10_EXCEPTION_H -#define QPID_AMQP_0_10_EXCEPTION_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/Exception.h" -#include "qpid/amqp_0_10/specification_fwd.h" - -namespace qpid { -namespace amqp_0_10 { - -/** - * Raised when the connection is unexpectedly closed. Sessions with - * non-0 timeout may be available for re-attachment on another connection. - */ -struct ConnectionException : public Exception { - typedef connection::CloseCode Code; - ConnectionException(Code c, const std::string m) - : Exception(m), code(c) {} - Code code; -}; - -/** - * Raised when a session is unexpectedly detached for any reason, or - * if an attempt is made to use a session that is not attached. - */ -struct SessionException : public Exception { - SessionException(const std::string& msg) : Exception(msg) {} -}; - -/** Raised when the state of a session has been destroyed */ -struct SessionDestroyedException : public SessionException { - SessionDestroyedException(const std::string& msg) : SessionException(msg){} -}; - -/** Raised when a session is destroyed due to an execution.exception */ -struct SessionAbortedException : public SessionDestroyedException { - typedef execution::ErrorCode Code; - SessionAbortedException(Code c, const std::string m) - : SessionDestroyedException(m), code(c) {} - Code code; -}; - -/** - * Raised when a session with 0 timeout is unexpectedly detached - * and therefore expires and is destroyed. - */ -struct SessionExpiredException : public SessionDestroyedException { - typedef session::DetachCode Code; - SessionExpiredException(Code c, const std::string m) - : SessionDestroyedException(m), code(c) {} - Code code; -}; - -/** - * Raised when a session with non-0 timeout is unexpectedly detached - * or if an attempt is made to use a session that is not attached. - * - * The session is not necessarily destroyed, it may be possible to - * re-attach. - */ -struct SessionDetachedException : public SessionException { - typedef session::DetachCode Code; - SessionDetachedException(Code c, const std::string m) - : SessionException(m), code(c) {} - Code code; -}; - -}} // namespace qpid::amqp_0_10 - -#endif /*!QPID_AMQP_0_10_EXCEPTION_H*/ diff --git a/cpp/src/qpid/amqp_0_10/FrameHeader.cpp b/cpp/src/qpid/amqp_0_10/FrameHeader.cpp index f1a59b9e27..371e3c1bcb 100644 --- a/cpp/src/qpid/amqp_0_10/FrameHeader.cpp +++ b/cpp/src/qpid/amqp_0_10/FrameHeader.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "FrameHeader.h" +#include "qpid/amqp_0_10/FrameHeader.h" #include <ios> #include <iomanip> #include <ostream> diff --git a/cpp/src/qpid/amqp_0_10/Header.cpp b/cpp/src/qpid/amqp_0_10/Header.cpp index 669c960e7f..d83814e969 100644 --- a/cpp/src/qpid/amqp_0_10/Header.cpp +++ b/cpp/src/qpid/amqp_0_10/Header.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "Header.h" +#include "qpid/amqp_0_10/Header.h" namespace qpid { namespace amqp_0_10 { diff --git a/cpp/src/qpid/amqp_0_10/Holder.h b/cpp/src/qpid/amqp_0_10/Holder.h index 8712db6c86..605d2e0ed5 100644 --- a/cpp/src/qpid/amqp_0_10/Holder.h +++ b/cpp/src/qpid/amqp_0_10/Holder.h @@ -22,7 +22,7 @@ * */ #include "qpid/framing/Blob.h" -#include "apply.h" +#include "qpid/amqp_0_10/apply.h" namespace qpid { namespace amqp_0_10 { diff --git a/cpp/src/qpid/amqp_0_10/Map.cpp b/cpp/src/qpid/amqp_0_10/Map.cpp index b517b8baba..af3b302d25 100644 --- a/cpp/src/qpid/amqp_0_10/Map.cpp +++ b/cpp/src/qpid/amqp_0_10/Map.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "Map.h" +#include "qpid/amqp_0_10/Map.h" #include "qpid/amqp_0_10/Struct32.h" #include "qpid/amqp_0_10/Array.h" #include <ostream> diff --git a/cpp/src/qpid/amqp_0_10/SessionHandler.cpp b/cpp/src/qpid/amqp_0_10/SessionHandler.cpp index 35587940e5..5f97d292bc 100644 --- a/cpp/src/qpid/amqp_0_10/SessionHandler.cpp +++ b/cpp/src/qpid/amqp_0_10/SessionHandler.cpp @@ -19,10 +19,11 @@ */ -#include "SessionHandler.h" +#include "qpid/amqp_0_10/SessionHandler.h" #include "qpid/SessionState.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/framing/AllInvoker.h" +#include "qpid/framing/enum.h" #include "qpid/log/Statement.h" @@ -33,30 +34,35 @@ namespace amqp_0_10 { using namespace framing; using namespace std; +void SessionHandler::checkAttached() { + if (!getState()) + throw NotAttachedException(QPID_MSG("Channel " << channel.get() << " is not attached")); +} + SessionHandler::SessionHandler(FrameHandler* out, ChannelId ch) - : channel(ch, out), peer(channel), ignoring(false), sendReady(), receiveReady() {} + : channel(ch, out), peer(channel), + awaitingDetached(false), + sendReady(), receiveReady() {} SessionHandler::~SessionHandler() {} namespace { bool isSessionControl(AMQMethodBody* m) { - return m && - m->amqpClassId() == SESSION_CLASS_ID; + return m && m->amqpClassId() == SESSION_CLASS_ID; } -bool isSessionDetachedControl(AMQMethodBody* m) { - return isSessionControl(m) && - m->amqpMethodId() == SESSION_DETACHED_METHOD_ID; -} -} // namespace -void SessionHandler::checkAttached() { - if (!getState()) - throw NotAttachedException( - QPID_MSG("Channel " << channel.get() << " is not attached")); - assert(getInHandler()); - assert(channel.next); +session::DetachCode convert(uint8_t code) { + switch(code) { + case 0: return session::DETACH_CODE_NORMAL; + case 1: return session::DETACH_CODE_SESSION_BUSY; + case 2: return session::DETACH_CODE_TRANSPORT_BUSY; + case 3: return session::DETACH_CODE_NOT_ATTACHED; + case 4: default: return session::DETACH_CODE_UNKNOWN_IDS; + } } +} // namespace + void SessionHandler::invoke(const AMQMethodBody& m) { framing::invoke(*this, m); } @@ -65,12 +71,19 @@ void SessionHandler::handleIn(AMQFrame& f) { // Note on channel states: a channel is attached if session != 0 AMQMethodBody* m = f.getBody()->getMethod(); try { - if (ignoring && !isSessionDetachedControl(m)) - return; - else if (isSessionControl(m)) + // Ignore all but detach controls while awaiting detach + if (awaitingDetached) { + if (!isSessionControl(m)) return; + if (m->amqpMethodId() != SESSION_DETACH_METHOD_ID && + m->amqpMethodId() != SESSION_DETACHED_METHOD_ID) + return; + } + if (isSessionControl(m)) { invoke(*m); + } else { - checkAttached(); + // Drop frames if we are detached. + if (!getState()) return; if (!receiveReady) throw IllegalStateException(QPID_MSG(getState()->getId() << ": Not ready to receive data")); if (!getState()->receiverRecord(f)) @@ -80,11 +93,21 @@ void SessionHandler::handleIn(AMQFrame& f) { getInHandler()->handle(f); } } + catch(const SessionException& e) { + QPID_LOG(error, "Execution exception: " << e.what()); + executionException(e.code, e.what()); // Let subclass handle this first. + framing::AMQP_AllProxy::Execution execution(channel); + AMQMethodBody* m = f.getMethod(); + SequenceNumber commandId; + if (getState()) commandId = getState()->receiverGetCurrent(); + execution.exception(e.code, commandId, m ? m->amqpClassId() : 0, m ? m->amqpMethodId() : 0, 0, e.what(), FieldTable()); + detaching(); + sendDetach(); + } catch(const ChannelException& e){ QPID_LOG(error, "Channel exception: " << e.what()); - if (getState()) - peer.detached(getState()->getId().getName(), e.code); - channelException(e.code, e.getMessage()); + channelException(e.code, e.what()); // Let subclass handle this first. + peer.detached(name, e.code); } catch(const ConnectionException& e) { QPID_LOG(error, "Connection exception: " << e.what()); @@ -92,16 +115,16 @@ void SessionHandler::handleIn(AMQFrame& f) { } catch(const std::exception& e) { QPID_LOG(error, "Unexpected exception: " << e.what()); - connectionException(connection::FRAMING_ERROR, e.what()); + connectionException(connection::CLOSE_CODE_FRAMING_ERROR, e.what()); } } namespace { bool isControl(const AMQFrame& f) { - return f.getMethod() && f.getMethod()->type() == framing::CONTROL; + return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_CONTROL; } bool isCommand(const AMQFrame& f) { - return f.getMethod() && f.getMethod()->type() == framing::COMMAND; + return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_COMMAND; } } // namespace @@ -117,19 +140,15 @@ void SessionHandler::handleOut(AMQFrame& f) { channel.handle(f); } -void SessionHandler::checkName(const std::string& name) { - checkAttached(); - if (name != getState()->getId().getName()) - throw InvalidArgumentException( - QPID_MSG("Incorrect session name: " << name - << ", expecting: " << getState()->getId().getName())); -} - -void SessionHandler::attach(const std::string& name, bool force) { +void SessionHandler::attach(const std::string& name_, bool force) { + // Save the name for possible session-busy exception. Session-busy + // can be thrown before we have attached the handler to a valid + // SessionState, and in that case we need the name to send peer.detached + name = name_; if (getState() && name == getState()->getId().getName()) return; // Idempotent if (getState()) - throw SessionBusyException( + throw TransportBusyException( QPID_MSG("Channel " << channel.get() << " already attached to " << getState()->getId())); setState(name, force); QPID_LOG(debug, "Attached channel " << channel.get() << " to " << getState()->getId()); @@ -140,21 +159,30 @@ void SessionHandler::attach(const std::string& name, bool force) { sendCommandPoint(getState()->senderGetCommandPoint()); } +#define CHECK_NAME(NAME, MSG) do { \ + checkAttached(); \ + if (NAME != getState()->getId().getName()) \ + throw InvalidArgumentException( \ + QPID_MSG(MSG << ": incorrect session name: " << NAME \ + << ", expecting: " << getState()->getId().getName())); \ + } while(0) + + void SessionHandler::attached(const std::string& name) { - checkName(name); + CHECK_NAME(name, "session.attached"); } void SessionHandler::detach(const std::string& name) { - checkName(name); - peer.detached(name, session::NORMAL); + CHECK_NAME(name, "session.detach"); + peer.detached(name, session::DETACH_CODE_NORMAL); handleDetach(); } void SessionHandler::detached(const std::string& name, uint8_t code) { - checkName(name); - ignoring = false; - if (code != session::NORMAL) - channelException(code, "session.detached from peer."); + CHECK_NAME(name, "session.detached"); + awaitingDetached = false; + if (code != session::DETACH_CODE_NORMAL) + channelException(convert(code), "session.detached from peer."); else { handleDetach(); } @@ -247,7 +275,7 @@ void SessionHandler::gap(const SequenceSet& /*commands*/) { void SessionHandler::sendDetach() { checkAttached(); - ignoring = true; + awaitingDetached = true; peer.detach(getState()->getId().getName()); } @@ -258,7 +286,6 @@ void SessionHandler::sendCompletion() { } void SessionHandler::sendAttach(bool force) { - checkAttached(); QPID_LOG(debug, "SessionHandler::sendAttach attach id=" << getState()->getId()); peer.attach(getState()->getId().getName(), force); if (getState()->hasState()) @@ -275,6 +302,12 @@ void SessionHandler::sendCommandPoint(const SessionPoint& point) { } } +void SessionHandler::markReadyToSend() { + if (!sendReady) { + sendReady = true; + } +} + void SessionHandler::sendTimeout(uint32_t t) { checkAttached(); peer.requestTimeout(t); diff --git a/cpp/src/qpid/amqp_0_10/SessionHandler.h b/cpp/src/qpid/amqp_0_10/SessionHandler.h index ccbe597bfc..fa6e6f4af6 100644 --- a/cpp/src/qpid/amqp_0_10/SessionHandler.h +++ b/cpp/src/qpid/amqp_0_10/SessionHandler.h @@ -26,6 +26,7 @@ #include "qpid/framing/AMQP_AllProxy.h" #include "qpid/framing/AMQP_AllOperations.h" #include "qpid/SessionState.h" +#include "qpid/CommonImportExport.h" namespace qpid { @@ -43,10 +44,8 @@ class SessionHandler : public framing::AMQP_AllOperations::SessionHandler, public framing::FrameHandler::InOutHandler { public: - typedef framing::AMQP_AllProxy::Session Peer; - - SessionHandler(framing::FrameHandler* out=0, uint16_t channel=0); - ~SessionHandler(); + QPID_COMMON_EXTERN SessionHandler(framing::FrameHandler* out=0, uint16_t channel=0); + QPID_COMMON_EXTERN ~SessionHandler(); void setChannel(uint16_t ch) { channel = ch; } uint16_t getChannel() const { return channel.get(); } @@ -57,58 +56,60 @@ class SessionHandler : public framing::AMQP_AllOperations::SessionHandler, virtual framing::FrameHandler* getInHandler() = 0; // Non-protocol methods, called locally to initiate some action. - void sendDetach(); - void sendCompletion(); - void sendAttach(bool force); - void sendTimeout(uint32_t t); - void sendFlush(); + QPID_COMMON_EXTERN void sendDetach(); + QPID_COMMON_EXTERN void sendCompletion(); + QPID_COMMON_EXTERN void sendAttach(bool force); + QPID_COMMON_EXTERN void sendTimeout(uint32_t t); + QPID_COMMON_EXTERN void sendFlush(); + QPID_COMMON_EXTERN void markReadyToSend();//TODO: only needed for inter-broker bridge; cleanup /** True if the handler is ready to send and receive */ bool ready() const; // Protocol methods - void attach(const std::string& name, bool force); - void attached(const std::string& name); - void detach(const std::string& name); - void detached(const std::string& name, uint8_t code); - - void requestTimeout(uint32_t t); - void timeout(uint32_t t); - - void commandPoint(const framing::SequenceNumber& id, uint64_t offset); - void expected(const framing::SequenceSet& commands, const framing::Array& fragments); - void confirmed(const framing::SequenceSet& commands,const framing::Array& fragments); - void completed(const framing::SequenceSet& commands, bool timelyReply); - void knownCompleted(const framing::SequenceSet& commands); - void flush(bool expected, bool confirmed, bool completed); - void gap(const framing::SequenceSet& commands); + QPID_COMMON_EXTERN void attach(const std::string& name, bool force); + QPID_COMMON_EXTERN void attached(const std::string& name); + QPID_COMMON_EXTERN void detach(const std::string& name); + QPID_COMMON_EXTERN void detached(const std::string& name, uint8_t code); + + QPID_COMMON_EXTERN void requestTimeout(uint32_t t); + QPID_COMMON_EXTERN void timeout(uint32_t t); + + QPID_COMMON_EXTERN void commandPoint(const framing::SequenceNumber& id, uint64_t offset); + QPID_COMMON_EXTERN void expected(const framing::SequenceSet& commands, const framing::Array& fragments); + QPID_COMMON_EXTERN void confirmed(const framing::SequenceSet& commands,const framing::Array& fragments); + QPID_COMMON_EXTERN void completed(const framing::SequenceSet& commands, bool timelyReply); + QPID_COMMON_EXTERN void knownCompleted(const framing::SequenceSet& commands); + QPID_COMMON_EXTERN void flush(bool expected, bool confirmed, bool completed); + QPID_COMMON_EXTERN void gap(const framing::SequenceSet& commands); protected: - virtual void invoke(const framing::AMQMethodBody& m); + QPID_COMMON_EXTERN virtual void invoke(const framing::AMQMethodBody& m); virtual void setState(const std::string& sessionName, bool force) = 0; - virtual void channelException(uint16_t code, const std::string& msg) = 0; - virtual void connectionException(uint16_t code, const std::string& msg) = 0; - + virtual void connectionException(framing::connection::CloseCode code, const std::string& msg) = 0; + virtual void channelException(framing::session::DetachCode, const std::string& msg) = 0; + virtual void executionException(framing::execution::ErrorCode, const std::string& msg) = 0; + virtual void detaching() = 0; // Notification of events virtual void readyToSend() {} virtual void readyToReceive() {} - virtual void handleDetach(); - virtual void handleIn(framing::AMQFrame&); - virtual void handleOut(framing::AMQFrame&); - - void checkAttached(); - void checkName(const std::string& name); + QPID_COMMON_EXTERN virtual void handleDetach(); + QPID_COMMON_EXTERN virtual void handleIn(framing::AMQFrame&); + QPID_COMMON_EXTERN virtual void handleOut(framing::AMQFrame&); framing::ChannelHandler channel; - Peer peer; - bool ignoring; - bool sendReady, receiveReady; private: + void checkAttached(); void sendCommandPoint(const SessionPoint&); + + framing::AMQP_AllProxy::Session peer; + std::string name; + bool awaitingDetached; + bool sendReady, receiveReady; }; }} // namespace qpid::amqp_0_10 diff --git a/cpp/src/qpid/amqp_0_10/Struct.h b/cpp/src/qpid/amqp_0_10/Struct.h index c0cea09c60..29ece84f6e 100644 --- a/cpp/src/qpid/amqp_0_10/Struct.h +++ b/cpp/src/qpid/amqp_0_10/Struct.h @@ -22,7 +22,7 @@ * */ -#include "built_in_types.h" +#include "qpid/amqp_0_10/built_in_types.h" #include <iosfwd> namespace qpid { diff --git a/cpp/src/qpid/amqp_0_10/Struct32.cpp b/cpp/src/qpid/amqp_0_10/Struct32.cpp index 541f02bcc4..2d38c09c21 100644 --- a/cpp/src/qpid/amqp_0_10/Struct32.cpp +++ b/cpp/src/qpid/amqp_0_10/Struct32.cpp @@ -18,7 +18,7 @@ * */ -#include "Struct32.h" +#include "qpid/amqp_0_10/Struct32.h" namespace qpid { namespace amqp_0_10 { diff --git a/cpp/src/qpid/amqp_0_10/Unit.cpp b/cpp/src/qpid/amqp_0_10/Unit.cpp index 75ea1c1b30..381de76dcc 100644 --- a/cpp/src/qpid/amqp_0_10/Unit.cpp +++ b/cpp/src/qpid/amqp_0_10/Unit.cpp @@ -18,8 +18,8 @@ * under the License. * */ -#include "Unit.h" -#include "Codec.h" +#include "qpid/amqp_0_10/Unit.h" +#include "qpid/amqp_0_10/Codec.h" namespace qpid { namespace amqp_0_10 { diff --git a/cpp/src/qpid/amqp_0_10/UnknownType.cpp b/cpp/src/qpid/amqp_0_10/UnknownType.cpp index 844891d732..cd45dd76db 100644 --- a/cpp/src/qpid/amqp_0_10/UnknownType.cpp +++ b/cpp/src/qpid/amqp_0_10/UnknownType.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "UnknownType.h" +#include "qpid/amqp_0_10/UnknownType.h" #include <boost/range/iterator_range.hpp> #include <ostream> diff --git a/cpp/src/qpid/amqp_0_10/UnknownType.h b/cpp/src/qpid/amqp_0_10/UnknownType.h index 1e4aa04bf4..77498871b3 100644 --- a/cpp/src/qpid/amqp_0_10/UnknownType.h +++ b/cpp/src/qpid/amqp_0_10/UnknownType.h @@ -21,9 +21,9 @@ * under the License. * */ +#include "qpid/sys/IntegerTypes.h" #include <vector> #include <iosfwd> -#include <stdint.h> namespace qpid { namespace amqp_0_10 { diff --git a/cpp/src/qpid/amqp_0_10/built_in_types.h b/cpp/src/qpid/amqp_0_10/built_in_types.h index 7665d91736..e95d1cf3e9 100644 --- a/cpp/src/qpid/amqp_0_10/built_in_types.h +++ b/cpp/src/qpid/amqp_0_10/built_in_types.h @@ -23,15 +23,15 @@ #include "qpid/Serializer.h" #include "qpid/framing/Uuid.h" +#include "qpid/sys/IntegerTypes.h" #include "qpid/sys/Time.h" -#include "Decimal.h" -#include "SerializableString.h" +#include "qpid/amqp_0_10/Decimal.h" +#include "qpid/amqp_0_10/SerializableString.h" #include <boost/array.hpp> #include <boost/range/iterator_range.hpp> #include <string> #include <ostream> #include <vector> -#include <stdint.h> /**@file Mapping from built-in AMQP types to C++ types */ @@ -145,7 +145,6 @@ typedef framing::SequenceSet SequenceSet; // Forward declare class types. class Map; class Struct32; -class List; class UnknownType; template <class T> struct ArrayDomain; diff --git a/cpp/src/qpid/assert.cpp b/cpp/src/qpid/assert.cpp index 5d039da528..bf36d3be86 100644 --- a/cpp/src/qpid/assert.cpp +++ b/cpp/src/qpid/assert.cpp @@ -35,7 +35,7 @@ void assert_fail(char const * expr, char const * function, char const * file, lo #ifdef NDEBUG throw framing::InternalErrorException(msg.str()); #else - std::cerr << msg << std::endl; + std::cerr << msg.str() << std::endl; abort(); #endif } diff --git a/cpp/src/qpid/broker/AclModule.h b/cpp/src/qpid/broker/AclModule.h index f766978d18..2f4f7eaacc 100644 --- a/cpp/src/qpid/broker/AclModule.h +++ b/cpp/src/qpid/broker/AclModule.h @@ -21,20 +21,30 @@ */ - -#include "qpid/shared_ptr.h" #include "qpid/RefCounted.h" +#include <boost/shared_ptr.hpp> #include <map> +#include <set> #include <string> - +#include <sstream> namespace qpid { -namespace acl{ -enum ObjectType {QUEUE,EXCHANGE,BROKER,LINK,ROUTE}; -enum Action {CONSUME,PUBLISH,CREATE,ACCESS,BIND,UNBIND,DELETE,PURGE,UPDATE}; -enum AclResult {ALLOW,ALLOWLOG,DENY,DENYNOLOG}; -} +namespace acl { + +enum ObjectType {OBJ_QUEUE, OBJ_EXCHANGE, OBJ_BROKER, OBJ_LINK, + OBJ_METHOD, OBJECTSIZE}; // OBJECTSIZE must be last in list +enum Action {ACT_CONSUME, ACT_PUBLISH, ACT_CREATE, ACT_ACCESS, ACT_BIND, + ACT_UNBIND, ACT_DELETE, ACT_PURGE, ACT_UPDATE, + ACTIONSIZE}; // ACTIONSIZE must be last in list +enum Property {PROP_NAME, PROP_DURABLE, PROP_OWNER, PROP_ROUTINGKEY, + PROP_PASSIVE, PROP_AUTODELETE, PROP_EXCLUSIVE, PROP_TYPE, + PROP_ALTERNATE, PROP_QUEUENAME, PROP_SCHEMAPACKAGE, + PROP_SCHEMACLASS, PROP_POLICYTYPE, PROP_MAXQUEUESIZE, + PROP_MAXQUEUECOUNT}; +enum AclResult {ALLOW, ALLOWLOG, DENY, DENYLOG}; + +} // namespace acl namespace broker { @@ -47,17 +57,225 @@ public: // effienty turn off ACL on message transfer. virtual bool doTransferAcl()=0; - virtual bool authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string name, - std::map<std::string, std::string>* params)=0; - virtual bool authorise(std::string id, acl::Action action, acl::ObjectType objType, std::string ExchangeName, - std::string RoutingKey)=0; + virtual bool authorise(const std::string& id, const acl::Action& action, const acl::ObjectType& objType, const std::string& name, + std::map<acl::Property, std::string>* params=0)=0; + virtual bool authorise(const std::string& id, const acl::Action& action, const acl::ObjectType& objType, const std::string& ExchangeName, + const std::string& RoutingKey)=0; // create specilied authorise methods for cases that need faster matching as needed. virtual ~AclModule() {}; }; +} // namespace broker + +namespace acl { + +class AclHelper { + private: + AclHelper(){} + public: + static inline ObjectType getObjectType(const std::string& str) { + if (str.compare("queue") == 0) return OBJ_QUEUE; + if (str.compare("exchange") == 0) return OBJ_EXCHANGE; + if (str.compare("broker") == 0) return OBJ_BROKER; + if (str.compare("link") == 0) return OBJ_LINK; + if (str.compare("method") == 0) return OBJ_METHOD; + throw str; + } + static inline std::string getObjectTypeStr(const ObjectType o) { + switch (o) { + case OBJ_QUEUE: return "queue"; + case OBJ_EXCHANGE: return "exchange"; + case OBJ_BROKER: return "broker"; + case OBJ_LINK: return "link"; + case OBJ_METHOD: return "method"; + default: assert(false); // should never get here + } + return ""; + } + static inline Action getAction(const std::string& str) { + if (str.compare("consume") == 0) return ACT_CONSUME; + if (str.compare("publish") == 0) return ACT_PUBLISH; + if (str.compare("create") == 0) return ACT_CREATE; + if (str.compare("access") == 0) return ACT_ACCESS; + if (str.compare("bind") == 0) return ACT_BIND; + if (str.compare("unbind") == 0) return ACT_UNBIND; + if (str.compare("delete") == 0) return ACT_DELETE; + if (str.compare("purge") == 0) return ACT_PURGE; + if (str.compare("update") == 0) return ACT_UPDATE; + throw str; + } + static inline std::string getActionStr(const Action a) { + switch (a) { + case ACT_CONSUME: return "consume"; + case ACT_PUBLISH: return "publish"; + case ACT_CREATE: return "create"; + case ACT_ACCESS: return "access"; + case ACT_BIND: return "bind"; + case ACT_UNBIND: return "unbind"; + case ACT_DELETE: return "delete"; + case ACT_PURGE: return "purge"; + case ACT_UPDATE: return "update"; + default: assert(false); // should never get here + } + return ""; + } + static inline Property getProperty(const std::string& str) { + if (str.compare("name") == 0) return PROP_NAME; + if (str.compare("durable") == 0) return PROP_DURABLE; + if (str.compare("owner") == 0) return PROP_OWNER; + if (str.compare("routingkey") == 0) return PROP_ROUTINGKEY; + if (str.compare("passive") == 0) return PROP_PASSIVE; + if (str.compare("autodelete") == 0) return PROP_AUTODELETE; + if (str.compare("exclusive") == 0) return PROP_EXCLUSIVE; + if (str.compare("type") == 0) return PROP_TYPE; + if (str.compare("alternate") == 0) return PROP_ALTERNATE; + if (str.compare("queuename") == 0) return PROP_QUEUENAME; + if (str.compare("schemapackage") == 0) return PROP_SCHEMAPACKAGE; + if (str.compare("schemaclass") == 0) return PROP_SCHEMACLASS; + if (str.compare("policytype") == 0) return PROP_POLICYTYPE; + if (str.compare("maxqueuesize") == 0) return PROP_MAXQUEUESIZE; + if (str.compare("maxqueuecount") == 0) return PROP_MAXQUEUECOUNT; + throw str; + } + static inline std::string getPropertyStr(const Property p) { + switch (p) { + case PROP_NAME: return "name"; + case PROP_DURABLE: return "durable"; + case PROP_OWNER: return "owner"; + case PROP_ROUTINGKEY: return "routingkey"; + case PROP_PASSIVE: return "passive"; + case PROP_AUTODELETE: return "autodelete"; + case PROP_EXCLUSIVE: return "exclusive"; + case PROP_TYPE: return "type"; + case PROP_ALTERNATE: return "alternate"; + case PROP_QUEUENAME: return "queuename"; + case PROP_SCHEMAPACKAGE: return "schemapackage"; + case PROP_SCHEMACLASS: return "schemaclass"; + case PROP_POLICYTYPE: return "policytype"; + case PROP_MAXQUEUESIZE: return "maxqueuesize"; + case PROP_MAXQUEUECOUNT: return "maxqueuecount"; + default: assert(false); // should never get here + } + return ""; + } + static inline AclResult getAclResult(const std::string& str) { + if (str.compare("allow") == 0) return ALLOW; + if (str.compare("allow-log") == 0) return ALLOWLOG; + if (str.compare("deny") == 0) return DENY; + if (str.compare("deny-log") == 0) return DENYLOG; + throw str; + } + static inline std::string getAclResultStr(const AclResult r) { + switch (r) { + case ALLOW: return "allow"; + case ALLOWLOG: return "allow-log"; + case DENY: return "deny"; + case DENYLOG: return "deny-log"; + default: assert(false); // should never get here + } + return ""; + } + + typedef std::set<Property> propSet; + typedef boost::shared_ptr<propSet> propSetPtr; + typedef std::pair<Action, propSetPtr> actionPair; + typedef std::map<Action, propSetPtr> actionMap; + typedef boost::shared_ptr<actionMap> actionMapPtr; + typedef std::pair<ObjectType, actionMapPtr> objectPair; + typedef std::map<ObjectType, actionMapPtr> objectMap; + typedef objectMap::const_iterator omCitr; + typedef boost::shared_ptr<objectMap> objectMapPtr; + typedef std::map<Property, std::string> propMap; + typedef propMap::const_iterator propMapItr; + + // This map contains the legal combinations of object/action/properties found in an ACL file + static void loadValidationMap(objectMapPtr& map) { + if (!map.get()) return; + map->clear(); + propSetPtr p0; // empty ptr, used for no properties + + // == Exchanges == + + propSetPtr p1(new propSet); + p1->insert(PROP_TYPE); + p1->insert(PROP_ALTERNATE); + p1->insert(PROP_PASSIVE); + p1->insert(PROP_DURABLE); + + propSetPtr p2(new propSet); + p2->insert(PROP_ROUTINGKEY); + + propSetPtr p3(new propSet); + p3->insert(PROP_QUEUENAME); + p3->insert(PROP_ROUTINGKEY); + + actionMapPtr a0(new actionMap); + a0->insert(actionPair(ACT_CREATE, p1)); + a0->insert(actionPair(ACT_DELETE, p0)); + a0->insert(actionPair(ACT_ACCESS, p0)); + a0->insert(actionPair(ACT_BIND, p2)); + a0->insert(actionPair(ACT_UNBIND, p2)); + a0->insert(actionPair(ACT_ACCESS, p3)); + a0->insert(actionPair(ACT_PUBLISH, p0)); + + map->insert(objectPair(OBJ_EXCHANGE, a0)); + + // == Queues == + + propSetPtr p4(new propSet); + p4->insert(PROP_ALTERNATE); + p4->insert(PROP_PASSIVE); + p4->insert(PROP_DURABLE); + p4->insert(PROP_EXCLUSIVE); + p4->insert(PROP_AUTODELETE); + p4->insert(PROP_POLICYTYPE); + p4->insert(PROP_MAXQUEUESIZE); + p4->insert(PROP_MAXQUEUECOUNT); + + actionMapPtr a1(new actionMap); + a1->insert(actionPair(ACT_ACCESS, p0)); + a1->insert(actionPair(ACT_CREATE, p4)); + a1->insert(actionPair(ACT_PURGE, p0)); + a1->insert(actionPair(ACT_DELETE, p0)); + a1->insert(actionPair(ACT_CONSUME, p0)); + + map->insert(objectPair(OBJ_QUEUE, a1)); + + // == Links == + + actionMapPtr a2(new actionMap); + a2->insert(actionPair(ACT_CREATE, p0)); + + map->insert(objectPair(OBJ_LINK, a2)); + + // == Method == + + propSetPtr p5(new propSet); + p5->insert(PROP_SCHEMAPACKAGE); + p5->insert(PROP_SCHEMACLASS); + + actionMapPtr a4(new actionMap); + a4->insert(actionPair(ACT_ACCESS, p5)); + + map->insert(objectPair(OBJ_METHOD, a4)); + } + + static std::string propertyMapToString(const std::map<Property, std::string>* params) { + std::ostringstream ss; + ss << "{"; + if (params) + { + for (propMapItr pMItr = params->begin(); pMItr != params->end(); pMItr++) { + ss << " " << getPropertyStr((Property) pMItr-> first) << "=" << pMItr->second; + } + } + ss << " }"; + return ss.str(); + } +}; -}} // namespace qpid::broker +}} // namespace qpid::acl #endif // QPID_ACLMODULE_ACL_H diff --git a/cpp/src/qpid/broker/Bridge.cpp b/cpp/src/qpid/broker/Bridge.cpp index 53bed020e2..79e311d032 100644 --- a/cpp/src/qpid/broker/Bridge.cpp +++ b/cpp/src/qpid/broker/Bridge.cpp @@ -18,36 +18,62 @@ * under the License. * */ -#include "Bridge.h" -#include "ConnectionState.h" -#include "LinkRegistry.h" +#include "qpid/broker/Bridge.h" +#include "qpid/broker/ConnectionState.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/SessionState.h" -#include "qpid/agent/ManagementAgent.h" -#include "qpid/framing/FieldTable.h" +#include "qpid/management/ManagementAgent.h" #include "qpid/framing/Uuid.h" #include "qpid/log/Statement.h" +#include <iostream> using qpid::framing::FieldTable; using qpid::framing::Uuid; using qpid::framing::Buffer; using qpid::management::ManagementAgent; +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace +{ +const std::string qpidFedOp("qpid.fed.op"); +const std::string qpidFedTags("qpid.fed.tags"); +const std::string qpidFedOrigin("qpid.fed.origin"); + +const std::string fedOpBind("B"); +const std::string fedOpUnbind("U"); +const std::string fedOpReorigin("R"); +const std::string fedOpHello("H"); +} namespace qpid { namespace broker { +void Bridge::PushHandler::handle(framing::AMQFrame& frame) +{ + conn->received(frame); +} + Bridge::Bridge(Link* _link, framing::ChannelId _id, CancellationListener l, - const management::ArgsLinkBridge& _args) : + const _qmf::ArgsLinkBridge& _args) : link(_link), id(_id), args(_args), mgmtObject(0), - listener(l), name(Uuid(true).str()), persistenceId(0) + listener(l), name(Uuid(true).str()), queueName("bridge_queue_"), persistenceId(0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + std::stringstream title; + title << id << "_" << link->getBroker()->getFederationTag(); + queueName += title.str(); + ManagementAgent* agent = link->getBroker()->getManagementAgent(); if (agent != 0) { - mgmtObject = new management::Bridge(agent, this, link, id, args.i_durable, args.i_src, args.i_dest, - args.i_key, args.i_srcIsQueue, args.i_srcIsLocal, - args.i_tag, args.i_excludes); + mgmtObject = new _qmf::Bridge + (agent, this, link, id, 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); if (!args.i_durable) agent->addObject(mgmtObject); } + QPID_LOG(debug, "Bridge created from " << args.i_src << " to " << args.i_dest); } Bridge::~Bridge() @@ -55,62 +81,111 @@ Bridge::~Bridge() mgmtObject->resourceDestroy(); } -void Bridge::create(ConnectionState& c) +void Bridge::create(Connection& c) { - channelHandler.reset(new framing::ChannelHandler(id, &(c.getOutput()))); - session.reset(new framing::AMQP_ServerProxy::Session(*channelHandler)); - peer.reset(new framing::AMQP_ServerProxy(*channelHandler)); + connState = &c; + conn = &c; + FieldTable options; + if (args.i_sync) options.setInt("qpid.sync_frequency", args.i_sync); + SessionHandler& sessionHandler = c.getChannel(id); + if (args.i_srcIsLocal) { + if (args.i_dynamic) + throw Exception("Dynamic routing not supported for push routes"); + // Point the bridging commands at the local connection handler + pushHandler.reset(new PushHandler(&c)); + channelHandler.reset(new framing::ChannelHandler(id, pushHandler.get())); - session->attach(name, false); - session->commandPoint(0,0); + session.reset(new framing::AMQP_ServerProxy::Session(*channelHandler)); + peer.reset(new framing::AMQP_ServerProxy(*channelHandler)); + + session->attach(name, false); + session->commandPoint(0,0); + } else { + sessionHandler.attachAs(name); + // Point the bridging commands at the remote peer broker + peer.reset(new framing::AMQP_ServerProxy(sessionHandler.out)); + } - if (args.i_srcIsLocal) { - //TODO: handle 'push' here... simplest way is to create frames and pass them to Connection::received() + if (args.i_srcIsLocal) sessionHandler.getSession()->disableReceiverTracking(); + if (args.i_srcIsQueue) { + peer->getMessage().subscribe(args.i_src, args.i_dest, args.i_sync ? 0 : 1, 0, false, "", 0, options); + peer->getMessage().flow(args.i_dest, 0, 0xFFFFFFFF); + peer->getMessage().flow(args.i_dest, 1, 0xFFFFFFFF); + QPID_LOG(debug, "Activated route from queue " << args.i_src << " to " << args.i_dest); } else { - if (args.i_srcIsQueue) { - peer->getMessage().subscribe(args.i_src, 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 queueSettings; + + if (args.i_tag.size()) { + queueSettings.setString("qpid.trace.id", args.i_tag); } else { - string queue = "bridge_queue_"; - queue += Uuid(true).str(); - FieldTable queueSettings; - if (args.i_tag.size()) { - queueSettings.setString("qpid.trace.id", args.i_tag); - } - if (args.i_excludes.size()) { - queueSettings.setString("qpid.trace.exclude", args.i_excludes); - } - - bool durable = false;//should this be an arg, or would be use srcIsQueue for durable queues? - bool autoDelete = !durable;//auto delete transient queues? - peer->getQueue().declare(queue, "", false, durable, true, autoDelete, queueSettings); - peer->getExchange().bind(queue, args.i_src, args.i_key, FieldTable()); - peer->getMessage().subscribe(queue, args.i_dest, 1, 0, false, "", 0, FieldTable()); - peer->getMessage().flow(args.i_dest, 0, 0xFFFFFFFF); - peer->getMessage().flow(args.i_dest, 1, 0xFFFFFFFF); + 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 autoDelete = !durable;//auto delete transient queues? + peer->getQueue().declare(queueName, "", false, durable, true, autoDelete, queueSettings); + if (!args.i_dynamic) + peer->getExchange().bind(queueName, args.i_src, args.i_key, FieldTable()); + 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); + + if (args.i_dynamic) { + Exchange::shared_ptr exchange = link->getBroker()->getExchanges().get(args.i_src); + if (exchange.get() == 0) + throw Exception("Exchange not found for dynamic route"); + exchange->registerDynamicBridge(this); + QPID_LOG(debug, "Activated dynamic route for exchange " << args.i_src); + } else { + QPID_LOG(debug, "Activated static route from exchange " << args.i_src << " to " << args.i_dest); } } + if (args.i_srcIsLocal) sessionHandler.getSession()->enableReceiverTracking(); } -void Bridge::cancel() +void Bridge::cancel(Connection& c) { + if (args.i_srcIsLocal) { + //recreate peer to be sure that the session handler reference + //is valid (it could have been deleted due to a detach) + SessionHandler& sessionHandler = c.getChannel(id); + peer.reset(new framing::AMQP_ServerProxy(sessionHandler.out)); + } peer->getMessage().cancel(args.i_dest); peer->getSession().detach(name); } +void Bridge::closed() +{ + if (args.i_dynamic) { + Exchange::shared_ptr exchange = link->getBroker()->getExchanges().get(args.i_src); + if (exchange.get() != 0) + exchange->removeDynamicBridge(this); + } +} + void Bridge::destroy() { listener(this); } -void Bridge::setPersistenceId(uint64_t id) const +void Bridge::setPersistenceId(uint64_t pId) const { if (mgmtObject != 0 && persistenceId == 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); - agent->addObject (mgmtObject, id); + ManagementAgent* agent = link->getBroker()->getManagementAgent(); + agent->addObject (mgmtObject, pId); } - persistenceId = id; + persistenceId = pId; } const string& Bridge::getName() const @@ -138,9 +213,11 @@ Bridge::shared_ptr Bridge::decode(LinkRegistry& links, Buffer& buffer) bool is_local(buffer.getOctet()); buffer.getShortString(id); buffer.getShortString(excludes); + bool dynamic(buffer.getOctet()); + uint16_t sync = buffer.getShort(); return links.declare(host, port, durable, src, dest, key, - is_queue, is_local, id, excludes).first; + is_queue, is_local, id, excludes, dynamic, sync).first; } void Bridge::encode(Buffer& buffer) const @@ -156,6 +233,8 @@ void Bridge::encode(Buffer& buffer) const buffer.putOctet(args.i_srcIsLocal ? 1 : 0); buffer.putShortString(args.i_tag); buffer.putShortString(args.i_excludes); + buffer.putOctet(args.i_dynamic ? 1 : 0); + buffer.putShort(args.i_sync); } uint32_t Bridge::encodedSize() const @@ -170,7 +249,9 @@ uint32_t Bridge::encodedSize() const + 1 // srcIsQueue + 1 // srcIsLocal + args.i_tag.size() + 1 - + args.i_excludes.size() + 1; + + args.i_excludes.size() + 1 + + 1 // dynamic + + 2; // sync } management::ManagementObject* Bridge::GetManagementObject (void) const @@ -178,9 +259,11 @@ management::ManagementObject* Bridge::GetManagementObject (void) const return (management::ManagementObject*) mgmtObject; } -management::Manageable::status_t Bridge::ManagementMethod(uint32_t methodId, management::Args& /*args*/) +management::Manageable::status_t Bridge::ManagementMethod(uint32_t methodId, + management::Args& /*args*/, + string&) { - if (methodId == management::Bridge::METHOD_CLOSE) { + if (methodId == _qmf::Bridge::METHOD_CLOSE) { //notify that we are closed destroy(); return management::Manageable::STATUS_OK; @@ -189,4 +272,53 @@ management::Manageable::status_t Bridge::ManagementMethod(uint32_t methodId, man } } +void Bridge::propagateBinding(const string& key, const string& tagList, + const string& op, const string& origin) +{ + const string& localTag = link->getBroker()->getFederationTag(); + const string& peerTag = connState->getFederationPeerTag(); + + if (tagList.find(peerTag) == tagList.npos) { + FieldTable bindArgs; + string newTagList(tagList + string(tagList.empty() ? "" : ",") + localTag); + + bindArgs.setString(qpidFedOp, op); + bindArgs.setString(qpidFedTags, newTagList); + if (origin.empty()) + bindArgs.setString(qpidFedOrigin, localTag); + else + bindArgs.setString(qpidFedOrigin, origin); + + conn->requestIOProcessing(boost::bind(&Bridge::ioThreadPropagateBinding, this, + queueName, args.i_src, key, bindArgs)); + } +} + +void Bridge::sendReorigin() +{ + FieldTable bindArgs; + + bindArgs.setString(qpidFedOp, fedOpReorigin); + bindArgs.setString(qpidFedTags, link->getBroker()->getFederationTag()); + + conn->requestIOProcessing(boost::bind(&Bridge::ioThreadPropagateBinding, this, + queueName, args.i_src, args.i_key, bindArgs)); +} + +void Bridge::ioThreadPropagateBinding(const string& queue, const string& exchange, const string& key, FieldTable args) +{ + peer->getExchange().bind(queue, exchange, key, args); +} + +bool Bridge::containsLocalTag(const string& tagList) const +{ + const string& localTag = link->getBroker()->getFederationTag(); + return (tagList.find(localTag) != tagList.npos); +} + +const string& Bridge::getLocalTag() const +{ + return link->getBroker()->getFederationTag(); +} + }} diff --git a/cpp/src/qpid/broker/Bridge.h b/cpp/src/qpid/broker/Bridge.h index 06fba25268..7dae5c37a1 100644 --- a/cpp/src/qpid/broker/Bridge.h +++ b/cpp/src/qpid/broker/Bridge.h @@ -21,13 +21,16 @@ #ifndef _Bridge_ #define _Bridge_ -#include "PersistableConfig.h" +#include "qpid/broker/PersistableConfig.h" #include "qpid/framing/AMQP_ServerProxy.h" #include "qpid/framing/ChannelHandler.h" #include "qpid/framing/Buffer.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/FieldTable.h" #include "qpid/management/Manageable.h" -#include "qpid/management/ArgsLinkBridge.h" -#include "qpid/management/Bridge.h" +#include "qpid/broker/Exchange.h" +#include "qmf/org/apache/qpid/broker/ArgsLinkBridge.h" +#include "qmf/org/apache/qpid/broker/Bridge.h" #include <boost/function.hpp> #include <memory> @@ -35,26 +38,31 @@ namespace qpid { namespace broker { +class Connection; class ConnectionState; class Link; class LinkRegistry; -class Bridge : public PersistableConfig, public management::Manageable +class Bridge : public PersistableConfig, public management::Manageable, public Exchange::DynamicBridge { public: typedef boost::shared_ptr<Bridge> shared_ptr; typedef boost::function<void(Bridge*)> CancellationListener; - Bridge(Link* link, framing::ChannelId id, CancellationListener l, const management::ArgsLinkBridge& args); + Bridge(Link* link, framing::ChannelId id, CancellationListener l, + const qmf::org::apache::qpid::broker::ArgsLinkBridge& args); ~Bridge(); - void create(ConnectionState& c); - void cancel(); + void create(Connection& c); + void cancel(Connection& c); + void closed(); void destroy(); bool isDurable() { return args.i_durable; } management::ManagementObject* GetManagementObject() const; - management::Manageable::status_t ManagementMethod(uint32_t methodId, management::Args& args); + management::Manageable::status_t ManagementMethod(uint32_t methodId, + management::Args& args, + std::string& text); // PersistableConfig: void setPersistenceId(uint64_t id) const; @@ -64,18 +72,35 @@ public: const std::string& getName() const; static Bridge::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer); + // Exchange::DynamicBridge methods + void propagateBinding(const std::string& key, const std::string& tagList, const std::string& op, const std::string& origin); + void sendReorigin(); + void ioThreadPropagateBinding(const string& queue, const string& exchange, const string& key, framing::FieldTable args); + bool containsLocalTag(const std::string& tagList) const; + const std::string& getLocalTag() const; + private: + struct PushHandler : framing::FrameHandler { + PushHandler(Connection* c) { conn = c; } + void handle(framing::AMQFrame& frame); + Connection* conn; + }; + + std::auto_ptr<PushHandler> pushHandler; std::auto_ptr<framing::ChannelHandler> channelHandler; std::auto_ptr<framing::AMQP_ServerProxy::Session> session; std::auto_ptr<framing::AMQP_ServerProxy> peer; Link* link; framing::ChannelId id; - management::ArgsLinkBridge args; - management::Bridge* mgmtObject; + qmf::org::apache::qpid::broker::ArgsLinkBridge args; + qmf::org::apache::qpid::broker::Bridge* mgmtObject; CancellationListener listener; std::string name; + std::string queueName; mutable uint64_t persistenceId; + ConnectionState* connState; + Connection* conn; }; diff --git a/cpp/src/qpid/broker/Broker.cpp b/cpp/src/qpid/broker/Broker.cpp index 4d7c07649b..849bf6d1f5 100644 --- a/cpp/src/qpid/broker/Broker.cpp +++ b/cpp/src/qpid/broker/Broker.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 @@ -19,23 +19,27 @@ * */ -#include "config.h" -#include "Broker.h" -#include "DirectExchange.h" -#include "FanOutExchange.h" -#include "HeadersExchange.h" -#include "MessageStoreModule.h" -#include "NullMessageStore.h" -#include "RecoveryManagerImpl.h" -#include "TopicExchange.h" -#include "Link.h" - -#include "qpid/management/PackageQpid.h" +#include "qpid/broker/Broker.h" +#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/RecoveryManagerImpl.h" +#include "qpid/broker/SaslAuthenticator.h" +#include "qpid/broker/SecureConnectionFactory.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/ExpiryPolicy.h" + +#include "qmf/org/apache/qpid/broker/Package.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerEcho.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerQueueMoveMessages.h" #include "qpid/management/ManagementExchange.h" -#include "qpid/management/ArgsBrokerEcho.h" #include "qpid/log/Statement.h" #include "qpid/framing/AMQFrame.h" #include "qpid/framing/ProtocolInitiation.h" +#include "qpid/framing/Uuid.h" #include "qpid/sys/ProtocolFactory.h" #include "qpid/sys/Poller.h" #include "qpid/sys/Dispatcher.h" @@ -45,20 +49,14 @@ #include "qpid/sys/ConnectionInputHandlerFactory.h" #include "qpid/sys/TimeoutHandler.h" #include "qpid/sys/SystemInfo.h" +#include "qpid/Address.h" #include "qpid/Url.h" +#include "qpid/Version.h" #include <boost/bind.hpp> #include <iostream> #include <memory> -#include <stdlib.h> - -#if HAVE_SASL -#include <sasl/sasl.h> -static const bool AUTH_DEFAULT=true; -#else -static const bool AUTH_DEFAULT=false; -#endif using qpid::sys::ProtocolFactory; using qpid::sys::Poller; @@ -66,11 +64,11 @@ using qpid::sys::Dispatcher; using qpid::sys::Thread; using qpid::framing::FrameHandler; using qpid::framing::ChannelId; -using qpid::management::ManagementBroker; +using qpid::management::ManagementAgent; using qpid::management::ManagementObject; using qpid::management::Manageable; using qpid::management::Args; -using qpid::management::ArgsBrokerEcho; +namespace _qmf = qmf::org::apache::qpid::broker; namespace qpid { namespace broker { @@ -85,22 +83,26 @@ Broker::Options::Options(const std::string& name) : stagingThreshold(5000000), enableMgmt(1), mgmtPubInterval(10), - auth(AUTH_DEFAULT), + queueCleanInterval(60*10),//10 minutes + auth(SaslAuthenticator::available()), realm("QPID"), replayFlushLimit(0), replayHardLimit(0), queueLimit(100*1048576/*100M default limit*/), - tcpNoDelay(false) + tcpNoDelay(false), + requireEncrypted(false), + maxSessionRate(0), + asyncQueueEvents(true) { int c = sys::SystemInfo::concurrency(); workerThreads=c+1; - char *home = ::getenv("HOME"); + std::string home = getHome(); - if (home == 0) - dataDir += "/tmp"; + if (home.length() == 0) + dataDir += DEFAULT_DATA_DIR_LOCATION; else dataDir += home; - dataDir += "/.qpidd"; + dataDir += DEFAULT_DATA_DIR_NAME; addOptions() ("data-dir", optValue(dataDir,"DIR"), "Directory to contain persistent data generated by the broker") @@ -112,10 +114,16 @@ Broker::Options::Options(const std::string& name) : ("staging-threshold", optValue(stagingThreshold, "N"), "Stages messages over N bytes to disk") ("mgmt-enable,m", optValue(enableMgmt,"yes|no"), "Enable Management") ("mgmt-pub-interval", optValue(mgmtPubInterval, "SECONDS"), "Management Publish Interval") + ("queue-purge-interval", optValue(queueCleanInterval, "SECONDS"), + "Interval between attempts to purge any expired messages from queues") ("auth", optValue(auth, "yes|no"), "Enable authentication, if disabled all incoming connections will be trusted") ("realm", optValue(realm, "REALM"), "Use the given realm when performing authentication") ("default-queue-limit", optValue(queueLimit, "BYTES"), "Default maximum size for queues (in bytes)") - ("tcp-nodelay", optValue(tcpNoDelay), "Set TCP_NODELAY on TCP connections"); + ("tcp-nodelay", optValue(tcpNoDelay), "Set TCP_NODELAY on TCP connections") + ("require-encryption", optValue(requireEncrypted), "Only accept connections that are encrypted") + ("known-hosts-url", optValue(knownHosts, "URL or 'none'"), "URL to send as 'known-hosts' to clients ('none' implies empty list)") + ("max-session-rate", optValue(maxSessionRate, "MESSAGES/S"), "Sets the maximum message rate per session (0=unlimited)") + ("async-queue-events", optValue(asyncQueueEvents, "yes|no"), "Set Queue Events async, used for services like replication"); } const std::string empty; @@ -124,79 +132,99 @@ 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"); Broker::Broker(const Broker::Options& conf) : poller(new Poller), config(conf), - managementAgentSingleton(!config.enableMgmt), - store(0), - acl(0), - dataDir(conf.noDataDir ? std::string () : conf.dataDir), + managementAgent(conf.enableMgmt ? new ManagementAgent() : 0), + store(new NullMessageStore), + acl(0), + dataDir(conf.noDataDir ? std::string() : conf.dataDir), + queues(this), + exchanges(this), links(this), - factory(*this), + factory(new SecureConnectionFactory(*this)), + dtxManager(timer), sessionManager( qpid::SessionState::Configuration( conf.replayFlushLimit*1024, // convert kb to bytes. conf.replayHardLimit*1024), - *this) + *this), + queueCleaner(queues, timer), + queueEvents(poller,!conf.asyncQueueEvents), + recovery(true), + clusterUpdatee(false), + expiryPolicy(new ExpiryPolicy), + connectionCounter(conf.maxConnections), + getKnownBrokers(boost::bind(&Broker::getKnownBrokersImpl, this)) { - if(conf.enableMgmt){ + if (conf.enableMgmt) { QPID_LOG(info, "Management enabled"); - managementAgent = managementAgentSingleton.getInstance(); - ((ManagementBroker*) managementAgent)->configure - (dataDir.isEnabled () ? dataDir.getPath () : string (), - conf.mgmtPubInterval, this, conf.workerThreads + 3); - qpid::management::PackageQpid packageInitializer (managementAgent); - - System* system = new System (dataDir.isEnabled () ? dataDir.getPath () : string ()); - systemObject = System::shared_ptr (system); - - mgmtObject = new management::Broker (managementAgent, this, system, conf.port); - mgmtObject->set_workerThreads (conf.workerThreads); - mgmtObject->set_maxConns (conf.maxConnections); - mgmtObject->set_connBacklog (conf.connectionBacklog); - mgmtObject->set_stagingThreshold (conf.stagingThreshold); - mgmtObject->set_mgmtPubInterval (conf.mgmtPubInterval); - mgmtObject->set_version (PACKAGE_VERSION); - mgmtObject->set_dataDirEnabled (dataDir.isEnabled ()); - mgmtObject->set_dataDir (dataDir.getPath ()); - - managementAgent->addObject (mgmtObject, 2, 1); + managementAgent->configure(dataDir.isEnabled() ? dataDir.getPath() : string(), + conf.mgmtPubInterval, this, conf.workerThreads + 3); + _qmf::Package packageInitializer(managementAgent.get()); + + System* system = new System (dataDir.isEnabled() ? dataDir.getPath() : string(), this); + systemObject = System::shared_ptr(system); + + mgmtObject = new _qmf::Broker(managementAgent.get(), this, system, conf.port); + mgmtObject->set_workerThreads(conf.workerThreads); + mgmtObject->set_maxConns(conf.maxConnections); + mgmtObject->set_connBacklog(conf.connectionBacklog); + mgmtObject->set_stagingThreshold(conf.stagingThreshold); + mgmtObject->set_mgmtPubInterval(conf.mgmtPubInterval); + mgmtObject->set_version(qpid::version); + if (dataDir.isEnabled()) + mgmtObject->set_dataDir(dataDir.getPath()); + else + mgmtObject->clr_dataDir(); + + managementAgent->addObject(mgmtObject, 0x1000000000000002LL); // Since there is currently no support for virtual hosts, a placeholder object // representing the implied single virtual host is added here to keep the // management schema correct. - Vhost* vhost = new Vhost (this); - vhostObject = Vhost::shared_ptr (vhost); - - queues.setParent (vhost); - exchanges.setParent (vhost); - links.setParent (vhost); + Vhost* vhost = new Vhost(this, this); + vhostObject = Vhost::shared_ptr(vhost); + framing::Uuid uuid(managementAgent->getUuid()); + federationTag = uuid.str(); + vhostObject->setFederationTag(federationTag); + + queues.setParent(vhost); + exchanges.setParent(vhost); + links.setParent(vhost); + } else { + // Management is disabled so there is no broker management ID. + // Create a unique uuid to use as the federation tag. + framing::Uuid uuid(true); + federationTag = uuid.str(); } QueuePolicy::setDefaultMaxSize(conf.queueLimit); + queues.setQueueEvents(&queueEvents); // Early-Initialize plugins - const Plugin::Plugins& plugins=Plugin::getPlugins(); - for (Plugin::Plugins::const_iterator i = plugins.begin(); - i != plugins.end(); - i++) - (*i)->earlyInitialize(*this); + Plugin::earlyInitAll(*this); // If no plugin store module registered itself, set up the null store. - if (store == 0) - setStore (new NullMessageStore (false)); - - queues.setStore (store); - dtxManager.setStore (store); - links.setStore (store); + if (NullMessageStore::isNullStore(store.get())) + setStore(); exchanges.declare(empty, DirectExchange::typeName); // Default exchange. - - if (store != 0) { - RecoveryManagerImpl recoverer(queues, exchanges, links, dtxManager, - conf.stagingThreshold); - store->recover(recoverer); + + if (store.get() != 0) { + // The cluster plug-in will setRecovery(false) on all but the first + // broker to join a cluster. + if (getRecovery()) { + RecoveryManagerImpl recoverer(queues, exchanges, links, dtxManager, + conf.stagingThreshold); + 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 + } } //ensure standard exchanges exist (done after recovery from store) @@ -209,9 +237,8 @@ Broker::Broker(const Broker::Options& conf) : exchanges.declare(qpid_management, ManagementExchange::typeName); Exchange::shared_ptr mExchange = exchanges.get (qpid_management); Exchange::shared_ptr dExchange = exchanges.get (amq_direct); - ((ManagementBroker*) managementAgent)->setExchange (mExchange, dExchange); - dynamic_pointer_cast<ManagementExchange>(mExchange)->setManagmentAgent - ((ManagementBroker*) managementAgent); + managementAgent->setExchange(mExchange, dExchange); + boost::dynamic_pointer_cast<ManagementExchange>(mExchange)->setManagmentAgent(managementAgent.get()); } else QPID_LOG(info, "Management not enabled"); @@ -220,29 +247,33 @@ Broker::Broker(const Broker::Options& conf) : * SASL setup, can fail and terminate startup */ if (conf.auth) { -#if HAVE_SASL - int code = sasl_server_init(NULL, BROKER_SASL_NAME); - if (code != SASL_OK) { - // TODO: Figure out who owns the char* returned by - // sasl_errstring, though it probably does not matter much - throw Exception(sasl_errstring(code, NULL, NULL)); - } + SaslAuthenticator::init(qpid::saslName); QPID_LOG(info, "SASL enabled"); -#else - throw Exception("Requested authentication but SASL unavailable"); -#endif + } else { + QPID_LOG(notice, "SASL disabled: No Authentication Performed"); } // Initialize plugins - for (Plugin::Plugins::const_iterator i = plugins.begin(); - i != plugins.end(); - i++) - (*i)->initialize(*this); + Plugin::initializeAll(*this); + + 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) { + knownBrokers.push_back(Url(conf.knownHosts)); + } } void Broker::declareStandardExchange(const std::string& name, const std::string& type) { - bool storeEnabled = store != NULL; + bool storeEnabled = store.get() != NULL; std::pair<Exchange::shared_ptr, bool> status = exchanges.declare(name, type, storeEnabled); if (status.second && storeEnabled) { store->create(*status.first, framing::FieldTable ()); @@ -250,28 +281,32 @@ void Broker::declareStandardExchange(const std::string& name, const std::string& } -boost::intrusive_ptr<Broker> Broker::create(int16_t port) +boost::intrusive_ptr<Broker> Broker::create(int16_t port) { Options config; config.port=port; return create(config); } -boost::intrusive_ptr<Broker> Broker::create(const Options& opts) +boost::intrusive_ptr<Broker> Broker::create(const Options& opts) { return boost::intrusive_ptr<Broker>(new Broker(opts)); } -void Broker::setStore (MessageStore* _store) +void Broker::setStore (boost::shared_ptr<MessageStore>& _store) { - assert (store == 0 && _store != 0); - if (store == 0 && _store != 0) - store = new MessageStoreModule (_store); + store.reset(new MessageStoreModule (_store)); + setStore(); +} + +void Broker::setStore () { + queues.setStore (store.get()); + dtxManager.setStore (store.get()); + links.setStore (store.get()); } void Broker::run() { - accept(); - + QPID_LOG(notice, "Broker running"); Dispatcher d(poller); int numIOThreads = config.workerThreads; std::vector<Thread> t(numIOThreads-1); @@ -282,7 +317,7 @@ void Broker::run() { // Run final thread d.run(); - + // Now wait for n-1 io threads to exit for (int i=0; i<numIOThreads-1; ++i) { t[i].join(); @@ -298,13 +333,10 @@ void Broker::shutdown() { Broker::~Broker() { shutdown(); + queueEvents.shutdown(); finalize(); // Finalize any plugins. - delete store; - if (config.auth) { -#if HAVE_SASL - sasl_done(); -#endif - } + if (config.auth) + SaslAuthenticator::fini(); QPID_LOG(notice, "Shut down"); } @@ -319,7 +351,8 @@ Manageable* Broker::GetVhostObject(void) const } Manageable::status_t Broker::ManagementMethod (uint32_t methodId, - Args& args) + Args& args, + string&) { Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; @@ -327,27 +360,36 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, switch (methodId) { - case management::Broker::METHOD_ECHO : + case _qmf::Broker::METHOD_ECHO : status = Manageable::STATUS_OK; break; - case management::Broker::METHOD_CONNECT : { - management::ArgsBrokerConnect& hp= - dynamic_cast<management::ArgsBrokerConnect&>(args); - - if (hp.i_useSsl) - return Manageable::STATUS_FEATURE_NOT_IMPLEMENTED; - + case _qmf::Broker::METHOD_CONNECT : { + _qmf::ArgsBrokerConnect& hp= + dynamic_cast<_qmf::ArgsBrokerConnect&>(args); + + string transport = hp.i_transport.empty() ? TCP_TRANSPORT : hp.i_transport; + if (!getProtocolFactory(transport)) { + QPID_LOG(error, "Transport '" << transport << "' not supported"); + return Manageable::STATUS_NOT_IMPLEMENTED; + } std::pair<Link::shared_ptr, bool> response = - links.declare (hp.i_host, hp.i_port, hp.i_useSsl, hp.i_durable, + links.declare (hp.i_host, hp.i_port, transport, hp.i_durable, hp.i_authMechanism, hp.i_username, hp.i_password); if (hp.i_durable && response.second) store->create(*response.first); - status = Manageable::STATUS_OK; break; } - case management::Broker::METHOD_JOINCLUSTER : - case management::Broker::METHOD_LEAVECLUSTER : + case _qmf::Broker::METHOD_QUEUEMOVEMESSAGES : { + _qmf::ArgsBrokerQueueMoveMessages& moveArgs= + dynamic_cast<_qmf::ArgsBrokerQueueMoveMessages&>(args); + if (queueMoveMessages(moveArgs.i_srcQueue, moveArgs.i_destQueue, moveArgs.i_qty)) + status = Manageable::STATUS_OK; + else + return Manageable::STATUS_PARAMETER_INVALID; + break; + } + default: status = Manageable::STATUS_NOT_IMPLEMENTED; break; } @@ -355,34 +397,40 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, return status; } -boost::shared_ptr<ProtocolFactory> Broker::getProtocolFactory() const { - assert(protocolFactories.size() > 0); - return protocolFactories[0]; +boost::shared_ptr<ProtocolFactory> Broker::getProtocolFactory(const std::string& name) const { + ProtocolFactoryMap::const_iterator i + = name.empty() ? protocolFactories.begin() : protocolFactories.find(name); + if (i == protocolFactories.end()) return boost::shared_ptr<ProtocolFactory>(); + else return i->second; } -void Broker::registerProtocolFactory(ProtocolFactory::shared_ptr protocolFactory) { - protocolFactories.push_back(protocolFactory); +uint16_t Broker::getPort(const std::string& name) const { + boost::shared_ptr<ProtocolFactory> factory = getProtocolFactory(name); + if (factory) { + return factory->getPort(); + } else { + throw NoSuchTransportException(QPID_MSG("No such transport: '" << name << "'")); + } } -// TODO: This can only work if there is only one protocolFactory -uint16_t Broker::getPort() const { - return getProtocolFactory()->getPort(); +void Broker::registerProtocolFactory(const std::string& name, ProtocolFactory::shared_ptr protocolFactory) { + protocolFactories[name] = protocolFactory; } -// TODO: This should iterate over all protocolFactories void Broker::accept() { - for (unsigned int i = 0; i < protocolFactories.size(); ++i) - protocolFactories[i]->accept(poller, &factory); + for (ProtocolFactoryMap::const_iterator i = protocolFactories.begin(); i != protocolFactories.end(); i++) { + i->second->accept(poller, factory.get()); + } } - -// TODO: How to chose the protocolFactory to use for the connection void Broker::connect( - const std::string& host, uint16_t port, bool /*useSsl*/, + const std::string& host, uint16_t port, const std::string& transport, boost::function2<void, int, std::string> failed, sys::ConnectionCodec::Factory* f) { - getProtocolFactory()->connect(poller, host, port, f ? f : &factory, failed); + boost::shared_ptr<ProtocolFactory> pf = getProtocolFactory(transport); + if (pf) pf->connect(poller, host, port, f ? f : factory.get(), failed); + else throw NoSuchTransportException(QPID_MSG("Unsupported transport type: " << transport)); } void Broker::connect( @@ -391,11 +439,35 @@ void Broker::connect( sys::ConnectionCodec::Factory* f) { url.throwIfEmpty(); - TcpAddress addr=boost::get<TcpAddress>(url[0]); - connect(addr.host, addr.port, false, failed, f); + const TcpAddress* addr=url[0].get<TcpAddress>(); + connect(addr->host, addr->port, TCP_TRANSPORT, failed, f); } +uint32_t Broker::queueMoveMessages( + const std::string& srcQueue, + const std::string& destQueue, + uint32_t qty) +{ + 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); +} + + boost::shared_ptr<sys::Poller> Broker::getPoller() { return poller; } +std::vector<Url> +Broker::getKnownBrokersImpl() +{ + return knownBrokers; +} + +const std::string Broker::TCP_TRANSPORT("tcp"); + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Broker.h b/cpp/src/qpid/broker/Broker.h index f7399c375f..b85aa7d96c 100644 --- a/cpp/src/qpid/broker/Broker.h +++ b/cpp/src/qpid/broker/Broker.h @@ -22,21 +22,25 @@ * */ -#include "ConnectionFactory.h" -#include "ConnectionToken.h" -#include "DirectExchange.h" -#include "DtxManager.h" -#include "ExchangeRegistry.h" -#include "MessageStore.h" -#include "QueueRegistry.h" -#include "LinkRegistry.h" -#include "SessionManager.h" -#include "Vhost.h" -#include "System.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/ConnectionFactory.h" +#include "qpid/broker/ConnectionToken.h" +#include "qpid/broker/DirectExchange.h" +#include "qpid/broker/DtxManager.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/SessionManager.h" +#include "qpid/broker/QueueCleaner.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/broker/Vhost.h" +#include "qpid/broker/System.h" +#include "qpid/broker/ExpiryPolicy.h" #include "qpid/management/Manageable.h" -#include "qpid/management/ManagementBroker.h" -#include "qpid/management/Broker.h" -#include "qpid/management/ArgsBrokerConnect.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" @@ -44,10 +48,13 @@ #include "qpid/framing/OutputHandler.h" #include "qpid/framing/ProtocolInitiation.h" #include "qpid/sys/Runnable.h" +#include "qpid/sys/Timer.h" #include "qpid/RefCounted.h" -#include "AclModule.h" +#include "qpid/broker/AclModule.h" +#include "qpid/sys/Mutex.h" #include <boost/intrusive_ptr.hpp> +#include <string> #include <vector> namespace qpid { @@ -57,22 +64,34 @@ namespace sys { class Poller; } -class Url; +struct Url; namespace broker { +class ExpiryPolicy; + static const uint16_t DEFAULT_PORT=5672; +struct NoSuchTransportException : qpid::Exception +{ + NoSuchTransportException(const std::string& s) : Exception(s) {} + virtual ~NoSuchTransportException() throw() {} +}; + /** * A broker instance. */ class Broker : public sys::Runnable, public Plugin::Target, - public management::Manageable, public RefCounted + public management::Manageable, + public RefCounted { - public: +public: struct Options : public qpid::Options { - Options(const std::string& name="Broker Options"); + static const std::string DEFAULT_DATA_DIR_LOCATION; + static const std::string DEFAULT_DATA_DIR_NAME; + + QPID_BROKER_EXTERN Options(const std::string& name="Broker Options"); bool noDataDir; std::string dataDir; @@ -83,45 +102,82 @@ class Broker : public sys::Runnable, public Plugin::Target, uint64_t stagingThreshold; bool enableMgmt; uint16_t mgmtPubInterval; + uint16_t queueCleanInterval; bool auth; std::string realm; size_t replayFlushLimit; size_t replayHardLimit; uint queueLimit; bool tcpNoDelay; + bool requireEncrypted; + std::string knownHosts; + uint32_t maxSessionRate; + bool asyncQueueEvents; + + private: + std::string getHome(); + }; + + class ConnectionCounter { + int maxConnections; + int connectionCount; + sys::Mutex connectionCountLock; + public: + ConnectionCounter(int mc): maxConnections(mc),connectionCount(0) {}; + void inc_connectionCount() { + sys::ScopedLock<sys::Mutex> l(connectionCountLock); + connectionCount++; + } + void dec_connectionCount() { + sys::ScopedLock<sys::Mutex> l(connectionCountLock); + connectionCount--; + } + bool allowConnection() { + sys::ScopedLock<sys::Mutex> l(connectionCountLock); + return (maxConnections <= connectionCount); + } }; - + private: + typedef std::map<std::string, boost::shared_ptr<sys::ProtocolFactory> > ProtocolFactoryMap; + + void declareStandardExchange(const std::string& name, const std::string& type); + void setStore (); + boost::shared_ptr<sys::Poller> poller; + sys::Timer timer; Options config; - management::ManagementAgent::Singleton managementAgentSingleton; - std::vector< boost::shared_ptr<sys::ProtocolFactory> > protocolFactories; - MessageStore* store; - AclModule* acl; + std::auto_ptr<management::ManagementAgent> managementAgent; + ProtocolFactoryMap protocolFactories; + std::auto_ptr<MessageStore> store; + AclModule* acl; DataDir dataDir; QueueRegistry queues; ExchangeRegistry exchanges; LinkRegistry links; - ConnectionFactory factory; + boost::shared_ptr<sys::ConnectionCodec::Factory> factory; DtxManager dtxManager; SessionManager sessionManager; - management::ManagementAgent* managementAgent; - management::Broker* mgmtObject; + qmf::org::apache::qpid::broker::Broker* mgmtObject; Vhost::shared_ptr vhostObject; System::shared_ptr systemObject; - - void declareStandardExchange(const std::string& name, const std::string& type); - - + QueueCleaner queueCleaner; + QueueEvents queueEvents; + std::vector<Url> knownBrokers; + std::vector<Url> getKnownBrokersImpl(); + std::string federationTag; + bool recovery; + bool clusterUpdatee; + boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; + ConnectionCounter connectionCounter; + public: - - virtual ~Broker(); - Broker(const Options& configuration); - static boost::intrusive_ptr<Broker> create(const Options& configuration); - static boost::intrusive_ptr<Broker> create(int16_t port = DEFAULT_PORT); + QPID_BROKER_EXTERN Broker(const Options& configuration); + static QPID_BROKER_EXTERN boost::intrusive_ptr<Broker> create(const Options& configuration); + static QPID_BROKER_EXTERN boost::intrusive_ptr<Broker> create(int16_t port = DEFAULT_PORT); /** * Return listening port. If called before bind this is @@ -129,7 +185,7 @@ class Broker : public sys::Runnable, public Plugin::Target, * port, which will be different if the configured port is * 0. */ - virtual uint16_t getPort() const; + virtual uint16_t getPort(const std::string& name) const; /** * Run the broker. Implements Runnable::run() so the broker @@ -140,7 +196,7 @@ class Broker : public sys::Runnable, public Plugin::Target, /** Shut down the broker */ virtual void shutdown(); - void setStore (MessageStore*); + QPID_BROKER_EXTERN void setStore (boost::shared_ptr<MessageStore>& store); MessageStore& getStore() { return *store; } void setAcl (AclModule* _acl) {acl = _acl;} AclModule* getAcl() { return acl; } @@ -151,21 +207,29 @@ class Broker : public sys::Runnable, public Plugin::Target, DtxManager& getDtxManager() { return dtxManager; } DataDir& getDataDir() { return dataDir; } Options& getOptions() { return config; } + QueueEvents& getQueueEvents() { return queueEvents; } + + void setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e) { expiryPolicy = e; } + boost::intrusive_ptr<ExpiryPolicy> getExpiryPolicy() { return expiryPolicy; } SessionManager& getSessionManager() { return sessionManager; } + const std::string& getFederationTag() const { return federationTag; } management::ManagementObject* GetManagementObject (void) const; management::Manageable* GetVhostObject (void) const; - management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args); - + management::Manageable::status_t ManagementMethod (uint32_t methodId, + management::Args& args, + std::string& text); + /** Add to the broker's protocolFactorys */ - void registerProtocolFactory(boost::shared_ptr<sys::ProtocolFactory>); + void registerProtocolFactory(const std::string& name, boost::shared_ptr<sys::ProtocolFactory>); /** Accept connections */ - void accept(); + QPID_BROKER_EXTERN void accept(); /** Create a connection to another broker. */ - void connect(const std::string& host, uint16_t port, bool useSsl, + void connect(const std::string& host, uint16_t port, + const std::string& transport, boost::function2<void, int, std::string> failed, sys::ConnectionCodec::Factory* =0); /** Create a connection to another broker. */ @@ -173,16 +237,38 @@ class Broker : public sys::Runnable, public Plugin::Target, boost::function2<void, int, std::string> failed, sys::ConnectionCodec::Factory* =0); - // TODO: There isn't a single ProtocolFactory so the use of the following needs to be fixed - // For the present just return the first ProtocolFactory registered. - boost::shared_ptr<sys::ProtocolFactory> getProtocolFactory() const; + /** Move messages from one queue to another. + A zero quantity means to move all messages + */ + uint32_t queueMoveMessages( const std::string& srcQueue, + const std::string& destQueue, + uint32_t qty); + + boost::shared_ptr<sys::ProtocolFactory> getProtocolFactory(const std::string& name = TCP_TRANSPORT) const; /** Expose poller so plugins can register their descriptors. */ - boost::shared_ptr<sys::Poller> getPoller(); + 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; } + + 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; } + + void setClusterUpdatee(bool set) { clusterUpdatee = set; } + bool isClusterUpdatee() const { return clusterUpdatee; } + + management::ManagementAgent* getManagementAgent() { return managementAgent.get(); } + + ConnectionCounter& getConnectionCounter() {return connectionCounter;} }; }} - - #endif /*!_Broker_*/ diff --git a/cpp/src/qpid/broker/BrokerImportExport.h b/cpp/src/qpid/broker/BrokerImportExport.h new file mode 100644 index 0000000000..4edf8c9844 --- /dev/null +++ b/cpp/src/qpid/broker/BrokerImportExport.h @@ -0,0 +1,33 @@ +#ifndef QPID_BROKER_IMPORT_EXPORT_H +#define QPID_BROKER_IMPORT_EXPORT_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. + */ + +#if defined(WIN32) && !defined(QPID_BROKER_STATIC) +#if defined(BROKER_EXPORT) || defined (qpidbroker_EXPORTS) +#define QPID_BROKER_EXTERN __declspec(dllexport) +#else +#define QPID_BROKER_EXTERN __declspec(dllimport) +#endif +#else +#define QPID_BROKER_EXTERN +#endif + +#endif diff --git a/cpp/src/qpid/broker/BrokerSingleton.h b/cpp/src/qpid/broker/BrokerSingleton.h deleted file mode 100644 index 22b707506b..0000000000 --- a/cpp/src/qpid/broker/BrokerSingleton.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef _broker_BrokerSingleton_h -#define _broker_BrokerSingleton_h - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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 "Broker.h" - -namespace qpid { -namespace broker { - -/** - * BrokerSingleton is a smart pointer to a process-wide singleton broker - * started on an os-chosen port. The broker starts the first time - * an instance of BrokerSingleton is created and runs untill the process exits. - * - * Useful for unit tests that want to share a broker between multiple - * tests to reduce overhead of starting/stopping a broker for every test. - * - * Tests that need a new broker can create it directly. - * - * THREAD UNSAFE. - */ -class BrokerSingleton : public boost::intrusive_ptr<Broker> -{ - public: - BrokerSingleton(); - ~BrokerSingleton(); - private: - static boost::intrusive_ptr<Broker> broker; -}; - -}} // namespace qpid::broker - - - -#endif /*!_broker_BrokerSingleton_h*/ diff --git a/cpp/src/qpid/broker/Connection.cpp b/cpp/src/qpid/broker/Connection.cpp index ab18d1f035..17de83e033 100644 --- a/cpp/src/qpid/broker/Connection.cpp +++ b/cpp/src/qpid/broker/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 @@ -18,14 +18,17 @@ * under the License. * */ -#include "Connection.h" -#include "SessionState.h" -#include "Bridge.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/Bridge.h" +#include "qpid/broker/Broker.h" #include "qpid/log/Statement.h" #include "qpid/ptr_map.h" #include "qpid/framing/AMQP_ClientProxy.h" -#include "qpid/agent/ManagementAgent.h" +#include "qpid/framing/enum.h" +#include "qmf/org/apache/qpid/broker/EventClientConnect.h" +#include "qmf/org/apache/qpid/broker/EventClientDisconnect.h" #include <boost/bind.hpp> #include <boost/ptr_container/ptr_vector.hpp> @@ -34,30 +37,53 @@ #include <iostream> #include <assert.h> -using namespace boost; using namespace qpid::sys; using namespace qpid::framing; -using namespace qpid::sys; using qpid::ptr_map_ptr; using qpid::management::ManagementAgent; using qpid::management::ManagementObject; using qpid::management::Manageable; using qpid::management::Args; +namespace _qmf = qmf::org::apache::qpid::broker; namespace qpid { namespace broker { -Connection::Connection(ConnectionOutputHandler* out_, Broker& broker_, const std::string& mgmtId_, bool isLink_) : +struct ConnectionTimeoutTask : public sys::TimerTask { + sys::Timer& timer; + Connection& connection; + + ConnectionTimeoutTask(uint16_t hb, sys::Timer& t, Connection& c) : + TimerTask(Duration(hb*2*TIME_SEC)), + timer(t), + connection(c) + {} + + void touch() { + restart(); + } + + void fire() { + // If we get here then we've not received any traffic in the timeout period + // Schedule closing the connection for the io thread + QPID_LOG(error, "Connection timed out: closing"); + connection.abort(); + } +}; + +Connection::Connection(ConnectionOutputHandler* out_, Broker& broker_, const std::string& mgmtId_, unsigned int ssf, bool isLink_, uint64_t objectId) : ConnectionState(out_, broker_), - receivedFn(boost::bind(&Connection::receivedImpl, this, _1)), - closedFn(boost::bind(&Connection::closedImpl, this)), - doOutputFn(boost::bind(&Connection::doOutputImpl, this)), + ssf(ssf), adapter(*this, isLink_), isLink(isLink_), mgmtClosing(false), mgmtId(mgmtId_), mgmtObject(0), - links(broker_.getLinks()) + links(broker_.getLinks()), + agent(0), + timer(broker_.getTimer()), + errorListener(0), + shadow(false) { Manageable* parent = broker.GetVhostObject(); @@ -66,33 +92,48 @@ Connection::Connection(ConnectionOutputHandler* out_, Broker& broker_, const std if (parent != 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + agent = broker_.getManagementAgent(); - if (agent != 0) - mgmtObject = new management::Connection(agent, this, parent, mgmtId, !isLink); - agent->addObject(mgmtObject); - } - Plugin::initializeAll(*this); // Let plug-ins update extension points. + // TODO set last bool true if system connection + if (agent != 0) { + mgmtObject = new _qmf::Connection(agent, this, parent, mgmtId, !isLink, false); + agent->addObject(mgmtObject, objectId, true); + } + ConnectionState::setUrl(mgmtId); + } + if (!isShadow()) broker.getConnectionCounter().inc_connectionCount(); } void Connection::requestIOProcessing(boost::function0<void> callback) { - ioCallback = callback; - out->activateOutput(); + ScopedLock<Mutex> l(ioCallbackLock); + ioCallbacks.push(callback); + out.activateOutput(); } Connection::~Connection() { - if (mgmtObject != 0) + if (mgmtObject != 0) { mgmtObject->resourceDestroy(); + if (!isLink) + agent->raiseEvent(_qmf::EventClientDisconnect(mgmtId, ConnectionState::getUserId())); + } if (isLink) links.notifyClosed(mgmtId); + + if (heartbeatTimer) + heartbeatTimer->cancel(); + if (timeoutTimer) + timeoutTimer->cancel(); + + if (!isShadow()) broker.getConnectionCounter().dec_connectionCount(); } -void Connection::received(framing::AMQFrame& frame) { receivedFn(frame); } +void Connection::received(framing::AMQFrame& frame) { + // Received frame on connection so delay timeout + restartTimeout(); -void Connection::receivedImpl(framing::AMQFrame& frame){ if (frame.getChannel() == 0 && frame.getMethod()) { adapter.handle(frame); } else { @@ -110,7 +151,7 @@ void Connection::recordFromServer(framing::AMQFrame& frame) if (mgmtObject != 0) { mgmtObject->inc_framesToClient(); - mgmtObject->inc_bytesToClient(frame.size()); + mgmtObject->inc_bytesToClient(frame.encodedSize()); } } @@ -119,7 +160,7 @@ void Connection::recordFromClient(framing::AMQFrame& frame) if (mgmtObject != 0) { mgmtObject->inc_framesFromClient(); - mgmtObject->inc_bytesFromClient(frame.size()); + mgmtObject->inc_bytesFromClient(frame.encodedSize()); } } @@ -156,29 +197,55 @@ void Connection::notifyConnectionForced(const string& text) void Connection::setUserId(const string& userId) { ConnectionState::setUserId(userId); - if (mgmtObject != 0) + if (mgmtObject != 0) { mgmtObject->set_authIdentity(userId); + agent->raiseEvent(_qmf::EventClientConnect(mgmtId, userId)); + } } -void Connection::close( - ReplyCode code, const string& text, ClassId classId, MethodId methodId) +void Connection::setFederationLink(bool b) { - adapter.close(code, text, classId, methodId); + ConnectionState::setFederationLink(b); + if (mgmtObject != 0) + mgmtObject->set_federationLink(b); +} + +void Connection::close(connection::CloseCode code, const string& text) +{ + QPID_LOG_IF(error, code != connection::CLOSE_CODE_NORMAL, "Connection " << mgmtId << " closed by error: " << text << "(" << code << ")"); + if (heartbeatTimer) + heartbeatTimer->cancel(); + if (timeoutTimer) + timeoutTimer->cancel(); + adapter.close(code, text); + //make sure we delete dangling pointers from outputTasks before deleting sessions + outputTasks.removeAll(); channels.clear(); 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(); + adapter.close(connection::CLOSE_CODE_NORMAL, "OK"); + getOutput().close(); +} + void Connection::idleOut(){} void Connection::idleIn(){} -void Connection::closed() { closedFn(); } - -void Connection::closedImpl(){ // Physically closed, suspend open sessions. +void Connection::closed(){ // Physically closed, suspend open sessions. + if (heartbeatTimer) + heartbeatTimer->cancel(); + if (timeoutTimer) + timeoutTimer->cancel(); try { - while (!channels.empty()) + while (!channels.empty()) ptr_map_ptr(channels.begin())->handleDetach(); - // FIXME aconway 2008-07-15: exclusive is per-session not per-connection in 0-10. while (!exclusiveQueues.empty()) { Queue::shared_ptr q(exclusiveQueues.front()); q->releaseExclusiveOwnership(); @@ -195,27 +262,36 @@ void Connection::closedImpl(){ // Physically closed, suspend open sessions. bool Connection::hasOutput() { return outputTasks.hasOutput(); } -bool Connection::doOutput() { return doOutputFn(); } - -bool Connection::doOutputImpl() { - try{ - if (ioCallback) - ioCallback(); // Lend the IO thread for management processing - ioCallback = 0; - - if (mgmtClosing) - close(403, "Closed by Management Request", 0, 0); - else +bool Connection::doOutput() { + try { + { + ScopedLock<Mutex> l(ioCallbackLock); + while (!ioCallbacks.empty()) { + boost::function0<void> cb = ioCallbacks.front(); + ioCallbacks.pop(); + ScopedUnlock<Mutex> ul(ioCallbackLock); + cb(); // Lend the IO thread for management processing + } + } + if (mgmtClosing) { + closed(); + close(connection::CLOSE_CODE_CONNECTION_FORCED, "Closed by Management Request"); + } else { //then do other output as needed: return outputTasks.doOutput(); + } }catch(ConnectionException& e){ - close(e.code, e.getMessage(), 0, 0); + close(e.code, e.getMessage()); }catch(std::exception& e){ - close(541/*internal error*/, e.what(), 0, 0); + close(connection::CLOSE_CODE_CONNECTION_FORCED, e.what()); } return false; } +void Connection::sendHeartbeat() { + adapter.heartbeat(); +} + void Connection::closeChannel(uint16_t id) { ChannelMap::iterator i = channels.find(id); if (i != channels.end()) channels.erase(i); @@ -234,7 +310,7 @@ ManagementObject* Connection::GetManagementObject(void) const return (ManagementObject*) mgmtObject; } -Manageable::status_t Connection::ManagementMethod(uint32_t methodId, Args&) +Manageable::status_t Connection::ManagementMethod(uint32_t methodId, Args&, string&) { Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; @@ -242,10 +318,10 @@ Manageable::status_t Connection::ManagementMethod(uint32_t methodId, Args&) switch (methodId) { - case management::Connection::METHOD_CLOSE : + case _qmf::Connection::METHOD_CLOSE : mgmtClosing = true; if (mgmtObject != 0) mgmtObject->set_closing(1); - out->activateOutput(); + out.activateOutput(); status = Manageable::STATUS_OK; break; } @@ -253,5 +329,55 @@ Manageable::status_t Connection::ManagementMethod(uint32_t methodId, Args&) return status; } -}} +void Connection::setSecureConnection(SecureConnection* s) +{ + adapter.setSecureConnection(s); +} + +struct ConnectionHeartbeatTask : public sys::TimerTask { + sys::Timer& timer; + Connection& connection; + ConnectionHeartbeatTask(uint16_t hb, sys::Timer& t, Connection& c) : + TimerTask(Duration(hb*TIME_SEC)), + timer(t), + connection(c) + {} + + void fire() { + // Setup next firing + setupNextFire(); + timer.add(this); + + // Send Heartbeat + connection.sendHeartbeat(); + } +}; +void Connection::abort() +{ + // Make sure that we don't try to send a heartbeat as we're + // aborting the connection + if (heartbeatTimer) + heartbeatTimer->cancel(); + + out.abort(); +} + +void Connection::setHeartbeatInterval(uint16_t heartbeat) +{ + setHeartbeat(heartbeat); + if (heartbeat > 0 && !isShadow()) { + heartbeatTimer = new ConnectionHeartbeatTask(heartbeat, timer, *this); + timer.add(heartbeatTimer); + timeoutTimer = new ConnectionTimeoutTask(heartbeat, timer, *this); + timer.add(timeoutTimer); + } +} + +void Connection::restartTimeout() +{ + if (timeoutTimer) + timeoutTimer->touch(); +} + +}} diff --git a/cpp/src/qpid/broker/Connection.h b/cpp/src/qpid/broker/Connection.h index 1367f3b9ca..66ede59df5 100644 --- a/cpp/src/qpid/broker/Connection.h +++ b/cpp/src/qpid/broker/Connection.h @@ -10,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 @@ -25,52 +25,68 @@ #include <memory> #include <sstream> #include <vector> +#include <queue> #include <boost/ptr_container/ptr_map.hpp> +#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_ServerOperations.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/ConnectionOutputHandler.h" #include "qpid/sys/ConnectionInputHandler.h" -#include "qpid/sys/TimeoutHandler.h" -#include "qpid/framing/ProtocolVersion.h" -#include "Broker.h" +#include "qpid/sys/ConnectionOutputHandler.h" #include "qpid/sys/Socket.h" -#include "qpid/Exception.h" -#include "ConnectionHandler.h" -#include "ConnectionState.h" -#include "SessionHandler.h" -#include "qpid/management/Manageable.h" -#include "qpid/management/Connection.h" -#include "qpid/Plugin.h" -#include "qpid/RefCounted.h" +#include "qpid/sys/TimeoutHandler.h" +#include "qpid/sys/Mutex.h" #include <boost/ptr_container/ptr_map.hpp> +#include <boost/bind.hpp> + +#include <algorithm> namespace qpid { namespace broker { +class Broker; class LinkRegistry; +class SecureConnection; +struct ConnectionTimeoutTask; -class Connection : public sys::ConnectionInputHandler, +class Connection : public sys::ConnectionInputHandler, public ConnectionState, - public Plugin::Target, public RefCounted { public: - Connection(sys::ConnectionOutputHandler* out, Broker& broker, const std::string& mgmtId, bool isLink = false); + /** + * Listener that can be registered with a Connection to be informed of errors. + */ + class ErrorListener + { + public: + virtual ~ErrorListener() {} + virtual void sessionError(uint16_t channel, const std::string&) = 0; + virtual void connectionError(const std::string&) = 0; + }; + + Connection(sys::ConnectionOutputHandler* out, Broker& broker, const std::string& mgmtId, unsigned int ssf, + bool isLink = false, uint64_t objectId = 0); ~Connection (); /** Get the SessionHandler for channel. Create if it does not already exist */ SessionHandler& getChannel(framing::ChannelId channel); /** Close the connection */ - void close(framing::ReplyCode code = 403, - const string& text = string(), - framing::ClassId classId = 0, - framing::MethodId methodId = 0); + void close(framing::connection::CloseCode code, const string& text); // ConnectionInputHandler methods void received(framing::AMQFrame& frame); @@ -85,7 +101,7 @@ class Connection : public sys::ConnectionInputHandler, // Manageable entry points management::ManagementObject* GetManagementObject (void) const; management::Manageable::status_t - ManagementMethod (uint32_t methodId, management::Args& args); + ManagementMethod (uint32_t methodId, management::Args& args, std::string&); void requestIOProcessing (boost::function0<void>); void recordFromServer (framing::AMQFrame& frame); @@ -94,29 +110,60 @@ class Connection : public sys::ConnectionInputHandler, std::string getAuthCredentials(); void notifyConnectionForced(const std::string& text); void setUserId(const string& uid); + const std::string& getUserId() const { return ConnectionState::getUserId(); } + const std::string& getMgmtId() const { return mgmtId; } + management::ManagementAgent* getAgent() const { return agent; } + void setFederationLink(bool b); + /** Connection does not delete the listener. 0 resets. */ + void setErrorListener(ErrorListener* l) { errorListener=l; } + ErrorListener* getErrorListener() { return errorListener; } + + void setHeartbeatInterval(uint16_t heartbeat); + void sendHeartbeat(); + void restartTimeout(); + void abort(); + + template <class F> void eachSessionHandler(F f) { + for (ChannelMap::iterator i = channels.begin(); i != channels.end(); ++i) + f(*ptr_map_ptr(i)); + } - // Extension points: allow plugins to insert additional functionality. - boost::function<void(framing::AMQFrame&)> receivedFn; - boost::function<void ()> closedFn; - boost::function<bool ()> doOutputFn; + void sendClose(); + void setSecureConnection(SecureConnection* secured); + + /** True if this is a shadow connection in a cluster. */ + bool isShadow() { return shadow; } + /** Called by cluster to mark shadow connections */ + void setShadow() { shadow = true; } + + // Used by cluster to update connection status + sys::AggregateOutput& getOutputTasks() { return outputTasks; } + + unsigned int getSSF() { return ssf; } private: typedef boost::ptr_map<framing::ChannelId, SessionHandler> ChannelMap; typedef std::vector<Queue::shared_ptr>::iterator queue_iterator; - void receivedImpl(framing::AMQFrame& frame); - void closedImpl(); - bool doOutputImpl(); - ChannelMap channels; - framing::AMQP_ClientProxy::Connection* client; + unsigned int ssf; ConnectionHandler adapter; - bool isLink; + const bool isLink; bool mgmtClosing; const std::string mgmtId; - boost::function0<void> ioCallback; - management::Connection* mgmtObject; + sys::Mutex ioCallbackLock; + std::queue<boost::function0<void> > ioCallbacks; + qmf::org::apache::qpid::broker::Connection* mgmtObject; LinkRegistry& links; + management::ManagementAgent* agent; + sys::Timer& timer; + boost::intrusive_ptr<sys::TimerTask> heartbeatTimer; + boost::intrusive_ptr<ConnectionTimeoutTask> timeoutTimer; + ErrorListener* errorListener; + bool shadow; + + public: + qmf::org::apache::qpid::broker::Connection* getMgmtObject() { return mgmtObject; } }; }} diff --git a/cpp/src/qpid/broker/ConnectionFactory.cpp b/cpp/src/qpid/broker/ConnectionFactory.cpp index 5de5a0230a..ffb0b34b95 100644 --- a/cpp/src/qpid/broker/ConnectionFactory.cpp +++ b/cpp/src/qpid/broker/ConnectionFactory.cpp @@ -18,30 +18,47 @@ * under the License. * */ -#include "ConnectionFactory.h" +#include "qpid/broker/ConnectionFactory.h" #include "qpid/framing/ProtocolVersion.h" #include "qpid/amqp_0_10/Connection.h" +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" namespace qpid { namespace broker { using framing::ProtocolVersion; +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) { - if (v == ProtocolVersion(0, 10)) - return new amqp_0_10::Connection(out, broker, id); +ConnectionFactory::create(ProtocolVersion v, sys::OutputControl& out, const std::string& id, + unsigned int ) { + if (broker.getConnectionCounter().allowConnection()) + { + QPID_LOG(error, "Client max connection count limit exceeded: " << broker.getOptions().maxConnections << " connection refused"); + return 0; + } + 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, false))); + return c.release(); + } return 0; } sys::ConnectionCodec* -ConnectionFactory::create(sys::OutputControl& out, const std::string& id) { +ConnectionFactory::create(sys::OutputControl& out, const std::string& id, + unsigned int) { // used to create connections from one broker to another - return new amqp_0_10::Connection(out, broker, id, true); + ConnectionPtr c(new amqp_0_10::Connection(out, id, true)); + c->setInputHandler(InputPtr(new broker::Connection(c.get(), broker, id, true))); + return c.release(); } + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/ConnectionFactory.h b/cpp/src/qpid/broker/ConnectionFactory.h index 5797495054..d812292ad1 100644 --- a/cpp/src/qpid/broker/ConnectionFactory.h +++ b/cpp/src/qpid/broker/ConnectionFactory.h @@ -27,17 +27,20 @@ namespace qpid { namespace broker { class Broker; -class ConnectionFactory : public sys::ConnectionCodec::Factory { +class ConnectionFactory : public sys::ConnectionCodec::Factory +{ public: ConnectionFactory(Broker& b); virtual ~ConnectionFactory(); sys::ConnectionCodec* - create(framing::ProtocolVersion, sys::OutputControl&, const std::string& id); + create(framing::ProtocolVersion, sys::OutputControl&, const std::string& id, + unsigned int conn_ssf); sys::ConnectionCodec* - create(sys::OutputControl&, const std::string& id); + create(sys::OutputControl&, const std::string& id, + unsigned int conn_ssf); private: Broker& broker; diff --git a/cpp/src/qpid/broker/ConnectionHandler.cpp b/cpp/src/qpid/broker/ConnectionHandler.cpp index 77a4d1a3de..50a5aff2c9 100644 --- a/cpp/src/qpid/broker/ConnectionHandler.cpp +++ b/cpp/src/qpid/broker/ConnectionHandler.cpp @@ -8,9 +8,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 @@ -20,70 +20,91 @@ * */ -#include "config.h" - -#include "ConnectionHandler.h" -#include "Connection.h" -#include "qpid/framing/ClientInvoker.h" -#include "qpid/framing/ServerInvoker.h" -#include "qpid/framing/constants.h" +#include "qpid/broker/ConnectionHandler.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/SecureConnection.h" +#include "qpid/Url.h" +#include "qpid/framing/AllInvoker.h" +#include "qpid/framing/enum.h" #include "qpid/log/Statement.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/broker/AclModule.h" +#include "qmf/org/apache/qpid/broker/EventClientConnectFail.h" using namespace qpid; using namespace qpid::broker; using namespace qpid::framing; +using qpid::sys::SecurityLayer; +namespace _qmf = qmf::org::apache::qpid::broker; - -namespace +namespace { const std::string ANONYMOUS = "ANONYMOUS"; const std::string PLAIN = "PLAIN"; const std::string en_US = "en_US"; +const std::string QPID_FED_LINK = "qpid.fed_link"; +const std::string QPID_FED_TAG = "qpid.federation_tag"; +const std::string SESSION_FLOW_CONTROL("qpid.session_flow"); +const std::string CLIENT_PROCESS_NAME("qpid.client_process"); +const std::string CLIENT_PID("qpid.client_pid"); +const std::string CLIENT_PPID("qpid.client_ppid"); +const int SESSION_FLOW_CONTROL_VER = 1; } -void ConnectionHandler::close(ReplyCode code, const string& text, ClassId, MethodId) +void ConnectionHandler::close(connection::CloseCode code, const string& text) { - handler->client.close(code, text); + handler->proxy.close(code, text); +} + +void ConnectionHandler::heartbeat() +{ + handler->proxy.heartbeat(); } void ConnectionHandler::handle(framing::AMQFrame& frame) { AMQMethodBody* method=frame.getBody()->getMethod(); + Connection::ErrorListener* errorListener = handler->connection.getErrorListener(); try{ - bool handled = false; - if (handler->serverMode) { - handled = invoke(static_cast<AMQP_ServerOperations::ConnectionHandler&>(*handler.get()), *method); - } else { - handled = invoke(static_cast<AMQP_ClientOperations::ConnectionHandler&>(*handler.get()), *method); - } - if (!handled) { + if (!invoke(static_cast<AMQP_AllOperations::ConnectionHandler&>(*handler.get()), *method)) { handler->connection.getChannel(frame.getChannel()).in(frame); } - }catch(ConnectionException& e){ - handler->client.close(e.code, e.what()); + if (errorListener) errorListener->connectionError(e.what()); + handler->proxy.close(e.code, e.what()); }catch(std::exception& e){ - handler->client.close(541/*internal error*/, e.what()); + if (errorListener) errorListener->connectionError(e.what()); + handler->proxy.close(541/*internal error*/, e.what()); } } +void ConnectionHandler::setSecureConnection(SecureConnection* secured) +{ + handler->secured = secured; +} + ConnectionHandler::ConnectionHandler(Connection& connection, bool isClient) : handler(new Handler(connection, isClient)) {} ConnectionHandler::Handler::Handler(Connection& c, bool isClient) : - client(c.getOutput()), server(c.getOutput()), - connection(c), serverMode(!isClient) + proxy(c.getOutput()), + connection(c), serverMode(!isClient), acl(0), secured(0) { if (serverMode) { + + acl = connection.getBroker().getAcl(); + FieldTable properties; Array mechanisms(0x95); - + + properties.setString(QPID_FED_TAG, connection.getBroker().getFederationTag()); + authenticator = SaslAuthenticator::createAuthenticator(c); authenticator->getMechanisms(mechanisms); - + Array locales(0x95); boost::shared_ptr<FieldValue> l(new Str16Value(en_US)); locales.add(l); - client.start(properties, mechanisms, locales); + proxy.start(properties, mechanisms, locales); } } @@ -91,65 +112,138 @@ ConnectionHandler::Handler::Handler(Connection& c, bool isClient) : ConnectionHandler::Handler::~Handler() {} -void ConnectionHandler::Handler::startOk(const framing::FieldTable& /*clientProperties*/, - const string& mechanism, +void ConnectionHandler::Handler::startOk(const framing::FieldTable& clientProperties, + const string& mechanism, const string& response, const string& /*locale*/) { - authenticator->start(mechanism, response); + try { + authenticator->start(mechanism, response); + } catch (std::exception& /*e*/) { + management::ManagementAgent* agent = connection.getAgent(); + if (agent) { + string error; + string uid; + authenticator->getError(error); + authenticator->getUid(uid); + agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + } + throw; + } + connection.setFederationLink(clientProperties.get(QPID_FED_LINK)); + connection.setFederationPeerTag(clientProperties.getAsString(QPID_FED_TAG)); + if (connection.isFederationLink()) { + if (acl && !acl->authorise(connection.getUserId(),acl::ACT_CREATE,acl::OBJ_LINK,"")){ + proxy.close(framing::connection::CLOSE_CODE_CONNECTION_FORCED,"ACL denied creating a federation link"); + return; + } + QPID_LOG(info, "Connection is a federation link"); + } + if (clientProperties.getAsInt(SESSION_FLOW_CONTROL) == SESSION_FLOW_CONTROL_VER) { + connection.setClientThrottling(); + } + + 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) { - authenticator->step(response); + try { + authenticator->step(response); + } catch (std::exception& /*e*/) { + management::ManagementAgent* agent = connection.getAgent(); + if (agent) { + string error; + string uid; + authenticator->getError(error); + authenticator->getUid(uid); + agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + } + throw; + } } - + void ConnectionHandler::Handler::tuneOk(uint16_t /*channelmax*/, uint16_t framemax, uint16_t heartbeat) { connection.setFrameMax(framemax); - connection.setHeartbeat(heartbeat); + connection.setHeartbeatInterval(heartbeat); } - + void ConnectionHandler::Handler::open(const string& /*virtualHost*/, const framing::Array& /*capabilities*/, bool /*insist*/) { - framing::Array knownhosts; - client.openOk(knownhosts); + std::vector<Url> urls = connection.broker.getKnownBrokers(); + framing::Array array(0x95); // str16 array + for (std::vector<Url>::iterator i = urls.begin(); i < urls.end(); ++i) + array.add(boost::shared_ptr<Str16Value>(new Str16Value(i->str()))); + proxy.openOk(array); + + //install security layer if one has been negotiated: + if (secured) { + std::auto_ptr<SecurityLayer> sl = authenticator->getSecurityLayer(connection.getFrameMax()); + if (sl.get()) secured->activateSecurityLayer(sl); + } } - + void ConnectionHandler::Handler::close(uint16_t replyCode, const string& replyText) { if (replyCode != 200) { QPID_LOG(warning, "Client closed connection with " << replyCode << ": " << replyText); } - if (replyCode == framing::connection::CONNECTION_FORCED) + if (replyCode == framing::connection::CLOSE_CODE_CONNECTION_FORCED) connection.notifyConnectionForced(replyText); - client.closeOk(); + proxy.closeOk(); connection.getOutput().close(); -} - +} + void ConnectionHandler::Handler::closeOk(){ connection.getOutput().close(); -} +} +void ConnectionHandler::Handler::heartbeat(){ + // For general case, do nothing - the purpose of heartbeats is + // just to make sure that there is some traffic on the connection + // within the heart beat interval, we check for the traffic and + // don't need to do anything in response to heartbeats. The + // exception is when we are in fact the client to another broker + // (i.e. an inter-broker link), in which case we echo the + // heartbeat back to the peer + if (!serverMode) proxy.heartbeat(); +} -void ConnectionHandler::Handler::start(const FieldTable& /*serverProperties*/, +void ConnectionHandler::Handler::start(const FieldTable& serverProperties, const framing::Array& /*mechanisms*/, const framing::Array& /*locales*/) { string mechanism = connection.getAuthMechanism(); string response = connection.getAuthCredentials(); - - server.startOk(FieldTable(), mechanism, response, en_US); + + connection.setFederationPeerTag(serverProperties.getAsString(QPID_FED_TAG)); + + FieldTable ft; + ft.setInt(QPID_FED_LINK,1); + ft.setString(QPID_FED_TAG, connection.getBroker().getFederationTag()); + proxy.startOk(ft, mechanism, response, en_US); } void ConnectionHandler::Handler::secure(const string& /*challenge*/) { - server.secureOk(""); + proxy.secureOk(""); } void ConnectionHandler::Handler::tune(uint16_t channelMax, @@ -159,15 +253,19 @@ void ConnectionHandler::Handler::tune(uint16_t channelMax, { connection.setFrameMax(frameMax); connection.setHeartbeat(heartbeatMax); - server.tuneOk(channelMax, frameMax, heartbeatMax); - server.open("/", Array(), true); + proxy.tuneOk(channelMax, frameMax, heartbeatMax); + proxy.open("/", Array(), true); } -void ConnectionHandler::Handler::openOk(const framing::Array& /*knownHosts*/) +void ConnectionHandler::Handler::openOk(const framing::Array& knownHosts) { + for (Array::ValueVector::const_iterator i = knownHosts.begin(); i != knownHosts.end(); ++i) { + Url url((*i)->get<std::string>()); + connection.getKnownHosts().push_back(url); + } } void ConnectionHandler::Handler::redirect(const string& /*host*/, const framing::Array& /*knownHosts*/) { - + } diff --git a/cpp/src/qpid/broker/ConnectionHandler.h b/cpp/src/qpid/broker/ConnectionHandler.h index a04936a943..d74f65da36 100644 --- a/cpp/src/qpid/broker/ConnectionHandler.h +++ b/cpp/src/qpid/broker/ConnectionHandler.h @@ -22,68 +22,71 @@ #define _ConnectionAdapter_ #include <memory> -#include "SaslAuthenticator.h" +#include "qpid/broker/SaslAuthenticator.h" #include "qpid/framing/amqp_types.h" #include "qpid/framing/AMQFrame.h" -#include "qpid/framing/AMQP_ClientOperations.h" -#include "qpid/framing/AMQP_ClientProxy.h" -#include "qpid/framing/AMQP_ServerOperations.h" -#include "qpid/framing/AMQP_ServerProxy.h" +#include "qpid/framing/AMQP_AllOperations.h" +#include "qpid/framing/AMQP_AllProxy.h" +#include "qpid/framing/enum.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/ProtocolInitiation.h" #include "qpid/framing/ProtocolVersion.h" #include "qpid/Exception.h" +#include "qpid/broker/AclModule.h" namespace qpid { namespace broker { class Connection; +class SecureConnection; class ConnectionHandler : public framing::FrameHandler { - struct Handler : public framing::AMQP_ServerOperations::ConnectionHandler, - public framing::AMQP_ClientOperations::ConnectionHandler + struct Handler : public framing::AMQP_AllOperations::ConnectionHandler { - framing::AMQP_ClientProxy::Connection client; - framing::AMQP_ServerProxy::Connection server; + framing::AMQP_AllProxy::Connection proxy; Connection& connection; bool serverMode; std::auto_ptr<SaslAuthenticator> authenticator; - + AclModule* acl; + SecureConnection* secured; + Handler(Connection& connection, bool isClient); ~Handler(); void startOk(const qpid::framing::FieldTable& clientProperties, const std::string& mechanism, const std::string& response, - const std::string& locale); - void secureOk(const std::string& response); - void tuneOk(uint16_t channelMax, uint16_t frameMax, uint16_t heartbeat); - void heartbeat() {} + const std::string& locale); + void secureOk(const std::string& response); + void tuneOk(uint16_t channelMax, uint16_t frameMax, uint16_t heartbeat); + void heartbeat(); void open(const std::string& virtualHost, - const framing::Array& capabilities, bool insist); - void close(uint16_t replyCode, const std::string& replyText); - void closeOk(); + const framing::Array& capabilities, bool insist); + void close(uint16_t replyCode, const std::string& replyText); + void closeOk(); void start(const qpid::framing::FieldTable& serverProperties, const framing::Array& mechanisms, const framing::Array& locales); - + void secure(const std::string& challenge); - + void tune(uint16_t channelMax, uint16_t frameMax, uint16_t heartbeatMin, uint16_t heartbeatMax); - + void openOk(const framing::Array& knownHosts); - - void redirect(const std::string& host, const framing::Array& knownHosts); + + void redirect(const std::string& host, const framing::Array& knownHosts); }; std::auto_ptr<Handler> handler; public: ConnectionHandler(Connection& connection, bool isClient); - void close(framing::ReplyCode code, const std::string& text, framing::ClassId classId, framing::MethodId methodId); + void close(framing::connection::CloseCode code, const std::string& text); + void heartbeat(); void handle(framing::AMQFrame& frame); + void setSecureConnection(SecureConnection* secured); }; diff --git a/cpp/src/qpid/broker/ConnectionState.h b/cpp/src/qpid/broker/ConnectionState.h index c9cf6ece8d..77ac5a59b0 100644 --- a/cpp/src/qpid/broker/ConnectionState.h +++ b/cpp/src/qpid/broker/ConnectionState.h @@ -24,61 +24,97 @@ #include <vector> #include "qpid/sys/AggregateOutput.h" -#include "qpid/sys/ConnectionOutputHandler.h" +#include "qpid/sys/ConnectionOutputHandlerPtr.h" #include "qpid/framing/ProtocolVersion.h" #include "qpid/management/Manageable.h" -#include "Broker.h" +#include "qpid/Url.h" +#include "qpid/broker/Broker.h" namespace qpid { namespace broker { class ConnectionState : public ConnectionToken, public management::Manageable { + protected: + sys::ConnectionOutputHandlerPtr out; + public: - ConnectionState(qpid::sys::ConnectionOutputHandler* o, Broker& b) : - broker(b), - outputTasks(*o), - out(o), - framemax(65535), + ConnectionState(qpid::sys::ConnectionOutputHandler* o, Broker& b) : + out(o), + broker(b), + outputTasks(out), + framemax(65535), heartbeat(0), - stagingThreshold(broker.getStagingThreshold()) - {} - - + heartbeatmax(120), + stagingThreshold(broker.getStagingThreshold()), + federationLink(true), + clientSupportsThrottling(false), + clusterOrderOut(0) + {} virtual ~ConnectionState () {} uint32_t getFrameMax() const { return framemax; } uint16_t getHeartbeat() const { return heartbeat; } + uint16_t getHeartbeatMax() const { return heartbeatmax; } uint64_t getStagingThreshold() const { return stagingThreshold; } - void setFrameMax(uint32_t fm) { framemax = fm; } + void setFrameMax(uint32_t fm) { framemax = std::max(fm, (uint32_t) 4096); } void setHeartbeat(uint16_t hb) { heartbeat = hb; } + void setHeartbeatMax(uint16_t hbm) { heartbeatmax = hbm; } void setStagingThreshold(uint64_t st) { stagingThreshold = st; } virtual void setUserId(const string& uid) { userId = uid; } const string& getUserId() const { return userId; } + + void setUrl(const string& _url) { url = _url; } + const string& getUrl() const { return url; } + + void setFederationLink(bool b) { federationLink = b; } + bool isFederationLink() const { return federationLink; } + void setFederationPeerTag(const string& tag) { federationPeerTag = string(tag); } + const string& getFederationPeerTag() const { return federationPeerTag; } + std::vector<Url>& getKnownHosts() { return knownHosts; } + void setClientThrottling(bool set=true) { clientSupportsThrottling = set; } + bool getClientThrottling() const { return clientSupportsThrottling; } + Broker& getBroker() { return broker; } Broker& broker; std::vector<Queue::shared_ptr> exclusiveQueues; - + //contained output tasks sys::AggregateOutput outputTasks; - sys::ConnectionOutputHandler& getOutput() const { return *out; } + sys::ConnectionOutputHandler& getOutput() { return out; } 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; } - void setOutputHandler(qpid::sys::ConnectionOutputHandler* o) { out = o; } + virtual void requestIOProcessing (boost::function0<void>) = 0; protected: framing::ProtocolVersion version; - sys::ConnectionOutputHandler* out; uint32_t framemax; uint16_t heartbeat; + uint16_t heartbeatmax; uint64_t stagingThreshold; string userId; + string url; + bool federationLink; + string federationPeerTag; + std::vector<Url> knownHosts; + bool clientSupportsThrottling; + framing::FrameHandler* clusterOrderOut; }; }} diff --git a/cpp/src/qpid/broker/ConnectionToken.h b/cpp/src/qpid/broker/ConnectionToken.h index 0e3b301897..9b40383c80 100644 --- a/cpp/src/qpid/broker/ConnectionToken.h +++ b/cpp/src/qpid/broker/ConnectionToken.h @@ -21,7 +21,7 @@ #ifndef _ConnectionToken_ #define _ConnectionToken_ -#include "OwnershipToken.h" +#include "qpid/broker/OwnershipToken.h" namespace qpid { namespace broker { /** diff --git a/cpp/src/qpid/broker/Consumer.h b/cpp/src/qpid/broker/Consumer.h index 4274ce823e..b96443fa7c 100644 --- a/cpp/src/qpid/broker/Consumer.h +++ b/cpp/src/qpid/broker/Consumer.h @@ -21,47 +21,33 @@ #ifndef _Consumer_ #define _Consumer_ -namespace qpid { - namespace broker { - class Queue; -}} - -#include "Message.h" -#include "OwnershipToken.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/broker/OwnershipToken.h" namespace qpid { - namespace broker { - - struct QueuedMessage - { - boost::intrusive_ptr<Message> payload; - framing::SequenceNumber position; - Queue* queue; - - QueuedMessage(Queue* q, boost::intrusive_ptr<Message> msg, framing::SequenceNumber sn) : - payload(msg), position(sn), queue(q) {} - QueuedMessage(Queue* q) : queue(q) {} - }; - +namespace broker { + +class Queue; + +class Consumer { + const bool acquires; + public: + typedef boost::shared_ptr<Consumer> shared_ptr; + + framing::SequenceNumber position; + + Consumer(bool preAcquires = true) : acquires(preAcquires) {} + bool preAcquires() const { return acquires; } + virtual bool deliver(QueuedMessage& msg) = 0; + virtual void notify() = 0; + virtual bool filter(boost::intrusive_ptr<Message>) { return true; } + virtual bool accept(boost::intrusive_ptr<Message>) { return true; } + virtual OwnershipToken* getSession() = 0; + virtual ~Consumer(){} +}; - class Consumer { - const bool acquires; - public: - typedef shared_ptr<Consumer> ptr; - - framing::SequenceNumber position; - - Consumer(bool preAcquires = true) : acquires(preAcquires) {} - bool preAcquires() const { return acquires; } - virtual bool deliver(QueuedMessage& msg) = 0; - virtual void notify() = 0; - virtual bool filter(boost::intrusive_ptr<Message>) { return true; } - virtual bool accept(boost::intrusive_ptr<Message>) { return true; } - virtual OwnershipToken* getSession() = 0; - virtual ~Consumer(){} - }; - } -} +}} #endif diff --git a/cpp/src/qpid/broker/Daemon.cpp b/cpp/src/qpid/broker/Daemon.cpp index c311730f76..b30e5f18cb 100644 --- a/cpp/src/qpid/broker/Daemon.cpp +++ b/cpp/src/qpid/broker/Daemon.cpp @@ -15,10 +15,16 @@ * limitations under the License. * */ -#include "Daemon.h" + +/* + * TODO: Note this is really a Posix specific implementation and so should be + * refactored together with windows/QpiddBroker into a more coherent daemon driver/ + * platform specific split + */ +#include "qpid/broker/Daemon.h" #include "qpid/log/Statement.h" #include "qpid/Exception.h" -#include "qpid/sys/LockFile.h" +#include "qpid/sys/posix/PidFile.h" #include <errno.h> #include <fcntl.h> @@ -31,7 +37,7 @@ namespace qpid { namespace broker { using namespace std; -using qpid::sys::LockFile; +using qpid::sys::PidFile; Daemon::Daemon(std::string _pidDir) : pidDir(_pidDir) { struct stat s; @@ -85,12 +91,13 @@ void Daemon::fork() child(); } catch (const exception& e) { - QPID_LOG(critical, "Daemon startup failed: " << e.what()); + QPID_LOG(critical, "Unexpected error: " << e.what()); uint16_t port = 0; - write(pipeFds[1], &port, sizeof(uint16_t)); + int unused_ret; //Supress warning about ignoring return value. + unused_ret = write(pipeFds[1], &port, sizeof(uint16_t)); std::string pipeFailureMessage = e.what(); - write ( pipeFds[1], + unused_ret = write ( pipeFds[1], pipeFailureMessage.c_str(), strlen(pipeFailureMessage.c_str()) ); @@ -108,52 +115,61 @@ Daemon::~Daemon() { } uint16_t Daemon::wait(int timeout) { // parent waits for child. - errno = 0; - struct timeval tv; - tv.tv_sec = timeout; - tv.tv_usec = 0; - - /* - * Rewritten using low-level IO, for compatibility - * with earlier Boost versions, i.e. 103200. - */ - fd_set fds; - FD_ZERO(&fds); - FD_SET(pipeFds[0], &fds); - int n=select(FD_SETSIZE, &fds, 0, 0, &tv); - if(n==0) throw Exception("Timed out waiting for daemon"); - if(n<0) throw ErrnoException("Error waiting for daemon"); - uint16_t port = 0; - /* - * Read the child's port number from the pipe. - */ - int desired_read = sizeof(uint16_t); - if ( desired_read > ::read(pipeFds[0], & port, desired_read) ) { - throw Exception("Cannot write lock file "+lockFile); + try { + errno = 0; + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + + /* + * Rewritten using low-level IO, for compatibility + * with earlier Boost versions, i.e. 103200. + */ + fd_set fds; + FD_ZERO(&fds); + FD_SET(pipeFds[0], &fds); + int n=select(FD_SETSIZE, &fds, 0, 0, &tv); + if(n==0) throw Exception("Timed out waiting for daemon (If store recovery is in progress, use longer wait time)"); + if(n<0) throw ErrnoException("Error waiting for daemon"); + uint16_t port = 0; + /* + * Read the child's port number from the pipe. + */ + int desired_read = sizeof(uint16_t); + if ( desired_read > ::read(pipeFds[0], & port, desired_read) ) + throw Exception("Cannot read from child process."); + + /* + * If the port number is 0, the child has put an error message + * on the pipe. Get it and throw it. + */ + if ( 0 == port ) { + // Skip whitespace + char c = ' '; + while ( isspace(c) ) { + if ( 1 > ::read(pipeFds[0], &c, 1) ) + throw Exception("Child port == 0, and no error message on pipe."); + } + + // Get Message + string errmsg; + do { + errmsg += c; + } while (::read(pipeFds[0], &c, 1)); + throw Exception("Daemon startup failed"+ + (errmsg.empty() ? string(".") : ": " + errmsg)); + } + return port; + } + catch (const std::exception& e) { + // Print directly to cerr. The caller will catch and log the + // exception, but in the case of a daemon parent process we + // also need to be sure the error goes to stderr. A + // dameon's logging configuration normally does not log to + // stderr. + std::cerr << e.what() << endl; + throw; } - - /* - * If the port number is 0, the child has put an error message - * on the pipe. Get it and throw it. - */ - if ( 0 == port ) { - // Skip whitespace - char c = ' '; - while ( isspace(c) ) { - if ( 1 > ::read(pipeFds[0], &c, 1) ) - throw Exception("Child port == 0, and no error message on pipe."); - } - - // Get Message - string errmsg; - do { - errmsg += c; - } while (::read(pipeFds[0], &c, 1)); - throw Exception("Daemon startup failed"+ - (errmsg.empty() ? string(".") : ": " + errmsg)); - } - - return port; } @@ -166,25 +182,17 @@ uint16_t Daemon::wait(int timeout) { // parent waits for child. */ void Daemon::ready(uint16_t port) { // child lockFile = pidFile(pidDir, port); - LockFile lf(lockFile, true); + PidFile lf(lockFile, true); /* - * Rewritten using low-level IO, for compatibility - * with earlier Boost versions, i.e. 103200. - */ - /* * Write the PID to the lockfile. */ - pid_t pid = getpid(); - int desired_write = sizeof(pid_t); - if ( desired_write > ::write(lf.fd, & pid, desired_write) ) { - throw Exception("Cannot write lock file "+lockFile); - } + lf.writePid(); /* * Write the port number to the parent. */ - desired_write = sizeof(uint16_t); + int desired_write = sizeof(uint16_t); if ( desired_write > ::write(pipeFds[1], & port, desired_write) ) { throw Exception("Error writing to parent." ); } @@ -198,17 +206,8 @@ void Daemon::ready(uint16_t port) { // child */ pid_t Daemon::getPid(string _pidDir, uint16_t port) { string name = pidFile(_pidDir, port); - LockFile lf(name, false); - pid_t pid; - - /* - * Rewritten using low-level IO, for compatibility - * with earlier Boost versions, i.e. 103200. - */ - int desired_read = sizeof(pid_t); - if ( desired_read > ::read(lf.fd, & pid, desired_read) ) { - throw Exception("Cannot read lock file " + name); - } + PidFile lf(name, false); + pid_t pid = lf.readPid(); if (kill(pid, 0) < 0 && errno != EPERM) { unlink(name.c_str()); throw Exception("Removing stale lock file "+name); diff --git a/cpp/src/qpid/broker/Daemon.h b/cpp/src/qpid/broker/Daemon.h index 98468debb7..a9cd98bce2 100644 --- a/cpp/src/qpid/broker/Daemon.h +++ b/cpp/src/qpid/broker/Daemon.h @@ -19,10 +19,12 @@ * */ -#include <string> +#include "qpid/sys/IntegerTypes.h" #include <boost/scoped_ptr.hpp> #include <boost/function.hpp> #include <boost/noncopyable.hpp> +#include <string> + namespace qpid { namespace broker { diff --git a/cpp/src/qpid/broker/Deliverable.h b/cpp/src/qpid/broker/Deliverable.h index c40780c4ae..433469a212 100644 --- a/cpp/src/qpid/broker/Deliverable.h +++ b/cpp/src/qpid/broker/Deliverable.h @@ -21,8 +21,8 @@ #ifndef _Deliverable_ #define _Deliverable_ -#include "Queue.h" -#include "Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/Message.h" namespace qpid { namespace broker { @@ -33,7 +33,7 @@ namespace qpid { virtual Message& getMessage() = 0; - virtual void deliverTo(Queue::shared_ptr& queue) = 0; + virtual void deliverTo(const boost::shared_ptr<Queue>& queue) = 0; virtual uint64_t contentSize() { return 0; } virtual ~Deliverable(){} }; diff --git a/cpp/src/qpid/broker/DeliverableMessage.cpp b/cpp/src/qpid/broker/DeliverableMessage.cpp index fd15acf464..658e6bf48f 100644 --- a/cpp/src/qpid/broker/DeliverableMessage.cpp +++ b/cpp/src/qpid/broker/DeliverableMessage.cpp @@ -18,15 +18,15 @@ * under the License. * */ -#include "DeliverableMessage.h" +#include "qpid/broker/DeliverableMessage.h" using namespace qpid::broker; -DeliverableMessage::DeliverableMessage(boost::intrusive_ptr<Message>& _msg) : msg(_msg) +DeliverableMessage::DeliverableMessage(const boost::intrusive_ptr<Message>& _msg) : msg(_msg) { } -void DeliverableMessage::deliverTo(Queue::shared_ptr& queue) +void DeliverableMessage::deliverTo(const boost::shared_ptr<Queue>& queue) { queue->deliver(msg); delivered = true; diff --git a/cpp/src/qpid/broker/DeliverableMessage.h b/cpp/src/qpid/broker/DeliverableMessage.h index 18e1ec5e29..08abce35ef 100644 --- a/cpp/src/qpid/broker/DeliverableMessage.h +++ b/cpp/src/qpid/broker/DeliverableMessage.h @@ -21,9 +21,10 @@ #ifndef _DeliverableMessage_ #define _DeliverableMessage_ -#include "Deliverable.h" -#include "Queue.h" -#include "Message.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/Message.h" #include <boost/intrusive_ptr.hpp> @@ -32,10 +33,10 @@ namespace qpid { class DeliverableMessage : public Deliverable{ boost::intrusive_ptr<Message> msg; public: - DeliverableMessage(boost::intrusive_ptr<Message>& msg); - virtual void deliverTo(Queue::shared_ptr& queue); - Message& getMessage(); - uint64_t contentSize(); + QPID_BROKER_EXTERN DeliverableMessage(const boost::intrusive_ptr<Message>& msg); + 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 index 4c2b2f615f..b0bec60890 100644 --- a/cpp/src/qpid/broker/DeliveryAdapter.h +++ b/cpp/src/qpid/broker/DeliveryAdapter.h @@ -21,30 +21,31 @@ #ifndef _DeliveryAdapter_ #define _DeliveryAdapter_ -#include "DeliveryId.h" -#include "DeliveryToken.h" -#include "Message.h" +#include "qpid/broker/DeliveryId.h" +#include "qpid/broker/Message.h" #include "qpid/framing/amqp_types.h" namespace qpid { namespace broker { - /** - * The intention behind this interface is to separate the generic - * handling of some form of message delivery to clients that is - * contained in the version independent Channel class from the - * details required for a particular situation or - * version. i.e. where the existing adapters allow (through - * supporting the generated interface for a version of the - * protocol) inputs of a channel to be adapted to the version - * independent part, this does the same for the outputs. - */ - class DeliveryAdapter - { - public: - virtual DeliveryId deliver(QueuedMessage& msg, DeliveryToken::shared_ptr token) = 0; - virtual ~DeliveryAdapter(){} - }; +class DeliveryRecord; + +/** + * The intention behind this interface is to separate the generic + * handling of some form of message delivery to clients that is + * contained in the version independent Channel class from the + * details required for a particular situation or + * version. i.e. where the existing adapters allow (through + * supporting the generated interface for a version of the + * protocol) inputs of a channel to be adapted to the version + * independent part, this does the same for the outputs. + */ +class DeliveryAdapter +{ + public: + virtual void deliver(DeliveryRecord&, bool sync) = 0; + virtual ~DeliveryAdapter(){} +}; }} diff --git a/cpp/src/qpid/broker/DeliveryRecord.cpp b/cpp/src/qpid/broker/DeliveryRecord.cpp index 530dca99a4..22ec5e86a0 100644 --- a/cpp/src/qpid/broker/DeliveryRecord.cpp +++ b/cpp/src/qpid/broker/DeliveryRecord.cpp @@ -18,91 +18,72 @@ * under the License. * */ -#include "DeliveryRecord.h" -#include "DeliverableMessage.h" -#include "SemanticState.h" -#include "Exchange.h" +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/SemanticState.h" +#include "qpid/broker/Exchange.h" #include "qpid/log/Statement.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/MessageTransferBody.h" +using namespace qpid; using namespace qpid::broker; using std::string; DeliveryRecord::DeliveryRecord(const QueuedMessage& _msg, - Queue::shared_ptr _queue, - const std::string _tag, - DeliveryToken::shared_ptr _token, - const DeliveryId _id, - bool _acquired, bool accepted) : msg(_msg), - queue(_queue), - tag(_tag), - token(_token), - id(_id), - acquired(_acquired), - pull(false), - cancelled(false), - credit(msg.payload ? msg.payload->getRequiredCredit() : 0), - size(msg.payload ? msg.payload->contentSize() : 0), - completed(false), - ended(accepted) -{ - if (accepted) setEnded(); -} - -DeliveryRecord::DeliveryRecord(const QueuedMessage& _msg, - Queue::shared_ptr _queue, - const DeliveryId _id) : msg(_msg), - queue(_queue), - id(_id), - acquired(true), - pull(true), - cancelled(false), - credit(msg.payload ? msg.payload->getRequiredCredit() : 0), - size(msg.payload ? msg.payload->contentSize() : 0), - completed(false), - ended(false) + const Queue::shared_ptr& _queue, + const std::string& _tag, + bool _acquired, + bool accepted, + bool _windowing, + uint32_t _credit) : msg(_msg), + queue(_queue), + tag(_tag), + acquired(_acquired), + acceptExpected(!accepted), + cancelled(false), + completed(false), + ended(accepted && acquired), + windowing(_windowing), + credit(msg.payload ? msg.payload->getRequiredCredit() : _credit) {} -void DeliveryRecord::setEnded() +bool DeliveryRecord::setEnded() { ended = true; //reset msg pointer, don't need to hold on to it anymore msg.payload = boost::intrusive_ptr<Message>(); - QPID_LOG(debug, "DeliveryRecord::setEnded() id=" << id); -} - -bool DeliveryRecord::matches(DeliveryId tag) const{ - return id == tag; -} - -bool DeliveryRecord::matchOrAfter(DeliveryId tag) const{ - return matches(tag) || after(tag); -} - -bool DeliveryRecord::after(DeliveryId tag) const{ - return id > tag; -} - -bool DeliveryRecord::coveredBy(const framing::SequenceSet* const range) const{ - return range->contains(id); + return isRedundant(); } void DeliveryRecord::redeliver(SemanticState* const session) { if (!ended) { - if(pull || cancelled){ - //if message was originally sent as response to get, we must requeue it - - //or if subscription was cancelled, requeue it (waiting for + if(cancelled){ + //if subscription was cancelled, requeue it (waiting for //final confirmation for AMQP WG on this case) - requeue(); }else{ msg.payload->redeliver();//mark as redelivered - id = session->redeliver(msg, token); + session->deliver(*this, false); } } } +void DeliveryRecord::deliver(framing::FrameHandler& h, DeliveryId deliveryId, uint16_t framesize) +{ + id = deliveryId; + if (msg.payload->getRedelivered()){ + msg.payload->getProperties<framing::DeliveryProperties>()->setRedelivered(true); + } + + framing::AMQFrame method((framing::MessageTransferBody(framing::ProtocolVersion(), tag, acceptExpected ? 0 : 1, acquired ? 0 : 1))); + method.setEof(false); + h.handle(method); + msg.payload->sendHeader(h, framesize); + msg.payload->sendContent(*queue, h, framesize); +} + void DeliveryRecord::requeue() const { if (acquired && !ended) { @@ -123,25 +104,29 @@ void DeliveryRecord::release(bool setRedelivered) } } -void DeliveryRecord::complete() -{ +void DeliveryRecord::complete() { completed = true; } -void DeliveryRecord::accept(TransactionContext* ctxt) { +bool DeliveryRecord::accept(TransactionContext* ctxt) { if (acquired && !ended) { - queue->dequeue(ctxt, msg.payload); + queue->dequeue(ctxt, msg); setEnded(); QPID_LOG(debug, "Accepted " << id); } + return isRedundant(); } void DeliveryRecord::dequeue(TransactionContext* ctxt) const{ if (acquired && !ended) { - queue->dequeue(ctxt, msg.payload); + queue->dequeue(ctxt, msg); } } +void DeliveryRecord::committed() const{ + queue->dequeueCommitted(msg); +} + void DeliveryRecord::reject() { Exchange::shared_ptr alternate = queue->getAlternateExchange(); @@ -161,29 +146,14 @@ uint32_t DeliveryRecord::getCredit() const return credit; } - -void DeliveryRecord::addTo(Prefetch& prefetch) const{ - if(!pull){ - //ignore 'pulled' messages (i.e. those that were sent in - //response to get) when calculating prefetch - prefetch.size += size; - prefetch.count++; - } -} - -void DeliveryRecord::subtractFrom(Prefetch& prefetch) const{ - if(!pull){ - //ignore 'pulled' messages (i.e. those that were sent in - //response to get) when calculating prefetch - prefetch.size -= size; - prefetch.count--; - } -} - void DeliveryRecord::acquire(DeliveryIds& results) { if (queue->acquire(msg)) { acquired = true; results.push_back(id); + if (!acceptExpected) { + if (ended) { QPID_LOG(error, "Can't dequeue ended message"); } + else { queue->dequeue(0, msg); setEnded(); } + } } else { QPID_LOG(info, "Message already acquired " << id.getValue()); } @@ -195,6 +165,16 @@ void DeliveryRecord::cancel(const std::string& cancelledTag) cancelled = true; } +AckRange DeliveryRecord::findRange(DeliveryRecords& records, DeliveryId first, DeliveryId last) +{ + DeliveryRecords::iterator start = lower_bound(records.begin(), records.end(), first); + // Find end - position it just after the last record in range + DeliveryRecords::iterator end = lower_bound(records.begin(), records.end(), last); + if (end != records.end() && end->getId() == last) ++end; + return AckRange(start, end); +} + + namespace qpid { namespace broker { @@ -206,9 +186,5 @@ std::ostream& operator<<(std::ostream& out, const DeliveryRecord& r) return out; } -bool operator<(const DeliveryRecord& a, const DeliveryRecord& b) -{ - return a.id < b.id; -} }} diff --git a/cpp/src/qpid/broker/DeliveryRecord.h b/cpp/src/qpid/broker/DeliveryRecord.h index 78dc99e3c6..5f802766b6 100644 --- a/cpp/src/qpid/broker/DeliveryRecord.h +++ b/cpp/src/qpid/broker/DeliveryRecord.h @@ -1,3 +1,6 @@ +#ifndef QPID_BROKER_DELIVERYRECORD_H +#define QPID_BROKER_DELIVERYRECORD_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -18,53 +21,60 @@ * under the License. * */ -#ifndef _DeliveryRecord_ -#define _DeliveryRecord_ #include <algorithm> -#include <list> +#include <deque> #include <vector> #include <ostream> #include "qpid/framing/SequenceSet.h" -#include "Queue.h" -#include "Consumer.h" -#include "DeliveryId.h" -#include "DeliveryToken.h" -#include "Message.h" -#include "Prefetch.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/broker/DeliveryId.h" +#include "qpid/broker/Message.h" namespace qpid { namespace broker { class SemanticState; +struct AckRange; /** * Record of a delivery for which an ack is outstanding. */ -class DeliveryRecord{ +class DeliveryRecord +{ QueuedMessage msg; mutable Queue::shared_ptr queue; - const std::string tag; - DeliveryToken::shared_ptr token; + std::string tag; DeliveryId id; - bool acquired; - const bool pull; - bool cancelled; - const uint32_t credit; - const uint64_t size; - - bool completed; - bool ended; + bool acquired : 1; + bool acceptExpected : 1; + bool cancelled : 1; + bool completed : 1; + bool ended : 1; + bool windowing : 1; + + /** + * Record required credit on construction as the pointer to the + * message may be reset once we no longer need to deliver it + * (e.g. when it is accepted), but we will still need to be able + * to reallocate credit when it is completed (which could happen + * after that). + */ + uint32_t credit; public: - DeliveryRecord(const QueuedMessage& msg, Queue::shared_ptr queue, const std::string tag, DeliveryToken::shared_ptr token, - const DeliveryId id, bool acquired, bool confirmed = false); - DeliveryRecord(const QueuedMessage& msg, Queue::shared_ptr queue, const DeliveryId id); - - bool matches(DeliveryId tag) const; - bool matchOrAfter(DeliveryId tag) const; - bool after(DeliveryId tag) const; - bool coveredBy(const framing::SequenceSet* const range) const; - + QPID_BROKER_EXTERN DeliveryRecord(const QueuedMessage& msg, + const Queue::shared_ptr& queue, + const std::string& tag, + bool acquired, + bool accepted, + bool windowing, + uint32_t credit=0 // Only used if msg is empty. + ); + + bool coveredBy(const framing::SequenceSet* const range) const { return range->contains(id); } + void dequeue(TransactionContext* ctxt = 0) const; void requeue() const; void release(bool setRedelivered); @@ -73,32 +83,37 @@ class DeliveryRecord{ void redeliver(SemanticState* const); void acquire(DeliveryIds& results); void complete(); - void accept(TransactionContext* ctxt); - void setEnded(); + bool accept(TransactionContext* ctxt); // Returns isRedundant() + bool setEnded(); // Returns isRedundant() + void committed() const; bool isAcquired() const { return acquired; } bool isComplete() const { return completed; } - bool isRedundant() const { return ended && completed; } - + bool isRedundant() const { return ended && (!windowing || completed); } + bool isCancelled() const { return cancelled; } + bool isAccepted() const { return !acceptExpected; } + bool isEnded() const { return ended; } + bool isWindowing() const { return windowing; } + uint32_t getCredit() const; - void addTo(Prefetch&) const; - void subtractFrom(Prefetch&) const; - const std::string& getTag() const { return tag; } - bool isPull() const { return pull; } - friend bool operator<(const DeliveryRecord&, const DeliveryRecord&); - friend std::ostream& operator<<(std::ostream&, const DeliveryRecord&); -}; + const std::string& getTag() const { return tag; } -typedef std::list<DeliveryRecord> DeliveryRecords; -typedef std::list<DeliveryRecord>::iterator ack_iterator; + void deliver(framing::FrameHandler& h, DeliveryId deliveryId, uint16_t framesize); + void setId(DeliveryId _id) { id = _id; } -struct AckRange -{ - ack_iterator start; - ack_iterator end; - AckRange(ack_iterator _start, ack_iterator _end) : start(_start), end(_end) {} + typedef std::deque<DeliveryRecord> DeliveryRecords; + static AckRange findRange(DeliveryRecords& records, DeliveryId first, DeliveryId last); + const QueuedMessage& getMessage() const { return msg; } + framing::SequenceNumber getId() const { return id; } + Queue::shared_ptr getQueue() const { return queue; } + + friend std::ostream& operator<<(std::ostream&, const DeliveryRecord&); }; +inline bool operator<(const DeliveryRecord& a, const DeliveryRecord& b) { return a.getId() < b.getId(); } +inline bool operator<(const framing::SequenceNumber& a, const DeliveryRecord& b) { return a < b.getId(); } +inline bool operator<(const DeliveryRecord& a, const framing::SequenceNumber& b) { return a.getId() < b; } + struct AcquireFunctor { DeliveryIds& results; @@ -111,8 +126,17 @@ struct AcquireFunctor } }; +typedef DeliveryRecord::DeliveryRecords DeliveryRecords; + +struct AckRange +{ + DeliveryRecords::iterator start; + DeliveryRecords::iterator end; + AckRange(DeliveryRecords::iterator _start, DeliveryRecords::iterator _end) : start(_start), end(_end) {} +}; + } } -#endif +#endif /*!QPID_BROKER_DELIVERYRECORD_H*/ diff --git a/cpp/src/qpid/broker/DirectExchange.cpp b/cpp/src/qpid/broker/DirectExchange.cpp index 4aa68bee9c..094f59cdec 100644 --- a/cpp/src/qpid/broker/DirectExchange.cpp +++ b/cpp/src/qpid/broker/DirectExchange.cpp @@ -19,111 +19,142 @@ * */ #include "qpid/log/Statement.h" -#include "DirectExchange.h" +#include "qpid/broker/DirectExchange.h" #include <iostream> using namespace qpid::broker; using namespace qpid::framing; using namespace qpid::sys; using qpid::management::Manageable; +namespace _qmf = qmf::org::apache::qpid::broker; -DirectExchange::DirectExchange(const string& _name, Manageable* _parent) : Exchange(_name, _parent) +namespace +{ +const std::string qpidFedOp("qpid.fed.op"); +const std::string qpidFedTags("qpid.fed.tags"); +const std::string qpidFedOrigin("qpid.fed.origin"); +const std::string qpidExclusiveBinding("qpid.exclusive-binding"); + +const std::string fedOpBind("B"); +const std::string fedOpUnbind("U"); +const std::string fedOpReorigin("R"); +const std::string fedOpHello("H"); +} + +DirectExchange::DirectExchange(const string& _name, Manageable* _parent, Broker* b) : Exchange(_name, _parent, b) { if (mgmtExchange != 0) - mgmtExchange->set_type (typeName); + mgmtExchange->set_type(typeName); } -DirectExchange::DirectExchange(const std::string& _name, bool _durable, - const FieldTable& _args, Manageable* _parent) : - Exchange(_name, _durable, _args, _parent) +DirectExchange::DirectExchange(const string& _name, bool _durable, + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b) { if (mgmtExchange != 0) - mgmtExchange->set_type (typeName); + mgmtExchange->set_type(typeName); } -bool DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable*){ - RWlock::ScopedWlock l(lock); - std::vector<Binding::shared_ptr>& queues(bindings[routingKey]); - std::vector<Binding::shared_ptr>::iterator i; - - for (i = queues.begin(); i != queues.end(); i++) - if ((*i)->queue == queue) - break; - - if (i == queues.end()) { - Binding::shared_ptr binding (new Binding (routingKey, queue, this)); - bindings[routingKey].push_back(binding); - if (mgmtExchange != 0) { - mgmtExchange->inc_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->inc_bindingCount(); - } - return true; - } else{ - return false; +bool DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args) +{ + string fedOp(fedOpBind); + string fedTags; + string fedOrigin; + bool exclusiveBinding = false; + if (args) { + fedOp = args->getAsString(qpidFedOp); + fedTags = args->getAsString(qpidFedTags); + fedOrigin = args->getAsString(qpidFedOrigin); + exclusiveBinding = args->get(qpidExclusiveBinding); } -} -bool DirectExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* /*args*/){ - RWlock::ScopedWlock l(lock); - std::vector<Binding::shared_ptr>& queues(bindings[routingKey]); - std::vector<Binding::shared_ptr>::iterator i; + bool propagate = false; - for (i = queues.begin(); i != queues.end(); i++) - if ((*i)->queue == queue) - break; + if (args == 0 || fedOp.empty() || fedOp == fedOpBind) { + Mutex::ScopedLock l(lock); + Binding::shared_ptr b(new Binding(routingKey, queue, this, FieldTable(), fedOrigin)); + BoundKey& bk = bindings[routingKey]; + if (exclusiveBinding) bk.queues.clear(); - if (i < queues.end()) { - queues.erase(i); - if (queues.empty()) { - bindings.erase(routingKey); + if (bk.queues.add_unless(b, MatchQueue(queue))) { + propagate = bk.fedBinding.addOrigin(fedOrigin); + if (mgmtExchange != 0) { + mgmtExchange->inc_bindingCount(); + } + } else { + return false; } - if (mgmtExchange != 0) { - mgmtExchange->dec_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->dec_bindingCount(); + } else if (fedOp == fedOpUnbind) { + Mutex::ScopedLock l(lock); + BoundKey& bk = bindings[routingKey]; + propagate = bk.fedBinding.delOrigin(fedOrigin); + if (bk.fedBinding.count() == 0) + unbind(queue, routingKey, 0); + } else if (fedOp == fedOpReorigin) { + /** gather up all the keys that need rebinding in a local vector + * while holding the lock. Then propagate once the lock is + * released + */ + std::vector<std::string> keys2prop; + { + Mutex::ScopedLock l(lock); + for (Bindings::iterator iter = bindings.begin(); + iter != bindings.end(); iter++) { + const BoundKey& bk = iter->second; + if (bk.fedBinding.hasLocal()) { + keys2prop.push_back(iter->first); + } + } + } /* lock dropped */ + for (std::vector<std::string>::const_iterator key = keys2prop.begin(); + key != keys2prop.end(); key++) { + propagateFedOp( *key, string(), fedOpBind, string()); } - return true; - } else { - return false; } + + routeIVE(); + if (propagate) + propagateFedOp(routingKey, fedTags, fedOp, fedOrigin); + return true; } -void DirectExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* /*args*/){ - RWlock::ScopedRlock l(lock); - std::vector<Binding::shared_ptr>& queues(bindings[routingKey]); - std::vector<Binding::shared_ptr>::iterator i; - int count(0); - - for(i = queues.begin(); i != queues.end(); i++, count++) { - msg.deliverTo((*i)->queue); - if ((*i)->mgmtBinding != 0) - (*i)->mgmtBinding->inc_msgMatched (); - } - - if(!count){ - QPID_LOG(warning, "DirectExchange " << getName() << " could not route message with key " << routingKey); - if (mgmtExchange != 0) { - mgmtExchange->inc_msgDrops (); - mgmtExchange->inc_byteDrops (msg.contentSize ()); - } - } - else { - if (mgmtExchange != 0) { - mgmtExchange->inc_msgRoutes (count); - mgmtExchange->inc_byteRoutes (count * msg.contentSize ()); +bool DirectExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* /*args*/) +{ + bool propagate = false; + + { + Mutex::ScopedLock l(lock); + BoundKey& bk = bindings[routingKey]; + if (bk.queues.remove_if(MatchQueue(queue))) { + propagate = bk.fedBinding.delOrigin(); + if (mgmtExchange != 0) { + mgmtExchange->dec_bindingCount(); + } + } else { + return false; } } - if (mgmtExchange != 0) { - mgmtExchange->inc_msgReceives (); - mgmtExchange->inc_byteReceives (msg.contentSize ()); + if (propagate) + propagateFedOp(routingKey, string(), fedOpUnbind, string()); + return true; +} + +void DirectExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* /*args*/) +{ + PreRoute pr(msg, this); + ConstBindingList b; + { + Mutex::ScopedLock l(lock); + b = bindings[routingKey].queues.snapshot(); } + doRoute(msg, b); } bool DirectExchange::isBound(Queue::shared_ptr queue, const string* const routingKey, const FieldTable* const) { - std::vector<Binding::shared_ptr>::iterator j; - + Mutex::ScopedLock l(lock); if (routingKey) { Bindings::iterator i = bindings.find(*routingKey); @@ -131,17 +162,17 @@ bool DirectExchange::isBound(Queue::shared_ptr queue, const string* const routin return false; if (!queue) return true; - for (j = i->second.begin(); j != i->second.end(); j++) - if ((*j)->queue == queue) - return true; + + Queues::ConstPtr p = i->second.queues.snapshot(); + return p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end(); } else if (!queue) { //if no queue or routing key is specified, just report whether any bindings exist return bindings.size() > 0; } else { - for (Bindings::iterator i = bindings.begin(); i != bindings.end(); i++) - for (j = i->second.begin(); j != i->second.end(); j++) - if ((*j)->queue == queue) - return true; + for (Bindings::iterator i = bindings.begin(); i != bindings.end(); i++) { + Queues::ConstPtr p = i->second.queues.snapshot(); + if (p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end()) return true; + } return false; } diff --git a/cpp/src/qpid/broker/DirectExchange.h b/cpp/src/qpid/broker/DirectExchange.h index 118f2ed4d3..9a73f3bc41 100644 --- a/cpp/src/qpid/broker/DirectExchange.h +++ b/cpp/src/qpid/broker/DirectExchange.h @@ -23,40 +23,53 @@ #include <map> #include <vector> -#include "Exchange.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" #include "qpid/framing/FieldTable.h" -#include "qpid/sys/Monitor.h" -#include "Queue.h" +#include "qpid/sys/CopyOnWriteArray.h" +#include "qpid/sys/Mutex.h" +#include "qpid/broker/Queue.h" namespace qpid { namespace broker { - class DirectExchange : public virtual Exchange{ - typedef std::vector<Binding::shared_ptr> Queues; - typedef std::map<string, Queues> Bindings; - Bindings bindings; - qpid::sys::RWlock lock; - - public: - static const std::string typeName; - - DirectExchange(const std::string& name, management::Manageable* parent = 0); - DirectExchange(const string& _name, bool _durable, - const qpid::framing::FieldTable& _args, management::Manageable* parent = 0); +class DirectExchange : public virtual Exchange { + typedef qpid::sys::CopyOnWriteArray<Binding::shared_ptr> Queues; + struct BoundKey { + Queues queues; + FedBinding fedBinding; + }; + typedef std::map<string, BoundKey> Bindings; + Bindings bindings; + qpid::sys::Mutex lock; - virtual std::string getType() const { return typeName; } +public: + static const std::string typeName; - virtual bool bind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); - - virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN DirectExchange(const std::string& name, + management::Manageable* parent = 0, Broker* broker = 0); + QPID_BROKER_EXTERN DirectExchange(const string& _name, + bool _durable, + const qpid::framing::FieldTable& _args, + management::Manageable* parent = 0, Broker* broker = 0); - virtual void route(Deliverable& msg, const std::string& routingKey, const qpid::framing::FieldTable* args); + virtual std::string getType() const { return typeName; } + + QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual void route(Deliverable& msg, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue, + const string* const routingKey, + const qpid::framing::FieldTable* const args); - virtual bool isBound(Queue::shared_ptr queue, const string* const routingKey, const qpid::framing::FieldTable* const args); + QPID_BROKER_EXTERN virtual ~DirectExchange(); - virtual ~DirectExchange(); - }; -} -} + virtual bool supportsDynamicBinding() { return true; } +}; +}} #endif diff --git a/cpp/src/qpid/broker/DtxAck.cpp b/cpp/src/qpid/broker/DtxAck.cpp index 47637369ca..bca3f90bbe 100644 --- a/cpp/src/qpid/broker/DtxAck.cpp +++ b/cpp/src/qpid/broker/DtxAck.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "DtxAck.h" +#include "qpid/broker/DtxAck.h" #include "qpid/log/Statement.h" using std::bind1st; @@ -26,7 +26,7 @@ using std::bind2nd; using std::mem_fun_ref; using namespace qpid::broker; -DtxAck::DtxAck(const framing::SequenceSet& acked, std::list<DeliveryRecord>& unacked) +DtxAck::DtxAck(const qpid::framing::SequenceSet& acked, DeliveryRecords& unacked) { remove_copy_if(unacked.begin(), unacked.end(), inserter(pending, pending.end()), not1(bind2nd(mem_fun_ref(&DeliveryRecord::coveredBy), &acked))); @@ -36,7 +36,7 @@ bool DtxAck::prepare(TransactionContext* ctxt) throw() { try{ //record dequeue in the store - for (ack_iterator i = pending.begin(); i != pending.end(); i++) { + for (DeliveryRecords::iterator i = pending.begin(); i != pending.end(); i++) { i->dequeue(ctxt); } return true; @@ -48,11 +48,26 @@ bool DtxAck::prepare(TransactionContext* ctxt) throw() void DtxAck::commit() throw() { - pending.clear(); + try { + for_each(pending.begin(), pending.end(), mem_fun_ref(&DeliveryRecord::committed)); + pending.clear(); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to commit: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to commit (unknown error)"); + } + } void DtxAck::rollback() throw() { - for_each(pending.begin(), pending.end(), mem_fun_ref(&DeliveryRecord::requeue)); - pending.clear(); + try { + for_each(pending.begin(), pending.end(), mem_fun_ref(&DeliveryRecord::requeue)); + pending.clear(); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to complete rollback: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to complete rollback (unknown error)"); + } + } diff --git a/cpp/src/qpid/broker/DtxAck.h b/cpp/src/qpid/broker/DtxAck.h index 05c4499839..166147e58d 100644 --- a/cpp/src/qpid/broker/DtxAck.h +++ b/cpp/src/qpid/broker/DtxAck.h @@ -25,20 +25,21 @@ #include <functional> #include <list> #include "qpid/framing/SequenceSet.h" -#include "DeliveryRecord.h" -#include "TxOp.h" +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/TxOp.h" namespace qpid { namespace broker { class DtxAck : public TxOp{ - std::list<DeliveryRecord> pending; + DeliveryRecords pending; public: - DtxAck(const framing::SequenceSet& acked, std::list<DeliveryRecord>& unacked); + DtxAck(const framing::SequenceSet& acked, DeliveryRecords& unacked); virtual bool prepare(TransactionContext* ctxt) throw(); virtual void commit() throw(); virtual void rollback() throw(); virtual ~DtxAck(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } }; } } diff --git a/cpp/src/qpid/broker/DtxBuffer.cpp b/cpp/src/qpid/broker/DtxBuffer.cpp index 29a07ea6d9..f1b8169cf7 100644 --- a/cpp/src/qpid/broker/DtxBuffer.cpp +++ b/cpp/src/qpid/broker/DtxBuffer.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "DtxBuffer.h" +#include "qpid/broker/DtxBuffer.h" using namespace qpid::broker; using qpid::sys::Mutex; diff --git a/cpp/src/qpid/broker/DtxBuffer.h b/cpp/src/qpid/broker/DtxBuffer.h index b302632037..1511cb032f 100644 --- a/cpp/src/qpid/broker/DtxBuffer.h +++ b/cpp/src/qpid/broker/DtxBuffer.h @@ -21,7 +21,8 @@ #ifndef _DtxBuffer_ #define _DtxBuffer_ -#include "TxBuffer.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/TxBuffer.h" #include "qpid/sys/Mutex.h" namespace qpid { @@ -37,9 +38,9 @@ namespace qpid { public: typedef boost::shared_ptr<DtxBuffer> shared_ptr; - DtxBuffer(const std::string& xid = ""); - ~DtxBuffer(); - void markEnded(); + QPID_BROKER_EXTERN DtxBuffer(const std::string& xid = ""); + QPID_BROKER_EXTERN ~DtxBuffer(); + QPID_BROKER_EXTERN void markEnded(); bool isEnded(); void setSuspended(bool suspended); bool isSuspended(); diff --git a/cpp/src/qpid/broker/DtxManager.cpp b/cpp/src/qpid/broker/DtxManager.cpp index 942dbdcbc6..a2ab20ec44 100644 --- a/cpp/src/qpid/broker/DtxManager.cpp +++ b/cpp/src/qpid/broker/DtxManager.cpp @@ -18,10 +18,11 @@ * under the License. * */ -#include "DtxManager.h" -#include "DtxTimeout.h" +#include "qpid/broker/DtxManager.h" +#include "qpid/broker/DtxTimeout.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/log/Statement.h" +#include "qpid/sys/Timer.h" #include "qpid/ptr_map.h" #include <boost/format.hpp> @@ -33,7 +34,7 @@ using qpid::ptr_map_ptr; using namespace qpid::broker; using namespace qpid::framing; -DtxManager::DtxManager() : store(0) {} +DtxManager::DtxManager(qpid::sys::Timer& t) : store(0), timer(t) {} DtxManager::~DtxManager() {} @@ -126,12 +127,11 @@ void DtxManager::setTimeout(const std::string& xid, uint32_t secs) intrusive_ptr<DtxTimeout> timeout = record->getTimeout(); if (timeout.get()) { if (timeout->timeout == secs) return;//no need to do anything further if timeout hasn't changed - timeout->cancelled = true; + timeout->cancel(); } timeout = intrusive_ptr<DtxTimeout>(new DtxTimeout(secs, *this, xid)); record->setTimeout(timeout); - timer.add(boost::static_pointer_cast<TimerTask>(timeout)); - + timer.add(timeout); } uint32_t DtxManager::getTimeout(const std::string& xid) @@ -160,13 +160,12 @@ void DtxManager::DtxCleanup::fire() { try { mgr.remove(xid); - } catch (ConnectionException& e) { + } catch (ConnectionException& /*e*/) { //assume it was explicitly cleaned up after a call to prepare, commit or rollback } } void DtxManager::setStore (TransactionalStore* _store) { - assert (store == 0 && _store != 0); store = _store; } diff --git a/cpp/src/qpid/broker/DtxManager.h b/cpp/src/qpid/broker/DtxManager.h index fa5c62c233..680b62eeb2 100644 --- a/cpp/src/qpid/broker/DtxManager.h +++ b/cpp/src/qpid/broker/DtxManager.h @@ -22,11 +22,11 @@ #define _DtxManager_ #include <boost/ptr_container/ptr_map.hpp> -#include "DtxBuffer.h" -#include "DtxWorkRecord.h" -#include "Timer.h" -#include "TransactionalStore.h" +#include "qpid/broker/DtxBuffer.h" +#include "qpid/broker/DtxWorkRecord.h" +#include "qpid/broker/TransactionalStore.h" #include "qpid/framing/amqp_types.h" +#include "qpid/sys/Timer.h" #include "qpid/sys/Mutex.h" namespace qpid { @@ -35,7 +35,7 @@ namespace broker { class DtxManager{ typedef boost::ptr_map<std::string, DtxWorkRecord> WorkMap; - struct DtxCleanup : public TimerTask + struct DtxCleanup : public sys::TimerTask { DtxManager& mgr; const std::string& xid; @@ -47,14 +47,14 @@ class DtxManager{ WorkMap work; TransactionalStore* store; qpid::sys::Mutex lock; - Timer timer; + qpid::sys::Timer& timer; void remove(const std::string& xid); DtxWorkRecord* getWork(const std::string& xid); DtxWorkRecord* createWork(std::string xid); public: - DtxManager(); + DtxManager(qpid::sys::Timer&); ~DtxManager(); void start(const std::string& xid, DtxBuffer::shared_ptr work); void join(const std::string& xid, DtxBuffer::shared_ptr work); diff --git a/cpp/src/qpid/broker/DtxTimeout.cpp b/cpp/src/qpid/broker/DtxTimeout.cpp index 8e0a7741c4..f5238d0909 100644 --- a/cpp/src/qpid/broker/DtxTimeout.cpp +++ b/cpp/src/qpid/broker/DtxTimeout.cpp @@ -18,8 +18,8 @@ * under the License. * */ -#include "DtxTimeout.h" -#include "DtxManager.h" +#include "qpid/broker/DtxTimeout.h" +#include "qpid/broker/DtxManager.h" #include "qpid/sys/Time.h" using namespace qpid::broker; diff --git a/cpp/src/qpid/broker/DtxTimeout.h b/cpp/src/qpid/broker/DtxTimeout.h index 6e949eab0d..680a210e4f 100644 --- a/cpp/src/qpid/broker/DtxTimeout.h +++ b/cpp/src/qpid/broker/DtxTimeout.h @@ -22,7 +22,7 @@ #define _DtxTimeout_ #include "qpid/Exception.h" -#include "Timer.h" +#include "qpid/sys/Timer.h" namespace qpid { namespace broker { @@ -31,12 +31,12 @@ class DtxManager; struct DtxTimeoutException : public Exception {}; -struct DtxTimeout : public TimerTask +struct DtxTimeout : public sys::TimerTask { const uint32_t timeout; DtxManager& mgr; const std::string xid; - + DtxTimeout(uint32_t timeout, DtxManager& mgr, const std::string& xid); void fire(); }; diff --git a/cpp/src/qpid/broker/DtxWorkRecord.cpp b/cpp/src/qpid/broker/DtxWorkRecord.cpp index cc79813dab..9f33e698db 100644 --- a/cpp/src/qpid/broker/DtxWorkRecord.cpp +++ b/cpp/src/qpid/broker/DtxWorkRecord.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "DtxWorkRecord.h" +#include "qpid/broker/DtxWorkRecord.h" #include "qpid/framing/reply_exceptions.h" #include <boost/format.hpp> #include <boost/mem_fn.hpp> @@ -34,7 +34,7 @@ DtxWorkRecord::DtxWorkRecord(const std::string& _xid, TransactionalStore* const DtxWorkRecord::~DtxWorkRecord() { if (timeout.get()) { - timeout->cancelled = true; + timeout->cancel(); } } diff --git a/cpp/src/qpid/broker/DtxWorkRecord.h b/cpp/src/qpid/broker/DtxWorkRecord.h index 6677784c32..aec2d2aed4 100644 --- a/cpp/src/qpid/broker/DtxWorkRecord.h +++ b/cpp/src/qpid/broker/DtxWorkRecord.h @@ -21,9 +21,10 @@ #ifndef _DtxWorkRecord_ #define _DtxWorkRecord_ -#include "DtxBuffer.h" -#include "DtxTimeout.h" -#include "TransactionalStore.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" #include "qpid/sys/Mutex.h" @@ -61,12 +62,13 @@ class DtxWorkRecord void abort(); bool prepare(TransactionContext* txn); public: - DtxWorkRecord(const std::string& xid, TransactionalStore* const store); - ~DtxWorkRecord(); - bool prepare(); - bool commit(bool onePhase); - void rollback(); - void add(DtxBuffer::shared_ptr ops); + QPID_BROKER_EXTERN DtxWorkRecord(const std::string& xid, + TransactionalStore* const store); + QPID_BROKER_EXTERN ~DtxWorkRecord(); + QPID_BROKER_EXTERN bool prepare(); + QPID_BROKER_EXTERN bool commit(bool onePhase); + QPID_BROKER_EXTERN void rollback(); + 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; } diff --git a/cpp/src/qpid/broker/Exchange.cpp b/cpp/src/qpid/broker/Exchange.cpp index fbfcaede82..8efb9ac545 100644 --- a/cpp/src/qpid/broker/Exchange.cpp +++ b/cpp/src/qpid/broker/Exchange.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 @@ -19,52 +19,157 @@ * */ -#include "Exchange.h" -#include "ExchangeRegistry.h" -#include "qpid/agent/ManagementAgent.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/Broker.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/broker/DeliverableMessage.h" using namespace qpid::broker; +using namespace qpid::framing; using qpid::framing::Buffer; using qpid::framing::FieldTable; +using qpid::sys::Mutex; using qpid::management::ManagementAgent; using qpid::management::ManagementObject; using qpid::management::Manageable; using qpid::management::Args; +namespace _qmf = qmf::org::apache::qpid::broker; -Exchange::Exchange (const string& _name, Manageable* parent) : - name(_name), durable(false), persistenceId(0), mgmtExchange(0) +namespace { - if (parent != 0) +const std::string qpidMsgSequence("qpid.msg_sequence"); +const std::string qpidSequenceCounter("qpid.sequence_counter"); +const std::string qpidIVE("qpid.ive"); +const std::string qpidFedOp("qpid.fed.op"); +const std::string qpidFedTags("qpid.fed.tags"); +const std::string qpidFedOrigin("qpid.fed.origin"); + +const std::string fedOpBind("B"); +const std::string fedOpUnbind("U"); +const std::string fedOpReorigin("R"); +const std::string fedOpHello("H"); + +const std::string QPID_MANAGEMENT("qpid.management"); +} + + +Exchange::PreRoute::PreRoute(Deliverable& msg, Exchange* _p):parent(_p) { + if (parent){ + if (parent->sequence || parent->ive) parent->sequenceLock.lock(); + + if (parent->sequence){ + parent->sequenceNo++; + msg.getMessage().getProperties<MessageProperties>()->getApplicationHeaders().setInt64(qpidMsgSequence,parent->sequenceNo); + } + if (parent->ive) { + parent->lastMsg = &( msg.getMessage()); + } + } +} + +Exchange::PreRoute::~PreRoute(){ + if (parent && (parent->sequence || parent->ive)){ + parent->sequenceLock.unlock(); + } +} + +void Exchange::doRoute(Deliverable& msg, ConstBindingList b) +{ + int count = 0; + + if (b.get()) { + // Block the content release if the message is transient AND there is more than one binding + if (!msg.getMessage().isPersistent() && b->size() > 1) + msg.getMessage().blockContentRelease(); + + for(std::vector<Binding::shared_ptr>::const_iterator i = b->begin(); i != b->end(); i++, count++) { + msg.deliverTo((*i)->queue); + if ((*i)->mgmtBinding != 0) + (*i)->mgmtBinding->inc_msgMatched(); + } + } + + if (mgmtExchange != 0) + { + mgmtExchange->inc_msgReceives (); + mgmtExchange->inc_byteReceives (msg.contentSize ()); + if (count == 0) + { + //QPID_LOG(warning, "Exchange " << getName() << " could not route message; no matching binding found"); + mgmtExchange->inc_msgDrops (); + mgmtExchange->inc_byteDrops (msg.contentSize ()); + } + else + { + mgmtExchange->inc_msgRoutes (count); + mgmtExchange->inc_byteRoutes (count * msg.contentSize ()); + } + } +} + +void Exchange::routeIVE(){ + if (ive && lastMsg.get()){ + DeliverableMessage dmsg(lastMsg); + route(dmsg, lastMsg->getRoutingKey(), lastMsg->getApplicationHeaders()); + } +} + + +Exchange::Exchange (const string& _name, Manageable* parent, Broker* b) : + name(_name), durable(false), persistenceId(0), sequence(false), + sequenceNo(0), ive(false), mgmtExchange(0), broker(b) +{ + if (parent != 0 && broker != 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - mgmtExchange = new management::Exchange (agent, this, parent, _name, durable); + mgmtExchange = new _qmf::Exchange (agent, this, parent, _name); + mgmtExchange->set_durable(durable); + mgmtExchange->set_autoDelete(false); agent->addObject (mgmtExchange); } } } Exchange::Exchange(const string& _name, bool _durable, const qpid::framing::FieldTable& _args, - Manageable* parent) - : name(_name), durable(_durable), args(_args), alternateUsers(0), persistenceId(0), mgmtExchange(0) + Manageable* parent, Broker* b) + : name(_name), durable(_durable), alternateUsers(0), persistenceId(0), + args(_args), sequence(false), sequenceNo(0), ive(false), mgmtExchange(0), broker(b) { - if (parent != 0) + if (parent != 0 && broker != 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - mgmtExchange = new management::Exchange (agent, this, parent, _name, durable); + mgmtExchange = new _qmf::Exchange (agent, this, parent, _name); + mgmtExchange->set_durable(durable); + mgmtExchange->set_autoDelete(false); + mgmtExchange->set_arguments(args); if (!durable) { - if (name == "") - agent->addObject (mgmtExchange, 4, 1); // Special default exchange ID - else if (name == "qpid.management") - agent->addObject (mgmtExchange, 5, 1); // Special management exchange ID - else - agent->addObject (mgmtExchange); + if (name.empty()) { + agent->addObject (mgmtExchange, 0x1000000000000004LL); // Special default exchange ID + } else if (name == QPID_MANAGEMENT) { + agent->addObject (mgmtExchange, 0x1000000000000005LL); // Special management exchange ID + } else { + agent->addObject (mgmtExchange, agent->allocateId(this)); + } } } } + + sequence = _args.get(qpidMsgSequence); + if (sequence) { + QPID_LOG(debug, "Configured exchange " << _name << " with Msg sequencing"); + args.setInt64(std::string(qpidSequenceCounter), sequenceNo); + } + + ive = _args.get(qpidIVE); + if (ive) QPID_LOG(debug, "Configured exchange " << _name << " with Initial Value"); } Exchange::~Exchange () @@ -73,12 +178,23 @@ Exchange::~Exchange () mgmtExchange->resourceDestroy (); } +void Exchange::setAlternate(Exchange::shared_ptr _alternate) +{ + alternate = _alternate; + if (mgmtExchange != 0) { + if (alternate.get() != 0) + mgmtExchange->set_altExchange(alternate->GetManagementObject()->getObjectId()); + else + mgmtExchange->clr_altExchange(); + } +} + void Exchange::setPersistenceId(uint64_t id) const { if (mgmtExchange != 0 && persistenceId == 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); - agent->addObject (mgmtExchange, id, 2); + ManagementAgent* agent = broker->getManagementAgent(); + agent->addObject (mgmtExchange, 0x2000000000000000LL + id); } persistenceId = id; } @@ -87,30 +203,58 @@ Exchange::shared_ptr Exchange::decode(ExchangeRegistry& exchanges, Buffer& buffe { string name; string type; + string altName; FieldTable args; - + buffer.getShortString(name); bool durable(buffer.getOctet()); buffer.getShortString(type); buffer.get(args); + // For backwards compatibility on restoring exchanges from before the alt-exchange update, perform check + if (buffer.available()) + buffer.getShortString(altName); - return exchanges.declare(name, type, durable, args).first; + try { + Exchange::shared_ptr exch = exchanges.declare(name, type, durable, args).first; + exch->sequenceNo = args.getAsInt64(qpidSequenceCounter); + exch->alternateName.assign(altName); + return exch; + } catch (const UnknownExchangeTypeException&) { + QPID_LOG(warning, "Could not create exchange " << name << "; type " << type << " is not recognised"); + return Exchange::shared_ptr(); + } } -void Exchange::encode(Buffer& buffer) const +void Exchange::encode(Buffer& buffer) const { buffer.putShortString(name); buffer.putOctet(durable); buffer.putShortString(getType()); + if (args.isSet(qpidSequenceCounter)) + args.setInt64(std::string(qpidSequenceCounter),sequenceNo); buffer.put(args); + buffer.putShortString(alternate.get() ? alternate->getName() : string("")); } -uint32_t Exchange::encodedSize() const -{ +uint32_t Exchange::encodedSize() const +{ return name.size() + 1/*short string size*/ + 1 /*durable*/ + getType().size() + 1/*short string size*/ - + args.size(); + + (alternate.get() ? alternate->getName().size() : 0) + 1/*short string size*/ + + args.encodedSize(); +} + +void Exchange::recoveryComplete(ExchangeRegistry& exchanges) +{ + if (!alternateName.empty()) { + try { + Exchange::shared_ptr ae = exchanges.get(alternateName); + setAlternate(ae); + } catch (const NotFoundException&) { + QPID_LOG(warning, "Could not set alternate exchange \"" << alternateName << "\": does not exist."); + } + } } ManagementObject* Exchange::GetManagementObject (void) const @@ -118,30 +262,85 @@ ManagementObject* Exchange::GetManagementObject (void) const return (ManagementObject*) mgmtExchange; } +void Exchange::registerDynamicBridge(DynamicBridge* db) +{ + if (!supportsDynamicBinding()) + throw Exception("Exchange type does not support dynamic binding"); + + { + Mutex::ScopedLock l(bridgeLock); + for (std::vector<DynamicBridge*>::iterator iter = bridgeVector.begin(); + iter != bridgeVector.end(); iter++) + (*iter)->sendReorigin(); + + bridgeVector.push_back(db); + } + + FieldTable args; + args.setString(qpidFedOp, fedOpReorigin); + bind(Queue::shared_ptr(), string(), &args); +} + +void Exchange::removeDynamicBridge(DynamicBridge* db) +{ + Mutex::ScopedLock l(bridgeLock); + for (std::vector<DynamicBridge*>::iterator iter = bridgeVector.begin(); + iter != bridgeVector.end(); iter++) + if (*iter == db) { + bridgeVector.erase(iter); + break; + } +} + +void Exchange::handleHelloRequest() +{ +} + +void Exchange::propagateFedOp(const string& routingKey, const string& tags, const string& op, const string& origin) +{ + Mutex::ScopedLock l(bridgeLock); + string myOp(op.empty() ? fedOpBind : op); + + for (std::vector<DynamicBridge*>::iterator iter = bridgeVector.begin(); + iter != bridgeVector.end(); iter++) + (*iter)->propagateBinding(routingKey, tags, op, origin); +} + Exchange::Binding::Binding(const string& _key, Queue::shared_ptr _queue, Exchange* parent, - FieldTable _args) + FieldTable _args, const string& origin) : queue(_queue), key(_key), args(_args), mgmtBinding(0) { if (parent != 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); - if (agent != 0) - { - ManagementObject* mo = queue->GetManagementObject(); - if (mo != 0) - { - uint64_t queueId = mo->getObjectId(); - mgmtBinding = new management::Binding (agent, this, (Manageable*) parent, queueId, key, args); - agent->addObject (mgmtBinding); - } + Broker* broker = parent->getBroker(); + if (broker != 0) { + ManagementAgent* agent = broker->getManagementAgent(); + if (agent != 0) + { + ManagementObject* mo = queue->GetManagementObject(); + if (mo != 0) + { + management::ObjectId queueId = mo->getObjectId(); + mgmtBinding = new _qmf::Binding + (agent, this, (Manageable*) parent, queueId, key, args); + if (!origin.empty()) + mgmtBinding->set_origin(origin); + agent->addObject (mgmtBinding, agent->allocateId(this)); + static_cast<_qmf::Queue*>(mo)->inc_bindingCount(); + } + } } } } Exchange::Binding::~Binding () { - if (mgmtBinding != 0) + if (mgmtBinding != 0) { + ManagementObject* mo = queue->GetManagementObject(); + if (mo != 0) + static_cast<_qmf::Queue*>(mo)->dec_bindingCount(); mgmtBinding->resourceDestroy (); + } } ManagementObject* Exchange::Binding::GetManagementObject () const @@ -149,7 +348,13 @@ ManagementObject* Exchange::Binding::GetManagementObject () const return (ManagementObject*) mgmtBinding; } -Manageable::status_t Exchange::Binding::ManagementMethod (uint32_t, Args&) +Exchange::MatchQueue::MatchQueue(Queue::shared_ptr q) : queue(q) {} + +bool Exchange::MatchQueue::operator()(Exchange::Binding::shared_ptr b) { - return Manageable::STATUS_UNKNOWN_METHOD; + return b->queue == queue; +} + +void Exchange::setProperties(const boost::intrusive_ptr<Message>& msg) { + msg->getProperties<DeliveryProperties>()->setExchange(getName()); } diff --git a/cpp/src/qpid/broker/Exchange.h b/cpp/src/qpid/broker/Exchange.h index f4ac4373e4..d630f7ae24 100644 --- a/cpp/src/qpid/broker/Exchange.h +++ b/cpp/src/qpid/broker/Exchange.h @@ -23,87 +23,171 @@ */ #include <boost/shared_ptr.hpp> -#include "Deliverable.h" -#include "Queue.h" -#include "MessageStore.h" -#include "PersistableExchange.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/PersistableExchange.h" #include "qpid/framing/FieldTable.h" +#include "qpid/sys/Mutex.h" #include "qpid/management/Manageable.h" -#include "qpid/management/Exchange.h" -#include "qpid/management/Binding.h" +#include "qmf/org/apache/qpid/broker/Exchange.h" +#include "qmf/org/apache/qpid/broker/Binding.h" namespace qpid { - namespace broker { - using std::string; - class ExchangeRegistry; - - class Exchange : public PersistableExchange, public management::Manageable { - private: - const string name; - const bool durable; - qpid::framing::FieldTable args; - boost::shared_ptr<Exchange> alternate; - uint32_t alternateUsers; - mutable uint64_t persistenceId; - - protected: - struct Binding : public management::Manageable { - typedef boost::shared_ptr<Binding> shared_ptr; - typedef std::vector<Binding::shared_ptr> vector; - - Queue::shared_ptr queue; - const std::string key; - const framing::FieldTable args; - management::Binding* mgmtBinding; - - Binding(const std::string& key, Queue::shared_ptr queue, Exchange* parent = 0, - framing::FieldTable args = framing::FieldTable ()); - ~Binding (); - management::ManagementObject* GetManagementObject () const; - management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args); - }; - - management::Exchange* mgmtExchange; - - public: - typedef boost::shared_ptr<Exchange> shared_ptr; - - explicit Exchange(const string& name, management::Manageable* parent = 0); - Exchange(const string& _name, bool _durable, const qpid::framing::FieldTable& _args, - management::Manageable* parent = 0); - virtual ~Exchange(); - - const string& getName() const { return name; } - bool isDurable() { return durable; } - qpid::framing::FieldTable& getArgs() { return args; } - - Exchange::shared_ptr getAlternate() { return alternate; } - void setAlternate(Exchange::shared_ptr _alternate) { alternate = _alternate; } - void incAlternateUsers() { alternateUsers++; } - void decAlternateUsers() { alternateUsers--; } - bool inUseAsAlternate() { return alternateUsers > 0; } - - virtual string getType() const = 0; - virtual bool bind(Queue::shared_ptr queue, const string& routingKey, const qpid::framing::FieldTable* args) = 0; - virtual bool unbind(Queue::shared_ptr queue, const string& routingKey, const qpid::framing::FieldTable* args) = 0; - virtual bool isBound(Queue::shared_ptr queue, const string* const routingKey, const qpid::framing::FieldTable* const args) = 0; - virtual void route(Deliverable& msg, const string& routingKey, const qpid::framing::FieldTable* args) = 0; - - //PersistableExchange: - void setPersistenceId(uint64_t id) const; - uint64_t getPersistenceId() const { return persistenceId; } - uint32_t encodedSize() const; - void encode(framing::Buffer& buffer) const; - - static Exchange::shared_ptr decode(ExchangeRegistry& exchanges, framing::Buffer& buffer); - - // Manageable entry points - management::ManagementObject* GetManagementObject (void) const; - management::Manageable::status_t - ManagementMethod (uint32_t, management::Args&) { return management::Manageable::STATUS_UNKNOWN_METHOD; } - }; - } -} - +namespace broker { + +class ExchangeRegistry; + +class Exchange : public PersistableExchange, public management::Manageable { +public: + struct Binding : public management::Manageable { + typedef boost::shared_ptr<Binding> shared_ptr; + typedef std::vector<Binding::shared_ptr> vector; + + Queue::shared_ptr queue; + const std::string key; + const framing::FieldTable args; + qmf::org::apache::qpid::broker::Binding* mgmtBinding; + + Binding(const std::string& key, Queue::shared_ptr queue, Exchange* parent = 0, + framing::FieldTable args = framing::FieldTable(), const std::string& origin = std::string()); + ~Binding(); + management::ManagementObject* GetManagementObject() const; + }; + +private: + const std::string name; + const bool durable; + std::string alternateName; + boost::shared_ptr<Exchange> alternate; + uint32_t alternateUsers; + mutable uint64_t persistenceId; + +protected: + mutable qpid::framing::FieldTable args; + bool sequence; + mutable qpid::sys::Mutex sequenceLock; + int64_t sequenceNo; + bool ive; + boost::intrusive_ptr<Message> lastMsg; + + class PreRoute{ + public: + PreRoute(Deliverable& msg, Exchange* _p); + ~PreRoute(); + private: + Exchange* parent; + }; + + typedef boost::shared_ptr<const std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> > > ConstBindingList; + typedef boost::shared_ptr< std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> > > BindingList; + void doRoute(Deliverable& msg, ConstBindingList b); + void routeIVE(); + + + struct MatchQueue { + const Queue::shared_ptr queue; + MatchQueue(Queue::shared_ptr q); + bool operator()(Exchange::Binding::shared_ptr b); + }; + + class FedBinding { + uint32_t localBindings; + std::set<std::string> originSet; + public: + FedBinding() : localBindings(0) {} + bool hasLocal() const { return localBindings != 0; } + bool addOrigin(const std::string& origin) { + if (origin.empty()) { + localBindings++; + return localBindings == 1; + } + originSet.insert(origin); + return true; + } + bool delOrigin(const std::string& origin) { + originSet.erase(origin); + return true; + } + bool delOrigin() { + if (localBindings > 0) + localBindings--; + return localBindings == 0; + } + uint32_t count() { + return localBindings + originSet.size(); + } + }; + + qmf::org::apache::qpid::broker::Exchange* mgmtExchange; + +public: + typedef boost::shared_ptr<Exchange> shared_ptr; + + QPID_BROKER_EXTERN explicit Exchange(const std::string& name, management::Manageable* parent = 0, + Broker* broker = 0); + QPID_BROKER_EXTERN Exchange(const std::string& _name, bool _durable, const qpid::framing::FieldTable& _args, + management::Manageable* parent = 0, Broker* broker = 0); + QPID_BROKER_EXTERN virtual ~Exchange(); + + const std::string& getName() const { return name; } + bool isDurable() { return durable; } + qpid::framing::FieldTable& getArgs() { return args; } + + Exchange::shared_ptr getAlternate() { return alternate; } + void setAlternate(Exchange::shared_ptr _alternate); + void incAlternateUsers() { alternateUsers++; } + void decAlternateUsers() { alternateUsers--; } + bool inUseAsAlternate() { return alternateUsers > 0; } + + virtual std::string getType() const = 0; + virtual bool bind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args) = 0; + virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args) = 0; + virtual bool isBound(Queue::shared_ptr queue, const std::string* const routingKey, const qpid::framing::FieldTable* const args) = 0; + QPID_BROKER_EXTERN virtual void setProperties(const boost::intrusive_ptr<Message>&); + virtual void route(Deliverable& msg, const std::string& routingKey, const qpid::framing::FieldTable* args) = 0; + + //PersistableExchange: + QPID_BROKER_EXTERN void setPersistenceId(uint64_t id) const; + uint64_t getPersistenceId() const { return persistenceId; } + QPID_BROKER_EXTERN uint32_t encodedSize() const; + QPID_BROKER_EXTERN virtual void encode(framing::Buffer& buffer) const; + + static QPID_BROKER_EXTERN Exchange::shared_ptr decode(ExchangeRegistry& exchanges, framing::Buffer& buffer); + + // Manageable entry points + QPID_BROKER_EXTERN management::ManagementObject* GetManagementObject(void) const; + + // Federation hooks + class DynamicBridge { + public: + virtual ~DynamicBridge() {} + virtual void propagateBinding(const std::string& key, const std::string& tagList, const std::string& op, const std::string& origin) = 0; + virtual void sendReorigin() = 0; + virtual bool containsLocalTag(const std::string& tagList) const = 0; + virtual const std::string& getLocalTag() const = 0; + }; + + void registerDynamicBridge(DynamicBridge* db); + void removeDynamicBridge(DynamicBridge* db); + virtual bool supportsDynamicBinding() { return false; } + Broker* getBroker() const { return broker; } + /** + * Notify exchange that recovery has completed. + */ + void recoveryComplete(ExchangeRegistry& exchanges); + +protected: + qpid::sys::Mutex bridgeLock; + std::vector<DynamicBridge*> bridgeVector; + Broker* broker; + + QPID_BROKER_EXTERN virtual void handleHelloRequest(); + void propagateFedOp(const std::string& routingKey, const std::string& tags, + const std::string& op, const std::string& origin); +}; + +}} #endif /*!_broker_Exchange.cpp_h*/ diff --git a/cpp/src/qpid/broker/ExchangeRegistry.cpp b/cpp/src/qpid/broker/ExchangeRegistry.cpp index 45eb308680..951cdbd395 100644 --- a/cpp/src/qpid/broker/ExchangeRegistry.cpp +++ b/cpp/src/qpid/broker/ExchangeRegistry.cpp @@ -19,15 +19,11 @@ * */ -#include "config.h" -#include "ExchangeRegistry.h" -#include "DirectExchange.h" -#include "FanOutExchange.h" -#include "HeadersExchange.h" -#include "TopicExchange.h" -#ifdef HAVE_XML -#include "XmlExchange.h" -#endif +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/DirectExchange.h" +#include "qpid/broker/FanOutExchange.h" +#include "qpid/broker/HeadersExchange.h" +#include "qpid/broker/TopicExchange.h" #include "qpid/management/ManagementExchange.h" #include "qpid/framing/reply_exceptions.h" @@ -36,42 +32,35 @@ using namespace qpid::sys; using std::pair; using qpid::framing::FieldTable; -pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, const string& type) - throw(UnknownExchangeTypeException){ +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) - throw(UnknownExchangeTypeException){ + bool durable, const FieldTable& args){ RWlock::ScopedWlock locker(lock); ExchangeMap::iterator i = exchanges.find(name); if (i == exchanges.end()) { Exchange::shared_ptr exchange; if(type == TopicExchange::typeName){ - exchange = Exchange::shared_ptr(new TopicExchange(name, durable, args, parent)); + exchange = Exchange::shared_ptr(new TopicExchange(name, durable, args, parent, broker)); }else if(type == DirectExchange::typeName){ - exchange = Exchange::shared_ptr(new DirectExchange(name, durable, args, parent)); + exchange = Exchange::shared_ptr(new DirectExchange(name, durable, args, parent, broker)); }else if(type == FanOutExchange::typeName){ - exchange = Exchange::shared_ptr(new FanOutExchange(name, durable, args, parent)); + exchange = Exchange::shared_ptr(new FanOutExchange(name, durable, args, parent, broker)); }else if (type == HeadersExchange::typeName) { - exchange = Exchange::shared_ptr(new HeadersExchange(name, durable, args, parent)); + exchange = Exchange::shared_ptr(new HeadersExchange(name, durable, args, parent, broker)); }else if (type == ManagementExchange::typeName) { - exchange = Exchange::shared_ptr(new ManagementExchange(name, durable, args, parent)); + exchange = Exchange::shared_ptr(new ManagementExchange(name, durable, args, parent, broker)); } -#ifdef HAVE_XML - else if (type == XmlExchange::typeName) { - exchange = Exchange::shared_ptr(new XmlExchange(name, durable, args, parent)); - } -#endif else{ FunctionMap::iterator i = factory.find(type); if (i == factory.end()) { throw UnknownExchangeTypeException(); } else { - exchange = i->second(name, durable, args, parent); + exchange = i->second(name, durable, args, parent, broker); } } exchanges[name] = exchange; @@ -82,6 +71,11 @@ pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, c } void ExchangeRegistry::destroy(const string& name){ + 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 << "'")); RWlock::ScopedWlock locker(lock); ExchangeMap::iterator i = exchanges.find(name); if (i != exchanges.end()) { @@ -97,6 +91,10 @@ Exchange::shared_ptr ExchangeRegistry::get(const string& name){ return i->second; } +bool ExchangeRegistry::registerExchange(const Exchange::shared_ptr& ex) { + return exchanges.insert(ExchangeMap::value_type(ex->getName(), ex)).second; +} + void ExchangeRegistry::registerType(const std::string& type, FactoryFunction f) { factory[type] = f; diff --git a/cpp/src/qpid/broker/ExchangeRegistry.h b/cpp/src/qpid/broker/ExchangeRegistry.h index 7573e3e415..2b75a8f3cf 100644 --- a/cpp/src/qpid/broker/ExchangeRegistry.h +++ b/cpp/src/qpid/broker/ExchangeRegistry.h @@ -22,51 +22,72 @@ * */ -#include <map> -#include <boost/function.hpp> -#include "Exchange.h" -#include "MessageStore.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/MessageStore.h" #include "qpid/framing/FieldTable.h" #include "qpid/sys/Monitor.h" #include "qpid/management/Manageable.h" +#include <boost/function.hpp> +#include <boost/bind.hpp> + +#include <algorithm> +#include <map> + namespace qpid { namespace broker { - struct UnknownExchangeTypeException{}; - - class ExchangeRegistry{ - public: - typedef boost::function4<Exchange::shared_ptr, const std::string&, - bool, const qpid::framing::FieldTable&, qpid::management::Manageable*> FactoryFunction; - - ExchangeRegistry () : parent(0) {} - std::pair<Exchange::shared_ptr, bool> declare(const std::string& name, const std::string& type) - throw(UnknownExchangeTypeException); - std::pair<Exchange::shared_ptr, bool> declare(const std::string& name, const std::string& type, - bool durable, const qpid::framing::FieldTable& args = framing::FieldTable()) - throw(UnknownExchangeTypeException); - void destroy(const std::string& name); - Exchange::shared_ptr get(const std::string& name); - Exchange::shared_ptr getDefault(); - - /** - * Register the manageable parent for declared exchanges - */ - void setParent (management::Manageable* _parent) { parent = _parent; } - - void registerType(const std::string& type, FactoryFunction); - private: - typedef std::map<std::string, Exchange::shared_ptr> ExchangeMap; - typedef std::map<std::string, FactoryFunction > FunctionMap; - - ExchangeMap exchanges; - FunctionMap factory; - qpid::sys::RWlock lock; - management::Manageable* parent; - - }; -} -} + +struct UnknownExchangeTypeException{}; + +class ExchangeRegistry{ + public: + typedef boost::function5<Exchange::shared_ptr, const std::string&, + 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 Exchange::shared_ptr get(const std::string& name); + Exchange::shared_ptr getDefault(); + + /** + * Register the manageable parent for declared exchanges + */ + void setParent (management::Manageable* _parent) { parent = _parent; } + + /** Register an exchange instance. + *@return true if registered, false if exchange with same name is already registered. + */ + bool registerExchange(const Exchange::shared_ptr&); + + QPID_BROKER_EXTERN void registerType(const std::string& type, FactoryFunction); + + /** Call f for each exchange in the registry. */ + template <class F> void eachExchange(F f) const { + qpid::sys::RWlock::ScopedRlock l(lock); + for (ExchangeMap::const_iterator i = exchanges.begin(); i != exchanges.end(); ++i) + f(i->second); + } + + private: + typedef std::map<std::string, Exchange::shared_ptr> ExchangeMap; + typedef std::map<std::string, FactoryFunction > FunctionMap; + + ExchangeMap exchanges; + FunctionMap factory; + mutable qpid::sys::RWlock lock; + management::Manageable* parent; + Broker* broker; +}; + +}} // namespace qpid::broker #endif /*!_broker_ExchangeRegistry_h*/ diff --git a/cpp/src/qpid/client/AsyncSession.h b/cpp/src/qpid/broker/ExpiryPolicy.cpp index 150aabe191..64a12d918a 100644 --- a/cpp/src/qpid/client/AsyncSession.h +++ b/cpp/src/qpid/broker/ExpiryPolicy.cpp @@ -1,6 +1,3 @@ -#ifndef QPID_CLIENT_ASYNCSESSION_H -#define QPID_CLIENT_ASYNCSESSION_H - /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -21,18 +18,21 @@ * under the License. * */ -#include "qpid/client/AsyncSession_0_10.h" +#include "qpid/broker/ExpiryPolicy.h" +#include "qpid/broker/Message.h" +#include "qpid/sys/Time.h" namespace qpid { -namespace client { +namespace broker { -/** - * AsyncSession is an alias for Session_0_10 - * - * \ingroup clientapi - */ -typedef AsyncSession_0_10 AsyncSession; +ExpiryPolicy::~ExpiryPolicy() {} + +void ExpiryPolicy::willExpire(Message&) {} + +bool ExpiryPolicy::hasExpired(Message& m) { + return m.getExpiration() < sys::AbsTime::now(); +} -}} // namespace qpid::client +void ExpiryPolicy::forget(Message&) {} -#endif /*!QPID_CLIENT_ASYNCSESSION_H*/ +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/ExpiryPolicy.h b/cpp/src/qpid/broker/ExpiryPolicy.h new file mode 100644 index 0000000000..40e793bf2c --- /dev/null +++ b/cpp/src/qpid/broker/ExpiryPolicy.h @@ -0,0 +1,46 @@ +#ifndef QPID_BROKER_EXPIRYPOLICY_H +#define QPID_BROKER_EXPIRYPOLICY_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/RefCounted.h" +#include "qpid/broker/BrokerImportExport.h" + +namespace qpid { +namespace broker { + +class Message; + +/** + * Default expiry policy. + */ +class ExpiryPolicy : public RefCounted +{ + public: + QPID_BROKER_EXTERN virtual ~ExpiryPolicy(); + QPID_BROKER_EXTERN virtual void willExpire(Message&); + QPID_BROKER_EXTERN virtual bool hasExpired(Message&); + QPID_BROKER_EXTERN virtual void forget(Message&); +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_EXPIRYPOLICY_H*/ diff --git a/cpp/src/qpid/broker/FanOutExchange.cpp b/cpp/src/qpid/broker/FanOutExchange.cpp index 373e9ab1cc..6d840b50df 100644 --- a/cpp/src/qpid/broker/FanOutExchange.cpp +++ b/cpp/src/qpid/broker/FanOutExchange.cpp @@ -18,106 +18,102 @@ * under the License. * */ -#include "FanOutExchange.h" +#include "qpid/broker/FanOutExchange.h" #include <algorithm> using namespace qpid::broker; using namespace qpid::framing; using namespace qpid::sys; +namespace _qmf = qmf::org::apache::qpid::broker; -FanOutExchange::FanOutExchange(const std::string& _name, Manageable* _parent) : - Exchange(_name, _parent) +namespace +{ +const std::string qpidFedOp("qpid.fed.op"); +const std::string qpidFedTags("qpid.fed.tags"); +const std::string qpidFedOrigin("qpid.fed.origin"); + +const std::string fedOpBind("B"); +const std::string fedOpUnbind("U"); +const std::string fedOpReorigin("R"); +const std::string fedOpHello("H"); +} + +FanOutExchange::FanOutExchange(const std::string& _name, Manageable* _parent, Broker* b) : + Exchange(_name, _parent, b) { if (mgmtExchange != 0) mgmtExchange->set_type (typeName); } FanOutExchange::FanOutExchange(const std::string& _name, bool _durable, - const FieldTable& _args, Manageable* _parent) : - Exchange(_name, _durable, _args, _parent) + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b) { if (mgmtExchange != 0) mgmtExchange->set_type (typeName); } -bool FanOutExchange::bind(Queue::shared_ptr queue, const string& /*routingKey*/, const FieldTable* /*args*/){ - RWlock::ScopedWlock locker(lock); - std::vector<Binding::shared_ptr>::iterator i; - - // Add if not already present. - for (i = bindings.begin (); i != bindings.end(); i++) - if ((*i)->queue == queue) - break; - - if (i == bindings.end()) { - Binding::shared_ptr binding (new Binding ("", queue, this)); - bindings.push_back(binding); - if (mgmtExchange != 0) { - mgmtExchange->inc_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->inc_bindingCount(); +bool FanOutExchange::bind(Queue::shared_ptr queue, const string& /*key*/, const FieldTable* args) +{ + string fedOp(args ? args->getAsString(qpidFedOp) : fedOpBind); + string fedTags(args ? args->getAsString(qpidFedTags) : ""); + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); + bool propagate = false; + + if (args == 0 || fedOp.empty() || fedOp == fedOpBind) { + Binding::shared_ptr binding (new Binding ("", queue, this, FieldTable(), fedOrigin)); + if (bindings.add_unless(binding, MatchQueue(queue))) { + propagate = fedBinding.addOrigin(fedOrigin); + if (mgmtExchange != 0) { + mgmtExchange->inc_bindingCount(); + } + } else { + return false; + } + } else if (fedOp == fedOpUnbind) { + propagate = fedBinding.delOrigin(fedOrigin); + if (fedBinding.count() == 0) + unbind(queue, "", 0); + } else if (fedOp == fedOpReorigin) { + if (fedBinding.hasLocal()) { + propagateFedOp(string(), string(), fedOpBind, string()); } - return true; - } else { - return false; } -} -bool FanOutExchange::unbind(Queue::shared_ptr queue, const string& /*routingKey*/, const FieldTable* /*args*/){ - RWlock::ScopedWlock locker(lock); - std::vector<Binding::shared_ptr>::iterator i; + routeIVE(); + if (propagate) + propagateFedOp(string(), fedTags, fedOp, fedOrigin); + return true; +} - for (i = bindings.begin (); i != bindings.end(); i++) - if ((*i)->queue == queue) - break; +bool FanOutExchange::unbind(Queue::shared_ptr queue, const string& /*key*/, const FieldTable* /*args*/) +{ + bool propagate = false; - if (i != bindings.end()) { - bindings.erase(i); + if (bindings.remove_if(MatchQueue(queue))) { + propagate = fedBinding.delOrigin(); if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->dec_bindingCount(); } - return true; } else { return false; } -} - -void FanOutExchange::route(Deliverable& msg, const string& /*routingKey*/, const FieldTable* /*args*/){ - RWlock::ScopedRlock locker(lock); - uint32_t count(0); - - for(std::vector<Binding::shared_ptr>::iterator i = bindings.begin(); i != bindings.end(); ++i, count++){ - msg.deliverTo((*i)->queue); - if ((*i)->mgmtBinding != 0) - (*i)->mgmtBinding->inc_msgMatched (); - } - if (mgmtExchange != 0) - { - mgmtExchange->inc_msgReceives (); - mgmtExchange->inc_byteReceives (msg.contentSize ()); - if (count == 0) - { - mgmtExchange->inc_msgDrops (); - mgmtExchange->inc_byteDrops (msg.contentSize ()); - } - else - { - mgmtExchange->inc_msgRoutes (count); - mgmtExchange->inc_byteRoutes (count * msg.contentSize ()); - } - } + if (propagate) + propagateFedOp(string(), string(), fedOpUnbind, string()); + return true; } +void FanOutExchange::route(Deliverable& msg, const string& /*routingKey*/, const FieldTable* /*args*/) +{ + PreRoute pr(msg, this); + doRoute(msg, bindings.snapshot()); +} + bool FanOutExchange::isBound(Queue::shared_ptr queue, const string* const, const FieldTable* const) { - std::vector<Binding::shared_ptr>::iterator i; - - for (i = bindings.begin (); i != bindings.end(); i++) - if ((*i)->queue == queue) - break; - - return i != bindings.end(); + BindingsArray::ConstPtr ptr = bindings.snapshot(); + return ptr && std::find_if(ptr->begin(), ptr->end(), MatchQueue(queue)) != ptr->end(); } diff --git a/cpp/src/qpid/broker/FanOutExchange.h b/cpp/src/qpid/broker/FanOutExchange.h index 4bc92f6b28..7bcf6367cf 100644 --- a/cpp/src/qpid/broker/FanOutExchange.h +++ b/cpp/src/qpid/broker/FanOutExchange.h @@ -23,37 +23,47 @@ #include <map> #include <vector> -#include "Exchange.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" #include "qpid/framing/FieldTable.h" -#include "qpid/sys/Monitor.h" -#include "Queue.h" +#include "qpid/sys/CopyOnWriteArray.h" +#include "qpid/broker/Queue.h" namespace qpid { namespace broker { class FanOutExchange : public virtual Exchange { - std::vector<Binding::shared_ptr> bindings; - qpid::sys::RWlock lock; - + typedef qpid::sys::CopyOnWriteArray<Binding::shared_ptr> BindingsArray; + BindingsArray bindings; + FedBinding fedBinding; public: static const std::string typeName; - FanOutExchange(const std::string& name, management::Manageable* parent = 0); - FanOutExchange(const string& _name, bool _durable, - const qpid::framing::FieldTable& _args, - management::Manageable* parent = 0); + QPID_BROKER_EXTERN FanOutExchange(const std::string& name, + management::Manageable* parent = 0, Broker* broker = 0); + QPID_BROKER_EXTERN FanOutExchange(const string& _name, + bool _durable, + const qpid::framing::FieldTable& _args, + management::Manageable* parent = 0, Broker* broker = 0); virtual std::string getType() const { return typeName; } - virtual bool bind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue, + const std::string& routingKey, + const qpid::framing::FieldTable* args); virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); - virtual void route(Deliverable& msg, const std::string& routingKey, const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual void route(Deliverable& msg, + const std::string& routingKey, + const qpid::framing::FieldTable* args); - virtual bool isBound(Queue::shared_ptr queue, const string* const routingKey, const qpid::framing::FieldTable* const args); + QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue, + const string* const routingKey, + const qpid::framing::FieldTable* const args); - virtual ~FanOutExchange(); + QPID_BROKER_EXTERN virtual ~FanOutExchange(); + virtual bool supportsDynamicBinding() { return true; } }; } diff --git a/cpp/src/qpid/broker/HandlerImpl.h b/cpp/src/qpid/broker/HandlerImpl.h index 4c51e2a826..aae636e818 100644 --- a/cpp/src/qpid/broker/HandlerImpl.h +++ b/cpp/src/qpid/broker/HandlerImpl.h @@ -19,9 +19,9 @@ * */ -#include "SemanticState.h" -#include "SessionContext.h" -#include "ConnectionState.h" +#include "qpid/broker/SemanticState.h" +#include "qpid/broker/SessionContext.h" +#include "qpid/broker/ConnectionState.h" namespace qpid { namespace broker { diff --git a/cpp/src/qpid/broker/HeadersExchange.cpp b/cpp/src/qpid/broker/HeadersExchange.cpp index 54519a7bf6..38cc0e4050 100644 --- a/cpp/src/qpid/broker/HeadersExchange.cpp +++ b/cpp/src/qpid/broker/HeadersExchange.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "HeadersExchange.h" +#include "qpid/broker/HeadersExchange.h" #include "qpid/framing/FieldValue.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/log/Statement.h" @@ -28,6 +28,7 @@ using namespace qpid::broker; using namespace qpid::framing; using namespace qpid::sys; +namespace _qmf = qmf::org::apache::qpid::broker; // TODO aconway 2006-09-20: More efficient matching algorithm. // The current search algorithm really sucks. @@ -42,16 +43,16 @@ namespace { const std::string empty; } -HeadersExchange::HeadersExchange(const string& _name, Manageable* _parent) : - Exchange(_name, _parent) +HeadersExchange::HeadersExchange(const string& _name, Manageable* _parent, Broker* b) : + Exchange(_name, _parent, b) { if (mgmtExchange != 0) mgmtExchange->set_type (typeName); } HeadersExchange::HeadersExchange(const std::string& _name, bool _durable, - const FieldTable& _args, Manageable* _parent) : - Exchange(_name, _durable, _args, _parent) + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b) { if (mgmtExchange != 0) mgmtExchange->set_type (typeName); @@ -73,50 +74,26 @@ std::string HeadersExchange::getMatch(const FieldTable* args) } bool HeadersExchange::bind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable* args){ - RWlock::ScopedWlock locker(lock); std::string what = getMatch(args); if (what != all && what != any) throw InternalErrorException(QPID_MSG("Invalid x-match value binding to headers exchange.")); - Bindings::iterator i; - - for (i = bindings.begin(); i != bindings.end(); i++) - if (i->first == *args && i->second->queue == queue) - break; - - if (i == bindings.end()) { - Binding::shared_ptr binding (new Binding (bindingKey, queue, this, *args)); - HeaderMap headerMap(*args, binding); - - bindings.push_back(headerMap); + Binding::shared_ptr binding (new Binding (bindingKey, queue, this, *args)); + if (bindings.add_unless(binding, MatchArgs(queue, args))) { if (mgmtExchange != 0) { mgmtExchange->inc_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->inc_bindingCount(); } + routeIVE(); return true; } else { return false; } } -bool HeadersExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable* args){ - RWlock::ScopedWlock locker(lock); - Bindings::iterator i; - for (i = bindings.begin(); i != bindings.end(); i++) { - if (bindingKey.empty() && args) { - if (i->first == *args && i->second->queue == queue) - break; - } else { - if (i->second->key == bindingKey && i->second->queue == queue) - break; - } - } - - if (i != bindings.end()) { - bindings.erase(i); +bool HeadersExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable*){ + if (bindings.remove_if(MatchKey(queue, bindingKey))) { if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->dec_bindingCount(); } return true; } else { @@ -125,41 +102,43 @@ bool HeadersExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, } -void HeadersExchange::route(Deliverable& msg, const string& /*routingKey*/, const FieldTable* args){ - if (!args) return;//can't match if there were no headers passed in - - RWlock::ScopedRlock locker(lock); - uint32_t count(0); - - for (Bindings::iterator i = bindings.begin(); i != bindings.end(); ++i, count++) { - if (match(i->first, *args)) msg.deliverTo(i->second->queue); - if (i->second->mgmtBinding != 0) - i->second->mgmtBinding->inc_msgMatched (); +void HeadersExchange::route(Deliverable& msg, const string& /*routingKey*/, const FieldTable* args) +{ + if (!args) { + //can't match if there were no headers passed in + if (mgmtExchange != 0) { + mgmtExchange->inc_msgReceives(); + mgmtExchange->inc_byteReceives(msg.contentSize()); + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + return; } - if (mgmtExchange != 0) + PreRoute pr(msg, this); + + ConstBindingList p = bindings.snapshot(); + BindingList b(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >); + if (p.get()) { - mgmtExchange->inc_msgReceives (); - mgmtExchange->inc_byteReceives (msg.contentSize ()); - if (count == 0) - { - mgmtExchange->inc_msgDrops (); - mgmtExchange->inc_byteDrops (msg.contentSize ()); - } - else - { - mgmtExchange->inc_msgRoutes (count); - mgmtExchange->inc_byteRoutes (count * msg.contentSize ()); + for (std::vector<Binding::shared_ptr>::const_iterator i = p->begin(); i != p->end(); ++i) { + if (match((*i)->args, *args)) { + b->push_back(*i); + } } } + doRoute(msg, b); } bool HeadersExchange::isBound(Queue::shared_ptr queue, const string* const, const FieldTable* const args) { - for (Bindings::iterator i = bindings.begin(); i != bindings.end(); ++i) { - if ( (!args || equal(i->first, *args)) && (!queue || i->second->queue == queue)) { - return true; + Bindings::ConstPtr p = bindings.snapshot(); + if (p.get()){ + for (std::vector<Binding::shared_ptr>::const_iterator i = p->begin(); i != p->end(); ++i) { + if ( (!args || equal((*i)->args, *args)) && (!queue || (*i)->queue == queue)) { + return true; + } } } return false; @@ -227,5 +206,15 @@ bool HeadersExchange::equal(const FieldTable& a, const FieldTable& b) { return true; } +HeadersExchange::MatchArgs::MatchArgs(Queue::shared_ptr q, const qpid::framing::FieldTable* a) : queue(q), args(a) {} +bool HeadersExchange::MatchArgs::operator()(Exchange::Binding::shared_ptr b) +{ + return b->queue == queue && b->args == *args; +} +HeadersExchange::MatchKey::MatchKey(Queue::shared_ptr q, const std::string& k) : queue(q), key(k) {} +bool HeadersExchange::MatchKey::operator()(Exchange::Binding::shared_ptr b) +{ + return b->queue == queue && b->key == key; +} diff --git a/cpp/src/qpid/broker/HeadersExchange.h b/cpp/src/qpid/broker/HeadersExchange.h index 6e101e193a..6425b44251 100644 --- a/cpp/src/qpid/broker/HeadersExchange.h +++ b/cpp/src/qpid/broker/HeadersExchange.h @@ -22,10 +22,12 @@ #define _HeadersExchange_ #include <vector> -#include "Exchange.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" #include "qpid/framing/FieldTable.h" -#include "qpid/sys/Monitor.h" -#include "Queue.h" +#include "qpid/sys/CopyOnWriteArray.h" +#include "qpid/sys/Mutex.h" +#include "qpid/broker/Queue.h" namespace qpid { namespace broker { @@ -33,34 +35,57 @@ namespace broker { class HeadersExchange : public virtual Exchange { typedef std::pair<qpid::framing::FieldTable, Binding::shared_ptr> HeaderMap; - typedef std::vector<HeaderMap> Bindings; + typedef qpid::sys::CopyOnWriteArray<Binding::shared_ptr> Bindings; + + struct MatchArgs + { + const Queue::shared_ptr queue; + const qpid::framing::FieldTable* args; + MatchArgs(Queue::shared_ptr q, const qpid::framing::FieldTable* a); + bool operator()(Exchange::Binding::shared_ptr b); + }; + struct MatchKey + { + const Queue::shared_ptr queue; + const std::string& key; + MatchKey(Queue::shared_ptr q, const std::string& k); + bool operator()(Exchange::Binding::shared_ptr b); + }; Bindings bindings; - qpid::sys::RWlock lock; + qpid::sys::Mutex lock; static std::string getMatch(const framing::FieldTable* args); public: static const std::string typeName; - HeadersExchange(const string& name, management::Manageable* parent = 0); - HeadersExchange(const string& _name, bool _durable, - const qpid::framing::FieldTable& _args, - management::Manageable* parent = 0); + QPID_BROKER_EXTERN HeadersExchange(const string& name, + management::Manageable* parent = 0, Broker* broker = 0); + QPID_BROKER_EXTERN HeadersExchange(const string& _name, + bool _durable, + const qpid::framing::FieldTable& _args, + management::Manageable* parent = 0, Broker* broker = 0); virtual std::string getType() const { return typeName; } - virtual bool bind(Queue::shared_ptr queue, const string& routingKey, const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue, + const string& routingKey, + const qpid::framing::FieldTable* args); virtual bool unbind(Queue::shared_ptr queue, const string& routingKey, const qpid::framing::FieldTable* args); - virtual void route(Deliverable& msg, const string& routingKey, const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual void route(Deliverable& msg, + const string& routingKey, + const qpid::framing::FieldTable* args); - virtual bool isBound(Queue::shared_ptr queue, const string* const routingKey, const qpid::framing::FieldTable* const args); + QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue, + const string* const routingKey, + const qpid::framing::FieldTable* const args); - virtual ~HeadersExchange(); + QPID_BROKER_EXTERN virtual ~HeadersExchange(); - static bool match(const qpid::framing::FieldTable& bindArgs, const qpid::framing::FieldTable& msgArgs); + static QPID_BROKER_EXTERN bool match(const qpid::framing::FieldTable& bindArgs, const qpid::framing::FieldTable& msgArgs); static bool equal(const qpid::framing::FieldTable& bindArgs, const qpid::framing::FieldTable& msgArgs); }; diff --git a/cpp/src/qpid/broker/IncomingExecutionContext.cpp b/cpp/src/qpid/broker/IncomingExecutionContext.cpp deleted file mode 100644 index 6c6cae6740..0000000000 --- a/cpp/src/qpid/broker/IncomingExecutionContext.cpp +++ /dev/null @@ -1,143 +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 "IncomingExecutionContext.h" -#include "qpid/Exception.h" - -namespace qpid { -namespace broker { - -using boost::intrusive_ptr; -using qpid::framing::AccumulatedAck; -using qpid::framing::SequenceNumber; -using qpid::framing::SequenceNumberSet; - -void IncomingExecutionContext::noop() -{ - complete(next()); -} - -void IncomingExecutionContext::flush() -{ - for (Messages::iterator i = incomplete.begin(); i != incomplete.end(); ) { - if ((*i)->isEnqueueComplete()) { - complete((*i)->getCommandId()); - i = incomplete.erase(i); - } else { - i++; - } - } - window.lwm = completed.mark; -} - -void IncomingExecutionContext::sync() -{ - while (completed.mark < window.hwm) { - wait(); - } -} - -void IncomingExecutionContext::sync(const SequenceNumber& point) -{ - while (!isComplete(point)) { - wait(); - } -} - -/** - * Every call to next() should be followed be either a call to - * complete() - in the case of commands, which are always synchronous - * - or track() - in the case of messages which may be asynchronously - * stored. - */ -SequenceNumber IncomingExecutionContext::next() -{ - return ++window.hwm; -} - -void IncomingExecutionContext::complete(const SequenceNumber& command) -{ - completed.update(command, command); -} - -void IncomingExecutionContext::track(intrusive_ptr<Message> msg) -{ - if (msg->isEnqueueComplete()) { - complete(msg->getCommandId()); - } else { - incomplete.push_back(msg); - } -} - -bool IncomingExecutionContext::isComplete(const SequenceNumber& command) -{ - if (command > window.hwm) { - throw Exception(QPID_MSG("Bad sync request: point exceeds last command received [" - << command.getValue() << " > " << window.hwm.getValue() << "]")); - } - - return completed.covers(command); -} - - -const SequenceNumber& IncomingExecutionContext::getMark() -{ - return completed.mark; -} - -SequenceNumberSet IncomingExecutionContext::getRange() -{ - SequenceNumberSet range; - completed.collectRanges(range); - return range; -} - -void IncomingExecutionContext::wait() -{ - check(); - // for IO flush on the store - for (Messages::iterator i = incomplete.begin(); i != incomplete.end(); i++) { - (*i)->flush(); - } - incomplete.front()->waitForEnqueueComplete(); - flush(); -} - -/** - * This is a check of internal state consistency. - */ -void IncomingExecutionContext::check() -{ - if (incomplete.empty()) { - if (window.hwm != completed.mark) { - //can only happen if there is a call to next() without a - //corresponding call to completed() or track() - or if - //there is a logical error in flush() or - //AccumulatedAck::update() - throw Exception(QPID_MSG("Completion tracking error: window.hwm=" - << window.hwm.getValue() << ", completed.mark=" - << completed.mark.getValue())); - } - } -} - -}} - diff --git a/cpp/src/qpid/broker/IncomingExecutionContext.h b/cpp/src/qpid/broker/IncomingExecutionContext.h deleted file mode 100644 index 7380e9ae64..0000000000 --- a/cpp/src/qpid/broker/IncomingExecutionContext.h +++ /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. - * - */ -#ifndef _IncomingExecutionContext_ -#define _IncomingExecutionContext_ - -#include "Message.h" - -#include "qpid/framing/AccumulatedAck.h" -#include "qpid/framing/SequenceNumber.h" - -#include <boost/intrusive_ptr.hpp> - -namespace qpid { -namespace broker { - -class IncomingExecutionContext -{ - typedef std::list<boost::intrusive_ptr<Message> > Messages; - framing::Window window; - framing::AccumulatedAck completed; - Messages incomplete; - - bool isComplete(const framing::SequenceNumber& command); - void check(); - void wait(); -public: - void noop(); - void flush(); - void sync(); - void sync(const framing::SequenceNumber& point); - framing::SequenceNumber next(); - void complete(const framing::SequenceNumber& command); - void track(boost::intrusive_ptr<Message>); - - const framing::SequenceNumber& getMark(); - framing::SequenceNumberSet getRange(); - -}; - - -}} - -#endif diff --git a/cpp/src/qpid/broker/IncompleteMessageList.cpp b/cpp/src/qpid/broker/IncompleteMessageList.cpp index dd7bbfc067..02265ab85c 100644 --- a/cpp/src/qpid/broker/IncompleteMessageList.cpp +++ b/cpp/src/qpid/broker/IncompleteMessageList.cpp @@ -18,34 +18,67 @@ * under the License. * */ -#include "IncompleteMessageList.h" -#include "Message.h" +#include "qpid/broker/IncompleteMessageList.h" namespace qpid { namespace broker { +IncompleteMessageList::IncompleteMessageList() : + callback(boost::bind(&IncompleteMessageList::enqueueComplete, this, _1)) +{} + +IncompleteMessageList::~IncompleteMessageList() +{ + sys::Mutex::ScopedLock l(lock); + for (Messages::iterator i = incomplete.begin(); i != incomplete.end(); ++i) { + (*i)->resetEnqueueCompleteCallback(); + (*i)->resetDequeueCompleteCallback(); + } +} + void IncompleteMessageList::add(boost::intrusive_ptr<Message> msg) { + sys::Mutex::ScopedLock l(lock); + msg->setEnqueueCompleteCallback(callback); incomplete.push_back(msg); } -void IncompleteMessageList::process(CompletionListener l, bool sync) +void IncompleteMessageList::enqueueComplete(const boost::intrusive_ptr<Message>& ) { + sys::Mutex::ScopedLock l(lock); + lock.notify(); +} + +void IncompleteMessageList::process(const CompletionListener& listen, bool sync) { + sys::Mutex::ScopedLock l(lock); while (!incomplete.empty()) { boost::intrusive_ptr<Message>& msg = incomplete.front(); if (!msg->isEnqueueComplete()) { if (sync){ - msg->flush(); - msg->waitForEnqueueComplete(); + { + sys::Mutex::ScopedUnlock u(lock); + msg->flush(); // Can re-enter IncompleteMessageList::enqueueComplete + } + while (!msg->isEnqueueComplete()) + lock.wait(); } else { //leave the message as incomplete for now return; } } - l(msg); + listen(msg); incomplete.pop_front(); } } +void IncompleteMessageList::each(const CompletionListener& listen) { + Messages snapshot; + { + sys::Mutex::ScopedLock l(lock); + snapshot = incomplete; + } + std::for_each(incomplete.begin(), incomplete.end(), listen); // FIXME aconway 2008-11-07: passed by ref or value? +} + }} diff --git a/cpp/src/qpid/broker/IncompleteMessageList.h b/cpp/src/qpid/broker/IncompleteMessageList.h index 2cfd7bfee5..a4debd1233 100644 --- a/cpp/src/qpid/broker/IncompleteMessageList.h +++ b/cpp/src/qpid/broker/IncompleteMessageList.h @@ -21,25 +21,35 @@ #ifndef _IncompleteMessageList_ #define _IncompleteMessageList_ -#include <list> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/sys/Monitor.h" +#include "qpid/broker/Message.h" #include <boost/intrusive_ptr.hpp> #include <boost/function.hpp> +#include <list> namespace qpid { namespace broker { -class Message; - class IncompleteMessageList { typedef std::list< boost::intrusive_ptr<Message> > Messages; + + void enqueueComplete(const boost::intrusive_ptr<Message>&); + + sys::Monitor lock; Messages incomplete; + Message::MessageCallback callback; public: - typedef boost::function<void(boost::intrusive_ptr<Message>)> CompletionListener; - - void add(boost::intrusive_ptr<Message> msg); - void process(CompletionListener l, bool sync); + typedef Message::MessageCallback CompletionListener; + + QPID_BROKER_EXTERN IncompleteMessageList(); + QPID_BROKER_EXTERN ~IncompleteMessageList(); + + QPID_BROKER_EXTERN void add(boost::intrusive_ptr<Message> msg); + QPID_BROKER_EXTERN void process(const CompletionListener& l, bool sync); + void each(const CompletionListener& l); }; diff --git a/cpp/src/qpid/broker/Link.cpp b/cpp/src/qpid/broker/Link.cpp index 05b759f695..cdba18ccf9 100644 --- a/cpp/src/qpid/broker/Link.cpp +++ b/cpp/src/qpid/broker/Link.cpp @@ -19,50 +19,61 @@ * */ -#include "Link.h" -#include "LinkRegistry.h" -#include "Broker.h" -#include "Connection.h" -#include "qpid/agent/ManagementAgent.h" -#include "qpid/management/Link.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Connection.h" +#include "qmf/org/apache/qpid/broker/EventBrokerLinkUp.h" +#include "qmf/org/apache/qpid/broker/EventBrokerLinkDown.h" #include "boost/bind.hpp" #include "qpid/log/Statement.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/broker/AclModule.h" using namespace qpid::broker; using qpid::framing::Buffer; using qpid::framing::FieldTable; +using qpid::framing::NotAllowedException; +using qpid::framing::connection::CLOSE_CODE_CONNECTION_FORCED; using qpid::management::ManagementAgent; using qpid::management::ManagementObject; using qpid::management::Manageable; using qpid::management::Args; using qpid::sys::Mutex; +using std::stringstream; +namespace _qmf = qmf::org::apache::qpid::broker; Link::Link(LinkRegistry* _links, MessageStore* _store, string& _host, uint16_t _port, - bool _useSsl, + string& _transport, bool _durable, string& _authMechanism, string& _username, string& _password, Broker* _broker, - management::Manageable* parent) - : links(_links), store(_store), host(_host), port(_port), useSsl(_useSsl), durable(_durable), + Manageable* parent) + : links(_links), store(_store), host(_host), port(_port), + transport(_transport), + durable(_durable), authMechanism(_authMechanism), username(_username), password(_password), persistenceId(0), mgmtObject(0), broker(_broker), state(0), visitCount(0), currentInterval(1), closing(false), + updateUrls(false), channelCounter(1), - connection(0) + connection(0), + agent(0) { - if (parent != 0) + if (parent != 0 && broker != 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + agent = broker->getManagementAgent(); if (agent != 0) { - mgmtObject = new management::Link(agent, this, parent, _host, _port, _useSsl, _durable); + mgmtObject = new _qmf::Link(agent, this, parent, _host, _port, _transport, _durable); if (!durable) agent->addObject(mgmtObject); } @@ -73,7 +84,7 @@ Link::Link(LinkRegistry* _links, Link::~Link () { if (state == STATE_OPERATIONAL && connection != 0) - connection->close(); + connection->close(CLOSE_CODE_CONNECTION_FORCED, "closed by management"); if (mgmtObject != 0) mgmtObject->resourceDestroy (); @@ -95,6 +106,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; } } @@ -104,8 +116,9 @@ 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, port, useSsl, + broker->connect (host, port, transport, boost::bind (&Link::closed, this, _1, _2)); + QPID_LOG (debug, "Inter-broker link connecting to " << host << ":" << port); } catch(std::exception& e) { setStateLH(STATE_WAITING); if (mgmtObject != 0) @@ -115,27 +128,39 @@ void Link::startConnectionLH () void Link::established () { - Mutex::ScopedLock mutex(lock); + stringstream addr; + addr << host << ":" << port; - QPID_LOG (info, "Inter-broker link established to " << host << ":" << port); - setStateLH(STATE_OPERATIONAL); - currentInterval = 1; - visitCount = 0; - if (closing) - destroy(); + QPID_LOG (info, "Inter-broker link established to " << addr.str()); + agent->raiseEvent(_qmf::EventBrokerLinkUp(addr.str())); + { + Mutex::ScopedLock mutex(lock); + setStateLH(STATE_OPERATIONAL); + currentInterval = 1; + visitCount = 0; + if (closing) + destroy(); + } } void Link::closed (int, std::string text) { Mutex::ScopedLock mutex(lock); + QPID_LOG (info, "Inter-broker link disconnected from " << host << ":" << port << " " << text); connection = 0; - if (state == STATE_OPERATIONAL) - QPID_LOG (warning, "Inter-broker link disconnected from " << host << ":" << port); + if (state == STATE_OPERATIONAL) { + stringstream addr; + addr << host << ":" << port; + QPID_LOG (warning, "Inter-broker link disconnected from " << addr.str()); + agent->raiseEvent(_qmf::EventBrokerLinkDown(addr.str())); + } - for (Bridges::iterator i = active.begin(); i != active.end(); i++) + for (Bridges::iterator i = active.begin(); i != active.end(); i++) { + (*i)->closed(); created.push_back(*i); + } active.clear(); if (state != STATE_FAILED) @@ -149,32 +174,46 @@ void Link::closed (int, std::string text) destroy(); } -void Link::destroy () +void Link::checkClosePermission() { Mutex::ScopedLock mutex(lock); - Bridges toDelete; + + AclModule* acl = getBroker()->getAcl(); + std::string userID = getUsername() + "@" + getBroker()->getOptions().realm; + if (acl && !acl->authorise(userID,acl::ACT_DELETE,acl::OBJ_LINK,"")){ + throw NotAllowedException("ACL denied delete link request"); + } +} - QPID_LOG (info, "Inter-broker link to " << host << ":" << port << " removed by management"); - if (connection) - connection->close(403, "closed by management"); - setStateLH(STATE_CLOSED); +void Link::destroy () +{ + Bridges toDelete; + { + Mutex::ScopedLock mutex(lock); - // Move the bridges to be deleted into a local vector so there is no - // corruption of the iterator caused by bridge deletion. - for (Bridges::iterator i = active.begin(); i != active.end(); i++) - toDelete.push_back(*i); - active.clear(); + QPID_LOG (info, "Inter-broker link to " << host << ":" << port << " removed by management"); + if (connection) + connection->close(CLOSE_CODE_CONNECTION_FORCED, "closed by management"); + + setStateLH(STATE_CLOSED); - for (Bridges::iterator i = created.begin(); i != created.end(); i++) - toDelete.push_back(*i); - created.clear(); + // Move the bridges to be deleted into a local vector so there is no + // corruption of the iterator caused by bridge deletion. + for (Bridges::iterator i = active.begin(); i != active.end(); i++) { + (*i)->closed(); + toDelete.push_back(*i); + } + active.clear(); - // Now delete all bridges on this link. + for (Bridges::iterator i = created.begin(); i != created.end(); i++) + toDelete.push_back(*i); + created.clear(); + } + // Now delete all bridges on this link (don't hold the lock for this). for (Bridges::iterator i = toDelete.begin(); i != toDelete.end(); i++) (*i)->destroy(); toDelete.clear(); - links->destroy (host, port); } @@ -186,21 +225,27 @@ void Link::add(Bridge::shared_ptr bridge) void Link::cancel(Bridge::shared_ptr bridge) { - Mutex::ScopedLock mutex(lock); - - for (Bridges::iterator i = created.begin(); i != created.end(); i++) { - if ((*i).get() == bridge.get()) { - created.erase(i); - break; + { + Mutex::ScopedLock mutex(lock); + + for (Bridges::iterator i = created.begin(); i != created.end(); i++) { + if ((*i).get() == bridge.get()) { + created.erase(i); + break; + } } - } - for (Bridges::iterator i = active.begin(); i != active.end(); i++) { - if ((*i).get() == bridge.get()) { - bridge->cancel(); - active.erase(i); - break; + for (Bridges::iterator i = active.begin(); i != active.end(); i++) { + if ((*i).get() == bridge.get()) { + cancellations.push_back(bridge); + bridge->closed(); + active.erase(i); + break; + } } } + if (!cancellations.empty()) { + connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); + } } void Link::ioThreadProcessing() @@ -209,8 +254,9 @@ void Link::ioThreadProcessing() if (state != STATE_OPERATIONAL) return; + QPID_LOG(debug, "Link::ioThreadProcessing()"); - //process any pending creates + //process any pending creates and/or cancellations if (!created.empty()) { for (Bridges::iterator i = created.begin(); i != created.end(); ++i) { active.push_back(*i); @@ -218,34 +264,77 @@ void Link::ioThreadProcessing() } created.clear(); } + if (!cancellations.empty()) { + for (Bridges::iterator i = cancellations.begin(); i != cancellations.end(); ++i) { + (*i)->cancel(*connection); + } + cancellations.clear(); + } } void Link::setConnection(Connection* c) { Mutex::ScopedLock mutex(lock); connection = c; + updateUrls = true; } void Link::maintenanceVisit () { Mutex::ScopedLock mutex(lock); + if (connection && updateUrls) { + urls.reset(connection->getKnownHosts()); + QPID_LOG(debug, "Known hosts for peer of inter-broker link: " << urls); + updateUrls = false; + } + if (state == STATE_WAITING) { visitCount++; if (visitCount >= currentInterval) { visitCount = 0; - currentInterval *= 2; - if (currentInterval > MAX_INTERVAL) - currentInterval = MAX_INTERVAL; - startConnectionLH(); + //switch host and port to next in url list if possible + if (!tryFailover()) { + currentInterval *= 2; + if (currentInterval > MAX_INTERVAL) + currentInterval = MAX_INTERVAL; + startConnectionLH(); + } } } - else if (state == STATE_OPERATIONAL && !created.empty() && connection != 0) + else if (state == STATE_OPERATIONAL && (!created.empty() || !cancellations.empty()) && connection != 0) connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); } +void Link::reconnect(const qpid::TcpAddress& a) +{ + Mutex::ScopedLock mutex(lock); + host = a.host; + port = a.port; + startConnectionLH(); + if (mgmtObject != 0) { + stringstream errorString; + errorString << "Failed over to " << a; + mgmtObject->set_lastError(errorString.str()); + } +} + +bool Link::tryFailover() +{ + //TODO: urls only work for TCP at present, update when that has changed + TcpAddress next; + if (transport == Broker::TCP_TRANSPORT && urls.next(next) && + (next.host != host || next.port != port)) { + links->changeAddress(TcpAddress(host, port), next); + QPID_LOG(debug, "Link failing over to " << host << ":" << port); + return true; + } else { + return false; + } +} + uint Link::nextChannel() { Mutex::ScopedLock mutex(lock); @@ -265,7 +354,7 @@ void Link::notifyConnectionForced(const string text) void Link::setPersistenceId(uint64_t id) const { if (mgmtObject != 0 && persistenceId == 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + ManagementAgent* agent = broker->getManagementAgent(); agent->addObject(mgmtObject, id); } persistenceId = id; @@ -280,19 +369,20 @@ Link::shared_ptr Link::decode(LinkRegistry& links, Buffer& buffer) { string host; uint16_t port; + string transport; string authMechanism; string username; string password; buffer.getShortString(host); port = buffer.getShort(); - bool useSsl(buffer.getOctet()); + buffer.getShortString(transport); bool durable(buffer.getOctet()); buffer.getShortString(authMechanism); buffer.getShortString(username); buffer.getShortString(password); - return links.declare(host, port, useSsl, durable, authMechanism, username, password).first; + return links.declare(host, port, transport, durable, authMechanism, username, password).first; } void Link::encode(Buffer& buffer) const @@ -300,7 +390,7 @@ void Link::encode(Buffer& buffer) const buffer.putShortString(string("link")); buffer.putShortString(host); buffer.putShort(port); - buffer.putOctet(useSsl ? 1 : 0); + buffer.putShortString(transport); buffer.putOctet(durable ? 1 : 0); buffer.putShortString(authMechanism); buffer.putShortString(username); @@ -312,7 +402,7 @@ uint32_t Link::encodedSize() const return host.size() + 1 // short-string (host) + 5 // short-string ("link") + 2 // port - + 1 // useSsl + + transport.size() + 1 // short-string(transport) + 1 // durable + authMechanism.size() + 1 + username.size() + 1 @@ -324,27 +414,48 @@ ManagementObject* Link::GetManagementObject (void) const return (ManagementObject*) mgmtObject; } -Manageable::status_t Link::ManagementMethod (uint32_t op, management::Args& args) +Manageable::status_t Link::ManagementMethod (uint32_t op, Args& args, string& text) { switch (op) { - case management::Link::METHOD_CLOSE : - closing = true; - if (state != STATE_CONNECTING) - destroy(); + case _qmf::Link::METHOD_CLOSE : + checkClosePermission(); + 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)); + } + } return Manageable::STATUS_OK; - case management::Link::METHOD_BRIDGE : - management::ArgsLinkBridge& iargs = (management::ArgsLinkBridge&) args; + case _qmf::Link::METHOD_BRIDGE : + _qmf::ArgsLinkBridge& iargs = (_qmf::ArgsLinkBridge&) args; + QPID_LOG(debug, "Link::bridge() request received"); // Durable bridges are only valid on durable links - if (iargs.i_durable && !durable) - return Manageable::STATUS_INVALID_PARAMETER; + if (iargs.i_durable && !durable) { + text = "Can't create a durable route on a non-durable link"; + return Manageable::STATUS_USER; + } + + if (iargs.i_dynamic) { + Exchange::shared_ptr exchange = getBroker()->getExchanges().get(iargs.i_src); + if (exchange.get() == 0) { + text = "Exchange not found"; + return Manageable::STATUS_USER; + } + if (!exchange->supportsDynamicBinding()) { + text = "Exchange type does not support dynamic routing"; + return Manageable::STATUS_USER; + } + } std::pair<Bridge::shared_ptr, bool> result = links->declare (host, port, iargs.i_durable, iargs.i_src, iargs.i_dest, iargs.i_key, iargs.i_srcIsQueue, - iargs.i_srcIsLocal, iargs.i_tag, iargs.i_excludes); + iargs.i_srcIsLocal, iargs.i_tag, iargs.i_excludes, + iargs.i_dynamic, iargs.i_sync); if (result.second && iargs.i_durable) store->create(*result.first); @@ -354,3 +465,17 @@ Manageable::status_t Link::ManagementMethod (uint32_t op, management::Args& args 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"); + } + } +} diff --git a/cpp/src/qpid/broker/Link.h b/cpp/src/qpid/broker/Link.h index d425c49800..318eb5bd32 100644 --- a/cpp/src/qpid/broker/Link.h +++ b/cpp/src/qpid/broker/Link.h @@ -23,13 +23,15 @@ */ #include <boost/shared_ptr.hpp> -#include "MessageStore.h" -#include "PersistableConfig.h" -#include "Bridge.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/PersistableConfig.h" +#include "qpid/broker/Bridge.h" +#include "qpid/broker/RetryList.h" #include "qpid/sys/Mutex.h" #include "qpid/framing/FieldTable.h" #include "qpid/management/Manageable.h" -#include "qpid/management/Link.h" +#include "qpid/management/ManagementAgent.h" +#include "qmf/org/apache/qpid/broker/Link.h" #include <boost/ptr_container/ptr_vector.hpp> namespace qpid { @@ -47,30 +49,35 @@ namespace qpid { MessageStore* store; string host; uint16_t port; - bool useSsl; + string transport; bool durable; string authMechanism; string username; string password; mutable uint64_t persistenceId; - management::Link* mgmtObject; + qmf::org::apache::qpid::broker::Link* mgmtObject; Broker* broker; int state; uint32_t visitCount; uint32_t currentInterval; bool closing; + RetryList urls; + bool updateUrls; typedef std::vector<Bridge::shared_ptr> Bridges; Bridges created; // Bridges pending creation Bridges active; // Bridges active + Bridges cancellations; // Bridges pending cancellation uint channelCounter; Connection* connection; + management::ManagementAgent* agent; static const int STATE_WAITING = 1; static const int STATE_CONNECTING = 2; 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 uint32_t MAX_INTERVAL = 32; @@ -78,6 +85,8 @@ namespace qpid { void startConnectionLH(); // Start the IO Connection void destroy(); // Called when mgmt deletes this link void ioThreadProcessing(); // Called on connection's IO thread by request + bool tryFailover(); // Called during maintenance visit + void checkClosePermission(); // ACL check for explict mgmt call to close this link public: typedef boost::shared_ptr<Link> shared_ptr; @@ -86,7 +95,7 @@ namespace qpid { MessageStore* store, string& host, uint16_t port, - bool useSsl, + string& transport, bool durable, string& authMechanism, string& username, @@ -106,12 +115,15 @@ namespace qpid { void established(); // Called when connection is created void closed(int, std::string); // Called when connection goes away void setConnection(Connection*); // Set pointer to the AMQP Connection + void reconnect(const TcpAddress&); //called by LinkRegistry string getAuthMechanism() { return authMechanism; } string getUsername() { return username; } string getPassword() { return password; } + Broker* getBroker() { return broker; } void notifyConnectionForced(const std::string text); + void setPassive(bool p); // PersistableConfig: void setPersistenceId(uint64_t id) const; @@ -123,8 +135,8 @@ namespace qpid { static Link::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer); // Manageable entry points - management::ManagementObject* GetManagementObject (void) const; - management::Manageable::status_t ManagementMethod (uint32_t, management::Args&); + management::ManagementObject* GetManagementObject(void) const; + management::Manageable::status_t ManagementMethod(uint32_t, management::Args&, std::string&); }; } } diff --git a/cpp/src/qpid/broker/LinkRegistry.cpp b/cpp/src/qpid/broker/LinkRegistry.cpp index 0703c276cf..f32587dd68 100644 --- a/cpp/src/qpid/broker/LinkRegistry.cpp +++ b/cpp/src/qpid/broker/LinkRegistry.cpp @@ -18,20 +18,49 @@ * under the License. * */ -#include "LinkRegistry.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" #include <iostream> +#include <boost/format.hpp> using namespace qpid::broker; using namespace qpid::sys; using std::pair; using std::stringstream; using boost::intrusive_ptr; +using boost::format; +using boost::str; +namespace _qmf = qmf::org::apache::qpid::broker; #define LINK_MAINT_INTERVAL 2 -LinkRegistry::LinkRegistry (Broker* _broker) : broker(_broker), parent(0), store(0) +// TODO: This constructor is only used by the store unit tests - +// That probably indicates that LinkRegistry isn't correctly +// factored: The persistence element and maintenance element +// should be factored separately +LinkRegistry::LinkRegistry () : + broker(0), timer(0), + parent(0), store(0), passive(false), passiveChanged(false), + realm("") { - timer.add (intrusive_ptr<TimerTask> (new Periodic(*this))); +} + +LinkRegistry::LinkRegistry (Broker* _broker) : + broker(_broker), timer(&broker->getTimer()), + maintenanceTask(new Periodic(*this)), + parent(0), store(0), passive(false), passiveChanged(false), + realm(broker->getOptions().realm) +{ + timer->add(maintenanceTask); +} + +LinkRegistry::~LinkRegistry() +{ + // This test is only necessary if the default constructor above is present + if (maintenanceTask) + maintenanceTask->cancel(); } LinkRegistry::Periodic::Periodic (LinkRegistry& _links) : @@ -40,7 +69,8 @@ LinkRegistry::Periodic::Periodic (LinkRegistry& _links) : void LinkRegistry::Periodic::fire () { links.periodicMaintenance (); - links.timer.add (intrusive_ptr<TimerTask> (new Periodic(links))); + setupNextFire(); + links.timer->add(this); } void LinkRegistry::periodicMaintenance () @@ -49,13 +79,53 @@ void LinkRegistry::periodicMaintenance () linksToDestroy.clear(); bridgesToDestroy.clear(); + if (passiveChanged) { + 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); + } + passiveChanged = false; + } for (LinkMap::iterator i = links.begin(); i != links.end(); i++) i->second->maintenanceVisit(); + //now process any requests for re-addressing + for (AddressMap::iterator i = reMappings.begin(); i != reMappings.end(); i++) + updateAddress(i->first, i->second); + reMappings.clear(); +} + +void LinkRegistry::changeAddress(const qpid::TcpAddress& oldAddress, const qpid::TcpAddress& newAddress) +{ + //done on periodic maintenance thread; hold changes in separate + //map to avoid modifying the link map that is iterated over + reMappings[createKey(oldAddress)] = newAddress; +} + +bool LinkRegistry::updateAddress(const std::string& oldKey, const qpid::TcpAddress& newAddress) +{ + std::string newKey = createKey(newAddress); + if (links.find(newKey) != links.end()) { + QPID_LOG(error, "Attempted to update key from " << oldKey << " to " << newKey << " which is already in use"); + return false; + } else { + LinkMap::iterator i = links.find(oldKey); + if (i == links.end()) { + QPID_LOG(error, "Attempted to update key from " << oldKey << " which does not exist, to " << newKey); + return false; + } else { + links[newKey] = i->second; + i->second->reconnect(newAddress); + links.erase(oldKey); + QPID_LOG(info, "Updated link key from " << oldKey << " to " << newKey); + return true; + } + } } pair<Link::shared_ptr, bool> LinkRegistry::declare(string& host, uint16_t port, - bool useSsl, + string& transport, bool durable, string& authMechanism, string& username, @@ -72,9 +142,10 @@ pair<Link::shared_ptr, bool> LinkRegistry::declare(string& host, { Link::shared_ptr link; - link = Link::shared_ptr (new Link (this, store, host, port, useSsl, durable, + link = Link::shared_ptr (new Link (this, store, host, port, transport, durable, authMechanism, username, password, broker, parent)); + if (passive) link->setPassive(true); links[key] = link; return std::pair<Link::shared_ptr, bool>(link, true); } @@ -90,9 +161,13 @@ pair<Bridge::shared_ptr, bool> LinkRegistry::declare(std::string& host, bool isQueue, bool isLocal, std::string& tag, - std::string& excludes) + std::string& excludes, + bool dynamic, + uint16_t sync) { Mutex::ScopedLock locker(lock); + QPID_LOG(debug, "Bridge declared " << host << ": " << port << " from " << src << " to " << dest << " (" << key << ")"); + stringstream keystream; keystream << host << ":" << port; string linkKey = string(keystream.str()); @@ -107,7 +182,7 @@ pair<Bridge::shared_ptr, bool> LinkRegistry::declare(std::string& host, BridgeMap::iterator b = bridges.find(bridgeKey); if (b == bridges.end()) { - management::ArgsLinkBridge args; + _qmf::ArgsLinkBridge args; Bridge::shared_ptr bridge; args.i_durable = durable; @@ -118,6 +193,8 @@ pair<Bridge::shared_ptr, bool> LinkRegistry::declare(std::string& host, args.i_srcIsLocal = isLocal; args.i_tag = tag; args.i_excludes = excludes; + args.i_dynamic = dynamic; + args.i_sync = sync; bridge = Bridge::shared_ptr (new Bridge (l->second.get(), l->second->nextChannel(), @@ -177,7 +254,6 @@ void LinkRegistry::destroy(const std::string& host, void LinkRegistry::setStore (MessageStore* _store) { - assert (store == 0 && _store != 0); store = _store; } @@ -185,66 +261,84 @@ MessageStore* LinkRegistry::getStore() const { return store; } -void LinkRegistry::notifyConnection(const std::string& key, Connection* c) +Link::shared_ptr LinkRegistry::findLink(const std::string& key) { Mutex::ScopedLock locker(lock); LinkMap::iterator l = links.find(key); - if (l != links.end()) - { - l->second->established(); - l->second->setConnection(c); + if (l != links.end()) return l->second; + else return Link::shared_ptr(); +} + +void LinkRegistry::notifyConnection(const std::string& key, Connection* c) +{ + Link::shared_ptr link = findLink(key); + if (link) { + link->established(); + link->setConnection(c); + c->setUserId(str(format("%1%@%2%") % link->getUsername() % realm)); } } void LinkRegistry::notifyClosed(const std::string& key) { - Mutex::ScopedLock locker(lock); - LinkMap::iterator l = links.find(key); - if (l != links.end()) - l->second->closed(0, "Closed by peer"); + Link::shared_ptr link = findLink(key); + if (link) { + link->closed(0, "Closed by peer"); + } } void LinkRegistry::notifyConnectionForced(const std::string& key, const std::string& text) { - Mutex::ScopedLock locker(lock); - LinkMap::iterator l = links.find(key); - if (l != links.end()) - l->second->notifyConnectionForced(text); + Link::shared_ptr link = findLink(key); + if (link) { + link->notifyConnectionForced(text); + } } std::string LinkRegistry::getAuthMechanism(const std::string& key) { - Mutex::ScopedLock locker(lock); - LinkMap::iterator l = links.find(key); - if (l != links.end()) - return l->second->getAuthMechanism(); + Link::shared_ptr link = findLink(key); + if (link) + return link->getAuthMechanism(); return string("ANONYMOUS"); } std::string LinkRegistry::getAuthCredentials(const std::string& key) { - Mutex::ScopedLock locker(lock); - LinkMap::iterator l = links.find(key); - if (l == links.end()) + Link::shared_ptr link = findLink(key); + if (!link) return string(); string result; result += '\0'; - result += l->second->getUsername(); + result += link->getUsername(); result += '\0'; - result += l->second->getPassword(); + result += link->getPassword(); return result; } std::string LinkRegistry::getAuthIdentity(const std::string& key) { - Mutex::ScopedLock locker(lock); - LinkMap::iterator l = links.find(key); - if (l == links.end()) + Link::shared_ptr link = findLink(key); + if (!link) return string(); - return l->second->getUsername(); + return link->getUsername(); } +std::string LinkRegistry::createKey(const qpid::TcpAddress& a) +{ + stringstream keystream; + keystream << a.host << ":" << a.port; + return string(keystream.str()); +} + +void LinkRegistry::setPassive(bool p) +{ + Mutex::ScopedLock locker(lock); + passiveChanged = p != passive; + passive = p; + //will activate or passivate links on maintenance visit +} diff --git a/cpp/src/qpid/broker/LinkRegistry.h b/cpp/src/qpid/broker/LinkRegistry.h index 242c0d58ba..09a89298b6 100644 --- a/cpp/src/qpid/broker/LinkRegistry.h +++ b/cpp/src/qpid/broker/LinkRegistry.h @@ -23,23 +23,26 @@ */ #include <map> -#include "Link.h" -#include "Bridge.h" -#include "MessageStore.h" -#include "Timer.h" +#include "qpid/broker/Bridge.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/Address.h" #include "qpid/sys/Mutex.h" +#include "qpid/sys/Timer.h" #include "qpid/management/Manageable.h" +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> namespace qpid { namespace broker { + class Link; class Broker; class Connection; class LinkRegistry { // Declare a timer task to manage the establishment of link connections and the // re-establishment of lost link connections. - struct Periodic : public TimerTask + struct Periodic : public sys::TimerTask { LinkRegistry& links; @@ -48,28 +51,40 @@ namespace broker { void fire(); }; - typedef std::map<std::string, Link::shared_ptr> LinkMap; + typedef std::map<std::string, boost::shared_ptr<Link> > LinkMap; typedef std::map<std::string, Bridge::shared_ptr> BridgeMap; + typedef std::map<std::string, TcpAddress> AddressMap; LinkMap links; LinkMap linksToDestroy; BridgeMap bridges; BridgeMap bridgesToDestroy; + AddressMap reMappings; qpid::sys::Mutex lock; Broker* broker; - Timer timer; + sys::Timer* timer; + boost::intrusive_ptr<qpid::sys::TimerTask> maintenanceTask; management::Manageable* parent; MessageStore* store; + bool passive; + bool passiveChanged; + std::string realm; void periodicMaintenance (); + bool updateAddress(const std::string& oldKey, const TcpAddress& newAddress); + boost::shared_ptr<Link> findLink(const std::string& key); + static std::string createKey(const TcpAddress& address); public: + LinkRegistry (); // Only used in store tests LinkRegistry (Broker* _broker); - std::pair<Link::shared_ptr, bool> + ~LinkRegistry(); + + std::pair<boost::shared_ptr<Link>, bool> declare(std::string& host, uint16_t port, - bool useSsl, + std::string& transport, bool durable, std::string& authMechanism, std::string& username, @@ -84,7 +99,9 @@ namespace broker { bool isQueue, bool isLocal, std::string& id, - std::string& excludes); + std::string& excludes, + bool dynamic, + uint16_t sync); void destroy(const std::string& host, const uint16_t port); void destroy(const std::string& host, @@ -114,6 +131,18 @@ namespace broker { std::string getAuthMechanism (const std::string& key); std::string getAuthCredentials (const std::string& key); std::string getAuthIdentity (const std::string& key); + + /** + * Called by links failing over to new address + */ + void changeAddress(const TcpAddress& oldAddress, const TcpAddress& newAddress); + /** + * 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. + */ + void setPassive(bool); }; } } diff --git a/cpp/src/qpid/broker/Message.cpp b/cpp/src/qpid/broker/Message.cpp index 331bb5e716..47ca7a7ae8 100644 --- a/cpp/src/qpid/broker/Message.cpp +++ b/cpp/src/qpid/broker/Message.cpp @@ -19,8 +19,9 @@ * */ -#include "Message.h" -#include "ExchangeRegistry.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/ExpiryPolicy.h" #include "qpid/StringUtils.h" #include "qpid/framing/frame_functors.h" #include "qpid/framing/FieldTable.h" @@ -30,17 +31,43 @@ #include "qpid/framing/TypeFilter.h" #include "qpid/log/Statement.h" +#include <time.h> + using boost::intrusive_ptr; -using namespace qpid::broker; -using namespace qpid::framing; +using qpid::sys::AbsTime; +using qpid::sys::Duration; +using qpid::sys::TIME_MSEC; +using qpid::sys::FAR_FUTURE; using std::string; +using namespace qpid::framing; + +namespace qpid { +namespace broker { TransferAdapter Message::TRANSFER; -Message::Message(const SequenceNumber& id) : frames(id), persistenceId(0), redelivered(false), loaded(false), staged(false), publisher(0), adapter(0) {} +Message::Message(const framing::SequenceNumber& id) : + frames(id), persistenceId(0), redelivered(false), loaded(false), + staged(false), forcePersistentPolicy(false), publisher(0), adapter(0), + expiration(FAR_FUTURE), enqueueCallback(0), dequeueCallback(0), requiredCredit(0) {} Message::~Message() { + if (expiryPolicy) + expiryPolicy->forget(*this); +} + +void Message::forcePersistent() +{ + // only set forced bit if we actually need to force. + if (! getAdapter().isPersistent(frames) ){ + forcePersistentPolicy = true; + } +} + +bool Message::isForcedPersistent() +{ + return forcePersistentPolicy; } std::string Message::getRoutingKey() const @@ -71,9 +98,9 @@ const FieldTable* Message::getApplicationHeaders() const return getAdapter().getApplicationHeaders(frames); } -bool Message::isPersistent() +bool Message::isPersistent() const { - return getAdapter().isPersistent(frames); + return (getAdapter().isPersistent(frames) || forcePersistentPolicy); } bool Message::requiresAccept() @@ -81,12 +108,16 @@ bool Message::requiresAccept() return getAdapter().requiresAccept(frames); } -uint32_t Message::getRequiredCredit() const +uint32_t Message::getRequiredCredit() { - //add up payload for all header and content frames in the frameset - SumBodySize sum; - frames.map_if(sum, TypeFilter2<HEADER_BODY, CONTENT_BODY>()); - return sum.getSize(); + sys::Mutex::ScopedLock l(lock); + if (!requiredCredit) { + //add up payload for all header and content frames in the frameset + SumBodySize sum; + frames.map_if(sum, TypeFilter2<HEADER_BODY, CONTENT_BODY>()); + requiredCredit = sum.getSize(); + } + return requiredCredit; } void Message::encode(framing::Buffer& buffer) const @@ -96,7 +127,7 @@ void Message::encode(framing::Buffer& buffer) const frames.map_if(f1, TypeFilter2<METHOD_BODY, HEADER_BODY>()); //then encode the payload of each content frame - EncodeBody f2(buffer); + framing::EncodeBody f2(buffer); frames.map_if(f2, TypeFilter<CONTENT_BODY>()); } @@ -141,9 +172,9 @@ void Message::decodeContent(framing::Buffer& buffer) if (buffer.available()) { //get the data as a string and set that as the content //body on a frame then add that frame to the frameset - AMQFrame frame; - frame.setBody(AMQContentBody()); + AMQFrame frame((AMQContentBody())); frame.castBody<AMQContentBody>()->decode(buffer, buffer.available()); + frame.setFirstSegment(false); frames.append(frame); } else { //adjust header flags @@ -154,17 +185,31 @@ void Message::decodeContent(framing::Buffer& buffer) loaded = true; } -void Message::releaseContent(MessageStore* _store) +void Message::tryReleaseContent() { - if (!store) { - store = _store; + if (checkContentReleasable()) { + releaseContent(); } +} + +void Message::releaseContent(MessageStore* s) +{ + //deprecated, use setStore(store); releaseContent(); instead + if (!store) setStore(s); + releaseContent(); +} + +void Message::releaseContent() +{ + sys::Mutex::ScopedLock l(lock); if (store) { if (!getPersistenceId()) { intrusive_ptr<PersistableMessage> pmsg(this); store->stage(pmsg); staged = true; } + //ensure required credit is cached before content frames are released + getRequiredCredit(); //remove any content frames from the frameset frames.remove(TypeFilter<CONTENT_BODY>()); setContentReleased(); @@ -182,30 +227,37 @@ void Message::destroy() } } -void Message::sendContent(Queue& queue, framing::FrameHandler& out, uint16_t maxFrameSize) const +bool Message::getContentFrame(const Queue& queue, AMQFrame& frame, uint16_t maxContentSize, uint64_t offset) const +{ + intrusive_ptr<const PersistableMessage> pmsg(this); + + bool done = false; + string& data = frame.castBody<AMQContentBody>()->getData(); + store->loadContent(queue, pmsg, data, offset, maxContentSize); + done = data.size() < maxContentSize; + frame.setBof(false); + frame.setEof(true); + QPID_LOG(debug, "loaded frame" << frame); + if (offset > 0) { + frame.setBos(false); + } + if (!done) { + frame.setEos(false); + } else return false; + return true; +} + +void Message::sendContent(const Queue& queue, framing::FrameHandler& out, uint16_t maxFrameSize) const { - if (isContentReleased()) { - //load content from store in chunks of maxContentSize + sys::Mutex::ScopedLock l(lock); + if (isContentReleased() && !frames.isComplete()) { + sys::Mutex::ScopedUnlock u(lock); uint16_t maxContentSize = maxFrameSize - AMQFrame::frameOverhead(); - intrusive_ptr<const PersistableMessage> pmsg(this); - - bool done = false; - for (uint64_t offset = 0; !done; offset += maxContentSize) + bool morecontent = true; + for (uint64_t offset = 0; morecontent; offset += maxContentSize) { - AMQFrame frame(in_place<AMQContentBody>()); - string& data = frame.castBody<AMQContentBody>()->getData(); - - store->loadContent(queue, pmsg, data, offset, maxContentSize); - done = data.size() < maxContentSize; - frame.setBof(false); - frame.setEof(true); - if (offset > 0) { - frame.setBos(false); - } - if (!done) { - frame.setEos(false); - } - QPID_LOG(debug, "loaded frame for delivery: " << frame); + AMQFrame frame((AMQContentBody())); + morecontent = getContentFrame(queue, frame, maxContentSize, offset); out.handle(frame); } } else { @@ -253,14 +305,14 @@ bool Message::isContentLoaded() const namespace { - const std::string X_QPID_TRACE("x-qpid.trace"); +const std::string X_QPID_TRACE("x-qpid.trace"); } bool Message::isExcluded(const std::vector<std::string>& excludes) const { const FieldTable* headers = getApplicationHeaders(); if (headers) { - std::string traceStr = headers->getString(X_QPID_TRACE); + std::string traceStr = headers->getAsString(X_QPID_TRACE); if (traceStr.size()) { std::vector<std::string> trace = split(traceStr, ", "); @@ -281,7 +333,7 @@ void Message::addTraceId(const std::string& id) sys::Mutex::ScopedLock l(lock); if (isA<MessageTransferBody>()) { FieldTable& headers = getProperties<MessageProperties>()->getApplicationHeaders(); - std::string trace = headers.getString(X_QPID_TRACE); + std::string trace = headers.getAsString(X_QPID_TRACE); if (trace.empty()) { headers.setString(X_QPID_TRACE, id); } else if (trace.find(id) == std::string::npos) { @@ -291,3 +343,86 @@ void Message::addTraceId(const std::string& id) } } } + +void Message::setTimestamp(const boost::intrusive_ptr<ExpiryPolicy>& e) +{ + DeliveryProperties* props = getProperties<DeliveryProperties>(); + if (props->getTtl()) { + // AMQP requires setting the expiration property to be posix + // time_t in seconds. TTL is in milliseconds + if (!props->getExpiration()) { + //only set expiration in delivery properties if not already set + time_t now = ::time(0); + props->setExpiration(now + (props->getTtl()/1000)); + } + // Use higher resolution time for the internal expiry calculation. + expiration = AbsTime(AbsTime::now(), Duration(props->getTtl() * TIME_MSEC)); + setExpiryPolicy(e); + } +} + +void Message::setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e) { + expiryPolicy = e; + if (expiryPolicy) + expiryPolicy->willExpire(*this); +} + +bool Message::hasExpired() +{ + return expiryPolicy && expiryPolicy->hasExpired(*this); +} + +boost::intrusive_ptr<Message>& Message::getReplacementMessage(const Queue* qfor) const +{ + sys::Mutex::ScopedLock l(lock); + Replacement::iterator i = replacement.find(qfor); + if (i != replacement.end()){ + return i->second; + } + return empty; +} + +void Message::setReplacementMessage(boost::intrusive_ptr<Message> msg, const Queue* qfor) +{ + sys::Mutex::ScopedLock l(lock); + replacement[qfor] = msg; +} + +void Message::allEnqueuesComplete() { + sys::Mutex::ScopedLock l(callbackLock); + MessageCallback* cb = enqueueCallback; + if (cb && *cb) (*cb)(intrusive_ptr<Message>(this)); +} + +void Message::allDequeuesComplete() { + sys::Mutex::ScopedLock l(callbackLock); + MessageCallback* cb = dequeueCallback; + if (cb && *cb) (*cb)(intrusive_ptr<Message>(this)); +} + +void Message::setEnqueueCompleteCallback(MessageCallback& cb) { + sys::Mutex::ScopedLock l(callbackLock); + enqueueCallback = &cb; +} + +void Message::resetEnqueueCompleteCallback() { + sys::Mutex::ScopedLock l(callbackLock); + enqueueCallback = 0; +} + +void Message::setDequeueCompleteCallback(MessageCallback& cb) { + sys::Mutex::ScopedLock l(callbackLock); + dequeueCallback = &cb; +} + +void Message::resetDequeueCompleteCallback() { + sys::Mutex::ScopedLock l(callbackLock); + dequeueCallback = 0; +} + +framing::FieldTable& Message::getOrInsertHeaders() +{ + return getProperties<MessageProperties>()->getApplicationHeaders(); +} + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Message.h b/cpp/src/qpid/broker/Message.h index 0a95fedea6..375fa9ce26 100644 --- a/cpp/src/qpid/broker/Message.h +++ b/cpp/src/qpid/broker/Message.h @@ -22,33 +22,38 @@ * */ -#include <string> -#include <vector> -#include <boost/shared_ptr.hpp> -#include <boost/variant.hpp> -#include "PersistableMessage.h" -#include "MessageAdapter.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/PersistableMessage.h" +#include "qpid/broker/MessageAdapter.h" #include "qpid/framing/amqp_types.h" #include "qpid/sys/Mutex.h" +#include "qpid/sys/Time.h" +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> +#include <string> +#include <vector> namespace qpid { - + namespace framing { class FieldTable; class SequenceNumber; } - + namespace broker { class ConnectionToken; class Exchange; class ExchangeRegistry; class MessageStore; class Queue; +class ExpiryPolicy; class Message : public PersistableMessage { public: - Message(const framing::SequenceNumber& id = framing::SequenceNumber()); - ~Message(); + typedef boost::function<void (const boost::intrusive_ptr<Message>&)> MessageCallback; + + QPID_BROKER_EXTERN Message(const framing::SequenceNumber& id = framing::SequenceNumber()); + QPID_BROKER_EXTERN ~Message(); uint64_t getPersistenceId() const { return persistenceId; } void setPersistenceId(uint64_t _persistenceId) const { persistenceId = _persistenceId; } @@ -61,16 +66,22 @@ public: const framing::SequenceNumber& getCommandId() { return frames.getId(); } - uint64_t contentSize() const; + QPID_BROKER_EXTERN uint64_t contentSize() const; - std::string getRoutingKey() const; + QPID_BROKER_EXTERN std::string getRoutingKey() const; const boost::shared_ptr<Exchange> getExchange(ExchangeRegistry&) const; - std::string getExchangeName() const; + QPID_BROKER_EXTERN std::string getExchangeName() const; bool isImmediate() const; - const framing::FieldTable* getApplicationHeaders() const; - bool isPersistent(); + QPID_BROKER_EXTERN const framing::FieldTable* getApplicationHeaders() const; + framing::FieldTable& getOrInsertHeaders(); + QPID_BROKER_EXTERN bool isPersistent() const; bool requiresAccept(); + QPID_BROKER_EXTERN void setTimestamp(const boost::intrusive_ptr<ExpiryPolicy>& e); + void setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e); + bool hasExpired(); + sys::AbsTime getExpiration() const { return expiration; } + framing::FrameSet& getFrames() { return frames; } const framing::FrameSet& getFrames() const { return frames; } @@ -84,15 +95,24 @@ public: return p->get<T>(true); } + template <class T> const T* hasProperties() const { + const qpid::framing::AMQHeaderBody* p = frames.getHeaders(); + return p->get<T>(); + } + template <class T> const T* getMethod() const { return frames.as<T>(); } + template <class T> T* getMethod() { + return frames.as<T>(); + } + template <class T> bool isA() const { return frames.isA<T>(); } - uint32_t getRequiredCredit() const; + uint32_t getRequiredCredit(); void encode(framing::Buffer& buffer) const; void encodeContent(framing::Buffer& buffer) const; @@ -110,26 +130,44 @@ public: uint32_t encodedHeaderSize() const; uint32_t encodedContentSize() const; - void decodeHeader(framing::Buffer& buffer); - void decodeContent(framing::Buffer& buffer); + QPID_BROKER_EXTERN void decodeHeader(framing::Buffer& buffer); + QPID_BROKER_EXTERN void decodeContent(framing::Buffer& buffer); - /** - * Releases the in-memory content data held by this - * message. Must pass in a store from which the data can - * be reloaded. - */ - void releaseContent(MessageStore* store); + void QPID_BROKER_EXTERN tryReleaseContent(); + void releaseContent(); + void releaseContent(MessageStore* s);//deprecated, use 'setStore(store); releaseContent();' instead void destroy(); - void sendContent(Queue& queue, framing::FrameHandler& out, uint16_t maxFrameSize) const; + bool getContentFrame(const Queue& queue, framing::AMQFrame& frame, uint16_t maxContentSize, uint64_t offset) const; + QPID_BROKER_EXTERN void sendContent(const Queue& queue, framing::FrameHandler& out, uint16_t maxFrameSize) const; void sendHeader(framing::FrameHandler& out, uint16_t maxFrameSize) const; - bool isContentLoaded() const; + QPID_BROKER_EXTERN bool isContentLoaded() const; bool isExcluded(const std::vector<std::string>& excludes) const; void addTraceId(const std::string& id); + + void forcePersistent(); + bool isForcedPersistent(); + + boost::intrusive_ptr<Message>& getReplacementMessage(const Queue* qfor) const; + void setReplacementMessage(boost::intrusive_ptr<Message> msg, const Queue* qfor); + + /** Call cb when enqueue is complete, may call immediately. Holds cb by reference. */ + void setEnqueueCompleteCallback(MessageCallback& cb); + void resetEnqueueCompleteCallback(); + + /** Call cb when dequeue is complete, may call immediately. Holds cb by reference. */ + void setDequeueCompleteCallback(MessageCallback& cb); + void resetDequeueCompleteCallback(); private: + typedef std::map<const Queue*,boost::intrusive_ptr<Message> > Replacement; + + MessageAdapter& getAdapter() const; + void allEnqueuesComplete(); + void allDequeuesComplete(); + mutable sys::Mutex lock; framing::FrameSet frames; mutable boost::shared_ptr<Exchange> exchange; @@ -137,12 +175,22 @@ public: bool redelivered; bool loaded; bool staged; + bool forcePersistentPolicy; // used to force message as durable, via a broker policy ConnectionToken* publisher; mutable MessageAdapter* adapter; + qpid::sys::AbsTime expiration; + boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; static TransferAdapter TRANSFER; - MessageAdapter& getAdapter() const; + mutable Replacement replacement; + mutable boost::intrusive_ptr<Message> empty; + + sys::Mutex callbackLock; + MessageCallback* enqueueCallback; + MessageCallback* dequeueCallback; + + uint32_t requiredCredit; }; }} diff --git a/cpp/src/qpid/broker/MessageAdapter.cpp b/cpp/src/qpid/broker/MessageAdapter.cpp index 12f01494de..c0c1c4445a 100644 --- a/cpp/src/qpid/broker/MessageAdapter.cpp +++ b/cpp/src/qpid/broker/MessageAdapter.cpp @@ -19,7 +19,7 @@ * */ -#include "MessageAdapter.h" +#include "qpid/broker/MessageAdapter.h" #include "qpid/framing/DeliveryProperties.h" #include "qpid/framing/MessageProperties.h" diff --git a/cpp/src/qpid/broker/MessageBuilder.cpp b/cpp/src/qpid/broker/MessageBuilder.cpp index eda71ed3da..b1a2b77b05 100644 --- a/cpp/src/qpid/broker/MessageBuilder.cpp +++ b/cpp/src/qpid/broker/MessageBuilder.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,10 +18,11 @@ * under the License. * */ -#include "MessageBuilder.h" +#include "qpid/broker/MessageBuilder.h" -#include "Message.h" -#include "MessageStore.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/NullMessageStore.h" #include "qpid/framing/AMQFrame.h" #include "qpid/framing/reply_exceptions.h" @@ -29,11 +30,13 @@ using boost::intrusive_ptr; using namespace qpid::broker; using namespace qpid::framing; -namespace +namespace { std::string type_str(uint8_t type); + const std::string QPID_MANAGEMENT("qpid.management"); } -MessageBuilder::MessageBuilder(MessageStore* const _store, uint64_t _stagingThreshold) : + +MessageBuilder::MessageBuilder(MessageStore* const _store, uint64_t _stagingThreshold) : state(DORMANT), store(_store), stagingThreshold(_stagingThreshold), staging(false) {} void MessageBuilder::handle(AMQFrame& frame) @@ -48,14 +51,13 @@ void MessageBuilder::handle(AMQFrame& frame) if (type == CONTENT_BODY) { //TODO: rethink how to handle non-existent headers(?)... //didn't get a header: add in a dummy - AMQFrame header; - header.setBody(AMQHeaderBody()); + AMQFrame header((AMQHeaderBody())); header.setBof(false); header.setEof(false); - message->getFrames().append(header); + message->getFrames().append(header); } else if (type != HEADER_BODY) { throw CommandInvalidException( - QPID_MSG("Invalid frame sequence for message, expected header or content got " + QPID_MSG("Invalid frame sequence for message, expected header or content got " << type_str(type) << ")")); } state = CONTENT; @@ -72,8 +74,13 @@ void MessageBuilder::handle(AMQFrame& frame) } else { message->getFrames().append(frame); //have we reached the staging limit? if so stage message and release content - if (state == CONTENT && stagingThreshold && message->getFrames().getContentSize() >= stagingThreshold) { - message->releaseContent(store); + if (state == CONTENT + && stagingThreshold + && message->getFrames().getContentSize() >= stagingThreshold + && !NullMessageStore::isNullStore(store) + && message->getExchangeName() != QPID_MANAGEMENT /* don't stage mgnt messages */) + { + message->releaseContent(); staging = true; } } @@ -89,6 +96,7 @@ void MessageBuilder::end() void MessageBuilder::start(const SequenceNumber& id) { message = intrusive_ptr<Message>(new Message(id)); + message->setStore(store); state = METHOD; staging = false; } @@ -101,7 +109,7 @@ const std::string CONTENT_BODY_S = "CONTENT"; const std::string HEARTBEAT_BODY_S = "HEARTBEAT"; const std::string UNKNOWN = "unknown"; -std::string type_str(uint8_t type) +std::string type_str(uint8_t type) { switch(type) { case METHOD_BODY: return METHOD_BODY_S; @@ -117,7 +125,7 @@ std::string type_str(uint8_t type) void MessageBuilder::checkType(uint8_t expected, uint8_t actual) { if (expected != actual) { - throw CommandInvalidException(QPID_MSG("Invalid frame sequence for message (expected " + throw CommandInvalidException(QPID_MSG("Invalid frame sequence for message (expected " << type_str(expected) << " got " << type_str(actual) << ")")); } } diff --git a/cpp/src/qpid/broker/MessageBuilder.h b/cpp/src/qpid/broker/MessageBuilder.h index 395de024ab..e63c108097 100644 --- a/cpp/src/qpid/broker/MessageBuilder.h +++ b/cpp/src/qpid/broker/MessageBuilder.h @@ -21,6 +21,7 @@ #ifndef _MessageBuilder_ #define _MessageBuilder_ +#include "qpid/broker/BrokerImportExport.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/SequenceNumber.h" #include "qpid/RefCounted.h" @@ -34,10 +35,11 @@ namespace qpid { class MessageBuilder : public framing::FrameHandler{ public: - MessageBuilder(MessageStore* const store, uint64_t stagingThreshold); - void handle(framing::AMQFrame& frame); + QPID_BROKER_EXTERN MessageBuilder(MessageStore* const store, + uint64_t stagingThreshold); + QPID_BROKER_EXTERN void handle(framing::AMQFrame& frame); boost::intrusive_ptr<Message> getMessage() { return message; } - void start(const framing::SequenceNumber& id); + QPID_BROKER_EXTERN void start(const framing::SequenceNumber& id); void end(); private: enum State {DORMANT, METHOD, HEADER, CONTENT}; diff --git a/cpp/src/qpid/broker/MessageDelivery.cpp b/cpp/src/qpid/broker/MessageDelivery.cpp deleted file mode 100644 index a757d191e7..0000000000 --- a/cpp/src/qpid/broker/MessageDelivery.cpp +++ /dev/null @@ -1,89 +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 "MessageDelivery.h" - -#include "DeliveryToken.h" -#include "Message.h" -#include "Queue.h" -#include "qpid/framing/FrameHandler.h" -#include "qpid/framing/MessageTransferBody.h" - - -using namespace boost; -using namespace qpid::broker; -using namespace qpid::framing; - -namespace qpid{ -namespace broker{ - -struct BaseToken : DeliveryToken -{ - virtual ~BaseToken() {} - virtual AMQFrame sendMethod(intrusive_ptr<Message> msg, DeliveryId id) = 0; -}; - -struct MessageDeliveryToken : BaseToken -{ - const std::string destination; - const uint8_t confirmMode; - const uint8_t acquireMode; - const bool isPreview; - - MessageDeliveryToken(const std::string& d, uint8_t c, uint8_t a, bool p) : - destination(d), confirmMode(c), acquireMode(a), isPreview(p) {} - - AMQFrame sendMethod(intrusive_ptr<Message> msg, DeliveryId /*id*/) - { - //may need to set the redelivered flag: - if (msg->getRedelivered()){ - msg->getProperties<DeliveryProperties>()->setRedelivered(true); - } - return AMQFrame(in_place<MessageTransferBody>( - ProtocolVersion(), destination, confirmMode, acquireMode)); - } -}; - -} -} - -DeliveryToken::shared_ptr MessageDelivery::getMessageDeliveryToken(const std::string& destination, - uint8_t confirmMode, uint8_t acquireMode) -{ - return DeliveryToken::shared_ptr(new MessageDeliveryToken(destination, confirmMode, acquireMode, false)); -} - -void MessageDelivery::deliver(QueuedMessage& msg, - framing::FrameHandler& handler, - DeliveryId id, - DeliveryToken::shared_ptr token, - uint16_t framesize) -{ - //currently a message published from one class and delivered to - //another may well have the wrong headers; however we will only - //have one content class for 0-10 proper - - boost::shared_ptr<BaseToken> t = dynamic_pointer_cast<BaseToken>(token); - AMQFrame method = t->sendMethod(msg.payload, id); - method.setEof(false); - handler.handle(method); - msg.payload->sendHeader(handler, framesize); - msg.payload->sendContent(*(msg.queue), handler, framesize); -} diff --git a/cpp/src/qpid/broker/MessageDelivery.h b/cpp/src/qpid/broker/MessageDelivery.h deleted file mode 100644 index cfde9ee307..0000000000 --- a/cpp/src/qpid/broker/MessageDelivery.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef _broker_MessageDelivery_h -#define _broker_MessageDelivery_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 "DeliveryId.h" -#include "Consumer.h" -#include "qpid/framing/FrameHandler.h" - -namespace qpid { -namespace broker { - -class DeliveryToken; -class Message; -class Queue; - -/** - * TODO: clean this up; we don't need it anymore in its current form - * - * Encapsulates the different options for message delivery currently supported. - */ -class MessageDelivery { -public: - static boost::shared_ptr<DeliveryToken> getMessageDeliveryToken(const std::string& destination, - uint8_t confirmMode, - uint8_t acquireMode); - - static void deliver(QueuedMessage& msg, framing::FrameHandler& out, - DeliveryId deliveryTag, boost::shared_ptr<DeliveryToken> token, uint16_t framesize); -}; - -} -} - - -#endif /*!_broker_MessageDelivery_h*/ diff --git a/cpp/src/qpid/broker/MessageStore.h b/cpp/src/qpid/broker/MessageStore.h index 4c4c21dfba..143e860ec7 100644 --- a/cpp/src/qpid/broker/MessageStore.h +++ b/cpp/src/qpid/broker/MessageStore.h @@ -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 @@ -21,12 +21,12 @@ #ifndef _MessageStore_ #define _MessageStore_ -#include "PersistableExchange.h" -#include "PersistableMessage.h" -#include "PersistableQueue.h" -#include "PersistableConfig.h" -#include "RecoveryManager.h" -#include "TransactionalStore.h" +#include "qpid/broker/PersistableExchange.h" +#include "qpid/broker/PersistableMessage.h" +#include "qpid/broker/PersistableQueue.h" +#include "qpid/broker/PersistableConfig.h" +#include "qpid/broker/RecoveryManager.h" +#include "qpid/broker/TransactionalStore.h" #include "qpid/framing/FieldTable.h" #include <qpid/Options.h> @@ -46,12 +46,16 @@ class MessageStore : public TransactionalStore, public Recoverable { public: /** - * init the store, call before any other call. If not called, store - * is free to pick any defaults - * - * @param options Options object provided by concrete store plug in. + * If called after initialization but before recovery, will discard the database + * and reinitialize using an empty store dir. If the parameter 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 bool init(const Options* options) = 0; + virtual void truncateInit(const bool pushDownStoreFiles = false) = 0; /** * Record the existence of a durable queue @@ -62,7 +66,7 @@ class MessageStore : public TransactionalStore, public Recoverable { * Destroy a durable queue */ virtual void destroy(PersistableQueue& queue) = 0; - + /** * Record the existence of a durable exchange */ @@ -72,17 +76,17 @@ class MessageStore : public TransactionalStore, public Recoverable { * Destroy a durable exchange */ virtual void destroy(const PersistableExchange& exchange) = 0; - + /** * Record a binding */ - virtual void bind(const PersistableExchange& exchange, const PersistableQueue& queue, + virtual void bind(const PersistableExchange& exchange, const PersistableQueue& queue, const std::string& key, const framing::FieldTable& args) = 0; /** * Forget a binding */ - virtual void unbind(const PersistableExchange& exchange, const PersistableQueue& queue, + virtual void unbind(const PersistableExchange& exchange, const PersistableQueue& queue, const std::string& key, const framing::FieldTable& args) = 0; /** @@ -102,10 +106,10 @@ class MessageStore : public TransactionalStore, public Recoverable { * point). If the message has not yet been stored it will * store the headers as well as any content passed in. A * persistence id will be set on the message which can be - * used to load the content or to append to it. + * used to load the content or to append to it. */ virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg) = 0; - + /** * Destroys a previously staged message. This only needs * to be called if the message is never enqueued. (Once @@ -119,7 +123,7 @@ class MessageStore : public TransactionalStore, public Recoverable { */ virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, const std::string& data) = 0; - + /** * Loads (a section) of content data for the specified * message (previously stored through a call to stage or @@ -128,18 +132,18 @@ class MessageStore : public TransactionalStore, public Recoverable { * content should be loaded, not the headers or related * meta-data). */ - virtual void loadContent(const qpid::broker::PersistableQueue& queue, + virtual void loadContent(const qpid::broker::PersistableQueue& queue, const boost::intrusive_ptr<const PersistableMessage>& msg, std::string& data, uint64_t offset, uint32_t length) = 0; - + /** * Enqueues a message, storing the message if it has not * been previously stored and recording that the given - * message is on the given queue. + * message is on the given queue. * * Note: that this is async so the return of the function does * not mean the opperation is complete. - * + * * @param msg the message to enqueue * @param queue the name of the queue onto which it is to be enqueued * @param xid (a pointer to) an identifier of the @@ -149,7 +153,7 @@ class MessageStore : public TransactionalStore, public Recoverable { virtual void enqueue(TransactionContext* ctxt, const boost::intrusive_ptr<PersistableMessage>& msg, const PersistableQueue& queue) = 0; - + /** * Dequeues a message, recording that the given message is * no longer on the given queue and deleting the message @@ -157,7 +161,7 @@ class MessageStore : public TransactionalStore, public Recoverable { * * Note: that this is async so the return of the function does * not mean the opperation is complete. - * + * * @param msg the message to dequeue * @param queue the name of the queue from which it is to be dequeued * @param xid (a pointer to) an identifier of the @@ -173,22 +177,22 @@ class MessageStore : public TransactionalStore, public Recoverable { * * Note: that this is async so the return of the function does * not mean the opperation is complete. - * + * * @param queue the name of the queue from which it is to be dequeued */ virtual void flush(const qpid::broker::PersistableQueue& queue)=0; /** * Returns the number of outstanding AIO's for a given queue - * - * If 0, than all the enqueue / dequeues have been stored + * + * If 0, than all the enqueue / dequeues have been stored * to disk * * @param queue the name of the queue to check for outstanding AIO */ virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue) = 0; - + virtual ~MessageStore(){} }; diff --git a/cpp/src/qpid/broker/MessageStoreModule.cpp b/cpp/src/qpid/broker/MessageStoreModule.cpp index c9528b9d98..5f7cceebd3 100644 --- a/cpp/src/qpid/broker/MessageStoreModule.cpp +++ b/cpp/src/qpid/broker/MessageStoreModule.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 @@ -19,25 +19,33 @@ * */ -#include "MessageStoreModule.h" +#include "qpid/broker/MessageStoreModule.h" +#include "qpid/broker/NullMessageStore.h" #include <iostream> // This transfer protects against the unloading of the store lib prior to the handling of the exception #define TRANSFER_EXCEPTION(fn) try { fn; } catch (std::exception& e) { throw Exception(e.what()); } using boost::intrusive_ptr; -using namespace qpid::broker; using qpid::framing::FieldTable; -MessageStoreModule::MessageStoreModule(MessageStore* _store) : store(_store) {} +namespace qpid { +namespace broker { + +MessageStoreModule::MessageStoreModule(boost::shared_ptr<MessageStore>& _store) + : store(_store) {} MessageStoreModule::~MessageStoreModule() { - delete store; } 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)); @@ -58,13 +66,13 @@ void MessageStoreModule::destroy(const PersistableExchange& exchange) TRANSFER_EXCEPTION(store->destroy(exchange)); } -void MessageStoreModule::bind(const PersistableExchange& e, const PersistableQueue& q, +void MessageStoreModule::bind(const PersistableExchange& e, const PersistableQueue& q, const std::string& k, const framing::FieldTable& a) { TRANSFER_EXCEPTION(store->bind(e, q, k, a)); } -void MessageStoreModule::unbind(const PersistableExchange& e, const PersistableQueue& q, +void MessageStoreModule::unbind(const PersistableExchange& e, const PersistableQueue& q, const std::string& k, const framing::FieldTable& a) { TRANSFER_EXCEPTION(store->unbind(e, q, k, a)); @@ -102,7 +110,7 @@ void MessageStoreModule::appendContent(const intrusive_ptr<const PersistableMess } void MessageStoreModule::loadContent( - const qpid::broker::PersistableQueue& queue, + const qpid::broker::PersistableQueue& queue, const intrusive_ptr<const PersistableMessage>& msg, string& data, uint64_t offset, uint32_t length) { @@ -162,3 +170,10 @@ void MessageStoreModule::collectPreparedXids(std::set<std::string>& xids) { TRANSFER_EXCEPTION(store->collectPreparedXids(xids)); } + +bool MessageStoreModule::isNull() const +{ + return NullMessageStore::isNullStore(store.get()); +} + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/MessageStoreModule.h b/cpp/src/qpid/broker/MessageStoreModule.h index a16ef4de21..56b5a3c1ae 100644 --- a/cpp/src/qpid/broker/MessageStoreModule.h +++ b/cpp/src/qpid/broker/MessageStoreModule.h @@ -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 @@ -21,11 +21,12 @@ #ifndef _MessageStoreModule_ #define _MessageStoreModule_ -#include "MessageStore.h" -#include "Queue.h" -#include "RecoveryManager.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/RecoveryManager.h" #include <boost/intrusive_ptr.hpp> +#include <boost/shared_ptr.hpp> namespace qpid { namespace broker { @@ -35,11 +36,12 @@ namespace broker { */ class MessageStoreModule : public MessageStore { - MessageStore* store; + boost::shared_ptr<MessageStore> store; public: - MessageStoreModule(MessageStore* store); + 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); @@ -51,9 +53,9 @@ class MessageStoreModule : public MessageStore void destroy(PersistableQueue& queue); void create(const PersistableExchange& exchange, const framing::FieldTable& args); void destroy(const PersistableExchange& exchange); - void bind(const PersistableExchange& exchange, const PersistableQueue& queue, + void bind(const PersistableExchange& exchange, const PersistableQueue& queue, const std::string& key, const framing::FieldTable& args); - void unbind(const PersistableExchange& exchange, const PersistableQueue& queue, + void unbind(const PersistableExchange& exchange, const PersistableQueue& queue, const std::string& key, const framing::FieldTable& args); void create(const PersistableConfig& config); void destroy(const PersistableConfig& config); @@ -61,7 +63,7 @@ class MessageStoreModule : public MessageStore void stage(const boost::intrusive_ptr<PersistableMessage>& msg); void destroy(PersistableMessage& msg); void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, const std::string& data); - void loadContent(const qpid::broker::PersistableQueue& queue, + void loadContent(const qpid::broker::PersistableQueue& queue, const boost::intrusive_ptr<const PersistableMessage>& msg, std::string& data, uint64_t offset, uint32_t length); @@ -73,7 +75,8 @@ class MessageStoreModule : public MessageStore const PersistableQueue& queue); uint32_t outstandingQueueAIO(const PersistableQueue& queue); void flush(const qpid::broker::PersistableQueue& queue); - + bool isNull() const; + ~MessageStoreModule(); }; diff --git a/cpp/src/qpid/broker/NameGenerator.cpp b/cpp/src/qpid/broker/NameGenerator.cpp index 8484f921e9..e7f193d546 100644 --- a/cpp/src/qpid/broker/NameGenerator.cpp +++ b/cpp/src/qpid/broker/NameGenerator.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "NameGenerator.h" +#include "qpid/broker/NameGenerator.h" #include <sstream> using namespace qpid::broker; diff --git a/cpp/src/qpid/broker/NullMessageStore.cpp b/cpp/src/qpid/broker/NullMessageStore.cpp index e1c7fe240d..6339b655f8 100644 --- a/cpp/src/qpid/broker/NullMessageStore.cpp +++ b/cpp/src/qpid/broker/NullMessageStore.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 @@ -19,9 +19,11 @@ * */ -#include "NullMessageStore.h" -#include "RecoveryManager.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/MessageStoreModule.h" +#include "qpid/broker/RecoveryManager.h" #include "qpid/log/Statement.h" +#include "qpid/framing/reply_exceptions.h" #include <iostream> @@ -32,47 +34,41 @@ namespace broker{ const std::string nullxid = ""; -class DummyCtxt : public TPCTransactionContext +class SimpleDummyCtxt : public TransactionContext {}; + +class DummyCtxt : public TPCTransactionContext { const std::string xid; public: DummyCtxt(const std::string& _xid) : xid(_xid) {} - static std::string getXid(TransactionContext& ctxt) + static std::string getXid(TransactionContext& ctxt) { DummyCtxt* c(dynamic_cast<DummyCtxt*>(&ctxt)); return c ? c->xid : nullxid; } }; -} +NullMessageStore::NullMessageStore() : nextPersistenceId(1) { + QPID_LOG(info, "No message store configured, persistence is disabled."); } -using namespace qpid::broker; - -NullMessageStore::NullMessageStore(bool _warn) : warn(_warn), 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*/) { - QPID_LOG(info, "Queue '" << queue.getName() - << "' will not be durable. Persistence not enabled."); queue.setPersistenceId(nextPersistenceId++); } -void NullMessageStore::destroy(PersistableQueue&) -{ -} +void NullMessageStore::destroy(PersistableQueue&) {} void NullMessageStore::create(const PersistableExchange& exchange, const framing::FieldTable& /*args*/) { - QPID_LOG(info, "Exchange'" << exchange.getName() - << "' will not be durable. Persistence not enabled."); exchange.setPersistenceId(nextPersistenceId++); } -void NullMessageStore::destroy(const PersistableExchange& ) -{} +void NullMessageStore::destroy(const PersistableExchange& ) {} void NullMessageStore::bind(const PersistableExchange&, const PersistableQueue&, const std::string&, const framing::FieldTable&){} @@ -80,47 +76,31 @@ void NullMessageStore::unbind(const PersistableExchange&, const PersistableQueue void NullMessageStore::create(const PersistableConfig& config) { - QPID_LOG(info, "Persistence not enabled, configuration not stored."); config.setPersistenceId(nextPersistenceId++); } -void NullMessageStore::destroy(const PersistableConfig&) -{ - QPID_LOG(info, "Persistence not enabled, configuration not stored."); -} +void NullMessageStore::destroy(const PersistableConfig&) {} -void NullMessageStore::recover(RecoveryManager&) -{ - QPID_LOG(info, "Persistence not enabled, no recovery attempted."); -} +void NullMessageStore::recover(RecoveryManager&) {} -void NullMessageStore::stage(const intrusive_ptr<PersistableMessage>&) -{ - QPID_LOG(info, "Can't stage message. Persistence not enabled."); -} +void NullMessageStore::stage(const intrusive_ptr<PersistableMessage>&) {} -void NullMessageStore::destroy(PersistableMessage&) -{ -} +void NullMessageStore::destroy(PersistableMessage&) {} -void NullMessageStore::appendContent(const intrusive_ptr<const PersistableMessage>&, const string&) -{ - QPID_LOG(info, "Can't append content. Persistence not enabled."); -} +void NullMessageStore::appendContent(const intrusive_ptr<const PersistableMessage>&, const string&) {} void NullMessageStore::loadContent(const qpid::broker::PersistableQueue&, const intrusive_ptr<const PersistableMessage>&, string&, uint64_t, uint32_t) { - QPID_LOG(info, "Can't load content. Persistence not enabled."); + throw qpid::framing::InternalErrorException("Can't load content; persistence not enabled"); } void NullMessageStore::enqueue(TransactionContext*, const intrusive_ptr<PersistableMessage>& msg, - const PersistableQueue& queue) + const PersistableQueue&) { - msg->enqueueComplete(); - QPID_LOG(info, "Message is not durably recorded on '" << queue.getName() << "'. Persistence not enabled."); + msg->enqueueComplete(); } void NullMessageStore::dequeue(TransactionContext*, @@ -130,18 +110,15 @@ void NullMessageStore::dequeue(TransactionContext*, msg->dequeueComplete(); } -void NullMessageStore::flush(const qpid::broker::PersistableQueue&) -{ -} +void NullMessageStore::flush(const qpid::broker::PersistableQueue&) {} -uint32_t NullMessageStore::outstandingQueueAIO(const PersistableQueue& ) -{ +uint32_t NullMessageStore::outstandingQueueAIO(const PersistableQueue& ) { return 0; } std::auto_ptr<TransactionContext> NullMessageStore::begin() { - return std::auto_ptr<TransactionContext>(); + return std::auto_ptr<TransactionContext>(new SimpleDummyCtxt()); } std::auto_ptr<TPCTransactionContext> NullMessageStore::begin(const std::string& xid) @@ -168,3 +145,21 @@ void NullMessageStore::collectPreparedXids(std::set<string>& out) { out.insert(prepared.begin(), prepared.end()); } + +bool NullMessageStore::isNull() const +{ + return true; +} + +bool NullMessageStore::isNullStore(const MessageStore* store) +{ + const MessageStoreModule* wrapper = dynamic_cast<const MessageStoreModule*>(store); + if (wrapper) { + return wrapper->isNull(); + } else { + const NullMessageStore* test = dynamic_cast<const NullMessageStore*>(store); + return test && test->isNull(); + } +} + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/NullMessageStore.h b/cpp/src/qpid/broker/NullMessageStore.h index 4b8d02d555..e148ec4d51 100644 --- a/cpp/src/qpid/broker/NullMessageStore.h +++ b/cpp/src/qpid/broker/NullMessageStore.h @@ -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 @@ -22,8 +22,9 @@ #define _NullMessageStore_ #include <set> -#include "MessageStore.h" -#include "Queue.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/Queue.h" #include <boost/intrusive_ptr.hpp> @@ -36,47 +37,58 @@ namespace broker { class NullMessageStore : public MessageStore { std::set<std::string> prepared; - const bool warn; uint64_t nextPersistenceId; public: - NullMessageStore(bool warn = false); + QPID_BROKER_EXTERN NullMessageStore(); - virtual bool init(const Options* options); - virtual std::auto_ptr<TransactionContext> begin(); - virtual std::auto_ptr<TPCTransactionContext> begin(const std::string& xid); - virtual void prepare(TPCTransactionContext& txn); - virtual void commit(TransactionContext& txn); - virtual void abort(TransactionContext& txn); - virtual void collectPreparedXids(std::set<std::string>& xids); + 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); + QPID_BROKER_EXTERN virtual void commit(TransactionContext& txn); + QPID_BROKER_EXTERN virtual void abort(TransactionContext& txn); + QPID_BROKER_EXTERN virtual void collectPreparedXids(std::set<std::string>& xids); - virtual void create(PersistableQueue& queue, const framing::FieldTable& args); - virtual void destroy(PersistableQueue& queue); - virtual void create(const PersistableExchange& exchange, const framing::FieldTable& args); - virtual void destroy(const PersistableExchange& exchange); + QPID_BROKER_EXTERN virtual void create(PersistableQueue& queue, + const framing::FieldTable& args); + QPID_BROKER_EXTERN virtual void destroy(PersistableQueue& queue); + QPID_BROKER_EXTERN virtual void create(const PersistableExchange& exchange, + const framing::FieldTable& args); + QPID_BROKER_EXTERN virtual void destroy(const PersistableExchange& exchange); - virtual void bind(const PersistableExchange& exchange, const PersistableQueue& queue, - const std::string& key, const framing::FieldTable& args); - virtual void unbind(const PersistableExchange& exchange, const PersistableQueue& queue, - const std::string& key, const framing::FieldTable& args); - virtual void create(const PersistableConfig& config); - virtual void destroy(const PersistableConfig& config); - virtual void recover(RecoveryManager& queues); - virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg); - virtual void destroy(PersistableMessage& msg); - virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, - const std::string& data); - virtual void loadContent(const qpid::broker::PersistableQueue& queue, - const boost::intrusive_ptr<const PersistableMessage>& msg, std::string& data, - uint64_t offset, uint32_t length); - virtual void enqueue(TransactionContext* ctxt, - const boost::intrusive_ptr<PersistableMessage>& msg, - const PersistableQueue& queue); - virtual void dequeue(TransactionContext* ctxt, - const boost::intrusive_ptr<PersistableMessage>& msg, - const PersistableQueue& queue); - virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue); - virtual void flush(const qpid::broker::PersistableQueue& queue); + QPID_BROKER_EXTERN virtual void bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args); + QPID_BROKER_EXTERN virtual void unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args); + QPID_BROKER_EXTERN virtual void create(const PersistableConfig& config); + QPID_BROKER_EXTERN virtual void destroy(const PersistableConfig& config); + QPID_BROKER_EXTERN virtual void recover(RecoveryManager& queues); + QPID_BROKER_EXTERN virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg); + QPID_BROKER_EXTERN virtual void destroy(PersistableMessage& msg); + QPID_BROKER_EXTERN virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data); + QPID_BROKER_EXTERN virtual void loadContent(const qpid::broker::PersistableQueue& queue, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + QPID_BROKER_EXTERN virtual void enqueue(TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + QPID_BROKER_EXTERN virtual void dequeue(TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + QPID_BROKER_EXTERN virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue); + QPID_BROKER_EXTERN virtual void flush(const qpid::broker::PersistableQueue& queue); ~NullMessageStore(){} + + QPID_BROKER_EXTERN virtual bool isNull() const; + static bool isNullStore(const MessageStore*); }; } diff --git a/cpp/src/qpid/broker/PersistableConfig.h b/cpp/src/qpid/broker/PersistableConfig.h index 914e91ea80..8ddb84d129 100644 --- a/cpp/src/qpid/broker/PersistableConfig.h +++ b/cpp/src/qpid/broker/PersistableConfig.h @@ -23,7 +23,7 @@ */ #include <string> -#include "Persistable.h" +#include "qpid/broker/Persistable.h" namespace qpid { namespace broker { diff --git a/cpp/src/qpid/broker/PersistableExchange.h b/cpp/src/qpid/broker/PersistableExchange.h index 683b740ddc..e1a0853247 100644 --- a/cpp/src/qpid/broker/PersistableExchange.h +++ b/cpp/src/qpid/broker/PersistableExchange.h @@ -23,7 +23,7 @@ */ #include <string> -#include "Persistable.h" +#include "qpid/broker/Persistable.h" namespace qpid { namespace broker { diff --git a/cpp/src/qpid/broker/PersistableMessage.cpp b/cpp/src/qpid/broker/PersistableMessage.cpp index 3bf390faf3..303a0501f4 100644 --- a/cpp/src/qpid/broker/PersistableMessage.cpp +++ b/cpp/src/qpid/broker/PersistableMessage.cpp @@ -20,12 +20,25 @@ */ -#include "PersistableMessage.h" -#include "MessageStore.h" +#include "qpid/broker/PersistableMessage.h" +#include "qpid/broker/MessageStore.h" #include <iostream> using namespace qpid::broker; +namespace qpid { +namespace broker { + +class MessageStore; + +PersistableMessage::~PersistableMessage() {} + +PersistableMessage::PersistableMessage() : + asyncEnqueueCounter(0), + asyncDequeueCounter(0), + store(0) +{} + void PersistableMessage::flush() { syncList copy; @@ -45,4 +58,126 @@ void PersistableMessage::flush() } } +void PersistableMessage::setContentReleased() +{ + contentReleaseState.released = true; +} + +bool PersistableMessage::isContentReleased() const +{ + return contentReleaseState.released; +} + +bool PersistableMessage::isEnqueueComplete() { + sys::ScopedLock<sys::Mutex> l(asyncEnqueueLock); + return asyncEnqueueCounter == 0; +} + +void PersistableMessage::enqueueComplete() { + bool notify = false; + { + sys::ScopedLock<sys::Mutex> l(asyncEnqueueLock); + if (asyncEnqueueCounter > 0) { + if (--asyncEnqueueCounter == 0) { + notify = true; + } + } + } + if (notify) { + allEnqueuesComplete(); + sys::ScopedLock<sys::Mutex> l(storeLock); + if (store) { + for (syncList::iterator i = synclist.begin(); i != synclist.end(); ++i) { + PersistableQueue::shared_ptr q(i->lock()); + if (q) q->notifyDurableIOComplete(); + } + } + } +} + +bool PersistableMessage::isStoredOnQueue(PersistableQueue::shared_ptr queue){ + if (store && (queue->getPersistenceId()!=0)) { + for (syncList::iterator i = synclist.begin(); i != synclist.end(); ++i) { + PersistableQueue::shared_ptr q(i->lock()); + if (q && q->getPersistenceId() == queue->getPersistenceId()) return true; + } + } + return false; +} + + +void PersistableMessage::addToSyncList(PersistableQueue::shared_ptr queue, MessageStore* _store) { + if (_store){ + sys::ScopedLock<sys::Mutex> l(storeLock); + store = _store; + boost::weak_ptr<PersistableQueue> q(queue); + synclist.push_back(q); + } +} + +void PersistableMessage::enqueueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store) { + addToSyncList(queue, _store); + enqueueAsync(); +} + +void PersistableMessage::enqueueAsync() { + sys::ScopedLock<sys::Mutex> l(asyncEnqueueLock); + asyncEnqueueCounter++; +} + +bool PersistableMessage::isDequeueComplete() { + sys::ScopedLock<sys::Mutex> l(asyncDequeueLock); + return asyncDequeueCounter == 0; +} +void PersistableMessage::dequeueComplete() { + bool notify = false; + { + sys::ScopedLock<sys::Mutex> l(asyncDequeueLock); + if (asyncDequeueCounter > 0) { + if (--asyncDequeueCounter == 0) { + notify = true; + } + } + } + if (notify) allDequeuesComplete(); +} + +void PersistableMessage::dequeueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store) { + if (_store){ + sys::ScopedLock<sys::Mutex> l(storeLock); + store = _store; + boost::weak_ptr<PersistableQueue> q(queue); + synclist.push_back(q); + } + dequeueAsync(); +} + +void PersistableMessage::dequeueAsync() { + sys::ScopedLock<sys::Mutex> l(asyncDequeueLock); + asyncDequeueCounter++; +} + +PersistableMessage::ContentReleaseState::ContentReleaseState() : blocked(false), requested(false), released(false) {} + +void PersistableMessage::setStore(MessageStore* s) +{ + store = s; +} + +void PersistableMessage::requestContentRelease() +{ + contentReleaseState.requested = true; +} +void PersistableMessage::blockContentRelease() +{ + contentReleaseState.blocked = true; +} +bool PersistableMessage::checkContentReleasable() +{ + return contentReleaseState.requested && !contentReleaseState.blocked; +} + +}} + + diff --git a/cpp/src/qpid/broker/PersistableMessage.h b/cpp/src/qpid/broker/PersistableMessage.h index 7ed54c0ff0..7d49491dfd 100644 --- a/cpp/src/qpid/broker/PersistableMessage.h +++ b/cpp/src/qpid/broker/PersistableMessage.h @@ -26,10 +26,11 @@ #include <list> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> -#include "Persistable.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Persistable.h" #include "qpid/framing/amqp_types.h" -#include "qpid/sys/Monitor.h" -#include "PersistableQueue.h" +#include "qpid/sys/Mutex.h" +#include "qpid/broker/PersistableQueue.h" namespace qpid { namespace broker { @@ -41,10 +42,11 @@ class MessageStore; */ class PersistableMessage : public Persistable { - sys::Monitor asyncEnqueueLock; - sys::Monitor asyncDequeueLock; + typedef std::list< boost::weak_ptr<PersistableQueue> > syncList; + sys::Mutex asyncEnqueueLock; + sys::Mutex asyncDequeueLock; sys::Mutex storeLock; - + /** * Tracks the number of outstanding asynchronous enqueue * operations. When the message is enqueued asynchronously the @@ -62,15 +64,33 @@ class PersistableMessage : public Persistable * dequeues. */ int asyncDequeueCounter; -protected: - typedef std::list< boost::weak_ptr<PersistableQueue> > syncList; + + void enqueueAsync(); + void dequeueAsync(); + syncList synclist; + struct ContentReleaseState + { + bool blocked; + bool requested; + bool released; + + ContentReleaseState(); + }; + ContentReleaseState contentReleaseState; + + protected: + /** Called when all enqueues are complete for this message. */ + virtual void allEnqueuesComplete() = 0; + /** Called when all dequeues are complete for this message. */ + virtual void allDequeuesComplete() = 0; + + void setContentReleased(); + MessageStore* store; - bool contentReleased; - - inline void setContentReleased() {contentReleased = true; } -public: + + public: typedef boost::shared_ptr<PersistableMessage> shared_ptr; /** @@ -78,105 +98,39 @@ public: */ virtual uint32_t encodedHeaderSize() const = 0; - virtual ~PersistableMessage() {}; + virtual ~PersistableMessage(); - PersistableMessage(): - asyncEnqueueCounter(0), - asyncDequeueCounter(0), - store(0), - contentReleased(false) - {} + PersistableMessage(); void flush(); - inline bool isContentReleased()const {return contentReleased; } - - inline void waitForEnqueueComplete() { - sys::ScopedLock<sys::Monitor> l(asyncEnqueueLock); - while (asyncEnqueueCounter > 0) { - asyncEnqueueLock.wait(); - } - } - - inline bool isEnqueueComplete() { - sys::ScopedLock<sys::Monitor> l(asyncEnqueueLock); - return asyncEnqueueCounter == 0; - } - - inline void enqueueComplete() { - bool notify = false; - { - sys::ScopedLock<sys::Monitor> l(asyncEnqueueLock); - if (asyncEnqueueCounter > 0) { - if (--asyncEnqueueCounter == 0) { - asyncEnqueueLock.notify(); - notify = true; - } - } - } - if (notify) { - sys::ScopedLock<sys::Mutex> l(storeLock); - if (store) { - for (syncList::iterator i = synclist.begin(); i != synclist.end(); ++i) { - PersistableQueue::shared_ptr q(i->lock()); - if (q) q->notifyDurableIOComplete(); - } - } - } - } - - inline void enqueueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store) { - if (_store){ - sys::ScopedLock<sys::Mutex> l(storeLock); - store = _store; - boost::weak_ptr<PersistableQueue> q(queue); - synclist.push_back(q); - } - enqueueAsync(); - } - - inline void enqueueAsync() { - sys::ScopedLock<sys::Monitor> l(asyncEnqueueLock); - asyncEnqueueCounter++; - } - - inline bool isDequeueComplete() { - sys::ScopedLock<sys::Monitor> l(asyncDequeueLock); - return asyncDequeueCounter == 0; - } + QPID_BROKER_EXTERN bool isContentReleased() const; + + QPID_BROKER_EXTERN void setStore(MessageStore*); + void requestContentRelease(); + void blockContentRelease(); + bool checkContentReleasable(); + + virtual QPID_BROKER_EXTERN bool isPersistent() const = 0; + + QPID_BROKER_EXTERN bool isEnqueueComplete(); + + QPID_BROKER_EXTERN void enqueueComplete(); + + QPID_BROKER_EXTERN void enqueueAsync(PersistableQueue::shared_ptr queue, + MessageStore* _store); + + + QPID_BROKER_EXTERN bool isDequeueComplete(); - inline void dequeueComplete() { - - sys::ScopedLock<sys::Monitor> l(asyncDequeueLock); - if (asyncDequeueCounter > 0) { - if (--asyncDequeueCounter == 0) { - asyncDequeueLock.notify(); - } - } - } - - inline void waitForDequeueComplete() { - sys::ScopedLock<sys::Monitor> l(asyncDequeueLock); - while (asyncDequeueCounter > 0) { - asyncDequeueLock.wait(); - } - } - - inline void dequeueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store) { - if (_store){ - sys::ScopedLock<sys::Mutex> l(storeLock); - store = _store; - boost::weak_ptr<PersistableQueue> q(queue); - synclist.push_back(q); - } - dequeueAsync(); - } - - inline void dequeueAsync() { - sys::ScopedLock<sys::Monitor> l(asyncDequeueLock); - asyncDequeueCounter++; - } + QPID_BROKER_EXTERN void dequeueComplete(); + QPID_BROKER_EXTERN void dequeueAsync(PersistableQueue::shared_ptr queue, + MessageStore* _store); + + bool isStoredOnQueue(PersistableQueue::shared_ptr queue); + + void addToSyncList(PersistableQueue::shared_ptr queue, MessageStore* _store); }; diff --git a/cpp/src/qpid/broker/PersistableQueue.h b/cpp/src/qpid/broker/PersistableQueue.h index 9236814ae3..8d85d36fef 100644 --- a/cpp/src/qpid/broker/PersistableQueue.h +++ b/cpp/src/qpid/broker/PersistableQueue.h @@ -23,7 +23,7 @@ */ #include <string> -#include "Persistable.h" +#include "qpid/broker/Persistable.h" #include "qpid/management/Manageable.h" #include <boost/shared_ptr.hpp> diff --git a/cpp/src/qpid/broker/Queue.cpp b/cpp/src/qpid/broker/Queue.cpp index 40dfb80da2..f4231f2397 100644 --- a/cpp/src/qpid/broker/Queue.cpp +++ b/cpp/src/qpid/broker/Queue.cpp @@ -19,19 +19,23 @@ * */ -#include "Broker.h" -#include "Queue.h" -#include "Exchange.h" -#include "DeliverableMessage.h" -#include "MessageStore.h" -#include "QueueRegistry.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/QueueRegistry.h" #include "qpid/StringUtils.h" #include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/FieldTable.h" #include "qpid/sys/Monitor.h" #include "qpid/sys/Time.h" -#include "qpid/management/ArgsQueuePurge.h" +#include "qmf/org/apache/qpid/broker/ArgsQueuePurge.h" #include <iostream> #include <algorithm> @@ -49,11 +53,34 @@ using qpid::management::Manageable; using qpid::management::Args; using std::for_each; using std::mem_fun; +namespace _qmf = qmf::org::apache::qpid::broker; + + +namespace +{ +const std::string qpidMaxSize("qpid.max_size"); +const std::string qpidMaxCount("qpid.max_count"); +const std::string qpidNoLocal("no-local"); +const std::string qpidTraceIdentity("qpid.trace.id"); +const std::string qpidTraceExclude("qpid.trace.exclude"); +const std::string qpidLastValueQueue("qpid.last_value_queue"); +const std::string qpidLastValueQueueNoBrowse("qpid.last_value_queue_no_browse"); +const std::string qpidPersistLastNode("qpid.persist_last_node"); +const std::string qpidVQMatchProperty("qpid.LVQ_key"); +const std::string qpidQueueEventGeneration("qpid.queue_event_generation"); +//following feature is not ready for general use as it doesn't handle +//the case where a message is enqueued on more than one queue well enough: +const std::string qpidInsertSequenceNumbers("qpid.insert_sequence_numbers"); + +const int ENQUEUE_ONLY=1; +const int ENQUEUE_AND_DEQUEUE=2; +} Queue::Queue(const string& _name, bool _autodelete, MessageStore* const _store, const OwnershipToken* const _owner, - Manageable* parent) : + Manageable* parent, + Broker* b) : name(_name), autodelete(_autodelete), @@ -62,22 +89,31 @@ Queue::Queue(const string& _name, bool _autodelete, consumerCount(0), exclusive(0), noLocal(false), + lastValueQueue(false), + lastValueQueueNoBrowse(false), + persistLastNode(false), + inLastNodeFailure(false), persistenceId(0), policyExceeded(false), - mgmtObject(0) + mgmtObject(0), + eventMode(0), + eventMgr(0), + insertSeqNo(0), + broker(b) { - if (parent != 0) + if (parent != 0 && broker != 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - mgmtObject = new management::Queue (agent, this, parent, _name, _store != 0, _autodelete, _owner != 0); + mgmtObject = new _qmf::Queue(agent, this, parent, _name, _store != 0, _autodelete, _owner != 0); // Add the object to the management agent only if this queue is not durable. // If it's durable, we will add it later when the queue is assigned a persistenceId. - if (store == 0) - agent->addObject (mgmtObject); + if (store == 0) { + agent->addObject (mgmtObject, agent->allocateId(this)); + } } } } @@ -90,8 +126,12 @@ Queue::~Queue() void Queue::notifyDurableIOComplete() { - Mutex::ScopedLock locker(messageLock); - notify(); + QueueListeners::NotificationSet copy; + { + Mutex::ScopedLock locker(messageLock); + listeners.populate(copy); + } + copy.notify(); } bool isLocalTo(const OwnershipToken* token, boost::intrusive_ptr<Message>& msg) @@ -129,35 +169,31 @@ void Queue::deliver(boost::intrusive_ptr<Message>& msg){ } else { // if no store then mark as enqueued if (!enqueue(0, msg)){ - if (mgmtObject != 0) { - mgmtObject->inc_msgTotalEnqueues (); - mgmtObject->inc_byteTotalEnqueues (msg->contentSize ()); - } push(msg); msg->enqueueComplete(); }else { - if (mgmtObject != 0) { - mgmtObject->inc_msgTotalEnqueues (); - mgmtObject->inc_byteTotalEnqueues (msg->contentSize ()); - mgmtObject->inc_msgPersistEnqueues (); - mgmtObject->inc_bytePersistEnqueues (msg->contentSize ()); - } push(msg); } + mgntEnqStats(msg); QPID_LOG(debug, "Message " << msg << " enqueued on " << name << "[" << this << "]"); } } +void Queue::recoverPrepared(boost::intrusive_ptr<Message>& msg) +{ + if (policy.get()) policy->recoverEnqueued(msg); +} void Queue::recover(boost::intrusive_ptr<Message>& msg){ - push(msg); - msg->enqueueComplete(); // mark the message as enqueued - if (mgmtObject != 0) { - mgmtObject->inc_msgTotalEnqueues (); - mgmtObject->inc_byteTotalEnqueues (msg->contentSize ()); - mgmtObject->inc_msgPersistEnqueues (); - mgmtObject->inc_bytePersistEnqueues (msg->contentSize ()); + if (policy.get()) policy->recoverEnqueued(msg); + + push(msg, true); + if (store){ + // setup synclist for recovered messages, so they don't get re-stored on lastNodeFailure + msg->addToSyncList(shared_from_this(), store); } + msg->enqueueComplete(); // mark the message as enqueued + mgntEnqStats(msg); if (store && !msg->isContentLoaded()) { //content has not been loaded, need to ensure that lazy loading mode is set: @@ -168,121 +204,188 @@ void Queue::recover(boost::intrusive_ptr<Message>& msg){ void Queue::process(boost::intrusive_ptr<Message>& msg){ push(msg); - if (mgmtObject != 0) { - mgmtObject->inc_msgTotalEnqueues (); - mgmtObject->inc_byteTotalEnqueues (msg->contentSize ()); + mgntEnqStats(msg); + if (mgmtObject != 0){ mgmtObject->inc_msgTxnEnqueues (); mgmtObject->inc_byteTxnEnqueues (msg->contentSize ()); - if (msg->isPersistent ()) { - mgmtObject->inc_msgPersistEnqueues (); - mgmtObject->inc_bytePersistEnqueues (msg->contentSize ()); - } } } void Queue::requeue(const QueuedMessage& msg){ + QueueListeners::NotificationSet copy; + { + Mutex::ScopedLock locker(messageLock); + if (!isEnqueued(msg)) return; + msg.payload->enqueueComplete(); // mark the message as enqueued + messages.insert(lower_bound(messages.begin(), messages.end(), msg), msg); + listeners.populate(copy); + + // for persistLastNode - don't force a message twice to disk, but force it if no force before + if(inLastNodeFailure && persistLastNode && !msg.payload->isStoredOnQueue(shared_from_this())) { + msg.payload->forcePersistent(); + if (msg.payload->isForcedPersistent() ){ + enqueue(0, msg.payload); + } + } + } + copy.notify(); +} + +void Queue::clearLVQIndex(const QueuedMessage& msg){ + const framing::FieldTable* ft = msg.payload ? msg.payload->getApplicationHeaders() : 0; + if (lastValueQueue && ft){ + string key = ft->getAsString(qpidVQMatchProperty); + lvq.erase(key); + } +} + +bool Queue::acquireMessageAt(const SequenceNumber& position, QueuedMessage& message) +{ Mutex::ScopedLock locker(messageLock); - msg.payload->enqueueComplete(); // mark the message as enqueued - messages.push_front(msg); - notify(); + QPID_LOG(debug, "Attempting to acquire message at " << position); + + Messages::iterator i = findAt(position); + if (i != messages.end() ) { + message = *i; + if (lastValueQueue) { + clearLVQIndex(*i); + } + QPID_LOG(debug, + "Acquired message at " << i->position << " from " << name); + messages.erase(i); + return true; + } + QPID_LOG(debug, "Could not acquire message at " << position << " from " << name << "; no message at that position"); + return false; } bool Queue::acquire(const QueuedMessage& msg) { Mutex::ScopedLock locker(messageLock); QPID_LOG(debug, "attempting to acquire " << msg.position); - for (Messages::iterator i = messages.begin(); i != messages.end(); i++) { - if (i->position == msg.position) { - messages.erase(i); - QPID_LOG(debug, "Match found, acquire succeeded: " << i->position << " == " << msg.position); - return true; - } else { - QPID_LOG(debug, "No match: " << i->position << " != " << msg.position); - } + Messages::iterator i = findAt(msg.position); + if ((i != messages.end() && !lastValueQueue) // note that in some cases payload not be set + || (lastValueQueue && (i->position == msg.position) && + msg.payload.get() == checkLvqReplace(*i).payload.get()) ) { + + clearLVQIndex(msg); + QPID_LOG(debug, + "Match found, acquire succeeded: " << + i->position << " == " << msg.position); + messages.erase(i); + return true; + } else { + QPID_LOG(debug, "No match: " << i->position << " != " << msg.position); } + QPID_LOG(debug, "Acquire failed for " << msg.position); return false; } -bool Queue::getNextMessage(QueuedMessage& m, Consumer& c) +void Queue::notifyListener() { - if (c.preAcquires()) { - return consumeNextMessage(m, c); + QueueListeners::NotificationSet set; + { + Mutex::ScopedLock locker(messageLock); + if (messages.size()) { + listeners.populate(set); + } + } + set.notify(); +} + +bool Queue::getNextMessage(QueuedMessage& m, Consumer::shared_ptr c) +{ + if (c->preAcquires()) { + switch (consumeNextMessage(m, c)) { + case CONSUMED: + return true; + case CANT_CONSUME: + notifyListener();//let someone else try + case NO_MESSAGES: + default: + return false; + } } else { return browseNextMessage(m, c); } } -bool Queue::checkForMessages(Consumer& c) +bool Queue::checkForMessages(Consumer::shared_ptr c) { Mutex::ScopedLock locker(messageLock); if (messages.empty()) { //no message available, register consumer for notification //when this changes - addListener(c); + listeners.addListener(c); return false; } else { - QueuedMessage msg = messages.front(); + QueuedMessage msg = getFront(); if (store && !msg.payload->isEnqueueComplete()) { //though a message is on the queue, it has not yet been //enqueued and so is not available for consumption yet, //register consumer for notification when this changes - addListener(c); + listeners.addListener(c); return false; } else { //check that consumer has sufficient credit for the //message (if it does not, no need to register it for //notification as the consumer itself will handle the //credit allocation required to change this condition). - return c.accept(msg.payload); + return c->accept(msg.payload); } } } -bool Queue::consumeNextMessage(QueuedMessage& m, Consumer& c) +Queue::ConsumeCode Queue::consumeNextMessage(QueuedMessage& m, Consumer::shared_ptr c) { while (true) { Mutex::ScopedLock locker(messageLock); if (messages.empty()) { QPID_LOG(debug, "No messages to dispatch on queue '" << name << "'"); - addListener(c); - return false; + listeners.addListener(c); + return NO_MESSAGES; } else { - QueuedMessage msg = messages.front(); - if (store && !msg.payload->isEnqueueComplete()) { - QPID_LOG(debug, "Messages not ready to dispatch on queue '" << name << "'"); - addListener(c); - return false; + QueuedMessage msg = getFront(); + if (msg.payload->hasExpired()) { + QPID_LOG(debug, "Message expired from queue '" << name << "'"); + popAndDequeue(); + continue; } - - if (c.filter(msg.payload)) { - if (c.accept(msg.payload)) { + + if (c->filter(msg.payload)) { + if (c->accept(msg.payload)) { m = msg; - messages.pop_front(); - return true; + popMsg(msg); + return CONSUMED; } else { //message(s) are available but consumer hasn't got enough credit QPID_LOG(debug, "Consumer can't currently accept message from '" << name << "'"); - return false; + return CANT_CONSUME; } } else { //consumer will never want this message QPID_LOG(debug, "Consumer doesn't want message from '" << name << "'"); - return false; + return CANT_CONSUME; } } } } -bool Queue::browseNextMessage(QueuedMessage& m, Consumer& c) +bool Queue::browseNextMessage(QueuedMessage& m, Consumer::shared_ptr c) { QueuedMessage msg(this); while (seek(msg, c)) { - if (c.filter(msg.payload)) { - if (c.accept(msg.payload)) { + if (c->filter(msg.payload) && !msg.payload->hasExpired()) { + if (c->accept(msg.payload)) { //consumer wants the message - c.position = msg.position; + c->position = msg.position; m = msg; + if (!lastValueQueueNoBrowse) clearLVQIndex(msg); + if (lastValueQueue) { + boost::intrusive_ptr<Message> replacement = msg.payload->getReplacementMessage(this); + if (replacement.get()) m.payload = replacement; + } return true; } else { //browser hasn't got enough credit for the message @@ -291,70 +394,89 @@ bool Queue::browseNextMessage(QueuedMessage& m, Consumer& c) } } else { //consumer will never want this message, continue seeking - c.position = msg.position; + c->position = msg.position; QPID_LOG(debug, "Browser skipping message from '" << name << "'"); } } return false; } -/** - * notify listeners that there may be messages to process - */ -void Queue::notify() -{ - if (listeners.empty()) return; - - Listeners copy(listeners); - listeners.clear(); - for_each(copy.begin(), copy.end(), mem_fun(&Consumer::notify)); -} - -void Queue::removeListener(Consumer& c) -{ - Mutex::ScopedLock locker(messageLock); - Listeners::iterator i = std::find(listeners.begin(), listeners.end(), &c); - if (i != listeners.end()) listeners.erase(i); -} - -void Queue::addListener(Consumer& c) +void Queue::removeListener(Consumer::shared_ptr c) { - Listeners::iterator i = std::find(listeners.begin(), listeners.end(), &c); - if (i == listeners.end()) listeners.push_back(&c); + QueueListeners::NotificationSet set; + { + Mutex::ScopedLock locker(messageLock); + listeners.removeListener(c); + if (messages.size()) { + listeners.populate(set); + } + } + set.notify(); } -bool Queue::dispatch(Consumer& c) +bool Queue::dispatch(Consumer::shared_ptr c) { QueuedMessage msg(this); if (getNextMessage(msg, c)) { - c.deliver(msg); + c->deliver(msg); return true; } else { return false; } } -bool Queue::seek(QueuedMessage& msg, Consumer& c) { +// Find the next message +bool Queue::seek(QueuedMessage& msg, Consumer::shared_ptr c) { Mutex::ScopedLock locker(messageLock); - if (!messages.empty() && messages.back().position > c.position) { - if (c.position < messages.front().position) { - msg = messages.front(); + if (!messages.empty() && messages.back().position > c->position) { + if (c->position < getFront().position) { + msg = getFront(); return true; } else { - //TODO: can improve performance of this search, for now just searching linearly from end - Messages::reverse_iterator pos; - for (Messages::reverse_iterator i = messages.rbegin(); i != messages.rend() && i->position > c.position; i++) { - pos = i; + Messages::iterator pos = findAt(c->position); + if (pos != messages.end() && pos+1 != messages.end()) { + msg = *(pos+1); + return true; } - msg = *pos; - return true; } } - addListener(c); + listeners.addListener(c); return false; } -void Queue::consume(Consumer& c, bool requestExclusive){ +Queue::Messages::iterator Queue::findAt(SequenceNumber pos) { + + if(!messages.empty()){ + QueuedMessage compM; + compM.position = pos; + unsigned long diff = pos.getValue() - messages.front().position.getValue(); + long maxEnd = diff < messages.size()? diff : messages.size(); + + Messages::iterator i = lower_bound(messages.begin(),messages.begin()+maxEnd,compM); + if (i!= messages.end() && i->position == pos) + return i; + } + return messages.end(); // no match found. +} + + +QueuedMessage Queue::find(SequenceNumber pos) const { + + Mutex::ScopedLock locker(messageLock); + if(!messages.empty()){ + QueuedMessage compM; + compM.position = pos; + unsigned long diff = pos.getValue() - messages.front().position.getValue(); + long maxEnd = diff < messages.size()? diff : messages.size(); + + Messages::const_iterator i = lower_bound(messages.begin(),messages.begin()+maxEnd,compM); + if (i != messages.end()) + return *i; + } + return QueuedMessage(); +} + +void Queue::consume(Consumer::shared_ptr c, bool requestExclusive){ Mutex::ScopedLock locker(consumerLock); if(exclusive) { throw ResourceLockedException( @@ -364,7 +486,7 @@ void Queue::consume(Consumer& c, bool requestExclusive){ throw ResourceLockedException( QPID_MSG("Queue " << getName() << " already has consumers. Exclusive access denied.")); } else { - exclusive = c.getSession(); + exclusive = c->getSession(); } } consumerCount++; @@ -372,7 +494,7 @@ void Queue::consume(Consumer& c, bool requestExclusive){ mgmtObject->inc_consumerCount (); } -void Queue::cancel(Consumer& c){ +void Queue::cancel(Consumer::shared_ptr c){ removeListener(c); Mutex::ScopedLock locker(consumerLock); consumerCount--; @@ -386,12 +508,35 @@ QueuedMessage Queue::get(){ QueuedMessage msg(this); if(!messages.empty()){ - msg = messages.front(); - messages.pop_front(); + msg = getFront(); + popMsg(msg); } return msg; } +void Queue::purgeExpired() +{ + //As expired messages are discarded during dequeue also, only + //bother explicitly expiring if the rate of dequeues since last + //attempt is less than one per second. + if (dequeueTracker.sampleRatePerSecond() < 1) { + Messages expired; + { + Mutex::ScopedLock locker(messageLock); + for (Messages::iterator i = messages.begin(); i != messages.end();) { + if (lastValueQueue) checkLvqReplace(*i); + if (i->payload->hasExpired()) { + expired.push_back(*i); + i = messages.erase(i); + } else { + ++i; + } + } + } + for_each(expired.begin(), expired.end(), bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); + } +} + /** * purge - for purging all or some messages on a queue * depending on the purge_request @@ -406,106 +551,252 @@ uint32_t Queue::purge(const uint32_t purge_request){ uint32_t count = 0; // Either purge them all or just the some (purge_count) while the queue isn't empty. - while((!purge_request || purge_count--) && !messages.empty()) - { + while((!purge_request || purge_count--) && !messages.empty()) { popAndDequeue(); - count++; + count++; } return count; } -void Queue::push(boost::intrusive_ptr<Message>& msg){ - Mutex::ScopedLock locker(messageLock); - messages.push_back(QueuedMessage(this, msg, ++sequence)); - if (policy.get()) { - policy->enqueued(msg->contentSize()); - if (policy->limitExceeded()) { - if (!policyExceeded) { - policyExceeded = true; - QPID_LOG(info, "Queue size exceeded policy for " << name); - } - if (store) { - QPID_LOG(debug, "Message " << msg << " on " << name << " released from memory"); - msg->releaseContent(store); - } else { - QPID_LOG(error, "Message " << msg << " on " << name - << " exceeds the policy for the queue but can't be released from memory as the queue is not durable"); - throw ResourceLimitExceededException(QPID_MSG("Policy exceeded for " << name << " " << *policy)); - } - } else { - if (policyExceeded) { - policyExceeded = false; - QPID_LOG(info, "Queue size within policy for " << name); - } +uint32_t Queue::move(const Queue::shared_ptr destq, uint32_t qty) { + Mutex::ScopedLock locker(messageLock); + uint32_t move_count = qty; // only comes into play if qty >0 + uint32_t count = 0; // count how many were moved for returning + + while((!qty || move_count--) && !messages.empty()) { + QueuedMessage qmsg = getFront(); + boost::intrusive_ptr<Message> msg = qmsg.payload; + destq->deliver(msg); // deliver message to the destination queue + popMsg(qmsg); + dequeue(0, qmsg); + count++; + } + return count; +} + +void Queue::popMsg(QueuedMessage& qmsg) +{ + const framing::FieldTable* ft = qmsg.payload->getApplicationHeaders(); + if (lastValueQueue && ft){ + string key = ft->getAsString(qpidVQMatchProperty); + lvq.erase(key); + } + messages.pop_front(); + ++dequeueTracker; +} + +void Queue::push(boost::intrusive_ptr<Message>& msg, bool isRecovery){ + QueueListeners::NotificationSet copy; + { + Mutex::ScopedLock locker(messageLock); + QueuedMessage qm(this, msg, ++sequence); + if (insertSeqNo) msg->getOrInsertHeaders().setInt64(seqNoKey, sequence); + + LVQ::iterator i; + const framing::FieldTable* ft = msg->getApplicationHeaders(); + if (lastValueQueue && ft){ + string key = ft->getAsString(qpidVQMatchProperty); + + i = lvq.find(key); + if (i == lvq.end() || (broker && broker->isClusterUpdatee())) { + messages.push_back(qm); + listeners.populate(copy); + lvq[key] = msg; + }else { + boost::intrusive_ptr<Message> old = i->second->getReplacementMessage(this); + if (!old) old = i->second; + i->second->setReplacementMessage(msg,this); + if (isRecovery) { + //can't issue new requests for the store until + //recovery is complete + pendingDequeues.push_back(QueuedMessage(qm.queue, old, qm.position)); + } else { + Mutex::ScopedUnlock u(messageLock); + dequeue(0, QueuedMessage(qm.queue, old, qm.position)); + } + } + }else { + messages.push_back(qm); + listeners.populate(copy); + } + if (eventMode) { + if (eventMgr) eventMgr->enqueued(qm); + else QPID_LOG(warning, "Enqueue manager not set, events not generated for " << getName()); + } + if (policy.get()) { + policy->enqueued(qm); } } - notify(); + copy.notify(); +} + +QueuedMessage Queue::getFront() +{ + QueuedMessage msg = messages.front(); + if (lastValueQueue) { + boost::intrusive_ptr<Message> replacement = msg.payload->getReplacementMessage(this); + if (replacement.get()) msg.payload = replacement; + } + return msg; +} + +QueuedMessage& Queue::checkLvqReplace(QueuedMessage& msg) +{ + boost::intrusive_ptr<Message> replacement = msg.payload->getReplacementMessage(this); + if (replacement.get()) { + const framing::FieldTable* ft = replacement->getApplicationHeaders(); + if (ft) { + string key = ft->getAsString(qpidVQMatchProperty); + if (lvq.find(key) != lvq.end()){ + lvq[key] = replacement; + } + } + msg.payload = replacement; + } + return msg; } /** function only provided for unit tests, or code not in critical message path */ -uint32_t Queue::getMessageCount() const{ +uint32_t Queue::getEnqueueCompleteMessageCount() const +{ Mutex::ScopedLock locker(messageLock); - - uint32_t count =0; + uint32_t count = 0; for ( Messages::const_iterator i = messages.begin(); i != messages.end(); ++i ) { + //NOTE: don't need to use checkLvqReplace() here as it + //is only relevant for LVQ which does not support persistence + //so the enqueueComplete check has no effect if ( i->payload->isEnqueueComplete() ) count ++; } return count; } -uint32_t Queue::getConsumerCount() const{ +uint32_t Queue::getMessageCount() const +{ + Mutex::ScopedLock locker(messageLock); + return messages.size(); +} + +uint32_t Queue::getConsumerCount() const +{ Mutex::ScopedLock locker(consumerLock); return consumerCount; } -bool Queue::canAutoDelete() const{ +bool Queue::canAutoDelete() const +{ Mutex::ScopedLock locker(consumerLock); return autodelete && !consumerCount; } +void Queue::clearLastNodeFailure() +{ + inLastNodeFailure = false; +} + +void Queue::setLastNodeFailure() +{ + if (persistLastNode){ + Mutex::ScopedLock locker(messageLock); + try { + for ( Messages::iterator i = messages.begin(); i != messages.end(); ++i ) { + if (lastValueQueue) checkLvqReplace(*i); + // don't force a message twice to disk. + if(!i->payload->isStoredOnQueue(shared_from_this())) { + i->payload->forcePersistent(); + if (i->payload->isForcedPersistent() ){ + enqueue(0, i->payload); + } + } + } + } catch (const std::exception& e) { + // Could not go into last node standing (for example journal not large enough) + QPID_LOG(error, "Unable to fail to last node standing for queue: " << name << " : " << e.what()); + } + inLastNodeFailure = true; + } +} + // return true if store exists, -bool Queue::enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message> msg) +bool Queue::enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message> msg, bool suppressPolicyCheck) { + if (policy.get() && !suppressPolicyCheck) { + Messages dequeues; + { + Mutex::ScopedLock locker(messageLock); + policy->tryEnqueue(msg); + policy->getPendingDequeues(dequeues); + } + //depending on policy, may have some dequeues that need to performed without holding the lock + for_each(dequeues.begin(), dequeues.end(), boost::bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); + } + + if (inLastNodeFailure && persistLastNode){ + msg->forcePersistent(); + } + if (traceId.size()) { msg->addTraceId(traceId); } - if (msg->isPersistent() && store) { + if ((msg->isPersistent() || msg->checkContentReleasable()) && store) { msg->enqueueAsync(shared_from_this(), store); //increment to async counter -- for message sent to more than one queue boost::intrusive_ptr<PersistableMessage> pmsg = boost::static_pointer_cast<PersistableMessage>(msg); store->enqueue(ctxt, pmsg, *this); return true; } - //msg->enqueueAsync(); // increments intrusive ptr cnt + if (!store) { + //Messages enqueued on a transient queue should be prevented + //from having their content released as it may not be + //recoverable by these queue for delivery + msg->blockContentRelease(); + } return false; } +void Queue::enqueueAborted(boost::intrusive_ptr<Message> msg) +{ + Mutex::ScopedLock locker(messageLock); + if (policy.get()) policy->enqueueAborted(msg); +} + // return true if store exists, -bool Queue::dequeue(TransactionContext* ctxt, boost::intrusive_ptr<Message> msg) +bool Queue::dequeue(TransactionContext* ctxt, const QueuedMessage& msg) { { Mutex::ScopedLock locker(messageLock); - dequeued(msg); + if (!isEnqueued(msg)) return false; + if (!ctxt) { + dequeued(msg); + } } - if (msg->isPersistent() && store) { - msg->dequeueAsync(shared_from_this(), store); //increment to async counter -- for message sent to more than one queue - boost::intrusive_ptr<PersistableMessage> pmsg = boost::static_pointer_cast<PersistableMessage>(msg); + if ((msg.payload->isPersistent() || msg.payload->checkContentReleasable()) && store) { + msg.payload->dequeueAsync(shared_from_this(), store); //increment to async counter -- for message sent to more than one queue + boost::intrusive_ptr<PersistableMessage> pmsg = boost::static_pointer_cast<PersistableMessage>(msg.payload); store->dequeue(ctxt, pmsg, *this); return true; } - //msg->dequeueAsync(); // decrements intrusive ptr cnt return false; } +void Queue::dequeueCommitted(const QueuedMessage& msg) +{ + Mutex::ScopedLock locker(messageLock); + dequeued(msg); + if (mgmtObject != 0) { + mgmtObject->inc_msgTxnDequeues(); + mgmtObject->inc_byteTxnDequeues(msg.payload->contentSize()); + } +} + /** * Removes a message from the in-memory delivery queue as well * dequeing it from the logical (and persistent if applicable) queue */ void Queue::popAndDequeue() { - boost::intrusive_ptr<Message> msg = messages.front().payload; - messages.pop_front(); + QueuedMessage msg = getFront(); + popMsg(msg); dequeue(0, msg); } @@ -513,29 +804,16 @@ void Queue::popAndDequeue() * Updates policy and management when a message has been dequeued, * expects messageLock to be held */ -void Queue::dequeued(boost::intrusive_ptr<Message>& msg) +void Queue::dequeued(const QueuedMessage& msg) { - if (policy.get()) policy->dequeued(msg->contentSize()); - if (mgmtObject != 0){ - mgmtObject->inc_msgTotalDequeues (); - mgmtObject->inc_byteTotalDequeues (msg->contentSize()); - if (msg->isPersistent ()){ - mgmtObject->inc_msgPersistDequeues (); - mgmtObject->inc_bytePersistDequeues (msg->contentSize()); - } + if (policy.get()) policy->dequeued(msg); + mgntDeqStats(msg.payload); + if (eventMode == ENQUEUE_AND_DEQUEUE && eventMgr) { + eventMgr->dequeued(msg); } } -namespace -{ - const std::string qpidMaxSize("qpid.max_size"); - const std::string qpidMaxCount("qpid.max_count"); - const std::string qpidNoLocal("no-local"); - const std::string qpidTraceIdentity("qpid.trace.id"); - const std::string qpidTraceExclude("qpid.trace.exclude"); -} - void Queue::create(const FieldTable& _settings) { settings = _settings; @@ -545,26 +823,56 @@ void Queue::create(const FieldTable& _settings) configure(_settings); } -void Queue::configure(const FieldTable& _settings) +void Queue::configure(const FieldTable& _settings, bool recovering) { - std::auto_ptr<QueuePolicy> _policy(new QueuePolicy(_settings)); - if (_policy->getMaxCount() || _policy->getMaxSize()) { - setPolicy(_policy); + + eventMode = _settings.getAsInt(qpidQueueEventGeneration); + + if (QueuePolicy::getType(_settings) == QueuePolicy::FLOW_TO_DISK && + (!store || NullMessageStore::isNullStore(store) || (eventMode && eventMgr && !eventMgr->isSync()) )) { + if ( NullMessageStore::isNullStore(store)) { + QPID_LOG(warning, "Flow to disk not valid for non-persisted queue:" << getName()); + } else if (eventMgr && !eventMgr->isSync() ) { + QPID_LOG(warning, "Flow to disk not valid with async Queue Events:" << getName()); + } + FieldTable copy(_settings); + copy.erase(QueuePolicy::typeKey); + setPolicy(QueuePolicy::createQueuePolicy(getName(), copy)); + } else { + setPolicy(QueuePolicy::createQueuePolicy(getName(), _settings)); } //set this regardless of owner to allow use of no-local with exclusive consumers also noLocal = _settings.get(qpidNoLocal); - QPID_LOG(debug, "Configured queue with no-local=" << noLocal); + QPID_LOG(debug, "Configured queue " << getName() << " with no-local=" << noLocal); + + lastValueQueue= _settings.get(qpidLastValueQueue); + if (lastValueQueue) QPID_LOG(debug, "Configured queue as Last Value Queue for: " << getName()); - traceId = _settings.getString(qpidTraceIdentity); - std::string excludeList = _settings.getString(qpidTraceExclude); + lastValueQueueNoBrowse = _settings.get(qpidLastValueQueueNoBrowse); + if (lastValueQueueNoBrowse){ + QPID_LOG(debug, "Configured queue as Last Value Queue No Browse for: " << getName()); + lastValueQueue = lastValueQueueNoBrowse; + } + + persistLastNode= _settings.get(qpidPersistLastNode); + if (persistLastNode) QPID_LOG(debug, "Configured queue to Persist data if cluster fails to one node for: " << getName()); + + traceId = _settings.getAsString(qpidTraceIdentity); + std::string excludeList = _settings.getAsString(qpidTraceExclude); if (excludeList.size()) { split(traceExclude, excludeList, ", "); } QPID_LOG(debug, "Configured queue " << getName() << " with qpid.trace.id='" << traceId << "' and qpid.trace.exclude='"<< excludeList << "' i.e. " << traceExclude.size() << " elements"); + FieldTable::ValuePtr p =_settings.get(qpidInsertSequenceNumbers); + if (p && p->convertsTo<std::string>()) insertSequenceNumbers(p->get<std::string>()); + if (mgmtObject != 0) mgmtObject->set_arguments (_settings); + + if ( isDurable() && ! getPersistenceId() && ! recovering ) + store->create(*this, _settings); } void Queue::destroy() @@ -572,7 +880,7 @@ void Queue::destroy() if (alternateExchange.get()) { Mutex::ScopedLock locker(messageLock); while(!messages.empty()){ - DeliverableMessage msg(messages.front().payload); + DeliverableMessage msg(getFront().payload); alternateExchange->route(msg, msg.getMessage().getRoutingKey(), msg.getMessage().getApplicationHeaders()); popAndDequeue(); @@ -617,8 +925,8 @@ void Queue::setPersistenceId(uint64_t _persistenceId) const { if (mgmtObject != 0 && persistenceId == 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); - agent->addObject (mgmtObject, _persistenceId, 3); + ManagementAgent* agent = broker->getManagementAgent(); + agent->addObject (mgmtObject, 0x3000000000000000LL + _persistenceId); if (externalQueueStore) { ManagementObject* childObj = externalQueueStore->GetManagementObject(); @@ -629,24 +937,40 @@ void Queue::setPersistenceId(uint64_t _persistenceId) const persistenceId = _persistenceId; } -void Queue::encode(framing::Buffer& buffer) const +void Queue::encode(Buffer& buffer) const { buffer.putShortString(name); buffer.put(settings); + if (policy.get()) { + buffer.put(*policy); + } + buffer.putShortString(alternateExchange.get() ? alternateExchange->getName() : std::string("")); } uint32_t Queue::encodedSize() const { - return name.size() + 1/*short string size octet*/ + settings.size(); + return name.size() + 1/*short string size octet*/ + + (alternateExchange.get() ? alternateExchange->getName().size() : 0) + 1 /* short string */ + + settings.encodedSize() + + (policy.get() ? (*policy).encodedSize() : 0); } -Queue::shared_ptr Queue::decode(QueueRegistry& queues, framing::Buffer& buffer) +Queue::shared_ptr Queue::decode ( QueueRegistry& queues, Buffer& buffer, bool recovering ) { string name; buffer.getShortString(name); std::pair<Queue::shared_ptr, bool> result = queues.declare(name, true); buffer.get(result.first->settings); - result.first->configure(result.first->settings); + result.first->configure(result.first->settings, recovering ); + if (result.first->policy.get() && buffer.available() >= result.first->policy->encodedSize()) { + buffer.get ( *(result.first->policy) ); + } + if (buffer.available()) { + string altExch; + buffer.getShortString(altExch); + result.first->alternateExchangeName.assign(altExch); + } + return result.first; } @@ -654,6 +978,12 @@ Queue::shared_ptr Queue::decode(QueueRegistry& queues, framing::Buffer& buffer) void Queue::setAlternateExchange(boost::shared_ptr<Exchange> exchange) { alternateExchange = exchange; + if (mgmtObject) { + if (exchange.get() != 0) + mgmtObject->set_altExchange(exchange->GetManagementObject()->getObjectId()); + else + mgmtObject->clr_altExchange(); + } } boost::shared_ptr<Exchange> Queue::getAlternateExchange() @@ -721,8 +1051,7 @@ ManagementObject* Queue::GetManagementObject (void) const return (ManagementObject*) mgmtObject; } -Manageable::status_t Queue::ManagementMethod (uint32_t methodId, - Args& args) +Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, string&) { Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; @@ -730,8 +1059,8 @@ Manageable::status_t Queue::ManagementMethod (uint32_t methodId, switch (methodId) { - case management::Queue::METHOD_PURGE : - management::ArgsQueuePurge iargs = dynamic_cast<const management::ArgsQueuePurge&>(args); + case _qmf::Queue::METHOD_PURGE : + _qmf::ArgsQueuePurge& iargs = (_qmf::ArgsQueuePurge&) args; purge (iargs.i_request); status = Manageable::STATUS_OK; break; @@ -739,3 +1068,63 @@ Manageable::status_t Queue::ManagementMethod (uint32_t methodId, return status; } + +void Queue::setPosition(SequenceNumber n) { + Mutex::ScopedLock locker(messageLock); + sequence = n; +} + +SequenceNumber Queue::getPosition() { + return sequence; +} + +int Queue::getEventMode() { return eventMode; } + +void Queue::setQueueEventManager(QueueEvents& mgr) +{ + eventMgr = &mgr; +} + +void Queue::recoveryComplete(ExchangeRegistry& exchanges) +{ + // set the alternate exchange + if (!alternateExchangeName.empty()) { + try { + Exchange::shared_ptr ae = exchanges.get(alternateExchangeName); + setAlternateExchange(ae); + } catch (const NotFoundException&) { + QPID_LOG(warning, "Could not set alternate exchange \"" << alternateExchangeName << "\" on queue \"" << name << "\": exchange does not exist."); + } + } + //process any pending dequeues + for_each(pendingDequeues.begin(), pendingDequeues.end(), boost::bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); + pendingDequeues.clear(); +} + +void Queue::insertSequenceNumbers(const std::string& key) +{ + seqNoKey = key; + insertSeqNo = !seqNoKey.empty(); + QPID_LOG(debug, "Inserting sequence numbers as " << key); +} + +void Queue::enqueued(const QueuedMessage& m) +{ + if (m.payload) { + if (policy.get()) { + policy->recoverEnqueued(m.payload); + policy->enqueued(m); + } + mgntEnqStats(m.payload); + enqueue ( 0, m.payload, true ); + } else { + QPID_LOG(warning, "Queue informed of enqueued message that has no payload"); + } +} + +bool Queue::isEnqueued(const QueuedMessage& msg) +{ + return !policy.get() || policy->isEnqueued(msg); +} + +QueueListeners& Queue::getListeners() { return listeners; } diff --git a/cpp/src/qpid/broker/Queue.h b/cpp/src/qpid/broker/Queue.h index 8b8ba8278f..5b177f1cf2 100644 --- a/cpp/src/qpid/broker/Queue.h +++ b/cpp/src/qpid/broker/Queue.h @@ -21,31 +21,38 @@ * under the License. * */ -#include "OwnershipToken.h" -#include "Consumer.h" -#include "Message.h" -#include "PersistableQueue.h" -#include "QueuePolicy.h" -#include "QueueBindings.h" + +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/OwnershipToken.h" +#include "qpid/broker/Consumer.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/PersistableQueue.h" +#include "qpid/broker/QueuePolicy.h" +#include "qpid/broker/QueueBindings.h" +#include "qpid/broker/QueueListeners.h" +#include "qpid/broker/RateTracker.h" #include "qpid/framing/FieldTable.h" #include "qpid/sys/Monitor.h" #include "qpid/management/Manageable.h" -#include "qpid/management/Queue.h" +#include "qmf/org/apache/qpid/broker/Queue.h" #include "qpid/framing/amqp_types.h" -#include <vector> -#include <memory> -#include <deque> - #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/intrusive_ptr.hpp> +#include <list> +#include <vector> +#include <memory> +#include <deque> +#include <algorithm> + namespace qpid { namespace broker { class Broker; class MessageStore; + class QueueEvents; class QueueRegistry; class TransactionContext; class Exchange; @@ -60,8 +67,10 @@ namespace qpid { */ class Queue : public boost::enable_shared_from_this<Queue>, public PersistableQueue, public management::Manageable { - typedef qpid::InlineVector<Consumer*, 5> Listeners; + typedef std::deque<QueuedMessage> Messages; + typedef std::map<string,boost::intrusive_ptr<Message> > LVQ; + enum ConsumeCode {NO_MESSAGES=0, CANT_CONSUME=1, CONSUMED=2}; const string name; const bool autodelete; @@ -70,10 +79,16 @@ namespace qpid { uint32_t consumerCount; OwnershipToken* exclusive; bool noLocal; + bool lastValueQueue; + bool lastValueQueueNoBrowse; + bool persistLastNode; + bool inLastNodeFailure; std::string traceId; std::vector<std::string> traceExclude; - Listeners listeners; + QueueListeners listeners; Messages messages; + Messages pendingDequeues;//used to avoid dequeuing during recovery + LVQ lvq; mutable qpid::sys::Mutex consumerLock; mutable qpid::sys::Mutex messageLock; mutable qpid::sys::Mutex ownershipLock; @@ -82,25 +97,59 @@ namespace qpid { std::auto_ptr<QueuePolicy> policy; bool policyExceeded; QueueBindings bindings; + std::string alternateExchangeName; boost::shared_ptr<Exchange> alternateExchange; framing::SequenceNumber sequence; - management::Queue* mgmtObject; - - void push(boost::intrusive_ptr<Message>& msg); + qmf::org::apache::qpid::broker::Queue* mgmtObject; + RateTracker dequeueTracker; + int eventMode; + QueueEvents* eventMgr; + bool insertSeqNo; + std::string seqNoKey; + Broker* broker; + + void push(boost::intrusive_ptr<Message>& msg, bool isRecovery=false); void setPolicy(std::auto_ptr<QueuePolicy> policy); - bool seek(QueuedMessage& msg, Consumer& position); - bool getNextMessage(QueuedMessage& msg, Consumer& c); - bool consumeNextMessage(QueuedMessage& msg, Consumer& c); - bool browseNextMessage(QueuedMessage& msg, Consumer& c); + bool seek(QueuedMessage& msg, Consumer::shared_ptr position); + bool getNextMessage(QueuedMessage& msg, Consumer::shared_ptr c); + ConsumeCode consumeNextMessage(QueuedMessage& msg, Consumer::shared_ptr c); + bool browseNextMessage(QueuedMessage& msg, Consumer::shared_ptr c); + void notifyListener(); - void notify(); - void removeListener(Consumer&); - void addListener(Consumer&); + void removeListener(Consumer::shared_ptr); bool isExcluded(boost::intrusive_ptr<Message>& msg); - void dequeued(boost::intrusive_ptr<Message>& msg); + void dequeued(const QueuedMessage& msg); void popAndDequeue(); + QueuedMessage getFront(); + QueuedMessage& checkLvqReplace(QueuedMessage& msg); + void clearLVQIndex(const QueuedMessage& msg); + + inline void mgntEnqStats(const boost::intrusive_ptr<Message>& msg) + { + if (mgmtObject != 0) { + mgmtObject->inc_msgTotalEnqueues (); + mgmtObject->inc_byteTotalEnqueues (msg->contentSize ()); + if (msg->isPersistent ()) { + mgmtObject->inc_msgPersistEnqueues (); + mgmtObject->inc_bytePersistEnqueues (msg->contentSize ()); + } + } + } + inline void mgntDeqStats(const boost::intrusive_ptr<Message>& msg) + { + if (mgmtObject != 0){ + mgmtObject->inc_msgTotalDequeues (); + mgmtObject->inc_byteTotalDequeues (msg->contentSize()); + if (msg->isPersistent ()){ + mgmtObject->inc_msgPersistDequeues (); + mgmtObject->inc_bytePersistDequeues (msg->contentSize()); + } + } + } + + Messages::iterator findAt(framing::SequenceNumber pos); public: @@ -109,58 +158,73 @@ namespace qpid { typedef std::vector<shared_ptr> vector; - Queue(const string& name, bool autodelete = false, - MessageStore* const store = 0, - const OwnershipToken* const owner = 0, - management::Manageable* parent = 0); - ~Queue(); + QPID_BROKER_EXTERN Queue(const string& name, + bool autodelete = false, + MessageStore* const store = 0, + const OwnershipToken* const owner = 0, + management::Manageable* parent = 0, + Broker* broker = 0); + QPID_BROKER_EXTERN ~Queue(); - bool dispatch(Consumer&); + QPID_BROKER_EXTERN bool dispatch(Consumer::shared_ptr); /** * Check whether there would be a message available for * dispatch to this consumer. If not, the consumer will be * notified of events that may have changed this * situation. */ - bool checkForMessages(Consumer&); + bool checkForMessages(Consumer::shared_ptr); void create(const qpid::framing::FieldTable& settings); - void configure(const qpid::framing::FieldTable& settings); + + // "recovering" means we are doing a MessageStore recovery. + QPID_BROKER_EXTERN void configure(const qpid::framing::FieldTable& settings, + bool recovering = false); void destroy(); - void bound(const string& exchange, const string& key, const qpid::framing::FieldTable& args); - void unbind(ExchangeRegistry& exchanges, Queue::shared_ptr shared_ref); + QPID_BROKER_EXTERN void bound(const string& exchange, + const string& key, + const qpid::framing::FieldTable& args); + QPID_BROKER_EXTERN void unbind(ExchangeRegistry& exchanges, + Queue::shared_ptr shared_ref); - bool acquire(const QueuedMessage& msg); + QPID_BROKER_EXTERN bool acquire(const QueuedMessage& msg); + QPID_BROKER_EXTERN bool acquireMessageAt(const qpid::framing::SequenceNumber& position, QueuedMessage& message); /** * Delivers a message to the queue. Will record it as * enqueued if persistent then process it. */ - void deliver(boost::intrusive_ptr<Message>& msg); + QPID_BROKER_EXTERN void deliver(boost::intrusive_ptr<Message>& msg); /** * Dispatches the messages immediately to a consumer if * one is available or stores it for later if not. */ - void process(boost::intrusive_ptr<Message>& msg); + QPID_BROKER_EXTERN void process(boost::intrusive_ptr<Message>& msg); /** * Returns a message to the in-memory queue (due to lack * of acknowledegement from a receiver). If a consumer is * available it will be dispatched immediately, else it * will be returned to the front of the queue. */ - void requeue(const QueuedMessage& msg); + QPID_BROKER_EXTERN void requeue(const QueuedMessage& msg); /** * Used during recovery to add stored messages back to the queue */ - void recover(boost::intrusive_ptr<Message>& msg); + QPID_BROKER_EXTERN void recover(boost::intrusive_ptr<Message>& msg); - void consume(Consumer& c, bool exclusive = false); - void cancel(Consumer& c); + QPID_BROKER_EXTERN void consume(Consumer::shared_ptr c, + bool exclusive = false); + QPID_BROKER_EXTERN void cancel(Consumer::shared_ptr c); uint32_t purge(const uint32_t purge_request = 0); //defaults to all messages + QPID_BROKER_EXTERN void purgeExpired(); - uint32_t getMessageCount() const; - uint32_t getConsumerCount() const; + //move qty # of messages to destination Queue destq + uint32_t move(const Queue::shared_ptr destq, uint32_t qty); + + QPID_BROKER_EXTERN uint32_t getMessageCount() const; + QPID_BROKER_EXTERN uint32_t getEnqueueCompleteMessageCount() const; + QPID_BROKER_EXTERN uint32_t getConsumerCount() const; inline const string& getName() const { return name; } bool isExclusiveOwner(const OwnershipToken* const o) const; void releaseExclusiveOwnership(); @@ -171,17 +235,50 @@ namespace qpid { inline const framing::FieldTable& getSettings() const { return settings; } inline bool isAutoDelete() const { return autodelete; } bool canAutoDelete() const; + const QueueBindings& getBindings() const { return bindings; } + + /** + * used to take messages from in memory and flush down to disk. + */ + QPID_BROKER_EXTERN void setLastNodeFailure(); + QPID_BROKER_EXTERN void clearLastNodeFailure(); - bool enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message> msg); + bool enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message> msg, bool suppressPolicyCheck = false); + void enqueueAborted(boost::intrusive_ptr<Message> msg); /** * dequeue from store (only done once messages is acknowledged) */ - bool dequeue(TransactionContext* ctxt, boost::intrusive_ptr<Message> msg); + QPID_BROKER_EXTERN bool dequeue(TransactionContext* ctxt, const QueuedMessage &msg); + /** + * Inform the queue that a previous transactional dequeue + * committed. + */ + void dequeueCommitted(const QueuedMessage& msg); + + /** + * Inform queue of messages that were enqueued, have since + * been acquired but not yet accepted or released (and + * thus are still logically on the queue) - used in + * clustered broker. + */ + void enqueued(const QueuedMessage& msg); /** + * Test whether the specified message (identified by its + * sequence/position), is still enqueued (note this + * doesn't mean it is available for delivery as it may + * have been delievered to a subscriber who has not yet + * accepted it). + */ + bool isEnqueued(const QueuedMessage& msg); + + /** * Gets the next available message */ - QueuedMessage get(); + QPID_BROKER_EXTERN QueuedMessage get(); + + /** Get the message at position pos */ + QPID_BROKER_EXTERN QueuedMessage find(framing::SequenceNumber pos) const; const QueuePolicy* getPolicy(); @@ -195,7 +292,8 @@ namespace qpid { void encode(framing::Buffer& buffer) const; uint32_t encodedSize() const; - static Queue::shared_ptr decode(QueueRegistry& queues, framing::Buffer& buffer); + // "recovering" means we are doing a MessageStore recovery. + static Queue::shared_ptr decode(QueueRegistry& queues, framing::Buffer& buffer, bool recovering = false ); static void tryAutoDelete(Broker& broker, Queue::shared_ptr); virtual void setExternalQueueStore(ExternalQueueStore* inst); @@ -203,7 +301,51 @@ namespace qpid { // Manageable entry points management::ManagementObject* GetManagementObject (void) const; management::Manageable::status_t - ManagementMethod (uint32_t methodId, management::Args& args); + ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + + /** Apply f to each Message on the queue. */ + template <class F> void eachMessage(F f) { + sys::Mutex::ScopedLock l(messageLock); + if (lastValueQueue) { + for (Messages::iterator i = messages.begin(); i != messages.end(); ++i) { + f(checkLvqReplace(*i)); + } + } else { + std::for_each(messages.begin(), messages.end(), f); + } + } + + /** Apply f to each QueueBinding on the queue */ + template <class F> void eachBinding(F f) { + bindings.eachBinding(f); + } + + void popMsg(QueuedMessage& qmsg); + + /** Set the position sequence number for the next message on the queue. + * Must be >= the current sequence number. + * Used by cluster to replicate queues. + */ + QPID_BROKER_EXTERN void setPosition(framing::SequenceNumber pos); + /** return current position sequence number for the next message on the queue. + */ + QPID_BROKER_EXTERN framing::SequenceNumber getPosition(); + int getEventMode(); + void setQueueEventManager(QueueEvents&); + QPID_BROKER_EXTERN void insertSequenceNumbers(const std::string& key); + /** + * Notify queue that recovery has completed. + */ + void recoveryComplete(ExchangeRegistry& exchanges); + + // For cluster update + QueueListeners& getListeners(); + + /** + * Reserve space in policy for an enqueued message that + * has been recovered in the prepared state (dtx only) + */ + void recoverPrepared(boost::intrusive_ptr<Message>& msg); }; } } diff --git a/cpp/src/qpid/broker/QueueBindings.cpp b/cpp/src/qpid/broker/QueueBindings.cpp index 95e529f47e..3f43a8ef68 100644 --- a/cpp/src/qpid/broker/QueueBindings.cpp +++ b/cpp/src/qpid/broker/QueueBindings.cpp @@ -18,8 +18,8 @@ * under the License. * */ -#include "QueueBindings.h" -#include "ExchangeRegistry.h" +#include "qpid/broker/QueueBindings.h" +#include "qpid/broker/ExchangeRegistry.h" #include "qpid/framing/reply_exceptions.h" using qpid::framing::FieldTable; @@ -29,7 +29,7 @@ using namespace qpid::broker; void QueueBindings::add(const string& exchange, const string& key, const FieldTable& args) { - bindings.push_back(new Binding(exchange, key, args)); + bindings.push_back(QueueBinding(exchange, key, args)); } void QueueBindings::unbind(ExchangeRegistry& exchanges, Queue::shared_ptr queue) @@ -37,11 +37,10 @@ void QueueBindings::unbind(ExchangeRegistry& exchanges, Queue::shared_ptr queue) for (Bindings::iterator i = bindings.begin(); i != bindings.end(); i++) { try { exchanges.get(i->exchange)->unbind(queue, i->key, &(i->args)); - } catch (const NotFoundException&) { - } + } catch (const NotFoundException&) {} } } -QueueBindings::Binding::Binding(const string& _exchange, const string& _key, const FieldTable& _args) +QueueBinding::QueueBinding(const string& _exchange, const string& _key, const FieldTable& _args) : exchange(_exchange), key(_key), args(_args) {} diff --git a/cpp/src/qpid/broker/QueueBindings.h b/cpp/src/qpid/broker/QueueBindings.h index b9b0f7c15c..1b90ba5540 100644 --- a/cpp/src/qpid/broker/QueueBindings.h +++ b/cpp/src/qpid/broker/QueueBindings.h @@ -24,32 +24,38 @@ #include "qpid/framing/FieldTable.h" #include <boost/ptr_container/ptr_list.hpp> #include <boost/shared_ptr.hpp> +#include <algorithm> namespace qpid { namespace broker { class ExchangeRegistry; class Queue; + +struct QueueBinding{ + std::string exchange; + std::string key; + qpid::framing::FieldTable args; + QueueBinding(const std::string& exchange, const std::string& key, const qpid::framing::FieldTable& args); +}; + class QueueBindings { - struct Binding{ - const std::string exchange; - const std::string key; - const qpid::framing::FieldTable args; - Binding(const std::string& exchange, const std::string& key, const qpid::framing::FieldTable& args); - }; - - typedef boost::ptr_list<Binding> Bindings; - Bindings bindings; + public: -public: + /** Apply f to each QueueBinding. */ + template <class F> void eachBinding(F f) const { std::for_each(bindings.begin(), bindings.end(), f); } + void add(const std::string& exchange, const std::string& key, const qpid::framing::FieldTable& args); void unbind(ExchangeRegistry& exchanges, boost::shared_ptr<Queue> queue); + + private: + typedef std::vector<QueueBinding> Bindings; + Bindings bindings; }; -} -} +}} // namespace qpid::broker #endif diff --git a/cpp/src/qpid/broker/QueueCleaner.cpp b/cpp/src/qpid/broker/QueueCleaner.cpp new file mode 100644 index 0000000000..c80fe89035 --- /dev/null +++ b/cpp/src/qpid/broker/QueueCleaner.cpp @@ -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. + * + */ +#include "qpid/broker/QueueCleaner.h" + +#include "qpid/broker/Broker.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace broker { + +QueueCleaner::QueueCleaner(QueueRegistry& q, sys::Timer& t) : queues(q), timer(t) {} + +QueueCleaner::~QueueCleaner() +{ + if (task) task->cancel(); +} + +void QueueCleaner::start(qpid::sys::Duration p) +{ + task = new Task(*this, p); + timer.add(task); +} + +QueueCleaner::Task::Task(QueueCleaner& p, qpid::sys::Duration d) : sys::TimerTask(d), parent(p) {} + +void QueueCleaner::Task::fire() +{ + parent.fired(); +} + +void QueueCleaner::fired() +{ + queues.eachQueue(boost::bind(&Queue::purgeExpired, _1)); + task->setupNextFire(); + timer.add(task); +} + + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/QueueCleaner.h b/cpp/src/qpid/broker/QueueCleaner.h new file mode 100644 index 0000000000..11c2d180ac --- /dev/null +++ b/cpp/src/qpid/broker/QueueCleaner.h @@ -0,0 +1,59 @@ +#ifndef QPID_BROKER_QUEUECLEANER_H +#define QPID_BROKER_QUEUECLEANER_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/BrokerImportExport.h" +#include "qpid/sys/Timer.h" + +namespace qpid { +namespace broker { + +class QueueRegistry; +/** + * TimerTask to purge expired messages from queues + */ +class QueueCleaner +{ + public: + QPID_BROKER_EXTERN QueueCleaner(QueueRegistry& queues, sys::Timer& timer); + QPID_BROKER_EXTERN ~QueueCleaner(); + QPID_BROKER_EXTERN void start(qpid::sys::Duration period); + private: + class Task : public sys::TimerTask + { + public: + Task(QueueCleaner& parent, qpid::sys::Duration duration); + void fire(); + private: + QueueCleaner& parent; + }; + + boost::intrusive_ptr<sys::TimerTask> task; + QueueRegistry& queues; + sys::Timer& timer; + + void fired(); +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_QUEUECLEANER_H*/ diff --git a/cpp/src/qpid/broker/QueueEvents.cpp b/cpp/src/qpid/broker/QueueEvents.cpp new file mode 100644 index 0000000000..bba054b0b8 --- /dev/null +++ b/cpp/src/qpid/broker/QueueEvents.cpp @@ -0,0 +1,122 @@ +/* + * + * 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/QueueEvents.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace broker { + +QueueEvents::QueueEvents(const boost::shared_ptr<sys::Poller>& poller, bool isSync) : + eventQueue(boost::bind(&QueueEvents::handle, this, _1), poller), enabled(true), sync(isSync) +{ + if (!sync) eventQueue.start(); +} + +QueueEvents::~QueueEvents() +{ + if (!sync) eventQueue.stop(); +} + +void QueueEvents::enqueued(const QueuedMessage& m) +{ + if (enabled) { + Event enq(ENQUEUE, m); + if (sync) { + for (Listeners::iterator j = listeners.begin(); j != listeners.end(); j++) + j->second(enq); + } else { + eventQueue.push(enq); + } + } +} + +void QueueEvents::dequeued(const QueuedMessage& m) +{ + if (enabled) { + Event deq(DEQUEUE, m); + if (sync) { + for (Listeners::iterator j = listeners.begin(); j != listeners.end(); j++) + j->second(deq); + } else { + eventQueue.push(Event(DEQUEUE, m)); + } + } +} + +void QueueEvents::registerListener(const std::string& id, const EventListener& listener) +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (listeners.find(id) == listeners.end()) { + listeners[id] = listener; + } else { + throw Exception(QPID_MSG("Event listener already registered for '" << id << "'")); + } +} + +void QueueEvents::unregisterListener(const std::string& id) +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (listeners.find(id) == listeners.end()) { + throw Exception(QPID_MSG("No event listener registered for '" << id << "'")); + } else { + listeners.erase(id); + } +} + +QueueEvents::EventQueue::Batch::const_iterator +QueueEvents::handle(const EventQueue::Batch& events) { + qpid::sys::Mutex::ScopedLock l(lock); + for (EventQueue::Batch::const_iterator i = events.begin(); i != events.end(); ++i) { + for (Listeners::iterator j = listeners.begin(); j != listeners.end(); j++) { + j->second(*i); + } + } + return events.end(); +} + +void QueueEvents::shutdown() +{ + if (!sync && !eventQueue.empty() && !listeners.empty()) eventQueue.shutdown(); +} + +void QueueEvents::enable() +{ + enabled = true; + QPID_LOG(debug, "Queue events enabled"); +} + +void QueueEvents::disable() +{ + enabled = false; + QPID_LOG(debug, "Queue events disabled"); +} + +bool QueueEvents::isSync() +{ + return sync; +} + + +QueueEvents::Event::Event(EventType t, const QueuedMessage& m) : type(t), msg(m) {} + + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/QueueEvents.h b/cpp/src/qpid/broker/QueueEvents.h new file mode 100644 index 0000000000..c42752133e --- /dev/null +++ b/cpp/src/qpid/broker/QueueEvents.h @@ -0,0 +1,84 @@ +#ifndef QPID_BROKER_QUEUEEVENTS_H +#define QPID_BROKER_QUEUEEVENTS_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/BrokerImportExport.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/PollableQueue.h" +#include <map> +#include <string> +#include <boost/function.hpp> + +namespace qpid { +namespace broker { + +/** + * Event manager for queue events. Allows queues to indicate when + * events have occured; allows listeners to register for notification + * of this. The notification happens asynchronously, in a separate + * thread. + */ +class QueueEvents +{ + public: + enum EventType {ENQUEUE, DEQUEUE}; + + struct Event + { + EventType type; + QueuedMessage msg; + + QPID_BROKER_EXTERN Event(EventType, const QueuedMessage&); + }; + + typedef boost::function<void (Event)> EventListener; + + QPID_BROKER_EXTERN QueueEvents(const boost::shared_ptr<sys::Poller>& poller, bool isSync = false); + QPID_BROKER_EXTERN ~QueueEvents(); + QPID_BROKER_EXTERN void enqueued(const QueuedMessage&); + QPID_BROKER_EXTERN void dequeued(const QueuedMessage&); + QPID_BROKER_EXTERN void registerListener(const std::string& id, + const EventListener&); + QPID_BROKER_EXTERN void unregisterListener(const std::string& id); + void enable(); + void disable(); + //process all outstanding events + QPID_BROKER_EXTERN void shutdown(); + QPID_BROKER_EXTERN bool isSync(); + private: + typedef qpid::sys::PollableQueue<Event> EventQueue; + typedef std::map<std::string, EventListener> Listeners; + + EventQueue eventQueue; + Listeners listeners; + volatile bool enabled; + qpid::sys::Mutex lock;//protect listeners from concurrent access + bool sync; + + EventQueue::Batch::const_iterator handle(const EventQueue::Batch& e); + +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_QUEUEEVENTS_H*/ diff --git a/cpp/src/qpid/broker/QueueListeners.cpp b/cpp/src/qpid/broker/QueueListeners.cpp new file mode 100644 index 0000000000..951de2184a --- /dev/null +++ b/cpp/src/qpid/broker/QueueListeners.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/QueueListeners.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace broker { + +void QueueListeners::addListener(Consumer::shared_ptr c) +{ + if (c->preAcquires()) { + add(consumers, c); + } else { + add(browsers, c); + } +} + +void QueueListeners::removeListener(Consumer::shared_ptr c) +{ + if (c->preAcquires()) { + remove(consumers, c); + } else { + remove(browsers, c); + } +} + +void QueueListeners::populate(NotificationSet& set) +{ + if (consumers.size()) { + set.consumer = consumers.front(); + consumers.erase(consumers.begin()); + } else { + // Don't swap the vectors, hang on to the memory allocated. + set.browsers = browsers; + browsers.clear(); + } +} + +void QueueListeners::add(Listeners& listeners, Consumer::shared_ptr c) +{ + Listeners::iterator i = std::find(listeners.begin(), listeners.end(), c); + if (i == listeners.end()) listeners.push_back(c); +} + +void QueueListeners::remove(Listeners& listeners, Consumer::shared_ptr c) +{ + Listeners::iterator i = std::find(listeners.begin(), listeners.end(), c); + if (i != listeners.end()) listeners.erase(i); +} + +void QueueListeners::NotificationSet::notify() +{ + if (consumer) consumer->notify(); + else std::for_each(browsers.begin(), browsers.end(), boost::mem_fn(&Consumer::notify)); +} + +bool QueueListeners::contains(Consumer::shared_ptr c) const { + return + std::find(browsers.begin(), browsers.end(), c) != browsers.end() || + std::find(consumers.begin(), consumers.end(), c) != consumers.end(); +} + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/QueueListeners.h b/cpp/src/qpid/broker/QueueListeners.h new file mode 100644 index 0000000000..51ef58eb06 --- /dev/null +++ b/cpp/src/qpid/broker/QueueListeners.h @@ -0,0 +1,75 @@ +#ifndef QPID_BROKER_QUEUELISTENERS_H +#define QPID_BROKER_QUEUELISTENERS_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/Consumer.h" +#include <vector> + +namespace qpid { +namespace broker { + +/** + * Track and notify components that wish to be notified of messages + * that become available on a queue. + * + * None of the methods defined here are protected by locking. However + * the populate method allows a 'snapshot' to be taken of the + * listeners to be notified. NotificationSet::notify() may then be + * called outside of any lock that protects the QueueListeners + * instance from concurrent access. + */ +class QueueListeners +{ + public: + typedef std::vector<Consumer::shared_ptr> Listeners; + + class NotificationSet + { + public: + void notify(); + private: + Listeners browsers; + Consumer::shared_ptr consumer; + friend class QueueListeners; + }; + + void addListener(Consumer::shared_ptr); + void removeListener(Consumer::shared_ptr); + void populate(NotificationSet&); + bool contains(Consumer::shared_ptr c) const; + + template <class F> void eachListener(F f) { + std::for_each(browsers.begin(), browsers.end(), f); + std::for_each(consumers.begin(), consumers.end(), f); + } + + private: + Listeners consumers; + Listeners browsers; + + void add(Listeners&, Consumer::shared_ptr); + void remove(Listeners&, Consumer::shared_ptr); + +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_QUEUELISTENERS_H*/ diff --git a/cpp/src/qpid/broker/QueuePolicy.cpp b/cpp/src/qpid/broker/QueuePolicy.cpp index 08838aac79..a8aa674c53 100644 --- a/cpp/src/qpid/broker/QueuePolicy.cpp +++ b/cpp/src/qpid/broker/QueuePolicy.cpp @@ -18,40 +18,99 @@ * under the License. * */ -#include "QueuePolicy.h" +#include "qpid/broker/QueuePolicy.h" +#include "qpid/broker/Queue.h" +#include "qpid/Exception.h" #include "qpid/framing/FieldValue.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" using namespace qpid::broker; using namespace qpid::framing; -QueuePolicy::QueuePolicy(uint32_t _maxCount, uint64_t _maxSize) : - maxCount(_maxCount), maxSize(_maxSize), count(0), size(0) {} - -QueuePolicy::QueuePolicy(const FieldTable& settings) : - maxCount(getInt(settings, maxCountKey, 0)), - maxSize(getInt(settings, maxSizeKey, defaultMaxSize)), count(0), size(0) {} +QueuePolicy::QueuePolicy(const std::string& _name, uint32_t _maxCount, uint64_t _maxSize, const std::string& _type) : + maxCount(_maxCount), maxSize(_maxSize), type(_type), count(0), size(0), policyExceeded(false), name(_name) {} void QueuePolicy::enqueued(uint64_t _size) { - if (maxCount) count++; + if (maxCount) ++count; if (maxSize) size += _size; } void QueuePolicy::dequeued(uint64_t _size) { - if (maxCount) count--; - if (maxSize) size -= _size; + if (maxCount) { + if (count > 0) { + --count; + } else { + throw Exception(QPID_MSG("Attempted count underflow on dequeue(" << _size << "): " << *this)); + } + } + if (maxSize) { + if (_size > size) { + throw Exception(QPID_MSG("Attempted size underflow on dequeue(" << _size << "): " << *this)); + } else { + size -= _size; + } + } +} + +bool QueuePolicy::checkLimit(boost::intrusive_ptr<Message> m) +{ + bool sizeExceeded = maxSize && (size + m->contentSize()) > maxSize; + bool countExceeded = maxCount && (count + 1) > maxCount; + bool exceeded = sizeExceeded || countExceeded; + if (exceeded) { + if (!policyExceeded) { + policyExceeded = true; + if (sizeExceeded) QPID_LOG(info, "Queue cumulative message size exceeded policy for " << name); + if (countExceeded) QPID_LOG(info, "Queue message count exceeded policy for " << name); + } + } else { + if (policyExceeded) { + policyExceeded = false; + QPID_LOG(info, "Queue cumulative message size and message count within policy for " << name); + } + } + return !exceeded; +} + +void QueuePolicy::tryEnqueue(boost::intrusive_ptr<Message> m) +{ + if (checkLimit(m)) { + enqueued(m->contentSize()); + } else { + throw ResourceLimitExceededException(QPID_MSG("Policy exceeded on " << name << ", policy: " << *this)); + } +} + +void QueuePolicy::recoverEnqueued(boost::intrusive_ptr<Message> m) +{ + enqueued(m->contentSize()); } -bool QueuePolicy::limitExceeded() +void QueuePolicy::enqueueAborted(boost::intrusive_ptr<Message> m) { - return (maxSize && size > maxSize) || (maxCount && count > maxCount); + dequeued(m->contentSize()); +} + +void QueuePolicy::enqueued(const QueuedMessage&) {} + +void QueuePolicy::dequeued(const QueuedMessage& m) +{ + dequeued(m.payload->contentSize()); +} + +bool QueuePolicy::isEnqueued(const QueuedMessage&) +{ + return true; } void QueuePolicy::update(FieldTable& settings) { if (maxCount) settings.setInt(maxCountKey, maxCount); - if (maxSize) settings.setInt(maxSizeKey, maxSize); + if (maxSize) settings.setInt(maxSizeKey, maxSize); + settings.setString(typeKey, type); } @@ -62,27 +121,195 @@ int QueuePolicy::getInt(const FieldTable& settings, const std::string& key, int else return defaultValue; } +std::string QueuePolicy::getType(const FieldTable& settings) +{ + FieldTable::ValuePtr v = settings.get(typeKey); + if (v && v->convertsTo<std::string>()) { + std::string t = v->get<std::string>(); + std::transform(t.begin(), t.end(), t.begin(), tolower); + if (t == REJECT || t == FLOW_TO_DISK || t == RING || t == RING_STRICT) return t; + } + return REJECT; +} + void QueuePolicy::setDefaultMaxSize(uint64_t s) { defaultMaxSize = s; } +void QueuePolicy::getPendingDequeues(Messages&) {} + + + + +void QueuePolicy::encode(Buffer& buffer) const +{ + buffer.putLong(maxCount); + buffer.putLongLong(maxSize); + buffer.putLong(count); + buffer.putLongLong(size); +} + +void QueuePolicy::decode ( Buffer& buffer ) +{ + maxCount = buffer.getLong(); + maxSize = buffer.getLongLong(); + count = buffer.getLong(); + size = buffer.getLongLong(); +} + + +uint32_t QueuePolicy::encodedSize() const { + return sizeof(uint32_t) + // maxCount + sizeof(uint64_t) + // maxSize + sizeof(uint32_t) + // count + sizeof(uint64_t); // size +} + + + const std::string QueuePolicy::maxCountKey("qpid.max_count"); const std::string QueuePolicy::maxSizeKey("qpid.max_size"); +const std::string QueuePolicy::typeKey("qpid.policy_type"); +const std::string QueuePolicy::REJECT("reject"); +const std::string QueuePolicy::FLOW_TO_DISK("flow_to_disk"); +const std::string QueuePolicy::RING("ring"); +const std::string QueuePolicy::RING_STRICT("ring_strict"); uint64_t QueuePolicy::defaultMaxSize(0); +FlowToDiskPolicy::FlowToDiskPolicy(const std::string& _name, uint32_t _maxCount, uint64_t _maxSize) : + QueuePolicy(_name, _maxCount, _maxSize, FLOW_TO_DISK) {} + +bool FlowToDiskPolicy::checkLimit(boost::intrusive_ptr<Message> m) +{ + if (!QueuePolicy::checkLimit(m)) m->requestContentRelease(); + return true; +} + +RingQueuePolicy::RingQueuePolicy(const std::string& _name, + uint32_t _maxCount, uint64_t _maxSize, const std::string& _type) : + QueuePolicy(_name, _maxCount, _maxSize, _type), strict(_type == RING_STRICT) {} + +bool before(const QueuedMessage& a, const QueuedMessage& b) +{ + return a.position < b.position; +} + +void RingQueuePolicy::enqueued(const QueuedMessage& m) +{ + //need to insert in correct location based on position + queue.insert(lower_bound(queue.begin(), queue.end(), m, before), m); +} + +void RingQueuePolicy::dequeued(const QueuedMessage& m) +{ + //find and remove m from queue + if (find(m, pendingDequeues, true) || find(m, queue, true)) { + //now update count and size + QueuePolicy::dequeued(m); + } +} + +bool RingQueuePolicy::isEnqueued(const QueuedMessage& m) +{ + //for non-strict ring policy, a message can be replaced (and + //therefore dequeued) before it is accepted or released by + //subscriber; need to detect this + return find(m, pendingDequeues, false) || find(m, queue, false); +} + +bool RingQueuePolicy::checkLimit(boost::intrusive_ptr<Message> m) +{ + if (QueuePolicy::checkLimit(m)) return true;//if haven't hit limit, ok to accept + + QueuedMessage oldest; + if (queue.empty()) { + QPID_LOG(debug, "Message too large for ring queue " << name + << " [" << *this << "] " + << ": message size = " << m->contentSize() << " bytes"); + return false; + } + oldest = queue.front(); + if (oldest.queue->acquire(oldest) || !strict) { + queue.pop_front(); + pendingDequeues.push_back(oldest); + QPID_LOG(debug, "Ring policy triggered in " << name + << ": removed message " << oldest.position << " to make way for new message"); + return true; + } else { + QPID_LOG(debug, "Ring policy could not be triggered in " << name + << ": oldest message (seq-no=" << oldest.position << ") has been delivered but not yet acknowledged or requeued"); + //in strict mode, if oldest message has been delivered (hence + //cannot be acquired) but not yet acked, it should not be + //removed and the attempted enqueue should fail + return false; + } +} + +void RingQueuePolicy::getPendingDequeues(Messages& result) +{ + result = pendingDequeues; +} + +bool RingQueuePolicy::find(const QueuedMessage& m, Messages& q, bool remove) +{ + for (Messages::iterator i = q.begin(); i != q.end(); i++) { + if (i->payload == m.payload) { + if (remove) q.erase(i); + return true; + } + } + return false; +} + +std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(uint32_t maxCount, uint64_t maxSize, const std::string& type) +{ + return createQueuePolicy("<unspecified>", maxCount, maxSize, type); +} + +std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(const qpid::framing::FieldTable& settings) +{ + return createQueuePolicy("<unspecified>", settings); +} + +std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(const std::string& name, const qpid::framing::FieldTable& settings) +{ + uint32_t maxCount = getInt(settings, maxCountKey, 0); + uint32_t maxSize = getInt(settings, maxSizeKey, defaultMaxSize); + if (maxCount || maxSize) { + return createQueuePolicy(name, maxCount, maxSize, getType(settings)); + } else { + return std::auto_ptr<QueuePolicy>(); + } +} + +std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(const std::string& name, + uint32_t maxCount, uint64_t maxSize, const std::string& type) +{ + if (type == RING || type == RING_STRICT) { + return std::auto_ptr<QueuePolicy>(new RingQueuePolicy(name, maxCount, maxSize, type)); + } else if (type == FLOW_TO_DISK) { + return std::auto_ptr<QueuePolicy>(new FlowToDiskPolicy(name, maxCount, maxSize)); + } else { + return std::auto_ptr<QueuePolicy>(new QueuePolicy(name, maxCount, maxSize, type)); + } + +} + namespace qpid { namespace broker { std::ostream& operator<<(std::ostream& out, const QueuePolicy& p) { if (p.maxSize) out << "size: max=" << p.maxSize << ", current=" << p.size; - else out << "size unlimited, current=" << p.size; + else out << "size: unlimited"; out << "; "; if (p.maxCount) out << "count: max=" << p.maxCount << ", current=" << p.count; - else out << "count unlimited, current=" << p.count; + else out << "count: unlimited"; + out << "; type=" << p.type; return out; } } } + diff --git a/cpp/src/qpid/broker/QueuePolicy.h b/cpp/src/qpid/broker/QueuePolicy.h index 4511a63b64..b2937e94c7 100644 --- a/cpp/src/qpid/broker/QueuePolicy.h +++ b/cpp/src/qpid/broker/QueuePolicy.h @@ -21,40 +21,100 @@ #ifndef _QueuePolicy_ #define _QueuePolicy_ +#include <deque> #include <iostream> +#include <memory> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/QueuedMessage.h" #include "qpid/framing/FieldTable.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/Mutex.h" namespace qpid { - namespace broker { - class QueuePolicy - { - static const std::string maxCountKey; - static const std::string maxSizeKey; - - static uint64_t defaultMaxSize; +namespace broker { + +class QueuePolicy +{ + static uint64_t defaultMaxSize; - const uint32_t maxCount; - const uint64_t maxSize; - uint32_t count; - uint64_t size; + uint32_t maxCount; + uint64_t maxSize; + const std::string type; + uint32_t count; + uint64_t size; + bool policyExceeded; - static int getInt(const qpid::framing::FieldTable& settings, const std::string& key, int defaultValue); - - public: - QueuePolicy(uint32_t maxCount, uint64_t maxSize); - QueuePolicy(const qpid::framing::FieldTable& settings); - void enqueued(uint64_t size); - void dequeued(uint64_t size); - void update(qpid::framing::FieldTable& settings); - bool limitExceeded(); - uint32_t getMaxCount() const { return maxCount; } - uint64_t getMaxSize() const { return maxSize; } - - static void setDefaultMaxSize(uint64_t); - friend std::ostream& operator<<(std::ostream&, const QueuePolicy&); - }; - } -} + static int getInt(const qpid::framing::FieldTable& settings, const std::string& key, int defaultValue); + + public: + typedef std::deque<QueuedMessage> Messages; + static QPID_BROKER_EXTERN const std::string maxCountKey; + static QPID_BROKER_EXTERN const std::string maxSizeKey; + static QPID_BROKER_EXTERN const std::string typeKey; + static QPID_BROKER_EXTERN const std::string REJECT; + static QPID_BROKER_EXTERN const std::string FLOW_TO_DISK; + static QPID_BROKER_EXTERN const std::string RING; + static QPID_BROKER_EXTERN const std::string RING_STRICT; + + virtual ~QueuePolicy() {} + QPID_BROKER_EXTERN void tryEnqueue(boost::intrusive_ptr<Message> msg); + QPID_BROKER_EXTERN void recoverEnqueued(boost::intrusive_ptr<Message> msg); + QPID_BROKER_EXTERN void enqueueAborted(boost::intrusive_ptr<Message> msg); + virtual void enqueued(const QueuedMessage&); + virtual void dequeued(const QueuedMessage&); + virtual bool isEnqueued(const QueuedMessage&); + QPID_BROKER_EXTERN void update(qpid::framing::FieldTable& settings); + uint32_t getMaxCount() const { return maxCount; } + uint64_t getMaxSize() const { return maxSize; } + void encode(framing::Buffer& buffer) const; + void decode ( framing::Buffer& buffer ); + uint32_t encodedSize() const; + virtual void getPendingDequeues(Messages& result); + + static QPID_BROKER_EXTERN std::auto_ptr<QueuePolicy> createQueuePolicy(const std::string& name, const qpid::framing::FieldTable& settings); + static QPID_BROKER_EXTERN std::auto_ptr<QueuePolicy> createQueuePolicy(const std::string& name, uint32_t maxCount, uint64_t maxSize, const std::string& type = REJECT); + static QPID_BROKER_EXTERN std::auto_ptr<QueuePolicy> createQueuePolicy(const qpid::framing::FieldTable& settings); + static QPID_BROKER_EXTERN std::auto_ptr<QueuePolicy> createQueuePolicy(uint32_t maxCount, uint64_t maxSize, const std::string& type = REJECT); + static std::string getType(const qpid::framing::FieldTable& settings); + static void setDefaultMaxSize(uint64_t); + friend QPID_BROKER_EXTERN std::ostream& operator<<(std::ostream&, + const QueuePolicy&); + protected: + const std::string name; + + QueuePolicy(const std::string& name, uint32_t maxCount, uint64_t maxSize, const std::string& type = REJECT); + + virtual bool checkLimit(boost::intrusive_ptr<Message> msg); + void enqueued(uint64_t size); + void dequeued(uint64_t size); +}; + + +class FlowToDiskPolicy : public QueuePolicy +{ + public: + FlowToDiskPolicy(const std::string& name, uint32_t maxCount, uint64_t maxSize); + bool checkLimit(boost::intrusive_ptr<Message> msg); +}; + +class RingQueuePolicy : public QueuePolicy +{ + public: + RingQueuePolicy(const std::string& name, uint32_t maxCount, uint64_t maxSize, const std::string& type = RING); + void enqueued(const QueuedMessage&); + void dequeued(const QueuedMessage&); + bool isEnqueued(const QueuedMessage&); + bool checkLimit(boost::intrusive_ptr<Message> msg); + void getPendingDequeues(Messages& result); + private: + Messages pendingDequeues; + Messages queue; + const bool strict; + + bool find(const QueuedMessage&, Messages&, bool remove); +}; + +}} #endif diff --git a/cpp/src/qpid/broker/QueueRegistry.cpp b/cpp/src/qpid/broker/QueueRegistry.cpp index 61bdb0ffde..4b1fa62709 100644 --- a/cpp/src/qpid/broker/QueueRegistry.cpp +++ b/cpp/src/qpid/broker/QueueRegistry.cpp @@ -18,7 +18,8 @@ * under the License. * */ -#include "QueueRegistry.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/QueueEvents.h" #include "qpid/log/Statement.h" #include <sstream> #include <assert.h> @@ -26,8 +27,8 @@ using namespace qpid::broker; using namespace qpid::sys; -QueueRegistry::QueueRegistry() : - counter(1), store(0), parent(0) {} +QueueRegistry::QueueRegistry(Broker* b) : + counter(1), store(0), events(0), parent(0), lastNode(false), broker(b) {} QueueRegistry::~QueueRegistry(){} @@ -41,8 +42,10 @@ QueueRegistry::declare(const string& declareName, bool durable, QueueMap::iterator i = queues.find(name); if (i == queues.end()) { - Queue::shared_ptr queue(new Queue(name, autoDelete, durable ? store : 0, owner, parent)); + Queue::shared_ptr queue(new Queue(name, autoDelete, durable ? store : 0, owner, parent, broker)); queues[name] = queue; + if (lastNode) queue->setLastNodeFailure(); + if (events) queue->setQueueEventManager(*events); return std::pair<Queue::shared_ptr, bool>(queue, true); } else { @@ -84,10 +87,27 @@ string QueueRegistry::generateName(){ void QueueRegistry::setStore (MessageStore* _store) { - assert (store == 0 && _store != 0); store = _store; } MessageStore* QueueRegistry::getStore() const { return store; } + +void QueueRegistry::updateQueueClusterState(bool _lastNode) +{ + RWlock::ScopedRlock locker(lock); + for (QueueMap::iterator i = queues.begin(); i != queues.end(); i++) { + if (_lastNode){ + i->second->setLastNodeFailure(); + } else { + i->second->clearLastNodeFailure(); + } + } + lastNode = _lastNode; +} + +void QueueRegistry::setQueueEvents(QueueEvents* e) +{ + events = e; +} diff --git a/cpp/src/qpid/broker/QueueRegistry.h b/cpp/src/qpid/broker/QueueRegistry.h index f7be1c551a..72a91dff24 100644 --- a/cpp/src/qpid/broker/QueueRegistry.h +++ b/cpp/src/qpid/broker/QueueRegistry.h @@ -21,14 +21,19 @@ #ifndef _QueueRegistry_ #define _QueueRegistry_ -#include <map> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Queue.h" #include "qpid/sys/Mutex.h" -#include "Queue.h" #include "qpid/management/Manageable.h" +#include <boost/bind.hpp> +#include <algorithm> +#include <map> namespace qpid { namespace broker { +class QueueEvents; + /** * A registry of queues indexed by queue name. * @@ -36,10 +41,10 @@ namespace broker { * are deleted when and only when they are no longer in use. * */ -class QueueRegistry{ +class QueueRegistry { public: - QueueRegistry(); - ~QueueRegistry(); + QPID_BROKER_EXTERN QueueRegistry(Broker* b = 0); + QPID_BROKER_EXTERN ~QueueRegistry(); /** * Declare a queue. @@ -47,8 +52,11 @@ class QueueRegistry{ * @return The queue and a boolean flag which is true if the queue * was created by this declare call false if it already existed. */ - std::pair<Queue::shared_ptr, bool> declare(const string& name, bool durable = false, bool autodelete = false, - const OwnershipToken* owner = 0); + QPID_BROKER_EXTERN std::pair<Queue::shared_ptr, bool> declare + (const string& name, + bool durable = false, + bool autodelete = false, + const OwnershipToken* owner = 0); /** * Destroy the named queue. @@ -62,7 +70,7 @@ class QueueRegistry{ * subsequent calls to find or declare with the same name. * */ - void destroy (const string& name); + QPID_BROKER_EXTERN void destroy(const string& name); template <class Test> bool destroyIf(const string& name, Test test) { qpid::sys::RWlock::ScopedWlock locker(lock); @@ -77,13 +85,15 @@ class QueueRegistry{ /** * Find the named queue. Return 0 if not found. */ - Queue::shared_ptr find(const string& name); + QPID_BROKER_EXTERN Queue::shared_ptr find(const string& name); /** * Generate unique queue name. */ string generateName(); + void setQueueEvents(QueueEvents*); + /** * Set the store to use. May only be called once. */ @@ -98,22 +108,37 @@ class QueueRegistry{ * Register the manageable parent for declared queues */ void setParent (management::Manageable* _parent) { parent = _parent; } + + /** Call f for each queue in the registry. */ + template <class F> void eachQueue(F f) const { + qpid::sys::RWlock::ScopedRlock l(lock); + for (QueueMap::const_iterator i = queues.begin(); i != queues.end(); ++i) + f(i->second); + } + + /** + * Change queue mode when cluster size drops to 1 node, expands again + * in practice allows flow queue to disk when last name to be exectuted + */ + void updateQueueClusterState(bool lastNode); private: typedef std::map<string, Queue::shared_ptr> QueueMap; QueueMap queues; - qpid::sys::RWlock lock; + mutable qpid::sys::RWlock lock; int counter; MessageStore* store; + QueueEvents* events; management::Manageable* parent; + bool lastNode; //used to set mode on queue declare + Broker* broker; //destroy impl that assumes lock is already held: void destroyLH (const string& name); }; -} -} +}} // namespace qpid::broker #endif diff --git a/cpp/src/qpid/broker/QueuedMessage.h b/cpp/src/qpid/broker/QueuedMessage.h new file mode 100644 index 0000000000..35e48b11f3 --- /dev/null +++ b/cpp/src/qpid/broker/QueuedMessage.h @@ -0,0 +1,48 @@ +/* + * + * 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 _QueuedMessage_ +#define _QueuedMessage_ + +#include "qpid/broker/Message.h" + +namespace qpid { +namespace broker { + +class Queue; + +struct QueuedMessage +{ + boost::intrusive_ptr<Message> payload; + framing::SequenceNumber position; + Queue* queue; + + QueuedMessage() : queue(0) {} + QueuedMessage(Queue* q, boost::intrusive_ptr<Message> msg, framing::SequenceNumber sn) : + payload(msg), position(sn), queue(q) {} + QueuedMessage(Queue* q) : queue(q) {} + +}; + inline bool operator<(const QueuedMessage& a, const QueuedMessage& b) { return a.position < b.position; } + +}} + + +#endif diff --git a/cpp/src/qpid/broker/RateFlowcontrol.h b/cpp/src/qpid/broker/RateFlowcontrol.h new file mode 100644 index 0000000000..99f9d2c0c4 --- /dev/null +++ b/cpp/src/qpid/broker/RateFlowcontrol.h @@ -0,0 +1,105 @@ +#ifndef broker_RateFlowcontrol_h +#define broker_RateFlowcontrol_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/Time.h" +#include "qpid/sys/IntegerTypes.h" + +#include <algorithm> + +namespace qpid { +namespace broker { + +// Class to keep track of issuing flow control to make sure that the peer doesn't exceed +// a given message rate +// +// Create the object with the target rate +// Then call sendCredit() whenever credit is issued to the peer +// Call receivedMessage() whenever a message is received, it returns the credit to issue. +// +// sentCredit() be sensibly called with a 0 parameter to indicate +// that we sent credit but treat it as if the value was 0 (we may do this at the start of the connection +// to allow our peer to send messages) +// +// receivedMessage() can be called with 0 to indicate that we've not received a message, but +// tell me what credit I can send. +class RateFlowcontrol { + uint32_t rate; // messages per second + uint32_t maxCredit; // max credit issued to client (issued at start) + uint32_t requestedCredit; + qpid::sys::AbsTime creditSent; + +public: + RateFlowcontrol(uint32_t r) : + rate(r), + maxCredit(0), + requestedCredit(0), + creditSent(qpid::sys::FAR_FUTURE) + {} + + uint32_t getRate() const { + return rate; + } + void sentCredit(const qpid::sys::AbsTime& t, uint32_t credit); + uint32_t receivedMessage(const qpid::sys::AbsTime& t, uint32_t msgs); + uint32_t availableCredit(const qpid::sys::AbsTime& t); + bool flowStopped() const; +}; + +inline void RateFlowcontrol::sentCredit(const qpid::sys::AbsTime& t, uint32_t credit) { + // If the client isn't currently requesting credit (ie it's not sent us anything yet) then + // this credit goes to the max credit held by the client (it can't go to reduce credit + // less than 0) + int32_t nextRequestedCredit = requestedCredit - credit; + if ( nextRequestedCredit<0 ) { + requestedCredit = 0; + maxCredit -= nextRequestedCredit; + } else { + requestedCredit = nextRequestedCredit; + } + creditSent = t; +} + +inline uint32_t RateFlowcontrol::availableCredit(const qpid::sys::AbsTime& t) { + qpid::sys::Duration d(creditSent, t); + // Could be -ve before first sentCredit + int64_t toSend = std::min(rate * d / qpid::sys::TIME_SEC, static_cast<int64_t>(requestedCredit)); + return toSend > 0 ? toSend : 0; +} + +inline uint32_t RateFlowcontrol::receivedMessage(const qpid::sys::AbsTime& t, uint32_t msgs) { + requestedCredit +=msgs; + // Don't send credit for every message, only send if more than 0.5s since last credit or + // we've got less than .25 of the max left (heuristic) + return requestedCredit*4 >= maxCredit*3 || qpid::sys::Duration(creditSent, t) >= 500*qpid::sys::TIME_MSEC + ? availableCredit(t) + : 0; +} + +inline bool RateFlowcontrol::flowStopped() const { + return requestedCredit >= maxCredit; +} + +}} + +#endif // broker_RateFlowcontrol_h diff --git a/cpp/src/qpid/broker/RateTracker.cpp b/cpp/src/qpid/broker/RateTracker.cpp new file mode 100644 index 0000000000..048349b658 --- /dev/null +++ b/cpp/src/qpid/broker/RateTracker.cpp @@ -0,0 +1,51 @@ +/* + * + * 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/RateTracker.h" + +using qpid::sys::AbsTime; +using qpid::sys::Duration; +using qpid::sys::TIME_SEC; + +namespace qpid { +namespace broker { + +RateTracker::RateTracker() : currentCount(0), lastCount(0), lastTime(AbsTime::now()) {} + +RateTracker& RateTracker::operator++() +{ + ++currentCount; + return *this; +} + +double RateTracker::sampleRatePerSecond() +{ + int32_t increment = currentCount - lastCount; + AbsTime now = AbsTime::now(); + Duration interval(lastTime, now); + lastCount = currentCount; + lastTime = now; + //if sampling at higher frequency than supported, will just return the number of increments + if (interval < TIME_SEC) return increment; + else if (increment == 0) return 0; + else return increment / (interval / TIME_SEC); +} + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/RateTracker.h b/cpp/src/qpid/broker/RateTracker.h new file mode 100644 index 0000000000..0c20b37312 --- /dev/null +++ b/cpp/src/qpid/broker/RateTracker.h @@ -0,0 +1,57 @@ +#ifndef QPID_BROKER_RATETRACKER_H +#define QPID_BROKER_RATETRACKER_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/Time.h" + +namespace qpid { +namespace broker { + +/** + * Simple rate tracker: represents some value that can be incremented, + * then can periodcially sample the rate of increments. + */ +class RateTracker +{ + public: + RateTracker(); + /** + * Increments the count being tracked. Can be called concurrently + * with other calls to this operator as well as with calls to + * sampleRatePerSecond(). + */ + RateTracker& operator++(); + /** + * Returns the rate of increments per second since last + * called. Calls to this method should be serialised, but can be + * called concurrently with the increment operator + */ + double sampleRatePerSecond(); + private: + volatile int32_t currentCount; + int32_t lastCount; + qpid::sys::AbsTime lastTime; +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_RATETRACKER_H*/ diff --git a/cpp/src/qpid/broker/RecoverableExchange.h b/cpp/src/qpid/broker/RecoverableExchange.h index 76d0d2ecdf..ca6cc1541e 100644 --- a/cpp/src/qpid/broker/RecoverableExchange.h +++ b/cpp/src/qpid/broker/RecoverableExchange.h @@ -40,7 +40,9 @@ public: /** * Recover binding. Nb: queue must have been recovered earlier. */ - virtual void bind(std::string& queue, std::string& routingKey, qpid::framing::FieldTable& args) = 0; + virtual void bind(const std::string& queue, + const std::string& routingKey, + qpid::framing::FieldTable& args) = 0; virtual ~RecoverableExchange() {}; }; diff --git a/cpp/src/qpid/broker/RecoverableMessage.h b/cpp/src/qpid/broker/RecoverableMessage.h index f755fdf727..c98857ceb0 100644 --- a/cpp/src/qpid/broker/RecoverableMessage.h +++ b/cpp/src/qpid/broker/RecoverableMessage.h @@ -37,6 +37,7 @@ class RecoverableMessage public: typedef boost::shared_ptr<RecoverableMessage> shared_ptr; virtual void setPersistenceId(uint64_t id) = 0; + virtual void setRedelivered() = 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/RecoverableQueue.h b/cpp/src/qpid/broker/RecoverableQueue.h index b32bae7f07..49f05f97a1 100644 --- a/cpp/src/qpid/broker/RecoverableQueue.h +++ b/cpp/src/qpid/broker/RecoverableQueue.h @@ -22,7 +22,7 @@ * */ -#include "RecoverableMessage.h" +#include "qpid/broker/RecoverableMessage.h" #include <boost/shared_ptr.hpp> namespace qpid { diff --git a/cpp/src/qpid/broker/RecoverableTransaction.h b/cpp/src/qpid/broker/RecoverableTransaction.h index 7fe34b6756..1b7d94bd1a 100644 --- a/cpp/src/qpid/broker/RecoverableTransaction.h +++ b/cpp/src/qpid/broker/RecoverableTransaction.h @@ -24,8 +24,8 @@ #include <boost/shared_ptr.hpp> -#include "RecoverableMessage.h" -#include "RecoverableQueue.h" +#include "qpid/broker/RecoverableMessage.h" +#include "qpid/broker/RecoverableQueue.h" namespace qpid { namespace broker { diff --git a/cpp/src/qpid/broker/RecoveredDequeue.cpp b/cpp/src/qpid/broker/RecoveredDequeue.cpp index e2d70964fb..658fd5a89e 100644 --- a/cpp/src/qpid/broker/RecoveredDequeue.cpp +++ b/cpp/src/qpid/broker/RecoveredDequeue.cpp @@ -18,22 +18,29 @@ * under the License. * */ -#include "RecoveredDequeue.h" +#include "qpid/broker/RecoveredDequeue.h" using boost::intrusive_ptr; using namespace qpid::broker; -RecoveredDequeue::RecoveredDequeue(Queue::shared_ptr _queue, intrusive_ptr<Message> _msg) : queue(_queue), msg(_msg) {} +RecoveredDequeue::RecoveredDequeue(Queue::shared_ptr _queue, intrusive_ptr<Message> _msg) : queue(_queue), msg(_msg) +{ + queue->recoverPrepared(msg); +} -bool RecoveredDequeue::prepare(TransactionContext*) throw(){ +bool RecoveredDequeue::prepare(TransactionContext*) throw() +{ //should never be called; transaction has already prepared if an enqueue is recovered return false; } -void RecoveredDequeue::commit() throw(){ +void RecoveredDequeue::commit() throw() +{ + queue->enqueueAborted(msg); } -void RecoveredDequeue::rollback() throw(){ +void RecoveredDequeue::rollback() throw() +{ msg->enqueueComplete(); queue->process(msg); } diff --git a/cpp/src/qpid/broker/RecoveredDequeue.h b/cpp/src/qpid/broker/RecoveredDequeue.h index 276e1f4c5c..67b37db5f9 100644 --- a/cpp/src/qpid/broker/RecoveredDequeue.h +++ b/cpp/src/qpid/broker/RecoveredDequeue.h @@ -21,11 +21,11 @@ #ifndef _RecoveredDequeue_ #define _RecoveredDequeue_ -#include "Deliverable.h" -#include "Message.h" -#include "MessageStore.h" -#include "Queue.h" -#include "TxOp.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/TxOp.h" #include <boost/intrusive_ptr.hpp> @@ -45,6 +45,10 @@ namespace qpid { virtual void commit() throw(); virtual void rollback() throw(); virtual ~RecoveredDequeue(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + + Queue::shared_ptr getQueue() const { return queue; } + boost::intrusive_ptr<Message> getMessage() const { return msg; } }; } } diff --git a/cpp/src/qpid/broker/RecoveredEnqueue.cpp b/cpp/src/qpid/broker/RecoveredEnqueue.cpp index 1984a5d4a8..48faa0942c 100644 --- a/cpp/src/qpid/broker/RecoveredEnqueue.cpp +++ b/cpp/src/qpid/broker/RecoveredEnqueue.cpp @@ -18,12 +18,15 @@ * under the License. * */ -#include "RecoveredEnqueue.h" +#include "qpid/broker/RecoveredEnqueue.h" using boost::intrusive_ptr; using namespace qpid::broker; -RecoveredEnqueue::RecoveredEnqueue(Queue::shared_ptr _queue, intrusive_ptr<Message> _msg) : queue(_queue), msg(_msg) {} +RecoveredEnqueue::RecoveredEnqueue(Queue::shared_ptr _queue, intrusive_ptr<Message> _msg) : queue(_queue), msg(_msg) +{ + queue->recoverPrepared(msg); +} bool RecoveredEnqueue::prepare(TransactionContext*) throw(){ //should never be called; transaction has already prepared if an enqueue is recovered @@ -36,5 +39,6 @@ void RecoveredEnqueue::commit() throw(){ } void RecoveredEnqueue::rollback() throw(){ + queue->enqueueAborted(msg); } diff --git a/cpp/src/qpid/broker/RecoveredEnqueue.h b/cpp/src/qpid/broker/RecoveredEnqueue.h index 6525179769..09f928f098 100644 --- a/cpp/src/qpid/broker/RecoveredEnqueue.h +++ b/cpp/src/qpid/broker/RecoveredEnqueue.h @@ -21,11 +21,11 @@ #ifndef _RecoveredEnqueue_ #define _RecoveredEnqueue_ -#include "Deliverable.h" -#include "Message.h" -#include "MessageStore.h" -#include "Queue.h" -#include "TxOp.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/TxOp.h" #include <boost/intrusive_ptr.hpp> @@ -45,6 +45,11 @@ namespace qpid { virtual void commit() throw(); virtual void rollback() throw(); virtual ~RecoveredEnqueue(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + + Queue::shared_ptr getQueue() const { return queue; } + boost::intrusive_ptr<Message> getMessage() const { return msg; } + }; } } diff --git a/cpp/src/qpid/broker/RecoveryManager.h b/cpp/src/qpid/broker/RecoveryManager.h index 7dcbe3a2b0..2929e92250 100644 --- a/cpp/src/qpid/broker/RecoveryManager.h +++ b/cpp/src/qpid/broker/RecoveryManager.h @@ -21,12 +21,12 @@ #ifndef _RecoveryManager_ #define _RecoveryManager_ -#include "RecoverableExchange.h" -#include "RecoverableQueue.h" -#include "RecoverableMessage.h" -#include "RecoverableTransaction.h" -#include "RecoverableConfig.h" -#include "TransactionalStore.h" +#include "qpid/broker/RecoverableExchange.h" +#include "qpid/broker/RecoverableQueue.h" +#include "qpid/broker/RecoverableMessage.h" +#include "qpid/broker/RecoverableTransaction.h" +#include "qpid/broker/RecoverableConfig.h" +#include "qpid/broker/TransactionalStore.h" #include "qpid/framing/Buffer.h" namespace qpid { diff --git a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp index b058978ccf..12ac2d2bfd 100644 --- a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp +++ b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp @@ -18,21 +18,22 @@ * under the License. * */ -#include "RecoveryManagerImpl.h" - -#include "Message.h" -#include "Queue.h" -#include "Link.h" -#include "Bridge.h" -#include "RecoveredEnqueue.h" -#include "RecoveredDequeue.h" +#include "qpid/broker/RecoveryManagerImpl.h" + +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/Bridge.h" +#include "qpid/broker/RecoveredEnqueue.h" +#include "qpid/broker/RecoveredDequeue.h" #include "qpid/framing/reply_exceptions.h" -using namespace qpid; -using namespace qpid::broker; using boost::dynamic_pointer_cast; using boost::intrusive_ptr; +namespace qpid { +namespace broker { + RecoveryManagerImpl::RecoveryManagerImpl(QueueRegistry& _queues, ExchangeRegistry& _exchanges, LinkRegistry& _links, DtxManager& _dtxMgr, uint64_t _stagingThreshold) : queues(_queues), exchanges(_exchanges), links(_links), dtxMgr(_dtxMgr), stagingThreshold(_stagingThreshold) {} @@ -44,10 +45,10 @@ class RecoverableMessageImpl : public RecoverableMessage intrusive_ptr<Message> msg; const uint64_t stagingThreshold; public: - RecoverableMessageImpl(const intrusive_ptr<Message>& _msg, uint64_t _stagingThreshold) - : msg(_msg), stagingThreshold(_stagingThreshold) {} + RecoverableMessageImpl(const intrusive_ptr<Message>& _msg, uint64_t _stagingThreshold); ~RecoverableMessageImpl() {}; void setPersistenceId(uint64_t id); + void setRedelivered(); bool loadContent(uint64_t available); void decodeContent(framing::Buffer& buffer); void recover(Queue::shared_ptr queue); @@ -59,7 +60,7 @@ class RecoverableQueueImpl : public RecoverableQueue { Queue::shared_ptr queue; public: - RecoverableQueueImpl(Queue::shared_ptr& _queue) : queue(_queue) {} + RecoverableQueueImpl(const boost::shared_ptr<Queue>& _queue) : queue(_queue) {} ~RecoverableQueueImpl() {}; void setPersistenceId(uint64_t id); uint64_t getPersistenceId() const; @@ -78,7 +79,7 @@ class RecoverableExchangeImpl : public RecoverableExchange public: RecoverableExchangeImpl(Exchange::shared_ptr _exchange, QueueRegistry& _queues) : exchange(_exchange), queues(_queues) {} void setPersistenceId(uint64_t id); - void bind(std::string& queue, std::string& routingKey, qpid::framing::FieldTable& args); + void bind(const std::string& queue, const std::string& routingKey, qpid::framing::FieldTable& args); }; class RecoverableConfigImpl : public RecoverableConfig @@ -102,18 +103,24 @@ public: RecoverableExchange::shared_ptr RecoveryManagerImpl::recoverExchange(framing::Buffer& buffer) { - return RecoverableExchange::shared_ptr(new RecoverableExchangeImpl(Exchange::decode(exchanges, buffer), queues)); + Exchange::shared_ptr e = Exchange::decode(exchanges, buffer); + if (e) { + return RecoverableExchange::shared_ptr(new RecoverableExchangeImpl(e, queues)); + } else { + return RecoverableExchange::shared_ptr(); + } } RecoverableQueue::shared_ptr RecoveryManagerImpl::recoverQueue(framing::Buffer& buffer) { - Queue::shared_ptr queue = Queue::decode(queues, buffer); + Queue::shared_ptr queue = Queue::decode(queues, buffer, true); try { Exchange::shared_ptr exchange = exchanges.getDefault(); if (exchange) { exchange->bind(queue, queue->getName(), 0); + queue->bound(exchange->getName(), queue->getName(), framing::FieldTable()); } - } catch (const framing::NotFoundException& e) { + } catch (const framing::NotFoundException& /*e*/) { //assume no default exchange has been declared } return RecoverableQueue::shared_ptr(new RecoverableQueueImpl(queue)); @@ -149,7 +156,16 @@ RecoverableConfig::shared_ptr RecoveryManagerImpl::recoverConfig(framing::Buffer void RecoveryManagerImpl::recoveryComplete() { - //TODO (finalise binding setup etc) + //notify all queues and exchanges + queues.eachQueue(boost::bind(&Queue::recoveryComplete, _1, boost::ref(exchanges))); + exchanges.eachExchange(boost::bind(&Exchange::recoveryComplete, _1, boost::ref(exchanges))); +} + +RecoverableMessageImpl:: RecoverableMessageImpl(const intrusive_ptr<Message>& _msg, uint64_t _stagingThreshold) : msg(_msg), stagingThreshold(_stagingThreshold) +{ + if (!msg->isPersistent()) { + msg->forcePersistent(); // set so that message will get dequeued from store. + } } bool RecoverableMessageImpl::loadContent(uint64_t available) @@ -172,6 +188,11 @@ void RecoverableMessageImpl::setPersistenceId(uint64_t id) msg->setPersistenceId(id); } +void RecoverableMessageImpl::setRedelivered() +{ + msg->redeliver(); +} + void RecoverableQueueImpl::recover(RecoverableMessage::shared_ptr msg) { dynamic_pointer_cast<RecoverableMessageImpl>(msg)->recover(queue); @@ -181,7 +202,7 @@ void RecoverableQueueImpl::setPersistenceId(uint64_t id) { queue->setPersistenceId(id); } - + uint64_t RecoverableQueueImpl::getPersistenceId() const { return queue->getPersistenceId(); @@ -215,10 +236,13 @@ void RecoverableConfigImpl::setPersistenceId(uint64_t id) bridge->setPersistenceId(id); } -void RecoverableExchangeImpl::bind(string& queueName, string& key, framing::FieldTable& args) +void RecoverableExchangeImpl::bind(const string& queueName, + const string& key, + framing::FieldTable& args) { Queue::shared_ptr queue = queues.find(queueName); exchange->bind(queue, key, &args); + queue->bound(exchange->getName(), key, args); } void RecoverableMessageImpl::dequeue(DtxBuffer::shared_ptr buffer, Queue::shared_ptr queue) @@ -251,3 +275,5 @@ void RecoverableTransactionImpl::enqueue(RecoverableQueue::shared_ptr queue, Rec { dynamic_pointer_cast<RecoverableQueueImpl>(queue)->enqueue(buffer, message); } + +}} diff --git a/cpp/src/qpid/broker/RecoveryManagerImpl.h b/cpp/src/qpid/broker/RecoveryManagerImpl.h index cd34d464f5..6fbbfc4a6c 100644 --- a/cpp/src/qpid/broker/RecoveryManagerImpl.h +++ b/cpp/src/qpid/broker/RecoveryManagerImpl.h @@ -22,11 +22,11 @@ #define _RecoveryManagerImpl_ #include <list> -#include "DtxManager.h" -#include "ExchangeRegistry.h" -#include "QueueRegistry.h" -#include "LinkRegistry.h" -#include "RecoveryManager.h" +#include "qpid/broker/DtxManager.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/RecoveryManager.h" namespace qpid { namespace broker { diff --git a/cpp/src/qpid/broker/RetryList.cpp b/cpp/src/qpid/broker/RetryList.cpp new file mode 100644 index 0000000000..8f600c086d --- /dev/null +++ b/cpp/src/qpid/broker/RetryList.cpp @@ -0,0 +1,60 @@ +/* + * + * 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/RetryList.h" + +namespace qpid { +namespace broker { + +RetryList::RetryList() : urlIndex(0), addressIndex(0) {} + +void RetryList::reset(const std::vector<Url>& u) +{ + urls = u; + urlIndex = addressIndex = 0;//reset indices +} + +bool RetryList::next(TcpAddress& address) +{ + while (urlIndex < urls.size()) { + while (addressIndex < urls[urlIndex].size()) { + const TcpAddress* tcp = urls[urlIndex][addressIndex++].get<TcpAddress>(); + if (tcp) { + address = *tcp; + return true; + } + } + urlIndex++; + addressIndex = 0; + } + + urlIndex = addressIndex = 0;//reset indices + return false; +} + +std::ostream& operator<<(std::ostream& os, const RetryList& l) +{ + for (size_t i = 0; i < l.urls.size(); i++) { + os << l.urls[i] << " "; + } + return os; +} + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/RetryList.h b/cpp/src/qpid/broker/RetryList.h new file mode 100644 index 0000000000..f87adf2c8d --- /dev/null +++ b/cpp/src/qpid/broker/RetryList.h @@ -0,0 +1,54 @@ +#ifndef QPID_BROKER_RETRYLIST_H +#define QPID_BROKER_RETRYLIST_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/BrokerImportExport.h" +#include "qpid/Address.h" +#include "qpid/Url.h" + +namespace qpid { +namespace broker { + +/** + * Simple utility for managing a list of urls to try on reconnecting a + * link. Currently only supports TCP urls. + */ +class RetryList +{ + public: + QPID_BROKER_EXTERN RetryList(); + QPID_BROKER_EXTERN void reset(const std::vector<Url>& urls); + QPID_BROKER_EXTERN bool next(TcpAddress& address); + private: + std::vector<Url> urls; + size_t urlIndex; + size_t addressIndex; + + friend std::ostream& operator<<(std::ostream& os, const RetryList& l); +}; + +std::ostream& operator<<(std::ostream& os, const RetryList& l); + +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_RETRYLIST_H*/ diff --git a/cpp/src/qpid/broker/SaslAuthenticator.cpp b/cpp/src/qpid/broker/SaslAuthenticator.cpp index 136cf6f785..0e509c8d93 100644 --- a/cpp/src/qpid/broker/SaslAuthenticator.cpp +++ b/cpp/src/qpid/broker/SaslAuthenticator.cpp @@ -19,17 +19,25 @@ * */ -#include "config.h" +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif -#include "Connection.h" +#include "qpid/broker/Connection.h" #include "qpid/log/Statement.h" #include "qpid/framing/reply_exceptions.h" +#include <boost/format.hpp> #if HAVE_SASL #include <sasl/sasl.h> +#include "qpid/sys/cyrus/CyrusSecurityLayer.h" +using qpid::sys::cyrus::CyrusSecurityLayer; #endif using namespace qpid::framing; +using qpid::sys::SecurityLayer; +using boost::format; +using boost::str; namespace qpid { namespace broker { @@ -39,12 +47,15 @@ class NullAuthenticator : public SaslAuthenticator { Connection& connection; framing::AMQP_ClientProxy::Connection client; + std::string realm; + const bool encrypt; public: - NullAuthenticator(Connection& connection); + NullAuthenticator(Connection& connection, bool encrypt); ~NullAuthenticator(); void getMechanisms(framing::Array& mechanisms); void start(const std::string& mechanism, const std::string& response); void step(const std::string&) {} + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); }; #if HAVE_SASL @@ -54,62 +65,132 @@ class CyrusAuthenticator : public SaslAuthenticator sasl_conn_t *sasl_conn; Connection& connection; framing::AMQP_ClientProxy::Connection client; + const bool encrypt; void processAuthenticationStep(int code, const char *challenge, unsigned int challenge_len); public: - CyrusAuthenticator(Connection& connection); + CyrusAuthenticator(Connection& connection, bool encrypt); ~CyrusAuthenticator(); void init(); void getMechanisms(framing::Array& mechanisms); void start(const std::string& mechanism, const std::string& response); void step(const std::string& response); + void getUid(std::string& uid); + void getError(std::string& error); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); }; +bool SaslAuthenticator::available(void) +{ + return true; +} + +// Initialize the SASL mechanism; throw if it fails. +void SaslAuthenticator::init(const std::string& saslName) +{ + int code = sasl_server_init(NULL, saslName.c_str()); + if (code != SASL_OK) { + // TODO: Figure out who owns the char* returned by + // sasl_errstring, though it probably does not matter much + throw Exception(sasl_errstring(code, NULL, NULL)); + } +} + +void SaslAuthenticator::fini(void) +{ + sasl_done(); +} + #else typedef NullAuthenticator CyrusAuthenticator; +bool SaslAuthenticator::available(void) +{ + return false; +} + +void SaslAuthenticator::init(const std::string& /*saslName*/) +{ + throw Exception("Requested authentication but SASL unavailable"); +} + +void SaslAuthenticator::fini(void) +{ + return; +} + #endif std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c) { + static bool needWarning = true; if (c.getBroker().getOptions().auth) { - return std::auto_ptr<SaslAuthenticator>(new CyrusAuthenticator(c)); + return std::auto_ptr<SaslAuthenticator>(new CyrusAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); } else { - return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c)); + QPID_LOG(debug, "SASL: No Authentication Performed"); + needWarning = false; + return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); } } -NullAuthenticator::NullAuthenticator(Connection& c) : connection(c), client(c.getOutput()) {} +NullAuthenticator::NullAuthenticator(Connection& c, bool e) : connection(c), client(c.getOutput()), + realm(c.getBroker().getOptions().realm), encrypt(e) {} NullAuthenticator::~NullAuthenticator() {} void NullAuthenticator::getMechanisms(Array& mechanisms) { mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("ANONYMOUS"))); + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("PLAIN")));//useful for testing } void NullAuthenticator::start(const string& mechanism, const string& response) { - QPID_LOG(warning, "SASL: No Authentication Performed"); + if (encrypt) { + QPID_LOG(error, "Rejected un-encrypted connection."); + throw ConnectionForcedException("Connection must be encrypted."); + } if (mechanism == "PLAIN") { // Old behavior - if (response.size() > 0 && response[0] == (char) 0) { - string temp = response.substr(1); - string::size_type i = temp.find((char)0); - string uid = temp.substr(0, i); - string pwd = temp.substr(i + 1); - connection.setUserId(uid); + if (response.size() > 0) { + string uid; + 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 != string::npos) uid = response.substr(1, i-1); + } else if (i != string::npos) { + //authorization id is first null delimited field + uid = response.substr(0, i); + }//else not a valid SASL PLAIN response, throw error? + if (!uid.empty()) { + //append realm if it has not already been added + i = uid.find(realm); + if (i == string::npos || realm.size() + i < uid.size()) { + uid = str(format("%1%@%2%") % uid % realm); + } + connection.setUserId(uid); + } } } else { connection.setUserId("anonymous"); } - client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax()); +} + + +std::auto_ptr<SecurityLayer> NullAuthenticator::getSecurityLayer(uint16_t) +{ + std::auto_ptr<SecurityLayer> securityLayer; + return securityLayer; } #if HAVE_SASL -CyrusAuthenticator::CyrusAuthenticator(Connection& c) : sasl_conn(0), connection(c), client(c.getOutput()) + +CyrusAuthenticator::CyrusAuthenticator(Connection& c, bool _encrypt) : + sasl_conn(0), connection(c), client(c.getOutput()), encrypt(_encrypt) { init(); } @@ -145,6 +226,39 @@ void CyrusAuthenticator::init() // server error, when one is available throw ConnectionForcedException("Unable to perform authentication"); } + + sasl_security_properties_t secprops; + + //TODO: should the actual SSF values be configurable here? + secprops.min_ssf = encrypt ? 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. + sasl_ssf_t external_ssf = (sasl_ssf_t) connection.getSSF(); + 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 ); + + secprops.maxbufsize = 65535; + secprops.property_names = 0; + secprops.property_values = 0; + secprops.security_flags = 0; /* or SASL_SEC_NOANONYMOUS etc as appropriate */ + + int result = sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << result)); + } + } CyrusAuthenticator::~CyrusAuthenticator() @@ -155,6 +269,23 @@ CyrusAuthenticator::~CyrusAuthenticator() } } +void CyrusAuthenticator::getError(string& error) +{ + error = string(sasl_errdetail(sasl_conn)); +} + +void CyrusAuthenticator::getUid(string& uid) +{ + int code; + const void* ptr; + + code = sasl_getprop(sasl_conn, SASL_USERNAME, &ptr); + if (SASL_OK != code) + return; + + uid = string(const_cast<char*>(static_cast<const char*>(ptr))); +} + void CyrusAuthenticator::getMechanisms(Array& mechanisms) { const char *separator = " "; @@ -239,7 +370,7 @@ void CyrusAuthenticator::processAuthenticationStep(int code, const char *challen connection.setUserId(const_cast<char*>(static_cast<const char*>(uid))); - client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax()); } else if (SASL_CONTINUE == code) { string challenge_str(challenge, challenge_len); @@ -264,6 +395,24 @@ void CyrusAuthenticator::processAuthenticationStep(int code, const char *challen } } } + +std::auto_ptr<SecurityLayer> CyrusAuthenticator::getSecurityLayer(uint16_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) { + QPID_LOG(info, "Installing security layer, SSF: "<< ssf); + securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize)); + } + return securityLayer; +} + #endif }} diff --git a/cpp/src/qpid/broker/SaslAuthenticator.h b/cpp/src/qpid/broker/SaslAuthenticator.h index c2d4ecf7c0..8ddaeb19a4 100644 --- a/cpp/src/qpid/broker/SaslAuthenticator.h +++ b/cpp/src/qpid/broker/SaslAuthenticator.h @@ -24,6 +24,7 @@ #include "qpid/framing/amqp_types.h" #include "qpid/framing/AMQP_ClientProxy.h" #include "qpid/Exception.h" +#include "qpid/sys/SecurityLayer.h" #include <memory> namespace qpid { @@ -38,6 +39,15 @@ public: virtual void getMechanisms(framing::Array& mechanisms) = 0; virtual void start(const std::string& mechanism, const std::string& response) = 0; virtual void step(const std::string& response) = 0; + virtual void getUid(std::string&) {} + virtual void getError(std::string&) {} + virtual std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(uint16_t maxFrameSize) = 0; + + static bool available(void); + + // Initialize the SASL mechanism; throw if it fails. + static void init(const std::string& saslName); + static void fini(void); static std::auto_ptr<SaslAuthenticator> createAuthenticator(Connection& connection); }; diff --git a/cpp/src/qpid/broker/SecureConnection.cpp b/cpp/src/qpid/broker/SecureConnection.cpp new file mode 100644 index 0000000000..74aec239ca --- /dev/null +++ b/cpp/src/qpid/broker/SecureConnection.cpp @@ -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. + * + */ +#include "qpid/broker/SecureConnection.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/framing/reply_exceptions.h" + +namespace qpid { +namespace broker { + +using qpid::sys::SecurityLayer; + +SecureConnection::SecureConnection() : secured(false) {} + +size_t SecureConnection::decode(const char* buffer, size_t size) +{ + if (!secured && securityLayer.get()) { + //security layer comes into effect on first read after its + //activated + secured = true; + } + if (secured) { + return securityLayer->decode(buffer, size); + } else { + return codec->decode(buffer, size); + } +} + +size_t SecureConnection::encode(const char* buffer, size_t size) +{ + if (secured) { + return securityLayer->encode(buffer, size); + } else { + return codec->encode(buffer, size); + } +} + +bool SecureConnection::canEncode() +{ + if (secured) return securityLayer->canEncode(); + else return codec->canEncode(); +} + +void SecureConnection::closed() +{ + codec->closed(); +} + +bool SecureConnection::isClosed() const +{ + return codec->isClosed(); +} + +framing::ProtocolVersion SecureConnection::getVersion() const +{ + return codec->getVersion(); +} + +void SecureConnection:: setCodec(std::auto_ptr<ConnectionCodec> c) +{ + codec = c; +} + +void SecureConnection::activateSecurityLayer(std::auto_ptr<SecurityLayer> sl) +{ + securityLayer = sl; + securityLayer->init(codec.get()); +} + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SecureConnection.h b/cpp/src/qpid/broker/SecureConnection.h new file mode 100644 index 0000000000..4a0cc50e34 --- /dev/null +++ b/cpp/src/qpid/broker/SecureConnection.h @@ -0,0 +1,60 @@ +#ifndef QPID_BROKER_SECURECONNECTION_H +#define QPID_BROKER_SECURECONNECTION_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 <memory> + +namespace qpid { + +namespace sys { +class SecurityLayer; +} + +namespace broker { + +/** + * A ConnectionCodec 'wrapper' that allows a connection to be + * 'secured' e.g. encrypted based on settings negotiatiated at the + * time of establishment. + */ +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); + bool canEncode(); + void closed(); + bool isClosed() const; + framing::ProtocolVersion getVersion() const; + void setCodec(std::auto_ptr<ConnectionCodec>); + void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + private: + std::auto_ptr<ConnectionCodec> codec; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + bool secured; +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_SECURECONNECTION_H*/ diff --git a/cpp/src/qpid/broker/SecureConnectionFactory.cpp b/cpp/src/qpid/broker/SecureConnectionFactory.cpp new file mode 100644 index 0000000000..5a31dbceeb --- /dev/null +++ b/cpp/src/qpid/broker/SecureConnectionFactory.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 "qpid/broker/SecureConnectionFactory.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/amqp_0_10/Connection.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/SecureConnection.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace broker { + +using framing::ProtocolVersion; +typedef std::auto_ptr<amqp_0_10::Connection> CodecPtr; +typedef std::auto_ptr<SecureConnection> SecureConnectionPtr; +typedef std::auto_ptr<Connection> ConnectionPtr; +typedef std::auto_ptr<sys::ConnectionInputHandler> InputPtr; + +SecureConnectionFactory::SecureConnectionFactory(Broker& b) : broker(b) {} + +sys::ConnectionCodec* +SecureConnectionFactory::create(ProtocolVersion v, sys::OutputControl& out, const std::string& id, + unsigned int conn_ssf ) { + if (broker.getConnectionCounter().allowConnection()) + { + QPID_LOG(error, "Client max connection count limit exceeded: " << broker.getOptions().maxConnections << " connection refused"); + return 0; + } + if (v == ProtocolVersion(0, 10)) { + SecureConnectionPtr sc(new SecureConnection()); + CodecPtr c(new amqp_0_10::Connection(out, id, false)); + ConnectionPtr i(new broker::Connection(c.get(), broker, id, conn_ssf, false)); + i->setSecureConnection(sc.get()); + c->setInputHandler(InputPtr(i.release())); + sc->setCodec(std::auto_ptr<sys::ConnectionCodec>(c)); + return sc.release(); + } + return 0; +} + +sys::ConnectionCodec* +SecureConnectionFactory::create(sys::OutputControl& out, const std::string& id, + unsigned int conn_ssf) { + // used to create connections from one broker to another + SecureConnectionPtr sc(new SecureConnection()); + CodecPtr c(new amqp_0_10::Connection(out, id, true)); + ConnectionPtr i(new broker::Connection(c.get(), broker, id, conn_ssf, true )); + i->setSecureConnection(sc.get()); + c->setInputHandler(InputPtr(i.release())); + sc->setCodec(std::auto_ptr<sys::ConnectionCodec>(c)); + return sc.release(); +} + + +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SecureConnectionFactory.h b/cpp/src/qpid/broker/SecureConnectionFactory.h new file mode 100644 index 0000000000..b1af6d4a0f --- /dev/null +++ b/cpp/src/qpid/broker/SecureConnectionFactory.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 _SecureConnectionFactory_ +#define _SecureConnectionFactory_ + +#include "qpid/sys/ConnectionCodec.h" + +namespace qpid { +namespace broker { +class Broker; + +class SecureConnectionFactory : public sys::ConnectionCodec::Factory +{ + public: + SecureConnectionFactory(Broker& b); + + sys::ConnectionCodec* + create(framing::ProtocolVersion, sys::OutputControl&, const std::string& id, + unsigned int conn_ssf); + + sys::ConnectionCodec* + create(sys::OutputControl&, const std::string& id, + unsigned int conn_ssf); + + private: + Broker& broker; +}; + +}} + + +#endif diff --git a/cpp/src/qpid/broker/SemanticState.cpp b/cpp/src/qpid/broker/SemanticState.cpp index 4d5c4e7537..e9b6aad967 100644 --- a/cpp/src/qpid/broker/SemanticState.cpp +++ b/cpp/src/qpid/broker/SemanticState.cpp @@ -19,21 +19,23 @@ * */ -#include "SessionState.h" -#include "Connection.h" -#include "DeliverableMessage.h" -#include "DtxAck.h" -#include "DtxTimeout.h" -#include "Message.h" -#include "Queue.h" -#include "SessionContext.h" -#include "TxAccept.h" -#include "TxPublish.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/DtxAck.h" +#include "qpid/broker/DtxTimeout.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/SessionContext.h" +#include "qpid/broker/TxAccept.h" +#include "qpid/broker/TxPublish.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/framing/IsInSequenceSet.h" #include "qpid/log/Statement.h" #include "qpid/ptr_map.h" -#include "AclModule.h" +#include "qpid/broker/AclModule.h" #include <boost/bind.hpp> #include <boost/format.hpp> @@ -49,30 +51,35 @@ namespace qpid { namespace broker { -using std::mem_fun_ref; +using namespace std; using boost::intrusive_ptr; +using boost::bind; using namespace qpid::broker; using namespace qpid::framing; using namespace qpid::sys; using qpid::ptr_map_ptr; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +namespace _qmf = qmf::org::apache::qpid::broker; SemanticState::SemanticState(DeliveryAdapter& da, SessionContext& ss) : session(ss), deliveryAdapter(da), - prefetchSize(0), - prefetchCount(0), tagGenerator("sgen"), dtxSelected(false), - outputTasks(ss) + authMsg(getSession().getBroker().getOptions().auth && !getSession().getConnection().isFederationLink()), + userID(getSession().getConnection().getUserId()), + defaultRealm(getSession().getBroker().getOptions().realm) { - outstanding.reset(); acl = getSession().getBroker().getAcl(); } SemanticState::~SemanticState() { //cancel all consumers for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { - cancel(*ptr_map_ptr(i)); + cancel(i->second); } if (dtxBuffer.get()) { @@ -85,23 +92,20 @@ bool SemanticState::exists(const string& consumerTag){ return consumers.find(consumerTag) != consumers.end(); } -void SemanticState::consume(DeliveryToken::shared_ptr token, string& tagInOut, - Queue::shared_ptr queue, bool nolocal, bool ackRequired, bool acquire, - bool exclusive, const FieldTable*) +void SemanticState::consume(const string& tag, + Queue::shared_ptr queue, bool ackRequired, bool acquire, + bool exclusive, const string& resumeId, uint64_t resumeTtl, const FieldTable& arguments) { - if(tagInOut.empty()) - tagInOut = tagGenerator.generate(); - std::auto_ptr<ConsumerImpl> c(new ConsumerImpl(this, token, tagInOut, queue, ackRequired, nolocal, acquire)); - queue->consume(*c, exclusive);//may throw exception - outputTasks.addOutputTask(c.get()); - consumers.insert(tagInOut, c.release()); + ConsumerImpl::shared_ptr c(new ConsumerImpl(this, tag, queue, ackRequired, acquire, exclusive, resumeId, resumeTtl, arguments)); + queue->consume(c, exclusive);//may throw exception + consumers[tag] = c; } void SemanticState::cancel(const string& tag){ ConsumerImplMap::iterator i = consumers.find(tag); if (i != consumers.end()) { - cancel(*ptr_map_ptr(i)); - consumers.erase(i); + cancel(i->second); + consumers.erase(i); //should cancel all unacked messages for this consumer so that //they are not redelivered on recovery for_each(unacked.begin(), unacked.end(), boost::bind(&DeliveryRecord::cancel, _1, tag)); @@ -149,7 +153,7 @@ void SemanticState::startDtx(const std::string& xid, DtxManager& mgr, bool join) throw CommandInvalidException(QPID_MSG("Session has not been selected for use with dtx")); } dtxBuffer = DtxBuffer::shared_ptr(new DtxBuffer(xid)); - txBuffer = static_pointer_cast<TxBuffer>(dtxBuffer); + txBuffer = boost::static_pointer_cast<TxBuffer>(dtxBuffer); if (join) { mgr.join(xid, dtxBuffer); } else { @@ -217,7 +221,7 @@ void SemanticState::resumeDtx(const std::string& xid) checkDtxTimeout(); dtxBuffer->setSuspended(false); - txBuffer = static_pointer_cast<TxBuffer>(dtxBuffer); + txBuffer = boost::static_pointer_cast<TxBuffer>(dtxBuffer); } void SemanticState::checkDtxTimeout() @@ -231,36 +235,70 @@ void SemanticState::checkDtxTimeout() void SemanticState::record(const DeliveryRecord& delivery) { unacked.push_back(delivery); - delivery.addTo(outstanding); } -bool SemanticState::checkPrefetch(intrusive_ptr<Message>& msg) -{ - bool countOk = !prefetchCount || prefetchCount > unacked.size(); - bool sizeOk = !prefetchSize || prefetchSize > msg->contentSize() + outstanding.size || unacked.empty(); - return countOk && sizeOk; -} +const std::string QPID_SYNC_FREQUENCY("qpid.sync_frequency"); SemanticState::ConsumerImpl::ConsumerImpl(SemanticState* _parent, - DeliveryToken::shared_ptr _token, - const string& _name, - Queue::shared_ptr _queue, - bool ack, - bool _nolocal, - bool _acquire - ) : + const string& _name, + Queue::shared_ptr _queue, + bool ack, + bool _acquire, + bool _exclusive, + const string& _resumeId, + uint64_t _resumeTtl, + const framing::FieldTable& _arguments + + +) : Consumer(_acquire), parent(_parent), - token(_token), name(_name), queue(_queue), ackExpected(ack), - nolocal(_nolocal), acquire(_acquire), blocked(true), - windowing(true), + windowing(true), + exclusive(_exclusive), + resumeId(_resumeId), + resumeTtl(_resumeTtl), + arguments(_arguments), msgCredit(0), - byteCredit(0){} + byteCredit(0), + notifyEnabled(true), + syncFrequency(_arguments.getAsInt(QPID_SYNC_FREQUENCY)), + deliveryCount(0), + mgmtObject(0) +{ + if (parent != 0 && queue.get() != 0 && queue->GetManagementObject() !=0) + { + ManagementAgent* agent = parent->session.getBroker().getManagementAgent(); + qpid::management::Manageable* ms = dynamic_cast<qpid::management::Manageable*> (&(parent->session)); + + if (agent != 0) + { + mgmtObject = new _qmf::Subscription(agent, this, ms , queue->GetManagementObject()->getObjectId() ,name, + !acquire, ackExpected, exclusive ,arguments); + agent->addObject (mgmtObject, agent->allocateId(this)); + mgmtObject->set_creditMode("WINDOW"); + } + } +} + +ManagementObject* SemanticState::ConsumerImpl::GetManagementObject (void) const +{ + return (ManagementObject*) mgmtObject; +} + +Manageable::status_t SemanticState::ConsumerImpl::ManagementMethod (uint32_t methodId, Args&, string&) +{ + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + + QPID_LOG (debug, "Queue::ManagementMethod [id=" << methodId << "]"); + + return status; +} + OwnershipToken* SemanticState::ConsumerImpl::getSession() { @@ -270,29 +308,49 @@ OwnershipToken* SemanticState::ConsumerImpl::getSession() bool SemanticState::ConsumerImpl::deliver(QueuedMessage& msg) { allocateCredit(msg.payload); - DeliveryId deliveryTag = - parent->deliveryAdapter.deliver(msg, token); + DeliveryRecord record(msg, queue, name, acquire, !ackExpected, windowing); + bool sync = syncFrequency && ++deliveryCount >= syncFrequency; + if (sync) deliveryCount = 0;//reset + parent->deliver(record, sync); + if (!ackExpected && acquire) record.setEnded();//allows message to be released now its been delivered if (windowing || ackExpected || !acquire) { - parent->record(DeliveryRecord(msg, queue, name, token, deliveryTag, acquire, !ackExpected)); + parent->record(record); } if (acquire && !ackExpected) { - queue->dequeue(0, msg.payload); + queue->dequeue(0, msg); } + if (mgmtObject) { mgmtObject->inc_delivered(); } return true; } -bool SemanticState::ConsumerImpl::filter(intrusive_ptr<Message> msg) +bool SemanticState::ConsumerImpl::filter(intrusive_ptr<Message>) { - return !(nolocal && - &parent->getSession().getConnection() == msg->getPublisher()); + return true; } bool SemanticState::ConsumerImpl::accept(intrusive_ptr<Message> msg) { - blocked = !(filter(msg) && checkCredit(msg) && (!ackExpected || parent->checkPrefetch(msg))); + // FIXME 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)); return !blocked; } +namespace { +struct ConsumerName { + const SemanticState::ConsumerImpl& consumer; + ConsumerName(const SemanticState::ConsumerImpl& ci) : consumer(ci) {} +}; + +ostream& operator<<(ostream& o, const ConsumerName& pc) { + return o << pc.consumer.getName() << " on " + << pc.consumer.getParent().getSession().getSessionId(); +} +} + void SemanticState::ConsumerImpl::allocateCredit(intrusive_ptr<Message>& msg) { uint32_t originalMsgCredit = msgCredit; @@ -303,7 +361,7 @@ void SemanticState::ConsumerImpl::allocateCredit(intrusive_ptr<Message>& msg) if (byteCredit != 0xFFFFFFFF) { byteCredit -= msg->getRequiredCredit(); } - QPID_LOG(debug, "Credit allocated for '" << name << "' on " << parent + QPID_LOG(debug, "Credit allocated for " << ConsumerName(*this) << ", was " << " bytes: " << originalByteCredit << " msgs: " << originalMsgCredit << " now bytes: " << byteCredit << " msgs: " << msgCredit); @@ -311,23 +369,27 @@ void SemanticState::ConsumerImpl::allocateCredit(intrusive_ptr<Message>& msg) bool SemanticState::ConsumerImpl::checkCredit(intrusive_ptr<Message>& msg) { - if (msgCredit == 0 || (byteCredit != 0xFFFFFFFF && byteCredit < msg->getRequiredCredit())) { - QPID_LOG(debug, "Not enough credit for '" << name << "' on " << parent - << ", bytes: " << byteCredit << " msgs: " << msgCredit); - return false; - } else { - QPID_LOG(debug, "Credit available for '" << name << "' on " << parent - << " bytes: " << byteCredit << " msgs: " << msgCredit); - return true; - } + bool enoughCredit = msgCredit > 0 && + (byteCredit == 0xFFFFFFFF || byteCredit >= msg->getRequiredCredit()); + QPID_LOG(debug, (enoughCredit ? "Sufficient credit for " : "Insufficient credit for ") + << ConsumerName(*this) + << ", have bytes: " << byteCredit << " msgs: " << msgCredit + << ", need " << msg->getRequiredCredit() << " bytes"); + return enoughCredit; } -SemanticState::ConsumerImpl::~ConsumerImpl() {} +SemanticState::ConsumerImpl::~ConsumerImpl() +{ + if (mgmtObject != 0) + mgmtObject->resourceDestroy (); +} -void SemanticState::cancel(ConsumerImpl& c) +void SemanticState::cancel(ConsumerImpl::shared_ptr c) { - outputTasks.removeOutputTask(&c); - Queue::shared_ptr queue = c.getQueue(); + c->disableNotify(); + if (session.isAttached()) + session.getConnection().outputTasks.removeOutputTask(c.get()); + Queue::shared_ptr queue = c->getQueue(); if(queue) { queue->cancel(c); if (queue->canAutoDelete() && !queue->hasExclusiveOwner()) { @@ -345,30 +407,48 @@ void SemanticState::handle(intrusive_ptr<Message> msg) { } else { DeliverableMessage deliverable(msg); route(msg, deliverable); + if (msg->checkContentReleasable()) { + msg->releaseContent(); + } } } +namespace +{ +const std::string nullstring; +} + void SemanticState::route(intrusive_ptr<Message> msg, Deliverable& strategy) { + msg->setTimestamp(getSession().getBroker().getExpiryPolicy()); + std::string exchangeName = msg->getExchangeName(); - //TODO: the following should be hidden behind message (using MessageAdapter or similar) - if (msg->isA<MessageTransferBody>()) { - msg->getProperties<DeliveryProperties>()->setExchange(exchangeName); - } - if (!cacheExchange || cacheExchange->getName() != exchangeName){ + if (!cacheExchange || cacheExchange->getName() != exchangeName) cacheExchange = session.getBroker().getExchanges().get(exchangeName); + cacheExchange->setProperties(msg); + + /* verify the userid if specified: */ + std::string id = + msg->hasProperties<MessageProperties>() ? msg->getProperties<MessageProperties>()->getUserId() : nullstring; + + if (authMsg && !id.empty() && id != userID && id.append("@").append(defaultRealm) != userID) + { + QPID_LOG(debug, "authorised user id : " << userID << " but user id in message declared as " << id); + throw UnauthorizedAccessException(QPID_MSG("authorised user id : " << userID << " but user id in message declared as " << id)); } - if (acl && acl->doTransferAcl()) - { - if (!acl->authorise(getSession().getConnection().getUserId(),acl::PUBLISH,acl::EXCHANGE,exchangeName, msg->getRoutingKey() )) - throw NotAllowedException("ACL denied exhange publish request"); + if (acl && acl->doTransferAcl()) + { + if (!acl->authorise(getSession().getConnection().getUserId(),acl::ACT_PUBLISH,acl::OBJ_EXCHANGE,exchangeName, msg->getRoutingKey() )) + throw NotAllowedException(QPID_MSG(userID << " cannot publish to " << + exchangeName << " with routing-key " << msg->getRoutingKey())); } cacheExchange->route(strategy, msg->getRoutingKey(), msg->getApplicationHeaders()); if (!strategy.delivered) { - //TODO:if reject-unroutable, then reject - //else route to alternate exchange + //TODO:if discard-unroutable, just drop it + //TODO:else if accept-mode is explicit, reject it + //else route it to alternate exchange if (cacheExchange->getAlternate()) { cacheExchange->getAlternate()->route(strategy, msg->getRoutingKey(), msg->getApplicationHeaders()); } @@ -380,30 +460,27 @@ void SemanticState::route(intrusive_ptr<Message> msg, Deliverable& strategy) { } void SemanticState::requestDispatch() -{ - for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { - requestDispatch(*ptr_map_ptr(i)); - } +{ + for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) + i->second->requestDispatch(); } -void SemanticState::requestDispatch(ConsumerImpl& c) -{ - if(c.isBlocked()) - outputTasks.activateOutput(); - // TODO aconway 2008-07-16: we could directly call - // c.doOutput(); - // since we are in the connections thread but for consistency - // activateOutput() will set it up to be called in the next write idle. - // Current cluster code depends on this, review cluster code to change. +void SemanticState::ConsumerImpl::requestDispatch() +{ + if (blocked) { + parent->session.getConnection().outputTasks.addOutputTask(this); + parent->session.getConnection().outputTasks.activateOutput(); + blocked = false; + } } -void SemanticState::complete(DeliveryRecord& delivery) +bool SemanticState::complete(DeliveryRecord& delivery) { - delivery.subtractFrom(outstanding); ConsumerImplMap::iterator i = consumers.find(delivery.getTag()); if (i != consumers.end()) { - ptr_map_ptr(i)->complete(delivery); + i->second->complete(delivery); } + return delivery.isRedundant(); } void SemanticState::ConsumerImpl::complete(DeliveryRecord& delivery) @@ -420,10 +497,9 @@ void SemanticState::ConsumerImpl::complete(DeliveryRecord& delivery) void SemanticState::recover(bool requeue) { if(requeue){ - outstanding.reset(); //take copy and clear unacked as requeue may result in redelivery to this session //which will in turn result in additions to unacked - std::list<DeliveryRecord> copy = unacked; + DeliveryRecords copy = unacked; unacked.clear(); for_each(copy.rbegin(), copy.rend(), mem_fun_ref(&DeliveryRecord::requeue)); }else{ @@ -431,27 +507,13 @@ void SemanticState::recover(bool requeue) //unconfirmed messages re redelivered and therefore have their //id adjusted, confirmed messages are not and so the ordering //w.r.t id is lost - unacked.sort(); - } -} - -bool SemanticState::get(DeliveryToken::shared_ptr token, Queue::shared_ptr queue, bool ackExpected) -{ - QueuedMessage msg = queue->get(); - if(msg.payload){ - DeliveryId myDeliveryTag = deliveryAdapter.deliver(msg, token); - if(ackExpected){ - unacked.push_back(DeliveryRecord(msg, queue, myDeliveryTag)); - } - return true; - }else{ - return false; + sort(unacked.begin(), unacked.end()); } } -DeliveryId SemanticState::redeliver(QueuedMessage& msg, DeliveryToken::shared_ptr token) +void SemanticState::deliver(DeliveryRecord& msg, bool sync) { - return deliveryAdapter.deliver(msg, token); + return deliveryAdapter.deliver(msg, sync); } SemanticState::ConsumerImpl& SemanticState::find(const std::string& destination) @@ -460,7 +522,7 @@ SemanticState::ConsumerImpl& SemanticState::find(const std::string& destination) if (i == consumers.end()) { throw NotFoundException(QPID_MSG("Unknown destination " << destination)); } else { - return *ptr_map_ptr(i); + return *(i->second); } } @@ -478,7 +540,7 @@ void SemanticState::addByteCredit(const std::string& destination, uint32_t value { ConsumerImpl& c = find(destination); c.addByteCredit(value); - requestDispatch(c); + c.requestDispatch(); } @@ -486,7 +548,7 @@ void SemanticState::addMessageCredit(const std::string& destination, uint32_t va { ConsumerImpl& c = find(destination); c.addMessageCredit(value); - requestDispatch(c); + c.requestDispatch(); } void SemanticState::flush(const std::string& destination) @@ -503,30 +565,48 @@ void SemanticState::stop(const std::string& destination) void SemanticState::ConsumerImpl::setWindowMode() { windowing = true; + if (mgmtObject){ + mgmtObject->set_creditMode("WINDOW"); + } } void SemanticState::ConsumerImpl::setCreditMode() { windowing = false; + if (mgmtObject){ + mgmtObject->set_creditMode("CREDIT"); + } } void SemanticState::ConsumerImpl::addByteCredit(uint32_t value) { if (byteCredit != 0xFFFFFFFF) { - byteCredit += value; + if (value == 0xFFFFFFFF) byteCredit = value; + else byteCredit += value; } } void SemanticState::ConsumerImpl::addMessageCredit(uint32_t value) { if (msgCredit != 0xFFFFFFFF) { - msgCredit += value; + if (value == 0xFFFFFFFF) msgCredit = value; + else msgCredit += value; + } +} + +bool SemanticState::ConsumerImpl::haveCredit() +{ + if (msgCredit && byteCredit) { + return true; + } else { + blocked = true; + return false; } } void SemanticState::ConsumerImpl::flush() { - while(queue->dispatch(*this)) + while(haveCredit() && queue->dispatch(shared_from_this())) ; stop(); } @@ -550,20 +630,8 @@ Queue::shared_ptr SemanticState::getQueue(const string& name) const { } AckRange SemanticState::findRange(DeliveryId first, DeliveryId last) -{ - ack_iterator start = find_if(unacked.begin(), unacked.end(), boost::bind(&DeliveryRecord::matchOrAfter, _1, first)); - ack_iterator end = start; - - if (start != unacked.end()) { - if (first == last) { - //just acked single element (move end past it) - ++end; - } else { - //need to find end (position it just after the last record in range) - end = find_if(start, unacked.end(), boost::bind(&DeliveryRecord::after, _1, last)); - } - } - return AckRange(start, end); +{ + return DeliveryRecord::findRange(unacked, first, last); } void SemanticState::acquire(DeliveryId first, DeliveryId last, DeliveryIds& acquired) @@ -591,29 +659,62 @@ void SemanticState::reject(DeliveryId first, DeliveryId last) } bool SemanticState::ConsumerImpl::hasOutput() { - return queue->checkForMessages(*this); + return queue->checkForMessages(shared_from_this()); } bool SemanticState::ConsumerImpl::doOutput() { - //TODO: think through properly - return queue->dispatch(*this); + return haveCredit() && queue->dispatch(shared_from_this()); } -void SemanticState::ConsumerImpl::notify() +void SemanticState::ConsumerImpl::enableNotify() { - //TODO: think through properly - parent->outputTasks.activateOutput(); + Mutex::ScopedLock l(lock); + notifyEnabled = true; } +void SemanticState::ConsumerImpl::disableNotify() +{ + Mutex::ScopedLock l(lock); + notifyEnabled = false; +} -void SemanticState::accepted(DeliveryId first, DeliveryId last) +bool SemanticState::ConsumerImpl::isNotifyEnabled() const { + Mutex::ScopedLock l(lock); + return notifyEnabled; +} + +void SemanticState::ConsumerImpl::notify() { - AckRange range = findRange(first, last); + Mutex::ScopedLock l(lock); + if (notifyEnabled) { + parent->session.getConnection().outputTasks.addOutputTask(this); + parent->session.getConnection().outputTasks.activateOutput(); + } +} + + +// Test that a DeliveryRecord's ID is in a sequence set and some other +// predicate on DeliveryRecord holds. +template <class Predicate> struct IsInSequenceSetAnd { + IsInSequenceSet isInSet; + Predicate predicate; + IsInSequenceSetAnd(const SequenceSet& s, Predicate p) : isInSet(s), predicate(p) {} + bool operator()(DeliveryRecord& dr) { + return isInSet(dr.getId()) && predicate(dr); + } +}; + +template<class Predicate> IsInSequenceSetAnd<Predicate> +isInSequenceSetAnd(const SequenceSet& s, Predicate p) { + return IsInSequenceSetAnd<Predicate>(s,p); +} + +void SemanticState::accepted(const SequenceSet& commands) { if (txBuffer.get()) { //in transactional mode, don't dequeue or remove, just //maintain set of acknowledged messages: - accumulatedAck.add(first, last); + accumulatedAck.add(commands); if (dtxBuffer.get()) { //if enlisted in a dtx, copy the relevant slice from @@ -623,25 +724,48 @@ void SemanticState::accepted(DeliveryId first, DeliveryId last) dtxBuffer->enlist(txAck); //mark the relevant messages as 'ended' in unacked - for_each(range.start, range.end, mem_fun_ref(&DeliveryRecord::setEnded)); - //if the messages are already completed, they can be //removed from the record - unacked.remove_if(mem_fun_ref(&DeliveryRecord::isRedundant)); - + DeliveryRecords::iterator removed = + remove_if(unacked.begin(), unacked.end(), + isInSequenceSetAnd(commands, + bind(&DeliveryRecord::setEnded, _1))); + unacked.erase(removed, unacked.end()); } } else { - for_each(range.start, range.end, boost::bind(&DeliveryRecord::accept, _1, (TransactionContext*) 0)); - unacked.remove_if(mem_fun_ref(&DeliveryRecord::isRedundant)); + DeliveryRecords::iterator removed = + remove_if(unacked.begin(), unacked.end(), + isInSequenceSetAnd(commands, + bind(&DeliveryRecord::accept, _1, + (TransactionContext*) 0))); + unacked.erase(removed, unacked.end()); } } -void SemanticState::completed(DeliveryId first, DeliveryId last) -{ - AckRange range = findRange(first, last); - for_each(range.start, range.end, boost::bind(&SemanticState::complete, this, _1)); - unacked.remove_if(mem_fun_ref(&DeliveryRecord::isRedundant)); +void SemanticState::completed(const SequenceSet& commands) { + DeliveryRecords::iterator removed = + remove_if(unacked.begin(), unacked.end(), + isInSequenceSetAnd(commands, + bind(&SemanticState::complete, this, _1))); + unacked.erase(removed, unacked.end()); requestDispatch(); } +void SemanticState::attached() +{ + for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { + i->second->enableNotify(); + session.getConnection().outputTasks.addOutputTask(i->second.get()); + } + session.getConnection().outputTasks.activateOutput(); +} + +void SemanticState::detached() +{ + for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { + i->second->disableNotify(); + session.getConnection().outputTasks.removeOutputTask(i->second.get()); + } +} + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SemanticState.h b/cpp/src/qpid/broker/SemanticState.h index e03d5ec89b..e5e3f909f1 100644 --- a/cpp/src/qpid/broker/SemanticState.h +++ b/cpp/src/qpid/broker/SemanticState.h @@ -22,29 +22,30 @@ * */ -#include "Consumer.h" -#include "Deliverable.h" -#include "DeliveryAdapter.h" -#include "DeliveryRecord.h" -#include "DeliveryToken.h" -#include "DtxBuffer.h" -#include "DtxManager.h" -#include "NameGenerator.h" -#include "Prefetch.h" -#include "TxBuffer.h" +#include "qpid/broker/Consumer.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/DeliveryAdapter.h" +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/DtxBuffer.h" +#include "qpid/broker/DtxManager.h" +#include "qpid/broker/NameGenerator.h" +#include "qpid/broker/TxBuffer.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/SequenceSet.h" #include "qpid/framing/Uuid.h" #include "qpid/sys/AggregateOutput.h" -#include "qpid/shared_ptr.h" -#include "AclModule.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/broker/AclModule.h" +#include "qmf/org/apache/qpid/broker/Subscription.h" #include <list> #include <map> #include <vector> #include <boost/intrusive_ptr.hpp> +#include <boost/cast.hpp> namespace qpid { namespace broker { @@ -55,36 +56,54 @@ class SessionContext; * SemanticState holds the L3 and L4 state of an open session, whether * attached to a channel or suspended. */ -class SemanticState : public sys::OutputTask, - private boost::noncopyable -{ - class ConsumerImpl : public Consumer, public sys::OutputTask +class SemanticState : private boost::noncopyable { + public: + class ConsumerImpl : public Consumer, public sys::OutputTask, + public boost::enable_shared_from_this<ConsumerImpl>, + public management::Manageable { + mutable qpid::sys::Mutex lock; SemanticState* const parent; - const DeliveryToken::shared_ptr token; const string name; const Queue::shared_ptr queue; const bool ackExpected; - const bool nolocal; const bool acquire; bool blocked; bool windowing; + bool exclusive; + string resumeId; + uint64_t resumeTtl; + framing::FieldTable arguments; uint32_t msgCredit; uint32_t byteCredit; + bool notifyEnabled; + const int syncFrequency; + int deliveryCount; + qmf::org::apache::qpid::broker::Subscription* mgmtObject; bool checkCredit(boost::intrusive_ptr<Message>& msg); void allocateCredit(boost::intrusive_ptr<Message>& msg); + bool haveCredit(); public: - ConsumerImpl(SemanticState* parent, DeliveryToken::shared_ptr token, + typedef boost::shared_ptr<ConsumerImpl> shared_ptr; + + ConsumerImpl(SemanticState* parent, const string& name, Queue::shared_ptr queue, - bool ack, bool nolocal, bool acquire); + bool ack, bool acquire, bool exclusive, + const std::string& resumeId, uint64_t resumeTtl, const framing::FieldTable& arguments); ~ConsumerImpl(); OwnershipToken* getSession(); bool deliver(QueuedMessage& msg); bool filter(boost::intrusive_ptr<Message> msg); bool accept(boost::intrusive_ptr<Message> msg); + + void disableNotify(); + void enableNotify(); void notify(); + bool isNotifyEnabled() const; + + void requestDispatch(); void setWindowMode(); void setCreditMode(); @@ -93,50 +112,68 @@ class SemanticState : public sys::OutputTask, void flush(); void stop(); void complete(DeliveryRecord&); - Queue::shared_ptr getQueue() { return queue; } - bool isBlocked() const { return blocked; } + Queue::shared_ptr getQueue() const { return queue; } + bool isBlocked() const { return blocked; } + bool setBlocked(bool set) { std::swap(set, blocked); return set; } bool hasOutput(); bool doOutput(); + + std::string getName() const { return name; } + + bool isAckExpected() const { return ackExpected; } + bool isAcquire() const { return acquire; } + bool isWindowing() const { return windowing; } + bool isExclusive() const { return exclusive; } + uint32_t getMsgCredit() const { return msgCredit; } + uint32_t getByteCredit() const { return byteCredit; } + std::string getResumeId() const { return resumeId; }; + uint64_t getResumeTtl() const { return resumeTtl; } + const framing::FieldTable& getArguments() const { return arguments; } + + SemanticState& getParent() { return *parent; } + const SemanticState& getParent() const { return *parent; } + // Manageable entry points + management::ManagementObject* GetManagementObject (void) const; + management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); }; - typedef boost::ptr_map<std::string,ConsumerImpl> ConsumerImplMap; + private: + typedef std::map<std::string, ConsumerImpl::shared_ptr> ConsumerImplMap; typedef std::map<std::string, DtxBuffer::shared_ptr> DtxBufferMap; SessionContext& session; DeliveryAdapter& deliveryAdapter; - Queue::shared_ptr defaultQueue; ConsumerImplMap consumers; - uint32_t prefetchSize; - uint16_t prefetchCount; - Prefetch outstanding; NameGenerator tagGenerator; - std::list<DeliveryRecord> unacked; + DeliveryRecords unacked; TxBuffer::shared_ptr txBuffer; DtxBuffer::shared_ptr dtxBuffer; bool dtxSelected; DtxBufferMap suspendedXids; framing::SequenceSet accumulatedAck; boost::shared_ptr<Exchange> cacheExchange; - sys::AggregateOutput outputTasks; AclModule* acl; - + const bool authMsg; + const string userID; + const string defaultRealm; + void route(boost::intrusive_ptr<Message> msg, Deliverable& strategy); - void record(const DeliveryRecord& delivery); - bool checkPrefetch(boost::intrusive_ptr<Message>& msg); void checkDtxTimeout(); - ConsumerImpl& find(const std::string& destination); - void complete(DeliveryRecord&); + + bool complete(DeliveryRecord&); AckRange findRange(DeliveryId first, DeliveryId last); void requestDispatch(); - void requestDispatch(ConsumerImpl&); - void cancel(ConsumerImpl&); + void cancel(ConsumerImpl::shared_ptr); public: SemanticState(DeliveryAdapter&, SessionContext&); ~SemanticState(); SessionContext& getSession() { return session; } + const SessionContext& getSession() const { return session; } + + ConsumerImpl& find(const std::string& destination); /** * Get named queue, never returns 0. @@ -146,16 +183,13 @@ class SemanticState : public sys::OutputTask, */ Queue::shared_ptr getQueue(const std::string& name) const; - uint32_t setPrefetchSize(uint32_t size){ return prefetchSize = size; } - uint16_t setPrefetchCount(uint16_t n){ return prefetchCount = n; } - bool exists(const string& consumerTag); - /** - *@param tagInOut - if empty it is updated with the generated token. - */ - void consume(DeliveryToken::shared_ptr token, string& tagInOut, Queue::shared_ptr queue, - bool nolocal, bool ackRequired, bool acquire, bool exclusive, const framing::FieldTable* = 0); + void consume(const string& destination, + Queue::shared_ptr queue, + bool ackRequired, bool acquire, bool exclusive, + const string& resumeId=string(), uint64_t resumeTtl=0, + const framing::FieldTable& = framing::FieldTable()); void cancel(const string& tag); @@ -166,7 +200,6 @@ class SemanticState : public sys::OutputTask, void flush(const std::string& destination); void stop(const std::string& destination); - bool get(DeliveryToken::shared_ptr token, Queue::shared_ptr queue, bool ackExpected); void startTx(); void commit(MessageStore* const store); void rollback(); @@ -176,17 +209,29 @@ class SemanticState : public sys::OutputTask, void suspendDtx(const std::string& xid); void resumeDtx(const std::string& xid); void recover(bool requeue); - DeliveryId redeliver(QueuedMessage& msg, DeliveryToken::shared_ptr token); + void deliver(DeliveryRecord& message, bool sync); void acquire(DeliveryId first, DeliveryId last, DeliveryIds& acquired); void release(DeliveryId first, DeliveryId last, bool setRedelivered); void reject(DeliveryId first, DeliveryId last); void handle(boost::intrusive_ptr<Message> msg); - bool hasOutput() { return outputTasks.hasOutput(); } - bool doOutput() { return outputTasks.doOutput(); } - //final 0-10 spec (completed and accepted are distinct): - void completed(DeliveryId deliveryTag, DeliveryId endTag); - void accepted(DeliveryId deliveryTag, DeliveryId endTag); + void completed(const framing::SequenceSet& commands); + void accepted(const framing::SequenceSet& commands); + + void attached(); + void detached(); + + // 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; } + void setTxBuffer(const TxBuffer::shared_ptr& txb) { txBuffer = txb; } + void setAccumulatedAck(const framing::SequenceSet& s) { accumulatedAck = s; } + void record(const DeliveryRecord& delivery); }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionAdapter.cpp b/cpp/src/qpid/broker/SessionAdapter.cpp index 03022b00bb..a7743d95ab 100644 --- a/cpp/src/qpid/broker/SessionAdapter.cpp +++ b/cpp/src/qpid/broker/SessionAdapter.cpp @@ -15,17 +15,23 @@ * limitations under the License. * */ -#include "SessionAdapter.h" -#include "Connection.h" -#include "DeliveryToken.h" -#include "MessageDelivery.h" -#include "Queue.h" +#include "qpid/broker/SessionAdapter.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/Queue.h" #include "qpid/Exception.h" #include "qpid/framing/reply_exceptions.h" -#include "qpid/framing/constants.h" +#include "qpid/framing/enum.h" #include "qpid/log/Statement.h" -#include "qpid/amqp_0_10/exceptions.h" #include "qpid/framing/SequenceSet.h" +#include "qpid/management/ManagementAgent.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 "qmf/org/apache/qpid/broker/EventSubscribe.h" +#include "qmf/org/apache/qpid/broker/EventUnsubscribe.h" #include <boost/format.hpp> #include <boost/cast.hpp> #include <boost/bind.hpp> @@ -35,6 +41,9 @@ namespace broker { using namespace qpid; using namespace qpid::framing; +using namespace qpid::framing::dtx; +using namespace qpid::management; +namespace _qmf = qmf::org::apache::qpid::broker; typedef std::vector<Queue::shared_ptr> QueueVector; @@ -48,23 +57,24 @@ SessionAdapter::SessionAdapter(SemanticState& s) : dtxImpl(s) {} +static const std::string _TRUE("true"); +static const std::string _FALSE("false"); void SessionAdapter::ExchangeHandlerImpl::declare(const string& exchange, const string& type, const string& alternateExchange, bool passive, bool durable, bool /*autoDelete*/, const FieldTable& args){ - AclModule* acl = getBroker().getAcl(); - if (acl) - { - std::map<std::string, std::string> params; - params.insert(make_pair("TYPE", type)); - params.insert(make_pair("ALT", alternateExchange)); - params.insert(make_pair("PAS", std::string(passive ? "Y" : "N") )); - params.insert(make_pair("DURA", std::string(durable ? "Y" : "N"))); - if (!acl->authorise(getConnection().getUserId(),acl::CREATE,acl::EXCHANGE,exchange,¶ms) ) - throw NotAllowedException("ACL denied exhange declare request"); - } - + AclModule* acl = getBroker().getAcl(); + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_TYPE, type)); + params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PROP_PASSIVE, std::string(passive ? _TRUE : _FALSE) )); + params.insert(make_pair(acl::PROP_DURABLE, std::string(durable ? _TRUE : _FALSE))); + if (!acl->authorise(getConnection().getUserId(),acl::ACT_CREATE,acl::OBJ_EXCHANGE,exchange,¶ms) ) + throw NotAllowedException(QPID_MSG("ACL denied exchange declare request from " << getConnection().getUserId())); + } + //TODO: implement autoDelete Exchange::shared_ptr alternate; if (!alternateExchange.empty()) { @@ -75,21 +85,31 @@ void SessionAdapter::ExchangeHandlerImpl::declare(const string& exchange, const checkType(actual, type); checkAlternate(actual, alternate); }else{ + if(exchange.find("amq.") == 0 || exchange.find("qpid.") == 0) { + throw framing::NotAllowedException(QPID_MSG("Exchange names beginning with \"amq.\" or \"qpid.\" are reserved. (exchange=\"" << exchange << "\")")); + } try{ std::pair<Exchange::shared_ptr, bool> response = getBroker().getExchanges().declare(exchange, type, durable, args); if (response.second) { - if (durable) { - getBroker().getStore().create(*response.first, args); - } if (alternate) { response.first->setAlternate(alternate); alternate->incAlternateUsers(); } + if (durable) { + getBroker().getStore().create(*response.first, args); + } } else { checkType(response.first, type); checkAlternate(response.first, alternate); } - }catch(UnknownExchangeTypeException& e){ + + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventExchangeDeclare(getConnection().getUrl(), getConnection().getUserId(), exchange, type, + alternateExchange, durable, false, args, + response.second ? "created" : "existing")); + + }catch(UnknownExchangeTypeException& /*e*/){ throw CommandInvalidException(QPID_MSG("Exchange type not implemented: " << type)); } } @@ -104,57 +124,62 @@ void SessionAdapter::ExchangeHandlerImpl::checkType(Exchange::shared_ptr exchang void SessionAdapter::ExchangeHandlerImpl::checkAlternate(Exchange::shared_ptr exchange, Exchange::shared_ptr alternate) { - if (alternate && alternate != exchange->getAlternate()) - throw NotAllowedException( - QPID_MSG("Exchange declared with alternate-exchange " - << exchange->getAlternate()->getName() << ", requested " - << alternate->getName())); + if (alternate && ((exchange->getAlternate() && alternate != exchange->getAlternate()) + || !exchange->getAlternate())) + throw NotAllowedException(QPID_MSG("Exchange declared with alternate-exchange " + << (exchange->getAlternate() ? exchange->getAlternate()->getName() : "<nonexistent>") + << ", requested " + << alternate->getName())); } -void SessionAdapter::ExchangeHandlerImpl::delete_(const string& name, bool /*ifUnused*/){ - - AclModule* acl = getBroker().getAcl(); - if (acl) - { - if (!acl->authorise(getConnection().getUserId(),acl::DELETE,acl::EXCHANGE,name,NULL) ) - throw NotAllowedException("ACL denied exhange delete request"); +void SessionAdapter::ExchangeHandlerImpl::delete_(const string& name, bool /*ifUnused*/) +{ + AclModule* acl = getBroker().getAcl(); + if (acl) { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_DELETE,acl::OBJ_EXCHANGE,name,NULL) ) + throw NotAllowedException(QPID_MSG("ACL denied exchange delete request from " << getConnection().getUserId())); } - //TODO: implement unused Exchange::shared_ptr exchange(getBroker().getExchanges().get(name)); if (exchange->inUseAsAlternate()) throw NotAllowedException(QPID_MSG("Exchange in use as alternate-exchange.")); if (exchange->isDurable()) getBroker().getStore().destroy(*exchange); if (exchange->getAlternate()) exchange->getAlternate()->decAlternateUsers(); getBroker().getExchanges().destroy(name); -} + + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventExchangeDelete(getConnection().getUrl(), getConnection().getUserId(), name)); +} ExchangeQueryResult SessionAdapter::ExchangeHandlerImpl::query(const string& name) { - - AclModule* acl = getBroker().getAcl(); - if (acl) - { - if (!acl->authorise(getConnection().getUserId(),acl::ACCESS,acl::EXCHANGE,name,NULL) ) - throw NotAllowedException("ACL denied exhange query request"); + AclModule* acl = getBroker().getAcl(); + if (acl) { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_EXCHANGE,name,NULL) ) + throw NotAllowedException(QPID_MSG("ACL denied exchange query request from " << getConnection().getUserId())); } try { Exchange::shared_ptr exchange(getBroker().getExchanges().get(name)); return ExchangeQueryResult(exchange->getType(), exchange->isDurable(), false, exchange->getArgs()); - } catch (const NotFoundException& e) { + } catch (const NotFoundException& /*e*/) { return ExchangeQueryResult("", false, true, FieldTable()); } } + void SessionAdapter::ExchangeHandlerImpl::bind(const string& queueName, const string& exchangeName, const string& routingKey, - const FieldTable& arguments){ + const FieldTable& arguments) +{ + AclModule* acl = getBroker().getAcl(); + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_QUEUENAME, queueName)); + params.insert(make_pair(acl::PROP_ROUTINGKEY, routingKey)); - AclModule* acl = getBroker().getAcl(); - if (acl) - { - if (!acl->authorise(getConnection().getUserId(),acl::BIND,acl::EXCHANGE,exchangeName,routingKey) ) - throw NotAllowedException("ACL denied exhange bind request"); + if (!acl->authorise(getConnection().getUserId(),acl::ACT_BIND,acl::OBJ_EXCHANGE,exchangeName,¶ms)) + throw NotAllowedException(QPID_MSG("ACL denied exchange bind request from " << getConnection().getUserId())); } Queue::shared_ptr queue = getQueue(queueName); @@ -166,30 +191,29 @@ void SessionAdapter::ExchangeHandlerImpl::bind(const string& queueName, if (exchange->isDurable() && queue->isDurable()) { getBroker().getStore().bind(*exchange, *queue, routingKey, arguments); } + + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventBind(getConnection().getUrl(), getConnection().getUserId(), exchangeName, queueName, exchangeRoutingKey, arguments)); } }else{ - throw NotFoundException( - "Bind failed. No such exchange: " + exchangeName); + throw NotFoundException("Bind failed. No such exchange: " + exchangeName); } } -void -SessionAdapter::ExchangeHandlerImpl::unbind(const string& queueName, - const string& exchangeName, - const string& routingKey) +void SessionAdapter::ExchangeHandlerImpl::unbind(const string& queueName, + const string& exchangeName, + const string& routingKey) { - - AclModule* acl = getBroker().getAcl(); - if (acl) - { - std::map<std::string, std::string> params; - params.insert(make_pair("QN", queueName)); - params.insert(make_pair("RKEY", routingKey)); - if (!acl->authorise(getConnection().getUserId(),acl::UNBIND,acl::EXCHANGE,exchangeName,¶ms) ) - throw NotAllowedException("ACL denied exchange unbind request"); + AclModule* acl = getBroker().getAcl(); + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_QUEUENAME, queueName)); + params.insert(make_pair(acl::PROP_ROUTINGKEY, routingKey)); + if (!acl->authorise(getConnection().getUserId(),acl::ACT_UNBIND,acl::OBJ_EXCHANGE,exchangeName,¶ms) ) + throw NotAllowedException(QPID_MSG("ACL denied exchange unbind request from " << getConnection().getUserId())); } - Queue::shared_ptr queue = getQueue(queueName); if (!queue.get()) throw NotFoundException("Unbind failed. No such exchange: " + exchangeName); @@ -197,10 +221,14 @@ SessionAdapter::ExchangeHandlerImpl::unbind(const string& queueName, if (!exchange.get()) throw NotFoundException("Unbind failed. No such exchange: " + exchangeName); //TODO: revise unbind to rely solely on binding key (not args) - if (exchange->unbind(queue, routingKey, 0) && exchange->isDurable() && queue->isDurable()) { - getBroker().getStore().unbind(*exchange, *queue, routingKey, FieldTable()); - } + if (exchange->unbind(queue, routingKey, 0)) { + if (exchange->isDurable() && queue->isDurable()) + getBroker().getStore().unbind(*exchange, *queue, routingKey, FieldTable()); + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventUnbind(getConnection().getUrl(), getConnection().getUserId(), exchangeName, queueName, routingKey)); + } } ExchangeBoundResult SessionAdapter::ExchangeHandlerImpl::bound(const std::string& exchangeName, @@ -208,16 +236,15 @@ ExchangeBoundResult SessionAdapter::ExchangeHandlerImpl::bound(const std::string const std::string& key, const framing::FieldTable& args) { - AclModule* acl = getBroker().getAcl(); - if (acl) - { - std::map<std::string, std::string> params; - params.insert(make_pair("QUEUE", queueName)); - params.insert(make_pair("RKEY", queueName)); - if (!acl->authorise(getConnection().getUserId(),acl::CREATE,acl::EXCHANGE,exchangeName,¶ms) ) - throw NotAllowedException("ACL denied exhange bound request"); + AclModule* acl = getBroker().getAcl(); + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_QUEUENAME, queueName)); + params.insert(make_pair(acl::PROP_ROUTINGKEY, key)); + if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_EXCHANGE,exchangeName,¶ms) ) + throw NotAllowedException(QPID_MSG("ACL denied exchange bound request from " << getConnection().getUserId())); } - + Exchange::shared_ptr exchange; try { exchange = getBroker().getExchanges().get(exchangeName); @@ -229,7 +256,7 @@ ExchangeBoundResult SessionAdapter::ExchangeHandlerImpl::bound(const std::string } if (!exchange) { - return ExchangeBoundResult(true, false, false, false, false); + return ExchangeBoundResult(true, (!queueName.empty() && !queue), false, false, false); } else if (!queueName.empty() && !queue) { return ExchangeBoundResult(false, true, false, false, false); } else if (exchange->isBound(queue, key.empty() ? 0 : &key, args.count() > 0 ? &args : &args)) { @@ -268,7 +295,6 @@ void SessionAdapter::QueueHandlerImpl::destroyExclusiveQueues() exclusiveQueues.erase(exclusiveQueues.begin()); } } - bool SessionAdapter::QueueHandlerImpl::isLocal(const ConnectionToken* t) const { @@ -278,13 +304,12 @@ bool SessionAdapter::QueueHandlerImpl::isLocal(const ConnectionToken* t) const QueueQueryResult SessionAdapter::QueueHandlerImpl::query(const string& name) { - AclModule* acl = getBroker().getAcl(); - if (acl) - { - if (!acl->authorise(getConnection().getUserId(),acl::ACCESS,acl::QUEUE,name,NULL) ) - throw NotAllowedException("ACL denied queue query request"); + AclModule* acl = getBroker().getAcl(); + if (acl) { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_QUEUE,name,NULL) ) + throw NotAllowedException(QPID_MSG("ACL denied queue query request from " << getConnection().getUserId())); } - + Queue::shared_ptr queue = session.getBroker().getQueues().find(name); if (queue) { @@ -304,20 +329,23 @@ QueueQueryResult SessionAdapter::QueueHandlerImpl::query(const string& name) } void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& alternateExchange, - bool passive, bool durable, bool exclusive, - bool autoDelete, const qpid::framing::FieldTable& arguments){ - - AclModule* acl = getBroker().getAcl(); - if (acl) - { - std::map<std::string, std::string> params; - params.insert(make_pair("ALT", alternateExchange)); - params.insert(make_pair("PAS", std::string(passive ? "Y" : "N") )); - params.insert(make_pair("DURA", std::string(durable ? "Y" : "N"))); - params.insert(make_pair("EXCLUS", std::string(exclusive ? "Y" : "N"))); - params.insert(make_pair("AUTOD", std::string(autoDelete ? "Y" : "N"))); - if (!acl->authorise(getConnection().getUserId(),acl::CREATE,acl::QUEUE,name,¶ms) ) - throw NotAllowedException("ACL denied queue create request"); + bool passive, bool durable, bool exclusive, + bool autoDelete, const qpid::framing::FieldTable& arguments) +{ + AclModule* acl = getBroker().getAcl(); + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PROP_PASSIVE, std::string(passive ? _TRUE : _FALSE) )); + params.insert(make_pair(acl::PROP_DURABLE, std::string(durable ? _TRUE : _FALSE))); + params.insert(make_pair(acl::PROP_EXCLUSIVE, std::string(exclusive ? _TRUE : _FALSE))); + params.insert(make_pair(acl::PROP_AUTODELETE, std::string(autoDelete ? _TRUE : _FALSE))); + params.insert(make_pair(acl::PROP_POLICYTYPE, arguments.getAsString("qpid.policy_type"))); + params.insert(make_pair(acl::PROP_MAXQUEUECOUNT, boost::lexical_cast<string>(arguments.getAsInt("qpid.max_count")))); + params.insert(make_pair(acl::PROP_MAXQUEUESIZE, boost::lexical_cast<string>(arguments.getAsInt64("qpid.max_size")))); + + if (!acl->authorise(getConnection().getUserId(),acl::ACT_CREATE,acl::OBJ_QUEUE,name,¶ms) ) + throw NotAllowedException(QPID_MSG("ACL denied queue create request from " << getConnection().getUserId())); } Exchange::shared_ptr alternate; @@ -326,17 +354,16 @@ void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& } Queue::shared_ptr queue; if (passive && !name.empty()) { - queue = getQueue(name); + queue = getQueue(name); //TODO: check alternate-exchange is as expected } else { - std::pair<Queue::shared_ptr, bool> queue_created = - getBroker().getQueues().declare( - name, durable, - autoDelete, - exclusive ? this : 0); - queue = queue_created.first; - assert(queue); - if (queue_created.second) { // This is a new queue + std::pair<Queue::shared_ptr, bool> queue_created = + getBroker().getQueues().declare(name, durable, + autoDelete, + exclusive ? &session : 0); + queue = queue_created.first; + assert(queue); + if (queue_created.second) { // This is a new queue if (alternate) { queue->setAlternateExchange(alternate); alternate->incAlternateUsers(); @@ -345,48 +372,56 @@ void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& //apply settings & create persistent record if required queue_created.first->create(arguments); - //add default binding: - getBroker().getExchanges().getDefault()->bind(queue, name, 0); + //add default binding: + getBroker().getExchanges().getDefault()->bind(queue, name, 0); queue->bound(getBroker().getExchanges().getDefault()->getName(), name, arguments); //handle automatic cleanup: - if (exclusive) { - exclusiveQueues.push_back(queue); - } - } else { - if (exclusive && queue->setExclusiveOwner(this)) { - exclusiveQueues.push_back(queue); + if (exclusive) { + exclusiveQueues.push_back(queue); + } + } else { + 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, arguments, + queue_created.second ? "created" : "existing")); } - if (exclusive && !queue->isExclusiveOwner(this)) - throw ResourceLockedException( - QPID_MSG("Cannot grant exclusive access to queue " - << queue->getName())); + + if (exclusive && !queue->isExclusiveOwner(&session)) + throw ResourceLockedException(QPID_MSG("Cannot grant exclusive access to queue " + << queue->getName())); } void SessionAdapter::QueueHandlerImpl::purge(const string& queue){ - AclModule* acl = getBroker().getAcl(); - if (acl) - { - if (!acl->authorise(getConnection().getUserId(),acl::PURGE,acl::QUEUE,queue,NULL) ) - throw NotAllowedException("ACL denied queue purge request"); + AclModule* acl = getBroker().getAcl(); + if (acl) + { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_PURGE,acl::OBJ_QUEUE,queue,NULL) ) + throw NotAllowedException(QPID_MSG("ACL denied queue purge request from " << getConnection().getUserId())); } getQueue(queue)->purge(); } void SessionAdapter::QueueHandlerImpl::delete_(const string& queue, bool ifUnused, bool ifEmpty){ - AclModule* acl = getBroker().getAcl(); - if (acl) - { - if (!acl->authorise(getConnection().getUserId(),acl::DELETE,acl::QUEUE,queue,NULL) ) - throw NotAllowedException("ACL denied queue delete request"); + AclModule* acl = getBroker().getAcl(); + if (acl) + { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_DELETE,acl::OBJ_QUEUE,queue,NULL) ) + throw NotAllowedException(QPID_MSG("ACL denied queue delete request from " << getConnection().getUserId())); } - ChannelException error(0, ""); Queue::shared_ptr q = getQueue(queue); + if (q->hasExclusiveOwner() && !q->isExclusiveOwner(&session)) + throw ResourceLockedException(QPID_MSG("Cannot delete queue " + << queue << "; it is exclusive to another session")); if(ifEmpty && q->getMessageCount() > 0){ throw PreconditionFailedException("Queue not empty."); }else if(ifUnused && q->getConsumerCount() > 0){ @@ -400,16 +435,18 @@ void SessionAdapter::QueueHandlerImpl::delete_(const string& queue, bool ifUnuse q->destroy(); getBroker().getQueues().destroy(queue); q->unbind(getBroker().getExchanges(), q); + + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventQueueDelete(getConnection().getUrl(), getConnection().getUserId(), queue)); } } - SessionAdapter::MessageHandlerImpl::MessageHandlerImpl(SemanticState& s) : HandlerHelper(s), releaseRedeliveredOp(boost::bind(&SemanticState::release, &state, _1, _2, true)), releaseOp(boost::bind(&SemanticState::release, &state, _1, _2, false)), - rejectOp(boost::bind(&SemanticState::reject, &state, _1, _2)), - acceptOp(boost::bind(&SemanticState::accepted, &state, _1, _2)) + rejectOp(boost::bind(&SemanticState::reject, &state, _1, _2)) {} // @@ -431,37 +468,47 @@ void SessionAdapter::MessageHandlerImpl::release(const SequenceSet& transfers, b void SessionAdapter::MessageHandlerImpl::subscribe(const string& queueName, - const string& destination, - uint8_t acceptMode, - uint8_t acquireMode, - bool exclusive, - const string& /*resumeId*/,//TODO implement resume behaviour - uint64_t /*resumeTtl*/, - const FieldTable& arguments) + const string& destination, + uint8_t acceptMode, + uint8_t acquireMode, + bool exclusive, + const string& resumeId, + uint64_t resumeTtl, + const FieldTable& arguments) { - AclModule* acl = getBroker().getAcl(); - if (acl) - { - // add flags as needed - if (!acl->authorise(getConnection().getUserId(),acl::CONSUME,acl::QUEUE,queueName,NULL) ) - throw NotAllowedException("ACL denied Queue subscribe request"); + AclModule* acl = getBroker().getAcl(); + if (acl) + { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_CONSUME,acl::OBJ_QUEUE,queueName,NULL) ) + throw NotAllowedException(QPID_MSG("ACL denied Queue subscribe request from " << getConnection().getUserId())); } Queue::shared_ptr queue = getQueue(queueName); if(!destination.empty() && state.exists(destination)) throw NotAllowedException(QPID_MSG("Consumer tags must be unique")); + if (queue->hasExclusiveOwner() && !queue->isExclusiveOwner(&session)) + throw ResourceLockedException(QPID_MSG("Cannot subscribe to exclusive queue " + << queue->getName())); + + state.consume(destination, queue, + acceptMode == 0, acquireMode == 0, exclusive, + resumeId, resumeTtl, arguments); - string tag = destination; - state.consume(MessageDelivery::getMessageDeliveryToken(destination, acceptMode, acquireMode), - tag, queue, false, //TODO get rid of no-local - acceptMode == 0, acquireMode == 0, exclusive, &arguments); + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventSubscribe(getConnection().getUrl(), getConnection().getUserId(), + queueName, destination, exclusive, arguments)); } void SessionAdapter::MessageHandlerImpl::cancel(const string& destination ) { state.cancel(destination); + + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventUnsubscribe(getConnection().getUrl(), getConnection().getUserId(), destination)); } void @@ -510,8 +557,7 @@ void SessionAdapter::MessageHandlerImpl::stop(const std::string& destination) void SessionAdapter::MessageHandlerImpl::accept(const framing::SequenceSet& commands) { - - commands.for_each(acceptOp); + state.accepted(commands); } framing::MessageAcquireResult SessionAdapter::MessageHandlerImpl::acquire(const framing::SequenceSet& transfers) @@ -595,7 +641,7 @@ XaResult SessionAdapter::DtxHandlerImpl::end(const Xid& xid, if (suspend) { throw CommandInvalidException(QPID_MSG("End and suspend cannot both be set.")); } else { - return XaResult(XA_RBROLLBACK); + return XaResult(XA_STATUS_XA_RBROLLBACK); } } else { if (suspend) { @@ -603,10 +649,10 @@ XaResult SessionAdapter::DtxHandlerImpl::end(const Xid& xid, } else { state.endDtx(convert(xid), false); } - return XaResult(XA_OK); + return XaResult(XA_STATUS_XA_OK); } - } catch (const DtxTimeoutException& e) { - return XaResult(XA_RBTIMEOUT); + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); } } @@ -623,9 +669,9 @@ XaResult SessionAdapter::DtxHandlerImpl::start(const Xid& xid, } else { state.startDtx(convert(xid), getBroker().getDtxManager(), join); } - return XaResult(XA_OK); - } catch (const DtxTimeoutException& e) { - return XaResult(XA_RBTIMEOUT); + return XaResult(XA_STATUS_XA_OK); + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); } } @@ -633,9 +679,9 @@ XaResult SessionAdapter::DtxHandlerImpl::prepare(const Xid& xid) { try { bool ok = getBroker().getDtxManager().prepare(convert(xid)); - return XaResult(ok ? XA_OK : XA_RBROLLBACK); - } catch (const DtxTimeoutException& e) { - return XaResult(XA_RBTIMEOUT); + return XaResult(ok ? XA_STATUS_XA_OK : XA_STATUS_XA_RBROLLBACK); + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); } } @@ -644,9 +690,9 @@ XaResult SessionAdapter::DtxHandlerImpl::commit(const Xid& xid, { try { bool ok = getBroker().getDtxManager().commit(convert(xid), onePhase); - return XaResult(ok ? XA_OK : XA_RBROLLBACK); - } catch (const DtxTimeoutException& e) { - return XaResult(XA_RBTIMEOUT); + return XaResult(ok ? XA_STATUS_XA_OK : XA_STATUS_XA_RBROLLBACK); + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); } } @@ -655,9 +701,9 @@ XaResult SessionAdapter::DtxHandlerImpl::rollback(const Xid& xid) { try { getBroker().getDtxManager().rollback(convert(xid)); - return XaResult(XA_OK); - } catch (const DtxTimeoutException& e) { - return XaResult(XA_RBTIMEOUT); + return XaResult(XA_STATUS_XA_OK); + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); } } @@ -699,11 +745,11 @@ void SessionAdapter::DtxHandlerImpl::setTimeout(const Xid& xid, Queue::shared_ptr SessionAdapter::HandlerHelper::getQueue(const string& name) const { Queue::shared_ptr queue; if (name.empty()) { - throw amqp_0_10::IllegalArgumentException(QPID_MSG("No queue name specified.")); + throw framing::IllegalArgumentException(QPID_MSG("No queue name specified.")); } else { queue = session.getBroker().getQueues().find(name); if (!queue) - throw amqp_0_10::NotFoundException(QPID_MSG("Queue not found: "<<name)); + throw framing::NotFoundException(QPID_MSG("Queue not found: "<<name)); } return queue; } diff --git a/cpp/src/qpid/broker/SessionAdapter.h b/cpp/src/qpid/broker/SessionAdapter.h index 4eaaf13f8d..b69f258037 100644 --- a/cpp/src/qpid/broker/SessionAdapter.h +++ b/cpp/src/qpid/broker/SessionAdapter.h @@ -19,15 +19,16 @@ * */ -#include "HandlerImpl.h" +#include "qpid/broker/HandlerImpl.h" -#include "ConnectionToken.h" -#include "OwnershipToken.h" +#include "qpid/broker/ConnectionToken.h" +#include "qpid/broker/OwnershipToken.h" #include "qpid/Exception.h" #include "qpid/framing/AMQP_ServerOperations.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/framing/StructHelper.h" +#include <algorithm> #include <vector> #include <boost/function.hpp> #include <boost/shared_ptr.hpp> @@ -68,6 +69,12 @@ class Queue; FileHandler* getFileHandler() { throw framing::NotImplementedException("Class not implemented"); } StreamHandler* getStreamHandler() { throw framing::NotImplementedException("Class not implemented"); } + template <class F> void eachExclusiveQueue(F f) + { + queueImpl.eachExclusiveQueue(f); + } + + private: //common base for utility methods etc that are specific to this adapter struct HandlerHelper : public HandlerImpl @@ -102,14 +109,14 @@ class Queue; const std::string& routingKey, const framing::FieldTable& arguments); private: - void checkType(shared_ptr<Exchange> exchange, const std::string& type); + void checkType(boost::shared_ptr<Exchange> exchange, const std::string& type); - void checkAlternate(shared_ptr<Exchange> exchange, - shared_ptr<Exchange> alternate); + void checkAlternate(boost::shared_ptr<Exchange> exchange, + boost::shared_ptr<Exchange> alternate); }; class QueueHandlerImpl : public QueueHandler, - public HandlerHelper, public OwnershipToken + public HandlerHelper { Broker& broker; std::vector< boost::shared_ptr<Queue> > exclusiveQueues; @@ -130,6 +137,10 @@ class Queue; bool isLocal(const ConnectionToken* t) const; void destroyExclusiveQueues(); + template <class F> void eachExclusiveQueue(F f) + { + std::for_each(exclusiveQueues.begin(), exclusiveQueues.end(), f); + } }; class MessageHandlerImpl : diff --git a/cpp/src/qpid/broker/SessionContext.h b/cpp/src/qpid/broker/SessionContext.h index 7a277964ab..afbbb2cc22 100644 --- a/cpp/src/qpid/broker/SessionContext.h +++ b/cpp/src/qpid/broker/SessionContext.h @@ -26,9 +26,9 @@ #include "qpid/framing/AMQP_ClientProxy.h" #include "qpid/framing/amqp_types.h" #include "qpid/sys/OutputControl.h" -#include "ConnectionState.h" -#include "OwnershipToken.h" - +#include "qpid/broker/ConnectionState.h" +#include "qpid/broker/OwnershipToken.h" +#include "qpid/SessionId.h" #include <boost/noncopyable.hpp> @@ -40,9 +40,12 @@ class SessionContext : public OwnershipToken, public sys::OutputControl public: virtual ~SessionContext(){} virtual bool isLocal(const ConnectionToken* t) const = 0; + virtual bool isAttached() const = 0; virtual ConnectionState& getConnection() = 0; virtual framing::AMQP_ClientProxy& getProxy() = 0; virtual Broker& getBroker() = 0; + virtual uint16_t getChannel() const = 0; + virtual const SessionId& getSessionId() const = 0; }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionHandler.cpp b/cpp/src/qpid/broker/SessionHandler.cpp index c752f6315b..7106f85807 100644 --- a/cpp/src/qpid/broker/SessionHandler.cpp +++ b/cpp/src/qpid/broker/SessionHandler.cpp @@ -18,9 +18,9 @@ * */ -#include "SessionHandler.h" -#include "SessionState.h" -#include "Connection.h" +#include "qpid/broker/SessionHandler.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/Connection.h" #include "qpid/log/Statement.h" #include <boost/bind.hpp> @@ -34,7 +34,8 @@ using namespace qpid::sys; SessionHandler::SessionHandler(Connection& c, ChannelId ch) : amqp_0_10::SessionHandler(&c.getOutput(), ch), connection(c), - proxy(out) + proxy(out), + clusterOrderProxy(c.getClusterOrderOutput() ? new SetChannelProxy(ch, c.getClusterOrderOutput()) : 0) {} SessionHandler::~SessionHandler() {} @@ -44,12 +45,18 @@ ClassId classId(AMQMethodBody* m) { return m ? m->amqpMethodId() : 0; } MethodId methodId(AMQMethodBody* m) { return m ? m->amqpClassId() : 0; } } // namespace -void SessionHandler::channelException(uint16_t, const std::string&) { - handleDetach(); +void SessionHandler::connectionException(framing::connection::CloseCode code, const std::string& msg) { + // NOTE: must tell the error listener _before_ calling connection.close() + if (connection.getErrorListener()) connection.getErrorListener()->connectionError(msg); + connection.close(code, msg); } -void SessionHandler::connectionException(uint16_t code, const std::string& msg) { - connection.close(code, msg, 0, 0); +void SessionHandler::channelException(framing::session::DetachCode, const std::string& msg) { + if (connection.getErrorListener()) connection.getErrorListener()->sessionError(getChannel(), msg); +} + +void SessionHandler::executionException(framing::execution::ErrorCode, const std::string& msg) { + if (connection.getErrorListener()) connection.getErrorListener()->sessionError(getChannel(), msg); } ConnectionState& SessionHandler::getConnection() { return connection; } @@ -71,6 +78,12 @@ void SessionHandler::setState(const std::string& name, bool force) { session = connection.broker.getSessionManager().attach(*this, id, force); } +void SessionHandler::detaching() +{ + assert(session.get()); + session->disableOutput(); +} + FrameHandler* SessionHandler::getInHandler() { return session.get() ? &session->in : 0; } qpid::SessionState* SessionHandler::getState() { return session.get(); } @@ -78,18 +91,31 @@ void SessionHandler::readyToSend() { if (session.get()) session->readyToSend(); } -// TODO aconway 2008-05-12: hacky - handle attached for bridge clients. -// We need to integrate the client code so we can run a real client -// in the bridge. -// -void SessionHandler::attached(const std::string& name) { - if (session.get()) - checkName(name); - else { +/** + * Used by inter-broker bridges to set up session id and attach + */ +void SessionHandler::attachAs(const std::string& name) +{ + SessionId id(connection.getUserId(), name); + SessionState::Configuration config = connection.broker.getSessionManager().getSessionConfig(); + session.reset(new SessionState(connection.getBroker(), *this, id, config)); + sendAttach(false); +} + +/** + * TODO: this is a little ugly, fix it; its currently still relied on + * for 'push' bridges + */ +void SessionHandler::attached(const std::string& name) +{ + if (session.get()) { + amqp_0_10::SessionHandler::attached(name); + } else { SessionId id(connection.getUserId(), name); SessionState::Configuration config = connection.broker.getSessionManager().getSessionConfig(); session.reset(new SessionState(connection.getBroker(), *this, id, config)); -} + markReadyToSend(); + } } }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionHandler.h b/cpp/src/qpid/broker/SessionHandler.h index 1aa3137fdf..ca6d6bb193 100644 --- a/cpp/src/qpid/broker/SessionHandler.h +++ b/cpp/src/qpid/broker/SessionHandler.h @@ -54,23 +54,42 @@ class SessionHandler : public 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(); - - // Overrides - void attached(const std::string& name); + void attached(const std::string& name);//used by 'pushing' inter-broker bridges + void attachAs(const std::string& name);//used by 'pulling' inter-broker bridges protected: virtual void setState(const std::string& sessionName, bool force); virtual qpid::SessionState* getState(); virtual framing::FrameHandler* getInHandler(); - virtual void channelException(uint16_t code, const std::string& msg); - virtual void connectionException(uint16_t code, const std::string& msg); + virtual void connectionException(framing::connection::CloseCode code, const std::string& msg); + virtual void channelException(framing::session::DetachCode, const std::string& msg); + virtual void executionException(framing::execution::ErrorCode, const std::string& msg); + virtual void detaching(); virtual void readyToSend(); private: + struct SetChannelProxy : public framing::AMQP_ClientProxy { // Proxy that sets the channel. + framing::ChannelHandler setChannel; + SetChannelProxy(uint16_t ch, framing::FrameHandler* out) + : framing::AMQP_ClientProxy(setChannel), setChannel(ch, out) {} + }; + Connection& connection; framing::AMQP_ClientProxy proxy; std::auto_ptr<SessionState> session; + std::auto_ptr<SetChannelProxy> clusterOrderProxy; }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionManager.cpp b/cpp/src/qpid/broker/SessionManager.cpp index e7190fdae6..996a02f4c6 100644 --- a/cpp/src/qpid/broker/SessionManager.cpp +++ b/cpp/src/qpid/broker/SessionManager.cpp @@ -19,8 +19,8 @@ * */ -#include "SessionManager.h" -#include "SessionState.h" +#include "qpid/broker/SessionManager.h" +#include "qpid/broker/SessionState.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/log/Statement.h" #include "qpid/log/Helpers.h" @@ -86,9 +86,14 @@ void SessionManager::forget(const SessionId& id) { void SessionManager::eraseExpired() { // Called with lock held. if (!detached.empty()) { - Detached::iterator keep = std::lower_bound( - detached.begin(), detached.end(), now(), - boost::bind(std::less<AbsTime>(), boost::bind(&SessionState::expiry, _1), _2)); + // This used to use a more elegant invocation of std::lower_bound + // but violated the strict weak ordering rule which Visual Studio + // enforced. See QPID-1424 for more info should you be tempted to + // replace the loop with something more elegant. + AbsTime now = AbsTime::now(); + Detached::iterator keep = detached.begin(); + while ((keep != detached.end()) && ((*keep).expiry < now)) + keep++; if (detached.begin() != keep) { QPID_LOG(debug, "Expiring sessions: " << log::formatList(detached.begin(), keep)); detached.erase(detached.begin(), keep); diff --git a/cpp/src/qpid/broker/SessionState.cpp b/cpp/src/qpid/broker/SessionState.cpp index aa6f6b7520..4c5aaf7fc4 100644 --- a/cpp/src/qpid/broker/SessionState.cpp +++ b/cpp/src/qpid/broker/SessionState.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,18 +18,22 @@ * under the License. * */ -#include "SessionState.h" -#include "Broker.h" -#include "ConnectionState.h" -#include "MessageDelivery.h" -#include "SessionManager.h" -#include "SessionHandler.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/ConnectionState.h" +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/SessionManager.h" +#include "qpid/broker/SessionHandler.h" +#include "qpid/broker/RateFlowcontrol.h" +#include "qpid/sys/Timer.h" #include "qpid/framing/AMQContentBody.h" #include "qpid/framing/AMQHeaderBody.h" #include "qpid/framing/AMQMethodBody.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/framing/ServerInvoker.h" #include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/framing/AMQP_ClientProxy.h" #include <boost/bind.hpp> #include <boost/lexical_cast.hpp> @@ -44,36 +48,51 @@ using qpid::management::ManagementAgent; using qpid::management::ManagementObject; using qpid::management::Manageable; using qpid::management::Args; +using qpid::sys::AbsTime; +//using qpid::sys::Timer; +namespace _qmf = qmf::org::apache::qpid::broker; SessionState::SessionState( - Broker& b, SessionHandler& h, const SessionId& id, const SessionState::Configuration& config) + Broker& b, SessionHandler& h, const SessionId& id, const SessionState::Configuration& config) : qpid::SessionState(id, config), broker(b), handler(&h), - ignoring(false), semanticState(*this, *this), adapter(semanticState), msgBuilder(&broker.getStore(), broker.getStagingThreshold()), enqueuedOp(boost::bind(&SessionState::enqueued, this, _1)), - mgmtObject(0) + mgmtObject(0), + rateFlowcontrol(0) { + uint32_t maxRate = broker.getOptions().maxSessionRate; + if (maxRate) { + if (handler->getConnection().getClientThrottling()) { + rateFlowcontrol.reset(new RateFlowcontrol(maxRate)); + } else { + QPID_LOG(warning, getId() << ": Unable to flow control client - client doesn't support"); + } + } Manageable* parent = broker.GetVhostObject (); if (parent != 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + ManagementAgent* agent = getBroker().getManagementAgent(); if (agent != 0) { - mgmtObject = new management::Session (agent, this, parent, getId().getName()); + mgmtObject = new _qmf::Session + (agent, this, parent, getId().getName()); mgmtObject->set_attached (0); mgmtObject->set_detachedLifespan (0); - agent->addObject (mgmtObject); + mgmtObject->clr_expireTime(); + if (rateFlowcontrol) mgmtObject->set_maxClientRate(maxRate); + agent->addObject (mgmtObject, agent->allocateId(this)); } } attach(h); } SessionState::~SessionState() { - // Remove ID from active session list. - broker.getSessionManager().forget(getId()); if (mgmtObject != 0) mgmtObject->resourceDestroy (); + + if (flowControlTimer) + flowControlTimer->cancel(); } AMQP_ClientProxy& SessionState::getProxy() { @@ -81,6 +100,11 @@ AMQP_ClientProxy& SessionState::getProxy() { return handler->getProxy(); } +uint16_t SessionState::getChannel() const { + assert(isAttached()); + return handler->getChannel(); +} + ConnectionState& SessionState::getConnection() { assert(isAttached()); return handler->getConnection(); @@ -92,18 +116,19 @@ bool SessionState::isLocal(const ConnectionToken* t) const } void SessionState::detach() { - // activateOutput can be called in a different thread, lock to protect attached status - Mutex::ScopedLock l(lock); QPID_LOG(debug, getId() << ": detached on broker."); - getConnection().outputTasks.removeOutputTask(&semanticState); + disableOutput(); handler = 0; if (mgmtObject != 0) mgmtObject->set_attached (0); } +void SessionState::disableOutput() +{ + semanticState.detached(); //prevents further activateOutput calls until reattached +} + void SessionState::attach(SessionHandler& h) { - // activateOutput can be called in a different thread, lock to protect attached status - Mutex::ScopedLock l(lock); QPID_LOG(debug, getId() << ": attached on broker."); handler = &h; if (mgmtObject != 0) @@ -114,34 +139,42 @@ void SessionState::attach(SessionHandler& h) { } } +void SessionState::abort() { + if (isAttached()) + getConnection().outputTasks.abort(); +} + void SessionState::activateOutput() { - // activateOutput can be called in a different thread, lock to protect attached status - Mutex::ScopedLock l(lock); - if (isAttached()) + if (isAttached()) getConnection().outputTasks.activateOutput(); } +void SessionState::giveReadCredit(int32_t credit) { + if (isAttached()) + getConnection().outputTasks.giveReadCredit(credit); +} + ManagementObject* SessionState::GetManagementObject (void) const { return (ManagementObject*) mgmtObject; } Manageable::status_t SessionState::ManagementMethod (uint32_t methodId, - Args& /*args*/) + Args& /*args*/, + string& /*text*/) { Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; switch (methodId) { - case management::Session::METHOD_DETACH : - if (handler != 0) - { + case _qmf::Session::METHOD_DETACH : + if (handler != 0) { handler->sendDetach(); } status = Manageable::STATUS_OK; break; - case management::Session::METHOD_CLOSE : + case _qmf::Session::METHOD_CLOSE : /* if (handler != 0) { @@ -151,8 +184,8 @@ Manageable::status_t SessionState::ManagementMethod (uint32_t methodId, break; */ - case management::Session::METHOD_SOLICITACK : - case management::Session::METHOD_RESETLIFESPAN : + case _qmf::Session::METHOD_SOLICITACK : + case _qmf::Session::METHOD_RESETLIFESPAN : status = Manageable::STATUS_NOT_IMPLEMENTED; break; } @@ -162,18 +195,42 @@ Manageable::status_t SessionState::ManagementMethod (uint32_t methodId, void SessionState::handleCommand(framing::AMQMethodBody* method, const SequenceNumber& id) { Invoker::Result invocation = invoke(adapter, *method); - receiverCompleted(id); + receiverCompleted(id); if (!invocation.wasHandled()) { throw NotImplementedException(QPID_MSG("Not implemented: " << *method)); } else if (invocation.hasResult()) { getProxy().getExecution().result(id, invocation.getResult()); } - if (method->isSync()) { + if (method->isSync()) { incomplete.process(enqueuedOp, true); - sendCompletion(); + sendAcceptAndCompletion(); } } +struct ScheduledCreditTask : public sys::TimerTask { + sys::Timer& timer; + SessionState& sessionState; + ScheduledCreditTask(const qpid::sys::Duration& d, sys::Timer& t, + SessionState& s) : + TimerTask(d), + timer(t), + sessionState(s) + {} + + void fire() { + // This is the best we can currently do to avoid a destruction/fire race + sessionState.getConnection().requestIOProcessing(boost::bind(&ScheduledCreditTask::sendCredit, this)); + } + + void sendCredit() { + if ( !sessionState.processSendCredit(0) ) { + QPID_LOG(warning, sessionState.getId() << ": Reschedule sending credit"); + setupNextFire(); + timer.add(this); + } + } +}; + void SessionState::handleContent(AMQFrame& frame, const SequenceNumber& id) { if (frame.getBof() && frame.getBos()) //start of frameset @@ -183,14 +240,13 @@ void SessionState::handleContent(AMQFrame& frame, const SequenceNumber& id) if (frame.getEof() && frame.getEos()) {//end of frameset if (frame.getBof()) { //i.e this is a just a command frame, add a dummy header - AMQFrame header; - header.setBody(AMQHeaderBody()); + AMQFrame header((AMQHeaderBody())); header.setBof(false); header.setEof(false); - msg->getFrames().append(header); + msg->getFrames().append(header); } msg->setPublisher(&getConnection()); - semanticState.handle(msg); + semanticState.handle(msg); msgBuilder.end(); if (msg->isEnqueueComplete()) { @@ -199,51 +255,80 @@ void SessionState::handleContent(AMQFrame& frame, const SequenceNumber& id) incomplete.add(msg); } - //hold up execution until async enqueue is complete - if (msg->getFrames().getMethod()->isSync()) { + //hold up execution until async enqueue is complete + if (msg->getFrames().getMethod()->isSync()) { incomplete.process(enqueuedOp, true); - sendCompletion(); + sendAcceptAndCompletion(); } else { incomplete.process(enqueuedOp, false); } } + + // Handle producer session flow control + if (rateFlowcontrol && frame.getBof() && frame.getBos()) { + if ( !processSendCredit(1) ) { + QPID_LOG(debug, getId() << ": Schedule sending credit"); + sys::Timer& timer = getBroker().getTimer(); + // Use heuristic for scheduled credit of time for 50 messages, but not longer than 500ms + sys::Duration d = std::min(sys::TIME_SEC * 50 / rateFlowcontrol->getRate(), 500 * sys::TIME_MSEC); + flowControlTimer = new ScheduledCreditTask(d, timer, *this); + timer.add(flowControlTimer); + } + } +} + +bool SessionState::processSendCredit(uint32_t msgs) +{ + qpid::sys::ScopedLock<Mutex> l(rateLock); + // Check for violating flow control + if ( msgs > 0 && rateFlowcontrol->flowStopped() ) { + QPID_LOG(warning, getId() << ": producer throttling violation"); + // TODO: Probably do message.stop("") first time then disconnect + // See comment on getClusterOrderProxy() in .h file + getClusterOrderProxy().getMessage().stop(""); + return true; + } + AbsTime now = AbsTime::now(); + uint32_t sendCredit = rateFlowcontrol->receivedMessage(now, msgs); + if (mgmtObject) mgmtObject->dec_clientCredit(msgs); + if ( sendCredit>0 ) { + QPID_LOG(debug, getId() << ": send producer credit " << sendCredit); + getClusterOrderProxy().getMessage().flow("", 0, sendCredit); + rateFlowcontrol->sentCredit(now, sendCredit); + if (mgmtObject) mgmtObject->inc_clientCredit(sendCredit); + return true; + } else { + return !rateFlowcontrol->flowStopped() ; + } +} + +void SessionState::sendAcceptAndCompletion() +{ + if (!accepted.empty()) { + getProxy().getMessage().accept(accepted); + accepted.clear(); + } + sendCompletion(); } void SessionState::enqueued(boost::intrusive_ptr<Message> msg) { receiverCompleted(msg->getCommandId()); - if (msg->requiresAccept()) - getProxy().getMessage().accept(SequenceSet(msg->getCommandId())); + if (msg->requiresAccept()) + accepted.add(msg->getCommandId()); } void SessionState::handleIn(AMQFrame& frame) { SequenceNumber commandId = receiverGetCurrent(); - try { - //TODO: make command handling more uniform, regardless of whether - //commands carry content. - AMQMethodBody* m = frame.getMethod(); - if (m == 0 || m->isContentBearing()) { - handleContent(frame, commandId); - } else if (frame.getBof() && frame.getEof()) { - handleCommand(frame.getMethod(), commandId); - } else { - throw InternalErrorException("Cannot handle multi-frame command segments yet"); - } - } catch(const SessionException& e) { - //TODO: better implementation of new exception handling mechanism - - //0-10 final changes the types of exceptions, 'model layer' - //exceptions will all be session exceptions regardless of - //current channel/connection classification - - AMQMethodBody* m = frame.getMethod(); - if (m) { - getProxy().getExecution().exception(e.code, commandId, m->amqpClassId(), m->amqpMethodId(), 0, e.what(), FieldTable()); - } else { - getProxy().getExecution().exception(e.code, commandId, 0, 0, 0, e.what(), FieldTable()); - } - ignoring = true; - handler->sendDetach(); + //TODO: make command handling more uniform, regardless of whether + //commands carry content. + AMQMethodBody* m = frame.getMethod(); + if (m == 0 || m->isContentBearing()) { + handleContent(frame, commandId); + } else if (frame.getBof() && frame.getEof()) { + handleCommand(frame.getMethod(), commandId); + } else { + throw InternalErrorException("Cannot handle multi-frame command segments yet"); } } @@ -252,32 +337,50 @@ void SessionState::handleOut(AMQFrame& frame) { handler->out(frame); } -DeliveryId SessionState::deliver(QueuedMessage& msg, DeliveryToken::shared_ptr token) +void SessionState::deliver(DeliveryRecord& msg, bool sync) { uint32_t maxFrameSize = getConnection().getFrameMax(); assert(senderGetCommandPoint().offset == 0); SequenceNumber commandId = senderGetCommandPoint().command; - MessageDelivery::deliver(msg, getProxy().getHandler(), commandId, token, maxFrameSize); + msg.deliver(getProxy().getHandler(), commandId, maxFrameSize); assert(senderGetCommandPoint() == SessionPoint(commandId+1, 0)); // Delivery has moved sendPoint. - return commandId; + if (sync) { + AMQP_ClientProxy::Execution& p(getProxy().getExecution()); + Proxy::ScopedSync s(p); + p.sync(); + } } -void SessionState::sendCompletion() { handler->sendCompletion(); } +void SessionState::sendCompletion() { + handler->sendCompletion(); +} void SessionState::senderCompleted(const SequenceSet& commands) { qpid::SessionState::senderCompleted(commands); - for (SequenceSet::RangeIterator i = commands.rangesBegin(); i != commands.rangesEnd(); i++) - semanticState.completed(i->first(), i->last()); + semanticState.completed(commands); } void SessionState::readyToSend() { QPID_LOG(debug, getId() << ": ready to send, activating output."); assert(handler); - sys::AggregateOutput& tasks = handler->getConnection().outputTasks; - tasks.addOutputTask(&semanticState); - tasks.activateOutput(); + semanticState.attached(); + if (rateFlowcontrol) { + qpid::sys::ScopedLock<Mutex> l(rateLock); + // Issue initial credit - use a heuristic here issue min of 300 messages or 1 secs worth + uint32_t credit = std::min(rateFlowcontrol->getRate(), 300U); + QPID_LOG(debug, getId() << ": Issuing producer message credit " << credit); + // See comment on getClusterOrderProxy() in .h file + getClusterOrderProxy().getMessage().setFlowMode("", 0); + getClusterOrderProxy().getMessage().flow("", 0, credit); + rateFlowcontrol->sentCredit(AbsTime::now(), credit); + if (mgmtObject) mgmtObject->inc_clientCredit(credit); + } } Broker& SessionState::getBroker() { return broker; } +framing::AMQP_ClientProxy& SessionState::getClusterOrderProxy() { + return handler->getClusterOrderProxy(); +} + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionState.h b/cpp/src/qpid/broker/SessionState.h index 96f2e8f512..eade93ddaa 100644 --- a/cpp/src/qpid/broker/SessionState.h +++ b/cpp/src/qpid/broker/SessionState.h @@ -25,16 +25,15 @@ #include "qpid/SessionState.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/SequenceSet.h" -#include "qpid/sys/Mutex.h" #include "qpid/sys/Time.h" #include "qpid/management/Manageable.h" -#include "qpid/management/Session.h" -#include "SessionAdapter.h" -#include "DeliveryAdapter.h" -#include "IncompleteMessageList.h" -#include "MessageBuilder.h" -#include "SessionContext.h" -#include "SemanticState.h" +#include "qmf/org/apache/qpid/broker/Session.h" +#include "qpid/broker/SessionAdapter.h" +#include "qpid/broker/DeliveryAdapter.h" +#include "qpid/broker/IncompleteMessageList.h" +#include "qpid/broker/MessageBuilder.h" +#include "qpid/broker/SessionContext.h" +#include "qpid/broker/SemanticState.h" #include <boost/noncopyable.hpp> #include <boost/scoped_ptr.hpp> @@ -49,6 +48,10 @@ namespace framing { class AMQP_ClientProxy; } +namespace sys { +class TimerTask; +} + namespace broker { class Broker; @@ -56,12 +59,13 @@ class ConnectionState; class Message; class SessionHandler; class SessionManager; +class RateFlowcontrol; /** * Broker-side session state includes session's handler chains, which * may themselves have state. */ -class SessionState : public qpid::SessionState, +class SessionState : public qpid::SessionState, public SessionContext, public DeliveryAdapter, public management::Manageable, @@ -74,10 +78,14 @@ class SessionState : public qpid::SessionState, void detach(); void attach(SessionHandler& handler); + void disableOutput(); /** @pre isAttached() */ framing::AMQP_ClientProxy& getProxy(); - + + /** @pre isAttached() */ + uint16_t getChannel() const; + /** @pre isAttached() */ ConnectionState& getConnection(); bool isLocal(const ConnectionToken* t) const; @@ -85,22 +93,33 @@ class SessionState : public qpid::SessionState, Broker& getBroker(); /** OutputControl **/ + void abort(); void activateOutput(); + void giveReadCredit(int32_t); void senderCompleted(const framing::SequenceSet& ranges); - + void sendCompletion(); //delivery adapter methods: - DeliveryId deliver(QueuedMessage& msg, DeliveryToken::shared_ptr token); + void deliver(DeliveryRecord&, bool sync); // Manageable entry points management::ManagementObject* GetManagementObject (void) const; management::Manageable::status_t - ManagementMethod (uint32_t methodId, management::Args& args); + 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<Message> getMessageInProgress() { return msgBuilder.getMessage(); } + SessionAdapter& getSessionAdapter() { return adapter; } + + bool processSendCredit(uint32_t msgs); + + const SessionId& getSessionId() const { return getId(); } + private: void handleCommand(framing::AMQMethodBody* method, const framing::SequenceNumber& id); @@ -114,20 +133,34 @@ class SessionState : public qpid::SessionState, void handleInLast(framing::AMQFrame& frame); void handleOutLast(framing::AMQFrame& frame); + 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; + SessionHandler* handler; sys::AbsTime expiry; // Used by SessionManager. - sys::Mutex lock; - bool ignoring; - std::string name; SemanticState semanticState; SessionAdapter adapter; MessageBuilder msgBuilder; IncompleteMessageList incomplete; IncompleteMessageList::CompletionListener enqueuedOp; - management::Session* mgmtObject; + qmf::org::apache::qpid::broker::Session* mgmtObject; + qpid::framing::SequenceSet accepted; + + // State used for producer flow control (rate limited) + qpid::sys::Mutex rateLock; + boost::scoped_ptr<RateFlowcontrol> rateFlowcontrol; + boost::intrusive_ptr<sys::TimerTask> flowControlTimer; - friend class SessionManager; + friend class SessionManager; }; diff --git a/cpp/src/qpid/broker/SignalHandler.cpp b/cpp/src/qpid/broker/SignalHandler.cpp index fee54cfdfc..b565cfd419 100644 --- a/cpp/src/qpid/broker/SignalHandler.cpp +++ b/cpp/src/qpid/broker/SignalHandler.cpp @@ -18,8 +18,8 @@ * under the License. * */ -#include "SignalHandler.h" -#include "Broker.h" +#include "qpid/broker/SignalHandler.h" +#include "qpid/broker/Broker.h" #include <signal.h> namespace qpid { @@ -36,11 +36,10 @@ void SignalHandler::setBroker(const boost::intrusive_ptr<Broker>& b) { signal(SIGHUP,SIG_IGN); // TODO aconway 2007-07-18: reload config. signal(SIGCHLD,SIG_IGN); - signal(SIGTSTP,SIG_IGN); - signal(SIGTTOU,SIG_IGN); - signal(SIGTTIN,SIG_IGN); } +void SignalHandler::shutdown() { shutdownHandler(0); } + void SignalHandler::shutdownHandler(int) { if (broker.get()) { broker->shutdown(); diff --git a/cpp/src/qpid/broker/SignalHandler.h b/cpp/src/qpid/broker/SignalHandler.h index d2cdfae07c..bbe831b61d 100644 --- a/cpp/src/qpid/broker/SignalHandler.h +++ b/cpp/src/qpid/broker/SignalHandler.h @@ -38,6 +38,9 @@ class SignalHandler /** Set the broker to be shutdown on signals */ static void setBroker(const boost::intrusive_ptr<Broker>& broker); + /** Initiate shut-down of broker */ + static void shutdown(); + private: static void shutdownHandler(int); static boost::intrusive_ptr<Broker> broker; diff --git a/cpp/src/qpid/broker/System.cpp b/cpp/src/qpid/broker/System.cpp index 6c58339432..455ad11cf2 100644 --- a/cpp/src/qpid/broker/System.cpp +++ b/cpp/src/qpid/broker/System.cpp @@ -17,20 +17,22 @@ // under the License. // -#include "System.h" -#include "qpid/agent/ManagementAgent.h" +#include "qpid/broker/System.h" +#include "qpid/broker/Broker.h" +#include "qpid/management/ManagementAgent.h" #include "qpid/framing/Uuid.h" -#include <sys/utsname.h> +#include "qpid/sys/SystemInfo.h" #include <iostream> #include <fstream> using qpid::management::ManagementAgent; using namespace qpid::broker; using namespace std; +namespace _qmf = qmf::org::apache::qpid::broker; -System::System (string _dataDir) : mgmtObject(0) +System::System (string _dataDir, Broker* broker) : mgmtObject(0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + ManagementAgent* agent = broker ? broker->getManagementAgent() : 0; if (agent != 0) { @@ -62,18 +64,20 @@ System::System (string _dataDir) : mgmtObject(0) } } - mgmtObject = new management::System (agent, this, systemId); - struct utsname _uname; - if (uname (&_uname) == 0) - { - mgmtObject->set_osName (std::string (_uname.sysname)); - mgmtObject->set_nodeName (std::string (_uname.nodename)); - mgmtObject->set_release (std::string (_uname.release)); - mgmtObject->set_version (std::string (_uname.version)); - mgmtObject->set_machine (std::string (_uname.machine)); - } + mgmtObject = new _qmf::System (agent, this, systemId); + std::string sysname, nodename, release, version, machine; + qpid::sys::SystemInfo::getSystemId (sysname, + nodename, + release, + version, + machine); + mgmtObject->set_osName (sysname); + mgmtObject->set_nodeName (nodename); + mgmtObject->set_release (release); + mgmtObject->set_version (version); + mgmtObject->set_machine (machine); - agent->addObject (mgmtObject, 1, 1); + agent->addObject (mgmtObject, 0x1000000000000001LL); } } diff --git a/cpp/src/qpid/broker/System.h b/cpp/src/qpid/broker/System.h index ef7c6ba73b..0fc2c2bd88 100644 --- a/cpp/src/qpid/broker/System.h +++ b/cpp/src/qpid/broker/System.h @@ -21,24 +21,26 @@ // #include "qpid/management/Manageable.h" -#include "qpid/management/System.h" +#include "qmf/org/apache/qpid/broker/System.h" #include <boost/shared_ptr.hpp> #include <string> namespace qpid { namespace broker { +class Broker; + class System : public management::Manageable { private: - management::System* mgmtObject; + qmf::org::apache::qpid::broker::System* mgmtObject; public: typedef boost::shared_ptr<System> shared_ptr; - System (std::string _dataDir); + System (std::string _dataDir, Broker* broker = 0); management::ManagementObject* GetManagementObject (void) const { return mgmtObject; } diff --git a/cpp/src/qpid/broker/Timer.cpp b/cpp/src/qpid/broker/Timer.cpp deleted file mode 100644 index 0b0d3ba63d..0000000000 --- a/cpp/src/qpid/broker/Timer.cpp +++ /dev/null @@ -1,104 +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 "Timer.h" -#include <iostream> - -using boost::intrusive_ptr; -using qpid::sys::AbsTime; -using qpid::sys::Duration; -using qpid::sys::Monitor; -using qpid::sys::Thread; -using namespace qpid::broker; - -TimerTask::TimerTask(Duration timeout) : - duration(timeout), time(AbsTime::now(), timeout), cancelled(false) {} - -TimerTask::TimerTask(AbsTime _time) : - duration(0), time(_time), cancelled(false) {} - -TimerTask::~TimerTask(){} - -void TimerTask::reset() { time = AbsTime(AbsTime::now(), duration); } - -Timer::Timer() : active(false) -{ - start(); -} - -Timer::~Timer() -{ - stop(); -} - -void Timer::run() -{ - Monitor::ScopedLock l(monitor); - while(active){ - if (tasks.empty()) { - monitor.wait(); - } else { - intrusive_ptr<TimerTask> t = tasks.top(); - if (t->cancelled) { - tasks.pop(); - } else if(t->time < AbsTime::now()) { - tasks.pop(); - Monitor::ScopedUnlock u(monitor); - t->fire(); - } else { - monitor.wait(t->time); - } - } - } -} - -void Timer::add(intrusive_ptr<TimerTask> task) -{ - Monitor::ScopedLock l(monitor); - tasks.push(task); - monitor.notify(); -} - -void Timer::start() -{ - Monitor::ScopedLock l(monitor); - if (!active) { - active = true; - runner = Thread(this); - } -} - -void Timer::stop() -{ - { - Monitor::ScopedLock l(monitor); - if (!active) return; - active = false; - monitor.notifyAll(); - } - runner.join(); -} - -bool Later::operator()(const intrusive_ptr<TimerTask>& a, - const intrusive_ptr<TimerTask>& b) const -{ - return a.get() && b.get() && a->time > b->time; -} - diff --git a/cpp/src/qpid/broker/Timer.h b/cpp/src/qpid/broker/Timer.h deleted file mode 100644 index f702f0f32d..0000000000 --- a/cpp/src/qpid/broker/Timer.h +++ /dev/null @@ -1,79 +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. - * - */ -#ifndef _Timer_ -#define _Timer_ - -#include "qpid/sys/Monitor.h" -#include "qpid/sys/Thread.h" -#include "qpid/sys/Runnable.h" -#include "qpid/RefCounted.h" - -#include <memory> -#include <queue> - -#include <boost/intrusive_ptr.hpp> - -namespace qpid { -namespace broker { - -struct TimerTask : public RefCounted { - const qpid::sys::Duration duration; - qpid::sys::AbsTime time; - volatile bool cancelled; - - TimerTask(qpid::sys::Duration timeout); - TimerTask(qpid::sys::AbsTime time); - virtual ~TimerTask(); - void reset(); - virtual void fire() = 0; -}; - -struct Later { - bool operator()(const boost::intrusive_ptr<TimerTask>& a, - const boost::intrusive_ptr<TimerTask>& b) const; -}; - -class Timer : private qpid::sys::Runnable { - protected: - qpid::sys::Monitor monitor; - std::priority_queue<boost::intrusive_ptr<TimerTask>, - std::vector<boost::intrusive_ptr<TimerTask> >, - Later> tasks; - qpid::sys::Thread runner; - bool active; - - virtual void run(); - - public: - Timer(); - virtual ~Timer(); - - void add(boost::intrusive_ptr<TimerTask> task); - void start(); - void stop(); - -}; - - -}} - - -#endif diff --git a/cpp/src/qpid/broker/TopicExchange.cpp b/cpp/src/qpid/broker/TopicExchange.cpp index 48d6e88503..dd57549b5d 100644 --- a/cpp/src/qpid/broker/TopicExchange.cpp +++ b/cpp/src/qpid/broker/TopicExchange.cpp @@ -18,138 +18,249 @@ * under the License. * */ -#include "TopicExchange.h" +#include "qpid/broker/TopicExchange.h" #include <algorithm> -using namespace qpid::broker; + +namespace qpid { +namespace broker { + using namespace qpid::framing; using namespace qpid::sys; +using namespace std; +namespace _qmf = qmf::org::apache::qpid::broker; + // TODO aconway 2006-09-20: More efficient matching algorithm. // Areas for improvement: // - excessive string copying: should be 0 copy, match from original buffer. // - match/lookup: use descision tree or other more efficient structure. -Tokens& Tokens::operator=(const std::string& s) { - clear(); - if (s.empty()) return *this; - std::string::const_iterator i = s.begin(); - while (true) { - // Invariant: i is at the beginning of the next untokenized word. - std::string::const_iterator j = std::find(i, s.end(), '.'); - push_back(std::string(i, j)); - if (j == s.end()) return *this; - i = j + 1; - } - return *this; -} +namespace +{ +const std::string qpidFedOp("qpid.fed.op"); +const std::string qpidFedTags("qpid.fed.tags"); +const std::string qpidFedOrigin("qpid.fed.origin"); -TopicPattern& TopicPattern::operator=(const Tokens& tokens) { - Tokens::operator=(tokens); - normalize(); - return *this; +const std::string fedOpBind("B"); +const std::string fedOpUnbind("U"); +const std::string fedOpReorigin("R"); +const std::string fedOpHello("H"); } + namespace { -const std::string hashmark("#"); -const std::string star("*"); -} +// Iterate over a string of '.'-separated tokens. +struct TokenIterator { + typedef pair<const char*,const char*> Token; + + TokenIterator(const char* b, const char* e) : token(make_pair(b, find(b,e,'.'))), end(e) {} + + bool finished() const { return !token.first; } + + void next() { + if (token.second == end) + token.first = token.second = 0; + else { + token.first=token.second+1; + token.second=(find(token.first, end, '.')); + } + } + + bool match1(char c) const { + return token.second==token.first+1 && *token.first == c; + } + + bool match(const Token& token2) { + ptrdiff_t l=len(); + return l == token2.second-token2.first && + strncmp(token.first, token2.first, l) == 0; + } + + ptrdiff_t len() const { return token.second - token.first; } -void TopicPattern::normalize() { - std::string word; - Tokens::iterator i = begin(); - while (i != end()) { - if (*i == hashmark) { - ++i; - while (i != end()) { - // Invariant: *(i-1)==#, [begin()..i-1] is normalized. - if (*i == star) { // Move * before #. - std::swap(*i, *(i-1)); - ++i; - } else if (*i == hashmark) { - erase(i); // Remove extra # - } else { - break; + Token token; + const char* end; +}; + +class Normalizer : public TokenIterator { + public: + Normalizer(string& p) + : TokenIterator(&p[0], &p[0]+p.size()), pattern(p) + { normalize(); } + + private: + // Apply 2 transformations: #.* -> *.# and #.# -> # + void normalize() { + while (!finished()) { + if (match1('#')) { + const char* hash1=token.first; + next(); + if (!finished()) { + if (match1('#')) { // Erase #.# -> # + pattern.erase(hash1-pattern.data(), 2); + token.first -= 2; + token.second -= 2; + end -= 2; + } + else if (match1('*')) { // Swap #.* -> *.# + swap(*const_cast<char*>(hash1), + *const_cast<char*>(token.first)); + } } } - } else { - i ++; + else + next(); } } -} + string& pattern; +}; -namespace { -// TODO aconway 2006-09-20: Ineficient to convert every routingKey to a string. -// Need StringRef class that operates on a string in place witout copy. -// Should be applied everywhere strings are extracted from frames. -// -bool do_match(Tokens::const_iterator pattern_begin, Tokens::const_iterator pattern_end, Tokens::const_iterator target_begin, Tokens::const_iterator target_end) -{ - // Invariant: [pattern_begin..p) matches [target_begin..t) - Tokens::const_iterator p = pattern_begin; - Tokens::const_iterator t = target_begin; - while (p != pattern_end && t != target_end) - { - if (*p == star || *p == *t) { - ++p, ++t; - } else if (*p == hashmark) { - ++p; - if (do_match(p, pattern_end, t, target_end)) return true; - while (t != target_end) { - ++t; - if (do_match(p, pattern_end, t, target_end)) return true; +class Matcher { + public: + Matcher(const string& p, const string& k) + : matched(), pattern(&p[0], &p[0]+p.size()), key(&k[0], &k[0]+k.size()) + { matched = match(); } + + operator bool() const { return matched; } + + private: + Matcher(const char* bp, const char* ep, const char* bk, const char* ek) + : matched(), pattern(bp,ep), key(bk,ek) { matched = match(); } + + bool match() { + // Invariant: pattern and key match up to but excluding + // pattern.token and key.token + while (!pattern.finished() && !key.finished()) { + if (pattern.match1('*') && !key.finished()) { + pattern.next(); + key.next(); } - return false; - } else { - return false; + else if (pattern.match1('#')) { + pattern.next(); + if (pattern.finished()) return true; // Trailing # matches anything. + while (!key.finished()) { + if (Matcher(pattern.token.first, pattern.end, + key.token.first, key.end)) + return true; + key.next(); + } + return false; + } + else if (pattern.len() == key.len() && + equal(pattern.token.first,pattern.token.second,key.token.first)) { + pattern.next(); + key.next(); + } + else + return false; } + if (!pattern.finished() && pattern.match1('#')) + pattern.next(); // Trailing # matches empty. + return pattern.finished() && key.finished(); } - while (p != pattern_end && *p == hashmark) ++p; // Ignore trailing # - return t == target_end && p == pattern_end; + + bool matched; + TokenIterator pattern, key; +}; } + +// Convert sequences of * and # to a sequence of * followed by a single # +string TopicExchange::normalize(const string& pattern) { + string normal(pattern); + Normalizer n(normal); + return normal; } -bool TopicPattern::match(const Tokens& target) const +bool TopicExchange::match(const string& pattern, const string& key) { - return do_match(begin(), end(), target.begin(), target.end()); + return Matcher(pattern, key); } -TopicExchange::TopicExchange(const string& _name, Manageable* _parent) : Exchange(_name, _parent) +TopicExchange::TopicExchange(const string& _name, Manageable* _parent, Broker* b) : Exchange(_name, _parent, b) { if (mgmtExchange != 0) mgmtExchange->set_type (typeName); } TopicExchange::TopicExchange(const std::string& _name, bool _durable, - const FieldTable& _args, Manageable* _parent) : - Exchange(_name, _durable, _args, _parent) + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b) { if (mgmtExchange != 0) mgmtExchange->set_type (typeName); } -bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* /*args*/){ - RWlock::ScopedWlock l(lock); - TopicPattern routingPattern(routingKey); - if (isBound(queue, routingPattern)) { - return false; - } else { - Binding::shared_ptr binding (new Binding (routingKey, queue, this)); - bindings[routingPattern].push_back(binding); - if (mgmtExchange != 0) { - mgmtExchange->inc_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->inc_bindingCount(); +bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args) +{ + string fedOp(args ? args->getAsString(qpidFedOp) : fedOpBind); + string fedTags(args ? args->getAsString(qpidFedTags) : ""); + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); + bool propagate = false; + bool reallyUnbind; + string routingPattern = normalize(routingKey); + + if (args == 0 || fedOp.empty() || fedOp == fedOpBind) { + RWlock::ScopedWlock l(lock); + if (isBound(queue, routingPattern)) { + return false; + } else { + Binding::shared_ptr binding (new Binding (routingPattern, queue, this, FieldTable(), fedOrigin)); + BoundKey& bk = bindings[routingPattern]; + bk.bindingVector.push_back(binding); + propagate = bk.fedBinding.addOrigin(fedOrigin); + if (mgmtExchange != 0) { + mgmtExchange->inc_bindingCount(); + } + } + } else if (fedOp == fedOpUnbind) { + { + RWlock::ScopedWlock l(lock); + BoundKey& bk = bindings[routingPattern]; + propagate = bk.fedBinding.delOrigin(fedOrigin); + reallyUnbind = bk.fedBinding.count() == 0; + } + if (reallyUnbind) + unbind(queue, routingPattern, 0); + } else if (fedOp == fedOpReorigin) { + /** gather up all the keys that need rebinding in a local vector + * while holding the lock. Then propagate once the lock is + * released + */ + std::vector<std::string> keys2prop; + { + RWlock::ScopedRlock l(lock); + for (BindingMap::iterator iter = bindings.begin(); + iter != bindings.end(); iter++) { + const BoundKey& bk = iter->second; + + if (bk.fedBinding.hasLocal()) { + keys2prop.push_back(iter->first); + } + } + } /* lock dropped */ + for (std::vector<std::string>::const_iterator key = keys2prop.begin(); + key != keys2prop.end(); key++) { + propagateFedOp( *key, string(), fedOpBind, string()); } - return true; } + + routeIVE(); + if (propagate) + propagateFedOp(routingKey, fedTags, fedOp, fedOrigin); + return true; } -bool TopicExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* /*args*/){ +bool TopicExchange::unbind(Queue::shared_ptr queue, const string& constRoutingKey, const FieldTable* /*args*/){ RWlock::ScopedWlock l(lock); - BindingMap::iterator bi = bindings.find(TopicPattern(routingKey)); - Binding::vector& qv(bi->second); + string routingKey = normalize(constRoutingKey); + + BindingMap::iterator bi = bindings.find(routingKey); if (bi == bindings.end()) return false; + BoundKey& bk = bi->second; + Binding::vector& qv(bk.bindingVector); + bool propagate = false; Binding::vector::iterator q; for (q = qv.begin(); q != qv.end(); q++) @@ -157,19 +268,22 @@ bool TopicExchange::unbind(Queue::shared_ptr queue, const string& routingKey, co break; if(q == qv.end()) return false; qv.erase(q); + propagate = bk.fedBinding.delOrigin(); if(qv.empty()) bindings.erase(bi); if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->dec_bindingCount(); } + + if (propagate) + propagateFedOp(routingKey, string(), fedOpUnbind, string()); return true; } -bool TopicExchange::isBound(Queue::shared_ptr queue, TopicPattern& pattern) +bool TopicExchange::isBound(Queue::shared_ptr queue, const string& pattern) { BindingMap::iterator bi = bindings.find(pattern); if (bi == bindings.end()) return false; - Binding::vector& qv(bi->second); + Binding::vector& qv(bi->second.bindingVector); Binding::vector::iterator q; for (q = qv.begin(); q != qv.end(); q++) if ((*q)->queue == queue) @@ -177,55 +291,41 @@ bool TopicExchange::isBound(Queue::shared_ptr queue, TopicPattern& pattern) return q != qv.end(); } -void TopicExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* /*args*/){ - RWlock::ScopedRlock l(lock); - uint32_t count(0); - Tokens tokens(routingKey); - - for (BindingMap::iterator i = bindings.begin(); i != bindings.end(); ++i) { - if (i->first.match(tokens)) { - Binding::vector& qv(i->second); - for(Binding::vector::iterator j = qv.begin(); j != qv.end(); j++, count++){ - msg.deliverTo((*j)->queue); - if ((*j)->mgmtBinding != 0) - (*j)->mgmtBinding->inc_msgMatched (); - } - } - } - - if (mgmtExchange != 0) +void TopicExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* /*args*/) +{ + Binding::vector mb; + BindingList b(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >); + PreRoute pr(msg, this); { - mgmtExchange->inc_msgReceives (); - mgmtExchange->inc_byteReceives (msg.contentSize ()); - if (count == 0) - { - mgmtExchange->inc_msgDrops (); - mgmtExchange->inc_byteDrops (msg.contentSize ()); - } - else - { - mgmtExchange->inc_msgRoutes (count); - mgmtExchange->inc_byteRoutes (count * msg.contentSize ()); + RWlock::ScopedRlock l(lock); + for (BindingMap::iterator i = bindings.begin(); i != bindings.end(); ++i) { + if (match(i->first, routingKey)) { + Binding::vector& qv(i->second.bindingVector); + for(Binding::vector::iterator j = qv.begin(); j != qv.end(); j++){ + b->push_back(*j); + } + } } } + doRoute(msg, b); } bool TopicExchange::isBound(Queue::shared_ptr queue, const string* const routingKey, const FieldTable* const) { + RWlock::ScopedRlock l(lock); if (routingKey && queue) { - TopicPattern key(*routingKey); + string key(normalize(*routingKey)); return isBound(queue, key); } else if (!routingKey && !queue) { return bindings.size() > 0; } else if (routingKey) { for (BindingMap::iterator i = bindings.begin(); i != bindings.end(); ++i) { - if (i->first.match(*routingKey)) { + if (match(i->first, *routingKey)) return true; } - } } else { for (BindingMap::iterator i = bindings.begin(); i != bindings.end(); ++i) { - Binding::vector& qv(i->second); + Binding::vector& qv(i->second.bindingVector); Binding::vector::iterator q; for (q = qv.begin(); q != qv.end(); q++) if ((*q)->queue == queue) @@ -233,10 +333,11 @@ bool TopicExchange::isBound(Queue::shared_ptr queue, const string* const routing } } return false; + return queue && routingKey; } TopicExchange::~TopicExchange() {} const std::string TopicExchange::typeName("topic"); - +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/TopicExchange.h b/cpp/src/qpid/broker/TopicExchange.h index 2e107142b7..3bbf143889 100644 --- a/cpp/src/qpid/broker/TopicExchange.h +++ b/cpp/src/qpid/broker/TopicExchange.h @@ -23,77 +23,57 @@ #include <map> #include <vector> -#include "Exchange.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" #include "qpid/framing/FieldTable.h" #include "qpid/sys/Monitor.h" -#include "Queue.h" +#include "qpid/broker/Queue.h" namespace qpid { namespace broker { -/** A vector of string tokens */ -class Tokens : public std::vector<std::string> { - public: - Tokens() {}; - // Default copy, assign, dtor are sufficient. - - /** Tokenize s, provides automatic conversion of string to Tokens */ - Tokens(const std::string& s) { operator=(s); } - /** Tokenizing assignment operator s */ - Tokens & operator=(const std::string& s); - - private: - size_t hash; -}; - - -/** - * Tokens that have been normalized as a pattern and can be matched - * with topic Tokens. Normalized meands all sequences of mixed * and - * # are reduced to a series of * followed by at most one #. - */ -class TopicPattern : public Tokens -{ - public: - TopicPattern() {} - // Default copy, assign, dtor are sufficient. - TopicPattern(const Tokens& tokens) { operator=(tokens); } - TopicPattern(const std::string& str) { operator=(str); } - TopicPattern& operator=(const Tokens&); - TopicPattern& operator=(const std::string& str) { return operator=(Tokens(str)); } - - /** Match a topic */ - bool match(const std::string& topic) { return match(Tokens(topic)); } - bool match(const Tokens& topic) const; - - private: - void normalize(); -}; - -class TopicExchange : public virtual Exchange{ - typedef std::map<TopicPattern, Binding::vector> BindingMap; +class TopicExchange : public virtual Exchange { + struct BoundKey { + Binding::vector bindingVector; + FedBinding fedBinding; + }; + typedef std::map<std::string, BoundKey> BindingMap; BindingMap bindings; qpid::sys::RWlock lock; - bool isBound(Queue::shared_ptr queue, TopicPattern& pattern); + bool isBound(Queue::shared_ptr queue, const string& pattern); + public: static const std::string typeName; - TopicExchange(const string& name, management::Manageable* parent = 0); - TopicExchange(const string& _name, bool _durable, - const qpid::framing::FieldTable& _args, management::Manageable* parent = 0); + static QPID_BROKER_EXTERN bool match(const std::string& pattern, const std::string& topic); + static QPID_BROKER_EXTERN std::string normalize(const std::string& pattern); + + QPID_BROKER_EXTERN TopicExchange(const string& name, + management::Manageable* parent = 0, Broker* broker = 0); + QPID_BROKER_EXTERN TopicExchange(const string& _name, + bool _durable, + const qpid::framing::FieldTable& _args, + management::Manageable* parent = 0, Broker* broker = 0); virtual std::string getType() const { return typeName; } - virtual bool bind(Queue::shared_ptr queue, const string& routingKey, const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue, + const string& routingKey, + const qpid::framing::FieldTable* args); virtual bool unbind(Queue::shared_ptr queue, const string& routingKey, const qpid::framing::FieldTable* args); - virtual void route(Deliverable& msg, const string& routingKey, const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual void route(Deliverable& msg, + const string& routingKey, + const qpid::framing::FieldTable* args); - virtual bool isBound(Queue::shared_ptr queue, const string* const routingKey, const qpid::framing::FieldTable* const args); + QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue, + const string* const routingKey, + const qpid::framing::FieldTable* const args); - virtual ~TopicExchange(); + QPID_BROKER_EXTERN virtual ~TopicExchange(); + virtual bool supportsDynamicBinding() { return true; } }; diff --git a/cpp/src/qpid/broker/TxAccept.cpp b/cpp/src/qpid/broker/TxAccept.cpp index 82acf61cd1..928ac12c10 100644 --- a/cpp/src/qpid/broker/TxAccept.cpp +++ b/cpp/src/qpid/broker/TxAccept.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "TxAccept.h" +#include "qpid/broker/TxAccept.h" #include "qpid/log/Statement.h" using std::bind1st; @@ -26,19 +26,56 @@ using std::bind2nd; using std::mem_fun_ref; using namespace qpid::broker; using qpid::framing::SequenceSet; +using qpid::framing::SequenceNumber; -TxAccept::TxAccept(SequenceSet& _acked, std::list<DeliveryRecord>& _unacked) : - acked(_acked), unacked(_unacked) {} +TxAccept::RangeOp::RangeOp(const AckRange& r) : range(r) {} + +void TxAccept::RangeOp::prepare(TransactionContext* ctxt) +{ + for_each(range.start, range.end, bind(&DeliveryRecord::dequeue, _1, ctxt)); +} + +void TxAccept::RangeOp::commit() +{ + for_each(range.start, range.end, bind(&DeliveryRecord::committed, _1)); + for_each(range.start, range.end, bind(&DeliveryRecord::setEnded, _1)); +} + +TxAccept::RangeOps::RangeOps(DeliveryRecords& u) : unacked(u) {} + +void TxAccept::RangeOps::operator()(SequenceNumber start, SequenceNumber end) +{ + ranges.push_back(RangeOp(DeliveryRecord::findRange(unacked, start, end))); +} + +void TxAccept::RangeOps::prepare(TransactionContext* ctxt) +{ + std::for_each(ranges.begin(), ranges.end(), bind(&RangeOp::prepare, _1, ctxt)); +} + +void TxAccept::RangeOps::commit() +{ + std::for_each(ranges.begin(), ranges.end(), bind(&RangeOp::commit, _1)); + //now remove if isRedundant(): + if (!ranges.empty()) { + DeliveryRecords::iterator begin = ranges.front().range.start; + DeliveryRecords::iterator end = ranges.back().range.end; + DeliveryRecords::iterator removed = remove_if(begin, end, mem_fun_ref(&DeliveryRecord::isRedundant)); + unacked.erase(removed, end); + } +} + +TxAccept::TxAccept(const SequenceSet& _acked, DeliveryRecords& _unacked) : + acked(_acked), unacked(_unacked), ops(unacked) +{ + //populate the ops + acked.for_each(ops); +} bool TxAccept::prepare(TransactionContext* ctxt) throw() { try{ - //dequeue messages from their respective queues: - for (ack_iterator i = unacked.begin(); i != unacked.end(); i++) { - if (i->coveredBy(&acked)) { - i->dequeue(ctxt); - } - } + ops.prepare(ctxt); return true; }catch(const std::exception& e){ QPID_LOG(error, "Failed to prepare: " << e.what()); @@ -51,11 +88,13 @@ bool TxAccept::prepare(TransactionContext* ctxt) throw() void TxAccept::commit() throw() { - for (ack_iterator i = unacked.begin(); i != unacked.end(); i++) { - if (i->coveredBy(&acked)) i->setEnded(); + try { + ops.commit(); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to commit: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to commit (unknown error)"); } - - unacked.remove_if(mem_fun_ref(&DeliveryRecord::isRedundant)); } void TxAccept::rollback() throw() {} diff --git a/cpp/src/qpid/broker/TxAccept.h b/cpp/src/qpid/broker/TxAccept.h index 9548c50c2a..314a150176 100644 --- a/cpp/src/qpid/broker/TxAccept.h +++ b/cpp/src/qpid/broker/TxAccept.h @@ -25,8 +25,8 @@ #include <functional> #include <list> #include "qpid/framing/SequenceSet.h" -#include "DeliveryRecord.h" -#include "TxOp.h" +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/TxOp.h" namespace qpid { namespace broker { @@ -34,9 +34,31 @@ namespace qpid { * Defines the transactional behaviour for accepts received by * a transactional channel. */ - class TxAccept : public TxOp{ - framing::SequenceSet& acked; - std::list<DeliveryRecord>& unacked; + class TxAccept : public TxOp { + struct RangeOp + { + AckRange range; + + RangeOp(const AckRange& r); + void prepare(TransactionContext* ctxt); + void commit(); + }; + + struct RangeOps + { + std::vector<RangeOp> ranges; + DeliveryRecords& unacked; + + RangeOps(DeliveryRecords& u); + + void operator()(framing::SequenceNumber start, framing::SequenceNumber end); + void prepare(TransactionContext* ctxt); + void commit(); + }; + + framing::SequenceSet acked; + DeliveryRecords& unacked; + RangeOps ops; public: /** @@ -44,11 +66,15 @@ namespace qpid { * acks received * @param unacked the record of delivered messages */ - TxAccept(framing::SequenceSet& acked, std::list<DeliveryRecord>& unacked); + TxAccept(const framing::SequenceSet& acked, DeliveryRecords& unacked); virtual bool prepare(TransactionContext* ctxt) throw(); virtual void commit() throw(); virtual void rollback() throw(); virtual ~TxAccept(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + + // Used by cluster replication. + const framing::SequenceSet& getAcked() const { return acked; } }; } } diff --git a/cpp/src/qpid/broker/TxBuffer.cpp b/cpp/src/qpid/broker/TxBuffer.cpp index 8fe2c17bf0..b509778e89 100644 --- a/cpp/src/qpid/broker/TxBuffer.cpp +++ b/cpp/src/qpid/broker/TxBuffer.cpp @@ -18,10 +18,11 @@ * under the License. * */ -#include "TxBuffer.h" +#include "qpid/broker/TxBuffer.h" #include "qpid/log/Statement.h" #include <boost/mem_fn.hpp> +#include <boost/bind.hpp> using boost::mem_fn; using namespace qpid::broker; @@ -73,3 +74,7 @@ bool TxBuffer::commitLocal(TransactionalStore* const store) } return false; } + +void TxBuffer::accept(TxOpConstVisitor& v) const { + std::for_each(ops.begin(), ops.end(), boost::bind(&TxOp::accept, _1, boost::ref(v))); +} diff --git a/cpp/src/qpid/broker/TxBuffer.h b/cpp/src/qpid/broker/TxBuffer.h index 361c47e92c..d49c8ba16a 100644 --- a/cpp/src/qpid/broker/TxBuffer.h +++ b/cpp/src/qpid/broker/TxBuffer.h @@ -24,8 +24,9 @@ #include <algorithm> #include <functional> #include <vector> -#include "TransactionalStore.h" -#include "TxOp.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/TransactionalStore.h" +#include "qpid/broker/TxOp.h" /** * Represents a single transaction. As such, an instance of this class @@ -68,7 +69,7 @@ namespace qpid { /** * Adds an operation to the transaction. */ - void enlist(TxOp::shared_ptr op); + QPID_BROKER_EXTERN void enlist(TxOp::shared_ptr op); /** * Requests that all ops are prepared. This should @@ -81,7 +82,7 @@ namespace qpid { * @returns true if all the operations prepared * successfully, false if not. */ - bool prepare(TransactionContext* const ctxt); + QPID_BROKER_EXTERN bool prepare(TransactionContext* const ctxt); /** * Signals that the ops all prepared successfully and can @@ -91,7 +92,7 @@ namespace qpid { * Should only be called after a call to prepare() returns * true. */ - void commit(); + QPID_BROKER_EXTERN void commit(); /** * Signals that all ops can be rolled back. @@ -100,13 +101,16 @@ namespace qpid { * returns true (2pc) or instead of a prepare call * ('server-local') */ - void rollback(); + QPID_BROKER_EXTERN void rollback(); /** * Helper method for managing the process of server local * commit */ - bool commitLocal(TransactionalStore* const store); + QPID_BROKER_EXTERN bool commitLocal(TransactionalStore* const store); + + // Used by cluster to replicate transaction status. + void accept(TxOpConstVisitor& v) const; }; } } diff --git a/cpp/src/qpid/broker/TxOp.h b/cpp/src/qpid/broker/TxOp.h index e687c437cc..a8fa1c2621 100644 --- a/cpp/src/qpid/broker/TxOp.h +++ b/cpp/src/qpid/broker/TxOp.h @@ -21,11 +21,13 @@ #ifndef _TxOp_ #define _TxOp_ -#include "TransactionalStore.h" +#include "qpid/broker/TxOpVisitor.h" +#include "qpid/broker/TransactionalStore.h" #include <boost/shared_ptr.hpp> namespace qpid { namespace broker { + class TxOp{ public: typedef boost::shared_ptr<TxOp> shared_ptr; @@ -34,9 +36,11 @@ namespace qpid { virtual void commit() throw() = 0; virtual void rollback() throw() = 0; virtual ~TxOp(){} + + virtual void accept(TxOpConstVisitor&) const = 0; }; - } -} + +}} // namespace qpid::broker #endif diff --git a/cpp/src/qpid/broker/TxOpVisitor.h b/cpp/src/qpid/broker/TxOpVisitor.h new file mode 100644 index 0000000000..ceb894896e --- /dev/null +++ b/cpp/src/qpid/broker/TxOpVisitor.h @@ -0,0 +1,97 @@ +#ifndef QPID_BROKER_TXOPVISITOR_H +#define QPID_BROKER_TXOPVISITOR_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 broker { + +class DtxAck; +class RecoveredDequeue; +class RecoveredEnqueue; +class TxAccept; +class TxPublish; + +/** + * Visitor for TxOp familly of classes. + */ +struct TxOpConstVisitor +{ + virtual ~TxOpConstVisitor() {} + virtual void operator()(const DtxAck&) = 0; + virtual void operator()(const RecoveredDequeue&) = 0; + virtual void operator()(const RecoveredEnqueue&) = 0; + virtual void operator()(const TxAccept&) = 0; + virtual void operator()(const TxPublish&) = 0; +}; + +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_TXOPVISITOR_H*/ +#ifndef QPID_BROKER_TXOPVISITOR_H +#define QPID_BROKER_TXOPVISITOR_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 broker { + +class DtxAck; +class RecoveredDequeue; +class RecoveredEnqueue; +class TxAccept; +class TxPublish; + +/** + * Visitor for TxOp familly of classes. + */ +struct TxOpConstVisitor +{ + virtual ~TxOpConstVisitor() {} + virtual void operator()(const DtxAck&) = 0; + virtual void operator()(const RecoveredDequeue&) = 0; + virtual void operator()(const RecoveredEnqueue&) = 0; + virtual void operator()(const TxAccept&) = 0; + virtual void operator()(const TxPublish&) = 0; +}; + +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_TXOPVISITOR_H*/ diff --git a/cpp/src/qpid/broker/TxPublish.cpp b/cpp/src/qpid/broker/TxPublish.cpp index dcee00e803..4b083033ea 100644 --- a/cpp/src/qpid/broker/TxPublish.cpp +++ b/cpp/src/qpid/broker/TxPublish.cpp @@ -19,16 +19,21 @@ * */ #include "qpid/log/Statement.h" -#include "TxPublish.h" +#include "qpid/broker/TxPublish.h" using boost::intrusive_ptr; using namespace qpid::broker; TxPublish::TxPublish(intrusive_ptr<Message> _msg) : msg(_msg) {} -bool TxPublish::prepare(TransactionContext* ctxt) throw(){ +bool TxPublish::prepare(TransactionContext* ctxt) throw() +{ try{ - for_each(queues.begin(), queues.end(), Prepare(ctxt, msg)); + while (!queues.empty()) { + prepare(ctxt, queues.front()); + prepared.push_back(queues.front()); + queues.pop_front(); + } return true; }catch(const std::exception& e){ QPID_LOG(error, "Failed to prepare: " << e.what()); @@ -38,14 +43,33 @@ bool TxPublish::prepare(TransactionContext* ctxt) throw(){ return false; } -void TxPublish::commit() throw(){ - for_each(queues.begin(), queues.end(), Commit(msg)); +void TxPublish::commit() throw() +{ + try { + for_each(prepared.begin(), prepared.end(), Commit(msg)); + if (msg->checkContentReleasable()) { + msg->releaseContent(); + } + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to commit: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to commit (unknown error)"); + } } -void TxPublish::rollback() throw(){ +void TxPublish::rollback() throw() +{ + try { + for_each(prepared.begin(), prepared.end(), Rollback(msg)); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to complete rollback: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to complete rollback (unknown error)"); + } + } -void TxPublish::deliverTo(Queue::shared_ptr& queue){ +void TxPublish::deliverTo(const boost::shared_ptr<Queue>& queue){ if (!queue->isLocal(msg)) { queues.push_back(queue); delivered = true; @@ -54,26 +78,30 @@ void TxPublish::deliverTo(Queue::shared_ptr& queue){ } } -TxPublish::Prepare::Prepare(TransactionContext* _ctxt, intrusive_ptr<Message>& _msg) - : ctxt(_ctxt), msg(_msg){} - -void TxPublish::Prepare::operator()(Queue::shared_ptr& queue){ +void TxPublish::prepare(TransactionContext* ctxt, const boost::shared_ptr<Queue> queue) +{ if (!queue->enqueue(ctxt, msg)){ /** - * if not store then mark message for ack and deleivery once - * commit happens, as async IO will never set it when no store - * exists - */ + * if not store then mark message for ack and deleivery once + * commit happens, as async IO will never set it when no store + * exists + */ msg->enqueueComplete(); } } TxPublish::Commit::Commit(intrusive_ptr<Message>& _msg) : msg(_msg){} -void TxPublish::Commit::operator()(Queue::shared_ptr& queue){ +void TxPublish::Commit::operator()(const boost::shared_ptr<Queue>& queue){ queue->process(msg); } +TxPublish::Rollback::Rollback(intrusive_ptr<Message>& _msg) : msg(_msg){} + +void TxPublish::Rollback::operator()(const boost::shared_ptr<Queue>& queue){ + queue->enqueueAborted(msg); +} + uint64_t TxPublish::contentSize () { return msg->contentSize (); diff --git a/cpp/src/qpid/broker/TxPublish.h b/cpp/src/qpid/broker/TxPublish.h index d2590debfb..b6ab9767ab 100644 --- a/cpp/src/qpid/broker/TxPublish.h +++ b/cpp/src/qpid/broker/TxPublish.h @@ -21,11 +21,12 @@ #ifndef _TxPublish_ #define _TxPublish_ -#include "Queue.h" -#include "Deliverable.h" -#include "Message.h" -#include "MessageStore.h" -#include "TxOp.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/TxOp.h" #include <algorithm> #include <functional> @@ -46,37 +47,43 @@ namespace qpid { * dispatch or to be added to the in-memory queue. */ class TxPublish : public TxOp, public Deliverable{ - class Prepare{ - TransactionContext* ctxt; - boost::intrusive_ptr<Message>& msg; - public: - Prepare(TransactionContext* ctxt, boost::intrusive_ptr<Message>& msg); - void operator()(Queue::shared_ptr& queue); - }; class Commit{ boost::intrusive_ptr<Message>& msg; public: Commit(boost::intrusive_ptr<Message>& msg); - void operator()(Queue::shared_ptr& queue); + void operator()(const boost::shared_ptr<Queue>& queue); + }; + class Rollback{ + boost::intrusive_ptr<Message>& msg; + public: + Rollback(boost::intrusive_ptr<Message>& msg); + void operator()(const boost::shared_ptr<Queue>& queue); }; boost::intrusive_ptr<Message> msg; std::list<Queue::shared_ptr> queues; + std::list<Queue::shared_ptr> prepared; + + void prepare(TransactionContext* ctxt, boost::shared_ptr<Queue>); public: - TxPublish(boost::intrusive_ptr<Message> msg); - virtual bool prepare(TransactionContext* ctxt) throw(); - virtual void commit() throw(); - virtual void rollback() throw(); + QPID_BROKER_EXTERN TxPublish(boost::intrusive_ptr<Message> msg); + QPID_BROKER_EXTERN virtual bool prepare(TransactionContext* ctxt) throw(); + QPID_BROKER_EXTERN virtual void commit() throw(); + QPID_BROKER_EXTERN virtual void rollback() throw(); virtual Message& getMessage() { return *msg; }; - virtual void deliverTo(Queue::shared_ptr& queue); + QPID_BROKER_EXTERN virtual void deliverTo(const boost::shared_ptr<Queue>& queue); virtual ~TxPublish(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + + QPID_BROKER_EXTERN uint64_t contentSize(); - uint64_t contentSize(); + boost::intrusive_ptr<Message> getMessage() const { return msg; } + const std::list<Queue::shared_ptr> getQueues() const { return queues; } }; } } diff --git a/cpp/src/qpid/broker/Vhost.cpp b/cpp/src/qpid/broker/Vhost.cpp index 23203ec13e..df37cba255 100644 --- a/cpp/src/qpid/broker/Vhost.cpp +++ b/cpp/src/qpid/broker/Vhost.cpp @@ -17,23 +17,33 @@ // under the License. // -#include "Vhost.h" -#include "qpid/agent/ManagementAgent.h" +#include "qpid/broker/Vhost.h" +#include "qpid/broker/Broker.h" +#include "qpid/management/ManagementAgent.h" using namespace qpid::broker; using qpid::management::ManagementAgent; +namespace _qmf = qmf::org::apache::qpid::broker; -Vhost::Vhost (management::Manageable* parentBroker) : mgmtObject(0) +namespace qpid { namespace management { +class Manageable; +}} + +Vhost::Vhost (qpid::management::Manageable* parentBroker, Broker* broker) : mgmtObject(0) { - if (parentBroker != 0) + if (parentBroker != 0 && broker != 0) { - ManagementAgent* agent = ManagementAgent::Singleton::getInstance(); + ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - mgmtObject = new management::Vhost (agent, this, parentBroker, "/"); - agent->addObject (mgmtObject, 3, 1); + mgmtObject = new _qmf::Vhost(agent, this, parentBroker, "/"); + agent->addObject (mgmtObject, 0x1000000000000003LL); } } } +void Vhost::setFederationTag(const std::string& tag) +{ + mgmtObject->set_federationTag(tag); +} diff --git a/cpp/src/qpid/broker/Vhost.h b/cpp/src/qpid/broker/Vhost.h index e56cc61272..9554d641c2 100644 --- a/cpp/src/qpid/broker/Vhost.h +++ b/cpp/src/qpid/broker/Vhost.h @@ -21,29 +21,28 @@ // #include "qpid/management/Manageable.h" -#include "qpid/management/Vhost.h" +#include "qmf/org/apache/qpid/broker/Vhost.h" #include <boost/shared_ptr.hpp> namespace qpid { namespace broker { +class Broker; class Vhost : public management::Manageable { private: - management::Vhost* mgmtObject; + qmf::org::apache::qpid::broker::Vhost* mgmtObject; public: typedef boost::shared_ptr<Vhost> shared_ptr; - Vhost (management::Manageable* parentBroker); + Vhost (management::Manageable* parentBroker, Broker* broker = 0); management::ManagementObject* GetManagementObject (void) const { return mgmtObject; } - - management::Manageable::status_t ManagementMethod (uint32_t, management::Args&) - { return management::Manageable::STATUS_OK; } + void setFederationTag(const std::string& tag); }; }} diff --git a/cpp/src/qpid/broker/XmlExchange.cpp b/cpp/src/qpid/broker/XmlExchange.cpp deleted file mode 100644 index cb0f9a9606..0000000000 --- a/cpp/src/qpid/broker/XmlExchange.cpp +++ /dev/null @@ -1,277 +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 "config.h" -#include "XmlExchange.h" - -#include "DeliverableMessage.h" - -#include "qpid/log/Statement.h" -#include "qpid/framing/FieldTable.h" -#include "qpid/framing/FieldValue.h" -#include "qpid/framing/reply_exceptions.h" - -#include <xercesc/framework/MemBufInputSource.hpp> - -#include <xqilla/context/ItemFactory.hpp> -#include <xqilla/xqilla-simple.hpp> - -#include <iostream> -#include <sstream> - -using namespace qpid::framing; -using namespace qpid::sys; -using qpid::management::Manageable; - -namespace qpid { -namespace broker { - -XmlExchange::XmlExchange(const string& _name, Manageable* _parent) : Exchange(_name, _parent) -{ - if (mgmtExchange != 0) - mgmtExchange->set_type (typeName); -} - -XmlExchange::XmlExchange(const std::string& _name, bool _durable, - const FieldTable& _args, Manageable* _parent) : - Exchange(_name, _durable, _args, _parent) -{ - if (mgmtExchange != 0) - mgmtExchange->set_type (typeName); -} - -/* - * Use the name of the query as the binding key. - * - * The first time a given name is used in a binding, the query body - * must be provided.After that, no query body should be present. - * - * To modify an installed query, the user must first unbind the - * existing query, then replace it by binding again with the same - * name. - * - */ - - // #### TODO: The Binding should take the query text - // #### only. Consider encapsulating the entire block, including - // #### the if condition. - - -bool XmlExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* bindingArguments) -{ - string queryText = bindingArguments->getString("xquery"); - - try { - RWlock::ScopedWlock l(lock); - XmlBinding::vector& bindings(bindingsMap[routingKey]); - XmlBinding::vector::iterator i; - - for (i = bindings.begin(); i != bindings.end(); i++) - if ((*i)->queue == queue) - break; - - if (i == bindings.end()) { - - Query query(xqilla.parse(X(queryText.c_str()))); - XmlBinding::shared_ptr binding(new XmlBinding (routingKey, queue, this, query)); - XmlBinding::vector bindings(1, binding); - bindingsMap[routingKey] = bindings; - QPID_LOG(trace, "Bound successfully with query: " << queryText ); - - if (mgmtExchange != 0) { - mgmtExchange->inc_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->inc_bindingCount(); - } - return true; - } else{ - return false; - } - } - catch (XQException& e) { - throw InternalErrorException(QPID_MSG("Could not parse xquery:"+ queryText)); - } - catch (...) { - throw InternalErrorException(QPID_MSG("Unexpected error - Could not parse xquery:"+ queryText)); - } -} - -bool XmlExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* /*args*/) -{ - RWlock::ScopedWlock l(lock); - XmlBinding::vector& bindings(bindingsMap[routingKey]); - XmlBinding::vector::iterator i; - - for (i = bindings.begin(); i != bindings.end(); i++) - if ((*i)->queue == queue) - break; - - if (i < bindings.end()) { - bindings.erase(i); - if (bindings.empty()) { - bindingsMap.erase(routingKey); - } - if (mgmtExchange != 0) { - mgmtExchange->dec_bindingCount(); - ((management::Queue*) queue->GetManagementObject())->dec_bindingCount(); - } - return true; - } else { - return false; - } -} - -bool XmlExchange::matches(Query& query, Deliverable& msg, const qpid::framing::FieldTable* args) -{ - // ### TODO: Need istream for frameset - // Hack alert - the following code does not work for really large messages - - string msgContent; - - try { - msg.getMessage().getFrames().getContent(msgContent); - - QPID_LOG(trace, "matches: query is [" << UTF8(query->getQueryText()) << "]"); - QPID_LOG(trace, "matches: message content is [" << msgContent << "]"); - - boost::scoped_ptr<DynamicContext> context(query->createDynamicContext()); - if (!context.get()) { - throw InternalErrorException(QPID_MSG("Query context looks munged ...")); - } - - XERCES_CPP_NAMESPACE::MemBufInputSource xml((XMLByte*) msgContent.c_str(), msgContent.length(), "input" ); - Sequence seq(context->parseDocument(xml)); - - if (args) { - FieldTable::ValueMap::const_iterator v = args->begin(); - for(; v != args->end(); ++v) { - // ### TODO: Do types properly - if (v->second->convertsTo<std::string>()) { - QPID_LOG(trace, "XmlExchange, external variable: " << v->first << " = " << v->second->getData().getString().c_str()); - Item::Ptr value = context->getItemFactory()->createString(X(v->second->getData().getString().c_str()), context.get()); - context->setExternalVariable(X(v->first.c_str()), value); - } - } - } - - if(!seq.isEmpty() && seq.first()->isNode()) { - context->setContextItem(seq.first()); - context->setContextPosition(1); - context->setContextSize(1); - } - Result result = query->execute(context.get()); - return result->getEffectiveBooleanValue(context.get(), 0); - } - catch (XQException& e) { - QPID_LOG(warning, "Could not parse XML content (or message headers):" << msgContent); - return 0; - } - catch (...) { - QPID_LOG(warning, "Unexpected error routing message: " << msgContent); - return 0; - } - return 0; -} - -void XmlExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* args) -{ - try { - RWlock::ScopedRlock l(lock); - XmlBinding::vector& bindings(bindingsMap[routingKey]); - XmlBinding::vector::iterator i; - int count(0); - - for (i = bindings.begin(); i != bindings.end(); i++) { - - if ((*i)->xquery && matches((*i)->xquery, msg, args)) { // Overly defensive? There should always be a query ... - msg.deliverTo((*i)->queue); - count++; - QPID_LOG(trace, "Delivered to queue" ); - - if ((*i)->mgmtBinding != 0) - (*i)->mgmtBinding->inc_msgMatched (); - } - - if(!count){ - QPID_LOG(warning, "XMLExchange " << getName() << ": could not route message with query " << routingKey); - if (mgmtExchange != 0) { - mgmtExchange->inc_msgDrops (); - mgmtExchange->inc_byteDrops (msg.contentSize ()); - } - } - else { - if (mgmtExchange != 0) { - mgmtExchange->inc_msgRoutes (count); - mgmtExchange->inc_byteRoutes (count * msg.contentSize ()); - } - } - - if (mgmtExchange != 0) { - mgmtExchange->inc_msgReceives (); - mgmtExchange->inc_byteReceives (msg.contentSize ()); - } - } - } - catch (...) { - QPID_LOG(warning, "XMLExchange " << getName() << ": exception routing message with query " << routingKey); - } - - -} - - -bool XmlExchange::isBound(Queue::shared_ptr queue, const string* const routingKey, const FieldTable* const) -{ - XmlBinding::vector::iterator j; - - if (routingKey) { - XmlBindingsMap::iterator i = bindingsMap.find(*routingKey); - - if (i == bindingsMap.end()) - return false; - if (!queue) - return true; - for (j = i->second.begin(); j != i->second.end(); j++) - if ((*j)->queue == queue) - return true; - } else if (!queue) { - //if no queue or routing key is specified, just report whether any bindings exist - return bindingsMap.size() > 0; - } else { - for (XmlBindingsMap::iterator i = bindingsMap.begin(); i != bindingsMap.end(); i++) - for (j = i->second.begin(); j != i->second.end(); j++) - if ((*j)->queue == queue) - return true; - return false; - } - - return false; -} - - -XmlExchange::~XmlExchange() -{ - bindingsMap.clear(); -} - -const std::string XmlExchange::typeName("xml"); - -} -} diff --git a/cpp/src/qpid/broker/Prefetch.h b/cpp/src/qpid/broker/posix/BrokerDefaults.cpp index 8eb27a3e21..9e463fa32d 100644 --- a/cpp/src/qpid/broker/Prefetch.h +++ b/cpp/src/qpid/broker/posix/BrokerDefaults.cpp @@ -18,25 +18,23 @@ * under the License. * */ -#ifndef _Prefetch_ -#define _Prefetch_ -#include "qpid/framing/amqp_types.h" +#include "qpid/broker/Broker.h" +#include <stdlib.h> namespace qpid { - namespace broker { - /** - * Count and total size of asynchronously delivered - * (i.e. pushed) messages that have acks outstanding. - */ - struct Prefetch{ - uint32_t size; - uint16_t count; +namespace broker { - void reset() { size = 0; count = 0; } - }; - } -} +const std::string Broker::Options::DEFAULT_DATA_DIR_LOCATION("/tmp"); +const std::string Broker::Options::DEFAULT_DATA_DIR_NAME("/.qpidd"); +std::string +Broker::Options::getHome() { + std::string home; + char *home_c = ::getenv("HOME"); + if (home_c != 0) + home += home_c; + return home; +} -#endif +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/DeliveryToken.h b/cpp/src/qpid/broker/windows/BrokerDefaults.cpp index 8bdf5e6359..b6862f0418 100644 --- a/cpp/src/qpid/broker/DeliveryToken.h +++ b/cpp/src/qpid/broker/windows/BrokerDefaults.cpp @@ -18,28 +18,24 @@ * under the License. * */ -#ifndef _DeliveryToken_ -#define _DeliveryToken_ -#include <boost/shared_ptr.hpp> +#include "qpid/broker/Broker.h" +#include <stdlib.h> namespace qpid { namespace broker { - /** - * A DeliveryToken allows the delivery of a message to be - * associated with whatever mechanism caused it to be - * delivered. (i.e. its a form of Memento). - */ - class DeliveryToken - { - public: - typedef boost::shared_ptr<DeliveryToken> shared_ptr; +const std::string Broker::Options::DEFAULT_DATA_DIR_LOCATION("\\TEMP"); +const std::string Broker::Options::DEFAULT_DATA_DIR_NAME("\\QPIDD.DATA"); - virtual ~DeliveryToken(){} - }; +std::string +Broker::Options::getHome() { + std::string home; + char home_c[MAX_PATH+1]; + size_t unused; + if (0 == getenv_s (&unused, home_c, sizeof(home_c), "HOME")) + home += home_c; + return home; +} -}} - - -#endif +}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp new file mode 100644 index 0000000000..212d7c4db4 --- /dev/null +++ b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp @@ -0,0 +1,190 @@ +/* + * + * 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. + * + */ + +// This source is only used on Windows; SSPI is the Windows mechanism for +// accessing authentication mechanisms, analogous to Cyrus SASL. + +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/reply_exceptions.h" + +#include <windows.h> + +using namespace qpid::framing; +using qpid::sys::SecurityLayer; + +namespace qpid { +namespace broker { + +class NullAuthenticator : public SaslAuthenticator +{ + Connection& connection; + framing::AMQP_ClientProxy::Connection client; +public: + NullAuthenticator(Connection& connection); + ~NullAuthenticator(); + void getMechanisms(framing::Array& mechanisms); + void start(const std::string& mechanism, const std::string& response); + void step(const std::string&) {} + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); +}; + +class SspiAuthenticator : public SaslAuthenticator +{ + HANDLE userToken; + Connection& connection; + framing::AMQP_ClientProxy::Connection client; + +public: + SspiAuthenticator(Connection& connection); + ~SspiAuthenticator(); + void getMechanisms(framing::Array& mechanisms); + void start(const std::string& mechanism, const std::string& response); + void step(const std::string& response); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); +}; + +bool SaslAuthenticator::available(void) +{ + return true; +} + +// Initialize the SASL mechanism; throw if it fails. +void SaslAuthenticator::init(const std::string& /*saslName*/) +{ + return; +} + +void SaslAuthenticator::fini(void) +{ + return; +} + +std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c) +{ + if (c.getBroker().getOptions().auth) { + return std::auto_ptr<SaslAuthenticator>(new SspiAuthenticator(c)); + } else { + return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c)); + } +} + +NullAuthenticator::NullAuthenticator(Connection& c) : connection(c), client(c.getOutput()) {} +NullAuthenticator::~NullAuthenticator() {} + +void NullAuthenticator::getMechanisms(Array& mechanisms) +{ + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("ANONYMOUS"))); +} + +void NullAuthenticator::start(const string& mechanism, const string& response) +{ + QPID_LOG(warning, "SASL: No Authentication Performed"); + if (mechanism == "PLAIN") { // Old behavior + if (response.size() > 0 && response[0] == (char) 0) { + string temp = response.substr(1); + string::size_type i = temp.find((char)0); + string uid = temp.substr(0, i); + string pwd = temp.substr(i + 1); + connection.setUserId(uid); + } + } else { + connection.setUserId("anonymous"); + } + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); +} + +std::auto_ptr<SecurityLayer> NullAuthenticator::getSecurityLayer(uint16_t) +{ + std::auto_ptr<SecurityLayer> securityLayer; + return securityLayer; +} + + +SspiAuthenticator::SspiAuthenticator(Connection& c) : userToken(INVALID_HANDLE_VALUE), connection(c), client(c.getOutput()) +{ +} + +SspiAuthenticator::~SspiAuthenticator() +{ + if (INVALID_HANDLE_VALUE != userToken) { + CloseHandle(userToken); + userToken = INVALID_HANDLE_VALUE; + } +} + +void SspiAuthenticator::getMechanisms(Array& mechanisms) +{ + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value(string("ANONYMOUS")))); + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value(string("PLAIN")))); + QPID_LOG(info, "SASL: Mechanism list: ANONYMOUS PLAIN"); +} + +void SspiAuthenticator::start(const string& mechanism, const string& response) +{ + QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); + if (mechanism == "ANONYMOUS") { + connection.setUserId("anonymous"); + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); + return; + } + if (mechanism != "PLAIN") + throw ConnectionForcedException("Unsupported mechanism"); + + // PLAIN's response is composed of 3 strings separated by 0 bytes: + // authorization id, authentication id (user), clear-text password. + if (response.size() == 0) + throw ConnectionForcedException("Authentication failed"); + + string::size_type i = response.find((char)0); + string auth = response.substr(0, i); + string::size_type j = response.find((char)0, i+1); + string uid = response.substr(i+1, j-1); + string pwd = response.substr(j+1); + int error = 0; + if (!LogonUser(uid.c_str(), ".", pwd.c_str(), + LOGON32_LOGON_NETWORK, + LOGON32_PROVIDER_DEFAULT, + &userToken)) + error = GetLastError(); + pwd.replace(0, string::npos, 1, (char)0); + if (error != 0) { + QPID_LOG(info, + "SASL: Auth failed [" << error << "]: " << qpid::sys::strError(error)); + throw ConnectionForcedException("Authentication failed"); + } + + connection.setUserId(uid); + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); +} + +void SspiAuthenticator::step(const string& response) +{ + QPID_LOG(info, "SASL: Need another step!!!"); +} + +std::auto_ptr<SecurityLayer> SspiAuthenticator::getSecurityLayer(uint16_t) +{ + std::auto_ptr<SecurityLayer> securityLayer; + return securityLayer; +} + +}} diff --git a/cpp/src/qpid/client/Bounds.cpp b/cpp/src/qpid/client/Bounds.cpp index aac18022bc..abb983a62e 100644 --- a/cpp/src/qpid/client/Bounds.cpp +++ b/cpp/src/qpid/client/Bounds.cpp @@ -1,4 +1,24 @@ -#include "Bounds.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/Bounds.h" #include "qpid/log/Statement.h" #include "qpid/sys/Waitable.h" @@ -28,8 +48,7 @@ void Bounds::reduce(size_t size) { if (current == 0) return; current -= std::min(size, current); if (current < max && lock.hasWaiters()) { - assert(lock.hasWaiters() == 1); - lock.notify(); + lock.notifyAll(); } } diff --git a/cpp/src/qpid/client/AckPolicy.cpp b/cpp/src/qpid/client/Completion.cpp index 7956ebad0f..a97c8c3534 100644 --- a/cpp/src/qpid/client/AckPolicy.cpp +++ b/cpp/src/qpid/client/Completion.cpp @@ -18,33 +18,23 @@ * under the License. * */ -#include "AckPolicy.h" + +#include "qpid/client/Completion.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/PrivateImplRef.h" namespace qpid { namespace client { -AckPolicy::AckPolicy(size_t n) : interval(n), count(n) {} +typedef PrivateImplRef<Completion> PI; +Completion::Completion(CompletionImpl* p) { PI::ctor(*this, p); } +Completion::Completion(const Completion& c) : Handle<CompletionImpl>() { PI::copy(*this, c); } +Completion::~Completion() { PI::dtor(*this); } +Completion& Completion::operator=(const Completion& c) { return PI::assign(*this, c); } -void AckPolicy::ack(const Message& msg, AsyncSession session) -{ - accepted.add(msg.getId()); - if (interval && --count==0) { - session.markCompleted(msg.getId(), false, true); - session.messageAccept(accepted); - accepted.clear(); - count = interval; - } else { - session.markCompleted(msg.getId(), false, false); - } -} -void AckPolicy::ackOutstanding(AsyncSession session) -{ - if (!accepted.empty()) { - session.messageAccept(accepted); - accepted.clear(); - session.sendCompletion(); - } -} +void Completion::wait() { impl->wait(); } +bool Completion::isComplete() { return impl->isComplete(); } +std::string Completion::getResult() { return impl->getResult(); } }} // namespace qpid::client diff --git a/cpp/src/qpid/client/Completion.h b/cpp/src/qpid/client/Completion.h deleted file mode 100644 index c4979d7934..0000000000 --- a/cpp/src/qpid/client/Completion.h +++ /dev/null @@ -1,71 +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. - * - */ - -#ifndef _Completion_ -#define _Completion_ - -#include <boost/shared_ptr.hpp> -#include "Future.h" -#include "SessionImpl.h" - -namespace qpid { -namespace client { - -/** - * Asynchronous commands that do not return a result will return a - * Completion. You can use the completion to wait for that specific - * command to complete. - * - *@see TypedResult - * - *\ingroup clientapi - */ -class Completion -{ -protected: - Future future; - shared_ptr<SessionImpl> session; - -public: - ///@internal - Completion() {} - - ///@internal - Completion(Future f, shared_ptr<SessionImpl> s) : future(f), session(s) {} - - /** Wait for the asynchronous command that returned this - *Completion to complete. - * - *@exception If the command returns an error, get() throws an exception. - */ - void wait() - { - future.wait(*session); - } - - bool isComplete() { - return future.isComplete(*session); - } -}; - -}} - -#endif diff --git a/cpp/src/qpid/client/CompletionImpl.h b/cpp/src/qpid/client/CompletionImpl.h new file mode 100644 index 0000000000..f180708316 --- /dev/null +++ b/cpp/src/qpid/client/CompletionImpl.h @@ -0,0 +1,51 @@ +#ifndef QPID_CLIENT_COMPLETIONIMPL_H +#define QPID_CLIENT_COMPLETIONIMPL_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/RefCounted.h" +#include "qpid/client/Future.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace client { + +///@internal +class CompletionImpl : public RefCounted +{ +public: + CompletionImpl() {} + CompletionImpl(Future f, boost::shared_ptr<SessionImpl> s) : future(f), session(s) {} + + bool isComplete() { return future.isComplete(*session); } + void wait() { future.wait(*session); } + std::string getResult() { return future.getResult(*session); } + +protected: + Future future; + boost::shared_ptr<SessionImpl> session; +}; + +}} // namespace qpid::client + + +#endif /*!QPID_CLIENT_COMPLETIONIMPL_H*/ diff --git a/cpp/src/qpid/client/Connection.cpp b/cpp/src/qpid/client/Connection.cpp index 6572794516..32a01b2c40 100644 --- a/cpp/src/qpid/client/Connection.cpp +++ b/cpp/src/qpid/client/Connection.cpp @@ -18,15 +18,16 @@ * under the License. * */ -#include "Connection.h" -#include "ConnectionSettings.h" -#include "Message.h" -#include "SessionImpl.h" -#include "SessionBase_0_10Access.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/Message.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/Url.h" #include "qpid/log/Logger.h" #include "qpid/log/Options.h" #include "qpid/log/Statement.h" -#include "qpid/shared_ptr.h" #include "qpid/framing/AMQP_HighestVersion.h" #include <algorithm> @@ -35,6 +36,7 @@ #include <functional> #include <boost/format.hpp> #include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> using namespace qpid::framing; using namespace qpid::sys; @@ -43,9 +45,45 @@ using namespace qpid::sys; namespace qpid { namespace client { -Connection::Connection() : channelIdCounter(0), version(framing::highestProtocolVersion) {} +Connection::Connection() : version(framing::highestProtocolVersion) {} -Connection::~Connection(){ } +Connection::~Connection() {} + +void Connection::open( + const Url& url, + const std::string& uid, const std::string& pwd, + const std::string& vhost, + uint16_t maxFrameSize) +{ + ConnectionSettings settings; + settings.username = uid; + settings.password = pwd; + settings.virtualhost = vhost; + settings.maxFrameSize = maxFrameSize; + open(url, settings); +} + +void Connection::open(const Url& url, const ConnectionSettings& settings) { + if (url.empty()) + throw Exception(QPID_MSG("Attempt to open URL with no addresses.")); + Url::const_iterator i = url.begin(); + do { + const TcpAddress* tcp = i->get<TcpAddress>(); + i++; + if (tcp) { + try { + ConnectionSettings cs(settings); + cs.host = tcp->host; + cs.port = tcp->port; + open(cs); + break; + } + catch (const Exception& /*e*/) { + if (i == url.end()) throw; + } + } + } while (i != url.end()); +} void Connection::open( const std::string& host, int port, @@ -67,39 +105,55 @@ bool Connection::isOpen() const { return impl && impl->isOpen(); } +void +Connection::registerFailureCallback ( boost::function<void ()> fn ) { + failureCallback = fn; + if ( impl ) + impl->registerFailureCallback ( fn ); +} + + + void Connection::open(const ConnectionSettings& settings) { if (isOpen()) throw Exception(QPID_MSG("Connection::open() was already called")); - impl = shared_ptr<ConnectionImpl>(new ConnectionImpl(version, settings)); - impl->open(settings.host, settings.port); - max_frame_size = impl->getNegotiatedSettings().maxFrameSize; + impl = boost::shared_ptr<ConnectionImpl>(new ConnectionImpl(version, settings)); + impl->open(); + if ( failureCallback ) + impl->registerFailureCallback ( failureCallback ); +} + +const ConnectionSettings& Connection::getNegotiatedSettings() +{ + if (!isOpen()) + throw Exception(QPID_MSG("Connection is not open.")); + return impl->getNegotiatedSettings(); } -Session Connection::newSession(const std::string& name) { +Session Connection::newSession(const std::string& name, uint32_t timeout) { if (!isOpen()) throw Exception(QPID_MSG("Connection has not yet been opened")); - shared_ptr<SessionImpl> simpl( - new SessionImpl(name, impl, ++channelIdCounter, max_frame_size)); - impl->addSession(simpl); - simpl->open(0); Session s; - SessionBase_0_10Access(s).set(simpl); + SessionBase_0_10Access(s).set(impl->newSession(name, timeout)); return s; } void Connection::resume(Session& session) { if (!isOpen()) throw Exception(QPID_MSG("Connection is not open.")); - - session.impl->setChannel(++channelIdCounter); impl->addSession(session.impl); session.impl->resume(impl); } void Connection::close() { - impl->close(); + if ( impl ) + impl->close(); +} + +std::vector<Url> Connection::getInitialBrokers() { + return impl ? impl->getInitialBrokers() : std::vector<Url>(); } }} // namespace qpid::client diff --git a/cpp/src/qpid/client/Connection.h b/cpp/src/qpid/client/Connection.h deleted file mode 100644 index ee543e20d2..0000000000 --- a/cpp/src/qpid/client/Connection.h +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef _client_Connection_ -#define _client_Connection_ - -/* - * - * 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 "qpid/client/Session.h" - -namespace qpid { -namespace client { - -class ConnectionSettings; - -/** - * Represents a connection to an AMQP broker. All communication is - * initiated by establishing a connection, then creating one or more - * Session objecst using the connection. @see newSession() - * - * \ingroup clientapi - */ -class Connection -{ - framing::ChannelId channelIdCounter; - framing::ProtocolVersion version; - uint16_t max_frame_size; - - protected: - boost::shared_ptr<ConnectionImpl> impl; - - public: - /** - * Creates a connection object, but does not open the connection. - * @see open() - */ - Connection(); - ~Connection(); - - /** - * Opens a connection to a broker. - * - * @param host the host on which the broker is running. - * - * @param port the port on the which the broker is listening. - * - * @param uid the userid to connect with. - * - * @param pwd the password to connect with (currently SASL - * PLAIN is the only authentication method supported so this - * is sent in clear text). - * - * @param virtualhost the AMQP virtual host to use (virtual - * hosts, where implemented(!), provide namespace partitioning - * within a single broker). - */ - void open(const std::string& host, int port = 5672, - const std::string& uid = "guest", - const std::string& pwd = "guest", - const std::string& virtualhost = "/", uint16_t maxFrameSize=65535); - - /** - * Opens a connection to a broker. - * - * @param the settings to use (host, port etc) @see ConnectionSettings - */ - void open(const ConnectionSettings& settings); - - /** - * Close the connection. - * - * Any further use of this connection (without reopening it) will - * not succeed. - */ - void close(); - - /** - * Create a new session on this connection. Sessions allow - * multiple streams of work to be multiplexed over the same - * connection. The returned Session provides functions to send - * commands to the broker. - * - * Session functions are synchronous. In other words, a Session - * function will send a command to the broker and does not return - * until it receives the broker's response confirming the command - * was executed. - * - * AsyncSession provides asynchronous versions of the same - * functions. These functions send a command to the broker but do - * not wait for a response. - * - * You can convert a Session s into an AsyncSession as follows: - * @code - * #include <qpid/client/AsyncSession.h> - * AsyncSession as = async(s); - * @endcode - * - * You can execute a single command asynchronously will a Session s - * like ths: - * @code - * async(s).messageTransfer(...); - * @endcode - * - * Using an AsyncSession is faster for sending large numbers of - * commands, since each command is sent as soon as possible - * without waiting for the previous command to be confirmed. - * - * However with AsyncSession you cannot assume that a command has - * completed until you explicitly synchronize. The simplest way to - * do this is to call Session::sync() or AsyncSession::sync(). - * Both of these functions wait for the broker to confirm all - * commands issued so far on the session. - * - *@param name: A name to identify the session. @see qpid::SessionId - * If the name is empty (the default) then a unique name will be - * chosen using a Universally-unique identifier (UUID) algorithm. - */ - Session newSession(const std::string& name=std::string()); - - /** - * Resume a suspended session. A session may be resumed - * on a different connection to the one that created it. - */ - void resume(Session& session); - - bool isOpen() const; -}; - -}} // namespace qpid::client - - -#endif diff --git a/cpp/src/qpid/client/FutureCompletion.h b/cpp/src/qpid/client/ConnectionAccess.h index 4248ddeab8..b662fd5d8b 100644 --- a/cpp/src/qpid/client/FutureCompletion.h +++ b/cpp/src/qpid/client/ConnectionAccess.h @@ -1,3 +1,6 @@ +#ifndef QPID_CLIENT_CONNECTIONACCESS_H +#define QPID_CLIENT_CONNECTIONACCESS_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -19,31 +22,20 @@ * */ -#ifndef _FutureCompletion_ -#define _FutureCompletion_ +#include "qpid/client/Connection.h" -#include "qpid/framing/amqp_framing.h" -#include "qpid/sys/Monitor.h" +/**@file @internal Internal use only */ namespace qpid { namespace client { -///@internal -class FutureCompletion -{ -protected: - mutable sys::Monitor lock; - bool complete; - -public: - FutureCompletion(); - virtual ~FutureCompletion(){} - bool isComplete() const; - void waitForCompletion() const; - void completed(); -}; -}} +struct ConnectionAccess { + static void setVersion(Connection& c, const framing::ProtocolVersion& v) { c.version = v; } + static boost::shared_ptr<ConnectionImpl> getImpl(Connection& c) { return c.impl; } +}; + +}} // namespace qpid::client -#endif +#endif /*!QPID_CLIENT_CONNECTIONACCESS_H*/ diff --git a/cpp/src/qpid/client/ConnectionHandler.cpp b/cpp/src/qpid/client/ConnectionHandler.cpp index 22ebec76bf..8f1cc7b03f 100644 --- a/cpp/src/qpid/client/ConnectionHandler.cpp +++ b/cpp/src/qpid/client/ConnectionHandler.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 @@ -19,17 +19,28 @@ * */ -#include "ConnectionHandler.h" +#include "qpid/client/ConnectionHandler.h" -#include "qpid/log/Statement.h" +#include "qpid/client/SaslFactory.h" #include "qpid/framing/amqp_framing.h" #include "qpid/framing/all_method_bodies.h" #include "qpid/framing/ClientInvoker.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Helpers.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/SystemInfo.h" using namespace qpid::client; using namespace qpid::framing; -using namespace boost; +using namespace qpid::framing::connection; +using qpid::sys::SecurityLayer; +using qpid::sys::Duration; +using qpid::sys::TimerTask; +using qpid::sys::Timer; +using qpid::sys::AbsTime; +using qpid::sys::TIME_SEC; +using qpid::sys::ScopedLock; +using qpid::sys::Mutex; namespace { const std::string OK("OK"); @@ -40,24 +51,54 @@ const std::string INVALID_STATE_START("start received in invalid state"); const std::string INVALID_STATE_TUNE("tune received in invalid state"); const std::string INVALID_STATE_OPEN_OK("open-ok received in invalid state"); const std::string INVALID_STATE_CLOSE_OK("close-ok received in invalid state"); + +const std::string SESSION_FLOW_CONTROL("qpid.session_flow"); +const std::string CLIENT_PROCESS_NAME("qpid.client_process"); +const std::string CLIENT_PID("qpid.client_pid"); +const std::string CLIENT_PPID("qpid.client_ppid"); +const int SESSION_FLOW_CONTROL_VER = 1; } -ConnectionHandler::ConnectionHandler(const ConnectionSettings& s, framing::ProtocolVersion& v) - : StateManager(NOT_STARTED), ConnectionSettings(s), outHandler(*this), proxy(outHandler), errorCode(200), version(v) -{ +CloseCode ConnectionHandler::convert(uint16_t replyCode) +{ + switch (replyCode) { + case 200: return CLOSE_CODE_NORMAL; + case 320: return CLOSE_CODE_CONNECTION_FORCED; + case 402: return CLOSE_CODE_INVALID_PATH; + case 501: default: + return CLOSE_CODE_FRAMING_ERROR; + } +} + +ConnectionHandler::ConnectionHandler(const ConnectionSettings& s, ProtocolVersion& v) + : StateManager(NOT_STARTED), ConnectionSettings(s), outHandler(*this), proxy(outHandler), + errorCode(CLOSE_CODE_NORMAL), version(v) +{ insist = true; ESTABLISHED.insert(FAILED); ESTABLISHED.insert(CLOSED); ESTABLISHED.insert(OPEN); -} + + FINISHED.insert(FAILED); + FINISHED.insert(CLOSED); + + properties.setInt(SESSION_FLOW_CONTROL, SESSION_FLOW_CONTROL_VER); + properties.setString(CLIENT_PROCESS_NAME, sys::SystemInfo::getProcessName()); + properties.setInt(CLIENT_PID, sys::SystemInfo::getProcessId()); + properties.setInt(CLIENT_PPID, sys::SystemInfo::getParentProcessId()); +} void ConnectionHandler::incoming(AMQFrame& frame) { if (getState() == CLOSED) { - throw Exception("Received frame on closed connection"); + throw Exception("Received frame on closed connection"); } + if (rcvTimeoutTask) { + // Received frame on connection so delay timeout + rcvTimeoutTask->restart(); + } AMQBody* body = frame.getBody(); try { @@ -67,26 +108,27 @@ void ConnectionHandler::incoming(AMQFrame& frame) in(frame); break; case CLOSING: - QPID_LOG(warning, "Ignoring frame while closing connection: " << frame); + QPID_LOG(warning, "Ignoring frame while closing connection: " << frame); break; default: throw Exception("Cannot receive frames on non-zero channel until connection is established."); } } }catch(std::exception& e){ - QPID_LOG(warning, "Closing connection due to " << e.what()); + QPID_LOG(warning, "Closing connection due to " << e.what()); setState(CLOSING); - proxy.close(501, e.what()); - if (onError) onError(501, e.what()); + errorCode = CLOSE_CODE_FRAMING_ERROR; + errorText = e.what(); + proxy.close(501, e.what()); } } void ConnectionHandler::outgoing(AMQFrame& frame) { - if (getState() == OPEN) + if (getState() == OPEN) out(frame); else - throw Exception(errorText.empty() ? "Connection is not open." : errorText); + throw TransportFailure(errorText.empty() ? "Connection is not open." : errorText); } void ConnectionHandler::waitForOpen() @@ -105,14 +147,29 @@ void ConnectionHandler::close() fail("Connection closed before it was established"); break; case OPEN: - setState(CLOSING); - proxy.close(200, OK); - waitFor(CLOSED); + if (setState(CLOSING, OPEN)) { + proxy.close(200, OK); + waitFor(FINISHED);//FINISHED = CLOSED or FAILED + } + //else, state was changed from open after we checked, can only + //change to failed or closed, so nothing to do break; - // Nothing to do for CLOSING, CLOSED, FAILED or NOT_STARTED + + // Nothing to do if already CLOSING, CLOSED, FAILED or if NOT_STARTED } } +void ConnectionHandler::heartbeat() +{ + // Do nothing - the purpose of heartbeats is just to make sure that there is some + // traffic on the connection within the heart beat interval, we check for the + // traffic and don't need to do anything in response to heartbeats + + // Although the above is still true we're now using a received heartbeat as a trigger + // to send out our own heartbeat + proxy.heartbeat(); +} + void ConnectionHandler::checkState(STATES s, const std::string& msg) { if (getState() != s) { @@ -122,45 +179,92 @@ void ConnectionHandler::checkState(STATES s, const std::string& msg) void ConnectionHandler::fail(const std::string& message) { - errorCode = 502; + errorCode = CLOSE_CODE_FRAMING_ERROR; errorText = message; QPID_LOG(warning, message); setState(FAILED); } -void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& /*mechanisms*/, const Array& /*locales*/) +namespace { +std::string SPACE(" "); +} + +void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& mechanisms, const Array& /*locales*/) { checkState(NOT_STARTED, INVALID_STATE_START); setState(NEGOTIATING); - //TODO: verify that desired mechanism and locale are supported - string response = ((char)0) + username + ((char)0) + password; - proxy.startOk(properties, mechanism, response, locale); + sasl = SaslFactory::getInstance().create(*this); + + std::string mechlist; + bool chosenMechanismSupported = mechanism.empty(); + for (Array::const_iterator i = mechanisms.begin(); i != mechanisms.end(); ++i) { + if (!mechanism.empty() && mechanism == (*i)->get<std::string>()) { + chosenMechanismSupported = true; + mechlist = (*i)->get<std::string>() + SPACE + mechlist; + } else { + if (i != mechanisms.begin()) mechlist += SPACE; + mechlist += (*i)->get<std::string>(); + } + } + + if (!chosenMechanismSupported) { + fail("Selected mechanism not supported: " + mechanism); + } + + if (sasl.get()) { + string response = sasl->start(mechanism.empty() ? mechlist : mechanism, + getSSF ? getSSF() : 0); + proxy.startOk(properties, sasl->getMechanism(), response, locale); + } else { + //TODO: verify that desired mechanism and locale are supported + string response = ((char)0) + username + ((char)0) + password; + proxy.startOk(properties, mechanism, response, locale); + } } -void ConnectionHandler::secure(const std::string& /*challenge*/) +void ConnectionHandler::secure(const std::string& challenge) { - throw NotImplementedException("Challenge-response cycle not yet implemented in client"); + if (sasl.get()) { + string response = sasl->step(challenge); + proxy.secureOk(response); + } else { + throw NotImplementedException("Challenge-response cycle not yet implemented in client"); + } } -void ConnectionHandler::tune(uint16_t maxChannelsProposed, uint16_t maxFrameSizeProposed, - uint16_t /*heartbeatMin*/, uint16_t /*heartbeatMax*/) +void ConnectionHandler::tune(uint16_t maxChannelsProposed, uint16_t maxFrameSizeProposed, + uint16_t heartbeatMin, uint16_t heartbeatMax) { checkState(NEGOTIATING, INVALID_STATE_TUNE); maxChannels = std::min(maxChannels, maxChannelsProposed); maxFrameSize = std::min(maxFrameSize, maxFrameSizeProposed); - //TODO: implement heartbeats and check desired value is in valid range + // Clip the requested heartbeat to the maximum/minimum offered + uint16_t heartbeat = ConnectionSettings::heartbeat; + heartbeat = heartbeat < heartbeatMin ? heartbeatMin : + heartbeat > heartbeatMax ? heartbeatMax : + heartbeat; + ConnectionSettings::heartbeat = heartbeat; proxy.tuneOk(maxChannels, maxFrameSize, heartbeat); setState(OPENING); proxy.open(virtualhost, capabilities, insist); } -void ConnectionHandler::openOk(const framing::Array& /*knownHosts*/) +void ConnectionHandler::openOk ( const Array& knownBrokers ) { checkState(OPENING, INVALID_STATE_OPEN_OK); - //TODO: store knownHosts for reconnection etc + knownBrokersUrls.clear(); + framing::Array::ValueVector::const_iterator i; + for ( i = knownBrokers.begin(); i != knownBrokers.end(); ++i ) + knownBrokersUrls.push_back(Url((*i)->get<std::string>())); + if (sasl.get()) { + securityLayer = sasl->getSecurityLayer(maxFrameSize); + operUserId = sasl->getUserId(); + } setState(OPEN); + QPID_LOG(debug, "Known-brokers for connection: " << log::formatList(knownBrokersUrls)); } + void ConnectionHandler::redirect(const std::string& /*host*/, const Array& /*knownHosts*/) { throw NotImplementedException("Redirection received from broker; not yet implemented in client"); @@ -169,7 +273,7 @@ void ConnectionHandler::redirect(const std::string& /*host*/, const Array& /*kno void ConnectionHandler::close(uint16_t replyCode, const std::string& replyText) { proxy.closeOk(); - errorCode = replyCode; + errorCode = convert(replyCode); errorText = replyText; setState(CLOSED); QPID_LOG(warning, "Broker closed connection: " << replyCode << ", " << replyText); @@ -181,7 +285,9 @@ void ConnectionHandler::close(uint16_t replyCode, const std::string& replyText) void ConnectionHandler::closeOk() { checkState(CLOSING, INVALID_STATE_CLOSE_OK); - if (onClose) { + if (onError && errorCode != CLOSE_CODE_NORMAL) { + onError(errorCode, errorText); + } else if (onClose) { onClose(); } setState(CLOSED); @@ -195,5 +301,17 @@ bool ConnectionHandler::isOpen() const bool ConnectionHandler::isClosed() const { int s = getState(); - return s == CLOSING || s == CLOSED || s == FAILED; + return s == CLOSED || s == FAILED; +} + +bool ConnectionHandler::isClosing() const { return getState() == CLOSING; } + +std::auto_ptr<qpid::sys::SecurityLayer> ConnectionHandler::getSecurityLayer() +{ + return securityLayer; +} + +void ConnectionHandler::setRcvTimeoutTask(boost::intrusive_ptr<qpid::sys::TimerTask> t) +{ + rcvTimeoutTask = t; } diff --git a/cpp/src/qpid/client/ConnectionHandler.h b/cpp/src/qpid/client/ConnectionHandler.h index f8bd5e5d49..ed1e385dcf 100644 --- a/cpp/src/qpid/client/ConnectionHandler.h +++ b/cpp/src/qpid/client/ConnectionHandler.h @@ -21,17 +21,23 @@ #ifndef _ConnectionHandler_ #define _ConnectionHandler_ -#include "ChainableFrameHandler.h" -#include "ConnectionSettings.h" -#include "StateManager.h" +#include "qpid/client/ChainableFrameHandler.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/Sasl.h" +#include "qpid/client/StateManager.h" #include "qpid/framing/AMQMethodBody.h" #include "qpid/framing/AMQP_HighestVersion.h" #include "qpid/framing/AMQP_ClientOperations.h" #include "qpid/framing/AMQP_ServerProxy.h" #include "qpid/framing/Array.h" +#include "qpid/framing/enum.h" #include "qpid/framing/FieldTable.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/InputHandler.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/Timer.h" +#include "qpid/Url.h" +#include <memory> namespace qpid { namespace client { @@ -44,7 +50,7 @@ class ConnectionHandler : private StateManager, { typedef framing::AMQP_ClientOperations::ConnectionHandler ConnectionOperations; enum STATES {NOT_STARTED, NEGOTIATING, OPENING, OPEN, CLOSING, CLOSED, FAILED}; - std::set<int> ESTABLISHED; + std::set<int> ESTABLISHED, FINISHED; class Adapter : public framing::FrameHandler { @@ -56,12 +62,16 @@ class ConnectionHandler : private StateManager, Adapter outHandler; framing::AMQP_ServerProxy::Connection proxy; - uint16_t errorCode; + framing::connection::CloseCode errorCode; std::string errorText; bool insist; framing::ProtocolVersion version; framing::Array capabilities; framing::FieldTable properties; + std::auto_ptr<Sasl> sasl; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + boost::intrusive_ptr<qpid::sys::TimerTask> rcvTimeoutTask; + std::string operUserId; void checkState(STATES s, const std::string& msg); @@ -79,11 +89,13 @@ class ConnectionHandler : private StateManager, const framing::Array& knownHosts); void close(uint16_t replyCode, const std::string& replyText); void closeOk(); + void heartbeat(); public: using InputHandler::handle; typedef boost::function<void()> CloseListener; - typedef boost::function<void(uint16_t, const std::string&)> ErrorListener; + typedef boost::function<void(uint16_t, const std::string&)> ErrorListener; + typedef boost::function<unsigned int()> GetConnSSF; ConnectionHandler(const ConnectionSettings&, framing::ProtocolVersion&); @@ -99,9 +111,19 @@ public: // Note that open and closed aren't related by open = !closed bool isOpen() const; bool isClosed() const; + bool isClosing() const; + + std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(); + void setRcvTimeoutTask(boost::intrusive_ptr<qpid::sys::TimerTask>); CloseListener onClose; ErrorListener onError; + + std::vector<Url> knownBrokersUrls; + + static framing::connection::CloseCode convert(uint16_t replyCode); + const std::string& getUserId() const { return operUserId; } + GetConnSSF getSSF; /** query the connection for its security strength factor */ }; }} diff --git a/cpp/src/qpid/client/ConnectionImpl.cpp b/cpp/src/qpid/client/ConnectionImpl.cpp index 5e8596cacb..cede7f7310 100644 --- a/cpp/src/qpid/client/ConnectionImpl.cpp +++ b/cpp/src/qpid/client/ConnectionImpl.cpp @@ -18,55 +18,97 @@ * under the License. * */ -#include "ConnectionImpl.h" -#include "Connector.h" -#include "ConnectionSettings.h" -#include "SessionImpl.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/Connector.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/SessionImpl.h" #include "qpid/log/Statement.h" -#include "qpid/framing/constants.h" +#include "qpid/Url.h" +#include "qpid/framing/enum.h" #include "qpid/framing/reply_exceptions.h" #include <boost/bind.hpp> #include <boost/format.hpp> -using namespace qpid::client; +#include <limits> + +namespace qpid { +namespace client { + using namespace qpid::framing; +using namespace qpid::framing::connection; using namespace qpid::sys; - using namespace qpid::framing::connection;//for connection error codes +// Get timer singleton +Timer& theTimer() { + static Mutex timerInitLock; + ScopedLock<Mutex> l(timerInitLock); + + static qpid::sys::Timer t; + return t; +} + +class HeartbeatTask : public TimerTask { + TimeoutHandler& timeout; + + void fire() { + // If we ever get here then we have timed out + QPID_LOG(debug, "Traffic timeout"); + timeout.idleIn(); + } + +public: + HeartbeatTask(Duration p, TimeoutHandler& t) : + TimerTask(p), + timeout(t) + {} +}; + ConnectionImpl::ConnectionImpl(framing::ProtocolVersion v, const ConnectionSettings& settings) : Bounds(settings.maxFrameSize * settings.bounds), handler(settings, v), - connector(new Connector(v, settings, this)), - version(v) + version(v), + nextChannel(1) { - QPID_LOG(debug, "ConnectionImpl created for " << version); + QPID_LOG(debug, "ConnectionImpl created for " << version.toString()); handler.in = boost::bind(&ConnectionImpl::incoming, this, _1); handler.out = boost::bind(&Connector::send, boost::ref(connector), _1); handler.onClose = boost::bind(&ConnectionImpl::closed, this, - NORMAL, std::string()); - connector->setInputHandler(&handler); - connector->setShutdownHandler(this); - + CLOSE_CODE_NORMAL, std::string()); //only set error handler once open handler.onError = boost::bind(&ConnectionImpl::closed, this, _1, _2); + handler.getSSF = boost::bind(&Connector::getSSF, boost::ref(connector)); } +const uint16_t ConnectionImpl::NEXT_CHANNEL = std::numeric_limits<uint16_t>::max(); + ConnectionImpl::~ConnectionImpl() { // Important to close the connector first, to ensure the // connector thread does not call on us while the destructor // is running. - connector->close(); + if (connector) connector->close(); } -void ConnectionImpl::addSession(const boost::shared_ptr<SessionImpl>& session) +void ConnectionImpl::addSession(const boost::shared_ptr<SessionImpl>& session, uint16_t channel) { Mutex::ScopedLock l(lock); - boost::weak_ptr<SessionImpl>& s = sessions[session->getChannel()]; - if (s.lock()) throw SessionBusyException(); - s = session; + for (uint16_t i = 0; i < NEXT_CHANNEL; i++) { //will at most search through channels once + uint16_t c = channel == NEXT_CHANNEL ? nextChannel++ : channel; + boost::weak_ptr<SessionImpl>& s = sessions[c]; + boost::shared_ptr<SessionImpl> ss = s.lock(); + if (!ss) { + //channel is free, we can assign it to this session + session->setChannel(c); + s = session; + return; + } else if (channel != NEXT_CHANNEL) { + //channel is taken and was requested explicitly so don't look for another + throw SessionBusyException(QPID_MSG("Channel " << ss->getChannel() << " attached to " << ss->getId())); + } //else channel is busy, but we can keep looking for a free one + } + } void ConnectionImpl::handle(framing::AMQFrame& frame) @@ -81,9 +123,11 @@ void ConnectionImpl::incoming(framing::AMQFrame& frame) Mutex::ScopedLock l(lock); s = sessions[frame.getChannel()].lock(); } - if (!s) - throw NotAttachedException(QPID_MSG("Invalid channel: " << frame.getChannel())); - s->in(frame); + if (!s) { + QPID_LOG(info, "Dropping frame received on invalid channel: " << frame); + } else { + s->in(frame); + } } bool ConnectionImpl::isOpen() const @@ -92,57 +136,121 @@ bool ConnectionImpl::isOpen() const } -void ConnectionImpl::open(const std::string& host, int port) +void ConnectionImpl::open() { - QPID_LOG(info, "Connecting to " << host << ":" << port); + const std::string& protocol = handler.protocol; + const std::string& host = handler.host; + int port = handler.port; + QPID_LOG(info, "Connecting to " << protocol << ":" << host << ":" << port); + + connector.reset(Connector::create(protocol, version, handler, this)); + connector->setInputHandler(&handler); + connector->setShutdownHandler(this); connector->connect(host, port); connector->init(); - handler.waitForOpen(); + + // Enable heartbeat if requested + uint16_t heartbeat = static_cast<ConnectionSettings&>(handler).heartbeat; + if (heartbeat) { + // Set connection timeout to be 2x heart beat interval and setup timer + heartbeatTask = new HeartbeatTask(heartbeat * 2 * TIME_SEC, *this); + handler.setRcvTimeoutTask(heartbeatTask); + theTimer().add(heartbeatTask); + } + + try { + handler.waitForOpen(); + } catch (...) { + // Make sure the connector thread is joined. + connector->close(); + throw; + } + + // If the SASL layer has provided an "operational" userId for the connection, + // put it in the negotiated settings. + const std::string& userId(handler.getUserId()); + if (!userId.empty()) + handler.username = userId; + + //enable security layer if one has been negotiated: + std::auto_ptr<SecurityLayer> securityLayer = handler.getSecurityLayer(); + if (securityLayer.get()) { + QPID_LOG(debug, "Activating security layer"); + connector->activateSecurityLayer(securityLayer); + } else { + QPID_LOG(debug, "No security layer in place"); + } } void ConnectionImpl::idleIn() { - close(); + connector->abort(); } void ConnectionImpl::idleOut() { - AMQFrame frame(in_place<AMQHeartbeatBody>()); + AMQFrame frame((AMQHeartbeatBody())); connector->send(frame); } void ConnectionImpl::close() { - if (!handler.isOpen()) return; - handler.close(); - closed(NORMAL, "Closed by client"); + if (heartbeatTask) + heartbeatTask->cancel(); + // close() must be idempotent and no-throw as it will often be called in destructors. + if (handler.isOpen()) { + try { + handler.close(); + closed(CLOSE_CODE_NORMAL, "Closed by client"); + } catch (...) {} + } + assert(!handler.isOpen()); } template <class F> void ConnectionImpl::closeInternal(const F& f) { - connector->close(); - for (SessionMap::iterator i = sessions.begin(); i != sessions.end(); ++i) { + if (heartbeatTask) { + heartbeatTask->cancel(); + } + { + Mutex::ScopedUnlock u(lock); + connector->close(); + } + //notifying sessions of failure can result in those session being + //deleted which in turn results in a call to erase(); this can + //even happen on this thread, when 's' goes out of scope + //below. Using a copy prevents the map being modified as we + //iterate through. + SessionMap copy; + sessions.swap(copy); + for (SessionMap::iterator i = copy.begin(); i != copy.end(); ++i) { boost::shared_ptr<SessionImpl> s = i->second.lock(); if (s) f(s); } - sessions.clear(); } void ConnectionImpl::closed(uint16_t code, const std::string& text) { Mutex::ScopedLock l(lock); - setException(new ConnectionException(code, text)); + setException(new ConnectionException(ConnectionHandler::convert(code), text)); closeInternal(boost::bind(&SessionImpl::connectionClosed, _1, code, text)); } -static const std::string CONN_CLOSED("Connection closed by broker"); +static const std::string CONN_CLOSED("Connection closed"); void ConnectionImpl::shutdown() { - Mutex::ScopedLock l(lock); - // FIXME aconway 2008-06-06: exception use, connection-forced is incorrect here. - setException(new ConnectionException(CONNECTION_FORCED, CONN_CLOSED)); + if ( failureCallback ) + failureCallback(); + if (handler.isClosed()) return; - handler.fail(CONN_CLOSED); - closeInternal(boost::bind(&SessionImpl::connectionBroke, _1, CONNECTION_FORCED, CONN_CLOSED)); + + // FIXME aconway 2008-06-06: exception use, amqp0-10 does not seem to have + // an appropriate close-code. connection-forced is not right. + bool isClosing = handler.isClosing(); + handler.fail(CONN_CLOSED);//ensure connection is marked as failed before notifying sessions + Mutex::ScopedLock l(lock); + if (!isClosing) + closeInternal(boost::bind(&SessionImpl::connectionBroke, _1, CONN_CLOSED)); + setException(new TransportFailure(CONN_CLOSED)); } void ConnectionImpl::erase(uint16_t ch) { @@ -154,4 +262,16 @@ const ConnectionSettings& ConnectionImpl::getNegotiatedSettings() { return handler; } - + +std::vector<qpid::Url> ConnectionImpl::getInitialBrokers() { + return handler.knownBrokersUrls; +} + +boost::shared_ptr<SessionImpl> ConnectionImpl::newSession(const std::string& name, uint32_t timeout, uint16_t channel) { + boost::shared_ptr<SessionImpl> simpl(new SessionImpl(name, shared_from_this())); + addSession(simpl, channel); + simpl->open(timeout); + return simpl; +} + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/ConnectionImpl.h b/cpp/src/qpid/client/ConnectionImpl.h index cd0788d68b..2b32e1ccf0 100644 --- a/cpp/src/qpid/client/ConnectionImpl.h +++ b/cpp/src/qpid/client/ConnectionImpl.h @@ -22,8 +22,9 @@ #ifndef _ConnectionImpl_ #define _ConnectionImpl_ -#include "Bounds.h" -#include "ConnectionHandler.h" +#include "qpid/client/Bounds.h" +#include "qpid/client/ConnectionHandler.h" + #include "qpid/framing/FrameHandler.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/ShutdownHandler.h" @@ -39,7 +40,7 @@ namespace qpid { namespace client { class Connector; -class ConnectionSettings; +struct ConnectionSettings; class SessionImpl; class ConnectionImpl : public Bounds, @@ -51,12 +52,17 @@ class ConnectionImpl : public Bounds, { typedef std::map<uint16_t, boost::weak_ptr<SessionImpl> > SessionMap; + static const uint16_t NEXT_CHANNEL; + SessionMap sessions; ConnectionHandler handler; boost::scoped_ptr<Connector> connector; framing::ProtocolVersion version; + uint16_t nextChannel; sys::Mutex lock; + boost::intrusive_ptr<qpid::sys::TimerTask> heartbeatTask; + template <class F> void closeInternal(const F&); void incoming(framing::AMQFrame& frame); @@ -65,20 +71,27 @@ class ConnectionImpl : public Bounds, void idleIn(); void shutdown(); + boost::function<void ()> failureCallback; + public: ConnectionImpl(framing::ProtocolVersion version, const ConnectionSettings& settings); ~ConnectionImpl(); - void open(const std::string& host, int port); + void open(); bool isOpen() const; - void addSession(const boost::shared_ptr<SessionImpl>&); + boost::shared_ptr<SessionImpl> newSession(const std::string& name, uint32_t timeout, uint16_t channel=NEXT_CHANNEL); + void addSession(const boost::shared_ptr<SessionImpl>&, uint16_t channel=NEXT_CHANNEL); void close(); void handle(framing::AMQFrame& frame); void erase(uint16_t channel); - const ConnectionSettings& getNegotiatedSettings(); + + std::vector<Url> getInitialBrokers(); + void registerFailureCallback ( boost::function<void ()> fn ) { failureCallback = fn; } + + framing::ProtocolVersion getVersion() { return version; } }; }} diff --git a/cpp/src/qpid/client/ConnectionSettings.cpp b/cpp/src/qpid/client/ConnectionSettings.cpp index 6bc220cd41..3ae4ee010a 100644 --- a/cpp/src/qpid/client/ConnectionSettings.cpp +++ b/cpp/src/qpid/client/ConnectionSettings.cpp @@ -18,27 +18,28 @@ * under the License. * */ -#include "ConnectionSettings.h" +#include "qpid/client/ConnectionSettings.h" #include "qpid/log/Logger.h" #include "qpid/sys/Socket.h" -#include <sys/socket.h> +#include "qpid/Version.h" namespace qpid { namespace client { ConnectionSettings::ConnectionSettings() : + protocol("tcp"), host("localhost"), port(TcpAddress::DEFAULT_PORT), - username("guest"), - password("guest"), - mechanism("PLAIN"), locale("en_US"), heartbeat(0), maxChannels(32767), maxFrameSize(65535), bounds(2), - tcpNoDelay(false) + tcpNoDelay(false), + service(qpid::saslName), + minSsf(0), + maxSsf(256) {} ConnectionSettings::~ConnectionSettings() {} @@ -46,7 +47,7 @@ ConnectionSettings::~ConnectionSettings() {} void ConnectionSettings::configureSocket(qpid::sys::Socket& socket) const { if (tcpNoDelay) { - socket.setTcpNoDelay(tcpNoDelay); + socket.setTcpNoDelay(); QPID_LOG(info, "Set TCP_NODELAY"); } } diff --git a/cpp/src/qpid/client/ConnectionSettings.h b/cpp/src/qpid/client/ConnectionSettings.h deleted file mode 100644 index 5e93b3103e..0000000000 --- a/cpp/src/qpid/client/ConnectionSettings.h +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef QPID_CLIENT_CONNECTIONSETTINGS_H -#define QPID_CLIENT_CONNECTIONSETTINGS_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/Options.h" -#include "qpid/log/Options.h" -#include "qpid/Url.h" - -#include <iostream> -#include <exception> - -namespace qpid { - -namespace sys { -class Socket; -} - -namespace client { - -/** - * Settings for a Connection. - */ -struct ConnectionSettings { - - ConnectionSettings(); - virtual ~ConnectionSettings(); - - /** - * Allows socket to be configured; default only sets tcp-nodelay - * based on the flag set. Can be overridden. - */ - virtual void configureSocket(qpid::sys::Socket&) const; - - /** - * The host (or ip address) to connect to (defaults to 'localhost'). - */ - std::string host; - /** - * The port to connect to (defaults to 5672). - */ - uint16_t port; - /** - * Allows an AMQP 'virtual host' to be specified for the - * connection. - */ - std::string virtualhost; - - /** - * The username to use when authenticating the connection. - */ - std::string username; - /** - * The password to use when authenticating the connection. - */ - std::string password; - /** - * The SASL mechanism to use when authenticating the connection; - * the options are currently PLAIN or ANONYMOUS. - */ - std::string mechanism; - /** - * Allows a locale to be specified for the connection. - */ - std::string locale; - /** - * Allows a heartbeat frequency to be specified (this feature is - * not yet implemented). - */ - uint16_t heartbeat; - /** - * The maximum number of channels that the client will request for - * use on this connection. - */ - uint16_t maxChannels; - /** - * The maximum frame size that the client will request for this - * connection. - */ - uint16_t maxFrameSize; - /** - * Allows the size of outgoing frames to be limited. The value - * should be a mutliple of the maximum buffer size in use (which - * is in turn set through the maxFrameSize setting above). - */ - uint bounds; - /** - * If true, TCP_NODELAY will be set for the connection. - */ - bool tcpNoDelay; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_CONNECTIONSETTINGS_H*/ diff --git a/cpp/src/qpid/client/Connector.cpp b/cpp/src/qpid/client/Connector.cpp index f4f414bc63..2c4feffdcf 100644 --- a/cpp/src/qpid/client/Connector.cpp +++ b/cpp/src/qpid/client/Connector.cpp @@ -18,20 +18,23 @@ * under the License. * */ -#include "Connector.h" -#include "Bounds.h" -#include "ConnectionImpl.h" -#include "ConnectionSettings.h" +#include "qpid/client/Connector.h" + +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" #include "qpid/log/Statement.h" +#include "qpid/sys/Codec.h" #include "qpid/sys/Time.h" #include "qpid/framing/AMQFrame.h" #include "qpid/sys/AsynchIO.h" #include "qpid/sys/Dispatcher.h" #include "qpid/sys/Poller.h" +#include "qpid/sys/SecurityLayer.h" #include "qpid/Msg.h" #include <iostream> +#include <map> #include <boost/bind.hpp> #include <boost/format.hpp> @@ -43,227 +46,37 @@ using namespace qpid::framing; using boost::format; using boost::str; -Connector::Connector(ProtocolVersion ver, - const ConnectionSettings& settings, - ConnectionImpl* cimpl) - : maxFrameSize(settings.maxFrameSize), - version(ver), - initiated(false), - closed(true), - joined(true), - timeout(0), - idleIn(0), idleOut(0), - timeoutHandler(0), - shutdownHandler(0), - writer(maxFrameSize, cimpl), - aio(0), - impl(cimpl) -{ - QPID_LOG(debug, "Connector created for " << version); - settings.configureSocket(socket); -} - -Connector::~Connector() { - close(); -} - -void Connector::connect(const std::string& host, int port){ - Mutex::ScopedLock l(closedLock); - assert(closed); - socket.connect(host, port); - identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); - closed = false; - poller = Poller::shared_ptr(new Poller); - aio = new AsynchIO(socket, - boost::bind(&Connector::readbuff, this, _1, _2), - boost::bind(&Connector::eof, this, _1), - boost::bind(&Connector::eof, this, _1), - 0, // closed - 0, // nobuffs - boost::bind(&Connector::writebuff, this, _1)); - writer.init(identifier, aio); -} - -void Connector::init(){ - Mutex::ScopedLock l(closedLock); - assert(joined); - ProtocolInitiation init(version); - writeDataBlock(init); - joined = false; - receiver = Thread(this); -} - -bool Connector::closeInternal() { - Mutex::ScopedLock l(closedLock); - bool ret = !closed; - if (!closed) { - closed = true; - poller->shutdown(); - } - if (!joined && receiver.id() != Thread::current().id()) { - joined = true; - Mutex::ScopedUnlock u(closedLock); - receiver.join(); - } - return ret; -} - -void Connector::close() { - closeInternal(); -} - -void Connector::setInputHandler(InputHandler* handler){ - input = handler; -} - -void Connector::setShutdownHandler(ShutdownHandler* handler){ - shutdownHandler = handler; -} - -OutputHandler* Connector::getOutputHandler(){ - return this; -} +// Stuff for the registry of protocol connectors (maybe should be moved to its own file) +namespace { + typedef std::map<std::string, Connector::Factory*> ProtocolRegistry; -void Connector::send(AMQFrame& frame) { - writer.handle(frame); -} + ProtocolRegistry& theProtocolRegistry() { + static ProtocolRegistry protocolRegistry; -void Connector::handleClosed() { - if (closeInternal() && shutdownHandler) - shutdownHandler->shutdown(); + return protocolRegistry; + } } -struct Connector::Buff : public AsynchIO::BufferBase { - Buff(size_t size) : AsynchIO::BufferBase(new char[size], size) {} - ~Buff() { delete [] bytes;} -}; - -Connector::Writer::Writer(uint16_t s, Bounds* b) : maxFrameSize(s), aio(0), buffer(0), lastEof(0), bounds(b) +Connector* Connector::create(const std::string& proto, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { -} - -Connector::Writer::~Writer() { delete buffer; } - -void Connector::Writer::init(std::string id, sys::AsynchIO* a) { - Mutex::ScopedLock l(lock); - identifier = id; - aio = a; - newBuffer(l); -} -void Connector::Writer::handle(framing::AMQFrame& frame) { - Mutex::ScopedLock l(lock); - frames.push_back(frame); - if (frame.getEof()) {//or if we already have a buffers worth - lastEof = frames.size(); - aio->notifyPendingWrite(); - } - QPID_LOG(trace, "SENT " << identifier << ": " << frame); -} - -void Connector::Writer::writeOne(const Mutex::ScopedLock& l) { - assert(buffer); - framesEncoded = 0; - - buffer->dataStart = 0; - buffer->dataCount = encode.getPosition(); - aio->queueWrite(buffer); - newBuffer(l); -} - -void Connector::Writer::newBuffer(const Mutex::ScopedLock&) { - buffer = aio->getQueuedBuffer(); - if (!buffer) buffer = new Buff(maxFrameSize); - encode = framing::Buffer(buffer->bytes, buffer->byteCount); - framesEncoded = 0; -} - -// Called in IO thread. -void Connector::Writer::write(sys::AsynchIO&) { - Mutex::ScopedLock l(lock); - assert(buffer); - size_t bytesWritten(0); - for (size_t i = 0; i < lastEof; ++i) { - AMQFrame& frame = frames[i]; - uint32_t size = frame.size(); - if (size > encode.available()) writeOne(l); - assert(size <= encode.available()); - frame.encode(encode); - ++framesEncoded; - bytesWritten += size; + ProtocolRegistry::const_iterator i = theProtocolRegistry().find(proto); + if (i==theProtocolRegistry().end()) { + throw Exception(QPID_MSG("Unknown protocol: " << proto)); } - frames.erase(frames.begin(), frames.begin()+lastEof); - lastEof = 0; - if (bounds) bounds->reduce(bytesWritten); - if (encode.getPosition() > 0) writeOne(l); + return (i->second)(v, s, c); } -void Connector::readbuff(AsynchIO& aio, AsynchIO::BufferBase* buff) { - framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); - - if (!initiated) { - framing::ProtocolInitiation protocolInit; - if (protocolInit.decode(in)) { - //TODO: check the version is correct - QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); - } - initiated = true; - } - AMQFrame frame; - while(frame.decode(in)){ - QPID_LOG(trace, "RECV " << identifier << ": " << frame); - input->received(frame); - } - // TODO: unreading needs to go away, and when we can cope - // with multiple sub-buffers in the general buffer scheme, it will - if (in.available() != 0) { - // Adjust buffer for used bytes and then "unread them" - buff->dataStart += buff->dataCount-in.available(); - buff->dataCount = in.available(); - aio.unread(buff); - } else { - // Give whole buffer back to aio subsystem - aio.queueReadBuffer(buff); +void Connector::registerFactory(const std::string& proto, Factory* connectorFactory) +{ + ProtocolRegistry::const_iterator i = theProtocolRegistry().find(proto); + if (i!=theProtocolRegistry().end()) { + QPID_LOG(error, "Tried to register protocol: " << proto << " more than once"); } + theProtocolRegistry()[proto] = connectorFactory; } -void Connector::writebuff(AsynchIO& aio_) { - writer.write(aio_); -} - -void Connector::writeDataBlock(const AMQDataBlock& data) { - AsynchIO::BufferBase* buff = new Buff(maxFrameSize); - framing::Buffer out(buff->bytes, buff->byteCount); - data.encode(out); - buff->dataCount = data.size(); - aio->queueWrite(buff); -} - -void Connector::eof(AsynchIO&) { - handleClosed(); -} - -// TODO: astitcher 20070908 This version of the code can never time out, so the idle processing -// will never be called -void Connector::run(){ - // Keep the connection impl in memory until run() completes. - boost::shared_ptr<ConnectionImpl> protect = impl->shared_from_this(); - assert(protect); - try { - Dispatcher d(poller); - - for (int i = 0; i < 32; i++) { - aio->queueReadBuffer(new Buff(maxFrameSize)); - } - - aio->start(poller); - d.run(); - aio->queueForDeletion(); - socket.close(); - } catch (const std::exception& e) { - QPID_LOG(error, e.what()); - handleClosed(); - } +void Connector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>) +{ } - }} // namespace qpid::client diff --git a/cpp/src/qpid/client/Connector.h b/cpp/src/qpid/client/Connector.h index cde12f7b5b..3a49ae9012 100644 --- a/cpp/src/qpid/client/Connector.h +++ b/cpp/src/qpid/client/Connector.h @@ -42,107 +42,40 @@ namespace qpid { namespace sys { -class Poller; -class AsynchIO; -class AsynchIOBufferBase; +class SecurityLayer; } - + namespace client { -class Bounds; -class ConnectionSettings; +struct ConnectionSettings; class ConnectionImpl; ///@internal -class Connector : public framing::OutputHandler, - private sys::Runnable -{ - struct Buff; - - /** Batch up frames for writing to aio. */ - class Writer : public framing::FrameHandler { - typedef sys::AsynchIOBufferBase BufferBase; - typedef std::vector<framing::AMQFrame> Frames; - - const uint16_t maxFrameSize; - sys::Mutex lock; - sys::AsynchIO* aio; - BufferBase* buffer; - Frames frames; - size_t lastEof; // Position after last EOF in frames - framing::Buffer encode; - size_t framesEncoded; - std::string identifier; - Bounds* bounds; - - void writeOne(const sys::Mutex::ScopedLock&); - void newBuffer(const sys::Mutex::ScopedLock&); - - public: - - Writer(uint16_t maxFrameSize, Bounds*); - ~Writer(); - void init(std::string id, sys::AsynchIO*); - void handle(framing::AMQFrame&); - void write(sys::AsynchIO&); - }; - - const uint16_t maxFrameSize; - framing::ProtocolVersion version; - bool initiated; - - sys::Mutex closedLock; - bool closed; - bool joined; - - sys::AbsTime lastIn; - sys::AbsTime lastOut; - sys::Duration timeout; - sys::Duration idleIn; - sys::Duration idleOut; - - sys::TimeoutHandler* timeoutHandler; - sys::ShutdownHandler* shutdownHandler; - framing::InputHandler* input; - framing::InitiationHandler* initialiser; - framing::OutputHandler* output; - - Writer writer; - - sys::Thread receiver; - - sys::Socket socket; - - sys::AsynchIO* aio; - boost::shared_ptr<sys::Poller> poller; - - void run(); - void handleClosed(); - bool closeInternal(); - - void readbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); - void writebuff(qpid::sys::AsynchIO&); - void writeDataBlock(const framing::AMQDataBlock& data); - void eof(qpid::sys::AsynchIO&); - - std::string identifier; - - ConnectionImpl* impl; - +class Connector : public framing::OutputHandler +{ public: - Connector(framing::ProtocolVersion pVersion, - const ConnectionSettings&, - ConnectionImpl*); - virtual ~Connector(); - virtual void connect(const std::string& host, int port); - virtual void init(); - virtual void close(); - virtual void setInputHandler(framing::InputHandler* handler); - virtual void setShutdownHandler(sys::ShutdownHandler* handler); - virtual sys::ShutdownHandler* getShutdownHandler() { return shutdownHandler; } - virtual framing::OutputHandler* getOutputHandler(); - virtual void send(framing::AMQFrame& frame); - const std::string& getIdentifier() const { return identifier; } + // Protocol connector factory related stuff (it might be better to separate this code from the TCP Connector in the future) + typedef Connector* Factory(framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*); + static Connector* create(const std::string& proto, framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*); + static void registerFactory(const std::string& proto, Factory* connectorFactory); + + virtual ~Connector() {}; + virtual void connect(const std::string& host, int port) = 0; + virtual void init() {}; + virtual void close() = 0; + virtual void send(framing::AMQFrame& frame) = 0; + virtual void abort() = 0; + + virtual void setInputHandler(framing::InputHandler* handler) = 0; + virtual void setShutdownHandler(sys::ShutdownHandler* handler) = 0; + virtual sys::ShutdownHandler* getShutdownHandler() const = 0; + virtual framing::OutputHandler* getOutputHandler() = 0; + virtual const std::string& getIdentifier() const = 0; + + virtual void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + + virtual unsigned int getSSF() = 0; + }; }} diff --git a/cpp/src/qpid/client/Demux.cpp b/cpp/src/qpid/client/Demux.cpp index b774214ae4..abc23c75df 100644 --- a/cpp/src/qpid/client/Demux.cpp +++ b/cpp/src/qpid/client/Demux.cpp @@ -19,7 +19,7 @@ * */ -#include "Demux.h" +#include "qpid/client/Demux.h" #include "qpid/Exception.h" #include "qpid/framing/MessageTransferBody.h" diff --git a/cpp/src/qpid/client/Demux.h b/cpp/src/qpid/client/Demux.h index 7304e799bb..31dc3f9c06 100644 --- a/cpp/src/qpid/client/Demux.h +++ b/cpp/src/qpid/client/Demux.h @@ -25,6 +25,7 @@ #include "qpid/framing/FrameSet.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/BlockingQueue.h" +#include "qpid/client/ClientImportExport.h" #ifndef _Demux_ #define _Demux_ @@ -49,17 +50,17 @@ public: typedef sys::BlockingQueue<framing::FrameSet::shared_ptr> Queue; typedef boost::shared_ptr<Queue> QueuePtr; - Demux(); - ~Demux(); + QPID_CLIENT_EXTERN Demux(); + QPID_CLIENT_EXTERN ~Demux(); - void handle(framing::FrameSet::shared_ptr); - void close(const sys::ExceptionHolder& ex); - void open(); - - QueuePtr add(const std::string& name, Condition); - void remove(const std::string& name); - QueuePtr get(const std::string& name); - QueuePtr getDefault(); + QPID_CLIENT_EXTERN void handle(framing::FrameSet::shared_ptr); + QPID_CLIENT_EXTERN void close(const sys::ExceptionHolder& ex); + QPID_CLIENT_EXTERN void open(); + + QPID_CLIENT_EXTERN QueuePtr add(const std::string& name, Condition); + QPID_CLIENT_EXTERN void remove(const std::string& name); + QPID_CLIENT_EXTERN QueuePtr get(const std::string& name); + QPID_CLIENT_EXTERN QueuePtr getDefault(); private: struct Record diff --git a/cpp/src/qpid/client/Dispatcher.cpp b/cpp/src/qpid/client/Dispatcher.cpp index 5028d68405..a715c623bf 100644 --- a/cpp/src/qpid/client/Dispatcher.cpp +++ b/cpp/src/qpid/client/Dispatcher.cpp @@ -18,15 +18,25 @@ * under the License. * */ -#include "Dispatcher.h" +#include "qpid/client/Dispatcher.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/SessionImpl.h" #include "qpid/framing/FrameSet.h" #include "qpid/framing/MessageTransferBody.h" #include "qpid/log/Statement.h" #include "qpid/sys/BlockingQueue.h" -#include "Message.h" - -#include <boost/state_saver.hpp> +#include "qpid/client/Message.h" +#include "qpid/client/MessageImpl.h" + +#include <boost/version.hpp> +#if (BOOST_VERSION >= 104000) +# include <boost/serialization/state_saver.hpp> + using boost::serialization::state_saver; +#else +# include <boost/state_saver.hpp> + using boost::state_saver; +#endif /* BOOST_VERSION */ using qpid::framing::FrameSet; using qpid::framing::MessageTransferBody; @@ -37,44 +47,40 @@ using qpid::sys::Thread; namespace qpid { namespace client { -Subscriber::Subscriber(const Session& s, MessageListener* l, AckPolicy a) - : session(s), listener(l), autoAck(a) {} - -void Subscriber::received(Message& msg) -{ - if (listener) { - listener->received(msg); - autoAck.ack(msg, session); - } -} - Dispatcher::Dispatcher(const Session& s, const std::string& q) - : session(s), running(false), autoStop(true) + : session(s), + running(false), + autoStop(true), + failoverHandler(0) { - queue = q.empty() ? - session.getExecution().getDemux().getDefault() : - session.getExecution().getDemux().get(q); -} + Demux& demux = SessionBase_0_10Access(session).get()->getDemux(); + queue = q.empty() ? demux.getDefault() : demux.get(q); +} void Dispatcher::start() { worker = Thread(this); } +void Dispatcher::wait() +{ + worker.join(); +} + void Dispatcher::run() { Mutex::ScopedLock l(lock); if (running) throw Exception("Dispatcher is already running."); - boost::state_saver<bool> reset(running); // Reset to false on exit. + state_saver<bool> reset(running); // Reset to false on exit. running = true; try { while (!queue->isClosed()) { Mutex::ScopedUnlock u(lock); FrameSet::shared_ptr content = queue->pop(); if (content->isA<MessageTransferBody>()) { - Message msg(*content); - Subscriber::shared_ptr listener = find(msg.getDestination()); + Message msg(new MessageImpl(*content)); + boost::intrusive_ptr<SubscriptionImpl> listener = find(msg.getDestination()); if (!listener) { QPID_LOG(error, "No listener found for destination " << msg.getDestination()); } else { @@ -91,9 +97,21 @@ void Dispatcher::run() } session.sync(); // Make sure all our acks are received before returning. } - catch (const ClosedException&) {} //ignore it and return + catch (const ClosedException&) { + QPID_LOG(debug, QPID_MSG(session.getId() << ": closed by peer")); + } + catch (const TransportFailure&) { + QPID_LOG(info, QPID_MSG(session.getId() << ": transport failure")); + throw; + } catch (const std::exception& e) { - QPID_LOG(error, "Exception in client dispatch thread: " << e.what()); + if ( failoverHandler ) { + QPID_LOG(debug, QPID_MSG(session.getId() << " failover: " << e.what())); + failoverHandler(); + } else { + QPID_LOG(error, session.getId() << " error: " << e.what()); + throw; + } } } @@ -109,7 +127,7 @@ void Dispatcher::setAutoStop(bool b) autoStop = b; } -Subscriber::shared_ptr Dispatcher::find(const std::string& name) +boost::intrusive_ptr<SubscriptionImpl> Dispatcher::find(const std::string& name) { ScopedLock<Mutex> l(lock); Listeners::iterator i = listeners.find(name); @@ -119,27 +137,14 @@ Subscriber::shared_ptr Dispatcher::find(const std::string& name) return i->second; } -void Dispatcher::listen( - MessageListener* listener, AckPolicy autoAck -) -{ +void Dispatcher::listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription) { ScopedLock<Mutex> l(lock); - defaultListener = Subscriber::shared_ptr( - new Subscriber(session, listener, autoAck)); + listeners[subscription->getName()] = subscription; } -void Dispatcher::listen(const std::string& destination, MessageListener* listener, AckPolicy autoAck) -{ - ScopedLock<Mutex> l(lock); - listeners[destination] = Subscriber::shared_ptr( - new Subscriber(session, listener, autoAck)); -} - -void Dispatcher::cancel(const std::string& destination) -{ +void Dispatcher::cancel(const std::string& destination) { ScopedLock<Mutex> l(lock); - listeners.erase(destination); - if (autoStop && listeners.empty()) + if (listeners.erase(destination) && running && autoStop && listeners.empty()) queue->close(); } diff --git a/cpp/src/qpid/client/Dispatcher.h b/cpp/src/qpid/client/Dispatcher.h index 7d42bf8793..74fdb90103 100644 --- a/cpp/src/qpid/client/Dispatcher.h +++ b/cpp/src/qpid/client/Dispatcher.h @@ -26,28 +26,18 @@ #include <string> #include <boost/shared_ptr.hpp> #include "qpid/client/Session.h" +#include "qpid/client/SessionBase_0_10Access.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/Runnable.h" #include "qpid/sys/Thread.h" -#include "MessageListener.h" -#include "AckPolicy.h" +#include "qpid/client/ClientImportExport.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/SubscriptionImpl.h" namespace qpid { namespace client { -///@internal -class Subscriber : public MessageListener -{ - AsyncSession session; - MessageListener* const listener; - AckPolicy autoAck; - -public: - typedef boost::shared_ptr<Subscriber> shared_ptr; - Subscriber(const Session& session, MessageListener* listener, AckPolicy); - void received(Message& msg); - -}; +class SubscriptionImpl; ///@internal typedef framing::Handler<framing::FrameSet> FrameSetHandler; @@ -55,7 +45,7 @@ typedef framing::Handler<framing::FrameSet> FrameSetHandler; ///@internal class Dispatcher : public sys::Runnable { - typedef std::map<std::string, Subscriber::shared_ptr> Listeners; + typedef std::map<std::string, boost::intrusive_ptr<SubscriptionImpl> >Listeners; sys::Mutex lock; sys::Thread worker; Session session; @@ -63,22 +53,32 @@ class Dispatcher : public sys::Runnable bool running; bool autoStop; Listeners listeners; - Subscriber::shared_ptr defaultListener; + boost::intrusive_ptr<SubscriptionImpl> defaultListener; std::auto_ptr<FrameSetHandler> handler; - Subscriber::shared_ptr find(const std::string& name); + boost::intrusive_ptr<SubscriptionImpl> find(const std::string& name); bool isStopped(); + boost::function<void ()> failoverHandler; + public: Dispatcher(const Session& session, const std::string& queue = ""); + ~Dispatcher() {} void start(); - void run(); + void wait(); + // As this class is marked 'internal', no extern should be made here; + // however, some test programs rely on it. + QPID_CLIENT_EXTERN void run(); void stop(); void setAutoStop(bool b); - void listen(MessageListener* listener, AckPolicy autoAck=AckPolicy()); - void listen(const std::string& destination, MessageListener* listener, AckPolicy autoAck=AckPolicy()); + void registerFailoverHandler ( boost::function<void ()> fh ) + { + failoverHandler = fh; + } + + void listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription); void cancel(const std::string& destination); }; diff --git a/cpp/src/qpid/client/Execution.h b/cpp/src/qpid/client/Execution.h index 10674afde0..ad622af9c1 100644 --- a/cpp/src/qpid/client/Execution.h +++ b/cpp/src/qpid/client/Execution.h @@ -22,7 +22,7 @@ #define _Execution_ #include "qpid/framing/SequenceNumber.h" -#include "Demux.h" +#include "qpid/client/Demux.h" namespace qpid { namespace client { diff --git a/cpp/src/qpid/client/FailoverListener.cpp b/cpp/src/qpid/client/FailoverListener.cpp new file mode 100644 index 0000000000..3396f5598c --- /dev/null +++ b/cpp/src/qpid/client/FailoverListener.cpp @@ -0,0 +1,89 @@ +/* + * + * 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/FailoverListener.h" +#include "qpid/client/Session.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include "qpid/log/Helpers.h" + +namespace qpid { +namespace client { + +const std::string FailoverListener::AMQ_FAILOVER("amq.failover"); + +FailoverListener::FailoverListener(Connection c) : + connection(c), + session(c.newSession(AMQ_FAILOVER+"."+framing::Uuid(true).str())), + subscriptions(session) +{ + knownBrokers = c.getInitialBrokers(); + if (session.exchangeQuery(arg::name=AMQ_FAILOVER).getNotFound()) { + session.close(); + return; + } + std::string qname=session.getId().getName(); + session.queueDeclare(arg::queue=qname, arg::exclusive=true, arg::autoDelete=true); + session.exchangeBind(arg::queue=qname, arg::exchange=AMQ_FAILOVER); + subscriptions.subscribe(*this, qname, SubscriptionSettings(FlowControl::unlimited(), + ACCEPT_MODE_NONE)); + thread = sys::Thread(*this); +} + +void FailoverListener::run() { + try { + subscriptions.run(); + } catch(...) {} +} + +FailoverListener::~FailoverListener() { + try { + subscriptions.stop(); + thread.join(); + if (connection.isOpen()) { + session.sync(); + session.close(); + } + } catch (...) {} +} + +void FailoverListener::received(Message& msg) { + sys::Mutex::ScopedLock l(lock); + knownBrokers = getKnownBrokers(msg); +} + +std::vector<Url> FailoverListener::getKnownBrokers() const { + sys::Mutex::ScopedLock l(lock); + return knownBrokers; +} + +std::vector<Url> FailoverListener::getKnownBrokers(const Message& msg) { + std::vector<Url> knownBrokers; + framing::Array urlArray; + msg.getHeaders().getArray("amq.failover", urlArray); + for (framing::Array::ValueVector::const_iterator i = urlArray.begin(); + i != urlArray.end(); + ++i ) + knownBrokers.push_back(Url((*i)->get<std::string>())); + return knownBrokers; +} + + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/FailoverManager.cpp b/cpp/src/qpid/client/FailoverManager.cpp new file mode 100644 index 0000000000..81f71eb7df --- /dev/null +++ b/cpp/src/qpid/client/FailoverManager.cpp @@ -0,0 +1,131 @@ +/* + * + * 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/FailoverManager.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" + + +namespace qpid { +namespace client { + +using qpid::sys::Monitor; +using qpid::sys::AbsTime; +using qpid::sys::Duration; + +FailoverManager::FailoverManager(const ConnectionSettings& s, + ReconnectionStrategy* rs) : settings(s), strategy(rs), state(IDLE) {} + +void FailoverManager::execute(Command& c) +{ + bool retry = false; + bool completed = false; + AbsTime failed; + while (!completed) { + try { + AsyncSession session = connect().newSession(); + if (retry) { + Duration failoverTime(failed, AbsTime::now()); + QPID_LOG(info, "Failed over for " << &c << " in " << (failoverTime/qpid::sys::TIME_MSEC) << " milliseconds"); + } + c.execute(session, retry); + session.sync();//TODO: shouldn't be required + session.close(); + completed = true; + } catch(const TransportFailure&) { + retry = true; + failed = AbsTime::now(); + } + } +} + +void FailoverManager::close() +{ + Monitor::ScopedLock l(lock); + connection.close(); +} + +Connection& FailoverManager::connect(std::vector<Url> brokers) +{ + Monitor::ScopedLock l(lock); + if (state == CANT_CONNECT) { + state = IDLE;//retry + } + while (!connection.isOpen()) { + if (state == CONNECTING) { + lock.wait(); + } else if (state == CANT_CONNECT) { + throw CannotConnectException("Cannot establish a connection"); + } else { + state = CONNECTING; + Connection c; + if (brokers.empty() && failoverListener.get()) + brokers = failoverListener->getKnownBrokers(); + attempt(c, settings, brokers); + if (c.isOpen()) state = IDLE; + else state = CANT_CONNECT; + connection = c; + lock.notifyAll(); + } + } + return connection; +} + +Connection& FailoverManager::getConnection() +{ + Monitor::ScopedLock l(lock); + return connection; +} + +void FailoverManager::attempt(Connection& c, ConnectionSettings s, std::vector<Url> urls) +{ + Monitor::ScopedUnlock u(lock); + if (strategy) strategy->editUrlList(urls); + if (urls.empty()) { + attempt(c, s); + } else { + for (std::vector<Url>::const_iterator i = urls.begin(); i != urls.end() && !c.isOpen(); ++i) { + for (Url::const_iterator j = i->begin(); j != i->end() && !c.isOpen(); ++j) { + const TcpAddress* tcp = j->get<TcpAddress>(); + if (tcp) { + s.host = tcp->host; + s.port = tcp->port; + attempt(c, s); + } + } + } + } +} + +void FailoverManager::attempt(Connection& c, ConnectionSettings s) +{ + try { + QPID_LOG(info, "Attempting to connect to " << s.host << " on " << s.port << "..."); + c.open(s); + failoverListener.reset(new FailoverListener(c)); + QPID_LOG(info, "Connected to " << s.host << " on " << s.port); + } catch (const Exception& e) { + QPID_LOG(info, "Could not connect to " << s.host << " on " << s.port << ": " << e.what()); + } +} + + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/FlowControl.h b/cpp/src/qpid/client/FlowControl.h deleted file mode 100644 index a4ed9879f4..0000000000 --- a/cpp/src/qpid/client/FlowControl.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef QPID_CLIENT_FLOWCONTROL_H -#define QPID_CLIENT_FLOWCONTROL_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 client { - -/** - * Flow control works by associating a finite amount of "credit" - * associated with a subscription. - * - * Credit includes a message count and a byte count. Each message - * received decreases the message count by one, and the byte count by - * the size of the message. Either count can have the special value - * UNLIMITED which is never decreased. - * - * A subscription's credit is exhausted when the message count is 0 or - * the byte count is too small for the next available message. The - * subscription will not receive any further messages until is credit - * is renewed. - * - * In "window mode" credit is automatically renewed when a message is - * acknowledged (@see AckPolicy) In non-window mode credit is not - * automatically renewed, it must be explicitly re-set (@see - * SubscriptionManager) - */ -struct FlowControl { - static const uint32_t UNLIMITED=0xFFFFFFFF; - FlowControl(uint32_t messages_=0, uint32_t bytes_=0, bool window_=false) - : messages(messages_), bytes(bytes_), window(window_) {} - - static FlowControl messageCredit(uint32_t messages_) { return FlowControl(messages_,UNLIMITED,false); } - static FlowControl messageWindow(uint32_t messages_) { return FlowControl(messages_,UNLIMITED,true); } - static FlowControl byteCredit(uint32_t bytes_) { return FlowControl(UNLIMITED,bytes_,false); } - static FlowControl byteWindow(uint32_t bytes_) { return FlowControl(UNLIMITED,bytes_,true); } - static FlowControl unlimited() { return FlowControl(UNLIMITED, UNLIMITED, false); } - static FlowControl zero() { return FlowControl(0, 0, false); } - - /** Message credit: subscription can accept up to this many messages. */ - uint32_t messages; - /** Byte credit: subscription can accept up to this many bytes of message content. */ - uint32_t bytes; - /** Window mode. If true credit is automatically renewed as messages are acknowledged. */ - bool window; - - bool operator==(const FlowControl& x) { - return messages == x.messages && bytes == x.bytes && window == x.window; - }; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_FLOWCONTROL_H*/ diff --git a/cpp/src/qpid/client/Future.cpp b/cpp/src/qpid/client/Future.cpp index 6a0c78ae4b..740cd3df59 100644 --- a/cpp/src/qpid/client/Future.cpp +++ b/cpp/src/qpid/client/Future.cpp @@ -19,7 +19,8 @@ * */ -#include "Future.h" +#include "qpid/client/Future.h" +#include "qpid/client/SessionImpl.h" namespace qpid { namespace client { diff --git a/cpp/src/qpid/client/Future.h b/cpp/src/qpid/client/Future.h deleted file mode 100644 index 67f39cdf3f..0000000000 --- a/cpp/src/qpid/client/Future.h +++ /dev/null @@ -1,64 +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. - * - */ - -#ifndef _Future_ -#define _Future_ - -#include <boost/bind.hpp> -#include <boost/shared_ptr.hpp> -#include "qpid/Exception.h" -#include "qpid/framing/SequenceNumber.h" -#include "qpid/framing/StructHelper.h" -#include "FutureCompletion.h" -#include "FutureResult.h" -#include "SessionImpl.h" - -namespace qpid { -namespace client { - -/**@internal */ -class Future : private framing::StructHelper -{ - framing::SequenceNumber command; - boost::shared_ptr<FutureResult> result; - bool complete; - -public: - Future() : complete(false) {} - Future(const framing::SequenceNumber& id) : command(id), complete(false) {} - - template <class T> void decodeResult(T& value, SessionImpl& session) - { - if (result) { - decode(value, result->getResult(session)); - } else { - throw Exception("Result not expected"); - } - } - - void wait(SessionImpl& session); - bool isComplete(SessionImpl& session); - void setFutureResult(boost::shared_ptr<FutureResult> r); -}; - -}} - -#endif diff --git a/cpp/src/qpid/client/FutureCompletion.cpp b/cpp/src/qpid/client/FutureCompletion.cpp index 130c65d6aa..ccfb073855 100644 --- a/cpp/src/qpid/client/FutureCompletion.cpp +++ b/cpp/src/qpid/client/FutureCompletion.cpp @@ -19,7 +19,7 @@ * */ -#include "FutureCompletion.h" +#include "qpid/client/FutureCompletion.h" using namespace qpid::client; using namespace qpid::sys; diff --git a/cpp/src/qpid/client/FutureResult.cpp b/cpp/src/qpid/client/FutureResult.cpp index 007f278051..0237eb1464 100644 --- a/cpp/src/qpid/client/FutureResult.cpp +++ b/cpp/src/qpid/client/FutureResult.cpp @@ -19,9 +19,9 @@ * */ -#include "FutureResult.h" +#include "qpid/client/FutureResult.h" -#include "SessionImpl.h" +#include "qpid/client/SessionImpl.h" using namespace qpid::client; using namespace qpid::framing; diff --git a/cpp/src/qpid/client/LoadPlugins.cpp b/cpp/src/qpid/client/LoadPlugins.cpp new file mode 100644 index 0000000000..82cdedc7fe --- /dev/null +++ b/cpp/src/qpid/client/LoadPlugins.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. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "qpid/Modules.h" +#include "qpid/sys/Shlib.h" +#include <string> +#include <vector> +using std::vector; +using std::string; + +namespace { + +struct LoadtimeInitialise { + LoadtimeInitialise() { + qpid::ModuleOptions moduleOptions(QPIDC_MODULE_DIR); + string defaultPath (moduleOptions.loadDir); + moduleOptions.parse (0, 0, QPIDC_CONF_FILE, true); + + for (vector<string>::iterator iter = moduleOptions.load.begin(); + iter != moduleOptions.load.end(); + iter++) + qpid::tryShlib (iter->data(), false); + + if (!moduleOptions.noLoad) { + bool isDefault = defaultPath == moduleOptions.loadDir; + qpid::loadModuleDir (moduleOptions.loadDir, isDefault); + } + } +} init; + +} // namespace diff --git a/cpp/src/qpid/client/LocalQueue.cpp b/cpp/src/qpid/client/LocalQueue.cpp index 99ab6f0133..0019adabaf 100644 --- a/cpp/src/qpid/client/LocalQueue.cpp +++ b/cpp/src/qpid/client/LocalQueue.cpp @@ -18,59 +18,35 @@ * under the License. * */ -#include "LocalQueue.h" +#include "qpid/client/LocalQueue.h" +#include "qpid/client/LocalQueueImpl.h" +#include "qpid/client/MessageImpl.h" #include "qpid/Exception.h" #include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageTransferBody.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/client/SubscriptionImpl.h" namespace qpid { namespace client { using namespace framing; -LocalQueue::LocalQueue(AckPolicy a) : autoAck(a) {} -LocalQueue::~LocalQueue() {} +typedef PrivateImplRef<LocalQueue> PI; -Message LocalQueue::pop() { return get(); } +LocalQueue::LocalQueue() { PI::ctor(*this, new LocalQueueImpl()); } +LocalQueue::LocalQueue(const LocalQueue& x) : Handle<LocalQueueImpl>() { PI::copy(*this, x); } +LocalQueue::~LocalQueue() { PI::dtor(*this); } +LocalQueue& LocalQueue::operator=(const LocalQueue& x) { return PI::assign(*this, x); } -Message LocalQueue::get() { - Message result; - bool ok = get(result, sys::TIME_INFINITE); - assert(ok); (void) ok; - return result; -} +Message LocalQueue::pop(sys::Duration timeout) { return impl->pop(timeout); } -bool LocalQueue::get(Message& result, sys::Duration timeout) { - if (!queue) - throw ClosedException(); - FrameSet::shared_ptr content; - bool ok = queue->pop(content, timeout); - if (!ok) return false; - if (content->isA<MessageTransferBody>()) { - result = Message(*content); - autoAck.ack(result, session); - return true; - } - else - throw CommandInvalidException( - QPID_MSG("Unexpected method: " << content->getMethod())); -} +Message LocalQueue::get(sys::Duration timeout) { return impl->get(timeout); } -void LocalQueue::setAckPolicy(AckPolicy a) { autoAck=a; } -AckPolicy& LocalQueue::getAckPolicy() { return autoAck; } +bool LocalQueue::get(Message& result, sys::Duration timeout) { return impl->get(result, timeout); } -bool LocalQueue::empty() const -{ - if (!queue) - throw ClosedException(); - return queue->empty(); -} - -size_t LocalQueue::size() const -{ - if (!queue) - throw ClosedException(); - return queue->size(); -} +bool LocalQueue::empty() const { return impl->empty(); } +size_t LocalQueue::size() const { return impl->size(); } }} // namespace qpid::client diff --git a/cpp/src/qpid/client/LocalQueue.h b/cpp/src/qpid/client/LocalQueue.h deleted file mode 100644 index f81065ef3c..0000000000 --- a/cpp/src/qpid/client/LocalQueue.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef QPID_CLIENT_LOCALQUEUE_H -#define QPID_CLIENT_LOCALQUEUE_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/Message.h" -#include "qpid/client/Demux.h" -#include "qpid/client/AckPolicy.h" -#include "qpid/sys/Time.h" - -namespace qpid { -namespace client { - -/** - * A local queue to collect messages retrieved from a remote broker - * queue. Create a queue and subscribe it using the SubscriptionManager. - * Messages from the remote queue on the broker will be stored in the - * local queue until you retrieve them. - * - * \ingroup clientapi - */ -class LocalQueue -{ - public: - /** Create a local queue. Subscribe the local queue to a remote broker - * queue with a SubscriptionManager. - * - * LocalQueue is an alternative to implementing a MessageListener. - * - *@param ackPolicy Policy for acknowledging messages. @see AckPolicy. - */ - LocalQueue(AckPolicy ackPolicy=AckPolicy()); - - ~LocalQueue(); - - /** Wait up to timeout for the next message from the local queue. - *@param result Set to the message from the queue. - *@param timeout wait up this timeout for a message to appear. - *@return true if result was set, false if queue was empty after timeout. - */ - bool get(Message& result, sys::Duration timeout=0); - - /** Get the next message off the local queue, or wait for a - * message from the broker queue. - *@exception ClosedException if subscription has been closed. - */ - Message get(); - - /** Synonym for get(). */ - Message pop(); - - /** Return true if local queue is empty. */ - bool empty() const; - - /** Number of messages on the local queue */ - size_t size() const; - - /** Set the message acknowledgement policy. @see AckPolicy. */ - void setAckPolicy(AckPolicy); - - /** Get the message acknowledgement policy. @see AckPolicy. */ - AckPolicy& getAckPolicy(); - - private: - Session session; - Demux::QueuePtr queue; - AckPolicy autoAck; - - friend class SubscriptionManager; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_LOCALQUEUE_H*/ diff --git a/cpp/src/qpid/client/LocalQueueImpl.cpp b/cpp/src/qpid/client/LocalQueueImpl.cpp new file mode 100644 index 0000000000..8b191728f4 --- /dev/null +++ b/cpp/src/qpid/client/LocalQueueImpl.cpp @@ -0,0 +1,78 @@ +/* + * + * 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/LocalQueueImpl.h" +#include "qpid/client/MessageImpl.h" +#include "qpid/Exception.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/CompletionImpl.h" + +namespace qpid { +namespace client { + +using namespace framing; + +Message LocalQueueImpl::pop(sys::Duration timeout) { return get(timeout); } + +Message LocalQueueImpl::get(sys::Duration timeout) { + Message result; + bool ok = get(result, timeout); + if (!ok) throw Exception("Timed out waiting for a message"); + return result; +} + +bool LocalQueueImpl::get(Message& result, sys::Duration timeout) { + if (!queue) + throw ClosedException(); + FrameSet::shared_ptr content; + bool ok = queue->pop(content, timeout); + if (!ok) return false; + if (content->isA<MessageTransferBody>()) { + + *MessageImpl::get(result) = MessageImpl(*content); + boost::intrusive_ptr<SubscriptionImpl> si = PrivateImplRef<Subscription>::get(subscription); + assert(si); + if (si) si->received(result); + return true; + } + else + throw CommandInvalidException( + QPID_MSG("Unexpected method: " << content->getMethod())); +} + +bool LocalQueueImpl::empty() const +{ + if (!queue) + throw ClosedException(); + return queue->empty(); +} + +size_t LocalQueueImpl::size() const +{ + if (!queue) + throw ClosedException(); + return queue->size(); +} + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/LocalQueueImpl.h b/cpp/src/qpid/client/LocalQueueImpl.h new file mode 100644 index 0000000000..75b62cf203 --- /dev/null +++ b/cpp/src/qpid/client/LocalQueueImpl.h @@ -0,0 +1,108 @@ +#ifndef QPID_CLIENT_LOCALQUEUEIMPL_H +#define QPID_CLIENT_LOCALQUEUEIMPL_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/ClientImportExport.h" +#include "qpid/client/Handle.h" +#include "qpid/client/Message.h" +#include "qpid/client/Subscription.h" +#include "qpid/client/Demux.h" +#include "qpid/sys/Time.h" +#include "qpid/RefCounted.h" + +namespace qpid { +namespace client { + +/** + * A local queue to collect messages retrieved from a remote broker + * queue. Create a queue and subscribe it using the SubscriptionManager. + * Messages from the remote queue on the broker will be stored in the + * local queue until you retrieve them. + * + * \ingroup clientapi + * + * \details Using a Local Queue + * + * <pre> + * LocalQueue local_queue; + * subscriptions.subscribe(local_queue, string("message_queue")); + * for (int i=0; i<10; i++) { + * Message message = local_queue.get(); + * std::cout << message.getData() << std::endl; + * } + * </pre> + * + * <h2>Getting Messages</h2> + * + * <ul><li> + * <p>get()</p> + * <pre>Message message = local_queue.get();</pre> + * <pre>// Specifying timeouts (TIME_SEC, TIME_MSEC, TIME_USEC, TIME_NSEC) + *#include <qpid/sys/Time.h> + *Message message; + *local_queue.get(message, 5*sys::TIME_SEC);</pre></li></ul> + * + * <h2>Checking size</h2> + * <ul><li> + * <p>empty()</p> + * <pre>if (local_queue.empty()) { ... }</pre></li> + * <li><p>size()</p> + * <pre>std::cout << local_queue.size();</pre></li> + * </ul> + */ + +class LocalQueueImpl : public RefCounted { + public: + /** Wait up to timeout for the next message from the local queue. + *@param result Set to the message from the queue. + *@param timeout wait up this timeout for a message to appear. + *@return true if result was set, false if queue was empty after timeout. + */ + bool get(Message& result, sys::Duration timeout=0); + + /** Get the next message off the local queue, or wait up to the timeout + * for message from the broker queue. + *@param timeout wait up this timeout for a message to appear. + *@return message from the queue. + *@throw ClosedException if subscription is closed or timeout exceeded. + */ + Message get(sys::Duration timeout=sys::TIME_INFINITE); + + /** Synonym for get() */ + Message pop(sys::Duration timeout=sys::TIME_INFINITE); + + /** Return true if local queue is empty. */ + bool empty() const; + + /** Number of messages on the local queue */ + size_t size() const; + + private: + Demux::QueuePtr queue; + Subscription subscription; + friend class SubscriptionManagerImpl; +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_LOCALQUEUEIMPL_H*/ diff --git a/cpp/src/qpid/client/Message.cpp b/cpp/src/qpid/client/Message.cpp index d5464594ee..00f911c57e 100644 --- a/cpp/src/qpid/client/Message.cpp +++ b/cpp/src/qpid/client/Message.cpp @@ -19,55 +19,44 @@ * */ -#include "Message.h" +#include "qpid/client/Message.h" +#include "qpid/client/MessageImpl.h" namespace qpid { namespace client { -Message::Message(const std::string& data_, - const std::string& routingKey, - const std::string& exchange) : TransferContent(data_, routingKey, exchange) {} +Message::Message(MessageImpl* mi) : impl(mi) {} -std::string Message::getDestination() const -{ - return method.getDestination(); -} +Message::Message(const std::string& data, const std::string& routingKey) + : impl(new MessageImpl(data, routingKey)) {} -bool Message::isRedelivered() const -{ - return hasDeliveryProperties() && getDeliveryProperties().getRedelivered(); -} +Message::Message(const Message& m) : impl(new MessageImpl(*m.impl)) {} -void Message::setRedelivered(bool redelivered) -{ - getDeliveryProperties().setRedelivered(redelivered); -} +Message::~Message() { delete impl; } -framing::FieldTable& Message::getHeaders() -{ - return getMessageProperties().getApplicationHeaders(); -} +Message& Message::operator=(const Message& m) { *impl = *m.impl; return *this; } -const framing::FieldTable& Message::getHeaders() const -{ - return getMessageProperties().getApplicationHeaders(); -} +void Message::swap(Message& m) { std::swap(impl, m.impl); } -const framing::MessageTransferBody& Message::getMethod() const -{ - return method; -} +std::string Message::getDestination() const { return impl->getDestination(); } +bool Message::isRedelivered() const { return impl->isRedelivered(); } +void Message::setRedelivered(bool redelivered) { impl->setRedelivered(redelivered); } +framing::FieldTable& Message::getHeaders() { return impl->getHeaders(); } +const framing::FieldTable& Message::getHeaders() const { return impl->getHeaders(); } +const framing::SequenceNumber& Message::getId() const { return impl->getId(); } -const framing::SequenceNumber& Message::getId() const -{ - return id; -} +void Message::setData(const std::string& s) { impl->setData(s); } +const std::string& Message::getData() const { return impl->getData(); } +std::string& Message::getData() { return impl->getData(); } -/**@internal for incoming messages */ -Message::Message(const framing::FrameSet& frameset) : - method(*frameset.as<framing::MessageTransferBody>()), id(frameset.getId()) -{ - populate(frameset); -} +void Message::appendData(const std::string& s) { impl->appendData(s); } + +bool Message::hasMessageProperties() const { return impl->hasMessageProperties(); } +framing::MessageProperties& Message::getMessageProperties() { return impl->getMessageProperties(); } +const framing::MessageProperties& Message::getMessageProperties() const { return impl->getMessageProperties(); } + +bool Message::hasDeliveryProperties() const { return impl->hasDeliveryProperties(); } +framing::DeliveryProperties& Message::getDeliveryProperties() { return impl->getDeliveryProperties(); } +const framing::DeliveryProperties& Message::getDeliveryProperties() const { return impl->getDeliveryProperties(); } }} diff --git a/cpp/src/qpid/client/MessageImpl.cpp b/cpp/src/qpid/client/MessageImpl.cpp new file mode 100644 index 0000000000..865c462b15 --- /dev/null +++ b/cpp/src/qpid/client/MessageImpl.cpp @@ -0,0 +1,71 @@ +/* + * + * 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/MessageImpl.h" + +namespace qpid { +namespace client { + +MessageImpl::MessageImpl(const std::string& data, const std::string& routingKey) : TransferContent(data, routingKey) {} + +std::string MessageImpl::getDestination() const +{ + return method.getDestination(); +} + +bool MessageImpl::isRedelivered() const +{ + return hasDeliveryProperties() && getDeliveryProperties().getRedelivered(); +} + +void MessageImpl::setRedelivered(bool redelivered) +{ + getDeliveryProperties().setRedelivered(redelivered); +} + +framing::FieldTable& MessageImpl::getHeaders() +{ + return getMessageProperties().getApplicationHeaders(); +} + +const framing::FieldTable& MessageImpl::getHeaders() const +{ + return getMessageProperties().getApplicationHeaders(); +} + +const framing::MessageTransferBody& MessageImpl::getMethod() const +{ + return method; +} + +const framing::SequenceNumber& MessageImpl::getId() const +{ + return id; +} + +/**@internal for incoming messages */ +MessageImpl::MessageImpl(const framing::FrameSet& frameset) : + method(*frameset.as<framing::MessageTransferBody>()), id(frameset.getId()) +{ + populate(frameset); +} + +}} diff --git a/cpp/src/qpid/client/Message.h b/cpp/src/qpid/client/MessageImpl.h index 4e6ed49bb4..a64ddd20d8 100644 --- a/cpp/src/qpid/client/Message.h +++ b/cpp/src/qpid/client/MessageImpl.h @@ -1,5 +1,5 @@ -#ifndef _client_Message_h -#define _client_Message_h +#ifndef QPID_CLIENT_MESSAGEIMPL_H +#define QPID_CLIENT_MESSAGEIMPL_H /* * @@ -21,30 +21,24 @@ * under the License. * */ -#include <string> +#include "qpid/client/Message.h" #include "qpid/client/Session.h" #include "qpid/framing/MessageTransferBody.h" #include "qpid/framing/TransferContent.h" +#include <string> namespace qpid { namespace client { -/** - * A message sent to or received from the broker. - * - * \ingroup clientapi - */ -class Message : public framing::TransferContent +class MessageImpl : public framing::TransferContent { public: /** Create a Message. *@param data Data for the message body. *@param routingKey Passed to the exchange that routes the message. - *@param exchange Name of the exchange that should route the message. */ - Message(const std::string& data=std::string(), - const std::string& routingKey=std::string(), - const std::string& exchange=std::string()); + MessageImpl(const std::string& data=std::string(), + const std::string& routingKey=std::string()); /** The destination of messages sent to the broker is the exchange * name. The destination of messages received from the broker is @@ -70,7 +64,10 @@ public: const framing::SequenceNumber& getId() const; /**@internal for incoming messages */ - Message(const framing::FrameSet& frameset); + MessageImpl(const framing::FrameSet& frameset); + + static MessageImpl* get(Message& m) { return m.impl; } + static const MessageImpl* get(const Message& m) { return m.impl; } private: //method and id are only set for received messages: @@ -80,4 +77,4 @@ private: }} -#endif /*!_client_Message_h*/ +#endif /*!QPID_CLIENT_MESSAGEIMPL_H*/ diff --git a/cpp/src/qpid/client/MessageListener.cpp b/cpp/src/qpid/client/MessageListener.cpp index 68ebedeb0d..0f2a71287c 100644 --- a/cpp/src/qpid/client/MessageListener.cpp +++ b/cpp/src/qpid/client/MessageListener.cpp @@ -19,6 +19,6 @@ * */ -#include "MessageListener.h" +#include "qpid/client/MessageListener.h" qpid::client::MessageListener::~MessageListener() {} diff --git a/cpp/src/qpid/client/MessageReplayTracker.cpp b/cpp/src/qpid/client/MessageReplayTracker.cpp new file mode 100644 index 0000000000..079fb1167a --- /dev/null +++ b/cpp/src/qpid/client/MessageReplayTracker.cpp @@ -0,0 +1,78 @@ +/* + * + * 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/MessageReplayTracker.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace client { + +MessageReplayTracker::MessageReplayTracker(uint f) : flushInterval(f), count(0) {} + +void MessageReplayTracker::send(const Message& message, const std::string& destination) +{ + buffer.push_back(ReplayRecord(message, destination)); + buffer.back().send(*this); + if (flushInterval && ++count >= flushInterval) { + checkCompletion(); + if (!buffer.empty()) session.flush(); + } +} +void MessageReplayTracker::init(AsyncSession s) +{ + session = s; +} + +void MessageReplayTracker::replay(AsyncSession s) +{ + session = s; + std::for_each(buffer.begin(), buffer.end(), boost::bind(&ReplayRecord::send, _1, boost::ref(*this))); + session.flush(); + count = 0; +} + +void MessageReplayTracker::setFlushInterval(uint f) +{ + flushInterval = f; +} + +uint MessageReplayTracker::getFlushInterval() +{ + return flushInterval; +} + +void MessageReplayTracker::checkCompletion() +{ + buffer.remove_if(boost::bind(&ReplayRecord::isComplete, _1)); +} + +MessageReplayTracker::ReplayRecord::ReplayRecord(const Message& m, const std::string& d) : message(m), destination(d) {} + +void MessageReplayTracker::ReplayRecord::send(MessageReplayTracker& tracker) +{ + status = tracker.session.messageTransfer(arg::destination=destination, arg::content=message); +} + +bool MessageReplayTracker::ReplayRecord::isComplete() +{ + return status.isComplete(); +} + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/PrivateImplRef.h b/cpp/src/qpid/client/PrivateImplRef.h new file mode 100644 index 0000000000..503a383c31 --- /dev/null +++ b/cpp/src/qpid/client/PrivateImplRef.h @@ -0,0 +1,94 @@ +#ifndef QPID_CLIENT_PRIVATEIMPL_H +#define QPID_CLIENT_PRIVATEIMPL_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/ClientImportExport.h" +#include <boost/intrusive_ptr.hpp> +#include "qpid/RefCounted.h" + +namespace qpid { +namespace client { + +// FIXME aconway 2009-04-24: details! +/** @file + * + * Helper class to implement a class with a private, reference counted + * implementation and reference semantics. + * + * Such classes are used in the public API to hide implementation, they + * should. Example of use: + * + * === Foo.h + * + * template <class T> PrivateImplRef; + * class FooImpl; + * + * Foo : public Handle<FooImpl> { + * public: + * Foo(FooImpl* = 0); + * Foo(const Foo&); + * ~Foo(); + * Foo& operator=(const Foo&); + * + * int fooDo(); // and other Foo functions... + * + * private: + * typedef FooImpl Impl; + * Impl* impl; + * friend class PrivateImplRef<Foo>; + * + * === Foo.cpp + * + * typedef PrivateImplRef<Foo> PI; + * Foo::Foo(FooImpl* p) { PI::ctor(*this, p); } + * Foo::Foo(const Foo& c) : Handle<FooImpl>() { PI::copy(*this, c); } + * Foo::~Foo() { PI::dtor(*this); } + * Foo& Foo::operator=(const Foo& c) { return PI::assign(*this, c); } + * + * int foo::fooDo() { return impl->fooDo(); } + * + */ +template <class T> class PrivateImplRef { + public: + typedef typename T::Impl Impl; + typedef boost::intrusive_ptr<Impl> intrusive_ptr; + + static intrusive_ptr get(const T& t) { return intrusive_ptr(t.impl); } + + static void set(T& t, const intrusive_ptr& p) { + if (t.impl == p) return; + if (t.impl) boost::intrusive_ptr_release(t.impl); + t.impl = p.get(); + if (t.impl) boost::intrusive_ptr_add_ref(t.impl); + } + + // Helper functions to implement the ctor, dtor, copy, assign + static void ctor(T& t, Impl* p) { t.impl = p; if (p) boost::intrusive_ptr_add_ref(p); } + static void copy(T& t, const T& x) { if (&t == &x) return; t.impl = 0; assign(t, x); } + static void dtor(T& t) { if(t.impl) boost::intrusive_ptr_release(t.impl); } + static T& assign(T& t, const T& x) { set(t, get(x)); return t;} +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_PRIVATEIMPL_H*/ diff --git a/cpp/src/qpid/client/QueueOptions.cpp b/cpp/src/qpid/client/QueueOptions.cpp new file mode 100644 index 0000000000..f4c1483859 --- /dev/null +++ b/cpp/src/qpid/client/QueueOptions.cpp @@ -0,0 +1,123 @@ +/* + * + * 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/QueueOptions.h" + +namespace qpid { +namespace client { + +enum QueueEventGeneration {ENQUEUE_ONLY=1, ENQUEUE_AND_DEQUEUE=2}; + + +QueueOptions::QueueOptions() +{} + +const std::string QueueOptions::strMaxCountKey("qpid.max_count"); +const std::string QueueOptions::strMaxSizeKey("qpid.max_size"); +const std::string QueueOptions::strTypeKey("qpid.policy_type"); +const std::string QueueOptions::strREJECT("reject"); +const std::string QueueOptions::strFLOW_TO_DISK("flow_to_disk"); +const std::string QueueOptions::strRING("ring"); +const std::string QueueOptions::strRING_STRICT("ring_strict"); +const std::string QueueOptions::strLastValueQueue("qpid.last_value_queue"); +const std::string QueueOptions::strPersistLastNode("qpid.persist_last_node"); +const std::string QueueOptions::strLVQMatchProperty("qpid.LVQ_key"); +const std::string QueueOptions::strLastValueQueueNoBrowse("qpid.last_value_queue_no_browse"); +const std::string QueueOptions::strQueueEventMode("qpid.queue_event_generation"); + + +QueueOptions::~QueueOptions() +{} + +void QueueOptions::setSizePolicy(QueueSizePolicy sp, uint64_t maxSize, uint32_t maxCount) +{ + if (maxCount) setInt(strMaxCountKey, maxCount); + if (maxSize) setInt(strMaxSizeKey, maxSize); + if (maxSize || maxCount){ + switch (sp) + { + case REJECT: + setString(strTypeKey, strREJECT); + break; + case FLOW_TO_DISK: + setString(strTypeKey, strFLOW_TO_DISK); + break; + case RING: + setString(strTypeKey, strRING); + break; + case RING_STRICT: + setString(strTypeKey, strRING_STRICT); + break; + case NONE: + clearSizePolicy(); + break; + } + } +} + + +void QueueOptions::setPersistLastNode() +{ + setInt(strPersistLastNode, 1); +} + +void QueueOptions::setOrdering(QueueOrderingPolicy op) +{ + if (op == LVQ){ + setInt(strLastValueQueue, 1); + }else if (op == LVQ_NO_BROWSE){ + setInt(strLastValueQueueNoBrowse, 1); + }else { + clearOrdering(); + } +} + +void QueueOptions::getLVQKey(std::string& key) +{ + key.assign(strLVQMatchProperty); +} + +void QueueOptions::clearSizePolicy() +{ + erase(strMaxCountKey); + erase(strMaxSizeKey); + erase(strTypeKey); +} + +void QueueOptions::clearPersistLastNode() +{ + erase(strPersistLastNode); +} + +void QueueOptions::clearOrdering() +{ + erase(strLastValueQueue); +} + +void QueueOptions::enableQueueEvents(bool enqueueOnly) +{ + setInt(strQueueEventMode, enqueueOnly ? ENQUEUE_ONLY : ENQUEUE_AND_DEQUEUE); +} + +} +} + + diff --git a/cpp/src/qpid/client/RdmaConnector.cpp b/cpp/src/qpid/client/RdmaConnector.cpp new file mode 100644 index 0000000000..77169db3a6 --- /dev/null +++ b/cpp/src/qpid/client/RdmaConnector.cpp @@ -0,0 +1,387 @@ +/* + * + * 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/Connector.h" + +#include "qpid/client/Bounds.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/sys/rdma/RdmaIO.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/Msg.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +// This stuff needs to abstracted out of here to a platform specific file +#include <netdb.h> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::framing; +using boost::format; +using boost::str; + + class RdmaConnector : public Connector, public sys::Codec, private sys::Runnable +{ + struct Buff; + + typedef Rdma::Buffer BufferBase; + typedef std::deque<framing::AMQFrame> Frames; + + const uint16_t maxFrameSize; + sys::Mutex lock; + Frames frames; + size_t lastEof; // Position after last EOF in frames + uint64_t currentSize; + Bounds* bounds; + + + framing::ProtocolVersion version; + bool initiated; + + sys::Mutex pollingLock; + bool polling; + bool joined; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + sys::Thread receiver; + + Rdma::AsynchIO* aio; + sys::Poller::shared_ptr poller; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + + ~RdmaConnector(); + + void run(); + void handleClosed(); + bool closeInternal(); + + // Callbacks + void connected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&); + void connectionError(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, Rdma::ErrorType); + void disconnected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&); + void rejected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&); + + void readbuff(Rdma::AsynchIO&, Rdma::Buffer*); + void writebuff(Rdma::AsynchIO&); + void writeDataBlock(const framing::AMQDataBlock& data); + void eof(Rdma::AsynchIO&); + + std::string identifier; + + ConnectionImpl* impl; + + void connect(const std::string& host, int port); + void close(); + void send(framing::AMQFrame& frame); + void abort() {} // TODO: need to fix this for heartbeat timeouts to work + + void setInputHandler(framing::InputHandler* handler); + void setShutdownHandler(sys::ShutdownHandler* handler); + sys::ShutdownHandler* getShutdownHandler() const; + framing::OutputHandler* getOutputHandler(); + const std::string& getIdentifier() const; + void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + +public: + RdmaConnector(framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); + unsigned int getSSF() { return 0; } +}; + +// Static constructor which registers connector here +namespace { + Connector* create(framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new RdmaConnector(v, s, c); + } + + struct StaticInit { + StaticInit() { + Connector::registerFactory("rdma", &create); + Connector::registerFactory("ib", &create); + }; + } init; +} + + +RdmaConnector::RdmaConnector(ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + lastEof(0), + currentSize(0), + bounds(cimpl), + version(ver), + initiated(false), + polling(false), + joined(true), + shutdownHandler(0), + aio(0), + impl(cimpl) +{ + QPID_LOG(debug, "RdmaConnector created for " << version); +} + +RdmaConnector::~RdmaConnector() { + close(); +} + +void RdmaConnector::connect(const std::string& host, int port){ + Mutex::ScopedLock l(pollingLock); + assert(!polling); + assert(joined); + poller = Poller::shared_ptr(new Poller); + + SocketAddress sa(host, boost::lexical_cast<std::string>(port)); + Rdma::Connector* c = new Rdma::Connector( + sa, + Rdma::ConnectionParams(maxFrameSize, Rdma::DEFAULT_WR_ENTRIES), + boost::bind(&RdmaConnector::connected, this, poller, _1, _2), + boost::bind(&RdmaConnector::connectionError, this, poller, _1, _2), + boost::bind(&RdmaConnector::disconnected, this, poller, _1), + boost::bind(&RdmaConnector::rejected, this, poller, _1, _2)); + c->start(poller); + + polling = true; + joined = false; + receiver = Thread(this); +} + +// The following only gets run when connected +void RdmaConnector::connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp) { + Rdma::QueuePair::intrusive_ptr q = ci->getQueuePair(); + + aio = new Rdma::AsynchIO(ci->getQueuePair(), + cp.maxRecvBufferSize, cp.initialXmitCredit , Rdma::DEFAULT_WR_ENTRIES, + boost::bind(&RdmaConnector::readbuff, this, _1, _2), + boost::bind(&RdmaConnector::writebuff, this, _1), + 0, // write buffers full + boost::bind(&RdmaConnector::eof, this, _1)); // data error - just close connection + aio->start(poller); + + identifier = str(format("[%1% %2%]") % ci->getLocalName() % ci->getPeerName()); + ProtocolInitiation init(version); + writeDataBlock(init); +} + +void RdmaConnector::connectionError(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, Rdma::ErrorType) { + QPID_LOG(trace, "Connection Error " << identifier); + eof(*aio); +} + +void RdmaConnector::disconnected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&) { + eof(*aio); +} + +void RdmaConnector::rejected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams& cp) { + QPID_LOG(trace, "Connection Rejected " << identifier << ": " << cp.maxRecvBufferSize); + eof(*aio); +} + +bool RdmaConnector::closeInternal() { + bool ret; + { + Mutex::ScopedLock l(pollingLock); + ret = polling; + if (polling) { + polling = false; + poller->shutdown(); + } + if (joined || receiver.id() == Thread::current().id()) { + return ret; + } + joined = true; + } + + receiver.join(); + return ret; +} + +void RdmaConnector::close() { + closeInternal(); +} + +void RdmaConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void RdmaConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* RdmaConnector::getOutputHandler(){ + return this; +} + +sys::ShutdownHandler* RdmaConnector::getShutdownHandler() const { + return shutdownHandler; +} + +const std::string& RdmaConnector::getIdentifier() const { + return identifier; +} + +void RdmaConnector::send(AMQFrame& frame) { + bool notifyWrite = false; + { + Mutex::ScopedLock l(lock); + frames.push_back(frame); + //only ask to write if this is the end of a frameset or if we + //already have a buffers worth of data + currentSize += frame.encodedSize(); + if (frame.getEof()) { + lastEof = frames.size(); + notifyWrite = true; + } else { + notifyWrite = (currentSize >= maxFrameSize); + } + } + if (notifyWrite) aio->notifyPendingWrite(); +} + +void RdmaConnector::handleClosed() { + if (closeInternal() && shutdownHandler) + shutdownHandler->shutdown(); +} + +// Called in IO thread. (write idle routine) +// This is NOT only called in response to previously calling notifyPendingWrite +void RdmaConnector::writebuff(Rdma::AsynchIO&) { + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + if (codec->canEncode()) { + std::auto_ptr<BufferBase> buffer = std::auto_ptr<BufferBase>(aio->getBuffer()); + size_t encoded = codec->encode(buffer->bytes, buffer->byteCount); + + buffer->dataStart = 0; + buffer->dataCount = encoded; + aio->queueWrite(buffer.release()); + } +} + +bool RdmaConnector::canEncode() +{ + Mutex::ScopedLock l(lock); + //have at least one full frameset or a whole buffers worth of data + return aio->writable() && aio->bufferAvailable() && (lastEof || currentSize >= maxFrameSize); +} + +size_t RdmaConnector::encode(const char* buffer, size_t size) +{ + framing::Buffer out(const_cast<char*>(buffer), size); + size_t bytesWritten(0); + { + Mutex::ScopedLock l(lock); + while (!frames.empty() && out.available() >= frames.front().encodedSize() ) { + frames.front().encode(out); + QPID_LOG(trace, "SENT " << identifier << ": " << frames.front()); + frames.pop_front(); + if (lastEof) --lastEof; + } + bytesWritten = size - out.available(); + currentSize -= bytesWritten; + } + if (bounds) bounds->reduce(bytesWritten); + return bytesWritten; +} + +void RdmaConnector::readbuff(Rdma::AsynchIO&, Rdma::Buffer* buff) { + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + codec->decode(buff->bytes+buff->dataStart, buff->dataCount); +} + +size_t RdmaConnector::decode(const char* buffer, size_t size) +{ + framing::Buffer in(const_cast<char*>(buffer), size); + if (!initiated) { + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + //TODO: check the version is correct + QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); + } + initiated = true; + } + AMQFrame frame; + while(frame.decode(in)){ + QPID_LOG(trace, "RECV " << identifier << ": " << frame); + input->received(frame); + } + return size - in.available(); +} + +void RdmaConnector::writeDataBlock(const AMQDataBlock& data) { + Rdma::Buffer* buff = aio->getBuffer(); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void RdmaConnector::eof(Rdma::AsynchIO&) { + handleClosed(); +} + +void RdmaConnector::run(){ + // Keep the connection impl in memory until run() completes. + //GRS: currently the ConnectionImpls destructor is where the Io thread is joined + //boost::shared_ptr<ConnectionImpl> protect = impl->shared_from_this(); + //assert(protect); + try { + Dispatcher d(poller); + + //aio->start(poller); + d.run(); + //aio->queueForDeletion(); + } catch (const std::exception& e) { + { + // We're no longer polling + Mutex::ScopedLock l(pollingLock); + polling = false; + } + QPID_LOG(error, e.what()); + handleClosed(); + } +} + +void RdmaConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl) +{ + securityLayer = sl; + securityLayer->init(this); +} + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/Results.cpp b/cpp/src/qpid/client/Results.cpp index c605af2878..0de3e8bd04 100644 --- a/cpp/src/qpid/client/Results.cpp +++ b/cpp/src/qpid/client/Results.cpp @@ -19,18 +19,21 @@ * */ -#include "Results.h" -#include "FutureResult.h" +#include "qpid/client/Results.h" +#include "qpid/client/FutureResult.h" #include "qpid/framing/SequenceSet.h" using namespace qpid::framing; -using namespace boost; namespace qpid { namespace client { Results::Results() {} +Results::~Results() { + try { close(); } catch (const std::exception& /*e*/) { assert(0); } +} + void Results::close() { for (Listeners::iterator i = listeners.begin(); i != listeners.end(); i++) { diff --git a/cpp/src/qpid/client/Results.h b/cpp/src/qpid/client/Results.h index 667f35089c..4c49f6b05b 100644 --- a/cpp/src/qpid/client/Results.h +++ b/cpp/src/qpid/client/Results.h @@ -38,6 +38,7 @@ public: typedef boost::shared_ptr<FutureResult> FutureResultPtr; Results(); + ~Results(); void completed(const framing::SequenceSet& set); void received(const framing::SequenceNumber& id, const std::string& result); FutureResultPtr listenForResult(const framing::SequenceNumber& point); diff --git a/cpp/src/qpid/client/Sasl.h b/cpp/src/qpid/client/Sasl.h new file mode 100644 index 0000000000..63da37fcb1 --- /dev/null +++ b/cpp/src/qpid/client/Sasl.h @@ -0,0 +1,70 @@ +#ifndef QPID_CLIENT_SASL_H +#define QPID_CLIENT_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 <memory> +#include <string> +#include "qpid/sys/IntegerTypes.h" + +namespace qpid { + +namespace sys { +class SecurityLayer; +} + +namespace client { + +struct ConnectionSettings; + +/** + * Interface to SASL support. This class is implemented by platform-specific + * SASL providers. + */ +class Sasl +{ + public: + /** + * Start SASL negotiation with the broker. + * + * @param mechanisms Comma-separated list of the SASL mechanism the + * client supports. + * @param ssf Security Strength Factor (SSF). SSF is used to negotiate + * a SASL security layer on top of the connection should both + * parties require and support it. The value indicates the + * required level of security for communication. Possible + * values are: + * @li 0 No security + * @li 1 Integrity checking only + * @li >1 Integrity and confidentiality with the number + * giving the encryption key length. + */ + virtual std::string start(const std::string& mechanisms, unsigned int ssf) = 0; + virtual std::string step(const std::string& challenge) = 0; + virtual std::string getMechanism() = 0; + virtual std::string getUserId() = 0; + virtual std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(uint16_t maxFrameSize) = 0; + virtual ~Sasl() {} +}; +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SASL_H*/ diff --git a/cpp/src/qpid/client/SaslFactory.cpp b/cpp/src/qpid/client/SaslFactory.cpp new file mode 100644 index 0000000000..5012b75c94 --- /dev/null +++ b/cpp/src/qpid/client/SaslFactory.cpp @@ -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. + * + */ +#include "qpid/client/SaslFactory.h" +#include "qpid/client/ConnectionSettings.h" +#include <map> + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef HAVE_SASL + +namespace qpid { +namespace client { + +//Null implementation + +SaslFactory::SaslFactory() {} + +SaslFactory::~SaslFactory() {} + +SaslFactory& SaslFactory::getInstance() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (!instance.get()) { + instance = std::auto_ptr<SaslFactory>(new SaslFactory()); + } + return *instance; +} + +std::auto_ptr<Sasl> SaslFactory::create(const ConnectionSettings&) +{ + return std::auto_ptr<Sasl>(); +} + +qpid::sys::Mutex SaslFactory::lock; +std::auto_ptr<SaslFactory> SaslFactory::instance; + +}} // namespace qpid::client + +#else + +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/cyrus/CyrusSecurityLayer.h" +#include "qpid/log/Statement.h" +#include <sasl/sasl.h> +#include <strings.h> + +namespace qpid { +namespace client { + +using qpid::sys::SecurityLayer; +using qpid::sys::cyrus::CyrusSecurityLayer; +using qpid::framing::InternalErrorException; + +const size_t MAX_LOGIN_LENGTH = 50; + +class CyrusSasl : public Sasl +{ + public: + CyrusSasl(const ConnectionSettings&); + ~CyrusSasl(); + std::string start(const std::string& mechanisms, unsigned int ssf); + std::string step(const std::string& challenge); + std::string getMechanism(); + std::string getUserId(); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); + private: + sasl_conn_t* conn; + sasl_callback_t callbacks[5];//realm, user, authname, password, end-of-list + ConnectionSettings settings; + std::string input; + std::string mechanism; + char login[MAX_LOGIN_LENGTH]; + + void interact(sasl_interact_t* client_interact); +}; + +//sasl callback functions +int getUserFromSettings(void *context, int id, const char **result, unsigned *len); +int getPasswordFromSettings(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret); +typedef int CallbackProc(); + +qpid::sys::Mutex SaslFactory::lock; +std::auto_ptr<SaslFactory> SaslFactory::instance; + +SaslFactory::SaslFactory() +{ + sasl_callback_t* callbacks = 0; + int result = sasl_client_init(callbacks); + if (result != SASL_OK) { + throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errstring(result, 0, 0))); + } +} + +SaslFactory::~SaslFactory() +{ + sasl_done(); +} + +SaslFactory& SaslFactory::getInstance() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (!instance.get()) { + instance = std::auto_ptr<SaslFactory>(new SaslFactory()); + } + return *instance; +} + +std::auto_ptr<Sasl> SaslFactory::create(const ConnectionSettings& settings) +{ + std::auto_ptr<Sasl> sasl(new CyrusSasl(settings)); + return sasl; +} + +CyrusSasl::CyrusSasl(const ConnectionSettings& s) : conn(0), settings(s) +{ + size_t i = 0; + + callbacks[i].id = SASL_CB_GETREALM; + callbacks[i].proc = 0; + callbacks[i++].context = 0; + + if (!settings.username.empty()) { + callbacks[i].id = SASL_CB_USER; + callbacks[i].proc = (CallbackProc*) &getUserFromSettings; + callbacks[i++].context = &settings; + + callbacks[i].id = SASL_CB_AUTHNAME; + callbacks[i].proc = (CallbackProc*) &getUserFromSettings; + callbacks[i++].context = &settings; + } + + callbacks[i].id = SASL_CB_PASS; + if (settings.password.empty()) { + callbacks[i].proc = 0; + callbacks[i++].context = 0; + } else { + callbacks[i].proc = (CallbackProc*) &getPasswordFromSettings; + callbacks[i++].context = &settings; + } + + callbacks[i].id = SASL_CB_LIST_END; + callbacks[i].proc = 0; + callbacks[i++].context = 0; +} + +CyrusSasl::~CyrusSasl() +{ + if (conn) { + sasl_dispose(&conn); + } +} + +namespace { + const std::string SSL("ssl"); +} + +std::string CyrusSasl::start(const std::string& mechanisms, unsigned int ssf) +{ + QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << ")"); + int result = sasl_client_new(settings.service.c_str(), + settings.host.c_str(), + 0, 0, /* Local and remote IP address strings */ + callbacks, + 0, /* security flags */ + &conn); + + if (result != SASL_OK) throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); + + sasl_security_properties_t secprops; + + if (ssf) { + sasl_ssf_t external_ssf = (sasl_ssf_t) ssf; + if (external_ssf) { + int result = sasl_setprop(conn, SASL_SSF_EXTERNAL, &external_ssf); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result)); + } + QPID_LOG(debug, "external SSF detected and set to " << ssf); + } + } + + secprops.min_ssf = settings.minSsf; + secprops.max_ssf = settings.maxSsf; + secprops.maxbufsize = 65535; + + QPID_LOG(debug, "min_ssf: " << secprops.min_ssf << ", max_ssf: " << secprops.max_ssf); + + secprops.property_names = 0; + secprops.property_values = 0; + secprops.security_flags = 0;//TODO: provide means for application to configure these + + result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn))); + } + + + sasl_interact_t* client_interact = 0; + const char *out = 0; + unsigned outlen = 0; + const char *chosenMechanism = 0; + + do { + result = sasl_client_start(conn, + mechanisms.c_str(), + &client_interact, + &out, + &outlen, + &chosenMechanism); + + if (result == SASL_INTERACT) { + interact(client_interact); + } + } while (result == SASL_INTERACT); + + if (result != SASL_CONTINUE && result != SASL_OK) { + throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); + } + + mechanism = std::string(chosenMechanism); + QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << "): selected " + << mechanism << " response: '" << std::string(out, outlen) << "'"); + return std::string(out, outlen); +} + +std::string CyrusSasl::step(const std::string& challenge) +{ + sasl_interact_t* client_interact = 0; + const char *out = 0; + unsigned outlen = 0; + int result = 0; + do { + result = sasl_client_step(conn, /* our context */ + challenge.data(), /* the data from the server */ + challenge.size(), /* it's length */ + &client_interact, /* this should be + unallocated and NULL */ + &out, /* filled in on success */ + &outlen); /* filled in on success */ + + if (result == SASL_INTERACT) { + interact(client_interact); + } + } while (result == SASL_INTERACT); + + std::string response; + if (result == SASL_CONTINUE || result == SASL_OK) response = std::string(out, outlen); + else if (result != SASL_OK) { + throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); + } + QPID_LOG(debug, "CyrusSasl::step(" << challenge << "): " << response); + return response; +} + +std::string CyrusSasl::getMechanism() +{ + return mechanism; +} + +std::string CyrusSasl::getUserId() +{ + int propResult; + const void* operName; + + propResult = sasl_getprop(conn, SASL_USERNAME, &operName); + if (propResult == SASL_OK) + return std::string((const char*) operName); + + return std::string(); +} + +void CyrusSasl::interact(sasl_interact_t* client_interact) +{ + + if (client_interact->id == SASL_CB_PASS) { + char* password = getpass(client_interact->prompt); + input = std::string(password); + client_interact->result = input.data(); + client_interact->len = input.size(); + } else { + std::cout << client_interact->prompt; + if (client_interact->defresult) std::cout << " (" << client_interact->defresult << ")"; + std::cout << ": "; + if (std::cin >> input) { + client_interact->result = input.data(); + client_interact->len = input.size(); + } + } + +} + +std::auto_ptr<SecurityLayer> CyrusSasl::getSecurityLayer(uint16_t maxFrameSize) +{ + const void* value(0); + int result = sasl_getprop(conn, SASL_SSF, &value); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn))); + } + uint ssf = *(reinterpret_cast<const unsigned*>(value)); + std::auto_ptr<SecurityLayer> securityLayer; + if (ssf) { + QPID_LOG(info, "Installing security layer, SSF: "<< ssf); + securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(conn, maxFrameSize)); + } + return securityLayer; +} + +int getUserFromSettings(void* context, int /*id*/, const char** result, unsigned* /*len*/) +{ + if (context) { + *result = ((ConnectionSettings*) context)->username.c_str(); + QPID_LOG(debug, "getUserFromSettings(): " << (*result)); + return SASL_OK; + } else { + return SASL_FAIL; + } +} + +namespace { +// Global map of secrest allocated for SASL connections via callback +// to getPasswordFromSettings. Ensures secrets are freed. +class SecretsMap { + typedef std::map<sasl_conn_t*, void*> Map; + Map map; + public: + void keep(sasl_conn_t* conn, void* secret) { + Map::iterator i = map.find(conn); + if (i != map.end()) free(i->second); + map[conn] = secret; + } + + ~SecretsMap() { + for (Map::iterator i = map.begin(); i != map.end(); ++i) + free(i->second); + } +}; +SecretsMap getPasswordFromSettingsSecrets; +} + +int getPasswordFromSettings(sasl_conn_t* conn, void* context, int /*id*/, sasl_secret_t** psecret) +{ + if (context) { + size_t length = ((ConnectionSettings*) context)->password.size(); + sasl_secret_t* secret = (sasl_secret_t*) malloc(sizeof(sasl_secret_t) + length); + getPasswordFromSettingsSecrets.keep(conn, secret); + secret->len = length; + memcpy(secret->data, ((ConnectionSettings*) context)->password.data(), length); + *psecret = secret; + return SASL_OK; + } else { + return SASL_FAIL; + } +} + +}} // namespace qpid::client + +#endif diff --git a/cpp/src/qpid/client/SaslFactory.h b/cpp/src/qpid/client/SaslFactory.h new file mode 100644 index 0000000000..d012af06f7 --- /dev/null +++ b/cpp/src/qpid/client/SaslFactory.h @@ -0,0 +1,48 @@ +#ifndef QPID_CLIENT_SASLFACTORY_H +#define QPID_CLIENT_SASLFACTORY_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/Sasl.h" +#include "qpid/sys/Mutex.h" +#include <memory> + +namespace qpid { +namespace client { + +/** + * Factory for instances of the Sasl interface through which Sasl + * support is provided to a ConnectionHandler. + */ +class SaslFactory +{ + public: + std::auto_ptr<Sasl> create(const ConnectionSettings&); + static SaslFactory& getInstance(); + ~SaslFactory(); + private: + SaslFactory(); + static qpid::sys::Mutex lock; + static std::auto_ptr<SaslFactory> instance; +}; +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SASLFACTORY_H*/ diff --git a/cpp/src/qpid/client/SessionBase_0_10.cpp b/cpp/src/qpid/client/SessionBase_0_10.cpp index 974acbfcf6..1a345d534e 100644 --- a/cpp/src/qpid/client/SessionBase_0_10.cpp +++ b/cpp/src/qpid/client/SessionBase_0_10.cpp @@ -18,21 +18,23 @@ * under the License. * */ -#include "SessionBase_0_10.h" +#include "qpid/client/SessionBase_0_10.h" +#include "qpid/client/Connection.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/Future.h" #include "qpid/framing/all_method_bodies.h" namespace qpid { namespace client { + using namespace framing; SessionBase_0_10::SessionBase_0_10() {} SessionBase_0_10::~SessionBase_0_10() {} -void SessionBase_0_10::close() { impl->close(); } - -Execution& SessionBase_0_10::getExecution() -{ - return *impl; +void SessionBase_0_10::close() +{ + if (impl) impl->close(); } void SessionBase_0_10::flush() @@ -47,6 +49,11 @@ void SessionBase_0_10::sync() impl->send(b).wait(*impl); } +void SessionBase_0_10::markCompleted(const framing::SequenceSet& ids, bool notifyPeer) +{ + impl->markCompleted(ids, notifyPeer); +} + void SessionBase_0_10::markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer) { impl->markCompleted(id, cumulative, notifyPeer); @@ -57,8 +64,14 @@ void SessionBase_0_10::sendCompletion() impl->sendCompletion(); } +uint16_t SessionBase_0_10::getChannel() const { return impl->getChannel(); } + +void SessionBase_0_10::suspend() { impl->suspend(); } +void SessionBase_0_10::resume(Connection c) { impl->resume(c.impl); } +uint32_t SessionBase_0_10::timeout(uint32_t seconds) { return impl->setTimeout(seconds); } + SessionId SessionBase_0_10::getId() const { return impl->getId(); } -framing::FrameSet::shared_ptr SessionBase_0_10::get() { return impl->get(); } +bool SessionBase_0_10::isValid() const { return impl; } }} // namespace qpid::client diff --git a/cpp/src/qpid/client/SessionBase_0_10.h b/cpp/src/qpid/client/SessionBase_0_10.h deleted file mode 100644 index 8634164dd1..0000000000 --- a/cpp/src/qpid/client/SessionBase_0_10.h +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef QPID_CLIENT_SESSIONBASE_H -#define QPID_CLIENT_SESSIONBASE_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/SessionId.h" -#include "qpid/framing/amqp_structs.h" -#include "qpid/framing/ProtocolVersion.h" -#include "qpid/framing/MethodContent.h" -#include "qpid/framing/TransferContent.h" -#include "qpid/client/Completion.h" -#include "qpid/client/ConnectionImpl.h" -#include "qpid/client/Execution.h" -#include "qpid/client/SessionImpl.h" -#include "qpid/client/TypedResult.h" -#include "qpid/shared_ptr.h" -#include <string> - -namespace qpid { -namespace client { - -using std::string; -using framing::Content; -using framing::FieldTable; -using framing::MethodContent; -using framing::SequenceNumber; -using framing::SequenceSet; -using framing::SequenceNumberSet; -using qpid::SessionId; -using framing::Xid; - -/** Unit of message credit: messages or bytes */ -enum CreditUnit { MESSAGE_CREDIT=0, BYTE_CREDIT=1, UNLIMITED_CREDIT=0xFFFFFFFF }; - -/** - * Base class for handles to an AMQP session. - * - * Subclasses provide the AMQP commands for a given - * version of the protocol. - */ -class SessionBase_0_10 { - public: - - typedef framing::TransferContent DefaultContent; - - ///@internal - SessionBase_0_10(); - ~SessionBase_0_10(); - - /** Get the next message frame-set from the session. */ - framing::FrameSet::shared_ptr get(); - - /** Get the session ID */ - SessionId getId() const; - - /** Close the session. - * A session is automatically closed when all handles to it are destroyed. - */ - void close(); - - /** - * Synchronize the session: sync() waits until all commands issued - * on this session so far have been completed by the broker. - * - * Note sync() is always synchronous, even on an AsyncSession object - * because that's almost always what you want. You can call - * AsyncSession::executionSync() directly in the unusual event - * that you want to do an asynchronous sync. - */ - void sync(); - - /** Set the timeout for this session. */ - uint32_t timeout(uint32_t seconds); - - Execution& getExecution(); - void flush(); - void markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer); - void sendCompletion(); - - protected: - boost::shared_ptr<SessionImpl> impl; - friend class SessionBase_0_10Access; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_SESSIONBASE_H*/ diff --git a/cpp/src/qpid/client/SessionBase_0_10Access.h b/cpp/src/qpid/client/SessionBase_0_10Access.h index e2189a53dd..4d08a7ceaf 100644 --- a/cpp/src/qpid/client/SessionBase_0_10Access.h +++ b/cpp/src/qpid/client/SessionBase_0_10Access.h @@ -33,7 +33,7 @@ class SessionBase_0_10Access { public: SessionBase_0_10Access(SessionBase_0_10& sb_) : sb(sb_) {} void set(const boost::shared_ptr<SessionImpl>& si) { sb.impl = si; } - boost::shared_ptr<SessionImpl> get() { return sb.impl; } + boost::shared_ptr<SessionImpl> get() const { return sb.impl; } private: SessionBase_0_10& sb; }; diff --git a/cpp/src/qpid/client/SessionImpl.cpp b/cpp/src/qpid/client/SessionImpl.cpp index 2075ed04f3..0f767c9f2e 100644 --- a/cpp/src/qpid/client/SessionImpl.cpp +++ b/cpp/src/qpid/client/SessionImpl.cpp @@ -19,21 +19,25 @@ * */ -#include "SessionImpl.h" +#include "qpid/client/SessionImpl.h" -#include "ConnectionImpl.h" -#include "Future.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/Future.h" #include "qpid/framing/all_method_bodies.h" #include "qpid/framing/ClientInvoker.h" -#include "qpid/framing/constants.h" +#include "qpid/framing/enum.h" #include "qpid/framing/FrameSet.h" +#include "qpid/framing/AMQFrame.h" #include "qpid/framing/MethodContent.h" #include "qpid/framing/SequenceSet.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/DeliveryProperties.h" #include "qpid/log/Statement.h" +#include "qpid/sys/IntegerTypes.h" #include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> namespace { const std::string EMPTY; } @@ -41,29 +45,26 @@ namespace qpid { namespace client { using namespace qpid::framing; -using namespace qpid::framing::session;//for detach codes +using namespace qpid::framing::session; //for detach codes typedef sys::Monitor::ScopedLock Lock; typedef sys::Monitor::ScopedUnlock UnLock; typedef sys::ScopedLock<sys::Semaphore> Acquire; -SessionImpl::SessionImpl(const std::string& name, - shared_ptr<ConnectionImpl> conn, - uint16_t ch, uint64_t _maxFrameSize) - : error(OK), - code(NORMAL), - text(EMPTY), - state(INACTIVE), +SessionImpl::SessionImpl(const std::string& name, boost::shared_ptr<ConnectionImpl> conn) + : state(INACTIVE), detachedLifetime(0), - maxFrameSize(_maxFrameSize), + maxFrameSize(conn->getNegotiatedSettings().maxFrameSize), id(conn->getNegotiatedSettings().username, name.empty() ? Uuid(true).str() : name), connection(conn), ioHandler(*this), - channel(ch), proxy(ioHandler), nextIn(0), - nextOut(0) + nextOut(0), + sendMsgCredit(0), + doClearDeliveryPropertiesExchange(true), + autoDetach(true) { channel.next = connection.get(); } @@ -71,12 +72,18 @@ SessionImpl::SessionImpl(const std::string& name, SessionImpl::~SessionImpl() { { Lock l(state); - if (state != DETACHED) { - QPID_LOG(error, "Session was not closed cleanly"); + if (state != DETACHED && state != DETACHING) { + QPID_LOG(warning, "Session was not closed cleanly: " << id); + try { + // Inform broker but don't wait for detached as that deadlocks. + // The detached will be ignored as the channel will be invalid. + if (autoDetach) detach(); + } catch (...) {} // ignore errors. setState(DETACHED); handleClosed(); state.waitWaiters(); } + delete sendMsgCredit; } connection->erase(channel); } @@ -102,7 +109,7 @@ void SessionImpl::open(uint32_t timeout) // user thread waitFor(ATTACHED); //TODO: timeout will not be set locally until get response to //confirm, should we wait for that? - proxy.requestTimeout(timeout); + setTimeout(timeout); proxy.commandPoint(nextOut, 0); } else { throw Exception("Open already called for this session"); @@ -112,16 +119,18 @@ void SessionImpl::open(uint32_t timeout) // user thread void SessionImpl::close() //user thread { Lock l(state); - if (detachedLifetime) { - proxy.requestTimeout(0); - //should we wait for the timeout response? - detachedLifetime = 0; + // close() must be idempotent and no-throw as it will often be called in destructors. + if (state != DETACHED && state != DETACHING) { + try { + if (detachedLifetime) setTimeout(0); + detach(); + waitFor(DETACHED); + } catch (...) {} + setState(DETACHED); } - detach(); - waitFor(DETACHED); } -void SessionImpl::resume(shared_ptr<ConnectionImpl>) // user thread +void SessionImpl::resume(boost::shared_ptr<ConnectionImpl>) // user thread { throw NotImplementedException("Resume not yet implemented by client!"); } @@ -135,8 +144,8 @@ void SessionImpl::suspend() //user thread void SessionImpl::detach() //call with lock held { if (state == ATTACHED) { - proxy.detach(id.getName()); setState(DETACHING); + proxy.detach(id.getName()); } } @@ -201,6 +210,16 @@ bool SessionImpl::isCompleteUpTo(const SequenceNumber& id) return f.result; } +framing::SequenceNumber SessionImpl::getCompleteUpTo() +{ + SequenceNumber firstIncomplete; + { + Lock l(state); + firstIncomplete = incompleteIn.front(); + } + return --firstIncomplete; +} + struct MarkCompleted { const SequenceNumber& id; @@ -219,6 +238,16 @@ struct MarkCompleted }; +void SessionImpl::markCompleted(const SequenceSet& ids, bool notifyPeer) +{ + Lock l(state); + incompleteIn.remove(ids); + completedIn.add(ids); + if (notifyPeer) { + sendCompletion(); + } +} + void SessionImpl::markCompleted(const SequenceNumber& id, bool cumulative, bool notifyPeer) { Lock l(state); @@ -239,17 +268,22 @@ void SessionImpl::markCompleted(const SequenceNumber& id, bool cumulative, bool } } +void SessionImpl::setException(const sys::ExceptionHolder& ex) { + Lock l(state); + setExceptionLH(ex); +} + +void SessionImpl::setExceptionLH(const sys::ExceptionHolder& ex) { // Call with lock held. + exceptionHolder = ex; + setState(DETACHED); +} + /** * Called by ConnectionImpl to notify active sessions when connection * is explictly closed */ -void SessionImpl::connectionClosed(uint16_t _code, const std::string& _text) -{ - Lock l(state); - error = CONNECTION_CLOSE; - code = _code; - text = _text; - setState(DETACHED); +void SessionImpl::connectionClosed(uint16_t code, const std::string& text) { + setException(createConnectionException(code, text)); handleClosed(); } @@ -257,9 +291,9 @@ void SessionImpl::connectionClosed(uint16_t _code, const std::string& _text) * Called by ConnectionImpl to notify active sessions when connection * is disconnected */ -void SessionImpl::connectionBroke(uint16_t _code, const std::string& _text) -{ - connectionClosed(_code, _text); +void SessionImpl::connectionBroke(const std::string& _text) { + setException(sys::ExceptionHolder(new TransportFailure(_text))); + handleClosed(); } Future SessionImpl::send(const AMQBody& command) @@ -272,8 +306,76 @@ Future SessionImpl::send(const AMQBody& command, const MethodContent& content) return sendCommand(command, &content); } +namespace { +// Functor for FrameSet::map to send header + content frames but, not method frames. +struct SendContentFn { + FrameHandler& handler; + void operator()(const AMQFrame& f) { + 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()) {} + + AMQHeaderBody getHeader() const + { + return header; + } + const std::string& getData() const + { + return content; + } +}; + +} + +Future SessionImpl::send(const AMQBody& command, const FrameSet& content, bool reframe) { + Acquire a(sendLock); + SequenceNumber id = nextOut++; + { + Lock l(state); + checkOpen(); + incompleteOut.add(id); + } + Future f(id); + if (command.getMethod()->resultExpected()) { + Lock l(state); + //result listener must be set before the command is sent + f.setFutureResult(results.listenForResult(id)); + } + AMQFrame frame(command); + frame.setEof(false); + handleOut(frame); + + if (reframe) { + MethodContentAdaptor c(content); + sendContent(c); + } else { + SendContentFn send(out); + content.map(send); + } + return f; +} + +void SessionImpl::sendRawFrame(AMQFrame& frame) { + Acquire a(sendLock); + handleOut(frame); +} + Future SessionImpl::sendCommand(const AMQBody& command, const MethodContent* content) { + // Only message transfers have content + if (content && sendMsgCredit) { + sendMsgCredit->acquire(); + } Acquire a(sendLock); SequenceNumber id = nextOut++; { @@ -297,9 +399,21 @@ Future SessionImpl::sendCommand(const AMQBody& command, const MethodContent* con } return f; } + 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){ @@ -309,7 +423,7 @@ void SessionImpl::sendContent(const MethodContent& content) const uint32_t frag_size = maxFrameSize - AMQFrame::frameOverhead(); if(data_length < frag_size){ - AMQFrame frame(in_place<AMQContentBody>(content.getData())); + AMQFrame frame((AMQContentBody(content.getData()))); frame.setFirstSegment(false); handleOut(frame); }else{ @@ -318,7 +432,7 @@ void SessionImpl::sendContent(const MethodContent& content) while (remaining > 0) { uint32_t length = remaining > frag_size ? frag_size : remaining; string frag(content.getData().substr(offset, length)); - AMQFrame frame(in_place<AMQContentBody>(frag)); + AMQFrame frame((AMQContentBody(frag))); frame.setFirstSegment(false); frame.setLastSegment(true); if (offset > 0) { @@ -359,37 +473,45 @@ bool isContentFrame(AMQFrame& frame) void SessionImpl::handleIn(AMQFrame& frame) // network thread { try { - if (!invoke(static_cast<SessionHandler&>(*this), *frame.getBody())) { - if (invoke(static_cast<ExecutionHandler&>(*this), *frame.getBody())) { - //make sure the command id sequence and completion - //tracking takes account of execution commands - Lock l(state); - completedIn.add(nextIn++); - } else { - //if not handled by this class, its for the application: - deliver(frame); - } + if (invoke(static_cast<SessionHandler&>(*this), *frame.getBody())) { + ; + } else if (invoke(static_cast<ExecutionHandler&>(*this), *frame.getBody())) { + //make sure the command id sequence and completion + //tracking takes account of execution commands + Lock l(state); + completedIn.add(nextIn++); + } else if (invoke(static_cast<MessageHandler&>(*this), *frame.getBody())) { + ; + } else { + //if not handled by this class, its for the application: + deliver(frame); } - } catch (const SessionException& e) { - //TODO: proper 0-10 exception handling - QPID_LOG(error, "Session exception:" << e.what()); - Lock l(state); - error = EXCEPTION; - code = e.code; - text = e.what(); + } + catch (const SessionException& e) { + setException(createSessionException(e.code, e.getMessage())); + } + catch (const ChannelException& e) { + setException(createChannelException(e.code, e.getMessage())); } } void SessionImpl::handleOut(AMQFrame& frame) // user thread { - connection->expand(frame.size(), true); - channel.handle(frame); + sendFrame(frame, true); } void SessionImpl::proxyOut(AMQFrame& frame) // network thread { - connection->expand(frame.size(), false); + //Note: this case is treated slightly differently that command + //frames sent by application; session controls should not be + //blocked by bounds checking on the outgoing frame queue. + sendFrame(frame, false); +} + +void SessionImpl::sendFrame(AMQFrame& frame, bool canBlock) +{ channel.handle(frame); + connection->expand(frame.encodedSize(), canBlock); } void SessionImpl::deliver(AMQFrame& frame) // network thread @@ -435,24 +557,22 @@ void SessionImpl::detach(const std::string& _name) if (id.getName() != _name) throw InternalErrorException("Incorrect session name"); setState(DETACHED); QPID_LOG(info, "Session detached by peer: " << id); + proxy.detached(_name, DETACH_CODE_NORMAL); } -void SessionImpl::detached(const std::string& _name, uint8_t _code) -{ +void SessionImpl::detached(const std::string& _name, uint8_t _code) { Lock l(state); if (id.getName() != _name) throw InternalErrorException("Incorrect session name"); setState(DETACHED); if (_code) { //TODO: make sure this works with execution.exception - don't //want to overwrite the code from that - QPID_LOG(error, "Session detached by peer: " << id << " " << code); - error = SESSION_DETACH; - code = _code; - text = "Session detached by peer"; + setExceptionLH(createChannelException(_code, "Session detached by peer")); + QPID_LOG(error, exceptionHolder.what()); } if (detachedLifetime == 0) { handleClosed(); - } +} } void SessionImpl::requestTimeout(uint32_t t) @@ -478,7 +598,7 @@ void SessionImpl::commandPoint(const framing::SequenceNumber& id, uint64_t offse void SessionImpl::expected(const framing::SequenceSet& commands, const framing::Array& fragments) { - if (!commands.empty() || fragments.size()) { + if (!commands.empty() || fragments.encodedSize()) { throw NotImplementedException("Session resumption not yet supported"); } } @@ -492,7 +612,7 @@ void SessionImpl::completed(const framing::SequenceSet& commands, bool timelyRep { Lock l(state); incompleteOut.remove(commands); - state.notify();//notify any waiters of completion + state.notifyAll();//notify any waiters of completion completedOut.add(commands); //notify any waiting results of completion results.completed(commands); @@ -561,20 +681,78 @@ void SessionImpl::exception(uint16_t errorCode, const std::string& description, const framing::FieldTable& /*errorInfo*/) { - QPID_LOG(warning, "Exception received from peer: " << errorCode << ":" << description + Lock l(state); + setExceptionLH(createSessionException(errorCode, description)); + QPID_LOG(warning, "Exception received from broker: " << exceptionHolder.what() << " [caused by " << commandId << " " << classCode << ":" << commandCode << "]"); + if (detachedLifetime) + setTimeout(0); +} + +// Message methods: +void SessionImpl::accept(const qpid::framing::SequenceSet&) +{ +} + +void SessionImpl::reject(const qpid::framing::SequenceSet&, uint16_t, const std::string&) +{ +} + +void SessionImpl::release(const qpid::framing::SequenceSet&, bool) +{ +} + +MessageResumeResult SessionImpl::resume(const std::string&, const std::string&) +{ + throw NotImplementedException("resuming transfers not yet supported"); +} + +namespace { + const std::string QPID_SESSION_DEST = ""; + const uint8_t FLOW_MODE_CREDIT = 0; + const uint8_t CREDIT_MODE_MSG = 0; +} + +void SessionImpl::setFlowMode(const std::string& dest, uint8_t flowMode) +{ + if ( dest != QPID_SESSION_DEST ) { + QPID_LOG(warning, "Ignoring flow control for unknown destination: " << dest); + return; + } + + if ( flowMode != FLOW_MODE_CREDIT ) { + throw NotImplementedException("window flow control mode not supported by producer"); + } Lock l(state); - error = EXCEPTION; - code = errorCode; - text = description; - if (detachedLifetime) { - proxy.requestTimeout(0); - //should we wait for the timeout response? - detachedLifetime = 0; + sendMsgCredit = new sys::Semaphore(0); +} + +void SessionImpl::flow(const std::string& dest, uint8_t mode, uint32_t credit) +{ + if ( dest != QPID_SESSION_DEST ) { + QPID_LOG(warning, "Ignoring flow control for unknown destination: " << dest); + return; + } + + if ( mode != CREDIT_MODE_MSG ) { + return; + } + if (sendMsgCredit) { + sendMsgCredit->release(credit); } } +void SessionImpl::stop(const std::string& dest) +{ + if ( dest != QPID_SESSION_DEST ) { + QPID_LOG(warning, "Ignoring flow control for unknown destination: " << dest); + return; + } + if (sendMsgCredit) { + sendMsgCredit->forceLock(); + } +} //private utility methods: @@ -586,25 +764,21 @@ inline void SessionImpl::setState(State s) //call with lock held inline void SessionImpl::waitFor(State s) //call with lock held { // We can be DETACHED at any time - state.waitFor(States(s, DETACHED)); + if (s == DETACHED) state.waitFor(DETACHED); + else state.waitFor(States(s, DETACHED)); check(); } void SessionImpl::check() const //call with lock held. { - switch (error) { - case OK: break; - case CONNECTION_CLOSE: throw ConnectionException(code, text); - case SESSION_DETACH: throw ChannelException(code, text); - case EXCEPTION: throwExecutionException(code, text); - } + exceptionHolder.raise(); } void SessionImpl::checkOpen() const //call with lock held. { check(); if (state != ATTACHED) { - throw NotAttachedException("Session isn't attached"); + throw NotAttachedException(QPID_MSG("Session " << getId() << " isn't attached")); } } @@ -616,10 +790,28 @@ void SessionImpl::assertOpen() const void SessionImpl::handleClosed() { - // FIXME aconway 2008-06-12: needs to be set to the correct exception type. - // - demux.close(sys::ExceptionHolder(text.empty() ? new ClosedException() : new Exception(text))); + demux.close(exceptionHolder.empty() ? + sys::ExceptionHolder(new ClosedException()) : exceptionHolder); results.close(); } +uint32_t SessionImpl::setTimeout(uint32_t seconds) { + proxy.requestTimeout(seconds); + // FIXME aconway 2008-10-07: wait for timeout response from broker + // and use value retured by broker. + detachedLifetime = seconds; + return detachedLifetime; +} + +uint32_t SessionImpl::getTimeout() const { + return detachedLifetime; +} + +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 55031a94ae..2f35032c4e 100644 --- a/cpp/src/qpid/client/SessionImpl.h +++ b/cpp/src/qpid/client/SessionImpl.h @@ -22,12 +22,13 @@ #ifndef _SessionImpl_ #define _SessionImpl_ -#include "Demux.h" -#include "Execution.h" -#include "Results.h" +#include "qpid/client/Demux.h" +#include "qpid/client/Execution.h" +#include "qpid/client/Results.h" +#include "qpid/client/ClientImportExport.h" #include "qpid/SessionId.h" -#include "qpid/shared_ptr.h" +#include "qpid/SessionState.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/ChannelHandler.h" #include "qpid/framing/SequenceNumber.h" @@ -35,7 +36,10 @@ #include "qpid/framing/AMQP_ServerProxy.h" #include "qpid/sys/Semaphore.h" #include "qpid/sys/StateMonitor.h" +#include "qpid/sys/ExceptionHolder.h" +#include <boost/weak_ptr.hpp> +#include <boost/shared_ptr.hpp> #include <boost/optional.hpp> namespace qpid { @@ -52,15 +56,17 @@ namespace client { class Future; class ConnectionImpl; +class SessionHandler; ///@internal class SessionImpl : public framing::FrameHandler::InOutHandler, public Execution, private framing::AMQP_ClientOperations::SessionHandler, - private framing::AMQP_ClientOperations::ExecutionHandler + private framing::AMQP_ClientOperations::ExecutionHandler, + private framing::AMQP_ClientOperations::MessageHandler { public: - SessionImpl(const std::string& name, shared_ptr<ConnectionImpl>, uint16_t channel, uint64_t maxFrameSize); + SessionImpl(const std::string& name, boost::shared_ptr<ConnectionImpl>); ~SessionImpl(); @@ -74,33 +80,57 @@ public: void open(uint32_t detachedLifetime); void close(); - void resume(shared_ptr<ConnectionImpl>); + void resume(boost::shared_ptr<ConnectionImpl>); void suspend(); void assertOpen() const; 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); + void sendRawFrame(framing::AMQFrame& frame); Demux& getDemux(); void markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer); + void markCompleted(const framing::SequenceSet& ids, bool notifyPeer); bool isComplete(const framing::SequenceNumber& id); bool isCompleteUpTo(const framing::SequenceNumber& id); + framing::SequenceNumber getCompleteUpTo(); void waitForCompletion(const framing::SequenceNumber& id); void sendCompletion(); void sendFlush(); + void setException(const sys::ExceptionHolder&); + //NOTE: these are called by the network thread when the connection is closed or dies void connectionClosed(uint16_t code, const std::string& text); - void connectionBroke(uint16_t code, const std::string& text); + void connectionBroke(const std::string& text); + + /** Set timeout in seconds, returns actual timeout allowed by broker */ + uint32_t setTimeout(uint32_t requestedSeconds); + + /** Get timeout in seconds. */ + uint32_t getTimeout() const; + + /** + * get the Connection associated with this connection + */ + 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 ErrorType { - OK, - CONNECTION_CLOSE, - SESSION_DETACH, - EXCEPTION - }; enum State { INACTIVE, ATTACHING, @@ -110,12 +140,14 @@ private: }; typedef framing::AMQP_ClientOperations::SessionHandler SessionHandler; typedef framing::AMQP_ClientOperations::ExecutionHandler ExecutionHandler; + typedef framing::AMQP_ClientOperations::MessageHandler MessageHandler; typedef sys::StateMonitor<State, DETACHED> StateMonitor; typedef StateMonitor::Set States; inline void setState(State s); inline void waitFor(State); + void setExceptionLH(const sys::ExceptionHolder&); // LH = lock held when called. void detach(); void check() const; @@ -124,13 +156,19 @@ private: void handleIn(framing::AMQFrame& frame); void handleOut(framing::AMQFrame& frame); + /** + * Sends session controls. This case is treated slightly + * differently than command frames sent by the application via + * handleOut(); session controlsare not subject to bounds checking + * on the outgoing frame queue. + */ void proxyOut(framing::AMQFrame& frame); + void sendFrame(framing::AMQFrame& frame, bool canBlock); void deliver(framing::AMQFrame& frame); Future sendCommand(const framing::AMQBody&, const framing::MethodContent* = 0); void sendContent(const framing::MethodContent&); void waitForCompletionImpl(const framing::SequenceNumber& id); - void requestTimeout(uint32_t timeout); void sendCompletionImpl(); @@ -139,7 +177,8 @@ private: void attach(const std::string& name, bool force); void attached(const std::string& name); void detach(const std::string& name); - void detached(const std::string& name, uint8_t detachCode); + void detached(const std::string& name, uint8_t detachCode); + void requestTimeout(uint32_t timeout); void timeout(uint32_t timeout); void commandPoint(const framing::SequenceNumber& commandId, uint64_t commandOffset); void expected(const framing::SequenceSet& commands, const framing::Array& fragments); @@ -160,17 +199,28 @@ private: uint8_t fieldIndex, const std::string& description, const framing::FieldTable& errorInfo); - - ErrorType error; - int code; // Error code - std::string text; // Error text + + // Note: Following methods are called by network thread in + // response to message commands from the broker + // EXCEPT Message.Transfer + void accept(const qpid::framing::SequenceSet&); + void reject(const qpid::framing::SequenceSet&, uint16_t, const std::string&); + void release(const qpid::framing::SequenceSet&, bool); + qpid::framing::MessageResumeResult resume(const std::string&, const std::string&); + void setFlowMode(const std::string&, uint8_t); + void flow(const std::string&, uint8_t, uint32_t); + void stop(const std::string&); + + + sys::ExceptionHolder exceptionHolder; mutable StateMonitor state; mutable sys::Semaphore sendLock; uint32_t detachedLifetime; const uint64_t maxFrameSize; const SessionId id; - shared_ptr<ConnectionImpl> connection; + boost::shared_ptr<ConnectionImpl> connection; + framing::FrameHandler::MemFunRef<SessionImpl, &SessionImpl::proxyOut> ioHandler; framing::ChannelHandler channel; framing::AMQP_ServerProxy::Session proxy; @@ -186,6 +236,16 @@ private: framing::SequenceNumber nextIn; framing::SequenceNumber nextOut; + SessionState sessionState; + + // Only keep track of message credit + sys::Semaphore* sendMsgCredit; + + bool doClearDeliveryPropertiesExchange; + + bool autoDetach; + + friend class client::SessionHandler; }; }} // namespace qpid::client diff --git a/cpp/src/qpid/client/SslConnector.cpp b/cpp/src/qpid/client/SslConnector.cpp new file mode 100644 index 0000000000..5cdaaa4615 --- /dev/null +++ b/cpp/src/qpid/client/SslConnector.cpp @@ -0,0 +1,400 @@ +/* + * + * 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/Connector.h" + +#include "config.h" +#include "qpid/client/Bounds.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/Options.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/sys/ssl/util.h" +#include "qpid/sys/ssl/SslIo.h" +#include "qpid/sys/ssl/SslSocket.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/Msg.h" + +#include <iostream> +#include <map> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::sys::ssl; +using namespace qpid::framing; +using boost::format; +using boost::str; + + +class SslConnector : public Connector, private sys::Runnable +{ + struct Buff; + + /** Batch up frames for writing to aio. */ + class Writer : public framing::FrameHandler { + typedef sys::ssl::SslIOBufferBase BufferBase; + typedef std::vector<framing::AMQFrame> Frames; + + const uint16_t maxFrameSize; + sys::Mutex lock; + sys::ssl::SslIO* aio; + BufferBase* buffer; + Frames frames; + size_t lastEof; // Position after last EOF in frames + framing::Buffer encode; + size_t framesEncoded; + std::string identifier; + Bounds* bounds; + + void writeOne(); + void newBuffer(); + + public: + + Writer(uint16_t maxFrameSize, Bounds*); + ~Writer(); + void init(std::string id, sys::ssl::SslIO*); + void handle(framing::AMQFrame&); + void write(sys::ssl::SslIO&); + }; + + const uint16_t maxFrameSize; + framing::ProtocolVersion version; + bool initiated; + + sys::Mutex closedLock; + bool closed; + bool joined; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + Writer writer; + + sys::Thread receiver; + + sys::ssl::SslSocket socket; + + sys::ssl::SslIO* aio; + boost::shared_ptr<sys::Poller> poller; + + ~SslConnector(); + + void run(); + void handleClosed(); + bool closeInternal(); + + void readbuff(qpid::sys::ssl::SslIO&, qpid::sys::ssl::SslIOBufferBase*); + void writebuff(qpid::sys::ssl::SslIO&); + void writeDataBlock(const framing::AMQDataBlock& data); + void eof(qpid::sys::ssl::SslIO&); + + std::string identifier; + + ConnectionImpl* impl; + + void connect(const std::string& host, int port); + void init(); + void close(); + void send(framing::AMQFrame& frame); + void abort() {} // TODO: Need to fix for heartbeat timeouts to work + + void setInputHandler(framing::InputHandler* handler); + void setShutdownHandler(sys::ShutdownHandler* handler); + sys::ShutdownHandler* getShutdownHandler() const; + framing::OutputHandler* getOutputHandler(); + const std::string& getIdentifier() const; + +public: + SslConnector(framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); + unsigned int getSSF() { return socket.getKeyLen(); } +}; + +// Static constructor which registers connector here +namespace { + Connector* create(framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new SslConnector(v, s, c); + } + + struct StaticInit { + StaticInit() { + try { + SslOptions options; + options.parse (0, 0, QPIDC_CONF_FILE, true); + if (options.certDbPath.empty()) { + QPID_LOG(info, "SSL connector not enabled, you must set QPID_SSL_CERT_DB to enable it."); + } else { + initNSS(options); + Connector::registerFactory("ssl", &create); + } + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to initialise SSL connector: " << e.what()); + } + }; + + ~StaticInit() { shutdownNSS(); } + } init; +} + +SslConnector::SslConnector(ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + version(ver), + initiated(false), + closed(true), + joined(true), + shutdownHandler(0), + writer(maxFrameSize, cimpl), + aio(0), + impl(cimpl) +{ + QPID_LOG(debug, "SslConnector created for " << version.toString()); + //TODO: how do we want to handle socket configuration with ssl? + //settings.configureSocket(socket); +} + +SslConnector::~SslConnector() { + close(); +} + +void SslConnector::connect(const std::string& host, int port){ + Mutex::ScopedLock l(closedLock); + assert(closed); + try { + socket.connect(host, port); + } catch (const std::exception& e) { + socket.close(); + throw; + } + + identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); + closed = false; + poller = Poller::shared_ptr(new Poller); + aio = new SslIO(socket, + boost::bind(&SslConnector::readbuff, this, _1, _2), + boost::bind(&SslConnector::eof, this, _1), + boost::bind(&SslConnector::eof, this, _1), + 0, // closed + 0, // nobuffs + boost::bind(&SslConnector::writebuff, this, _1)); + writer.init(identifier, aio); +} + +void SslConnector::init(){ + Mutex::ScopedLock l(closedLock); + assert(joined); + ProtocolInitiation init(version); + writeDataBlock(init); + joined = false; + receiver = Thread(this); +} + +bool SslConnector::closeInternal() { + Mutex::ScopedLock l(closedLock); + bool ret = !closed; + if (!closed) { + closed = true; + aio->queueForDeletion(); + poller->shutdown(); + } + if (!joined && receiver.id() != Thread::current().id()) { + joined = true; + Mutex::ScopedUnlock u(closedLock); + receiver.join(); + } + return ret; +} + +void SslConnector::close() { + closeInternal(); +} + +void SslConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void SslConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* SslConnector::getOutputHandler() { + return this; +} + +sys::ShutdownHandler* SslConnector::getShutdownHandler() const { + return shutdownHandler; +} + +const std::string& SslConnector::getIdentifier() const { + return identifier; +} + +void SslConnector::send(AMQFrame& frame) { + writer.handle(frame); +} + +void SslConnector::handleClosed() { + if (closeInternal() && shutdownHandler) + shutdownHandler->shutdown(); +} + +struct SslConnector::Buff : public SslIO::BufferBase { + Buff(size_t size) : SslIO::BufferBase(new char[size], size) {} + ~Buff() { delete [] bytes;} +}; + +SslConnector::Writer::Writer(uint16_t s, Bounds* b) : maxFrameSize(s), aio(0), buffer(0), lastEof(0), bounds(b) +{ +} + +SslConnector::Writer::~Writer() { delete buffer; } + +void SslConnector::Writer::init(std::string id, sys::ssl::SslIO* a) { + Mutex::ScopedLock l(lock); + identifier = id; + aio = a; + newBuffer(); +} +void SslConnector::Writer::handle(framing::AMQFrame& frame) { + Mutex::ScopedLock l(lock); + frames.push_back(frame); + if (frame.getEof() || (bounds && bounds->getCurrentSize() >= maxFrameSize)) { + lastEof = frames.size(); + aio->notifyPendingWrite(); + } + QPID_LOG(trace, "SENT " << identifier << ": " << frame); +} + +void SslConnector::Writer::writeOne() { + assert(buffer); + framesEncoded = 0; + + buffer->dataStart = 0; + buffer->dataCount = encode.getPosition(); + aio->queueWrite(buffer); + newBuffer(); +} + +void SslConnector::Writer::newBuffer() { + buffer = aio->getQueuedBuffer(); + if (!buffer) buffer = new Buff(maxFrameSize); + encode = framing::Buffer(buffer->bytes, buffer->byteCount); + framesEncoded = 0; +} + +// Called in IO thread. +void SslConnector::Writer::write(sys::ssl::SslIO&) { + Mutex::ScopedLock l(lock); + assert(buffer); + size_t bytesWritten(0); + for (size_t i = 0; i < lastEof; ++i) { + AMQFrame& frame = frames[i]; + uint32_t size = frame.encodedSize(); + if (size > encode.available()) writeOne(); + assert(size <= encode.available()); + frame.encode(encode); + ++framesEncoded; + bytesWritten += size; + } + frames.erase(frames.begin(), frames.begin()+lastEof); + lastEof = 0; + if (bounds) bounds->reduce(bytesWritten); + if (encode.getPosition() > 0) writeOne(); +} + +void SslConnector::readbuff(SslIO& aio, SslIO::BufferBase* buff) { + framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); + + if (!initiated) { + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + //TODO: check the version is correct + QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); + } + initiated = true; + } + AMQFrame frame; + while(frame.decode(in)){ + QPID_LOG(trace, "RECV " << identifier << ": " << frame); + input->received(frame); + } + // TODO: unreading needs to go away, and when we can cope + // with multiple sub-buffers in the general buffer scheme, it will + if (in.available() != 0) { + // Adjust buffer for used bytes and then "unread them" + buff->dataStart += buff->dataCount-in.available(); + buff->dataCount = in.available(); + aio.unread(buff); + } else { + // Give whole buffer back to aio subsystem + aio.queueReadBuffer(buff); + } +} + +void SslConnector::writebuff(SslIO& aio_) { + writer.write(aio_); +} + +void SslConnector::writeDataBlock(const AMQDataBlock& data) { + SslIO::BufferBase* buff = new Buff(maxFrameSize); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void SslConnector::eof(SslIO&) { + handleClosed(); +} + +void SslConnector::run(){ + // Keep the connection impl in memory until run() completes. + boost::shared_ptr<ConnectionImpl> protect = impl->shared_from_this(); + assert(protect); + try { + Dispatcher d(poller); + + for (int i = 0; i < 32; i++) { + aio->queueReadBuffer(new Buff(maxFrameSize)); + } + + aio->start(poller); + d.run(); + socket.close(); + } catch (const std::exception& e) { + QPID_LOG(error, e.what()); + handleClosed(); + } +} + + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/StateManager.cpp b/cpp/src/qpid/client/StateManager.cpp index 0cb3c6b9d4..5462e0fed4 100644 --- a/cpp/src/qpid/client/StateManager.cpp +++ b/cpp/src/qpid/client/StateManager.cpp @@ -19,7 +19,7 @@ * */ -#include "StateManager.h" +#include "qpid/client/StateManager.h" #include "qpid/framing/amqp_framing.h" using namespace qpid::client; @@ -60,6 +60,18 @@ void StateManager::setState(int s) stateLock.notifyAll(); } +bool StateManager::setState(int s, int expected) +{ + Monitor::ScopedLock l(stateLock); + if (state == expected) { + state = s; + stateLock.notifyAll(); + return true; + } else { + return false; + } +} + int StateManager::getState() const { Monitor::ScopedLock l(stateLock); diff --git a/cpp/src/qpid/client/StateManager.h b/cpp/src/qpid/client/StateManager.h index b01664a0c1..3c8412dfa7 100644 --- a/cpp/src/qpid/client/StateManager.h +++ b/cpp/src/qpid/client/StateManager.h @@ -36,6 +36,7 @@ class StateManager public: StateManager(int initial); void setState(int state); + bool setState(int state, int expected); int getState() const ; void waitForStateChange(int current); void waitFor(std::set<int> states); diff --git a/cpp/src/qpid/client/Subscription.cpp b/cpp/src/qpid/client/Subscription.cpp new file mode 100644 index 0000000000..988f372604 --- /dev/null +++ b/cpp/src/qpid/client/Subscription.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. + * + */ + +#include "qpid/client/Subscription.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/framing/enum.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<Subscription> PI; +Subscription::Subscription(SubscriptionImpl* p) { PI::ctor(*this, p); } +Subscription::~Subscription() { PI::dtor(*this); } +Subscription::Subscription(const Subscription& c) : Handle<SubscriptionImpl>() { PI::copy(*this, c); } +Subscription& Subscription::operator=(const Subscription& c) { return PI::assign(*this, c); } + + +std::string Subscription::getName() const { return impl->getName(); } +std::string Subscription::getQueue() const { return impl->getQueue(); } +const SubscriptionSettings& Subscription::getSettings() const { return impl->getSettings(); } +void Subscription::setFlowControl(const FlowControl& f) { impl->setFlowControl(f); } +void Subscription::setAutoAck(unsigned int n) { impl->setAutoAck(n); } +SequenceSet Subscription::getUnacquired() const { return impl->getUnacquired(); } +SequenceSet Subscription::getUnaccepted() const { return impl->getUnaccepted(); } +void Subscription::acquire(const SequenceSet& messageIds) { impl->acquire(messageIds); } +void Subscription::accept(const SequenceSet& messageIds) { impl->accept(messageIds); } +void Subscription::release(const SequenceSet& messageIds) { impl->release(messageIds); } +Session Subscription::getSession() const { return impl->getSession(); } +SubscriptionManager Subscription::getSubscriptionManager() { return impl->getSubscriptionManager(); } +void Subscription::cancel() { impl->cancel(); } +void Subscription::grantMessageCredit(uint32_t value) { impl->grantCredit(framing::message::CREDIT_UNIT_MESSAGE, value); } +void Subscription::grantByteCredit(uint32_t value) { impl->grantCredit(framing::message::CREDIT_UNIT_BYTE, value); } +}} // namespace qpid::client + + diff --git a/cpp/src/qpid/client/SubscriptionImpl.cpp b/cpp/src/qpid/client/SubscriptionImpl.cpp new file mode 100644 index 0000000000..a8a0b47d94 --- /dev/null +++ b/cpp/src/qpid/client/SubscriptionImpl.cpp @@ -0,0 +1,169 @@ +/* + * + * 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/AsyncSession.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SubscriptionManagerImpl.h" +#include "qpid/client/MessageImpl.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/SubscriptionSettings.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +using sys::Mutex; +using framing::MessageAcquireResult; + +SubscriptionImpl::SubscriptionImpl(SubscriptionManager m, const std::string& q, const SubscriptionSettings& s, const std::string& n, MessageListener* l) + : manager(*PrivateImplRef<SubscriptionManager>::get(m)), name(n), queue(q), settings(s), listener(l) +{} + +void SubscriptionImpl::subscribe() +{ + async(manager.getSession()).messageSubscribe( + arg::queue=queue, + arg::destination=name, + arg::acceptMode=settings.acceptMode, + arg::acquireMode=settings.acquireMode, + arg::exclusive=settings.exclusive); + setFlowControl(settings.flowControl); +} + +std::string SubscriptionImpl::getName() const { return name; } + +std::string SubscriptionImpl::getQueue() const { return queue; } + +const SubscriptionSettings& SubscriptionImpl::getSettings() const { + Mutex::ScopedLock l(lock); + return settings; +} + +void SubscriptionImpl::setFlowControl(const FlowControl& f) { + Mutex::ScopedLock l(lock); + AsyncSession s=manager.getSession(); + if (&settings.flowControl != &f) settings.flowControl = f; + s.messageSetFlowMode(name, f.window); + s.messageFlow(name, CREDIT_UNIT_MESSAGE, f.messages); + s.messageFlow(name, CREDIT_UNIT_BYTE, f.bytes); + s.sync(); +} + +void SubscriptionImpl::grantCredit(framing::message::CreditUnit unit, uint32_t value) { + async(manager.getSession()).messageFlow(name, unit, value); +} + +void SubscriptionImpl::setAutoAck(size_t n) { + Mutex::ScopedLock l(lock); + settings.autoAck = n; +} + +SequenceSet SubscriptionImpl::getUnacquired() const { Mutex::ScopedLock l(lock); return unacquired; } +SequenceSet SubscriptionImpl::getUnaccepted() const { Mutex::ScopedLock l(lock); return unaccepted; } + +void SubscriptionImpl::acquire(const SequenceSet& messageIds) { + Mutex::ScopedLock l(lock); + MessageAcquireResult result = manager.getSession().messageAcquire(messageIds); + unacquired.remove(result.getTransfers()); + if (settings.acceptMode == ACCEPT_MODE_EXPLICIT) + unaccepted.add(result.getTransfers()); +} + +void SubscriptionImpl::accept(const SequenceSet& messageIds) { + Mutex::ScopedLock l(lock); + manager.getSession().messageAccept(messageIds); + unaccepted.remove(messageIds); + switch (settings.completionMode) { + case COMPLETE_ON_ACCEPT: + manager.getSession().markCompleted(messageIds, true); + break; + case COMPLETE_ON_DELIVERY: + manager.getSession().sendCompletion(); + break; + default://do nothing + break; + } +} + +void SubscriptionImpl::release(const SequenceSet& messageIds) { + Mutex::ScopedLock l(lock); + manager.getSession().messageRelease(messageIds); + if (settings.acceptMode == ACCEPT_MODE_EXPLICIT) + unaccepted.remove(messageIds); +} + +Session SubscriptionImpl::getSession() const { return manager.getSession(); } + +SubscriptionManager SubscriptionImpl::getSubscriptionManager() { return SubscriptionManager(&manager); } + +void SubscriptionImpl::cancel() { manager.cancel(name); } + +void SubscriptionImpl::received(Message& m) { + Mutex::ScopedLock l(lock); + MessageImpl& mi = *MessageImpl::get(m); + if (mi.getMethod().getAcquireMode() == ACQUIRE_MODE_NOT_ACQUIRED) + unacquired.add(m.getId()); + else if (mi.getMethod().getAcceptMode() == ACCEPT_MODE_EXPLICIT) + unaccepted.add(m.getId()); + + if (listener) { + Mutex::ScopedUnlock u(lock); + listener->received(m); + } + + if (settings.completionMode == COMPLETE_ON_DELIVERY) { + manager.getSession().markCompleted(m.getId(), false, false); + } + if (settings.autoAck) { + if (unaccepted.size() >= settings.autoAck) { + async(manager.getSession()).messageAccept(unaccepted); + switch (settings.completionMode) { + case COMPLETE_ON_ACCEPT: + manager.getSession().markCompleted(unaccepted, true); + break; + case COMPLETE_ON_DELIVERY: + manager.getSession().sendCompletion(); + break; + default://do nothing + break; + } + unaccepted.clear(); + } + } +} + +Demux::QueuePtr SubscriptionImpl::divert() +{ + Session session(manager.getSession()); + Demux& demux = SessionBase_0_10Access(session).get()->getDemux(); + demuxRule = std::auto_ptr<ScopedDivert>(new ScopedDivert(name, demux)); + return demuxRule->getQueue(); +} + +void SubscriptionImpl::cancelDiversion() { + demuxRule.reset(); +} + +}} // namespace qpid::client + diff --git a/cpp/src/qpid/client/SubscriptionImpl.h b/cpp/src/qpid/client/SubscriptionImpl.h new file mode 100644 index 0000000000..da77213423 --- /dev/null +++ b/cpp/src/qpid/client/SubscriptionImpl.h @@ -0,0 +1,125 @@ +#ifndef QPID_CLIENT_SUBSCRIPTIONIMPL_H +#define QPID_CLIENT_SUBSCRIPTIONIMPL_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/SubscriptionSettings.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/Session.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/Demux.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/sys/Mutex.h" +#include "qpid/RefCounted.h" +#include "qpid/client/ClientImportExport.h" +#include <memory> + +namespace qpid { +namespace client { + +class SubscriptionManager; +class SubscriptionManagerImpl; + +class SubscriptionImpl : public RefCounted, public MessageListener { + public: + QPID_CLIENT_EXTERN SubscriptionImpl(SubscriptionManager, const std::string& queue, + const SubscriptionSettings&, const std::string& name, MessageListener* =0); + + /** The name of the subsctription, used as the "destination" for messages from the broker. + * Usually the same as the queue name but can be set differently. + */ + QPID_CLIENT_EXTERN std::string getName() const; + + /** Name of the queue this subscription subscribes to */ + QPID_CLIENT_EXTERN std::string getQueue() const; + + /** Get the flow control and acknowledgement settings for this subscription */ + QPID_CLIENT_EXTERN const SubscriptionSettings& getSettings() const; + + /** Set the flow control parameters */ + QPID_CLIENT_EXTERN void setFlowControl(const FlowControl&); + + /** Automatically acknowledge (acquire and accept) batches of n messages. + * You can disable auto-acknowledgement by setting n=0, and use acquire() and accept() + * to manually acquire and accept messages. + */ + QPID_CLIENT_EXTERN void setAutoAck(size_t n); + + /** Get the set of ID's for messages received by this subscription but not yet acquired. + * This will always be empty if acquireMode=ACQUIRE_MODE_PRE_ACQUIRED + */ + QPID_CLIENT_EXTERN SequenceSet getUnacquired() const; + + /** Get the set of ID's for messages acquired by this subscription but not yet accepted. */ + QPID_CLIENT_EXTERN SequenceSet getUnaccepted() const; + + /** Acquire messageIds and remove them from the un-acquired set for the session. */ + QPID_CLIENT_EXTERN void acquire(const SequenceSet& messageIds); + + /** Accept messageIds and remove them from the un-accepted set for the session. */ + QPID_CLIENT_EXTERN void accept(const SequenceSet& messageIds); + + /** Release messageIds and remove them from the un-accepted set for the session. */ + QPID_CLIENT_EXTERN void release(const SequenceSet& messageIds); + + /** Get the session associated with this subscription */ + QPID_CLIENT_EXTERN Session getSession() const; + + /** Get the subscription manager associated with this subscription */ + QPID_CLIENT_EXTERN SubscriptionManager getSubscriptionManager(); + + /** Send subscription request and issue appropriate flow control commands. */ + QPID_CLIENT_EXTERN void subscribe(); + + /** Cancel the subscription. */ + QPID_CLIENT_EXTERN void cancel(); + + /** Grant specified credit for this subscription **/ + QPID_CLIENT_EXTERN void grantCredit(framing::message::CreditUnit unit, uint32_t value); + + QPID_CLIENT_EXTERN void received(Message&); + + /** + * Set up demux diversion for messages sent to this subscription + */ + Demux::QueuePtr divert(); + /** + * Cancel any demux diversion that may have been setup for this + * subscription + */ + QPID_CLIENT_EXTERN void cancelDiversion(); + + private: + + mutable sys::Mutex lock; + SubscriptionManagerImpl& manager; + std::string name, queue; + SubscriptionSettings settings; + framing::SequenceSet unacquired, unaccepted; + MessageListener* listener; + std::auto_ptr<ScopedDivert> demuxRule; +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SUBSCRIPTIONIMPL_H*/ diff --git a/cpp/src/qpid/client/SubscriptionManager.cpp b/cpp/src/qpid/client/SubscriptionManager.cpp index b4c48f7365..7eac3c541b 100644 --- a/cpp/src/qpid/client/SubscriptionManager.cpp +++ b/cpp/src/qpid/client/SubscriptionManager.cpp @@ -18,129 +18,85 @@ * under the License. * */ -#ifndef _Subscription_ -#define _Subscription_ -#include "SubscriptionManager.h" -#include <qpid/client/Dispatcher.h> -#include <qpid/client/Session.h> -#include <qpid/client/MessageListener.h> -#include <qpid/framing/Uuid.h> -#include <set> -#include <sstream> +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/SubscriptionManagerImpl.h" +#include "qpid/client/PrivateImplRef.h" namespace qpid { namespace client { -SubscriptionManager::SubscriptionManager(const Session& s) - : dispatcher(s), session(s), - flowControl(UNLIMITED, UNLIMITED, false), - acceptMode(0), acquireMode(0), - autoStop(true) -{} - -void SubscriptionManager::subscribeInternal( - const std::string& q, const std::string& dest, const FlowControl& fc) -{ - session.messageSubscribe( - arg::queue=q, arg::destination=dest, - arg::acceptMode=acceptMode, arg::acquireMode=acquireMode); - if (fc.messages || fc.bytes) // No need to set if all 0. - setFlowControl(dest, fc); -} +typedef PrivateImplRef<SubscriptionManager> PI; -void SubscriptionManager::subscribe( - MessageListener& listener, const std::string& q, const std::string& d) -{ - subscribe(listener, q, getFlowControl(), d); -} +SubscriptionManager::SubscriptionManager(const Session& s) { PI::ctor(*this, new SubscriptionManagerImpl(s)); } +SubscriptionManager::SubscriptionManager(SubscriptionManagerImpl* i) { PI::ctor(*this, i); } +SubscriptionManager::SubscriptionManager(const SubscriptionManager& x) : Runnable(), Handle<SubscriptionManagerImpl>() { PI::copy(*this, x); } +SubscriptionManager::~SubscriptionManager() { PI::dtor(*this); } +SubscriptionManager& SubscriptionManager::operator=(const SubscriptionManager& x) { return PI::assign(*this, x); } -void SubscriptionManager::subscribe( - MessageListener& listener, const std::string& q, const FlowControl& fc, const std::string& d) -{ - std::string dest=d.empty() ? q:d; - dispatcher.listen(dest, &listener, autoAck); - return subscribeInternal(q, dest, fc); -} +Subscription SubscriptionManager::subscribe( + MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ return impl->subscribe(listener, q, ss, n); } -void SubscriptionManager::subscribe( - LocalQueue& lq, const std::string& q, const std::string& d) -{ - subscribe(lq, q, getFlowControl(), d); -} +Subscription SubscriptionManager::subscribe( + LocalQueue& lq, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ return impl->subscribe(lq, q, ss, n); } -void SubscriptionManager::subscribe( - LocalQueue& lq, const std::string& q, const FlowControl& fc, const std::string& d) -{ - std::string dest=d.empty() ? q:d; - lq.session=session; - lq.queue=session.getExecution().getDemux().add(dest, ByTransferDest(dest)); - return subscribeInternal(q, dest, fc); -} -void SubscriptionManager::setFlowControl( - const std::string& dest, uint32_t messages, uint32_t bytes, bool window) -{ - session.messageSetFlowMode(dest, window); - session.messageFlow(dest, 0, messages); - session.messageFlow(dest, 1, bytes); - session.sync(); -} +Subscription SubscriptionManager::subscribe( + MessageListener& listener, const std::string& q, const std::string& n) +{ return impl->subscribe(listener, q, n); } -void SubscriptionManager::setFlowControl(const std::string& dest, const FlowControl& fc) { - setFlowControl(dest, fc.messages, fc.bytes, fc.window); -} -void SubscriptionManager::setFlowControl(const FlowControl& fc) { flowControl=fc; } +Subscription SubscriptionManager::subscribe( + LocalQueue& lq, const std::string& q, const std::string& n) +{ return impl->subscribe(lq, q, n); } -void SubscriptionManager::setFlowControl( - uint32_t messages_, uint32_t bytes_, bool window_) -{ - setFlowControl(FlowControl(messages_, bytes_, window_)); -} +void SubscriptionManager::cancel(const std::string& dest) { return impl->cancel(dest); } -const FlowControl& SubscriptionManager::getFlowControl() const { return flowControl; } +void SubscriptionManager::setAutoStop(bool set) { impl->setAutoStop(set); } -void SubscriptionManager::setAcceptMode(bool c) { acceptMode=c; } +void SubscriptionManager::run() { impl->run(); } -void SubscriptionManager::setAcquireMode(bool a) { acquireMode=a; } +void SubscriptionManager::start() { impl->start(); } -void SubscriptionManager::setAckPolicy(const AckPolicy& a) { autoAck=a; } +void SubscriptionManager::wait() { impl->wait(); } -AckPolicy& SubscriptionManager::getAckPolicy() { return autoAck; } +void SubscriptionManager::stop() { impl->stop(); } -void SubscriptionManager::cancel(const std::string dest) -{ - sync(session).messageCancel(dest); - dispatcher.cancel(dest); +bool SubscriptionManager::get(Message& result, const std::string& queue, sys::Duration timeout) { + return impl->get(result, queue, timeout); } -void SubscriptionManager::setAutoStop(bool set) { autoStop=set; } +Message SubscriptionManager::get(const std::string& queue, sys::Duration timeout) { + return impl->get(queue, timeout); +} + +Session SubscriptionManager::getSession() const { return impl->getSession(); } -void SubscriptionManager::run() -{ - dispatcher.setAutoStop(autoStop); - dispatcher.run(); +Subscription SubscriptionManager::getSubscription(const std::string& name) const { + return impl->getSubscription(name); +} +void SubscriptionManager::registerFailoverHandler (boost::function<void ()> fh) { + impl->registerFailoverHandler(fh); } -void SubscriptionManager::stop() -{ - dispatcher.stop(); +void SubscriptionManager::setFlowControl(const std::string& name, const FlowControl& flow) { + impl->setFlowControl(name, flow); } -bool SubscriptionManager::get(Message& result, const std::string& queue, sys::Duration timeout) { - LocalQueue lq; - std::string unique = framing::Uuid(true).str(); - subscribe(lq, queue, FlowControl::messageCredit(1), unique); - AutoCancel ac(*this, unique); - //first wait for message to be delivered if a timeout has been specified - if (timeout && lq.get(result, timeout)) return true; - //make sure message is not on queue before final check - sync(session).messageFlush(unique); - return lq.get(result, 0); +void SubscriptionManager::setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window) { + impl->setFlowControl(name, FlowControl(messages, bytes, window)); } +void SubscriptionManager::setFlowControl(uint32_t messages, uint32_t bytes, bool window) { + impl->setFlowControl(messages, bytes, window); +} + +void SubscriptionManager::setAcceptMode(AcceptMode mode) { impl->setAcceptMode(mode); } +void SubscriptionManager::setAcquireMode(AcquireMode mode) { impl->setAcquireMode(mode); } + }} // namespace qpid::client -#endif + diff --git a/cpp/src/qpid/client/SubscriptionManager.h b/cpp/src/qpid/client/SubscriptionManager.h deleted file mode 100644 index 3dad15fd29..0000000000 --- a/cpp/src/qpid/client/SubscriptionManager.h +++ /dev/null @@ -1,210 +0,0 @@ -#ifndef QPID_CLIENT_SUBSCRIPTIONMANAGER_H -#define QPID_CLIENT_SUBSCRIPTIONMANAGER_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/client/Dispatcher.h> -#include <qpid/client/Completion.h> -#include <qpid/client/Session.h> -#include <qpid/client/MessageListener.h> -#include <qpid/client/LocalQueue.h> -#include <qpid/client/FlowControl.h> -#include <qpid/sys/Runnable.h> -#include <set> -#include <sstream> - -namespace qpid { -namespace client { - -/** - * A class to help create and manage subscriptions. - * - * Set up your subscriptions, then call run() to have messages - * delivered. - * - * \ingroup clientapi - */ -class SubscriptionManager : public sys::Runnable -{ - typedef sys::Mutex::ScopedLock Lock; - typedef sys::Mutex::ScopedUnlock Unlock; - - void subscribeInternal(const std::string& q, const std::string& dest, const FlowControl&); - - qpid::client::Dispatcher dispatcher; - qpid::client::AsyncSession session; - FlowControl flowControl; - AckPolicy autoAck; - bool acceptMode; - bool acquireMode; - bool autoStop; - - public: - /** Create a new SubscriptionManager associated with a session */ - SubscriptionManager(const Session& session); - - /** - * Subscribe a MessagesListener to receive messages from queue. - * - * Provide your own subclass of MessagesListener to process - * incoming messages. It will be called for each message received. - * - *@param listener Listener object to receive messages. - *@param queue Name of the queue to subscribe to. - *@param flow initial FlowControl for the subscription. - *@param tag Unique destination tag for the listener. - * If not specified, the queue name is used. - */ - void subscribe(MessageListener& listener, - const std::string& queue, - const FlowControl& flow, - const std::string& tag=std::string()); - - /** - * Subscribe a LocalQueue to receive messages from queue. - * - * Incoming messages are stored in the queue for you to retrieve. - * - *@param queue Name of the queue to subscribe to. - *@param flow initial FlowControl for the subscription. - *@param tag Unique destination tag for the listener. - * If not specified, the queue name is used. - */ - void subscribe(LocalQueue& localQueue, - const std::string& queue, - const FlowControl& flow, - const std::string& tag=std::string()); - - /** - * Subscribe a MessagesListener to receive messages from queue. - * - * Provide your own subclass of MessagesListener to process - * incoming messages. It will be called for each message received. - * - *@param listener Listener object to receive messages. - *@param queue Name of the queue to subscribe to. - *@param tag Unique destination tag for the listener. - * If not specified, the queue name is used. - */ - void subscribe(MessageListener& listener, - const std::string& queue, - const std::string& tag=std::string()); - - /** - * Subscribe a LocalQueue to receive messages from queue. - * - * Incoming messages are stored in the queue for you to retrieve. - * - *@param queue Name of the queue to subscribe to. - *@param tag Unique destination tag for the listener. - * If not specified, the queue name is used. - */ - void subscribe(LocalQueue& localQueue, - const std::string& queue, - const std::string& tag=std::string()); - - - /** Get a single message from a queue. - *@param result is set to the message from the queue. - *@ - *@param timeout wait up this timeout for a message to appear. - *@return true if result was set, false if no message available after timeout. - */ - bool get(Message& result, const std::string& queue, sys::Duration timeout=0); - - /** Cancel a subscription. */ - void cancel(const std::string tag); - - /** Deliver messages until stop() is called. */ - void run(); - - /** If set true, run() will stop when all subscriptions - * are cancelled. If false, run will only stop when stop() - * is called. True by default. - */ - void setAutoStop(bool set=true); - - /** Cause run() to return */ - void stop(); - - static const uint32_t UNLIMITED=0xFFFFFFFF; - - /** Set the flow control for destination. */ - void setFlowControl(const std::string& destintion, const FlowControl& flow); - - /** Set the default initial flow control for subscriptions that do not specify it. */ - void setFlowControl(const FlowControl& flow); - - /** Get the default flow control for new subscriptions that do not specify it. */ - const FlowControl& getFlowControl() const; - - /** Set the flow control for destination tag. - *@param tag: name of the destination. - *@param messages: message credit. - *@param bytes: byte credit. - *@param window: if true use window-based flow control. - */ - void setFlowControl(const std::string& tag, uint32_t messages, uint32_t bytes, bool window=true); - - /** Set the initial flow control settings to be applied to each new subscribtion. - *@param messages: message credit. - *@param bytes: byte credit. - *@param window: if true use window-based flow control. - */ - void setFlowControl(uint32_t messages, uint32_t bytes, bool window=true); - - /** Set the accept-mode for new subscriptions. Defaults to true. - *@param required: if true messages must be confirmed by calling - *Message::acknowledge() or automatically, see setAckPolicy() - */ - void setAcceptMode(bool required); - - /** Set the acquire-mode for new subscriptions. Defaults to false. - *@param acquire: if false messages pre-acquired, if true - * messages are dequed on acknowledgement or on transfer - * depending on acceptMode. - */ - void setAcquireMode(bool acquire); - - /** Set the acknowledgement policy for new subscriptions. - * Default is to acknowledge every message automatically. - */ - void setAckPolicy(const AckPolicy& autoAck); - /** - * - */ - AckPolicy& getAckPolicy(); -}; - -/** AutoCancel cancels a subscription in its destructor */ -class AutoCancel { - public: - AutoCancel(SubscriptionManager& sm_, const std::string& tag_) : sm(sm_), tag(tag_) {} - ~AutoCancel() { sm.cancel(tag); } - private: - SubscriptionManager& sm; - std::string tag; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_SUBSCRIPTIONMANAGER_H*/ diff --git a/cpp/src/qpid/client/SubscriptionManagerImpl.cpp b/cpp/src/qpid/client/SubscriptionManagerImpl.cpp new file mode 100644 index 0000000000..a558d90be8 --- /dev/null +++ b/cpp/src/qpid/client/SubscriptionManagerImpl.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 "qpid/client/SubscriptionManager.h" +#include "qpid/client/SubscriptionManagerImpl.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/LocalQueueImpl.h" +#include "qpid/client/PrivateImplRef.h" +#include <qpid/client/Dispatcher.h> +#include <qpid/client/Session.h> +#include <qpid/client/MessageListener.h> +#include <qpid/framing/Uuid.h> +#include <set> +#include <sstream> + + +namespace qpid { +namespace client { + +SubscriptionManagerImpl::SubscriptionManagerImpl(const Session& s) + : dispatcher(s), session(s), autoStop(true) +{} + +Subscription SubscriptionManagerImpl::subscribe( + MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ + sys::Mutex::ScopedLock l(lock); + std::string name=n.empty() ? q:n; + boost::intrusive_ptr<SubscriptionImpl> si = new SubscriptionImpl(SubscriptionManager(this), q, ss, name, &listener); + dispatcher.listen(si); + //issue subscription request after listener is registered with dispatcher + si->subscribe(); + return subscriptions[name] = Subscription(si.get()); +} + +Subscription SubscriptionManagerImpl::subscribe( + LocalQueue& lq, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ + sys::Mutex::ScopedLock l(lock); + std::string name=n.empty() ? q:n; + boost::intrusive_ptr<SubscriptionImpl> si = new SubscriptionImpl(SubscriptionManager(this), q, ss, name, 0); + boost::intrusive_ptr<LocalQueueImpl> lqi = PrivateImplRef<LocalQueue>::get(lq); + lqi->queue=si->divert(); + si->subscribe(); + lqi->subscription = Subscription(si.get()); + return subscriptions[name] = lqi->subscription; +} + +Subscription SubscriptionManagerImpl::subscribe( + MessageListener& listener, const std::string& q, const std::string& n) +{ + return subscribe(listener, q, defaultSettings, n); +} + +Subscription SubscriptionManagerImpl::subscribe( + LocalQueue& lq, const std::string& q, const std::string& n) +{ + return subscribe(lq, q, defaultSettings, n); +} + +void SubscriptionManagerImpl::cancel(const std::string& dest) +{ + sys::Mutex::ScopedLock l(lock); + std::map<std::string, Subscription>::iterator i = subscriptions.find(dest); + if (i != subscriptions.end()) { + sync(session).messageCancel(dest); + dispatcher.cancel(dest); + Subscription s = i->second; + if (s.isValid()) + PrivateImplRef<Subscription>::get(s)->cancelDiversion(); + subscriptions.erase(i); + } +} + +void SubscriptionManagerImpl::setAutoStop(bool set) { autoStop=set; } + +void SubscriptionManagerImpl::run() +{ + dispatcher.setAutoStop(autoStop); + dispatcher.run(); +} + +void SubscriptionManagerImpl::start() +{ + dispatcher.setAutoStop(autoStop); + dispatcher.start(); +} + +void SubscriptionManagerImpl::wait() +{ + dispatcher.wait(); +} + +void SubscriptionManagerImpl::stop() +{ + dispatcher.stop(); +} + +bool SubscriptionManagerImpl::get(Message& result, const std::string& queue, sys::Duration timeout) { + LocalQueue lq; + std::string unique = framing::Uuid(true).str(); + subscribe(lq, queue, SubscriptionSettings(FlowControl::messageCredit(1)), unique); + SubscriptionManager sm(this); + AutoCancel ac(sm, unique); + //first wait for message to be delivered if a timeout has been specified + if (timeout && lq.get(result, timeout)) + return true; + //make sure message is not on queue before final check + sync(session).messageFlush(unique); + return lq.get(result, 0); +} + +Message SubscriptionManagerImpl::get(const std::string& queue, sys::Duration timeout) { + Message result; + if (!get(result, queue, timeout)) + throw Exception("Timed out waiting for a message"); + return result; +} + +Session SubscriptionManagerImpl::getSession() const { return session; } + +Subscription SubscriptionManagerImpl::getSubscription(const std::string& name) const { + sys::Mutex::ScopedLock l(lock); + std::map<std::string, Subscription>::const_iterator i = subscriptions.find(name); + if (i == subscriptions.end()) + throw Exception(QPID_MSG("Subscription not found: " << name)); + return i->second; +} + +void SubscriptionManagerImpl::registerFailoverHandler (boost::function<void ()> fh) { + dispatcher.registerFailoverHandler(fh); +} + +void SubscriptionManagerImpl::setFlowControl(const std::string& name, const FlowControl& flow) { + getSubscription(name).setFlowControl(flow); +} + +void SubscriptionManagerImpl::setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window) { + setFlowControl(name, FlowControl(messages, bytes, window)); +} + +}} // namespace qpid::client + + diff --git a/cpp/src/qpid/client/SubscriptionManagerImpl.h b/cpp/src/qpid/client/SubscriptionManagerImpl.h new file mode 100644 index 0000000000..6376a05c45 --- /dev/null +++ b/cpp/src/qpid/client/SubscriptionManagerImpl.h @@ -0,0 +1,278 @@ +#ifndef QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H +#define QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_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/client/Dispatcher.h> +#include <qpid/client/Completion.h> +#include <qpid/client/Session.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/client/MessageListener.h> +#include <qpid/client/LocalQueue.h> +#include <qpid/client/Subscription.h> +#include <qpid/sys/Runnable.h> +#include <qpid/RefCounted.h> +#include <set> +#include <sstream> + +namespace qpid { +namespace client { + +/** + * A class to help create and manage subscriptions. + * + * Set up your subscriptions, then call run() to have messages + * delivered. + * + * \ingroup clientapi + * + * \details + * + * <h2>Subscribing and canceling subscriptions</h2> + * + * <ul> + * <li> + * <p>subscribe()</p> + * <pre> SubscriptionManager subscriptions(session); + * Listener listener(subscriptions); + * subscriptions.subscribe(listener, myQueue);</pre> + * <pre> SubscriptionManager subscriptions(session); + * LocalQueue local_queue; + * subscriptions.subscribe(local_queue, string("message_queue"));</pre></li> + * <li> + * <p>cancel()</p> + * <pre>subscriptions.cancel();</pre></li> + * </ul> + * + * <h2>Waiting for messages (and returning)</h2> + * + * <ul> + * <li> + * <p>run()</p> + * <pre> // Give up control to receive messages + * subscriptions.run();</pre></li> + * <li> + * <p>stop()</p> + * <pre>.// Use this code in a listener to return from run() + * subscriptions.stop();</pre></li> + * <li> + * <p>setAutoStop()</p> + * <pre>.// Return from subscriptions.run() when last subscription is cancelled + *.subscriptions.setAutoStop(true); + *.subscriptons.run(); + * </pre></li> + * <li> + * <p>Ending a subscription in a listener</p> + * <pre> + * void Listener::received(Message& message) { + * + * if (message.getData() == "That's all, folks!") { + * subscriptions.cancel(message.getDestination()); + * } + * } + * </pre> + * </li> + * </ul> + * + */ +class SubscriptionManagerImpl : public sys::Runnable, public RefCounted +{ + public: + /** Create a new SubscriptionManagerImpl associated with a session */ + SubscriptionManagerImpl(const Session& session); + + /** + * Subscribe a MessagesListener to receive messages from queue. + * + * Provide your own subclass of MessagesListener to process + * incoming messages. It will be called for each message received. + * + *@param listener Listener object to receive messages. + *@param queue Name of the queue to subscribe to. + *@param settings settings for the subscription. + *@param name unique destination name for the subscription, defaults to queue name. + */ + Subscription subscribe(MessageListener& listener, + const std::string& queue, + const SubscriptionSettings& settings, + const std::string& name=std::string()); + + /** + * Subscribe a LocalQueue to receive messages from queue. + * + * Incoming messages are stored in the queue for you to retrieve. + * + *@param queue Name of the queue to subscribe to. + *@param flow initial FlowControl for the subscription. + *@param name unique destination name for the subscription, defaults to queue name. + * If not specified, the queue name is used. + */ + Subscription subscribe(LocalQueue& localQueue, + const std::string& queue, + const SubscriptionSettings& settings, + const std::string& name=std::string()); + + /** + * Subscribe a MessagesListener to receive messages from queue. + * + * Provide your own subclass of MessagesListener to process + * incoming messages. It will be called for each message received. + * + *@param listener Listener object to receive messages. + *@param queue Name of the queue to subscribe to. + *@param name unique destination name for the subscription, defaults to queue name. + * If not specified, the queue name is used. + */ + Subscription subscribe(MessageListener& listener, + const std::string& queue, + const std::string& name=std::string()); + + /** + * Subscribe a LocalQueue to receive messages from queue. + * + * Incoming messages are stored in the queue for you to retrieve. + * + *@param queue Name of the queue to subscribe to. + *@param name unique destination name for the subscription, defaults to queue name. + * If not specified, the queue name is used. + */ + Subscription subscribe(LocalQueue& localQueue, + const std::string& queue, + const std::string& name=std::string()); + + + /** Get a single message from a queue. + *@param result is set to the message from the queue. + *@param timeout wait up this timeout for a message to appear. + *@return true if result was set, false if no message available after timeout. + */ + bool get(Message& result, const std::string& queue, sys::Duration timeout=0); + + /** Get a single message from a queue. + *@param timeout wait up this timeout for a message to appear. + *@return message from the queue. + *@throw Exception if the timeout is exceeded. + */ + Message get(const std::string& queue, sys::Duration timeout=sys::TIME_INFINITE); + + /** Get a subscription by name. + *@throw Exception if not found. + */ + Subscription getSubscription(const std::string& name) const; + + /** Cancel a subscription. See also: Subscription.cancel() */ + void cancel(const std::string& name); + + /** Deliver messages in the current thread until stop() is called. + * Only one thread may be running in a SubscriptionManager at a time. + * @see run + */ + void run(); + + /** Start a new thread to deliver messages. + * Only one thread may be running in a SubscriptionManager at a time. + * @see start + */ + void start(); + + /** + * Wait for the thread started by a call to start() to complete. + */ + void wait(); + + /** If set true, run() will stop when all subscriptions + * are cancelled. If false, run will only stop when stop() + * is called. True by default. + */ + void setAutoStop(bool set=true); + + /** Stop delivery. Causes run() to return, or the thread started with start() to exit. */ + void stop(); + + static const uint32_t UNLIMITED=0xFFFFFFFF; + + /** Set the flow control for a subscription. */ + void setFlowControl(const std::string& name, const FlowControl& flow); + + /** Set the flow control for a subscription. + *@param name: name of the subscription. + *@param messages: message credit. + *@param bytes: byte credit. + *@param window: if true use window-based flow control. + */ + void setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window=true); + + /** Set the default settings for subscribe() calls that don't + * include a SubscriptionSettings parameter. + */ + void setDefaultSettings(const SubscriptionSettings& s) { defaultSettings = s; } + + /** Get the default settings for subscribe() calls that don't + * include a SubscriptionSettings parameter. + */ + const SubscriptionSettings& getDefaultSettings() const { return defaultSettings; } + + /** Get the default settings for subscribe() calls that don't + * include a SubscriptionSettings parameter. + */ + SubscriptionSettings& getDefaultSettings() { return defaultSettings; } + + /** + * Set the default flow control settings for subscribe() calls + * that don't include a SubscriptionSettings parameter. + * + *@param messages: message credit. + *@param bytes: byte credit. + *@param window: if true use window-based flow control. + */ + void setFlowControl(uint32_t messages, uint32_t bytes, bool window=true) { + defaultSettings.flowControl = FlowControl(messages, bytes, window); + } + + /** + *Set the default accept-mode for subscribe() calls that don't + *include a SubscriptionSettings parameter. + */ + void setAcceptMode(AcceptMode mode) { defaultSettings.acceptMode = mode; } + + /** + * Set the default acquire-mode subscribe()s that don't specify SubscriptionSettings. + */ + void setAcquireMode(AcquireMode mode) { defaultSettings.acquireMode = mode; } + + void registerFailoverHandler ( boost::function<void ()> fh ); + + Session getSession() const; + + private: + mutable sys::Mutex lock; + qpid::client::Dispatcher dispatcher; + qpid::client::AsyncSession session; + bool autoStop; + SubscriptionSettings defaultSettings; + std::map<std::string, Subscription> subscriptions; +}; + + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H*/ diff --git a/cpp/src/qpid/client/TCPConnector.cpp b/cpp/src/qpid/client/TCPConnector.cpp new file mode 100644 index 0000000000..1a6e51d54d --- /dev/null +++ b/cpp/src/qpid/client/TCPConnector.cpp @@ -0,0 +1,327 @@ +/* + * + * 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/TCPConnector.h" + +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Codec.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/Msg.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::framing; +using boost::format; +using boost::str; + +// Static constructor which registers connector here +namespace { + Connector* create(framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new TCPConnector(v, s, c); + } + + struct StaticInit { + StaticInit() { + Connector::registerFactory("tcp", &create); + }; + } init; +} + +struct TCPConnector::Buff : public AsynchIO::BufferBase { + Buff(size_t size) : AsynchIO::BufferBase(new char[size], size) {} + ~Buff() { delete [] bytes;} +}; + +TCPConnector::TCPConnector(ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + lastEof(0), + currentSize(0), + bounds(cimpl), + version(ver), + initiated(false), + closed(true), + joined(true), + shutdownHandler(0), + aio(0), + impl(cimpl->shared_from_this()) +{ + QPID_LOG(debug, "TCPConnector created for " << version.toString()); + settings.configureSocket(socket); +} + +TCPConnector::~TCPConnector() { + close(); +} + +void TCPConnector::connect(const std::string& host, int port) { + Mutex::ScopedLock l(lock); + assert(closed); + assert(joined); + poller = Poller::shared_ptr(new Poller); + AsynchConnector::create(socket, + poller, + host, port, + boost::bind(&TCPConnector::connected, this, _1), + boost::bind(&TCPConnector::connectFailed, this, _3)); + closed = false; + joined = false; + receiver = Thread(this); +} + +void TCPConnector::connected(const Socket&) { + aio = AsynchIO::create(socket, + boost::bind(&TCPConnector::readbuff, this, _1, _2), + boost::bind(&TCPConnector::eof, this, _1), + boost::bind(&TCPConnector::eof, this, _1), + 0, // closed + 0, // nobuffs + boost::bind(&TCPConnector::writebuff, this, _1)); + for (int i = 0; i < 32; i++) { + aio->queueReadBuffer(new Buff(maxFrameSize)); + } + aio->start(poller); + + identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); + ProtocolInitiation init(version); + writeDataBlock(init); +} + +void TCPConnector::connectFailed(const std::string& msg) { + QPID_LOG(warning, "Connecting failed: " << msg); + closed = true; + poller->shutdown(); + closeInternal(); + if (shutdownHandler) + shutdownHandler->shutdown(); +} + +bool TCPConnector::closeInternal() { + bool ret; + { + Mutex::ScopedLock l(lock); + ret = !closed; + if (!closed) { + closed = true; + aio->queueForDeletion(); + poller->shutdown(); + } + if (joined || receiver.id() == Thread::current().id()) { + return ret; + } + joined = true; + } + receiver.join(); + return ret; +} + +void TCPConnector::close() { + closeInternal(); +} + +void TCPConnector::abort() { + // Can't abort a closed connection + if (!closed) { + if (aio) { + // Established connection + aio->requestCallback(boost::bind(&TCPConnector::eof, this, _1)); + } else { + // We're still connecting + connectFailed("Connection timedout"); + } + } +} + +void TCPConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void TCPConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* TCPConnector::getOutputHandler() { + return this; +} + +sys::ShutdownHandler* TCPConnector::getShutdownHandler() const { + return shutdownHandler; +} + +const std::string& TCPConnector::getIdentifier() const { + return identifier; +} + +void TCPConnector::send(AMQFrame& frame) { + Mutex::ScopedLock l(lock); + frames.push_back(frame); + //only ask to write if this is the end of a frameset or if we + //already have a buffers worth of data + currentSize += frame.encodedSize(); + bool notifyWrite = false; + if (frame.getEof()) { + lastEof = frames.size(); + notifyWrite = true; + } else { + notifyWrite = (currentSize >= maxFrameSize); + } + if (notifyWrite && !closed) aio->notifyPendingWrite(); +} + +void TCPConnector::handleClosed() { + if (closeInternal() && shutdownHandler) + shutdownHandler->shutdown(); +} + +void TCPConnector::writebuff(AsynchIO& /*aio*/) +{ + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + if (codec->canEncode()) { + std::auto_ptr<AsynchIO::BufferBase> buffer = std::auto_ptr<AsynchIO::BufferBase>(aio->getQueuedBuffer()); + if (!buffer.get()) buffer = std::auto_ptr<AsynchIO::BufferBase>(new Buff(maxFrameSize)); + + size_t encoded = codec->encode(buffer->bytes, buffer->byteCount); + + buffer->dataStart = 0; + buffer->dataCount = encoded; + aio->queueWrite(buffer.release()); + } +} + +// Called in IO thread. +bool TCPConnector::canEncode() +{ + Mutex::ScopedLock l(lock); + //have at least one full frameset or a whole buffers worth of data + return lastEof || currentSize >= maxFrameSize; +} + +// Called in IO thread. +size_t TCPConnector::encode(const char* buffer, size_t size) +{ + framing::Buffer out(const_cast<char*>(buffer), size); + size_t bytesWritten(0); + { + Mutex::ScopedLock l(lock); + while (!frames.empty() && out.available() >= frames.front().encodedSize() ) { + frames.front().encode(out); + QPID_LOG(trace, "SENT " << identifier << ": " << frames.front()); + frames.pop_front(); + if (lastEof) --lastEof; + } + bytesWritten = size - out.available(); + currentSize -= bytesWritten; + } + if (bounds) bounds->reduce(bytesWritten); + return bytesWritten; +} + +bool TCPConnector::readbuff(AsynchIO& aio, AsynchIO::BufferBase* buff) +{ + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + int32_t decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount); + // TODO: unreading needs to go away, and when we can cope + // with multiple sub-buffers in the general buffer scheme, it will + if (decoded < 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); + } + return true; +} + +size_t TCPConnector::decode(const char* buffer, size_t size) +{ + framing::Buffer in(const_cast<char*>(buffer), size); + if (!initiated) { + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); + if(!(protocolInit==version)){ + throw Exception(QPID_MSG("Unsupported version: " << protocolInit + << " supported version " << version)); + } + } + initiated = true; + } + AMQFrame frame; + while(frame.decode(in)){ + QPID_LOG(trace, "RECV " << identifier << ": " << frame); + input->received(frame); + } + return size - in.available(); +} + +void TCPConnector::writeDataBlock(const AMQDataBlock& data) { + AsynchIO::BufferBase* buff = new Buff(maxFrameSize); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void TCPConnector::eof(AsynchIO&) { + handleClosed(); +} + +void TCPConnector::run() { + // Keep the connection impl in memory until run() completes. + boost::shared_ptr<ConnectionImpl> protect = impl.lock(); + assert(protect); + try { + Dispatcher d(poller); + + d.run(); + } catch (const std::exception& e) { + QPID_LOG(error, QPID_MSG("FAIL " << identifier << ": " << e.what())); + handleClosed(); + } + try { + socket.close(); + } catch (const std::exception&) {} +} + +void TCPConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl) +{ + securityLayer = sl; + securityLayer->init(this); +} + + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/TCPConnector.h b/cpp/src/qpid/client/TCPConnector.h new file mode 100644 index 0000000000..6dc07d1f5d --- /dev/null +++ b/cpp/src/qpid/client/TCPConnector.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 _TCPConnector_ +#define _TCPConnector_ + +#include "Connector.h" +#include "qpid/client/Bounds.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/Codec.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/Thread.h" + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <deque> +#include <string> + +namespace qpid { +namespace client { + +class TCPConnector : public Connector, public sys::Codec, private sys::Runnable +{ + typedef std::deque<framing::AMQFrame> Frames; + struct Buff; + + const uint16_t maxFrameSize; + + sys::Mutex lock; + Frames frames; // Outgoing frame queue + size_t lastEof; // Position after last EOF in frames + uint64_t currentSize; + Bounds* bounds; + + framing::ProtocolVersion version; + bool initiated; + bool closed; + bool joined; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + sys::Thread receiver; + + sys::Socket socket; + + sys::AsynchIO* aio; + std::string identifier; + boost::shared_ptr<sys::Poller> poller; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + + ~TCPConnector(); + + void run(); + void handleClosed(); + bool closeInternal(); + + virtual void connected(const qpid::sys::Socket&); + void connectFailed(const std::string& msg); + bool readbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); + void writebuff(qpid::sys::AsynchIO&); + void writeDataBlock(const framing::AMQDataBlock& data); + void eof(qpid::sys::AsynchIO&); + + boost::weak_ptr<ConnectionImpl> impl; + + void connect(const std::string& host, int port); + void close(); + void send(framing::AMQFrame& frame); + void abort(); + + void setInputHandler(framing::InputHandler* handler); + void setShutdownHandler(sys::ShutdownHandler* handler); + sys::ShutdownHandler* getShutdownHandler() const; + framing::OutputHandler* getOutputHandler(); + const std::string& getIdentifier() const; + void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + +public: + TCPConnector(framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); + unsigned int getSSF() { return 0; } +}; + +}} // namespace qpid::client + +#endif /* _TCPConnector_ */ diff --git a/cpp/src/qpid/client/TypedResult.h b/cpp/src/qpid/client/TypedResult.h deleted file mode 100644 index 5306997d74..0000000000 --- a/cpp/src/qpid/client/TypedResult.h +++ /dev/null @@ -1,65 +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. - * - */ - -#ifndef _TypedResult_ -#define _TypedResult_ - -#include "Completion.h" - -namespace qpid { -namespace client { - -/** - * Returned by asynchronous commands that return a result. - * You can use get() to wait for completion and get the result value. - * \ingroup clientapi - */ -template <class T> class TypedResult : public Completion -{ - T result; - bool decoded; - -public: - ///@internal - TypedResult(Future f, shared_ptr<SessionImpl> s) : Completion(f, s), decoded(false) {} - - /** - * Wait for the asynchronous command that returned this TypedResult to complete - * and return its result. - * - *@return The result returned by the command. - *@exception If the command returns an error, get() throws an exception. - * - */ - T& get() - { - if (!decoded) { - future.decodeResult(result, *session); - decoded = true; - } - - return result; - } -}; - -}} - -#endif diff --git a/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp b/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp new file mode 100644 index 0000000000..80be5c56f3 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp @@ -0,0 +1,111 @@ +/* + * + * 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 "AcceptTracker.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +void AcceptTracker::State::accept() +{ + unconfirmed.add(unaccepted); + unaccepted.clear(); +} + +void AcceptTracker::State::release() +{ + unaccepted.clear(); +} + +uint32_t AcceptTracker::State::acceptsPending() +{ + return unconfirmed.size(); +} + +void AcceptTracker::State::completed(qpid::framing::SequenceSet& set) +{ + unconfirmed.remove(set); +} + +void AcceptTracker::delivered(const std::string& destination, const qpid::framing::SequenceNumber& id) +{ + aggregateState.unaccepted.add(id); + destinationState[destination].unaccepted.add(id); +} + +void AcceptTracker::accept(qpid::client::AsyncSession& session) +{ + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.accept(); + } + Record record; + record.status = session.messageAccept(aggregateState.unaccepted); + record.accepted = aggregateState.unaccepted; + pending.push_back(record); + aggregateState.accept(); +} + +void AcceptTracker::release(qpid::client::AsyncSession& session) +{ + session.messageRelease(aggregateState.unaccepted); + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.release(); + } + aggregateState.release(); +} + +uint32_t AcceptTracker::acceptsPending() +{ + checkPending(); + return aggregateState.acceptsPending(); +} + +uint32_t AcceptTracker::acceptsPending(const std::string& destination) +{ + checkPending(); + return destinationState[destination].acceptsPending(); +} + +void AcceptTracker::reset() +{ + destinationState.clear(); + aggregateState.unaccepted.clear(); + aggregateState.unconfirmed.clear(); + pending.clear(); +} + +void AcceptTracker::checkPending() +{ + while (!pending.empty() && pending.front().status.isComplete()) { + completed(pending.front().accepted); + pending.pop_front(); + } +} + +void AcceptTracker::completed(qpid::framing::SequenceSet& set) +{ + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.completed(set); + } + aggregateState.completed(set); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/AcceptTracker.h b/cpp/src/qpid/client/amqp0_10/AcceptTracker.h new file mode 100644 index 0000000000..fb58a3a8c8 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/AcceptTracker.h @@ -0,0 +1,85 @@ +#ifndef QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H +#define QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_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/AsyncSession.h" +#include "qpid/client/Completion.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/SequenceSet.h" +#include <deque> +#include <map> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +/** + * Tracks the set of messages requiring acceptance, and those for + * which an accept has been issued but is yet to be confirmed + * complete. + */ +class AcceptTracker +{ + public: + void delivered(const std::string& destination, const qpid::framing::SequenceNumber& id); + void accept(qpid::client::AsyncSession&); + void release(qpid::client::AsyncSession&); + uint32_t acceptsPending(); + uint32_t acceptsPending(const std::string& destination); + void reset(); + private: + struct State + { + /** + * ids of messages that have been delivered but not yet + * accepted + */ + qpid::framing::SequenceSet unaccepted; + /** + * ids of messages for which an accpet has been issued but not + * yet confirmed as completed + */ + qpid::framing::SequenceSet unconfirmed; + + void accept(); + void release(); + uint32_t acceptsPending(); + void completed(qpid::framing::SequenceSet&); + }; + typedef std::map<std::string, State> StateMap; + struct Record + { + qpid::client::Completion status; + qpid::framing::SequenceSet accepted; + }; + typedef std::deque<Record> Records; + + State aggregateState; + StateMap destinationState; + Records pending; + + void checkPending(); + void completed(qpid::framing::SequenceSet&); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp b/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp new file mode 100644 index 0000000000..b70e67d12f --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp @@ -0,0 +1,819 @@ +/* + * + * 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/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/client/amqp0_10/CodecsInternal.h" +#include "qpid/client/amqp0_10/MessageSource.h" +#include "qpid/client/amqp0_10/MessageSink.h" +#include "qpid/client/amqp0_10/OutgoingMessage.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Variant.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/ExchangeBoundResult.h" +#include "qpid/framing/ExchangeQueryResult.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/QueueQueryResult.h" +#include "qpid/framing/ReplyTo.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/Uuid.h" +#include <boost/assign.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::Exception; +using qpid::messaging::Address; +using qpid::messaging::InvalidAddress; +using qpid::messaging::Variant; +using qpid::framing::ExchangeBoundResult; +using qpid::framing::ExchangeQueryResult; +using qpid::framing::FieldTable; +using qpid::framing::QueueQueryResult; +using qpid::framing::ReplyTo; +using qpid::framing::Uuid; +using namespace qpid::framing::message; +using namespace boost::assign; + + +namespace{ +const Variant EMPTY_VARIANT; +const FieldTable EMPTY_FIELD_TABLE; +const std::string EMPTY_STRING; + +//option names +const std::string BROWSE("browse"); +const std::string EXCLUSIVE("exclusive"); +const std::string NO_LOCAL("no-local"); +const std::string FILTER("filter"); +const std::string RELIABILITY("reliability"); +const std::string NAME("subscription-name"); +const std::string NODE_PROPERTIES("node-properties"); +const std::string X_PROPERTIES("x-properties"); + +//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 QUEUE_ADDRESS("queue"); +const std::string TOPIC_ADDRESS("topic"); + +const std::string UNRELIABLE("unreliable"); +const std::string AT_MOST_ONCE("at-most-once"); +const std::string AT_LEAST_ONCE("at-least-once"); +const std::string EXACTLY_ONCE("exactly-once"); +const std::string DURABLE_SUBSCRIPTION("durable"); +const std::string DURABLE("durable"); + +const std::string TOPIC_EXCHANGE("topic"); +const std::string FANOUT_EXCHANGE("fanout"); +const std::string DIRECT_EXCHANGE("direct"); +const std::string HEADERS_EXCHANGE("headers"); +const std::string XML_EXCHANGE("xml"); +const std::string WILDCARD_ANY("*"); +} + +//some amqp 0-10 specific options +namespace xamqp{ +const std::string AUTO_DELETE("auto-delete"); +const std::string EXCHANGE_TYPE("type"); +const std::string EXCLUSIVE("exclusive"); +const std::string ALTERNATE_EXCHANGE("alternate-exchange"); +const std::string QUEUE_ARGUMENTS("x-queue-arguments"); +const std::string SUBSCRIBE_ARGUMENTS("x-subscribe-arguments"); +} + +class Node +{ + protected: + enum CheckMode {FOR_RECEIVER, FOR_SENDER}; + + Node(const Address& address); + + const std::string name; + Variant createPolicy; + Variant assertPolicy; + Variant deletePolicy; + + static bool enabled(const Variant& policy, CheckMode mode); + static bool createEnabled(const Address& address, CheckMode mode); + static void convert(const Variant& option, FieldTable& arguments); + static std::vector<std::string> RECEIVER_MODES; + static std::vector<std::string> SENDER_MODES; +}; + +class Queue : protected Node +{ + public: + Queue(const Address& address); + protected: + void checkCreate(qpid::client::AsyncSession&, CheckMode); + void checkAssert(qpid::client::AsyncSession&, CheckMode); + void checkDelete(qpid::client::AsyncSession&, CheckMode); + private: + bool durable; + bool autoDelete; + bool exclusive; + std::string alternateExchange; + FieldTable arguments; + + void configure(const Address&); +}; + +class Exchange : protected Node +{ + public: + Exchange(const Address& address); + protected: + void checkCreate(qpid::client::AsyncSession&, CheckMode); + void checkAssert(qpid::client::AsyncSession&, CheckMode); + void checkDelete(qpid::client::AsyncSession&, CheckMode); + const std::string& getDesiredExchangeType() { return type; } + + private: + std::string type; + bool typeSpecified; + bool durable; + bool autoDelete; + std::string alternateExchange; + FieldTable arguments; + + void configure(const Address&); +}; + +class QueueSource : public Queue, public MessageSource +{ + public: + QueueSource(const Address& address); + void subscribe(qpid::client::AsyncSession& session, const std::string& destination); + void cancel(qpid::client::AsyncSession& session, const std::string& destination); + private: + const AcceptMode acceptMode; + const AcquireMode acquireMode; + bool exclusive; + FieldTable options; +}; + +class Subscription : public Exchange, public MessageSource +{ + public: + Subscription(const Address&, const std::string& exchangeType=""); + void subscribe(qpid::client::AsyncSession& session, const std::string& destination); + void cancel(qpid::client::AsyncSession& session, const std::string& destination); + private: + struct Binding + { + Binding(const std::string& exchange, const std::string& key, const FieldTable& options = EMPTY_FIELD_TABLE); + + std::string exchange; + std::string key; + FieldTable options; + }; + + typedef std::vector<Binding> Bindings; + + const std::string queue; + const bool reliable; + const bool durable; + FieldTable queueOptions; + FieldTable subscriptionOptions; + Bindings bindings; + + void bindSpecial(const std::string& exchangeType); + void bind(const std::string& subject); + void bind(const std::string& subject, const Variant& filter); + void bind(const std::string& subject, const Variant::Map& filter); + void bind(const std::string& subject, const Variant::List& filter); + void add(const std::string& exchange, const std::string& key, const FieldTable& options = EMPTY_FIELD_TABLE); + static std::string getSubscriptionName(const std::string& base, const Variant& name); +}; + +class ExchangeSink : public Exchange, public MessageSink +{ + public: + ExchangeSink(const Address& name); + void declare(qpid::client::AsyncSession& session, const std::string& name); + void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message); + void cancel(qpid::client::AsyncSession& session, const std::string& name); + private: +}; + +class QueueSink : public Queue, public MessageSink +{ + public: + QueueSink(const Address& name); + void declare(qpid::client::AsyncSession& session, const std::string& name); + void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message); + void cancel(qpid::client::AsyncSession& session, const std::string& name); + private: +}; + + +bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address); +bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address); + +bool in(const Variant& value, const std::vector<std::string>& choices) +{ + if (!value.isVoid()) { + for (std::vector<std::string>::const_iterator i = choices.begin(); i != choices.end(); ++i) { + if (value.asString() == *i) return true; + } + } + return false; +} + +bool getReceiverPolicy(const Address& address, const std::string& key) +{ + return in(address.getOption(key), list_of<std::string>(ALWAYS)(RECEIVER)); +} + +bool getSenderPolicy(const Address& address, const std::string& key) +{ + return in(address.getOption(key), list_of<std::string>(ALWAYS)(SENDER)); +} + +bool is_unreliable(const Address& address) +{ + return in(address.getOption(RELIABILITY), list_of<std::string>(UNRELIABLE)(AT_MOST_ONCE)); +} + +bool is_reliable(const Address& address) +{ + return in(address.getOption(RELIABILITY), list_of<std::string>(AT_LEAST_ONCE)(EXACTLY_ONCE)); +} + +std::string checkAddressType(qpid::client::Session session, const Address& address) +{ + std::string type = address.getType(); + if (type.empty()) { + ExchangeBoundResult result = session.exchangeBound(arg::exchange=address.getName(), arg::queue=address.getName()); + if (result.getQueueNotFound() && result.getExchangeNotFound()) { + //neither a queue nor an exchange exists with that name; treat it as a queue + type = QUEUE_ADDRESS; + } else if (result.getExchangeNotFound()) { + //name refers to a queue + type = QUEUE_ADDRESS; + } else if (result.getQueueNotFound()) { + //name refers to an exchange + type = TOPIC_ADDRESS; + } else { + //both a queue and exchange exist for that name + throw InvalidAddress("Ambiguous address, please specify queue or topic as node type"); + } + } + return type; +} + +std::auto_ptr<MessageSource> AddressResolution::resolveSource(qpid::client::Session session, + const Address& address) +{ + std::string type = checkAddressType(session, address); + if (type == TOPIC_ADDRESS) { + std::auto_ptr<MessageSource> source(new Subscription(address)); + QPID_LOG(debug, "treating source address as topic: " << address); + return source; + } else if (type == QUEUE_ADDRESS) { + std::auto_ptr<MessageSource> source(new QueueSource(address)); + QPID_LOG(debug, "treating source address as queue: " << address); + return source; + } else { + throw InvalidAddress("Unrecognised type: " + type); + } +} + + +std::auto_ptr<MessageSink> AddressResolution::resolveSink(qpid::client::Session session, + const qpid::messaging::Address& address) +{ + std::string type = checkAddressType(session, address); + if (type == TOPIC_ADDRESS) { + std::auto_ptr<MessageSink> sink(new ExchangeSink(address)); + QPID_LOG(debug, "treating target address as topic: " << address); + return sink; + } else if (type == QUEUE_ADDRESS) { + std::auto_ptr<MessageSink> sink(new QueueSink(address)); + QPID_LOG(debug, "treating target address as queue: " << address); + return sink; + } else { + throw InvalidAddress("Unrecognised type: " + type); + } +} + +const Variant& getNestedOption(const Variant::Map& options, const std::vector<std::string>& keys, size_t index = 0) +{ + Variant::Map::const_iterator i = options.find(keys[index]); + if (i == options.end()) { + return EMPTY_VARIANT; + } else if (index+1 < keys.size()) { + return getNestedOption(i->second.asMap(), keys, index+1); + } else { + return i->second; + } +} + +QueueSource::QueueSource(const Address& address) : + Queue(address), + acceptMode(is_unreliable(address) ? ACCEPT_MODE_NONE : ACCEPT_MODE_EXPLICIT), + acquireMode(address.getOption(BROWSE).asBool() ? ACQUIRE_MODE_NOT_ACQUIRED : ACQUIRE_MODE_PRE_ACQUIRED), + exclusive(false) +{ + //extract subscription arguments from address options + const Variant& x = address.getOption(X_PROPERTIES); + if (!x.isVoid()) { + const Variant::Map& xProps = x.asMap(); + Variant::Map passthrough; + for (Variant::Map::const_iterator i = xProps.begin(); i != xProps.end(); ++i) { + if (i->first == xamqp::EXCLUSIVE) exclusive = i->second; + else passthrough[i->first] = i->second; + } + translate(passthrough, options); + } +} + +void QueueSource::subscribe(qpid::client::AsyncSession& session, const std::string& destination) +{ + checkCreate(session, FOR_RECEIVER); + checkAssert(session, FOR_RECEIVER); + session.messageSubscribe(arg::queue=name, + arg::destination=destination, + arg::acceptMode=acceptMode, + arg::acquireMode=acquireMode, + arg::exclusive=exclusive, + arg::arguments=options); +} + +void QueueSource::cancel(qpid::client::AsyncSession& session, const std::string& destination) +{ + session.messageCancel(destination); + checkDelete(session, FOR_RECEIVER); +} + +std::string Subscription::getSubscriptionName(const std::string& base, const Variant& name) +{ + if (name.isVoid()) { + return (boost::format("%1%_%2%") % base % Uuid(true).str()).str(); + } else { + return (boost::format("%1%_%2%") % base % name.asString()).str(); + } +} + +Subscription::Subscription(const Address& address, const std::string& exchangeType) + : Exchange(address), + queue(getSubscriptionName(name, address.getOption(NAME))), + reliable(is_reliable(address)), + durable(address.getOption(DURABLE_SUBSCRIPTION).asBool()) +{ + if (address.getOption(NO_LOCAL).asBool()) queueOptions.setInt(NO_LOCAL, 1); + const Variant& x = address.getOption(X_PROPERTIES); + if (!x.isVoid()) { + const Variant::Map& xProps = x.asMap(); + Variant::Map passthrough; + for (Variant::Map::const_iterator i = xProps.begin(); i != xProps.end(); ++i) { + if (i->first == xamqp::QUEUE_ARGUMENTS) convert(i->second.asMap(), queueOptions); + else passthrough[i->first] = i->second; + } + translate(passthrough, subscriptionOptions); + } + + const Variant& filter = address.getOption(FILTER); + if (!filter.isVoid()) { + bind(address.getSubject(), filter); + } else if (address.hasSubject()) { + //Note: This will not work for headers- or xml- exchange; + //fanout exchange will do no filtering. + //TODO: for headers- or xml- exchange can construct a match + //for the subject in the application-headers + bind(address.getSubject()); + } else { + //Neither a subject nor a filter has been defined, treat this + //as wanting to match all messages (Note: direct exchange is + //currently unable to support this case). + if (!exchangeType.empty()) bindSpecial(exchangeType); + else if (!getDesiredExchangeType().empty()) bindSpecial(getDesiredExchangeType()); + } +} + +void Subscription::add(const std::string& exchange, const std::string& key, const FieldTable& options) +{ + bindings.push_back(Binding(exchange, key, options)); +} + +void Subscription::subscribe(qpid::client::AsyncSession& session, const std::string& destination) +{ + //create exchange if required and specified by policy: + checkCreate(session, FOR_RECEIVER); + checkAssert(session, FOR_RECEIVER); + + //create subscription queue: + session.queueDeclare(arg::queue=queue, arg::exclusive=true, + arg::autoDelete=!reliable, arg::durable=durable, arg::arguments=queueOptions); + //bind subscription queue to exchange: + for (Bindings::const_iterator i = bindings.begin(); i != bindings.end(); ++i) { + session.exchangeBind(arg::queue=queue, arg::exchange=i->exchange, arg::bindingKey=i->key, arg::arguments=i->options); + } + //subscribe to subscription queue: + AcceptMode accept = reliable ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE; + session.messageSubscribe(arg::queue=queue, arg::destination=destination, + arg::exclusive=true, arg::acceptMode=accept, arg::arguments=subscriptionOptions); +} + +void Subscription::cancel(qpid::client::AsyncSession& session, const std::string& destination) +{ + session.messageCancel(destination); + session.queueDelete(arg::queue=queue); + checkDelete(session, FOR_RECEIVER); +} + +Subscription::Binding::Binding(const std::string& e, const std::string& k, const FieldTable& o): + exchange(e), key(k), options(o) {} + +ExchangeSink::ExchangeSink(const Address& address) : Exchange(address) {} + +void ExchangeSink::declare(qpid::client::AsyncSession& session, const std::string&) +{ + checkCreate(session, FOR_SENDER); + checkAssert(session, FOR_SENDER); +} + +void ExchangeSink::send(qpid::client::AsyncSession& session, const std::string&, OutgoingMessage& m) +{ + m.message.getDeliveryProperties().setRoutingKey(m.getSubject()); + m.status = session.messageTransfer(arg::destination=name, arg::content=m.message); +} + +void ExchangeSink::cancel(qpid::client::AsyncSession& session, const std::string&) +{ + checkDelete(session, FOR_SENDER); +} + +QueueSink::QueueSink(const Address& address) : Queue(address) {} + +void QueueSink::declare(qpid::client::AsyncSession& session, const std::string&) +{ + checkCreate(session, FOR_SENDER); + checkAssert(session, FOR_SENDER); +} +void QueueSink::send(qpid::client::AsyncSession& session, const std::string&, OutgoingMessage& m) +{ + m.message.getDeliveryProperties().setRoutingKey(name); + m.status = session.messageTransfer(arg::content=m.message); +} + +void QueueSink::cancel(qpid::client::AsyncSession& session, const std::string&) +{ + checkDelete(session, FOR_SENDER); +} + +Address AddressResolution::convert(const qpid::framing::ReplyTo& rt) +{ + Address address; + if (rt.getExchange().empty()) {//if default exchange, treat as queue + address.setName(rt.getRoutingKey()); + address.setType(QUEUE_ADDRESS); + } else { + address.setName(rt.getExchange()); + address.setSubject(rt.getRoutingKey()); + address.setType(TOPIC_ADDRESS); + } + return address; +} + +qpid::framing::ReplyTo AddressResolution::convert(const Address& address) +{ + if (address.getType() == QUEUE_ADDRESS || address.getType().empty()) { + return ReplyTo(EMPTY_STRING, address.getName()); + } else if (address.getType() == TOPIC_ADDRESS) { + return ReplyTo(address.getName(), address.getSubject()); + } else { + QPID_LOG(notice, "Unrecognised type for reply-to: " << address.getType()); + return ReplyTo(EMPTY_STRING, address.getName());//treat as queue + } +} + +bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address) +{ + return address.getType() == QUEUE_ADDRESS || + (address.getType().empty() && session.queueQuery(address.getName()).getQueue() == address.getName()); +} + +bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address) +{ + if (address.getType().empty()) { + return !session.exchangeQuery(address.getName()).getNotFound(); + } else if (address.getType() == TOPIC_ADDRESS) { + return true; + } else { + return false; + } +} + +void Subscription::bind(const std::string& subject) +{ + add(name, subject); +} + +void Subscription::bind(const std::string& subject, const Variant& filter) +{ + switch (filter.getType()) { + case qpid::messaging::VAR_MAP: + bind(subject, filter.asMap()); + break; + case qpid::messaging::VAR_LIST: + bind(subject, filter.asList()); + break; + default: + //TODO: if both subject _and_ filter are specified, combine in + //some way; for now we just ignore the subject in that case. + add(name, filter.asString()); + break; + } +} + +void Subscription::bind(const std::string& subject, const Variant::Map& filter) +{ + qpid::framing::FieldTable arguments; + translate(filter, arguments); + add(name, subject.empty() ? queue : subject, arguments); +} + +void Subscription::bind(const std::string& subject, const Variant::List& filter) +{ + for (Variant::List::const_iterator i = filter.begin(); i != filter.end(); ++i) { + bind(subject, *i); + } +} + +void Subscription::bindSpecial(const std::string& exchangeType) +{ + if (exchangeType == TOPIC_EXCHANGE) { + add(name, WILDCARD_ANY); + } else if (exchangeType == FANOUT_EXCHANGE) { + add(name, queue); + } else if (exchangeType == HEADERS_EXCHANGE) { + //TODO: add special binding for headers exchange to match all messages + } else if (exchangeType == XML_EXCHANGE) { + //TODO: add special binding for xml exchange to match all messages + } else { //E.g. direct + throw qpid::Exception(QPID_MSG("Cannot create binding to match all messages for exchange of type " << exchangeType)); + } +} + +Node::Node(const Address& address) : name(address.getName()), + createPolicy(address.getOption(CREATE)), + assertPolicy(address.getOption(ASSERT)), + deletePolicy(address.getOption(DELETE)) {} + +Queue::Queue(const Address& a) : Node(a), + durable(false), + autoDelete(false), + exclusive(false) +{ + configure(a); +} + +void Queue::checkCreate(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(createPolicy, mode)) { + QPID_LOG(debug, "Auto-creating queue '" << name << "'"); + try { + sync(session).queueDeclare(arg::queue=name, + arg::durable=durable, + arg::autoDelete=autoDelete, + arg::exclusive=exclusive, + arg::alternateExchange=alternateExchange, + arg::arguments=arguments); + } catch (const qpid::Exception& e) { + throw InvalidAddress((boost::format("Could not create queue %1%; %2%") % name % e.what()).str()); + } + } else { + try { + sync(session).queueDeclare(arg::queue=name, arg::passive=true); + } catch (const qpid::framing::NotFoundException& /*e*/) { + throw InvalidAddress((boost::format("Queue %1% does not exist") % name).str()); + } catch (const std::exception& e) { + throw InvalidAddress(e.what()); + } + } +} + +void Queue::checkDelete(qpid::client::AsyncSession& session, CheckMode mode) +{ + //Note: queue-delete will cause a session exception if the queue + //does not exist, the query here prevents obvious cases of this + //but there is a race whenever two deletions are made concurrently + //so careful use of the delete policy is recommended at present + if (enabled(deletePolicy, mode) && sync(session).queueQuery(name).getQueue() == name) { + QPID_LOG(debug, "Auto-deleting queue '" << name << "'"); + sync(session).queueDelete(arg::queue=name); + } +} + +void Queue::checkAssert(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(assertPolicy, mode)) { + QueueQueryResult result = sync(session).queueQuery(name); + if (result.getQueue() != name) { + throw InvalidAddress((boost::format("Queue not found: %1%") % name).str()); + } else { + if (durable && !result.getDurable()) { + throw InvalidAddress((boost::format("Queue not durable: %1%") % name).str()); + } + if (autoDelete && !result.getAutoDelete()) { + throw InvalidAddress((boost::format("Queue not set to auto-delete: %1%") % name).str()); + } + if (exclusive && !result.getExclusive()) { + throw InvalidAddress((boost::format("Queue not exclusive: %1%") % name).str()); + } + if (!alternateExchange.empty() && result.getAlternateExchange() != alternateExchange) { + throw InvalidAddress((boost::format("Alternate exchange does not match for %1%, expected %2%, got %3%") + % name % alternateExchange % result.getAlternateExchange()).str()); + } + for (FieldTable::ValueMap::const_iterator i = arguments.begin(); i != arguments.end(); ++i) { + FieldTable::ValuePtr v = result.getArguments().get(i->first); + if (!v) { + throw InvalidAddress((boost::format("Option %1% not set for %2%") % i->first % name).str()); + } else if (*i->second != *v) { + throw InvalidAddress((boost::format("Option %1% does not match for %2%, expected %3%, got %4%") + % i->first % name % *(i->second) % *v).str()); + } + } + } + } +} + +void Queue::configure(const Address& address) +{ + const Variant& v = address.getOption(NODE_PROPERTIES); + if (!v.isVoid()) { + Variant::Map nodeProps = v.asMap(); + durable = nodeProps[DURABLE]; + Variant::Map::const_iterator x = nodeProps.find(X_PROPERTIES); + if (x != nodeProps.end()) { + const Variant::Map& xProps = x->second.asMap(); + Variant::Map passthrough; + for (Variant::Map::const_iterator i = xProps.begin(); i != xProps.end(); ++i) { + if (i->first == xamqp::AUTO_DELETE) autoDelete = i->second; + else if (i->first == xamqp::EXCLUSIVE) exclusive = i->second; + else if (i->first == xamqp::ALTERNATE_EXCHANGE) alternateExchange = i->second.asString(); + else passthrough[i->first] = i->second; + } + translate(passthrough, arguments); + } + } +} + +Exchange::Exchange(const Address& a) : Node(a), + type(TOPIC_EXCHANGE), + typeSpecified(false), + durable(false), + autoDelete(false) +{ + configure(a); +} + +void Exchange::checkCreate(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(createPolicy, mode)) { + try { + sync(session).exchangeDeclare(arg::exchange=name, + arg::type=type, + arg::durable=durable, + arg::autoDelete=autoDelete, + arg::alternateExchange=alternateExchange, + arg::arguments=arguments); + } catch (const qpid::Exception& e) { + throw InvalidAddress((boost::format("Could not create exchange %1%; %2%") % name % e.what()).str()); + } + } else { + try { + sync(session).exchangeDeclare(arg::exchange=name, arg::passive=true); + } catch (const qpid::framing::NotFoundException& /*e*/) { + throw InvalidAddress((boost::format("Exchange %1% does not exist") % name).str()); + } catch (const std::exception& e) { + throw InvalidAddress(e.what()); + } + } +} + +void Exchange::checkDelete(qpid::client::AsyncSession& session, CheckMode mode) +{ + //Note: exchange-delete will cause a session exception if the + //exchange does not exist, the query here prevents obvious cases + //of this but there is a race whenever two deletions are made + //concurrently so careful use of the delete policy is recommended + //at present + if (enabled(deletePolicy, mode) && !sync(session).exchangeQuery(name).getNotFound()) { + sync(session).exchangeDelete(arg::exchange=name); + } +} + +void Exchange::checkAssert(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(assertPolicy, mode)) { + ExchangeQueryResult result = sync(session).exchangeQuery(name); + if (result.getNotFound()) { + throw InvalidAddress((boost::format("Exchange not found: %1%") % name).str()); + } else { + if (typeSpecified && result.getType() != type) { + throw InvalidAddress((boost::format("Exchange %1% is of incorrect type, expected %2% but got %3%") + % name % type % result.getType()).str()); + } + if (durable && !result.getDurable()) { + throw InvalidAddress((boost::format("Exchange not durable: %1%") % name).str()); + } + //Note: Can't check auto-delete or alternate-exchange via + //exchange-query-result as these are not returned + //TODO: could use a passive declare to check alternate-exchange + for (FieldTable::ValueMap::const_iterator i = arguments.begin(); i != arguments.end(); ++i) { + FieldTable::ValuePtr v = result.getArguments().get(i->first); + if (!v) { + throw InvalidAddress((boost::format("Option %1% not set for %2%") % i->first % name).str()); + } else if (i->second != v) { + throw InvalidAddress((boost::format("Option %1% does not match for %2%, expected %3%, got %4%") + % i->first % name % *(i->second) % *v).str()); + } + } + } + } +} + +void Exchange::configure(const Address& address) +{ + const Variant& v = address.getOption(NODE_PROPERTIES); + if (!v.isVoid()) { + Variant::Map nodeProps = v.asMap(); + durable = nodeProps[DURABLE]; + Variant::Map::const_iterator x = nodeProps.find(X_PROPERTIES); + if (x != nodeProps.end()) { + const Variant::Map& xProps = x->second.asMap(); + Variant::Map passthrough; + for (Variant::Map::const_iterator i = xProps.begin(); i != xProps.end(); ++i) { + if (i->first == xamqp::AUTO_DELETE) autoDelete = i->second; + else if (i->first == xamqp::EXCHANGE_TYPE) { type = i->second.asString(); typeSpecified = true; } + else if (i->first == xamqp::ALTERNATE_EXCHANGE) alternateExchange = i->second.asString(); + else passthrough[i->first] = i->second; + } + translate(passthrough, arguments); + } + } +} + + +bool Node::enabled(const Variant& policy, CheckMode mode) +{ + 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; +} + +bool Node::createEnabled(const Address& address, CheckMode mode) +{ + const Variant& policy = address.getOption(CREATE); + return enabled(policy, mode); +} + +void Node::convert(const Variant& options, FieldTable& arguments) +{ + if (!options.isVoid()) { + translate(options.asMap(), arguments); + } +} +std::vector<std::string> Node::RECEIVER_MODES = list_of<std::string>(ALWAYS) (RECEIVER); +std::vector<std::string> Node::SENDER_MODES = list_of<std::string>(ALWAYS) (SENDER); + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/AddressResolution.h b/cpp/src/qpid/client/amqp0_10/AddressResolution.h new file mode 100644 index 0000000000..01c8c51595 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/AddressResolution.h @@ -0,0 +1,64 @@ +#ifndef QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H +#define QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_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/Variant.h" +#include "qpid/client/Session.h" + +namespace qpid { + +namespace framing{ +class ReplyTo; +} + +namespace messaging { +class Address; +} + +namespace client { +namespace amqp0_10 { + +class MessageSource; +class MessageSink; + +/** + * Maps from a generic Address and optional Filter to an AMQP 0-10 + * MessageSource which will then be used by a ReceiverImpl instance + * created for the address. + */ +class AddressResolution +{ + public: + std::auto_ptr<MessageSource> resolveSource(qpid::client::Session session, + const qpid::messaging::Address& address); + + std::auto_ptr<MessageSink> resolveSink(qpid::client::Session session, + const qpid::messaging::Address& address); + + static qpid::messaging::Address convert(const qpid::framing::ReplyTo&); + static qpid::framing::ReplyTo convert(const qpid::messaging::Address&); + + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/Codecs.cpp b/cpp/src/qpid/client/amqp0_10/Codecs.cpp new file mode 100644 index 0000000000..ff72dfbf4e --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/Codecs.cpp @@ -0,0 +1,299 @@ +/* + * + * 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/amqp0_10/Codecs.h" +#include "qpid/messaging/Variant.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/List.h" +#include <algorithm> +#include <functional> + +using namespace qpid::framing; +using namespace qpid::messaging; + +namespace qpid { +namespace client { +namespace amqp0_10 { + +namespace { +const std::string iso885915("iso-8859-15"); +const std::string utf8("utf8"); +const std::string utf16("utf16"); +const std::string amqp0_10_binary("amqp0-10:binary"); +const std::string amqp0_10_bit("amqp0-10:bit"); +const std::string amqp0_10_datetime("amqp0-10:datetime"); +const std::string amqp0_10_struct("amqp0-10:struct"); +} + +template <class T, class U, class F> void convert(const T& from, U& to, F f) +{ + std::transform(from.begin(), from.end(), std::inserter(to, to.begin()), f); +} + +Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in); +FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in); +Variant toVariant(boost::shared_ptr<FieldValue> in); +boost::shared_ptr<FieldValue> toFieldValue(const Variant& in); + +template <class T, class U, class F> void translate(boost::shared_ptr<FieldValue> in, U& u, F f) +{ + T t; + getEncodedValue<T>(in, t); + convert(t, u, f); +} + +template <class T, class U, class F> T* toFieldValueCollection(const U& u, F f) +{ + typename T::ValueType t; + convert(u, t, f); + return new T(t); +} + +FieldTableValue* toFieldTableValue(const Variant::Map& map) +{ + FieldTable ft; + convert(map, ft, &toFieldTableEntry); + return new FieldTableValue(ft); +} + +ListValue* toListValue(const Variant::List& list) +{ + List l; + convert(list, l, &toFieldValue); + return new ListValue(l); +} + +void setEncodingFor(Variant& out, uint8_t code) +{ + switch(code){ + case 0x80: + case 0x90: + case 0xa0: + out.setEncoding(amqp0_10_binary); + break; + case 0x84: + case 0x94: + out.setEncoding(iso885915); + break; + case 0x85: + case 0x95: + out.setEncoding(utf8); + break; + case 0x86: + case 0x96: + out.setEncoding(utf16); + break; + case 0xab: + out.setEncoding(amqp0_10_struct); + break; + default: + //do nothing + break; + } +} + +Variant toVariant(boost::shared_ptr<FieldValue> in) +{ + Variant out; + //based on AMQP 0-10 typecode, pick most appropriate variant type + switch (in->getType()) { + //Fixed Width types: + case 0x01: out.setEncoding(amqp0_10_binary); + case 0x02: out = in->getIntegerValue<int8_t, 1>(); break; + case 0x03: out = in->getIntegerValue<uint8_t, 1>(); break; + case 0x04: break; //TODO: iso-8859-15 char + case 0x08: out = in->getIntegerValue<bool, 1>(); break; + case 0x010: out.setEncoding(amqp0_10_binary); + case 0x011: out = in->getIntegerValue<int16_t, 2>(); break; + case 0x012: out = in->getIntegerValue<uint16_t, 2>(); break; + case 0x020: out.setEncoding(amqp0_10_binary); + case 0x021: out = in->getIntegerValue<int32_t, 4>(); break; + case 0x022: out = in->getIntegerValue<uint32_t, 4>(); break; + case 0x023: out = in->get<float>(); break; + case 0x027: break; //TODO: utf-32 char + case 0x030: out.setEncoding(amqp0_10_binary); + case 0x031: out = in->getIntegerValue<int64_t, 8>(); break; + case 0x038: out.setEncoding(amqp0_10_datetime); //treat datetime as uint64_t, but set encoding + case 0x032: out = in->getIntegerValue<uint64_t, 8>(); break; + case 0x033:out = in->get<double>(); break; + + //TODO: figure out whether and how to map values with codes 0x40-0xd8 + + case 0xf0: break;//void, which is the default value for Variant + case 0xf1: out.setEncoding(amqp0_10_bit); break;//treat 'bit' as void, which is the default value for Variant + + //Variable Width types: + //strings: + case 0x80: + case 0x84: + case 0x85: + case 0x86: + case 0x90: + case 0x94: + case 0x95: + case 0x96: + case 0xa0: + case 0xab: + setEncodingFor(out, in->getType()); + out = in->get<std::string>(); + break; + + case 0xa8: + out = Variant::Map(); + translate<FieldTable>(in, out.asMap(), &toVariantMapEntry); + break; + + case 0xa9: + out = Variant::List(); + translate<List>(in, out.asList(), &toVariant); + break; + case 0xaa: //convert amqp0-10 array into variant list + out = Variant::List(); + translate<Array>(in, out.asList(), &toVariant); + break; + + default: + //error? + break; + } + return out; +} + +boost::shared_ptr<FieldValue> toFieldValue(const Variant& in) +{ + boost::shared_ptr<FieldValue> out; + switch (in.getType()) { + case VAR_VOID: out = boost::shared_ptr<FieldValue>(new VoidValue()); break; + case VAR_BOOL: out = boost::shared_ptr<FieldValue>(new BoolValue(in.asBool())); break; + case VAR_UINT8: out = boost::shared_ptr<FieldValue>(new Unsigned8Value(in.asUint8())); break; + case VAR_UINT16: out = boost::shared_ptr<FieldValue>(new Unsigned16Value(in.asUint16())); break; + case VAR_UINT32: out = boost::shared_ptr<FieldValue>(new Unsigned32Value(in.asUint32())); break; + case VAR_UINT64: out = boost::shared_ptr<FieldValue>(new Unsigned64Value(in.asUint64())); break; + case VAR_INT8: out = boost::shared_ptr<FieldValue>(new Integer8Value(in.asInt8())); break; + case VAR_INT16: out = boost::shared_ptr<FieldValue>(new Integer16Value(in.asInt16())); break; + case VAR_INT32: out = boost::shared_ptr<FieldValue>(new Integer32Value(in.asInt32())); break; + case VAR_INT64: out = boost::shared_ptr<FieldValue>(new Integer64Value(in.asInt64())); break; + case VAR_FLOAT: out = boost::shared_ptr<FieldValue>(new FloatValue(in.asFloat())); break; + case VAR_DOUBLE: out = boost::shared_ptr<FieldValue>(new DoubleValue(in.asDouble())); break; + //TODO: check encoding (and length?) when deciding what AMQP type to treat string as + case VAR_STRING: out = boost::shared_ptr<FieldValue>(new Str16Value(in.asString())); break; + case VAR_MAP: + //out = boost::shared_ptr<FieldValue>(toFieldValueCollection<FieldTableValue>(in.asMap(), &toFieldTableEntry)); + out = boost::shared_ptr<FieldValue>(toFieldTableValue(in.asMap())); + break; + case VAR_LIST: + //out = boost::shared_ptr<FieldValue>(toFieldValueCollection<ListValue>(in.asList(), &toFieldValue)); + out = boost::shared_ptr<FieldValue>(toListValue(in.asList())); + break; + } + return out; +} + +Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in) +{ + return Variant::Map::value_type(in.first, toVariant(in.second)); +} + +FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in) +{ + return FieldTable::value_type(in.first, toFieldValue(in.second)); +} + +struct EncodeBuffer +{ + char* data; + Buffer buffer; + + EncodeBuffer(size_t size) : data(new char[size]), buffer(data, size) {} + ~EncodeBuffer() { delete[] data; } + + template <class T> void encode(T& t) { t.encode(buffer); } + + void getData(std::string& s) { + s.assign(data, buffer.getSize()); + } +}; + +struct DecodeBuffer +{ + Buffer buffer; + + DecodeBuffer(const std::string& s) : buffer(const_cast<char*>(s.data()), s.size()) {} + + template <class T> void decode(T& t) { t.decode(buffer); } + +}; + +template <class T, class U, class F> void _encode(const U& value, std::string& data, F f) +{ + T t; + convert(value, t, f); + EncodeBuffer buffer(t.encodedSize()); + buffer.encode(t); + buffer.getData(data); +} + +template <class T, class U, class F> void _decode(const std::string& data, U& value, F f) +{ + T t; + DecodeBuffer buffer(data); + buffer.decode(t); + convert(t, value, f); +} + +void MapCodec::encode(const Variant& value, std::string& data) +{ + _encode<FieldTable>(value.asMap(), data, &toFieldTableEntry); +} + +void MapCodec::decode(const std::string& data, Variant& value) +{ + value = Variant::Map(); + _decode<FieldTable>(data, value.asMap(), &toVariantMapEntry); +} + +void ListCodec::encode(const Variant& value, std::string& data) +{ + _encode<List>(value.asList(), data, &toFieldValue); +} + +void ListCodec::decode(const std::string& data, Variant& value) +{ + value = Variant::List(); + _decode<List>(data, value.asList(), &toVariant); +} + +void translate(const Variant::Map& from, FieldTable& to) +{ + convert(from, to, &toFieldTableEntry); +} + +void translate(const FieldTable& from, Variant::Map& to) +{ + convert(from, to, &toVariantMapEntry); +} + +const std::string ListCodec::contentType("amqp/list"); +const std::string MapCodec::contentType("amqp/map"); + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/CodecsInternal.h b/cpp/src/qpid/client/amqp0_10/CodecsInternal.h new file mode 100644 index 0000000000..b5a561a9c3 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/CodecsInternal.h @@ -0,0 +1,41 @@ +#ifndef QPID_CLIENT_AMQP0_10_CODECSINTERNAL_H +#define QPID_CLIENT_AMQP0_10_CODECSINTERNAL_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/Variant.h" +#include "qpid/framing/FieldTable.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +/** + * Declarations of a couple of conversion functions implemented in + * Codecs.cpp but not exposed through API + */ + +void translate(const qpid::messaging::Variant::Map& from, qpid::framing::FieldTable& to); +void translate(const qpid::framing::FieldTable& from, qpid::messaging::Variant::Map& to); + +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_CODECSINTERNAL_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp new file mode 100644 index 0000000000..cd5c0214e3 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp @@ -0,0 +1,208 @@ +/* + * + * 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 "ConnectionImpl.h" +#include "SessionImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include <boost/intrusive_ptr.hpp> +#include <vector> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Variant; +using qpid::framing::Uuid; +using namespace qpid::sys; + +template <class T> void setIfFound(const Variant::Map& map, const std::string& key, T& value) +{ + Variant::Map::const_iterator i = map.find(key); + if (i != map.end()) { + value = (T) i->second; + } +} + +void convert(const Variant::Map& from, ConnectionSettings& to) +{ + setIfFound(from, "username", to.username); + setIfFound(from, "password", to.password); + setIfFound(from, "sasl-mechanism", to.mechanism); + setIfFound(from, "sasl-service", to.service); + setIfFound(from, "sasl-min-ssf", to.minSsf); + setIfFound(from, "sasl-max-ssf", to.maxSsf); + + setIfFound(from, "heartbeat", to.heartbeat); + setIfFound(from, "tcp-nodelay", to.tcpNoDelay); + + setIfFound(from, "locale", to.locale); + setIfFound(from, "max-channels", to.maxChannels); + setIfFound(from, "max-frame-size", to.maxFrameSize); + setIfFound(from, "bounds", to.bounds); +} + +ConnectionImpl::ConnectionImpl(const std::string& u, const Variant::Map& options) : + url(u), reconnectionEnabled(true), timeout(-1), + minRetryInterval(1), maxRetryInterval(30) +{ + QPID_LOG(debug, "Opening connection to " << url << " with " << options); + convert(options, settings); + setIfFound(options, "reconnection-enabled", reconnectionEnabled); + setIfFound(options, "reconnection-timeout", timeout); + setIfFound(options, "min-retry-interval", minRetryInterval); + setIfFound(options, "max-retry-interval", maxRetryInterval); + connection.open(url, settings); +} + +void ConnectionImpl::close() +{ + std::vector<std::string> names; + { + qpid::sys::Mutex::ScopedLock l(lock); + for (Sessions::const_iterator i = sessions.begin(); i != sessions.end(); ++i) names.push_back(i->first); + } + for (std::vector<std::string>::const_iterator i = names.begin(); i != names.end(); ++i) { + getSession(*i).close(); + } + + qpid::sys::Mutex::ScopedLock l(lock); + connection.close(); +} + +boost::intrusive_ptr<SessionImpl> getImplPtr(qpid::messaging::Session& session) +{ + return boost::dynamic_pointer_cast<SessionImpl>( + qpid::client::PrivateImplRef<qpid::messaging::Session>::get(session) + ); +} + +void ConnectionImpl::closed(SessionImpl& s) +{ + qpid::sys::Mutex::ScopedLock l(lock); + for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) { + if (getImplPtr(i->second).get() == &s) { + sessions.erase(i); + break; + } + } +} + +qpid::messaging::Session ConnectionImpl::getSession(const std::string& name) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + Sessions::const_iterator i = sessions.find(name); + if (i == sessions.end()) { + throw qpid::messaging::KeyError("No such session: " + name); + } else { + return i->second; + } +} + +qpid::messaging::Session ConnectionImpl::newSession(bool transactional, const std::string& n) +{ + std::string name = n.empty() ? Uuid(true).str() : n; + qpid::messaging::Session impl(new SessionImpl(*this, transactional)); + { + qpid::sys::Mutex::ScopedLock l(lock); + sessions[name] = impl; + } + try { + getImplPtr(impl)->setSession(connection.newSession(name)); + } catch (const TransportFailure&) { + reconnect(); + } + return impl; +} + +void ConnectionImpl::reconnect() +{ + AbsTime start = now(); + ScopedLock<Semaphore> l(semaphore); + if (!connection.isOpen()) connect(start); +} + +bool expired(const AbsTime& start, int timeout) +{ + if (timeout == 0) return true; + if (timeout < 0) return false; + Duration used(start, now()); + Duration allowed = timeout * TIME_SEC; + return allowed > used; +} + +void ConnectionImpl::connect(const AbsTime& started) +{ + for (int i = minRetryInterval; !tryConnect(); i = std::min(i * 2, maxRetryInterval)) { + if (expired(started, timeout)) throw TransportFailure(); + else qpid::sys::sleep(i); + } +} + +bool ConnectionImpl::tryConnect() +{ + if (tryConnect(url) || + (failoverListener.get() && tryConnect(failoverListener->getKnownBrokers()))) + { + return resetSessions(); + } else { + return false; + } +} + +bool ConnectionImpl::tryConnect(const Url& u) +{ + try { + QPID_LOG(info, "Trying to connect to " << url << "..."); + connection.open(u, settings); + failoverListener.reset(new FailoverListener(connection)); + return true; + } catch (const Exception& e) { + //TODO: need to fix timeout on open so that it throws TransportFailure + QPID_LOG(info, "Failed to connect to " << u << ": " << e.what()); + } + return false; +} + +bool ConnectionImpl::tryConnect(const std::vector<Url>& urls) +{ + for (std::vector<Url>::const_iterator i = urls.begin(); i != urls.end(); ++i) { + if (tryConnect(*i)) return true; + } + return false; +} + +bool ConnectionImpl::resetSessions() +{ + try { + qpid::sys::Mutex::ScopedLock l(lock); + for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) { + getImplPtr(i->second)->setSession(connection.newSession(i->first)); + } + return true; + } catch (const TransportFailure&) { + QPID_LOG(debug, "Connection failed while re-inialising sessions"); + return false; + } +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h new file mode 100644 index 0000000000..979cc6c82a --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h @@ -0,0 +1,72 @@ +#ifndef QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H +#define QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_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/ConnectionImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/Url.h" +#include "qpid/client/Connection.h" +#include "qpid/client/FailoverListener.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Semaphore.h" +#include <map> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class SessionImpl; + +class ConnectionImpl : public qpid::messaging::ConnectionImpl +{ + public: + ConnectionImpl(const std::string& url, const qpid::messaging::Variant::Map& options); + void close(); + qpid::messaging::Session newSession(bool transactional, const std::string& name); + qpid::messaging::Session getSession(const std::string& name) const; + void closed(SessionImpl&); + void reconnect(); + private: + typedef std::map<std::string, qpid::messaging::Session> Sessions; + + mutable qpid::sys::Mutex lock;//used to protect data structures + qpid::sys::Semaphore semaphore;//used to coordinate reconnection + Sessions sessions; + qpid::client::Connection connection; + std::auto_ptr<FailoverListener> failoverListener; + qpid::Url url; + qpid::client::ConnectionSettings settings; + bool reconnectionEnabled; + int timeout; + int minRetryInterval; + int maxRetryInterval; + + void connect(const qpid::sys::AbsTime& started); + bool tryConnect(); + bool tryConnect(const std::vector<Url>& urls); + bool tryConnect(const Url&); + bool resetSessions(); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp b/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp new file mode 100644 index 0000000000..e66dc5915c --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp @@ -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. + * + */ +#include "qpid/client/amqp0_10/IncomingMessages.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/client/amqp0_10/CodecsInternal.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/enum.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using namespace qpid::framing; +using namespace qpid::framing::message; +using qpid::sys::AbsTime; +using qpid::sys::Duration; +using qpid::messaging::MessageImplAccess; +using qpid::messaging::Variant; + +namespace { +const std::string EMPTY_STRING; + + +struct GetNone : IncomingMessages::Handler +{ + bool accept(IncomingMessages::MessageTransfer&) { return false; } +}; + +struct GetAny : IncomingMessages::Handler +{ + bool accept(IncomingMessages::MessageTransfer& transfer) + { + transfer.retrieve(0); + return true; + } +}; + +struct MatchAndTrack +{ + const std::string destination; + SequenceSet ids; + + MatchAndTrack(const std::string& d) : destination(d) {} + + bool operator()(boost::shared_ptr<qpid::framing::FrameSet> command) + { + if (command->as<MessageTransferBody>()->getDestination() == destination) { + ids.add(command->getId()); + return true; + } else { + return false; + } + } +}; + +struct Match +{ + const std::string destination; + uint32_t matched; + + Match(const std::string& d) : destination(d), matched(0) {} + + bool operator()(boost::shared_ptr<qpid::framing::FrameSet> command) + { + if (command->as<MessageTransferBody>()->getDestination() == destination) { + ++matched; + return true; + } else { + return false; + } + } +}; +} + +void IncomingMessages::setSession(qpid::client::AsyncSession s) +{ + session = s; + incoming = SessionBase_0_10Access(session).get()->getDemux().getDefault(); + acceptTracker.reset(); +} + +bool IncomingMessages::get(Handler& handler, Duration timeout) +{ + //search through received list for any transfer of interest: + for (FrameSetQueue::iterator i = received.begin(); i != received.end(); i++) + { + MessageTransfer transfer(*i, *this); + if (handler.accept(transfer)) { + received.erase(i); + return true; + } + } + //none found, check incoming: + return process(&handler, timeout); +} + +bool IncomingMessages::getNextDestination(std::string& destination, Duration timeout) +{ + //if there is not already a received message, we must wait for one + if (received.empty() && !wait(timeout)) return false; + //else we have a message in received; return the corresponding destination + destination = received.front()->as<MessageTransferBody>()->getDestination(); + return true; +} + +void IncomingMessages::accept() +{ + acceptTracker.accept(session); +} + +void IncomingMessages::releaseAll() +{ + //first process any received messages... + while (!received.empty()) { + retrieve(received.front(), 0); + received.pop_front(); + } + //then pump out any available messages from incoming queue... + GetAny handler; + while (process(&handler, 0)) ; + //now release all messages + acceptTracker.release(session); +} + +void IncomingMessages::releasePending(const std::string& destination) +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) ; + + //now remove all messages for this destination from received list, recording their ids... + MatchAndTrack match(destination); + for (FrameSetQueue::iterator i = received.begin(); i != received.end(); i = match(*i) ? received.erase(i) : ++i) ; + //now release those messages + session.messageRelease(match.ids); +} + +/** + * Get a frameset that is accepted by the specified handler from + * session queue, waiting for up to the specified duration and + * returning true if this could be achieved, false otherwise. Messages + * that are not accepted by the handler are pushed onto received queue + * for later retrieval. + */ +bool IncomingMessages::process(Handler* handler, qpid::sys::Duration duration) +{ + AbsTime deadline(AbsTime::now(), duration); + FrameSet::shared_ptr content; + for (Duration timeout = duration; incoming->pop(content, timeout); timeout = Duration(AbsTime::now(), deadline)) { + if (content->isA<MessageTransferBody>()) { + MessageTransfer transfer(content, *this); + if (handler && handler->accept(transfer)) { + QPID_LOG(debug, "Delivered " << *content->getMethod()); + return true; + } else { + //received message for another destination, keep for later + QPID_LOG(debug, "Pushed " << *content->getMethod() << " to received queue"); + received.push_back(content); + } + } else { + //TODO: handle other types of commands (e.g. message-accept, message-flow etc) + } + } + return false; +} + +bool IncomingMessages::wait(qpid::sys::Duration duration) +{ + AbsTime deadline(AbsTime::now(), duration); + FrameSet::shared_ptr content; + for (Duration timeout = duration; incoming->pop(content, timeout); timeout = Duration(AbsTime::now(), deadline)) { + if (content->isA<MessageTransferBody>()) { + QPID_LOG(debug, "Pushed " << *content->getMethod() << " to received queue"); + received.push_back(content); + return true; + } else { + //TODO: handle other types of commands (e.g. message-accept, message-flow etc) + } + } + return false; +} + +uint32_t IncomingMessages::pendingAccept() +{ + return acceptTracker.acceptsPending(); +} +uint32_t IncomingMessages::pendingAccept(const std::string& destination) +{ + return acceptTracker.acceptsPending(destination); +} + +uint32_t IncomingMessages::available() +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) {} + //return the count of received messages + return received.size(); +} + +uint32_t IncomingMessages::available(const std::string& destination) +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) {} + + //count all messages for this destination from received list + return std::for_each(received.begin(), received.end(), Match(destination)).matched; +} + +void populate(qpid::messaging::Message& message, FrameSet& command); + +/** + * Called when message is retrieved; records retrieval for subsequent + * acceptance, marks the command as completed and converts command to + * message if message is required + */ +void IncomingMessages::retrieve(FrameSetPtr command, qpid::messaging::Message* message) +{ + if (message) { + populate(*message, *command); + } + const MessageTransferBody* transfer = command->as<MessageTransferBody>(); + if (transfer->getAcquireMode() == ACQUIRE_MODE_PRE_ACQUIRED && transfer->getAcceptMode() == ACCEPT_MODE_EXPLICIT) { + acceptTracker.delivered(transfer->getDestination(), command->getId()); + } + session.markCompleted(command->getId(), false, false); +} + +IncomingMessages::MessageTransfer::MessageTransfer(FrameSetPtr c, IncomingMessages& p) : content(c), parent(p) {} + +const std::string& IncomingMessages::MessageTransfer::getDestination() +{ + return content->as<MessageTransferBody>()->getDestination(); +} +void IncomingMessages::MessageTransfer::retrieve(qpid::messaging::Message* message) +{ + parent.retrieve(content, message); +} + +void populateHeaders(qpid::messaging::Message& message, + const DeliveryProperties* deliveryProperties, + const MessageProperties* messageProperties) +{ + if (deliveryProperties) { + message.setSubject(deliveryProperties->getRoutingKey()); + //TODO: convert other delivery properties + } + if (messageProperties) { + message.setContentType(messageProperties->getContentType()); + if (messageProperties->hasReplyTo()) { + message.setReplyTo(AddressResolution::convert(messageProperties->getReplyTo())); + } + message.getHeaders().clear(); + translate(messageProperties->getApplicationHeaders(), message.getHeaders()); + //TODO: convert other message properties + } +} + +void populateHeaders(qpid::messaging::Message& message, const AMQHeaderBody* headers) +{ + populateHeaders(message, headers->get<DeliveryProperties>(), headers->get<MessageProperties>()); +} + +void populate(qpid::messaging::Message& message, FrameSet& command) +{ + //need to be able to link the message back to the transfer it was delivered by + //e.g. for rejecting. + MessageImplAccess::get(message).setInternalId(command.getId()); + + command.getContent(message.getContent()); + + populateHeaders(message, command.getHeaders()); +} + + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/IncomingMessages.h b/cpp/src/qpid/client/amqp0_10/IncomingMessages.h new file mode 100644 index 0000000000..2bc6dd49c4 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/IncomingMessages.h @@ -0,0 +1,98 @@ +#ifndef QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H +#define QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_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 <boost/shared_ptr.hpp> +#include "qpid/client/AsyncSession.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/sys/BlockingQueue.h" +#include "qpid/sys/Time.h" +#include "qpid/client/amqp0_10/AcceptTracker.h" + +namespace qpid { + +namespace framing{ +class FrameSet; +} + +namespace messaging { +class Message; +} + +namespace client { +namespace amqp0_10 { + +/** + * + */ +class IncomingMessages +{ + public: + typedef boost::shared_ptr<qpid::framing::FrameSet> FrameSetPtr; + class MessageTransfer + { + public: + const std::string& getDestination(); + void retrieve(qpid::messaging::Message* message); + private: + FrameSetPtr content; + IncomingMessages& parent; + + MessageTransfer(FrameSetPtr, IncomingMessages&); + friend class IncomingMessages; + }; + + struct Handler + { + virtual ~Handler() {} + virtual bool accept(MessageTransfer& transfer) = 0; + }; + + void setSession(qpid::client::AsyncSession session); + bool get(Handler& handler, qpid::sys::Duration timeout); + bool getNextDestination(std::string& destination, qpid::sys::Duration timeout); + void accept(); + void releaseAll(); + void releasePending(const std::string& destination); + + uint32_t pendingAccept(); + uint32_t pendingAccept(const std::string& destination); + + uint32_t available(); + uint32_t available(const std::string& destination); + private: + typedef std::deque<FrameSetPtr> FrameSetQueue; + + qpid::client::AsyncSession session; + boost::shared_ptr< sys::BlockingQueue<FrameSetPtr> > incoming; + FrameSetQueue received; + AcceptTracker acceptTracker; + + bool process(Handler*, qpid::sys::Duration); + bool wait(qpid::sys::Duration); + void retrieve(FrameSetPtr, qpid::messaging::Message*); + +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H*/ diff --git a/cpp/src/qpid/client/AckPolicy.h b/cpp/src/qpid/client/amqp0_10/MessageSink.h index b34f1d15d1..8d87a3c7bb 100644 --- a/cpp/src/qpid/client/AckPolicy.h +++ b/cpp/src/qpid/client/amqp0_10/MessageSink.h @@ -1,7 +1,8 @@ -#ifndef QPID_CLIENT_ACKPOLICY_H -#define QPID_CLIENT_ACKPOLICY_H +#ifndef QPID_CLIENT_AMQP0_10_MESSAGESINK_H +#define QPID_CLIENT_AMQP0_10_MESSAGESINK_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 @@ -9,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 @@ -20,39 +21,32 @@ * under the License. * */ - -#include "qpid/framing/SequenceSet.h" +#include <string> #include "qpid/client/AsyncSession.h" -#include "qpid/client/Message.h" namespace qpid { + +namespace messaging { +class Message; +} + namespace client { +namespace amqp0_10 { + +struct OutgoingMessage; /** - * Policy for automatic acknowledgement of messages. - * * - * \ingroup clientapi */ -class AckPolicy +class MessageSink { - framing::SequenceSet accepted; - size_t interval; - size_t count; - public: - /** - * Sends accepts and marks completion of received transfers. - * - *@param n: acknowledge every n messages. - *n==0 means no automatic acknowledgement. - */ - AckPolicy(size_t n=1); - void ack(const Message& msg, AsyncSession session); - void ackOutstanding(AsyncSession session);}; - -}} // namespace qpid::client - - - -#endif /*!QPID_CLIENT_ACKPOLICY_H*/ + virtual ~MessageSink() {} + virtual void declare(qpid::client::AsyncSession& session, const std::string& name) = 0; + virtual void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message) = 0; + virtual void cancel(qpid::client::AsyncSession& session, const std::string& name) = 0; + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_MESSAGESINK_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/MessageSource.h b/cpp/src/qpid/client/amqp0_10/MessageSource.h new file mode 100644 index 0000000000..74f2732f59 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/MessageSource.h @@ -0,0 +1,47 @@ +#ifndef QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H +#define QPID_CLIENT_AMQP0_10_MESSAGESOURCE_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 "qpid/client/AsyncSession.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +/** + * Abstraction behind which the AMQP 0-10 commands required to + * establish (and tear down) an incoming stream of messages from a + * given address are hidden. + */ +class MessageSource +{ + public: + virtual ~MessageSource() {} + virtual void subscribe(qpid::client::AsyncSession& session, const std::string& destination) = 0; + virtual void cancel(qpid::client::AsyncSession& session, const std::string& destination) = 0; + + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp new file mode 100644 index 0000000000..abd4f4c28c --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp @@ -0,0 +1,67 @@ +/* + * + * 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/amqp0_10/OutgoingMessage.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/client/amqp0_10/CodecsInternal.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Address; +using qpid::messaging::MessageImplAccess; + +void OutgoingMessage::convert(const qpid::messaging::Message& from) +{ + //TODO: need to avoid copying as much as possible + message.setData(from.getContent()); + message.getMessageProperties().setContentType(from.getContentType()); + const Address& address = from.getReplyTo(); + if (address) { + message.getMessageProperties().setReplyTo(AddressResolution::convert(address)); + } + translate(from.getHeaders(), message.getMessageProperties().getApplicationHeaders()); + //TODO: set other message properties + message.getDeliveryProperties().setRoutingKey(from.getSubject()); + //TODO: set other delivery properties +} + +namespace { +const std::string SUBJECT("subject"); +} + +void OutgoingMessage::setSubject(const std::string& subject) +{ + if (!subject.empty()) { + message.getMessageProperties().getApplicationHeaders().setString(SUBJECT, subject); + } +} + +std::string OutgoingMessage::getSubject() const +{ + return message.getMessageProperties().getApplicationHeaders().getAsString(SUBJECT); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h new file mode 100644 index 0000000000..0cdd2a2336 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h @@ -0,0 +1,48 @@ +#ifndef QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H +#define QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_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/Completion.h" +#include "qpid/client/Message.h" + +namespace qpid { +namespace messaging { +class Message; +} +namespace client { +namespace amqp0_10 { + +struct OutgoingMessage +{ + qpid::client::Message message; + qpid::client::Completion status; + + void convert(const qpid::messaging::Message&); + void setSubject(const std::string& subject); + std::string getSubject() const; +}; + + + +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp new file mode 100644 index 0000000000..bc5c53fde6 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.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. + * + */ +#include "ReceiverImpl.h" +#include "AddressResolution.h" +#include "MessageSource.h" +#include "SessionImpl.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Receiver; + +void ReceiverImpl::received(qpid::messaging::Message&) +{ + //TODO: should this be configurable + if (capacity && --window <= capacity/2) { + session.sendCompletion(); + window = capacity; + } +} + +qpid::messaging::Message ReceiverImpl::get(qpid::sys::Duration timeout) +{ + qpid::messaging::Message result; + if (!get(result, timeout)) throw Receiver::NoMessageAvailable(); + return result; +} + +qpid::messaging::Message ReceiverImpl::fetch(qpid::sys::Duration timeout) +{ + qpid::messaging::Message result; + if (!fetch(result, timeout)) throw Receiver::NoMessageAvailable(); + return result; +} + +bool ReceiverImpl::get(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + Get f(*this, message, timeout); + while (!parent.execute(f)) {} + return f.result; +} + +bool ReceiverImpl::fetch(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + Fetch f(*this, message, timeout); + while (!parent.execute(f)) {} + return f.result; +} + +void ReceiverImpl::cancel() +{ + execute<Cancel>(); +} + +void ReceiverImpl::start() +{ + if (state == STOPPED) { + state = STARTED; + startFlow(); + } +} + +void ReceiverImpl::stop() +{ + state = STOPPED; + session.messageStop(destination); +} + +void ReceiverImpl::setCapacity(uint32_t c) +{ + execute1<SetCapacity>(c); +} + +void ReceiverImpl::startFlow() +{ + if (capacity > 0) { + session.messageSetFlowMode(destination, FLOW_MODE_WINDOW); + session.messageFlow(destination, CREDIT_UNIT_MESSAGE, capacity); + session.messageFlow(destination, CREDIT_UNIT_BYTE, byteCredit); + window = capacity; + } +} + +void ReceiverImpl::init(qpid::client::AsyncSession s, AddressResolution& resolver) +{ + + session = s; + if (state == UNRESOLVED) { + source = resolver.resolveSource(session, address); + state = STARTED; + } + if (state == CANCELLED) { + source->cancel(session, destination); + parent.receiverCancelled(destination); + } else { + source->subscribe(session, destination); + start(); + } +} + + +const std::string& ReceiverImpl::getName() const { return destination; } + +uint32_t ReceiverImpl::getCapacity() +{ + return capacity; +} + +uint32_t ReceiverImpl::available() +{ + return parent.available(destination); +} + +uint32_t ReceiverImpl::pendingAck() +{ + return parent.pendingAck(destination); +} + +ReceiverImpl::ReceiverImpl(SessionImpl& p, const std::string& name, + const qpid::messaging::Address& a) : + + parent(p), destination(name), address(a), byteCredit(0xFFFFFFFF), + state(UNRESOLVED), capacity(0), window(0) {} + +bool ReceiverImpl::getImpl(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + return parent.get(*this, message, timeout); +} + +bool ReceiverImpl::fetchImpl(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + if (state == CANCELLED) return false;//TODO: or should this be an error? + + if (capacity == 0 || state != STARTED) { + session.messageSetFlowMode(destination, FLOW_MODE_CREDIT); + session.messageFlow(destination, CREDIT_UNIT_MESSAGE, 1); + session.messageFlow(destination, CREDIT_UNIT_BYTE, 0xFFFFFFFF); + } + + if (getImpl(message, timeout)) { + return true; + } else { + sync(session).messageFlush(destination); + startFlow();//reallocate credit + return getImpl(message, 0); + } +} + +void ReceiverImpl::cancelImpl() +{ + if (state != CANCELLED) { + state = CANCELLED; + source->cancel(session, destination); + parent.receiverCancelled(destination); + } +} + +void ReceiverImpl::setCapacityImpl(uint32_t c) +{ + if (c != capacity) { + capacity = c; + if (state == STARTED) { + session.messageStop(destination); + startFlow(); + } + } +} +qpid::messaging::Session ReceiverImpl::getSession() const +{ + return qpid::messaging::Session(&parent); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h new file mode 100644 index 0000000000..d40aac4058 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h @@ -0,0 +1,148 @@ +#ifndef QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H +#define QPID_CLIENT_AMQP0_10_RECEIVERIMPL_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 "qpid/messaging/Message.h" +#include "qpid/messaging/ReceiverImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/amqp0_10/SessionImpl.h" +#include "qpid/sys/Time.h" +#include <memory> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class AddressResolution; +class MessageSource; + +/** + * A receiver implementation based on an AMQP 0-10 subscription. + */ +class ReceiverImpl : public qpid::messaging::ReceiverImpl +{ + public: + + enum State {UNRESOLVED, STOPPED, STARTED, CANCELLED}; + + ReceiverImpl(SessionImpl& parent, const std::string& name, + const qpid::messaging::Address& address); + + void init(qpid::client::AsyncSession session, AddressResolution& resolver); + bool get(qpid::messaging::Message& message, qpid::sys::Duration timeout); + qpid::messaging::Message get(qpid::sys::Duration timeout); + bool fetch(qpid::messaging::Message& message, qpid::sys::Duration timeout); + qpid::messaging::Message fetch(qpid::sys::Duration timeout); + void cancel(); + void start(); + void stop(); + const std::string& getName() const; + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t available(); + uint32_t pendingAck(); + void received(qpid::messaging::Message& message); + qpid::messaging::Session getSession() const; + private: + SessionImpl& parent; + const std::string destination; + const qpid::messaging::Address address; + const uint32_t byteCredit; + State state; + + std::auto_ptr<MessageSource> source; + uint32_t capacity; + qpid::client::AsyncSession session; + qpid::messaging::MessageListener* listener; + uint32_t window; + + void startFlow(); + //implementation of public facing methods + bool fetchImpl(qpid::messaging::Message& message, qpid::sys::Duration timeout); + bool getImpl(qpid::messaging::Message& message, qpid::sys::Duration timeout); + void cancelImpl(); + void setCapacityImpl(uint32_t); + + //functors for public facing methods (allows locking and retry + //logic to be centralised) + struct Command + { + ReceiverImpl& impl; + + Command(ReceiverImpl& i) : impl(i) {} + }; + + struct Get : Command + { + qpid::messaging::Message& message; + qpid::sys::Duration timeout; + bool result; + + Get(ReceiverImpl& i, qpid::messaging::Message& m, qpid::sys::Duration t) : + Command(i), message(m), timeout(t), result(false) {} + void operator()() { result = impl.getImpl(message, timeout); } + }; + + struct Fetch : Command + { + qpid::messaging::Message& message; + qpid::sys::Duration timeout; + bool result; + + Fetch(ReceiverImpl& i, qpid::messaging::Message& m, qpid::sys::Duration t) : + Command(i), message(m), timeout(t), result(false) {} + void operator()() { result = impl.fetchImpl(message, timeout); } + }; + + struct Cancel : Command + { + Cancel(ReceiverImpl& i) : Command(i) {} + void operator()() { impl.cancelImpl(); } + }; + + struct SetCapacity : Command + { + uint32_t capacity; + + SetCapacity(ReceiverImpl& i, uint32_t c) : Command(i), capacity(c) {} + void operator()() { impl.setCapacityImpl(capacity); } + }; + + //helper templates for some common patterns + template <class F> void execute() + { + F f(*this); + parent.execute(f); + } + + template <class F, class P> void execute1(P p) + { + F f(*this, p); + parent.execute(f); + } +}; + +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp b/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp new file mode 100644 index 0000000000..4d6b9869e6 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp @@ -0,0 +1,144 @@ +/* + * + * 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 "SenderImpl.h" +#include "MessageSink.h" +#include "SessionImpl.h" +#include "AddressResolution.h" +#include "OutgoingMessage.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +SenderImpl::SenderImpl(SessionImpl& _parent, const std::string& _name, + const qpid::messaging::Address& _address) : + parent(_parent), name(_name), address(_address), state(UNRESOLVED), + capacity(50), window(0), flushed(false) {} + +void SenderImpl::send(const qpid::messaging::Message& message) +{ + Send f(*this, &message); + while (f.repeat) parent.execute(f); +} + +void SenderImpl::cancel() +{ + execute<Cancel>(); +} + +void SenderImpl::setCapacity(uint32_t c) +{ + bool flush = c < capacity; + capacity = c; + execute1<CheckPendingSends>(flush); +} +uint32_t SenderImpl::getCapacity() { return capacity; } +uint32_t SenderImpl::pending() +{ + CheckPendingSends f(*this, false); + parent.execute(f); + return f.pending; +} + +void SenderImpl::init(qpid::client::AsyncSession s, AddressResolution& resolver) +{ + session = s; + if (state == UNRESOLVED) { + sink = resolver.resolveSink(session, address); + state = ACTIVE; + } + if (state == CANCELLED) { + sink->cancel(session, name); + parent.senderCancelled(name); + } else { + sink->declare(session, name); + replay(); + } +} + +void SenderImpl::waitForCapacity() +{ + //TODO: add option to throw exception rather than blocking? + if (capacity <= (flushed ? checkPendingSends(false) : outgoing.size())) { + //Initial implementation is very basic. As outgoing is + //currently only reduced on receiving completions and we are + //blocking anyway we may as well sync(). If successful that + //should clear all outstanding sends. + session.sync(); + checkPendingSends(false); + } + //flush periodically and check for conmpleted sends + if (++window > (capacity / 4)) {//TODO: make this configurable? + checkPendingSends(true); + window = 0; + } +} + +void SenderImpl::sendImpl(const qpid::messaging::Message& m) +{ + //TODO: make recording for replay optional (would still want to track completion however) + std::auto_ptr<OutgoingMessage> msg(new OutgoingMessage()); + msg->convert(m); + msg->setSubject(m.getSubject().empty() ? address.getSubject() : m.getSubject()); + outgoing.push_back(msg.release()); + sink->send(session, name, outgoing.back()); +} + +void SenderImpl::replay() +{ + for (OutgoingMessages::iterator i = outgoing.begin(); i != outgoing.end(); ++i) { + sink->send(session, name, *i); + } +} + +uint32_t SenderImpl::checkPendingSends(bool flush) +{ + if (flush) { + session.flush(); + flushed = true; + } else { + flushed = false; + } + while (!outgoing.empty() && outgoing.front().status.isComplete()) { + outgoing.pop_front(); + } + return outgoing.size(); +} + +void SenderImpl::cancelImpl() +{ + state = CANCELLED; + sink->cancel(session, name); + parent.senderCancelled(name); +} + +const std::string& SenderImpl::getName() const +{ + return name; +} + +qpid::messaging::Session SenderImpl::getSession() const +{ + return qpid::messaging::Session(&parent); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/SenderImpl.h b/cpp/src/qpid/client/amqp0_10/SenderImpl.h new file mode 100644 index 0000000000..80d9843d9e --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/SenderImpl.h @@ -0,0 +1,140 @@ +#ifndef QPID_CLIENT_AMQP0_10_SENDERIMPL_H +#define QPID_CLIENT_AMQP0_10_SENDERIMPL_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 "qpid/messaging/Message.h" +#include "qpid/messaging/SenderImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/amqp0_10/SessionImpl.h" +#include <memory> +#include <boost/ptr_container/ptr_deque.hpp> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class AddressResolution; +class MessageSink; +struct OutgoingMessage; + +/** + * + */ +class SenderImpl : public qpid::messaging::SenderImpl +{ + public: + enum State {UNRESOLVED, ACTIVE, CANCELLED}; + + SenderImpl(SessionImpl& parent, const std::string& name, + const qpid::messaging::Address& address); + void send(const qpid::messaging::Message&); + void cancel(); + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t pending(); + void init(qpid::client::AsyncSession, AddressResolution&); + const std::string& getName() const; + qpid::messaging::Session getSession() const; + + private: + SessionImpl& parent; + const std::string name; + const qpid::messaging::Address address; + State state; + std::auto_ptr<MessageSink> sink; + + qpid::client::AsyncSession session; + std::string destination; + std::string routingKey; + + typedef boost::ptr_deque<OutgoingMessage> OutgoingMessages; + OutgoingMessages outgoing; + uint32_t capacity; + uint32_t window; + bool flushed; + + uint32_t checkPendingSends(bool flush); + void replay(); + void waitForCapacity(); + + //logic for application visible methods: + void sendImpl(const qpid::messaging::Message&); + void cancelImpl(); + + + //functors for application visible methods (allowing locking and + //retry to be centralised): + struct Command + { + SenderImpl& impl; + + Command(SenderImpl& i) : impl(i) {} + }; + + struct Send : Command + { + const qpid::messaging::Message* message; + bool repeat; + + Send(SenderImpl& i, const qpid::messaging::Message* m) : Command(i), message(m), repeat(true) {} + void operator()() + { + impl.waitForCapacity(); + //from this point message will be recorded if there is any + //failure (and replayed) so need not repeat the call + repeat = false; + impl.sendImpl(*message); + } + }; + + struct Cancel : Command + { + Cancel(SenderImpl& i) : Command(i) {} + void operator()() { impl.cancelImpl(); } + }; + + struct CheckPendingSends : Command + { + bool flush; + uint32_t pending; + CheckPendingSends(SenderImpl& i, bool f) : Command(i), flush(f), pending(0) {} + void operator()() { pending = impl.checkPendingSends(flush); } + }; + + //helper templates for some common patterns + template <class F> void execute() + { + F f(*this); + parent.execute(f); + } + + template <class F, class P> bool execute1(P p) + { + F f(*this, p); + return parent.execute(f); + } +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_SENDERIMPL_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp b/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp new file mode 100644 index 0000000000..c7dff05dd7 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp @@ -0,0 +1,433 @@ +/* + * + * 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/amqp0_10/SessionImpl.h" +#include "qpid/client/amqp0_10/ConnectionImpl.h" +#include "qpid/client/amqp0_10/ReceiverImpl.h" +#include "qpid/client/amqp0_10/SenderImpl.h" +#include "qpid/client/amqp0_10/MessageSource.h" +#include "qpid/client/amqp0_10/MessageSink.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Session.h" +#include "qpid/framing/reply_exceptions.h" +#include <boost/format.hpp> +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> + +using qpid::messaging::KeyError; +using qpid::messaging::MessageImplAccess; +using qpid::messaging::Sender; +using qpid::messaging::Receiver; +using qpid::messaging::VariantMap; + +namespace qpid { +namespace client { +namespace amqp0_10 { + +SessionImpl::SessionImpl(ConnectionImpl& c, bool t) : connection(c), transactional(t) {} + + +void SessionImpl::sync() +{ + retry<Sync>(); +} + +void SessionImpl::flush() +{ + retry<Flush>(); +} + +void SessionImpl::commit() +{ + if (!execute<Commit>()) { + throw Exception();//TODO: what type? + } +} + +void SessionImpl::rollback() +{ + //If the session fails during this operation, the transaction will + //be rolled back anyway. + execute<Rollback>(); +} + +void SessionImpl::acknowledge() +{ + //Should probably throw an exception on failure here, or indicate + //it through a return type at least. Failure means that the + //message may be redelivered; i.e. the application cannot delete + //any state necessary for preventing reprocessing of the message + execute<Acknowledge>(); +} + +void SessionImpl::reject(qpid::messaging::Message& m) +{ + //Possibly want to somehow indicate failure here as well. Less + //clear need as compared to acknowledge however. + execute1<Reject>(m); +} + +void SessionImpl::close() +{ + //cancel all the senders and receivers (get copy of names and then + //make the calls to avoid modifying maps while iterating over + //them): + std::vector<std::string> s; + std::vector<std::string> r; + { + qpid::sys::Mutex::ScopedLock l(lock); + for (Senders::const_iterator i = senders.begin(); i != senders.end(); ++i) s.push_back(i->first); + for (Receivers::const_iterator i = receivers.begin(); i != receivers.end(); ++i) r.push_back(i->first); + } + for (std::vector<std::string>::const_iterator i = s.begin(); i != s.end(); ++i) getSender(*i).cancel(); + for (std::vector<std::string>::const_iterator i = r.begin(); i != r.end(); ++i) getReceiver(*i).cancel(); + + + connection.closed(*this); + session.close(); +} + +template <class T, class S> boost::intrusive_ptr<S> getImplPtr(T& t) +{ + return boost::dynamic_pointer_cast<S>(qpid::client::PrivateImplRef<T>::get(t)); +} + +template <class T> void getFreeKey(std::string& key, T& map) +{ + std::string name = key; + int count = 1; + for (typename T::const_iterator i = map.find(name); i != map.end(); i = map.find(name)) { + name = (boost::format("%1%_%2%") % key % ++count).str(); + } + key = name; +} + + +void SessionImpl::setSession(qpid::client::Session s) +{ + qpid::sys::Mutex::ScopedLock l(lock); + session = s; + incoming.setSession(session); + if (transactional) session.txSelect(); + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) { + getImplPtr<Receiver, ReceiverImpl>(i->second)->init(session, resolver); + } + for (Senders::iterator i = senders.begin(); i != senders.end(); ++i) { + getImplPtr<Sender, SenderImpl>(i->second)->init(session, resolver); + } +} + +struct SessionImpl::CreateReceiver : Command +{ + qpid::messaging::Receiver result; + const qpid::messaging::Address& address; + + CreateReceiver(SessionImpl& i, const qpid::messaging::Address& a) : + Command(i), address(a) {} + void operator()() { result = impl.createReceiverImpl(address); } +}; + +Receiver SessionImpl::createReceiver(const qpid::messaging::Address& address) +{ + return get1<CreateReceiver, Receiver>(address); +} + +Receiver SessionImpl::createReceiverImpl(const qpid::messaging::Address& address) +{ + std::string name = address.getName(); + getFreeKey(name, receivers); + Receiver receiver(new ReceiverImpl(*this, name, address)); + getImplPtr<Receiver, ReceiverImpl>(receiver)->init(session, resolver); + receivers[name] = receiver; + return receiver; +} + +struct SessionImpl::CreateSender : Command +{ + qpid::messaging::Sender result; + const qpid::messaging::Address& address; + + CreateSender(SessionImpl& i, const qpid::messaging::Address& a) : + Command(i), address(a) {} + void operator()() { result = impl.createSenderImpl(address); } +}; + +Sender SessionImpl::createSender(const qpid::messaging::Address& address) +{ + return get1<CreateSender, Sender>(address); +} + +Sender SessionImpl::createSenderImpl(const qpid::messaging::Address& address) +{ + std::string name = address.getName(); + getFreeKey(name, senders); + Sender sender(new SenderImpl(*this, name, address)); + getImplPtr<Sender, SenderImpl>(sender)->init(session, resolver); + senders[name] = sender; + return sender; +} + +Sender SessionImpl::getSender(const std::string& name) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + Senders::const_iterator i = senders.find(name); + if (i == senders.end()) { + throw KeyError(name); + } else { + return i->second; + } +} + +Receiver SessionImpl::getReceiver(const std::string& name) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + Receivers::const_iterator i = receivers.find(name); + if (i == receivers.end()) { + throw KeyError(name); + } else { + return i->second; + } +} + +SessionImpl& SessionImpl::convert(qpid::messaging::Session& s) +{ + boost::intrusive_ptr<SessionImpl> impl = getImplPtr<qpid::messaging::Session, SessionImpl>(s); + if (!impl) { + throw qpid::Exception(QPID_MSG("Configuration error; require qpid::client::amqp0_10::SessionImpl")); + } + return *impl; +} + +namespace { + +struct IncomingMessageHandler : IncomingMessages::Handler +{ + typedef boost::function1<bool, IncomingMessages::MessageTransfer&> Callback; + Callback callback; + + IncomingMessageHandler(Callback c) : callback(c) {} + + bool accept(IncomingMessages::MessageTransfer& transfer) + { + return callback(transfer); + } +}; + +} + + +bool SessionImpl::getNextReceiver(Receiver* receiver, IncomingMessages::MessageTransfer& transfer) +{ + Receivers::const_iterator i = receivers.find(transfer.getDestination()); + if (i == receivers.end()) { + QPID_LOG(error, "Received message for unknown destination " << transfer.getDestination()); + return false; + } else { + *receiver = i->second; + return true; + } +} + +bool SessionImpl::accept(ReceiverImpl* receiver, + qpid::messaging::Message* message, + IncomingMessages::MessageTransfer& transfer) +{ + if (receiver->getName() == transfer.getDestination()) { + transfer.retrieve(message); + receiver->received(*message); + return true; + } else { + return false; + } +} + +bool SessionImpl::getIncoming(IncomingMessages::Handler& handler, qpid::sys::Duration timeout) +{ + return incoming.get(handler, timeout); +} + +bool SessionImpl::get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + IncomingMessageHandler handler(boost::bind(&SessionImpl::accept, this, &receiver, &message, _1)); + return getIncoming(handler, timeout); +} + +bool SessionImpl::nextReceiver(qpid::messaging::Receiver& receiver, qpid::sys::Duration timeout) +{ + qpid::sys::Mutex::ScopedLock l(lock); + while (true) { + try { + std::string destination; + if (incoming.getNextDestination(destination, timeout)) { + Receivers::const_iterator i = receivers.find(destination); + if (i == receivers.end()) { + throw qpid::Exception(QPID_MSG("Received message for unknown destination " << destination)); + } else { + receiver = i->second; + } + return true; + } else { + return false; + } + } catch (TransportFailure&) { + reconnect(); + } + } +} + +qpid::messaging::Receiver SessionImpl::nextReceiver(qpid::sys::Duration timeout) +{ + qpid::messaging::Receiver receiver; + if (!nextReceiver(receiver, timeout)) throw Receiver::NoMessageAvailable(); + if (!receiver) throw qpid::Exception("Bad receiver returned!"); + return receiver; +} + +uint32_t SessionImpl::available() +{ + return get1<Available, uint32_t>((const std::string*) 0); +} +uint32_t SessionImpl::available(const std::string& destination) +{ + return get1<Available, uint32_t>(&destination); +} + +struct SessionImpl::Available : Command +{ + const std::string* destination; + uint32_t result; + + Available(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {} + void operator()() { result = impl.availableImpl(destination); } +}; + +uint32_t SessionImpl::availableImpl(const std::string* destination) +{ + if (destination) { + return incoming.available(*destination); + } else { + return incoming.available(); + } +} + +uint32_t SessionImpl::pendingAck() +{ + return get1<PendingAck, uint32_t>((const std::string*) 0); +} + +uint32_t SessionImpl::pendingAck(const std::string& destination) +{ + return get1<PendingAck, uint32_t>(&destination); +} + +struct SessionImpl::PendingAck : Command +{ + const std::string* destination; + uint32_t result; + + PendingAck(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {} + void operator()() { result = impl.pendingAckImpl(destination); } +}; + +uint32_t SessionImpl::pendingAckImpl(const std::string* destination) +{ + if (destination) { + return incoming.pendingAccept(*destination); + } else { + return incoming.pendingAccept(); + } +} + +void SessionImpl::syncImpl() +{ + session.sync(); +} + +void SessionImpl::flushImpl() +{ + session.flush(); +} + + +void SessionImpl::commitImpl() +{ + incoming.accept(); + session.txCommit(); +} + +void SessionImpl::rollbackImpl() +{ + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) { + getImplPtr<Receiver, ReceiverImpl>(i->second)->stop(); + } + //ensure that stop has been processed and all previously sent + //messages are available for release: + session.sync(); + incoming.releaseAll(); + session.txRollback(); + + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) { + getImplPtr<Receiver, ReceiverImpl>(i->second)->start(); + } +} + +void SessionImpl::acknowledgeImpl() +{ + incoming.accept(); +} + +void SessionImpl::rejectImpl(qpid::messaging::Message& m) +{ + SequenceSet set; + set.add(MessageImplAccess::get(m).getInternalId()); + session.messageReject(set); +} + +void SessionImpl::receiverCancelled(const std::string& name) +{ + receivers.erase(name); + session.sync(); + incoming.releasePending(name); +} + +void SessionImpl::senderCancelled(const std::string& name) +{ + senders.erase(name); +} + +void SessionImpl::reconnect() +{ + connection.reconnect(); +} + +qpid::messaging::Connection SessionImpl::getConnection() const +{ + return qpid::messaging::Connection(&connection); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/SessionImpl.h b/cpp/src/qpid/client/amqp0_10/SessionImpl.h new file mode 100644 index 0000000000..96c7ca93a3 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/SessionImpl.h @@ -0,0 +1,212 @@ +#ifndef QPID_CLIENT_AMQP0_10_SESSIONIMPL_H +#define QPID_CLIENT_AMQP0_10_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 "qpid/messaging/SessionImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/IncomingMessages.h" +#include "qpid/sys/Mutex.h" + +namespace qpid { + +namespace messaging { +class Address; +class Connection; +class Message; +class Receiver; +class Sender; +class Session; +} + +namespace client { +namespace amqp0_10 { + +class ConnectionImpl; +class ReceiverImpl; +class SenderImpl; + +/** + * Implementation of the protocol independent Session interface using + * AMQP 0-10. + */ +class SessionImpl : public qpid::messaging::SessionImpl +{ + public: + SessionImpl(ConnectionImpl&, bool transactional); + void commit(); + void rollback(); + void acknowledge(); + void reject(qpid::messaging::Message&); + void close(); + void sync(); + void flush(); + qpid::messaging::Sender createSender(const qpid::messaging::Address& address); + qpid::messaging::Receiver createReceiver(const qpid::messaging::Address& address); + + qpid::messaging::Sender getSender(const std::string& name) const; + qpid::messaging::Receiver getReceiver(const std::string& name) const; + + bool nextReceiver(qpid::messaging::Receiver& receiver, qpid::sys::Duration timeout); + qpid::messaging::Receiver nextReceiver(qpid::sys::Duration timeout); + + qpid::messaging::Connection getConnection() const; + + bool get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::sys::Duration timeout); + + void receiverCancelled(const std::string& name); + void senderCancelled(const std::string& name); + + uint32_t available(); + uint32_t available(const std::string& destination); + + uint32_t pendingAck(); + uint32_t pendingAck(const std::string& destination); + + void setSession(qpid::client::Session); + + template <class T> bool execute(T& f) + { + try { + qpid::sys::Mutex::ScopedLock l(lock); + f(); + return true; + } catch (TransportFailure&) { + reconnect(); + return false; + } + } + + static SessionImpl& convert(qpid::messaging::Session&); + + private: + typedef std::map<std::string, qpid::messaging::Receiver> Receivers; + typedef std::map<std::string, qpid::messaging::Sender> Senders; + + mutable qpid::sys::Mutex lock; + ConnectionImpl& connection; + qpid::client::Session session; + AddressResolution resolver; + IncomingMessages incoming; + Receivers receivers; + Senders senders; + const bool transactional; + + bool accept(ReceiverImpl*, qpid::messaging::Message*, IncomingMessages::MessageTransfer&); + bool getIncoming(IncomingMessages::Handler& handler, qpid::sys::Duration timeout); + bool getNextReceiver(qpid::messaging::Receiver* receiver, IncomingMessages::MessageTransfer& transfer); + void reconnect(); + + void commitImpl(); + void rollbackImpl(); + void acknowledgeImpl(); + void rejectImpl(qpid::messaging::Message&); + void closeImpl(); + void syncImpl(); + void flushImpl(); + qpid::messaging::Sender createSenderImpl(const qpid::messaging::Address& address); + qpid::messaging::Receiver createReceiverImpl(const qpid::messaging::Address& address); + uint32_t availableImpl(const std::string* destination); + uint32_t pendingAckImpl(const std::string* destination); + + //functors for public facing methods (allows locking and retry + //logic to be centralised) + struct Command + { + SessionImpl& impl; + + Command(SessionImpl& i) : impl(i) {} + }; + + struct Commit : Command + { + Commit(SessionImpl& i) : Command(i) {} + void operator()() { impl.commitImpl(); } + }; + + struct Rollback : Command + { + Rollback(SessionImpl& i) : Command(i) {} + void operator()() { impl.rollbackImpl(); } + }; + + struct Acknowledge : Command + { + Acknowledge(SessionImpl& i) : Command(i) {} + void operator()() { impl.acknowledgeImpl(); } + }; + + struct Sync : Command + { + Sync(SessionImpl& i) : Command(i) {} + void operator()() { impl.syncImpl(); } + }; + + struct Flush : Command + { + Flush(SessionImpl& i) : Command(i) {} + void operator()() { impl.flushImpl(); } + }; + + struct Reject : Command + { + qpid::messaging::Message& message; + + Reject(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {} + void operator()() { impl.rejectImpl(message); } + }; + + struct CreateSender; + struct CreateReceiver; + struct PendingAck; + struct Available; + + //helper templates for some common patterns + template <class F> bool execute() + { + F f(*this); + return execute(f); + } + + template <class F> void retry() + { + while (!execute<F>()) {} + } + + template <class F, class P> bool execute1(P p) + { + F f(*this, p); + return execute(f); + } + + template <class F, class R, class P> R get1(P p) + { + F f(*this, p); + while (!execute(f)) {} + return f.result; + } +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_SESSIONIMPL_H*/ diff --git a/cpp/src/qpid/client/windows/SaslFactory.cpp b/cpp/src/qpid/client/windows/SaslFactory.cpp new file mode 100644 index 0000000000..87df187ab2 --- /dev/null +++ b/cpp/src/qpid/client/windows/SaslFactory.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/client/SaslFactory.h" +#include "qpid/client/ConnectionSettings.h" + +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/log/Statement.h" + +#include "boost/tokenizer.hpp" + +namespace qpid { +namespace client { + +using qpid::sys::SecurityLayer; +using qpid::framing::InternalErrorException; + +class WindowsSasl : public Sasl +{ + public: + WindowsSasl(const ConnectionSettings&); + ~WindowsSasl(); + std::string start(const std::string& mechanisms, unsigned int ssf); + std::string step(const std::string& challenge); + std::string getMechanism(); + std::string getUserId(); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); + private: + ConnectionSettings settings; + std::string mechanism; +}; + +qpid::sys::Mutex SaslFactory::lock; +std::auto_ptr<SaslFactory> SaslFactory::instance; + +SaslFactory::SaslFactory() +{ +} + +SaslFactory::~SaslFactory() +{ +} + +SaslFactory& SaslFactory::getInstance() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (!instance.get()) { + instance = std::auto_ptr<SaslFactory>(new SaslFactory()); + } + return *instance; +} + +std::auto_ptr<Sasl> SaslFactory::create(const ConnectionSettings& settings) +{ + std::auto_ptr<Sasl> sasl(new WindowsSasl(settings)); + return sasl; +} + +namespace { + const std::string ANONYMOUS = "ANONYMOUS"; + const std::string PLAIN = "PLAIN"; +} + +WindowsSasl::WindowsSasl(const ConnectionSettings& s) + : settings(s) +{ +} + +WindowsSasl::~WindowsSasl() +{ +} + +std::string WindowsSasl::start(const std::string& mechanisms, + unsigned int /*ssf*/) +{ + QPID_LOG(debug, "WindowsSasl::start(" << mechanisms << ")"); + + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep(" "); + bool havePlain = false; + bool haveAnon = false; + tokenizer mechs(mechanisms, sep); + for (tokenizer::iterator mech = mechs.begin(); + mech != mechs.end(); + ++mech) { + if (*mech == ANONYMOUS) + haveAnon = true; + else if (*mech == PLAIN) + havePlain = true; + } + if (!haveAnon && !havePlain) + throw InternalErrorException(QPID_MSG("Sasl error: no common mechanism")); + + std::string resp = ""; + if (havePlain) { + mechanism = PLAIN; + resp = ((char)0) + settings.username + ((char)0) + settings.password; + } + else { + mechanism = ANONYMOUS; + } + return resp; +} + +std::string WindowsSasl::step(const std::string& challenge) +{ + // Shouldn't get this for PLAIN... + throw InternalErrorException(QPID_MSG("Sasl step error")); +} + +std::string WindowsSasl::getMechanism() +{ + return mechanism; +} + +std::string WindowsSasl::getUserId() +{ + return std::string(); // TODO - when GSSAPI is supported, return userId for connection. +} + +std::auto_ptr<SecurityLayer> WindowsSasl::getSecurityLayer(uint16_t maxFrameSize) +{ + return std::auto_ptr<SecurityLayer>(0); +} + +}} // namespace qpid::client diff --git a/cpp/src/qpid/cluster/ClassifierHandler.cpp b/cpp/src/qpid/cluster/ClassifierHandler.cpp deleted file mode 100644 index b78f795d20..0000000000 --- a/cpp/src/qpid/cluster/ClassifierHandler.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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 "ClassifierHandler.h" - -#include "qpid/framing/FrameDefaultVisitor.h" -#include "qpid/framing/AMQFrame.h" - -namespace qpid { -namespace cluster { - -using namespace framing; - -struct ClassifierHandler::Visitor : public FrameDefaultVisitor { - Visitor(AMQFrame& f, ClassifierHandler& c) - : chosen(0), frame(f), classifier(c) { f.getBody()->accept(*this); } - - void visit(const ExchangeDeclareBody&) { chosen=&classifier.wiring; } - void visit(const ExchangeDeleteBody&) { chosen=&classifier.wiring; } - void visit(const ExchangeBindBody&) { chosen=&classifier.wiring; } - void visit(const ExchangeUnbindBody&) { chosen=&classifier.wiring; } - void visit(const QueueDeclareBody&) { chosen=&classifier.wiring; } - void visit(const QueueDeleteBody&) { chosen=&classifier.wiring; } - void defaultVisit(const AMQBody&) { chosen=&classifier.other; } - - using framing::FrameDefaultVisitor::visit; - using framing::FrameDefaultVisitor::defaultVisit; - - FrameHandler* chosen; - AMQFrame& frame; - ClassifierHandler& classifier; -}; - -void ClassifierHandler::handle(AMQFrame& f) { Visitor(f, *this).chosen->handle(f); } - -}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/ClassifierHandler.h b/cpp/src/qpid/cluster/ClassifierHandler.h deleted file mode 100644 index 696e457c04..0000000000 --- a/cpp/src/qpid/cluster/ClassifierHandler.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef QPID_CLUSTER_CLASSIFIERHANDLER_H -#define QPID_CLUSTER_CLASSIFIERHANDLER_H - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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/framing/FrameHandler.h" - -namespace qpid { -namespace cluster { - -/** - * Classify frames and forward to the appropriate handler. - */ -class ClassifierHandler : public framing::FrameHandler -{ - public: - ClassifierHandler(framing::FrameHandler& wiring_, - framing::FrameHandler& other_) - : wiring(wiring_), other(other_) {} - - void handle(framing::AMQFrame&); - - private: - struct Visitor; - friend struct Visitor; - framing::FrameHandler& wiring; - framing::FrameHandler& other; -}; - -}} // namespace qpid::cluster - - - -#endif /*!QPID_CLUSTER_CLASSIFIERHANDLER_H*/ diff --git a/cpp/src/qpid/cluster/Cluster.cpp b/cpp/src/qpid/cluster/Cluster.cpp index 05ab9148b5..d049001eb0 100644 --- a/cpp/src/qpid/cluster/Cluster.cpp +++ b/cpp/src/qpid/cluster/Cluster.cpp @@ -16,305 +16,964 @@ * */ -#include "Cluster.h" -#include "ConnectionInterceptor.h" - +/** CLUSTER IMPLEMENTATION OVERVIEW + * + * The cluster works on the principle that if all members of the + * cluster receive identical input, they will all produce identical + * results. cluster::Connections intercept data received from clients + * and multicast it via CPG. The data is processed (passed to the + * broker::Connection) only when it is received from CPG in cluster + * order. Each cluster member has Connection objects for directly + * connected clients and "shadow" Connection objects for connections + * to other members. + * + * This assumes that all broker actions occur deterministically in + * response to data arriving on client connections. There are two + * situations where this assumption fails: + * - sending data in response to polling local connections for writabiliy. + * - taking actions based on a timer or timestamp comparison. + * + * IMPORTANT NOTE: any time code is added to the broker that uses timers, + * the cluster may need to be updated to take account of this. + * + * + * USE OF TIMESTAMPS IN THE BROKER + * + * The following are the current areas where broker uses timers or timestamps: + * + * - Producer flow control: broker::SemanticState uses connection::getClusterOrderOutput. + * a FrameHandler that sends frames to the client via the cluster. Used by broker::SessionState + * + * - QueueCleaner, Message TTL: uses ExpiryPolicy, which is implemented by cluster::ExpiryPolicy. + * + * - Connection heartbeat: sends connection controls, not part of session command counting so OK to ignore. + * + * - LinkRegistry: only cluster elder is ever active for links. + * + * - management::ManagementBroker: uses MessageHandler supplied by cluster + * to send messages to the broker via the cluster. + * + * - Dtx: not yet supported with cluster. + * + * cluster::ExpiryPolicy implements the strategy for message expiry. + * + * CLUSTER PROTOCOL OVERVIEW + * + * Messages sent to/from CPG are called Events. + * + * An Event carries a ConnectionId, which includes a MemberId and a + * connection number. + * + * Events are either + * - Connection events: non-0 connection number and are associated with a connection. + * - Cluster Events: 0 connection number, are not associated with a connection. + * + * Events are further categorized as: + * - Control: carries method frame(s) that affect cluster behavior. + * - Data: carries raw data received from a client connection. + * + * The cluster defines extensions to the AMQP command set in ../../../xml/cluster.xml + * which defines two classes: + * - cluster: cluster control information. + * - cluster.connection: control information for a specific connection. + * + * The following combinations are legal: + * - Data frames carrying connection data. + * - Cluster control events carrying cluster commands. + * - Connection control events carrying cluster.connection commands. + * - Connection control events carrying non-cluster frames: frames sent to the client. + * e.g. flow-control frames generated on a timer. + * + * CLUSTER INITIALIZATION OVERVIEW + * + * When a new member joins the CPG group, all members (including the + * new one) multicast their "initial status." The new member is in + * INIT mode until it gets a complete set of initial status messages + * from all cluster members. + * + * The newcomer uses initial status to determine + * - The cluster UUID + * - Am I speaking the correct version of the cluster protocol? + * - Do I need to get an update from an existing active member? + * - Can I recover from my own store? + * + * Initialization happens in the Cluster constructor (plugin + * early-init phase) because it needs to be done before the store + * initializes. In INIT mode sending & receiving from the cluster are + * done single-threaded, bypassing the normal PollableQueues because + * the Poller is not active at this point to service them. + */ +#include "qpid/Exception.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/ClusterSettings.h" +#include "qpid/cluster/Connection.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/cluster/RetractClient.h" +#include "qpid/cluster/FailoverExchange.h" +#include "qpid/cluster/UpdateExchange.h" + +#include "qpid/assert.h" +#include "qmf/org/apache/qpid/cluster/ArgsClusterStopClusterNode.h" +#include "qmf/org/apache/qpid/cluster/Package.h" #include "qpid/broker/Broker.h" -#include "qpid/broker/SessionState.h" #include "qpid/broker/Connection.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/SignalHandler.h" #include "qpid/framing/AMQFrame.h" -#include "qpid/framing/ClusterNotifyBody.h" -#include "qpid/framing/ClusterConnectionCloseBody.h" +#include "qpid/framing/AMQP_AllOperations.h" +#include "qpid/framing/AllInvoker.h" +#include "qpid/framing/ClusterConfigChangeBody.h" +#include "qpid/framing/ClusterConnectionDeliverCloseBody.h" +#include "qpid/framing/ClusterConnectionAbortBody.h" +#include "qpid/framing/ClusterRetractOfferBody.h" +#include "qpid/framing/ClusterConnectionDeliverDoOutputBody.h" +#include "qpid/framing/ClusterReadyBody.h" +#include "qpid/framing/ClusterShutdownBody.h" +#include "qpid/framing/ClusterUpdateOfferBody.h" +#include "qpid/framing/ClusterUpdateRequestBody.h" +#include "qpid/framing/ClusterConnectionAnnounceBody.h" +#include "qpid/framing/ClusterErrorCheckBody.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Helpers.h" #include "qpid/log/Statement.h" +#include "qpid/management/IdAllocator.h" +#include "qpid/management/ManagementAgent.h" #include "qpid/memory.h" -#include "qpid/shared_ptr.h" +#include "qpid/sys/Thread.h" +#include <boost/shared_ptr.hpp> #include <boost/bind.hpp> #include <boost/cast.hpp> +#include <boost/current_function.hpp> #include <algorithm> #include <iterator> #include <map> +#include <ostream> namespace qpid { namespace cluster { +using namespace qpid; using namespace qpid::framing; using namespace qpid::sys; +using namespace qpid::cluster; +using namespace framing::cluster; using namespace std; -using broker::Connection; +using management::ManagementAgent; +using management::ManagementObject; +using management::Manageable; +using management::Args; +namespace _qmf = ::qmf::org::apache::qpid::cluster; + +/** + * NOTE: must increment this number whenever any incompatible changes in + * cluster protocol/behavior are made. It allows early detection and + * sensible reporting of an attempt to mix different versions in a + * cluster. + * + * Currently use SVN revision to avoid clashes with versions from + * different branches. + */ +const uint32_t Cluster::CLUSTER_VERSION = 884125; -ostream& operator <<(ostream& out, const Cluster& cluster) { - return out << cluster.name.str() << "-" << cluster.self; -} +struct ClusterDispatcher : public framing::AMQP_AllOperations::ClusterHandler { + qpid::cluster::Cluster& cluster; + MemberId member; + Cluster::Lock& l; + ClusterDispatcher(Cluster& c, const MemberId& id, Cluster::Lock& l_) : cluster(c), member(id), l(l_) {} -ostream& operator<<(ostream& out, const Cluster::MemberMap::value_type& m) { - return out << m.first << "=" << m.second.url; -} + void updateRequest(const std::string& url) { cluster.updateRequest(member, url, l); } -ostream& operator <<(ostream& out, const Cluster::MemberMap& members) { - ostream_iterator<Cluster::MemberMap::value_type> o(out, " "); - copy(members.begin(), members.end(), o); - return out; -} + void initialStatus(uint32_t version, bool active, const Uuid& clusterId, + uint8_t storeState, const Uuid& shutdownId) + { + cluster.initialStatus(member, version, active, clusterId, + framing::cluster::StoreState(storeState), shutdownId, l); + } + void ready(const std::string& url) { cluster.ready(member, url, l); } + void configChange(const std::string& current) { cluster.configChange(member, current, l); } + void updateOffer(uint64_t updatee) { + cluster.updateOffer(member, updatee, l); + } + void retractOffer(uint64_t updatee) { cluster.retractOffer(member, updatee, l); } + void messageExpired(uint64_t id) { cluster.messageExpired(member, id, l); } + void errorCheck(uint8_t type, const framing::SequenceNumber& frameSeq) { + cluster.errorCheck(member, type, frameSeq, l); + } + + void shutdown(const Uuid& id) { cluster.shutdown(member, id, l); } + + bool invoke(AMQBody& body) { return framing::invoke(*this, body).wasHandled(); } +}; -Cluster::Cluster(const std::string& name_, const Url& url_, broker::Broker& b) : - broker(&b), +Cluster::Cluster(const ClusterSettings& set, broker::Broker& b) : + settings(set), + broker(b), + mgmtObject(0), poller(b.getPoller()), cpg(*this), - name(name_), - url(url_), + name(settings.name), + myUrl(settings.url.empty() ? Url() : Url(settings.url)), self(cpg.self()), - cpgDispatchHandle(cpg, - boost::bind(&Cluster::dispatch, this, _1), // read - 0, // write - boost::bind(&Cluster::disconnect, this, _1) // disconnect - ), - deliverQueue(boost::bind(&Cluster::deliverQueueCb, this, _1, _2)), - mcastQueue(boost::bind(&Cluster::mcastQueueCb, this, _1, _2)) + clusterId(true), + expiryPolicy(new ExpiryPolicy(mcast, self, broker.getTimer())), + mcast(cpg, poller, boost::bind(&Cluster::leave, this)), + dispatcher(cpg, poller, boost::bind(&Cluster::leave, this)), + deliverEventQueue(boost::bind(&Cluster::deliveredEvent, this, _1), + boost::bind(&Cluster::leave, this), + "Error decoding events", + poller), + deliverFrameQueue(boost::bind(&Cluster::deliveredFrame, this, _1), + boost::bind(&Cluster::leave, this), + "Error delivering frames", + poller), + quorum(boost::bind(&Cluster::leave, this)), + decoder(boost::bind(&Cluster::deliverFrame, this, _1)), + discarding(true), + state(INIT), + initMap(self, settings.size), + store(broker.getDataDir().getPath()), + lastSize(0), + lastBroker(false), + updateRetracted(false), + error(*this) { - broker->addFinalizer(boost::bind(&Cluster::leave, this)); - QPID_LOG(trace, "Joining cluster: " << name_); - cpg.join(name); - notify(); + mAgent = broker.getManagementAgent(); + if (mAgent != 0){ + _qmf::Package packageInit(mAgent); + mgmtObject = new _qmf::Cluster (mAgent, this, &broker,name,myUrl.str()); + mAgent->addObject (mgmtObject); + mgmtObject->set_status("JOINING"); + } - // FIXME aconway 2008-08-11: can we remove this loop? - // Dispatch till we show up in the cluster map. - while (empty()) - cpg.dispatchOne(); + // Failover exchange provides membership updates to clients. + failoverExchange.reset(new FailoverExchange(this)); + broker.getExchanges().registerExchange(failoverExchange); + + // Update exchange is used during updates to replicate messages + // without modifying delivery-properties.exchange. + broker.getExchanges().registerExchange( + boost::shared_ptr<broker::Exchange>(new UpdateExchange(this))); + // Load my store status before we go into initialization + if (! broker::NullMessageStore::isNullStore(&broker.getStore())) { + store.load(); + if (store.getClusterId()) + clusterId = store.getClusterId(); // Use stored ID if there is one. + QPID_LOG(notice, "Cluster store state: " << store) + } - // Start dispatching from the poller. - cpgDispatchHandle.startWatch(poller); - deliverQueue.start(poller); - mcastQueue.start(poller); + cpg.join(name); + // Pump the CPG dispatch manually till we get initialized. + while (state == INIT) + cpg.dispatchOne(); } Cluster::~Cluster() { - for (ShadowConnectionMap::iterator i = shadowConnectionMap.begin(); - i != shadowConnectionMap.end(); - ++i) - { - i->second->dirtyClose(); + if (updateThread.id()) updateThread.join(); // Join the previous updatethread. +} + +void Cluster::initialize() { + if (settings.quorum) quorum.start(poller); + if (myUrl.empty()) + myUrl = Url::getIpAddressesUrl(broker.getPort(broker::Broker::TCP_TRANSPORT)); + // Cluster constructor will leave us in either READY or JOINER state. + switch (state) { + case READY: + mcast.mcastControl(ClusterReadyBody(ProtocolVersion(), myUrl.str()), self); + break; + case JOINER: + mcast.mcastControl(ClusterUpdateRequestBody(ProtocolVersion(), myUrl.str()), self); + break; + default: + assert(0); } - std::for_each(localConnectionSet.begin(), localConnectionSet.end(), boost::bind(&ConnectionInterceptor::dirtyClose, _1)); + QPID_LOG(notice, *this << (state == READY ? " joined" : " joining") << " cluster " << name); + broker.getKnownBrokers = boost::bind(&Cluster::getUrls, this); + broker.setExpiryPolicy(expiryPolicy); + dispatcher.start(); + deliverEventQueue.start(); + deliverFrameQueue.start(); + + // Add finalizer last for exception safety. + broker.addFinalizer(boost::bind(&Cluster::brokerShutdown, this)); } -// local connection initializes plugins -void Cluster::initialize(broker::Connection& c) { - bool isLocal = &c.getOutput() != &shadowOut; - if (isLocal) - localConnectionSet.insert(new ConnectionInterceptor(c, *this)); +// Called in connection thread to insert a client connection. +void Cluster::addLocalConnection(const boost::intrusive_ptr<Connection>& c) { + QPID_LOG(info, *this << " new local connection " << c->getId()); + localConnections.insert(c); + assert(c->getId().getMember() == self); + // Announce the connection to the cluster. + if (c->isLocalClient()) + mcast.mcastControl(ClusterConnectionAnnounceBody(ProtocolVersion(), + c->getBrokerConnection().getSSF() ), + c->getId()); } -void Cluster::leave() { - Mutex::ScopedLock l(lock); - if (!broker) return; // Already left. - // At this point the poller has already been shut down so - // no dispatches can occur thru the cpgDispatchHandle. - // - // FIXME aconway 2008-08-11: assert this is the cae. - - QPID_LOG(debug, "Leaving cluster " << *this); - cpg.leave(name); - // broker= is set to 0 when the final config-change is delivered. - while(broker) { - Mutex::ScopedUnlock u(lock); - cpg.dispatchAll(); - } - cpg.shutdown(); +// Called in connection thread to insert an updated shadow connection. +void Cluster::addShadowConnection(const boost::intrusive_ptr<Connection>& c) { + QPID_LOG(info, *this << " new shadow connection " << c->getId()); + // Safe to use connections here because we're pre-catchup, stalled + // and discarding, so deliveredFrame is not processing any + // connection events. + assert(discarding); + pair<ConnectionMap::iterator, bool> ib + = connections.insert(ConnectionMap::value_type(c->getId(), c)); + assert(ib.second); +} + +void Cluster::erase(const ConnectionId& id) { + Lock l(lock); + erase(id,l); } -template <class T> void decodePtr(Buffer& buf, T*& ptr) { - uint64_t value = buf.getLongLong(); - ptr = reinterpret_cast<T*>(value); +// Called by Connection::deliverClose() in deliverFrameQueue thread. +void Cluster::erase(const ConnectionId& id, Lock&) { + QPID_LOG(info, *this << " connection closed " << id); + connections.erase(id); + decoder.erase(id); } -template <class T> void encodePtr(Buffer& buf, T* ptr) { - uint64_t value = reinterpret_cast<uint64_t>(ptr); - buf.putLongLong(value); +std::vector<string> Cluster::getIds() const { + Lock l(lock); + return getIds(l); } -void Cluster::send(const AMQFrame& frame, ConnectionInterceptor* connection) { - QPID_LOG(trace, "MCAST [" << connection << "] " << frame); - mcastQueue.push(Message(frame, self, connection)); +std::vector<string> Cluster::getIds(Lock&) const { + return map.memberIds(); } -void Cluster::mcastQueueCb(const MessageQueue::iterator& begin, - const MessageQueue::iterator& end) +std::vector<Url> Cluster::getUrls() const { + Lock l(lock); + return getUrls(l); +} + +std::vector<Url> Cluster::getUrls(Lock&) const { + return map.memberUrls(); +} + +void Cluster::leave() { + Lock l(lock); + leave(l); +} + +#define LEAVE_TRY(STMT) try { STMT; } \ + catch (const std::exception& e) { \ + QPID_LOG(warning, *this << " error leaving cluster: " << e.what()); \ + } do {} while(0) + +void Cluster::leave(Lock&) { + if (state != LEFT) { + state = LEFT; + QPID_LOG(notice, *this << " leaving cluster " << name); + // Finalize connections now now to avoid problems later in destructor. + LEAVE_TRY(localConnections.clear()); + LEAVE_TRY(connections.clear()); + LEAVE_TRY(broker::SignalHandler::shutdown()); + } +} + +// Deliver CPG message. +void Cluster::deliver( + cpg_handle_t /*handle*/, + const cpg_name* /*group*/, + uint32_t nodeid, + uint32_t pid, + void* msg, + int msg_len) { - // Static is OK because there is only one cluster allowed per - // process and only one thread in mcastQueueCb at a time. - static char buffer[64*1024]; // FIXME aconway 2008-07-04: buffer management. - MessageQueue::iterator i = begin; - while (i != end) { - Buffer buf(buffer, sizeof(buffer)); - while (i != end && buf.available() > i->frame.size() + sizeof(uint64_t)) { - i->frame.encode(buf); - encodePtr(buf, i->connection); - ++i; + MemberId from(nodeid, pid); + framing::Buffer buf(static_cast<char*>(msg), msg_len); + Event e(Event::decodeCopy(from, buf)); + deliverEvent(e); +} + +void Cluster::deliverEvent(const Event& e) { + // During initialization, execute events directly in the same thread. + // Once initialized, push to pollable queue to be processed in another thread. + if (state == INIT) + deliveredEvent(e); + else + deliverEventQueue.push(e); +} + +void Cluster::deliverFrame(const EventFrame& e) { + // During initialization, execute events directly in the same thread. + // Once initialized, push to pollable queue to be processed in another thread. + if (state == INIT) + deliveredFrame(e); + else + deliverFrameQueue.push(e); +} + +const ClusterUpdateOfferBody* castUpdateOffer(const framing::AMQBody* body) { + return (body && body->getMethod() && + body->getMethod()->isA<ClusterUpdateOfferBody>()) ? + static_cast<const ClusterUpdateOfferBody*>(body) : 0; +} + +const ClusterConnectionAnnounceBody* castAnnounce( const framing::AMQBody *body) { + return (body && body->getMethod() && + body->getMethod()->isA<ClusterConnectionAnnounceBody>()) ? + static_cast<const ClusterConnectionAnnounceBody*>(body) : 0; +} + +// Handler for deliverEventQueue. +// This thread decodes frames from events. +void Cluster::deliveredEvent(const Event& e) { + if (e.isCluster()) { + EventFrame ef(e, e.getFrame()); + // Stop the deliverEventQueue on update offers. + // This preserves the connection decoder fragments for an update. + const ClusterUpdateOfferBody* offer = castUpdateOffer(ef.frame.getBody()); + if (offer) { + QPID_LOG(info, *this << " stall for update offer from " << e.getMemberId() + << " to " << MemberId(offer->getUpdatee())); + deliverEventQueue.stop(); + } + deliverFrame(ef); + } + else if(!discarding) { + if (e.isControl()) + deliverFrame(EventFrame(e, e.getFrame())); + else { + try { decoder.decode(e, e.getData()); } + catch (const Exception& ex) { + // Close a connection that is sending us invalid data. + QPID_LOG(error, *this << " aborting connection " + << e.getConnectionId() << ": " << ex.what()); + framing::AMQFrame abort((ClusterConnectionAbortBody())); + deliverFrame(EventFrame(EventHeader(CONTROL, e.getConnectionId()), abort)); + } } - iovec iov = { buffer, buf.getPosition() }; - cpg.mcast(name, &iov, 1); } } -void Cluster::notify() { - send(AMQFrame(in_place<ClusterNotifyBody>(ProtocolVersion(), url.str())), 0); +void Cluster::flagError( + Connection& connection, ErrorCheck::ErrorType type, const std::string& msg) +{ + Mutex::ScopedLock l(lock); + if (connection.isCatchUp()) { + QPID_LOG(critical, *this << " error on update connection " << connection + << ": " << msg); + leave(l); + } + error.error(connection, type, map.getFrameSeq(), map.getMembers(), msg); } -size_t Cluster::size() const { +// Handler for deliverFrameQueue. +// This thread executes the main logic. +void Cluster::deliveredFrame(const EventFrame& efConst) { Mutex::ScopedLock l(lock); - return members.size(); + if (state == LEFT) return; + EventFrame e(efConst); + const ClusterUpdateOfferBody* offer = castUpdateOffer(e.frame.getBody()); + if (offer && error.isUnresolved()) { + // We can't honour an update offer that is delivered while an + // error is in progress so replace it with a retractOffer and re-start + // the event queue. + e.frame = AMQFrame( + ClusterRetractOfferBody(ProtocolVersion(), offer->getUpdatee())); + deliverEventQueue.start(); + } + // Process each frame through the error checker. + if (error.isUnresolved()) { + error.delivered(e); + while (error.canProcess()) // There is a frame ready to process. + processFrame(error.getNext(), l); + } + else + processFrame(e, l); } -Cluster::MemberList Cluster::getMembers() const { - Mutex::ScopedLock l(lock); - MemberList result(members.size()); - std::transform(members.begin(), members.end(), result.begin(), - boost::bind(&MemberMap::value_type::second, _1)); - return result; + +void Cluster::processFrame(const EventFrame& e, Lock& l) { + if (e.isCluster()) { + QPID_LOG(trace, *this << " DLVR: " << e); + ClusterDispatcher dispatch(*this, e.connectionId.getMember(), l); + if (!framing::invoke(dispatch, *e.frame.getBody()).wasHandled()) + throw Exception(QPID_MSG("Invalid cluster control")); + } + else if (state >= CATCHUP) { + map.incrementFrameSeq(); + ConnectionPtr connection = getConnection(e, l); + if (connection) { + QPID_LOG(trace, *this << " DLVR " << map.getFrameSeq() << ": " << e); + connection->deliveredFrame(e); + } + else + QPID_LOG(trace, *this << " DROP (no connection): " << e); + } + else // Drop connection frames while state < CATCHUP + QPID_LOG(trace, *this << " DROP (joining): " << e); } -// ################ HERE - leaking shadow connections. -// FIXME aconway 2008-08-11: revisit memory management for shadow -// connections, what if the Connection is closed other than via -// disconnect? Dangling pointer in shadow map. Use ptr_map for shadow -// map, add deleted state to ConnectionInterceptor? Interceptors need -// to know about map? Check how Connections can be deleted. +// Called in deliverFrameQueue thread +ConnectionPtr Cluster::getConnection(const EventFrame& e, Lock&) { + ConnectionId id = e.connectionId; + ConnectionMap::iterator i = connections.find(id); + if (i != connections.end()) return i->second; + ConnectionPtr cp; + // If the frame is an announcement for a new connection, add it. + if (e.frame.getBody() && e.frame.getMethod() && + e.frame.getMethod()->isA<ClusterConnectionAnnounceBody>()) + { + if (id.getMember() == self) { // Announces one of my own + cp = localConnections.getErase(id); + assert(cp); + } + else { // New remote connection, create a shadow. + std::ostringstream mgmtId; + unsigned int ssf; + const ClusterConnectionAnnounceBody *announce = castAnnounce(e.frame.getBody()); + + mgmtId << id; + ssf = (announce && announce->hasSsf()) ? announce->getSsf() : 0; + QPID_LOG(debug, *this << "new connection's ssf =" << ssf ); + cp = new Connection(*this, shadowOut, mgmtId.str(), id, ssf ); + } + connections.insert(ConnectionMap::value_type(id, cp)); + } + return cp; +} -ConnectionInterceptor* Cluster::getShadowConnection(const Cpg::Id& member, void* remotePtr) { - ShadowConnectionId id(member, remotePtr); - ShadowConnectionMap::iterator i = shadowConnectionMap.find(id); - if (i == shadowConnectionMap.end()) { // A new shadow connection. - std::ostringstream os; - os << name << ":" << member << ":" << remotePtr; - assert(broker); - broker::Connection* c = new broker::Connection(&shadowOut, *broker, os.str()); - ShadowConnectionMap::value_type value(id, new ConnectionInterceptor(*c, *this, id)); - i = shadowConnectionMap.insert(value).first; +Cluster::ConnectionVector Cluster::getConnections(Lock&) { + ConnectionVector result(connections.size()); + std::transform(connections.begin(), connections.end(), result.begin(), + boost::bind(&ConnectionMap::value_type::second, _1)); + return result; +} + +struct AddrList { + const cpg_address* addrs; + int count; + const char *prefix, *suffix; + AddrList(const cpg_address* a, int n, const char* p="", const char* s="") + : addrs(a), count(n), prefix(p), suffix(s) {} +}; + +ostream& operator<<(ostream& o, const AddrList& a) { + if (!a.count) return o; + o << a.prefix; + for (const cpg_address* p = a.addrs; p < a.addrs+a.count; ++p) { + const char* reasonString; + switch (p->reason) { + case CPG_REASON_JOIN: reasonString = "(joined) "; break; + case CPG_REASON_LEAVE: reasonString = "(left) "; break; + case CPG_REASON_NODEDOWN: reasonString = "(node-down) "; break; + case CPG_REASON_NODEUP: reasonString = "(node-up) "; break; + case CPG_REASON_PROCDOWN: reasonString = "(process-down) "; break; + default: reasonString = " "; + } + qpid::cluster::MemberId member(*p); + o << member << reasonString; } - return i->second; + return o << a.suffix; } -void Cluster::deliver( +void Cluster::configChange ( cpg_handle_t /*handle*/, - cpg_name* /*group*/, - uint32_t nodeid, - uint32_t pid, - void* msg, - int msg_len) + const cpg_name */*group*/, + const cpg_address *current, int nCurrent, + const cpg_address *left, int nLeft, + const cpg_address *joined, int nJoined) { - Id from(nodeid, pid); - try { - Buffer buf(static_cast<char*>(msg), msg_len); - while (buf.available() > 0) { - AMQFrame frame; - if (!frame.decode(buf)) // Not enough data. - throw Exception("Received incomplete cluster event."); - void* connection; - decodePtr(buf, connection); - deliverQueue.push(Message(frame, from, connection)); + Mutex::ScopedLock l(lock); + QPID_LOG(notice, *this << " membership change: " + << AddrList(current, nCurrent) << "(" + << AddrList(joined, nJoined, "joined: ") + << AddrList(left, nLeft, "left: ") + << ")"); + std::string addresses; + for (const cpg_address* p = current; p < current+nCurrent; ++p) + addresses.append(MemberId(*p).str()); + deliverEvent(Event::control(ClusterConfigChangeBody(ProtocolVersion(), addresses), self)); +} + +void Cluster::setReady(Lock&) { + state = READY; + if (mgmtObject!=0) mgmtObject->set_status("ACTIVE"); + mcast.setReady(); + broker.getQueueEvents().enable(); +} + +void Cluster::initMapCompleted(Lock& l) { + // Called on completion of the initial status map. + QPID_LOG(debug, *this << " initial status map complete. "); + if (state == INIT) { + // We have status for all members so we can make join descisions. + initMap.checkConsistent(); + elders = initMap.getElders(); + QPID_LOG(debug, *this << " elders: " << elders); + if (!elders.empty()) { // I'm not the elder, I don't handle links & replication. + broker.getLinks().setPassive(true); + broker.getQueueEvents().disable(); + QPID_LOG(info, *this << " not active for links."); + } + else { + QPID_LOG(info, this << " active for links."); + } + setClusterId(initMap.getClusterId(), l); + if (store.hasStore()) store.dirty(clusterId); + + if (initMap.isUpdateNeeded()) { // Joining established cluster. + broker.setRecovery(false); // Ditch my current store. + broker.setClusterUpdatee(true); + state = JOINER; + } + else { // I can go ready. + discarding = false; + setReady(l); + memberUpdate(l); } + QPID_LOG(debug, *this << "Initialization complete"); + } +} + +void Cluster::configChange(const MemberId&, const std::string& configStr, Lock& l) { + if (state == LEFT) return; + + MemberSet config = decodeMemberSet(configStr); + elders = intersection(elders, config); + if (elders.empty() && INIT < state && state < CATCHUP) { + QPID_LOG(critical, "Cannot update, all potential updaters left the cluster."); + leave(l); + return; } + bool memberChange = map.configChange(config); + + // Update initital status for new members joining. + initMap.configChange(config); + if (initMap.isResendNeeded()) { + mcast.mcastControl( + ClusterInitialStatusBody( + ProtocolVersion(), CLUSTER_VERSION, state > INIT, clusterId, + store.getState(), store.getShutdownId() + ), + self); + } + if (initMap.transitionToComplete()) initMapCompleted(l); + + if (state >= CATCHUP && memberChange) { + memberUpdate(l); + if (elders.empty()) { + // We are the oldest, reactive links if necessary + QPID_LOG(info, this << " becoming active for links."); + broker.getLinks().setPassive(false); + } + } +} + +void Cluster::makeOffer(const MemberId& id, Lock& ) { + if (state == READY && map.isJoiner(id)) { + state = OFFER; + QPID_LOG(info, *this << " send update-offer to " << id); + mcast.mcastControl(ClusterUpdateOfferBody(ProtocolVersion(), id), self); + } +} + +// Called from Broker::~Broker when broker is shut down. At this +// point we know the poller has stopped so no poller callbacks will be +// invoked. We must ensure that CPG has also shut down so no CPG +// callbacks will be invoked. +// +void Cluster::brokerShutdown() { + try { cpg.shutdown(); } catch (const std::exception& e) { - // FIXME aconway 2008-01-30: exception handling. - QPID_LOG(critical, "Error in cluster deliver: " << e.what()); - assert(0); - throw; + QPID_LOG(error, *this << " shutting down CPG: " << e.what()); } + delete this; +} + +void Cluster::updateRequest(const MemberId& id, const std::string& url, Lock& l) { + map.updateRequest(id, url); + makeOffer(id, l); } -void Cluster::deliverQueueCb(const MessageQueue::iterator& begin, - const MessageQueue::iterator& end) +void Cluster::initialStatus(const MemberId& member, uint32_t version, bool active, + const framing::Uuid& id, + framing::cluster::StoreState store, + const framing::Uuid& shutdownId, + Lock& l) { - for (MessageQueue::iterator i = begin; i != end; ++i) { - AMQFrame& frame(i->frame); - Id from(i->from); - ConnectionInterceptor* connection = reinterpret_cast<ConnectionInterceptor*>(i->connection); - try { - QPID_LOG(trace, "DLVR [" << from << " " << connection << "] " << frame); - - if (!broker) { - QPID_LOG(warning, "Unexpected DLVR, already left the cluster."); - return; - } - if (connection && from != self) // Look up shadow for remote connections - connection = getShadowConnection(from, connection); + if (version != CLUSTER_VERSION) { + QPID_LOG(critical, *this << " incompatible cluster versions " << + version << " != " << CLUSTER_VERSION); + leave(l); + return; + } + initMap.received( + member, + ClusterInitialStatusBody(ProtocolVersion(), version, active, id, store, shutdownId) + ); + if (initMap.transitionToComplete()) initMapCompleted(l); +} - if (frame.getMethod() && frame.getMethod()->amqpClassId() == CLUSTER_CLASS_ID) - handleMethod(from, connection, *frame.getMethod()); - else - connection->deliver(frame); +void Cluster::ready(const MemberId& id, const std::string& url, Lock& l) { + if (map.ready(id, Url(url))) + memberUpdate(l); + if (state == CATCHUP && id == self) { + setReady(l); + QPID_LOG(notice, *this << " caught up."); + } +} + +void Cluster::updateOffer(const MemberId& updater, uint64_t updateeInt, Lock& l) { + // NOTE: deliverEventQueue has been stopped at the update offer by + // deliveredEvent in case an update is required. + if (state == LEFT) return; + MemberId updatee(updateeInt); + boost::optional<Url> url = map.updateOffer(updater, updatee); + if (updater == self) { + assert(state == OFFER); + if (url) // My offer was first. + updateStart(updatee, *url, l); + else { // Another offer was first. + QPID_LOG(info, *this << " cancelled offer to " << updatee << " unstall"); + setReady(l); + makeOffer(map.firstJoiner(), l); // Maybe make another offer. + deliverEventQueue.start(); // Go back to normal processing } - catch (const std::exception& e) { - // FIXME aconway 2008-01-30: exception handling. - QPID_LOG(critical, "Error in cluster deliverQueueCb: " << e.what()); - assert(0); - throw; + } + else if (updatee == self && url) { + assert(state == JOINER); + state = UPDATEE; + QPID_LOG(notice, *this << " receiving update from " << updater); + checkUpdateIn(l); + } + else { + QPID_LOG(debug,*this << " unstall, ignore update " << updater + << " to " << updatee); + deliverEventQueue.start(); // Not involved in update. + } +} + +static client::ConnectionSettings connectionSettings(const ClusterSettings& settings) { + client::ConnectionSettings cs; + cs.username = settings.username; + cs.password = settings.password; + cs.mechanism = settings.mechanism; + return cs; +} + +void Cluster::retractOffer(const MemberId& updater, uint64_t updateeInt, Lock& l) { + // An offer was received while handling an error, and converted to a retract. + // Behavior is very similar to updateOffer. + if (state == LEFT) return; + MemberId updatee(updateeInt); + boost::optional<Url> url = map.updateOffer(updater, updatee); + if (updater == self) { + assert(state == OFFER); + if (url) { // My offer was first. + if (updateThread.id()) + updateThread.join(); // Join the previous updateThread to avoid leaks. + updateThread = Thread(new RetractClient(*url, connectionSettings(settings))); } + setReady(l); + makeOffer(map.firstJoiner(), l); // Maybe make another offer. + // Don't unstall the event queue, that was already done in deliveredFrame } + QPID_LOG(debug,*this << " retracted offer " << updater << " to " << updatee); +} + +void Cluster::updateStart(const MemberId& updatee, const Url& url, Lock& l) { + // NOTE: deliverEventQueue is already stopped at the stall point by deliveredEvent. + if (state == LEFT) return; + assert(state == OFFER); + state = UPDATER; + QPID_LOG(notice, *this << " sending update to " << updatee << " at " << url); + if (updateThread.id()) + updateThread.join(); // Join the previous updateThread to avoid leaks. + updateThread = Thread( + new UpdateClient(self, updatee, url, broker, map, *expiryPolicy, + getConnections(l), decoder, + boost::bind(&Cluster::updateOutDone, this), + boost::bind(&Cluster::updateOutError, this, _1), + connectionSettings(settings))); } -// Handle cluster methods -// FIXME aconway 2008-07-11: Generate/template a better dispatch mechanism. -void Cluster::handleMethod(Id from, ConnectionInterceptor* connection, AMQMethodBody& method) { - assert(method.amqpClassId() == CLUSTER_CLASS_ID); - switch (method.amqpMethodId()) { - case CLUSTER_NOTIFY_METHOD_ID: { - ClusterNotifyBody& notify=static_cast<ClusterNotifyBody&>(method); - Mutex::ScopedLock l(lock); - members[from].url=notify.getUrl(); - lock.notifyAll(); - break; - } - case CLUSTER_CONNECTION_CLOSE_METHOD_ID: { - if (!connection->isLocal()) - shadowConnectionMap.erase(connection->getShadowId()); - else - localConnectionSet.erase(connection); - connection->deliverClosed(); - break; - } - case CLUSTER_CONNECTION_DO_OUTPUT_METHOD_ID: { - connection->deliverDoOutput(); - break; - } +// Called in update thread. +void Cluster::updateInDone(const ClusterMap& m) { + Lock l(lock); + updatedMap = m; + checkUpdateIn(l); +} + +void Cluster::updateInRetracted() { + Lock l(lock); + updateRetracted = true; + map.clearStatus(); + checkUpdateIn(l); +} + +void Cluster::checkUpdateIn(Lock& l) { + if (state != UPDATEE) return; // Wait till we reach the stall point. + if (updatedMap) { // We're up to date + map = *updatedMap; + memberUpdate(l); + mcast.mcastControl(ClusterReadyBody(ProtocolVersion(), myUrl.str()), self); + state = CATCHUP; + broker.setClusterUpdatee(false); + discarding = false; // ok to set, we're stalled for update. + QPID_LOG(notice, *this << " update complete, starting catch-up."); + deliverEventQueue.start(); + } + else if (updateRetracted) { // Update was retracted, request another update + updateRetracted = false; + state = JOINER; + QPID_LOG(notice, *this << " update retracted, sending new update request."); + mcast.mcastControl(ClusterUpdateRequestBody(ProtocolVersion(), myUrl.str()), self); + deliverEventQueue.start(); + } +} + +void Cluster::updateOutDone() { + Monitor::ScopedLock l(lock); + updateOutDone(l); +} + +void Cluster::updateOutDone(Lock& l) { + QPID_LOG(notice, *this << " update sent"); + assert(state == UPDATER); + state = READY; + deliverEventQueue.start(); // Start processing events again. + makeOffer(map.firstJoiner(), l); // Try another offer +} + +void Cluster::updateOutError(const std::exception& e) { + Monitor::ScopedLock l(lock); + QPID_LOG(error, *this << " error sending update: " << e.what()); + updateOutDone(l); +} + +void Cluster ::shutdown(const MemberId& , const Uuid& id, Lock& l) { + QPID_LOG(notice, *this << " cluster shut down by administrator."); + if (store.hasStore()) store.clean(Uuid(id)); + leave(l); +} + +ManagementObject* Cluster::GetManagementObject() const { return mgmtObject; } + +Manageable::status_t Cluster::ManagementMethod (uint32_t methodId, Args& args, string&) { + Lock l(lock); + QPID_LOG(debug, *this << " managementMethod [id=" << methodId << "]"); + switch (methodId) { + case _qmf::Cluster::METHOD_STOPCLUSTERNODE : + { + _qmf::ArgsClusterStopClusterNode& iargs = (_qmf::ArgsClusterStopClusterNode&) args; + stringstream stream; + stream << self; + if (iargs.i_brokerId == stream.str()) + stopClusterNode(l); + } + break; + case _qmf::Cluster::METHOD_STOPFULLCLUSTER : + stopFullCluster(l); + break; default: - assert(0); + return Manageable::STATUS_UNKNOWN_METHOD; } + return Manageable::STATUS_OK; } -void Cluster::configChange( - cpg_handle_t /*handle*/, - cpg_name */*group*/, - cpg_address *current, int nCurrent, - cpg_address *left, int nLeft, - cpg_address */*joined*/, int nJoined) -{ - Mutex::ScopedLock l(lock); - for (int i = 0; i < nLeft; ++i) - members.erase(left[i]); - for(int j = 0; j < nCurrent; ++j) - members[current[j]].id = current[j]; - QPID_LOG(debug, "Cluster members: " << nCurrent << " ("<< nLeft << " left, " << nJoined << " joined):" - << members); - assert(members.size() == size_t(nCurrent)); - if (members.find(self) == members.end()) - broker = 0; // We have left the group, this is the final config change. - lock.notifyAll(); // Threads waiting for membership changes. +void Cluster::stopClusterNode(Lock& l) { + QPID_LOG(notice, *this << " cluster member stopped by administrator."); + leave(l); } -void Cluster::dispatch(sys::DispatchHandle& h) { - cpg.dispatchAll(); - h.rewatch(); +void Cluster::stopFullCluster(Lock& ) { + QPID_LOG(notice, *this << " shutting down cluster " << name); + mcast.mcastControl(ClusterShutdownBody(ProtocolVersion(), Uuid(true)), self); } -void Cluster::disconnect(sys::DispatchHandle& h) { - h.stopWatch(); - // FIXME aconway 2008-08-11: error handling if we are disconnected. - // Kill the broker? - assert(0); +void Cluster::memberUpdate(Lock& l) { + QPID_LOG(info, *this << " member update: " << map); + std::vector<Url> urls = getUrls(l); + std::vector<string> ids = getIds(l); + size_t size = urls.size(); + failoverExchange->setUrls(urls); + + if (size == 1 && lastSize > 1 && state >= CATCHUP) { + QPID_LOG(notice, *this << " last broker standing, update queue policies"); + lastBroker = true; + broker.getQueues().updateQueueClusterState(true); + } + else if (size > 1 && lastBroker) { + QPID_LOG(notice, *this << " last broker standing joined by " << size-1 << " replicas, updating queue policies" << size); + lastBroker = false; + broker.getQueues().updateQueueClusterState(false); + } + lastSize = size; + + if (mgmtObject) { + mgmtObject->set_clusterSize(size); + string urlstr; + for(std::vector<Url>::iterator iter = urls.begin(); iter != urls.end(); iter++ ) { + if (iter != urls.begin()) urlstr += ";"; + urlstr += iter->str(); + } + string idstr; + for(std::vector<string>::iterator iter = ids.begin(); iter != ids.end(); iter++ ) { + if (iter != ids.begin()) idstr += ";"; + idstr += (*iter); + } + mgmtObject->set_members(urlstr); + mgmtObject->set_memberIDs(idstr); + } + + // Close connections belonging to members that have left the cluster. + ConnectionMap::iterator i = connections.begin(); + while (i != connections.end()) { + ConnectionMap::iterator j = i++; + MemberId m = j->second->getId().getMember(); + if (m != self && !map.isMember(m)) { + j->second->getBrokerConnection().closed(); + erase(j->second->getId(), l); + } + } } -}} // namespace qpid::cluster +std::ostream& operator<<(std::ostream& o, const Cluster& cluster) { + static const char* STATE[] = { + "INIT", "JOINER", "UPDATEE", "CATCHUP", "READY", "OFFER", "UPDATER", "LEFT" + }; + assert(sizeof(STATE)/sizeof(*STATE) == Cluster::LEFT+1); + o << "cluster(" << cluster.self << " " << STATE[cluster.state]; + if (cluster.error.isUnresolved()) o << "/error"; + return o << ")";; +} +MemberId Cluster::getId() const { + return self; // Immutable, no need to lock. +} +broker::Broker& Cluster::getBroker() const { + return broker; // Immutable, no need to lock. +} + +void Cluster::setClusterId(const Uuid& uuid, Lock&) { + clusterId = uuid; + if (mgmtObject) { + stringstream stream; + stream << self; + mgmtObject->set_clusterID(clusterId.str()); + mgmtObject->set_memberID(stream.str()); + } + QPID_LOG(debug, *this << " cluster-uuid = " << clusterId); +} + +void Cluster::messageExpired(const MemberId&, uint64_t id, Lock&) { + expiryPolicy->deliverExpire(id); +} + +void Cluster::errorCheck(const MemberId& from, uint8_t type, framing::SequenceNumber frameSeq, Lock&) { + // If we see an errorCheck here (rather than in the ErrorCheck + // class) then we have processed succesfully past the point of the + // error. + if (state >= CATCHUP) // Don't respond pre catchup, we don't know what happened + error.respondNone(from, type, frameSeq); +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/Cluster.h b/cpp/src/qpid/cluster/Cluster.h index 2b40193dd3..7872588307 100644 --- a/cpp/src/qpid/cluster/Cluster.h +++ b/cpp/src/qpid/cluster/Cluster.h @@ -19,151 +19,261 @@ * */ -#include "qpid/cluster/Cpg.h" -#include "qpid/cluster/ShadowConnectionOutputHandler.h" -#include "qpid/cluster/PollableQueue.h" - +#include "ClusterMap.h" +#include "ClusterSettings.h" +#include "Cpg.h" +#include "Decoder.h" +#include "ErrorCheck.h" +#include "Event.h" +#include "EventFrame.h" +#include "ExpiryPolicy.h" +#include "FailoverExchange.h" +#include "InitialStatusMap.h" +#include "LockedConnectionMap.h" +#include "Multicaster.h" +#include "NoOpConnectionOutputHandler.h" +#include "PollableQueue.h" +#include "PollerDispatch.h" +#include "Quorum.h" +#include "StoreStatus.h" +#include "UpdateReceiver.h" + +#include "qmf/org/apache/qpid/cluster/Cluster.h" +#include "qpid/Url.h" #include "qpid/broker/Broker.h" -#include "qpid/broker/Connection.h" -#include "qpid/sys/Dispatcher.h" +#include "qpid/management/Manageable.h" #include "qpid/sys/Monitor.h" -#include "qpid/sys/Runnable.h" -#include "qpid/sys/Thread.h" -#include "qpid/log/Logger.h" -#include "qpid/Url.h" -#include "qpid/RefCounted.h" -#include <boost/optional.hpp> -#include <boost/function.hpp> +#include <boost/bind.hpp> #include <boost/intrusive_ptr.hpp> +#include <boost/optional.hpp> +#include <algorithm> #include <map> #include <vector> namespace qpid { + +namespace framing { +class AMQBody; +class Uuid; +} + namespace cluster { -class ConnectionInterceptor; +class Connection; +class EventFrame; /** - * Connection to the cluster. - * Keeps cluster membership data. + * Connection to the cluster */ -class Cluster : private Cpg::Handler, public RefCounted -{ +class Cluster : private Cpg::Handler, public management::Manageable { public: - typedef boost::tuple<Cpg::Id, void*> ShadowConnectionId; + typedef boost::intrusive_ptr<Connection> ConnectionPtr; + typedef std::vector<ConnectionPtr> ConnectionVector; - /** Details of a cluster member */ - struct Member { - Cpg::Id id; - Url url; - }; - - typedef std::vector<Member> MemberList; - - /** - * Join a cluster. - * @param name of the cluster. - * @param url of this broker, sent to the cluster. - */ - Cluster(const std::string& name, const Url& url, broker::Broker&); + // Public functions are thread safe unless otherwise mentioned in a comment. + // Construct the cluster in plugin earlyInitialize. + Cluster(const ClusterSettings&, broker::Broker&); virtual ~Cluster(); - /** Initialize interceptors for a new connection */ - void initialize(broker::Connection&); - - /** Get the current cluster membership. */ - MemberList getMembers() const; - - /** Number of members in the cluster. */ - size_t size() const; + // Called by plugin initialize: cluster start-up requires transport plugins . + // Thread safety: only called by plugin initialize. + void initialize(); - bool empty() const { return size() == 0; } + // Connection map. + void addLocalConnection(const ConnectionPtr&); + void addShadowConnection(const ConnectionPtr&); + void erase(const ConnectionId&); - /** Send frame to the cluster */ - void send(const framing::AMQFrame&, ConnectionInterceptor*); + // URLs of current cluster members. + std::vector<std::string> getIds() const; + std::vector<Url> getUrls() const; + boost::shared_ptr<FailoverExchange> getFailoverExchange() const { return failoverExchange; } - /** Leave the cluster */ + // Leave the cluster - called when fatal errors occur. void leave(); - - // Cluster frame handing functions - void notify(const std::string& url); - void connectionClose(); + + // Update completed - called in update thread + void updateInDone(const ClusterMap&); + void updateInRetracted(); + + MemberId getId() const; + broker::Broker& getBroker() const; + Multicaster& getMulticast() { return mcast; } + + const ClusterSettings& getSettings() const { return settings; } + + void deliverFrame(const EventFrame&); + + // Called in deliverFrame thread to indicate an error from the broker. + void flagError(Connection&, ErrorCheck::ErrorType, const std::string& msg); + + // Called only during update by Connection::shadowReady + Decoder& getDecoder() { return decoder; } + + ExpiryPolicy& getExpiryPolicy() { return *expiryPolicy; } + + UpdateReceiver& getUpdateReceiver() { return updateReceiver; } private: - typedef Cpg::Id Id; - typedef std::map<Id, Member> MemberMap; - typedef std::map<ShadowConnectionId, ConnectionInterceptor*> ShadowConnectionMap; - typedef std::set<ConnectionInterceptor*> LocalConnectionSet; - - /** Message sent over the cluster. */ - struct Message { - framing::AMQFrame frame; Id from; void* connection; - Message(const framing::AMQFrame& f, const Id i, void* c) - : frame(f), from(i), connection(c) {} - }; - typedef PollableQueue<Message> MessageQueue; - - boost::function<void()> shutdownNext; - - void notify(); ///< Notify cluster of my details. + typedef sys::Monitor::ScopedLock Lock; - /** CPG deliver callback. */ - void deliver( + typedef PollableQueue<Event> PollableEventQueue; + typedef PollableQueue<EventFrame> PollableFrameQueue; + typedef std::map<ConnectionId, ConnectionPtr> ConnectionMap; + + /** Version number of the cluster protocol, to avoid mixed versions. */ + static const uint32_t CLUSTER_VERSION; + + // NB: A dummy Lock& parameter marks functions that must only be + // called with Cluster::lock locked. + + void leave(Lock&); + std::vector<std::string> getIds(Lock&) const; + std::vector<Url> getUrls(Lock&) const; + + // == Called in main thread from Broker destructor. + void brokerShutdown(); + + // == Called in deliverEventQueue thread + void deliveredEvent(const Event&); + + // == Called in deliverFrameQueue thread + void deliveredFrame(const EventFrame&); + void processFrame(const EventFrame&, Lock&); + + // Cluster controls implement XML methods from cluster.xml. + void updateRequest(const MemberId&, const std::string&, Lock&); + void updateOffer(const MemberId& updater, uint64_t updatee, Lock&); + void retractOffer(const MemberId& updater, uint64_t updatee, Lock&); + void initialStatus(const MemberId&, + uint32_t version, + bool active, + const framing::Uuid& clusterId, + framing::cluster::StoreState, + const framing::Uuid& shutdownId, + Lock&); + void ready(const MemberId&, const std::string&, Lock&); + void configChange(const MemberId&, const std::string& current, Lock& l); + void messageExpired(const MemberId&, uint64_t, Lock& l); + void errorCheck(const MemberId&, uint8_t type, SequenceNumber frameSeq, Lock&); + + void shutdown(const MemberId&, const framing::Uuid& shutdownId, Lock&); + + // Helper functions + ConnectionPtr getConnection(const EventFrame&, Lock&); + ConnectionVector getConnections(Lock&); + void updateStart(const MemberId& updatee, const Url& url, Lock&); + void makeOffer(const MemberId&, Lock&); + void setReady(Lock&); + void memberUpdate(Lock&); + void setClusterId(const framing::Uuid&, Lock&); + void erase(const ConnectionId&, Lock&); + + void initMapCompleted(Lock&); + + + + // == Called in CPG dispatch thread + void deliver( // CPG deliver callback. cpg_handle_t /*handle*/, - struct cpg_name *group, + const struct cpg_name *group, uint32_t /*nodeid*/, uint32_t /*pid*/, void* /*msg*/, int /*msg_len*/); - /** CPG config change callback */ - void configChange( + void deliverEvent(const Event&); + + void configChange( // CPG config change callback. cpg_handle_t /*handle*/, - struct cpg_name */*group*/, - struct cpg_address */*members*/, int /*nMembers*/, - struct cpg_address */*left*/, int /*nLeft*/, - struct cpg_address */*joined*/, int /*nJoined*/ + const struct cpg_name */*group*/, + const struct cpg_address */*members*/, int /*nMembers*/, + const struct cpg_address */*left*/, int /*nLeft*/, + const struct cpg_address */*joined*/, int /*nJoined*/ ); - /** Callback to handle delivered frames from the deliverQueue. */ - void deliverQueueCb(const MessageQueue::iterator& begin, - const MessageQueue::iterator& end); - - /** Callback to multi-cast frames from mcastQueue */ - void mcastQueueCb(const MessageQueue::iterator& begin, - const MessageQueue::iterator& end); + // == Called in management threads. + virtual qpid::management::ManagementObject* GetManagementObject() const; + virtual management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + void stopClusterNode(Lock&); + void stopFullCluster(Lock&); - /** Callback to dispatch CPG events. */ - void dispatch(sys::DispatchHandle&); - /** Callback if CPG fd is disconnected. */ - void disconnect(sys::DispatchHandle&); + // == Called in connection IO threads . + void checkUpdateIn(Lock&); - void handleMethod(Id from, ConnectionInterceptor* connection, framing::AMQMethodBody& method); + // == Called in UpdateClient thread. + void updateOutDone(); + void updateOutError(const std::exception&); + void updateOutDone(Lock&); - ConnectionInterceptor* getShadowConnection(const Cpg::Id&, void*); - - mutable sys::Monitor lock; // Protect access to members. - broker::Broker* broker; + // Immutable members set on construction, never changed. + const ClusterSettings settings; + broker::Broker& broker; + qmf::org::apache::qpid::cluster::Cluster* mgmtObject; // mgnt owns lifecycle boost::shared_ptr<sys::Poller> poller; Cpg cpg; - Cpg::Name name; - Url url; - MemberMap members; - Id self; - ShadowConnectionMap shadowConnectionMap; - LocalConnectionSet localConnectionSet; - ShadowConnectionOutputHandler shadowOut; - sys::DispatchHandle cpgDispatchHandle; - MessageQueue deliverQueue; - MessageQueue mcastQueue; - - friend std::ostream& operator <<(std::ostream&, const Cluster&); - friend std::ostream& operator <<(std::ostream&, const MemberMap::value_type&); - friend std::ostream& operator <<(std::ostream&, const MemberMap&); + const std::string name; + Url myUrl; + const MemberId self; + framing::Uuid clusterId; + NoOpConnectionOutputHandler shadowOut; + qpid::management::ManagementAgent* mAgent; + boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; + + // Thread safe members + Multicaster mcast; + PollerDispatch dispatcher; + PollableEventQueue deliverEventQueue; + PollableFrameQueue deliverFrameQueue; + boost::shared_ptr<FailoverExchange> failoverExchange; + Quorum quorum; + LockedConnectionMap localConnections; + + // Used only in deliverEventQueue thread or when stalled for update. + Decoder decoder; + bool discarding; + + + // Remaining members are protected by lock. + + // TODO aconway 2009-03-06: Most of these members are also only used in + // deliverFrameQueue thread or during stall. Review and separate members + // that require a lock, drop lock when not needed. + + mutable sys::Monitor lock; + + + // Local cluster state, cluster map + enum { + INIT, ///< Establishing inital cluster stattus. + JOINER, ///< Sent update request, waiting for update offer. + UPDATEE, ///< Stalled receive queue at update offer, waiting for update to complete. + CATCHUP, ///< Update complete, unstalled but has not yet seen own "ready" event. + READY, ///< Fully operational + OFFER, ///< Sent an offer, waiting for accept/reject. + UPDATER, ///< Offer accepted, sending a state update. + LEFT ///< Final state, left the cluster. + } state; + + ConnectionMap connections; + InitialStatusMap initMap; + StoreStatus store; + ClusterMap map; + MemberSet elders; + size_t lastSize; + bool lastBroker; + sys::Thread updateThread; + boost::optional<ClusterMap> updatedMap; + bool updateRetracted; + ErrorCheck error; + UpdateReceiver updateReceiver; + + friend std::ostream& operator<<(std::ostream&, const Cluster&); + friend class ClusterDispatcher; }; }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/ClusterMap.cpp b/cpp/src/qpid/cluster/ClusterMap.cpp new file mode 100644 index 0000000000..8cac470ef3 --- /dev/null +++ b/cpp/src/qpid/cluster/ClusterMap.cpp @@ -0,0 +1,175 @@ +/* + * + * 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/cluster/ClusterMap.h" +#include "qpid/Url.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> +#include <algorithm> +#include <functional> +#include <iterator> +#include <ostream> + +using namespace std; +using namespace boost; + +namespace qpid { +using namespace framing; + +namespace cluster { + +namespace { + +void addFieldTableValue(FieldTable::ValueMap::value_type vt, ClusterMap::Map& map, ClusterMap::Set& set) { + MemberId id(vt.first); + set.insert(id); + string url = vt.second->get<string>(); + if (!url.empty()) + map.insert(ClusterMap::Map::value_type(id, Url(url))); +} + +void insertFieldTableFromMapValue(FieldTable& ft, const ClusterMap::Map::value_type& vt) { + ft.setString(vt.first.str(), vt.second.str()); +} + +void assignFieldTable(FieldTable& ft, const ClusterMap::Map& map) { + ft.clear(); + for_each(map.begin(), map.end(), bind(&insertFieldTableFromMapValue, ref(ft), _1)); +} + +} + +ClusterMap::ClusterMap() : frameSeq(0) {} + +ClusterMap::ClusterMap(const Map& map) : frameSeq(0) { + transform(map.begin(), map.end(), inserter(alive, alive.begin()), bind(&Map::value_type::first, _1)); + members = map; +} + +ClusterMap::ClusterMap(const FieldTable& joinersFt, const FieldTable& membersFt, framing::SequenceNumber frameSeq_) + : frameSeq(frameSeq_) +{ + for_each(joinersFt.begin(), joinersFt.end(), bind(&addFieldTableValue, _1, ref(joiners), ref(alive))); + for_each(membersFt.begin(), membersFt.end(), bind(&addFieldTableValue, _1, ref(members), ref(alive))); +} + +void ClusterMap::toMethodBody(framing::ClusterConnectionMembershipBody& b) const { + b.getJoiners().clear(); + for_each(joiners.begin(), joiners.end(), bind(&insertFieldTableFromMapValue, ref(b.getJoiners()), _1)); + for(Set::const_iterator i = alive.begin(); i != alive.end(); ++i) { + if (!isMember(*i) && !isJoiner(*i)) + b.getJoiners().setString(i->str(), string()); + } + b.getMembers().clear(); + for_each(members.begin(), members.end(), bind(&insertFieldTableFromMapValue, ref(b.getMembers()), _1)); + b.setFrameSeq(frameSeq); +} + +Url ClusterMap::getUrl(const Map& map, const MemberId& id) { + Map::const_iterator i = map.find(id); + return i == map.end() ? Url() : i->second; +} + +MemberId ClusterMap::firstJoiner() const { + return joiners.empty() ? MemberId() : joiners.begin()->first; +} + +vector<string> ClusterMap::memberIds() const { + vector<string> ids; + for (Map::const_iterator iter = members.begin(); + iter != members.end(); iter++) { + stringstream stream; + stream << iter->first; + ids.push_back(stream.str()); + } + return ids; +} + +vector<Url> ClusterMap::memberUrls() const { + vector<Url> urls(members.size()); + transform(members.begin(), members.end(), urls.begin(), + bind(&Map::value_type::second, _1)); + return urls; +} + +ClusterMap::Set ClusterMap::getAlive() const { return alive; } + +ClusterMap::Set ClusterMap::getMembers() const { + Set s; + transform(members.begin(), members.end(), inserter(s, s.begin()), + bind(&Map::value_type::first, _1)); + return s; +} + +ostream& operator<<(ostream& o, const ClusterMap::Map& m) { + ostream_iterator<MemberId> oi(o); + transform(m.begin(), m.end(), oi, bind(&ClusterMap::Map::value_type::first, _1)); + return o; +} + +ostream& operator<<(ostream& o, const ClusterMap& m) { + for (ClusterMap::Set::const_iterator i = m.alive.begin(); i != m.alive.end(); ++i) { + o << *i; + if (m.isMember(*i)) o << "(member)"; + else if (m.isJoiner(*i)) o << "(joiner)"; + else o << "(unknown)"; + o << " "; + } + return o; +} + +bool ClusterMap::updateRequest(const MemberId& id, const string& url) { + if (isAlive(id)) { + joiners[id] = Url(url); + return true; + } + return false; +} + +bool ClusterMap::ready(const MemberId& id, const Url& url) { + return isAlive(id) && members.insert(Map::value_type(id,url)).second; +} + +bool ClusterMap::configChange(const Set& update) { + bool memberChange = false; + Set removed; + set_difference(alive.begin(), alive.end(), + update.begin(), update.end(), + inserter(removed, removed.begin())); + alive = update; + for (Set::const_iterator i = removed.begin(); i != removed.end(); ++i) { + memberChange = memberChange || members.erase(*i); + joiners.erase(*i); + } + return memberChange; +} + +optional<Url> ClusterMap::updateOffer(const MemberId& from, const MemberId& to) { + Map::iterator i = joiners.find(to); + if (isAlive(from) && i != joiners.end()) { + Url url= i->second; + joiners.erase(i); // No longer a potential updatee. + return url; + } + return optional<Url>(); +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/ClusterMap.h b/cpp/src/qpid/cluster/ClusterMap.h new file mode 100644 index 0000000000..98572813a8 --- /dev/null +++ b/cpp/src/qpid/cluster/ClusterMap.h @@ -0,0 +1,105 @@ +#ifndef QPID_CLUSTER_CLUSTERMAP_H +#define QPID_CLUSTER_CLUSTERMAP_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 "MemberSet.h" +#include "qpid/Url.h" +#include "qpid/framing/ClusterConnectionMembershipBody.h" +#include "qpid/framing/SequenceNumber.h" + +#include <boost/function.hpp> +#include <boost/optional.hpp> + +#include <vector> +#include <deque> +#include <map> +#include <iosfwd> + +namespace qpid { +namespace cluster { + +/** + * Map of established cluster members and joiners waiting for an update, + * along with other cluster state that must be updated. + */ +class ClusterMap { + public: + typedef std::map<MemberId, Url> Map; + typedef std::set<MemberId> Set; + + ClusterMap(); + ClusterMap(const Map& map); + ClusterMap(const framing::FieldTable& joiners, const framing::FieldTable& members, framing::SequenceNumber frameSeq); + + /** Update from config change. + *@return true if member set changed. + */ + bool configChange(const Set& members); + + bool isJoiner(const MemberId& id) const { return joiners.find(id) != joiners.end(); } + bool isMember(const MemberId& id) const { return members.find(id) != members.end(); } + bool isAlive(const MemberId& id) const { return alive.find(id) != alive.end(); } + + Url getJoinerUrl(const MemberId& id) { return getUrl(joiners, id); } + Url getMemberUrl(const MemberId& id) { return getUrl(members, id); } + + /** First joiner in the cluster in ID order, target for offers */ + MemberId firstJoiner() const; + + /** Convert map contents to a cluster control body. */ + void toMethodBody(framing::ClusterConnectionMembershipBody&) const; + + size_t aliveCount() const { return alive.size(); } + size_t memberCount() const { return members.size(); } + std::vector<std::string> memberIds() const; + std::vector<Url> memberUrls() const; + Set getAlive() const; + Set getMembers() const; + + bool updateRequest(const MemberId& id, const std::string& url); + /** Return non-empty Url if accepted */ + boost::optional<Url> updateOffer(const MemberId& from, const MemberId& to); + + /**@return true If this is a new member */ + bool ready(const MemberId& id, const Url&); + + framing::SequenceNumber getFrameSeq() { return frameSeq; } + framing::SequenceNumber incrementFrameSeq() { return ++frameSeq; } + + /** Clear out all knowledge of joiners & members, just keep alive set */ + void clearStatus() { joiners.clear(); members.clear(); } + + private: + Url getUrl(const Map& map, const MemberId& id); + + Map joiners, members; + Set alive; + framing::SequenceNumber frameSeq; + + friend std::ostream& operator<<(std::ostream&, const Map&); + friend std::ostream& operator<<(std::ostream&, const ClusterMap&); +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_CLUSTERMAP_H*/ diff --git a/cpp/src/qpid/cluster/ClusterPlugin.cpp b/cpp/src/qpid/cluster/ClusterPlugin.cpp index 1d07660455..e4aee6730b 100644 --- a/cpp/src/qpid/cluster/ClusterPlugin.cpp +++ b/cpp/src/qpid/cluster/ClusterPlugin.cpp @@ -16,89 +16,157 @@ * */ -#include "ConnectionInterceptor.h" +#include "config.h" +#include "qpid/cluster/Connection.h" +#include "qpid/cluster/ConnectionCodec.h" +#include "qpid/cluster/ClusterSettings.h" -#include "qpid/broker/Broker.h" #include "qpid/cluster/Cluster.h" +#include "qpid/cluster/ConnectionCodec.h" +#include "qpid/cluster/UpdateClient.h" + +#include "qpid/broker/Broker.h" #include "qpid/Plugin.h" #include "qpid/Options.h" -#include "qpid/shared_ptr.h" - +#include "qpid/sys/AtomicValue.h" +#include "qpid/log/Statement.h" + +#include "qpid/management/ManagementAgent.h" +#include "qpid/management/IdAllocator.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/SessionState.h" +#include "qpid/client/ConnectionSettings.h" + +#include <boost/shared_ptr.hpp> #include <boost/utility/in_place_factory.hpp> +#include <boost/scoped_ptr.hpp> namespace qpid { namespace cluster { using namespace std; +using broker::Broker; +using management::IdAllocator; +using management::ManagementAgent; -struct ClusterValues { - string name; - string url; - - Url getUrl(uint16_t port) const { - if (url.empty()) return Url::getIpAddressesUrl(port); - return Url(url); - } -}; -/** Note separating options from values to work around boost version differences. +/** Note separating options from settings to work around boost version differences. * Old boost takes a reference to options objects, but new boost makes a copy. * New boost allows a shared_ptr but that's not compatible with old boost. */ struct ClusterOptions : public Options { - ClusterValues& values; + ClusterSettings& settings; - ClusterOptions(ClusterValues& v) : Options("Cluster Options"), values(v) { + ClusterOptions(ClusterSettings& v) : Options("Cluster Options"), settings(v) { addOptions() - ("cluster-name", optValue(values.name, "NAME"), "Name of cluster to join") - ("cluster-url", optValue(values.url,"URL"), + ("cluster-name", optValue(settings.name, "NAME"), "Name of cluster to join") + ("cluster-url", optValue(settings.url,"URL"), "URL of this broker, advertized to the cluster.\n" "Defaults to a URL listing all the local IP addresses\n") + ("cluster-username", optValue(settings.username, "USER"), "Username for connections between brokers") + ("cluster-password", optValue(settings.password, "PASS"), "Password for connections between brokers") + ("cluster-mechanism", optValue(settings.mechanism, "MECH"), "Authentication mechanism for connections between brokers") +#if HAVE_LIBCMAN_H + ("cluster-cman", optValue(settings.quorum), "Integrate with Cluster Manager (CMAN) cluster.") +#endif + ("cluster-size", optValue(settings.size, "N"), "Wait for N cluster members before allowing clients to connect.") + ("cluster-read-max", optValue(settings.readMax,"N"), "Experimental: flow-control limit reads per connection. 0=no limit.") ; } }; +struct UpdateClientIdAllocator : management::IdAllocator +{ + qpid::sys::AtomicValue<uint64_t> sequence; + + UpdateClientIdAllocator() : sequence(0x4000000000000000LL) {} + + uint64_t getIdFor(management::Manageable* m) + { + if (isUpdateQueue(m) || isUpdateExchange(m) || isUpdateSession(m) || isUpdateBinding(m)) { + return ++sequence; + } else { + return 0; + } + } + + bool isUpdateQueue(management::Manageable* manageable) + { + qpid::broker::Queue* queue = dynamic_cast<qpid::broker::Queue*>(manageable); + return queue && queue->getName() == UpdateClient::UPDATE; + } + + bool isUpdateExchange(management::Manageable* manageable) + { + qpid::broker::Exchange* exchange = dynamic_cast<qpid::broker::Exchange*>(manageable); + return exchange && exchange->getName() == UpdateClient::UPDATE; + } + + bool isUpdateSession(management::Manageable* manageable) + { + broker::SessionState* session = dynamic_cast<broker::SessionState*>(manageable); + return session && session->getId().getName() == UpdateClient::UPDATE; + } + + bool isUpdateBinding(management::Manageable* manageable) + { + broker::Exchange::Binding* binding = dynamic_cast<broker::Exchange::Binding*>(manageable); + return binding && binding->queue->getName() == UpdateClient::UPDATE; + } +}; + struct ClusterPlugin : public Plugin { - ClusterValues values; + ClusterSettings settings; ClusterOptions options; - boost::intrusive_ptr<Cluster> cluster; + Cluster* cluster; + boost::scoped_ptr<ConnectionCodec::Factory> factory; - ClusterPlugin() : options(values) {} + ClusterPlugin() : options(settings), cluster(0) {} + // Cluster needs to be initialized after the store + int initOrder() const { return Plugin::DEFAULT_INIT_ORDER+500; } + Options* getOptions() { return &options; } - void init(broker::Broker& b) { - if (values.name.empty()) return; // Only if --cluster-name option was specified. - if (cluster) throw Exception("Cluster plugin cannot be initialized twice in one process."); - cluster = new Cluster(values.name, values.getUrl(b.getPort()), b); - b.addFinalizer(boost::bind(&ClusterPlugin::shutdown, this)); + void earlyInitialize(Plugin::Target& target) { + if (settings.name.empty()) return; // Only if --cluster-name option was specified. + Broker* broker = dynamic_cast<Broker*>(&target); + if (!broker) return; + cluster = new Cluster(settings, *broker); + broker->setConnectionFactory( + boost::shared_ptr<sys::ConnectionCodec::Factory>( + new ConnectionCodec::Factory(broker->getConnectionFactory(), *cluster))); + ManagementAgent* mgmt = broker->getManagementAgent(); + if (mgmt) { + std::auto_ptr<IdAllocator> allocator(new UpdateClientIdAllocator()); + mgmt->setAllocator(allocator); + } } - template <class T> void init(T& t) { - if (cluster) cluster->initialize(t); + void disallow(ManagementAgent* agent, const string& className, const string& methodName) { + string message = "Management method " + className + ":" + methodName + " is not allowed on a clustered broker."; + agent->disallow(className, methodName, message); } - - template <class T> bool init(Plugin::Target& target) { - T* t = dynamic_cast<T*>(&target); - if (t) init(*t); - return t; + void disallowManagementMethods(ManagementAgent* agent) { + if (!agent) return; + disallow(agent, "queue", "purge"); + disallow(agent, "session", "detach"); + disallow(agent, "session", "close"); + disallow(agent, "connection", "close"); } - void earlyInitialize(Plugin::Target&) {} - void initialize(Plugin::Target& target) { - if (init<broker::Broker>(target)) return; - if (!cluster) return; // Remaining plugins only valid if cluster initialized. - if (init<broker::Connection>(target)) return; + Broker* broker = dynamic_cast<Broker*>(&target); + if (broker && cluster) { + disallowManagementMethods(broker->getManagementAgent()); + cluster->initialize(); + } } - - void shutdown() { cluster = 0; } }; static ClusterPlugin instance; // Static initialization. -// For test purposes. -boost::intrusive_ptr<Cluster> getGlobalCluster() { return instance.cluster; } - }} // namespace qpid::cluster diff --git a/cpp/src/qpid/sys/posix/PrivatePosix.h b/cpp/src/qpid/cluster/ClusterSettings.h index 33c0cd81bc..d37c7792bf 100644 --- a/cpp/src/qpid/sys/posix/PrivatePosix.h +++ b/cpp/src/qpid/cluster/ClusterSettings.h @@ -1,5 +1,5 @@ -#ifndef _sys_posix_PrivatePosix_h -#define _sys_posix_PrivatePosix_h +#ifndef QPID_CLUSTER_CLUSTERSETTINGS_H +#define QPID_CLUSTER_CLUSTERSETTINGS_H /* * @@ -22,31 +22,29 @@ * */ -#include "qpid/sys/Time.h" - -struct timespec; -struct timeval; +#include <qpid/Url.h> +#include <string> namespace qpid { -namespace sys { +namespace cluster { -// Private Time related implementation details -struct timespec& toTimespec(struct timespec& ts, const Duration& t); -struct timeval& toTimeval(struct timeval& tv, const Duration& t); -Duration toTime(const struct timespec& ts); +struct ClusterSettings { + std::string name; + std::string url; + bool quorum; + size_t readMax; + std::string username, password, mechanism; + size_t size; -// Private fd related implementation details -class IOHandlePrivate { -public: - IOHandlePrivate(int f = -1) : - fd(f) + ClusterSettings() : quorum(false), readMax(10), mechanism("ANONYMOUS"), size(1) {} - - int fd; + + Url getUrl(uint16_t port) const { + if (url.empty()) return Url::getIpAddressesUrl(port); + return Url(url); + } }; -int toFd(const IOHandlePrivate* h); - -}} +}} // namespace qpid::cluster -#endif /*!_sys_posix_PrivatePosix_h*/ +#endif /*!QPID_CLUSTER_CLUSTERSETTINGS_H*/ diff --git a/cpp/src/qpid/cluster/Connection.cpp b/cpp/src/qpid/cluster/Connection.cpp new file mode 100644 index 0000000000..d223244f15 --- /dev/null +++ b/cpp/src/qpid/cluster/Connection.cpp @@ -0,0 +1,479 @@ +/* + * + * 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 "UpdateClient.h" +#include "Cluster.h" +#include "UpdateReceiver.h" + +#include "qpid/broker/SessionState.h" +#include "qpid/broker/SemanticState.h" +#include "qpid/broker/TxBuffer.h" +#include "qpid/broker/TxPublish.h" +#include "qpid/broker/TxAccept.h" +#include "qpid/broker/RecoveredEnqueue.h" +#include "qpid/broker/RecoveredDequeue.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/Queue.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AllInvoker.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/ClusterConnectionDeliverCloseBody.h" +#include "qpid/framing/ConnectionCloseBody.h" +#include "qpid/framing/ConnectionCloseOkBody.h" +#include "qpid/log/Statement.h" + +#include <boost/current_function.hpp> + +// TODO aconway 2008-11-03: +// +// Refactor code for receiving an update into a separate UpdateConnection +// class. +// + + +namespace qpid { +namespace cluster { + +using namespace framing; +using namespace framing::cluster; + +qpid::sys::AtomicValue<uint64_t> Connection::catchUpId(0x5000000000000000LL); + +Connection::NullFrameHandler Connection::nullFrameHandler; + +struct NullFrameHandler : public framing::FrameHandler { + void handle(framing::AMQFrame&) {} +}; + + +namespace { +sys::AtomicValue<uint64_t> idCounter; +const std::string shadowPrefix("[shadow]"); +} + + +// Shadow connection + Connection::Connection(Cluster& c, sys::ConnectionOutputHandler& out, const std::string& logId, + const ConnectionId& id, unsigned int ssf) + : cluster(c), self(id), catchUp(false), output(*this, out), + connection(&output, cluster.getBroker(), shadowPrefix+logId, ssf), expectProtocolHeader(false), + mcastFrameHandler(cluster.getMulticast(), self), + consumerNumbering(c.getUpdateReceiver().consumerNumbering) +{ init(); } + +// Local connection +Connection::Connection(Cluster& c, sys::ConnectionOutputHandler& out, + const std::string& logId, MemberId member, + bool isCatchUp, bool isLink, unsigned int ssf +) : cluster(c), self(member, ++idCounter), catchUp(isCatchUp), output(*this, out), + connection(&output, cluster.getBroker(), + isCatchUp ? shadowPrefix+logId : logId, + ssf, + isLink, + isCatchUp ? ++catchUpId : 0), + expectProtocolHeader(isLink), mcastFrameHandler(cluster.getMulticast(), self), + consumerNumbering(c.getUpdateReceiver().consumerNumbering) +{ init(); } + +void Connection::init() { + QPID_LOG(debug, cluster << " new connection: " << *this); + if (isLocalClient()) { + connection.setClusterOrderOutput(mcastFrameHandler); // Actively send cluster-order frames from local node + cluster.addLocalConnection(this); + giveReadCredit(cluster.getSettings().readMax); + } + else { // Shadow or catch-up connection + connection.setClusterOrderOutput(nullFrameHandler); // Passive, discard cluster-order frames + connection.setClientThrottling(false); // Disable client throttling, done by active node. + connection.setShadow(); // Mark the broker connection as a shadow. + } + if (!isCatchUp()) + connection.setErrorListener(this); +} + +void Connection::giveReadCredit(int credit) { + if (cluster.getSettings().readMax && credit) + output.giveReadCredit(credit); +} + +Connection::~Connection() { + connection.setErrorListener(0); + QPID_LOG(debug, cluster << " deleted connection: " << *this); +} + +bool Connection::doOutput() { + return output.doOutput(); +} + +// Received from a directly connected client. +void Connection::received(framing::AMQFrame& f) { + QPID_LOG(trace, cluster << " RECV " << *this << ": " << f); + if (isLocal()) { // Local catch-up connection. + currentChannel = f.getChannel(); + if (!framing::invoke(*this, *f.getBody()).wasHandled()) + connection.received(f); + } + else { // Shadow or updated catch-up connection. + if (f.getMethod() && f.getMethod()->isA<ConnectionCloseBody>()) { + if (isShadow()) + cluster.addShadowConnection(this); + AMQFrame ok((ConnectionCloseOkBody())); + connection.getOutput().send(ok); + output.closeOutput(); + catchUp = false; + } + else + QPID_LOG(warning, cluster << " ignoring unexpected frame " << *this << ": " << f); + } +} + +bool Connection::checkUnsupported(const AMQBody& body) { + std::string message; + if (body.getMethod()) { + switch (body.getMethod()->amqpClassId()) { + case DTX_CLASS_ID: message = "DTX transactions are not currently supported by cluster."; break; + } + } + if (!message.empty()) + connection.close(connection::CLOSE_CODE_FRAMING_ERROR, message); + return !message.empty(); +} + +struct GiveReadCreditOnExit { + Connection& connection; + int credit; + GiveReadCreditOnExit(Connection& connection_, int credit_) : + connection(connection_), credit(credit_) {} + ~GiveReadCreditOnExit() { connection.giveReadCredit(credit); } +}; + +// Called in delivery thread, in cluster order. +void Connection::deliveredFrame(const EventFrame& f) { + GiveReadCreditOnExit gc(*this, f.readCredit); + assert(!catchUp); + currentChannel = f.frame.getChannel(); + if (f.frame.getBody() // frame can be emtpy with just readCredit + && !framing::invoke(*this, *f.frame.getBody()).wasHandled() // Connection contol. + && !checkUnsupported(*f.frame.getBody())) // Unsupported operation. + { + if (f.type == DATA) // incoming data frames to broker::Connection + connection.received(const_cast<AMQFrame&>(f.frame)); + else { // frame control, send frame via SessionState + broker::SessionState* ss = connection.getChannel(currentChannel).getSession(); + if (ss) ss->out(const_cast<AMQFrame&>(f.frame)); + } + } +} + +// A local connection is closed by the network layer. +void Connection::closed() { + try { + if (catchUp) { + QPID_LOG(critical, cluster << " catch-up connection closed prematurely " << *this); + cluster.leave(); + } + else if (isUpdated()) { + QPID_LOG(debug, cluster << " closed update connection " << *this); + connection.closed(); + } + else if (isLocal()) { + QPID_LOG(debug, cluster << " local close of replicated connection " << *this); + // This was a local replicated connection. Multicast a deliver + // closed and process any outstanding frames from the cluster + // until self-delivery of deliver-close. + output.closeOutput(); + cluster.getMulticast().mcastControl(ClusterConnectionDeliverCloseBody(), self); + } + } + catch (const std::exception& e) { + QPID_LOG(error, cluster << " error closing connection " << *this << ": " << e.what()); + } +} + +// Self-delivery of close message, close the connection. +void Connection::deliverClose () { + assert(!catchUp); + connection.closed(); + cluster.erase(self); +} + +// The connection has been killed for misbehaving +void Connection::abort() { + connection.abort(); + cluster.erase(self); +} + +// Member of a shadow connection left the cluster. +void Connection::left() { + assert(isShadow()); + connection.closed(); +} + +// ConnectoinCodec::decode receives read buffers from directly-connected clients. +size_t Connection::decode(const char* buffer, size_t size) { + if (catchUp) { // Handle catch-up locally. + Buffer buf(const_cast<char*>(buffer), size); + while (localDecoder.decode(buf)) + received(localDecoder.getFrame()); + } + else { // Multicast local connections. + assert(isLocal()); + const char* remainingData = buffer; + size_t remainingSize = size; + if (expectProtocolHeader) { + //If this is an outgoing link, we will receive a protocol + //header which needs to be decoded first + framing::ProtocolInitiation pi; + Buffer buf(const_cast<char*>(buffer), size); + if (pi.decode(buf)) { + //TODO: check the version is correct + QPID_LOG(debug, "Outgoing clustered link connection received INIT(" << pi << ")"); + expectProtocolHeader = false; + remainingData = buffer + pi.encodedSize(); + remainingSize = size - pi.encodedSize(); + } else { + QPID_LOG(debug, "Not enough data for protocol header on outgoing clustered link"); + giveReadCredit(1); // We're not going to mcast so give read credit now. + return 0; + } + } + cluster.getMulticast().mcastBuffer(remainingData, remainingSize, self); + } + return size; +} + +broker::SessionState& Connection::sessionState() { + return *connection.getChannel(currentChannel).getSession(); +} + +broker::SemanticState& Connection::semanticState() { + return sessionState().getSemanticState(); +} + +void Connection::consumerState(const string& name, bool blocked, bool notifyEnabled, const SequenceNumber& position) +{ + broker::SemanticState::ConsumerImpl& c = semanticState().find(name); + c.position = position; + c.setBlocked(blocked); + if (notifyEnabled) c.enableNotify(); else c.disableNotify(); + consumerNumbering.add(c.shared_from_this()); +} + + +void Connection::sessionState( + const SequenceNumber& replayStart, + const SequenceNumber& sendCommandPoint, + const SequenceSet& sentIncomplete, + const SequenceNumber& expected, + const SequenceNumber& received, + const SequenceSet& unknownCompleted, + const SequenceSet& receivedIncomplete) +{ + + sessionState().setState( + replayStart, + sendCommandPoint, + sentIncomplete, + expected, + received, + unknownCompleted, + receivedIncomplete); + QPID_LOG(debug, cluster << " received session state update for " << sessionState().getId()); + // The output tasks will be added later in the update process. + connection.getOutputTasks().removeAll(); +} + +void Connection::outputTask(uint16_t channel, const std::string& name) { + broker::SessionState* session = connection.getChannel(channel).getSession(); + if (!session) + throw Exception(QPID_MSG(cluster << " channel not attached " << *this + << "[" << channel << "] ")); + OutputTask* task = &session->getSemanticState().find(name); + connection.getOutputTasks().addOutputTask(task); +} + +void Connection::shadowReady(uint64_t memberId, uint64_t connectionId, const string& username, const string& fragment, uint32_t sendMax) { + ConnectionId shadowId = ConnectionId(memberId, connectionId); + QPID_LOG(debug, cluster << " catch-up connection " << *this << " becomes shadow " << shadowId); + self = shadowId; + connection.setUserId(username); + // OK to use decoder here because cluster is stalled for update. + cluster.getDecoder().get(self).setFragment(fragment.data(), fragment.size()); + connection.setErrorListener(this); + output.setSendMax(sendMax); +} + +void Connection::membership(const FieldTable& joiners, const FieldTable& members, const framing::SequenceNumber& frameSeq) { + QPID_LOG(debug, cluster << " incoming update complete on connection " << *this); + cluster.updateInDone(ClusterMap(joiners, members, frameSeq)); + consumerNumbering.clear(); + self.second = 0; // Mark this as completed update connection. +} + +void Connection::retractOffer() { + QPID_LOG(debug, cluster << " incoming update retracted on connection " << *this); + cluster.updateInRetracted(); + self.second = 0; // Mark this as completed update connection. +} + +bool Connection::isLocal() const { + return self.first == cluster.getId() && self.second; +} + +bool Connection::isShadow() const { + return self.first != cluster.getId(); +} + +bool Connection::isUpdated() const { + return self.first == cluster.getId() && self.second == 0; +} + + +boost::shared_ptr<broker::Queue> Connection::findQueue(const std::string& qname) { + boost::shared_ptr<broker::Queue> queue = cluster.getBroker().getQueues().find(qname); + if (!queue) throw Exception(QPID_MSG(cluster << " can't find queue " << qname)); + return queue; +} + +broker::QueuedMessage Connection::getUpdateMessage() { + boost::shared_ptr<broker::Queue> updateq = findQueue(UpdateClient::UPDATE); + assert(!updateq->isDurable()); + broker::QueuedMessage m = updateq->get(); + if (!m.payload) throw Exception(QPID_MSG(cluster << " empty update queue")); + return m; +} + +void Connection::deliveryRecord(const string& qname, + const SequenceNumber& position, + const string& tag, + const SequenceNumber& id, + bool acquired, + bool accepted, + bool cancelled, + bool completed, + bool ended, + bool windowing, + bool enqueued, + uint32_t credit) +{ + broker::QueuedMessage m; + broker::Queue::shared_ptr queue = findQueue(qname); + if (!ended) { // Has a message + if (acquired) { // Message is on the update queue + m = getUpdateMessage(); + m.queue = queue.get(); + m.position = position; + if (enqueued) queue->enqueued(m); //inform queue of the message + } else { // Message at original position in original queue + m = queue->find(position); + } + if (!m.payload) + throw Exception(QPID_MSG("deliveryRecord no update message")); + } + + broker::DeliveryRecord dr(m, queue, tag, acquired, accepted, windowing, credit); + dr.setId(id); + if (cancelled) dr.cancel(dr.getTag()); + if (completed) dr.complete(); + if (ended) dr.setEnded(); // Exsitance of message + semanticState().record(dr); // Part of the session's unacked list. +} + +void Connection::queuePosition(const string& qname, const SequenceNumber& position) { + findQueue(qname)->setPosition(position); +} + +void Connection::expiryId(uint64_t id) { + cluster.getExpiryPolicy().setId(id); +} + +std::ostream& operator<<(std::ostream& o, const Connection& c) { + const char* type="unknown"; + if (c.isLocal()) type = "local"; + else if (c.isShadow()) type = "shadow"; + else if (c.isUpdated()) type = "updated"; + return o << c.getId() << "(" << type << (c.isCatchUp() ? ",catchup" : "") << ")"; +} + +void Connection::txStart() { + txBuffer.reset(new broker::TxBuffer()); +} +void Connection::txAccept(const framing::SequenceSet& acked) { + txBuffer->enlist(boost::shared_ptr<broker::TxAccept>( + new broker::TxAccept(acked, semanticState().getUnacked()))); +} + +void Connection::txDequeue(const std::string& queue) { + txBuffer->enlist(boost::shared_ptr<broker::RecoveredDequeue>( + new broker::RecoveredDequeue(findQueue(queue), getUpdateMessage().payload))); +} + +void Connection::txEnqueue(const std::string& queue) { + txBuffer->enlist(boost::shared_ptr<broker::RecoveredEnqueue>( + new broker::RecoveredEnqueue(findQueue(queue), getUpdateMessage().payload))); +} + +void Connection::txPublish(const framing::Array& queues, bool delivered) { + boost::shared_ptr<broker::TxPublish> txPub(new broker::TxPublish(getUpdateMessage().payload)); + for (framing::Array::const_iterator i = queues.begin(); i != queues.end(); ++i) + txPub->deliverTo(findQueue((*i)->get<std::string>())); + txPub->delivered = delivered; + txBuffer->enlist(txPub); +} + +void Connection::txEnd() { + semanticState().setTxBuffer(txBuffer); +} + +void Connection::accumulatedAck(const qpid::framing::SequenceSet& s) { + semanticState().setAccumulatedAck(s); +} + +void Connection::exchange(const std::string& encoded) { + Buffer buf(const_cast<char*>(encoded.data()), encoded.size()); + broker::Exchange::shared_ptr ex = broker::Exchange::decode(cluster.getBroker().getExchanges(), buf); + QPID_LOG(debug, cluster << " updated exchange " << ex->getName()); +} + +void Connection::queue(const std::string& encoded) { + Buffer buf(const_cast<char*>(encoded.data()), encoded.size()); + broker::Queue::shared_ptr q = broker::Queue::decode(cluster.getBroker().getQueues(), buf); + QPID_LOG(debug, cluster << " updated queue " << q->getName()); +} + +void Connection::sessionError(uint16_t , const std::string& msg) { + cluster.flagError(*this, ERROR_TYPE_SESSION, msg); + +} + +void Connection::connectionError(const std::string& msg) { + cluster.flagError(*this, ERROR_TYPE_CONNECTION, msg); +} + +void Connection::addQueueListener(const std::string& q, uint32_t listener) { + if (listener >= consumerNumbering.size()) + throw Exception(QPID_MSG("Invalid listener ID: " << listener)); + findQueue(q)->getListeners().addListener(consumerNumbering[listener]); +} + +}} // Namespace qpid::cluster + diff --git a/cpp/src/qpid/cluster/Connection.h b/cpp/src/qpid/cluster/Connection.h new file mode 100644 index 0000000000..7f94338348 --- /dev/null +++ b/cpp/src/qpid/cluster/Connection.h @@ -0,0 +1,210 @@ +#ifndef QPID_CLUSTER_CONNECTION_H +#define QPID_CLUSTER_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 "types.h" +#include "OutputInterceptor.h" +#include "EventFrame.h" +#include "McastFrameHandler.h" +#include "UpdateReceiver.h" + +#include "qpid/broker/Connection.h" +#include "qpid/broker/SemanticState.h" +#include "qpid/amqp_0_10/Connection.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/ConnectionInputHandler.h" +#include "qpid/sys/ConnectionOutputHandler.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/FrameDecoder.h" + +#include <iosfwd> + +namespace qpid { + +namespace framing { class AMQFrame; } + +namespace broker { +class SemanticState; +class QueuedMessage; +class TxBuffer; +class TxAccept; +} + +namespace cluster { +class Cluster; +class Event; + +/** Intercept broker::Connection calls for shadow and local cluster connections. */ +class Connection : + public RefCounted, + public sys::ConnectionInputHandler, + public framing::AMQP_AllOperations::ClusterConnectionHandler, + private broker::Connection::ErrorListener + +{ + public: + + /** Local connection. */ + Connection(Cluster&, sys::ConnectionOutputHandler& out, const std::string& logId, MemberId, bool catchUp, bool isLink, + unsigned int ssf); + /** Shadow connection. */ + Connection(Cluster&, sys::ConnectionOutputHandler& out, const std::string& logId, const ConnectionId& id, + unsigned int ssf); + ~Connection(); + + ConnectionId getId() const { return self; } + broker::Connection& getBrokerConnection() { return connection; } + + /** Local connections may be clients or catch-up connections */ + bool isLocal() const; + + bool isLocalClient() const { return isLocal() && !isCatchUp(); } + + /** True for connections that are shadowing remote broker connections */ + bool isShadow() const; + + /** True if the connection is in "catch-up" mode: building initial broker state. */ + bool isCatchUp() const { return catchUp; } + + /** True if the connection is a completed shared update connection */ + bool isUpdated() const; + + Cluster& getCluster() { return cluster; } + + // ConnectionInputHandler methods + void received(framing::AMQFrame&); + void closed(); + bool doOutput(); + bool hasOutput() { return connection.hasOutput(); } + void idleOut() { connection.idleOut(); } + void idleIn() { connection.idleIn(); } + + /** Called if the connectors member has left the cluster */ + void left(); + + // ConnectionCodec methods - called by IO layer with a read buffer. + size_t decode(const char* buffer, size_t size); + + // Called for data delivered from the cluster. + void deliveredFrame(const EventFrame&); + + void consumerState(const std::string& name, bool blocked, bool notifyEnabled, const qpid::framing::SequenceNumber& position); + + // ==== Used in catch-up mode to build initial state. + // + // State update methods. + void sessionState(const framing::SequenceNumber& replayStart, + const framing::SequenceNumber& sendCommandPoint, + const framing::SequenceSet& sentIncomplete, + const framing::SequenceNumber& expected, + const framing::SequenceNumber& received, + const framing::SequenceSet& unknownCompleted, + const SequenceSet& receivedIncomplete); + + void outputTask(uint16_t channel, const std::string& name); + + void shadowReady(uint64_t memberId, uint64_t connectionId, const std::string& username, const std::string& fragment, uint32_t sendMax); + + void membership(const framing::FieldTable&, const framing::FieldTable&, const framing::SequenceNumber& frameSeq); + + void retractOffer(); + + void deliveryRecord(const std::string& queue, + const framing::SequenceNumber& position, + const std::string& tag, + const framing::SequenceNumber& id, + bool acquired, + bool accepted, + bool cancelled, + bool completed, + bool ended, + bool windowing, + bool enqueued, + uint32_t credit); + + void queuePosition(const std::string&, const framing::SequenceNumber&); + void expiryId(uint64_t); + + void txStart(); + void txAccept(const framing::SequenceSet&); + void txDequeue(const std::string&); + void txEnqueue(const std::string&); + void txPublish(const framing::Array&, bool); + void txEnd(); + void accumulatedAck(const framing::SequenceSet&); + + // Encoded queue/exchange replication. + void queue(const std::string& encoded); + void exchange(const std::string& encoded); + + void giveReadCredit(int credit); + void announce(uint32_t) {} // handled by Cluster. + void abort(); + void deliverClose(); + + OutputInterceptor& getOutput() { return output; } + + void addQueueListener(const std::string& queue, uint32_t listener); + + private: + struct NullFrameHandler : public framing::FrameHandler { + void handle(framing::AMQFrame&) {} + }; + + + static NullFrameHandler nullFrameHandler; + + // Error listener functions + void connectionError(const std::string&); + void sessionError(uint16_t channel, const std::string&); + + void init(); + bool checkUnsupported(const framing::AMQBody& body); + void deliverDoOutput(uint32_t limit) { output.deliverDoOutput(limit); } + + boost::shared_ptr<broker::Queue> findQueue(const std::string& qname); + broker::SessionState& sessionState(); + broker::SemanticState& semanticState(); + broker::QueuedMessage getUpdateMessage(); + + Cluster& cluster; + ConnectionId self; + bool catchUp; + OutputInterceptor output; + framing::FrameDecoder localDecoder; + broker::Connection connection; + framing::SequenceNumber deliverSeq; + framing::ChannelId currentChannel; + boost::shared_ptr<broker::TxBuffer> txBuffer; + bool expectProtocolHeader; + McastFrameHandler mcastFrameHandler; + UpdateReceiver::ConsumerNumbering& consumerNumbering; + + static qpid::sys::AtomicValue<uint64_t> catchUpId; + + friend std::ostream& operator<<(std::ostream&, const Connection&); +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_CONNECTION_H*/ diff --git a/cpp/src/qpid/cluster/ConnectionCodec.cpp b/cpp/src/qpid/cluster/ConnectionCodec.cpp new file mode 100644 index 0000000000..8f6f1d9ad5 --- /dev/null +++ b/cpp/src/qpid/cluster/ConnectionCodec.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. + * + */ +#include "qpid/cluster/ConnectionCodec.h" +#include "qpid/cluster/Connection.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/ProxyInputHandler.h" +#include "qpid/broker/Connection.h" +#include "qpid/framing/ConnectionCloseBody.h" +#include "qpid/framing/ConnectionCloseOkBody.h" +#include "qpid/log/Statement.h" +#include "qpid/memory.h" +#include <stdexcept> +#include <boost/utility/in_place_factory.hpp> + +namespace qpid { +namespace cluster { + +using namespace framing; + +sys::ConnectionCodec* +ConnectionCodec::Factory::create(ProtocolVersion v, sys::OutputControl& out, const std::string& id, + unsigned int ssf) { + if (v == ProtocolVersion(0, 10)) + return new ConnectionCodec(v, out, id, cluster, false, false, ssf); + else if (v == ProtocolVersion(0x80 + 0, 0x80 + 10)) // Catch-up connection + return new ConnectionCodec(v, out, id, cluster, true, false, ssf); + return 0; +} + +// Used for outgoing Link connections +sys::ConnectionCodec* +ConnectionCodec::Factory::create(sys::OutputControl& out, const std::string& logId, + unsigned int ssf) { + return new ConnectionCodec(ProtocolVersion(0,10), out, logId, cluster, false, true, ssf); +} + +ConnectionCodec::ConnectionCodec( + const ProtocolVersion& v, sys::OutputControl& out, + const std::string& logId, Cluster& cluster, bool catchUp, bool isLink, unsigned int ssf +) : codec(out, logId, isLink), + interceptor(new Connection(cluster, codec, logId, cluster.getId(), catchUp, isLink, ssf)) +{ + std::auto_ptr<sys::ConnectionInputHandler> ih(new ProxyInputHandler(interceptor)); + codec.setInputHandler(ih); + codec.setVersion(v); +} + +ConnectionCodec::~ConnectionCodec() {} + +size_t ConnectionCodec::decode(const char* buffer, size_t size) { + return interceptor->decode(buffer, size); +} + +bool ConnectionCodec::isClosed() const { return codec.isClosed(); } + +size_t ConnectionCodec::encode(const char* buffer, size_t size) { return codec.encode(buffer, size); } + +bool ConnectionCodec::canEncode() { return codec.canEncode(); } + +void ConnectionCodec::closed() { codec.closed(); } + +ProtocolVersion ConnectionCodec::getVersion() const { return codec.getVersion(); } + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/ConnectionCodec.h b/cpp/src/qpid/cluster/ConnectionCodec.h new file mode 100644 index 0000000000..74cb3c507d --- /dev/null +++ b/cpp/src/qpid/cluster/ConnectionCodec.h @@ -0,0 +1,82 @@ +#ifndef QPID_CLUSTER_CONNCTIONCODEC_H +#define QPID_CLUSTER_CONNCTIONCODEC_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_0_10/Connection.h" +#include "qpid/cluster/Connection.h" +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> + +namespace qpid { + +namespace broker { +class Connection; +} + +namespace cluster { +class Cluster; + +/** + * Encapsulates the standard amqp_0_10::ConnectionCodec and sets up + * a cluster::Connection for the connection. + * + * The ConnectionCodec is deleted by the network layer when the + * connection closes. The cluster::Connection needs to be kept + * around until all cluster business on the connection is complete. + * + */ +class ConnectionCodec : public sys::ConnectionCodec { + public: + struct Factory : public sys::ConnectionCodec::Factory { + boost::shared_ptr<sys::ConnectionCodec::Factory> next; + Cluster& cluster; + Factory(boost::shared_ptr<sys::ConnectionCodec::Factory> f, Cluster& c) + : next(f), cluster(c) {} + sys::ConnectionCodec* create(framing::ProtocolVersion, sys::OutputControl&, const std::string& id, + unsigned int conn_ssf); + sys::ConnectionCodec* create(sys::OutputControl&, const std::string& id, + unsigned int conn_ssf); + }; + + ConnectionCodec(const framing::ProtocolVersion&, sys::OutputControl& out, + const std::string& logId, Cluster& c, bool catchUp, bool isLink, + unsigned int ssf); + ~ConnectionCodec(); + + // ConnectionCodec functions. + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + void closed(); + bool isClosed() const; + framing::ProtocolVersion getVersion() const; + + + private: + amqp_0_10::Connection codec; + boost::intrusive_ptr<cluster::Connection> interceptor; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_CONNCTIONCODEC_H*/ diff --git a/cpp/src/qpid/cluster/ConnectionInterceptor.cpp b/cpp/src/qpid/cluster/ConnectionInterceptor.cpp deleted file mode 100644 index c13651eccb..0000000000 --- a/cpp/src/qpid/cluster/ConnectionInterceptor.cpp +++ /dev/null @@ -1,108 +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 "ConnectionInterceptor.h" -#include "qpid/framing/ClusterConnectionCloseBody.h" -#include "qpid/framing/ClusterConnectionDoOutputBody.h" -#include "qpid/framing/AMQFrame.h" - -namespace qpid { -namespace cluster { - -using namespace framing; - -template <class T, class U, class V> void shift(T& a, U& b, const V& c) { a = b; b = c; } - -ConnectionInterceptor::ConnectionInterceptor( - broker::Connection& conn, Cluster& clust, Cluster::ShadowConnectionId shadowId_) - : connection(&conn), cluster(clust), isClosed(false), shadowId(shadowId_) -{ - connection->addFinalizer(boost::bind(operator delete, this)); - // Attach my functions to Connection extension points. - shift(receivedNext, connection->receivedFn, boost::bind(&ConnectionInterceptor::received, this, _1)); - shift(closedNext, connection->closedFn, boost::bind(&ConnectionInterceptor::closed, this)); - shift(doOutputNext, connection->doOutputFn, boost::bind(&ConnectionInterceptor::doOutput, this)); -} - -ConnectionInterceptor::~ConnectionInterceptor() { - assert(connection == 0); -} - -void ConnectionInterceptor::received(framing::AMQFrame& f) { - if (isClosed) return; - cluster.send(f, this); -} - -void ConnectionInterceptor::deliver(framing::AMQFrame& f) { - receivedNext(f); -} - -void ConnectionInterceptor::closed() { - if (isClosed) return; - try { - // Called when the local network connection is closed. We still - // need to process any outstanding cluster frames for this - // connection to ensure our sessions are up-to-date. We defer - // closing the Connection object till deliverClosed(), but replace - // its output handler with a null handler since the network output - // handler will be deleted. - // - connection->setOutputHandler(&discardHandler); - cluster.send(AMQFrame(in_place<ClusterConnectionCloseBody>()), this); - isClosed = true; - } - catch (const std::exception& e) { - QPID_LOG(error, QPID_MSG("While closing connection: " << e.what())); - } -} - -void ConnectionInterceptor::deliverClosed() { - closedNext(); - // Drop reference so connection will be deleted, which in turn - // will delete this via finalizer added in ctor. - connection = 0; -} - -void ConnectionInterceptor::dirtyClose() { - // Not closed via cluster self-delivery but closed locally. - // Used for dirty cluster shutdown where active connections - // must be cleaned up. - connection = 0; -} - -bool ConnectionInterceptor::doOutput() { - // FIXME aconway 2008-08-15: this is not correct. - // Run in write threads so order of execution of doOutput is not determinate. - // Will only work reliably for in single-consumer tests. - - if (connection->hasOutput()) { - cluster.send(AMQFrame(in_place<ClusterConnectionDoOutputBody>()), this); - return doOutputNext(); - } - return false; -} - -void ConnectionInterceptor::deliverDoOutput() { - // FIXME aconway 2008-08-15: see comment in doOutput. - if (isShadow()) - doOutputNext(); -} - -}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/ConnectionInterceptor.h b/cpp/src/qpid/cluster/ConnectionInterceptor.h deleted file mode 100644 index 370572bd9d..0000000000 --- a/cpp/src/qpid/cluster/ConnectionInterceptor.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef QPID_CLUSTER_CONNECTIONPLUGIN_H -#define QPID_CLUSTER_CONNECTIONPLUGIN_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 "Cluster.h" -#include "qpid/broker/Connection.h" -#include "qpid/sys/ConnectionOutputHandler.h" - -namespace qpid { -namespace framing { class AMQFrame; } -namespace cluster { - -/** - * Plug-in associated with broker::Connections, both local and shadow. - */ -class ConnectionInterceptor { - public: - ConnectionInterceptor(broker::Connection&, Cluster&, - Cluster::ShadowConnectionId shadowId=Cluster::ShadowConnectionId(0,0)); - ~ConnectionInterceptor(); - - Cluster::ShadowConnectionId getShadowId() const { return shadowId; } - - bool isLocal() const { return shadowId == Cluster::ShadowConnectionId(0,0); } - - // self-delivery of intercepted extension points. - void deliver(framing::AMQFrame& f); - void deliverClosed(); - void deliverDoOutput(); - - void dirtyClose(); - - private: - struct NullConnectionHandler : public qpid::sys::ConnectionOutputHandler { - void close() {} - void send(framing::AMQFrame&) {} - void doOutput() {} - void activateOutput() {} - }; - - bool isShadow() { return shadowId != Cluster::ShadowConnectionId(0,0); } - - // Functions to intercept to Connection extension points. - void received(framing::AMQFrame&); - void closed(); - bool doOutput(); - - boost::function<void (framing::AMQFrame&)> receivedNext; - boost::function<void ()> closedNext; - boost::function<bool ()> doOutputNext; - - boost::intrusive_ptr<broker::Connection> connection; - Cluster& cluster; - NullConnectionHandler discardHandler; - bool isClosed; - Cluster::ShadowConnectionId shadowId; -}; - -}} // namespace qpid::cluster - -#endif /*!QPID_CLUSTER_CONNECTIONPLUGIN_H*/ - diff --git a/cpp/src/qpid/cluster/Cpg.cpp b/cpp/src/qpid/cluster/Cpg.cpp index 2ffd3509bf..3ae0c970c7 100644 --- a/cpp/src/qpid/cluster/Cpg.cpp +++ b/cpp/src/qpid/cluster/Cpg.cpp @@ -16,33 +16,85 @@ * */ -#include "Cpg.h" +#include "qpid/cluster/Cpg.h" #include "qpid/sys/Mutex.h" -// Note cpg is currently unix-specific. Refactor if availble on other platforms. +#include "qpid/sys/Time.h" #include "qpid/sys/posix/PrivatePosix.h" #include "qpid/log/Statement.h" #include <vector> #include <limits> #include <iterator> +#include <sstream> #include <unistd.h> +// This is a macro instead of a function because we don't want to +// evaluate the MSG argument unless there is an error. +#define CPG_CHECK(RESULT, MSG) \ + if ((RESULT) != CPG_OK) throw Exception(errorStr((RESULT), (MSG))) + namespace qpid { namespace cluster { using namespace std; + + Cpg* Cpg::cpgFromHandle(cpg_handle_t handle) { void* cpg=0; - check(cpg_context_get(handle, &cpg), "Cannot get CPG instance."); + CPG_CHECK(cpg_context_get(handle, &cpg), "Cannot get CPG instance."); if (!cpg) throw Exception("Cannot get CPG instance."); return reinterpret_cast<Cpg*>(cpg); } +// Applies the same retry-logic to all cpg calls that need it. +void Cpg::callCpg ( CpgOp & c ) { + cpg_error_t result; + unsigned int snooze = 10; + for ( unsigned int nth_try = 0; nth_try < cpgRetries; ++ nth_try ) { + if ( CPG_OK == (result = c.op(handle, & group))) { + QPID_LOG(info, c.opName << " successful."); + break; + } + else if ( result == CPG_ERR_TRY_AGAIN ) { + QPID_LOG(info, "Retrying " << c.opName ); + sys::usleep ( snooze ); + snooze *= 10; + snooze = (snooze <= maxCpgRetrySleep) ? snooze : maxCpgRetrySleep; + } + else break; // Don't retry unless CPG tells us to. + } + + if ( result != CPG_OK ) + CPG_CHECK(result, c.msg(group)); +} + // Global callback functions. void Cpg::globalDeliver ( cpg_handle_t handle, + const struct cpg_name *group, + uint32_t nodeid, + uint32_t pid, + void* msg, + size_t msg_len) +{ + cpgFromHandle(handle)->handler.deliver(handle, group, nodeid, pid, msg, msg_len); +} + +void Cpg::globalConfigChange( + cpg_handle_t handle, + const struct cpg_name *group, + const struct cpg_address *members, size_t nMembers, + const struct cpg_address *left, size_t nLeft, + const struct cpg_address *joined, size_t nJoined +) +{ + cpgFromHandle(handle)->handler.configChange(handle, group, members, nMembers, left, nLeft, joined, nJoined); +} + +void Cpg::globalDeliver ( + cpg_handle_t handle, struct cpg_name *group, uint32_t nodeid, uint32_t pid, @@ -65,103 +117,119 @@ void Cpg::globalConfigChange( int Cpg::getFd() { int fd; - check(cpg_fd_get(handle, &fd), "Cannot get CPG file descriptor"); + CPG_CHECK(cpg_fd_get(handle, &fd), "Cannot get CPG file descriptor"); return fd; } Cpg::Cpg(Handler& h) : IOHandle(new sys::IOHandlePrivate), handler(h), isShutdown(false) { - cpg_callbacks_t callbacks = { &globalDeliver, &globalConfigChange }; - check(cpg_initialize(&handle, &callbacks), "Cannot initialize CPG"); - check(cpg_context_set(handle, this), "Cannot set CPG context"); + cpg_callbacks_t callbacks; + ::memset(&callbacks, 0, sizeof(callbacks)); + callbacks.cpg_deliver_fn = &globalDeliver; + callbacks.cpg_confchg_fn = &globalConfigChange; + + QPID_LOG(notice, "Initializing CPG"); + cpg_error_t err = cpg_initialize(&handle, &callbacks); + int retries = 6; // FIXME aconway 2009-08-06: make this configurable. + while (err == CPG_ERR_TRY_AGAIN && --retries) { + QPID_LOG(notice, "Re-trying CPG initialization."); + sys::sleep(5); + err = cpg_initialize(&handle, &callbacks); + } + CPG_CHECK(err, "Failed to initialize CPG."); + CPG_CHECK(cpg_context_set(handle, this), "Cannot set CPG context"); // Note: CPG is currently unix-specific. If CPG is ported to // windows then this needs to be refactored into // qpid::sys::<platform> IOHandle::impl->fd = getFd(); - QPID_LOG(debug, "Initialized CPG handle 0x" << std::hex << handle); } Cpg::~Cpg() { try { shutdown(); } catch (const std::exception& e) { - QPID_LOG(error, "Exception in Cpg destructor: " << e.what()); + QPID_LOG(error, "Error during CPG shutdown: " << e.what()); } } -void Cpg::join(const Name& group) { - check(cpg_join(handle, const_cast<Name*>(&group)),cantJoinMsg(group)); +void Cpg::join(const std::string& name) { + group = name; + callCpg ( cpgJoinOp ); } -void Cpg::leave(const Name& group) { - check(cpg_leave(handle,const_cast<Name*>(&group)),cantLeaveMsg(group)); +void Cpg::leave() { + callCpg ( cpgLeaveOp ); } -bool Cpg::isFlowControlEnabled() { + + + +bool Cpg::mcast(const iovec* iov, int iovLen) { + // Check for flow control cpg_flow_control_state_t flowState; - check(cpg_flow_control_state_get(handle, &flowState), "Cannot get CPG flow control status."); - return flowState == CPG_FLOW_CONTROL_ENABLED; -} - -// TODO aconway 2008-08-07: better handling of flow control. -// Wait for flow control to be disabled. -// FIXME aconway 2008-08-08: does flow control check involve a round-trip? If so maybe remove... -void Cpg::waitForFlowControl() { - int delayNs=1000; // one millisecond - int tries=8; // double the delay on each try. - while (isFlowControlEnabled() && tries > 0) { - QPID_LOG(warning, "CPG flow control enabled, retry in " << delayNs << "ns"); - ::usleep(delayNs); - --tries; - delayNs *= 2; - }; - if (tries == 0) { - // FIXME aconway 2008-08-07: this is a fatal leave-the-cluster condition. - throw Cpg::Exception("CPG flow control enabled, failed to send."); - } -} + CPG_CHECK(cpg_flow_control_state_get(handle, &flowState), "Cannot get CPG flow control status."); + if (flowState == CPG_FLOW_CONTROL_ENABLED) + return false; -void Cpg::mcast(const Name& group, const iovec* iov, int iovLen) { - waitForFlowControl(); cpg_error_t result; do { result = cpg_mcast_joined(handle, CPG_TYPE_AGREED, const_cast<iovec*>(iov), iovLen); - if (result != CPG_ERR_TRY_AGAIN) check(result, cantMcastMsg(group)); + if (result != CPG_ERR_TRY_AGAIN) CPG_CHECK(result, cantMcastMsg(group)); } while(result == CPG_ERR_TRY_AGAIN); + return true; } void Cpg::shutdown() { if (!isShutdown) { QPID_LOG(debug,"Shutting down CPG"); isShutdown=true; - check(cpg_finalize(handle), "Error in shutdown of CPG"); + + callCpg ( cpgFinalizeOp ); } } +void Cpg::dispatchOne() { + CPG_CHECK(cpg_dispatch(handle,CPG_DISPATCH_ONE), "Error in CPG dispatch"); +} + +void Cpg::dispatchAll() { + CPG_CHECK(cpg_dispatch(handle,CPG_DISPATCH_ALL), "Error in CPG dispatch"); +} + +void Cpg::dispatchBlocking() { + CPG_CHECK(cpg_dispatch(handle,CPG_DISPATCH_BLOCKING), "Error in CPG dispatch"); +} + string Cpg::errorStr(cpg_error_t err, const std::string& msg) { + std::ostringstream os; + os << msg << ": "; switch (err) { - case CPG_OK: return msg+": ok"; - case CPG_ERR_LIBRARY: return msg+": library"; - case CPG_ERR_TIMEOUT: return msg+": timeout"; - case CPG_ERR_TRY_AGAIN: return msg+": timeout. The aisexec daemon may not be running"; - case CPG_ERR_INVALID_PARAM: return msg+": invalid param"; - case CPG_ERR_NO_MEMORY: return msg+": no memory"; - case CPG_ERR_BAD_HANDLE: return msg+": bad handle"; - case CPG_ERR_ACCESS: return msg+": access denied. You may need to set your group ID to 'ais'"; - case CPG_ERR_NOT_EXIST: return msg+": not exist"; - case CPG_ERR_EXIST: return msg+": exist"; - case CPG_ERR_NOT_SUPPORTED: return msg+": not supported"; - case CPG_ERR_SECURITY: return msg+": security"; - case CPG_ERR_TOO_MANY_GROUPS: return msg+": too many groups"; - default: - assert(0); - return ": unknown"; + case CPG_OK: os << "ok"; break; + case CPG_ERR_LIBRARY: os << "library"; break; + case CPG_ERR_TIMEOUT: os << "timeout"; break; + case CPG_ERR_TRY_AGAIN: os << "try again"; break; + case CPG_ERR_INVALID_PARAM: os << "invalid param"; break; + case CPG_ERR_NO_MEMORY: os << "no memory"; break; + case CPG_ERR_BAD_HANDLE: os << "bad handle"; break; + case CPG_ERR_ACCESS: os << "access denied. You may need to set your group ID to 'ais'"; break; + case CPG_ERR_NOT_EXIST: os << "not exist"; break; + case CPG_ERR_EXIST: os << "exist"; break; + case CPG_ERR_NOT_SUPPORTED: os << "not supported"; break; + case CPG_ERR_SECURITY: os << "security"; break; + case CPG_ERR_TOO_MANY_GROUPS: os << "too many groups"; break; + default: os << ": unknown cpg error " << err; }; + os << " (" << err << ")"; + return os.str(); } std::string Cpg::cantJoinMsg(const Name& group) { return "Cannot join CPG group "+group.str(); } +std::string Cpg::cantFinalizeMsg(const Name& group) { + return "Cannot finalize CPG group "+group.str(); +} + std::string Cpg::cantLeaveMsg(const Name& group) { return "Cannot leave CPG group "+group.str(); } @@ -170,27 +238,44 @@ std::string Cpg::cantMcastMsg(const Name& group) { return "Cannot mcast to CPG group "+group.str(); } -Cpg::Id Cpg::self() const { +MemberId Cpg::self() const { unsigned int nodeid; - check(cpg_local_get(handle, &nodeid), "Cannot get local CPG identity"); - return Id(nodeid, getpid()); + CPG_CHECK(cpg_local_get(handle, &nodeid), "Cannot get local CPG identity"); + return MemberId(nodeid, getpid()); } -ostream& operator<<(ostream& o, std::pair<cpg_address*,int> a) { - ostream_iterator<Cpg::Id> i(o, " "); - std::copy(a.first, a.first+a.second, i); - return o; +namespace { int byte(uint32_t value, int i) { return (value >> (i*8)) & 0xff; } } + +ostream& operator<<(ostream& out, const MemberId& id) { + if (id.first) { + out << byte(id.first, 0) << "." + << byte(id.first, 1) << "." + << byte(id.first, 2) << "." + << byte(id.first, 3) + << ":"; + } + return out << id.second; } -ostream& operator <<(ostream& out, const Cpg::Id& id) { - return out << id.getNodeId() << "-" << id.getPid(); +ostream& operator<<(ostream& o, const ConnectionId& c) { + return o << c.first << "-" << c.second; } -ostream& operator <<(ostream& out, const cpg_name& name) { - return out << string(name.value, name.length); +std::string MemberId::str() const { + char s[8]; + uint32_t x; + x = htonl(first); + ::memcpy(s, &x, 4); + x = htonl(second); + ::memcpy(s+4, &x, 4); + return std::string(s,8); } +MemberId::MemberId(const std::string& s) { + uint32_t x; + memcpy(&x, &s[0], 4); + first = ntohl(x); + memcpy(&x, &s[4], 4); + second = ntohl(x); +} }} // namespace qpid::cluster - - - diff --git a/cpp/src/qpid/cluster/Cpg.h b/cpp/src/qpid/cluster/Cpg.h index 96fd692a77..6b81c602bd 100644 --- a/cpp/src/qpid/cluster/Cpg.h +++ b/cpp/src/qpid/cluster/Cpg.h @@ -20,25 +20,18 @@ */ #include "qpid/Exception.h" +#include "qpid/cluster/types.h" #include "qpid/sys/IOHandle.h" -#include "qpid/cluster/Dispatchable.h" +#include "qpid/sys/Mutex.h" -#include <boost/tuple/tuple.hpp> -#include <boost/tuple/tuple_comparison.hpp> #include <boost/scoped_ptr.hpp> #include <cassert> - #include <string.h> -extern "C" { -#include <openais/cpg.h> -} - namespace qpid { namespace cluster { - /** * Lightweight C++ interface to cpg.h operations. * @@ -46,6 +39,7 @@ namespace cluster { * On error all functions throw Cpg::Exception. * */ + class Cpg : public sys::IOHandle { public: struct Exception : public ::qpid::Exception { @@ -53,6 +47,7 @@ class Cpg : public sys::IOHandle { }; struct Name : public cpg_name { + Name() { length = 0; } Name(const char* s) { copy(s, strlen(s)); } Name(const char* s, size_t n) { copy(s,n); } Name(const std::string& s) { copy(s.data(), s.size()); } @@ -65,14 +60,6 @@ class Cpg : public sys::IOHandle { std::string str() const { return std::string(value, length); } }; - // boost::tuple gives us == and < for free. - struct Id : public boost::tuple<uint32_t, uint32_t> { - Id(uint32_t n=0, uint32_t p=0) : boost::tuple<uint32_t, uint32_t>(n, p) {} - Id(const cpg_address& addr) : boost::tuple<uint32_t, uint32_t>(addr.nodeid, addr.pid) {} - uint32_t getNodeId() const { return boost::get<0>(*this); } - uint32_t getPid() const { return boost::get<1>(*this); } - }; - static std::string str(const cpg_name& n) { return std::string(n.value, n.length); } @@ -81,7 +68,7 @@ class Cpg : public sys::IOHandle { virtual ~Handler() {}; virtual void deliver( cpg_handle_t /*handle*/, - struct cpg_name *group, + const struct cpg_name *group, uint32_t /*nodeid*/, uint32_t /*pid*/, void* /*msg*/, @@ -89,10 +76,10 @@ class Cpg : public sys::IOHandle { virtual void configChange( cpg_handle_t /*handle*/, - struct cpg_name */*group*/, - struct cpg_address */*members*/, int /*nMembers*/, - struct cpg_address */*left*/, int /*nLeft*/, - struct cpg_address */*joined*/, int /*nJoined*/ + const struct cpg_name */*group*/, + const struct cpg_address */*members*/, int /*nMembers*/, + const struct cpg_address */*left*/, int /*nLeft*/, + const struct cpg_address */*joined*/, int /*nJoined*/ ) = 0; }; @@ -107,41 +94,115 @@ class Cpg : public sys::IOHandle { /** Disconnect from CPG */ void shutdown(); - /** Dispatch CPG events. - *@param type one of - * - CPG_DISPATCH_ONE - dispatch exactly one event. - * - CPG_DISPATCH_ALL - dispatch all available events, don't wait. - * - CPG_DISPATCH_BLOCKING - blocking dispatch loop. - */ - void dispatch(cpg_dispatch_t type) { - check(cpg_dispatch(handle,type), "Error in CPG dispatch"); - } + void dispatchOne(); + void dispatchAll(); + void dispatchBlocking(); - void dispatchOne() { dispatch(CPG_DISPATCH_ONE); } - void dispatchAll() { dispatch(CPG_DISPATCH_ALL); } - void dispatchBlocking() { dispatch(CPG_DISPATCH_BLOCKING); } + void join(const std::string& group); + void leave(); - void join(const Name& group); - void leave(const Name& group); - void mcast(const Name& group, const iovec* iov, int iovLen); + /** Multicast to the group. NB: must not be called concurrently. + * + *@return true if the message was multi-cast, false if + * it was not sent due to flow control. + */ + bool mcast(const iovec* iov, int iovLen); cpg_handle_t getHandle() const { return handle; } - Id self() const; + MemberId self() const; int getFd(); private: + + // Maximum number of retries for cog functions that can tell + // us to "try again later". + static const unsigned int cpgRetries = 5; + + // Don't let sleep-time between cpg retries to go above 0.1 second. + static const unsigned int maxCpgRetrySleep = 100000; + + + // Base class for the Cpg operations that need retry capability. + struct CpgOp { + std::string opName; + + CpgOp ( std::string opName ) + : opName(opName) { } + + virtual cpg_error_t op ( cpg_handle_t handle, struct cpg_name * ) = 0; + virtual std::string msg(const Name&) = 0; + virtual ~CpgOp ( ) { } + }; + + + struct CpgJoinOp : public CpgOp { + CpgJoinOp ( ) + : CpgOp ( std::string("cpg_join") ) { } + + cpg_error_t op(cpg_handle_t handle, struct cpg_name * group) { + return cpg_join ( handle, group ); + } + + std::string msg(const Name& name) { return cantJoinMsg(name); } + }; + + struct CpgLeaveOp : public CpgOp { + CpgLeaveOp ( ) + : CpgOp ( std::string("cpg_leave") ) { } + + cpg_error_t op(cpg_handle_t handle, struct cpg_name * group) { + return cpg_leave ( handle, group ); + } + + std::string msg(const Name& name) { return cantLeaveMsg(name); } + }; + + struct CpgFinalizeOp : public CpgOp { + CpgFinalizeOp ( ) + : CpgOp ( std::string("cpg_finalize") ) { } + + cpg_error_t op(cpg_handle_t handle, struct cpg_name *) { + return cpg_finalize ( handle ); + } + + std::string msg(const Name& name) { return cantFinalizeMsg(name); } + }; + + // This fn standardizes retry policy across all Cpg ops that need it. + void callCpg ( CpgOp & ); + + CpgJoinOp cpgJoinOp; + CpgLeaveOp cpgLeaveOp; + CpgFinalizeOp cpgFinalizeOp; + static std::string errorStr(cpg_error_t err, const std::string& msg); static std::string cantJoinMsg(const Name&); - static std::string cantLeaveMsg(const Name&); std::string cantMcastMsg(const Name&); - - static void check(cpg_error_t result, const std::string& msg) { - if (result != CPG_OK) throw Exception(errorStr(result, msg)); - } + static std::string cantLeaveMsg(const Name&); + static std::string cantMcastMsg(const Name&); + static std::string cantFinalizeMsg(const Name&); static Cpg* cpgFromHandle(cpg_handle_t); + // New versions for corosync 1.0 and higher + static void globalDeliver( + cpg_handle_t handle, + const struct cpg_name *group, + uint32_t nodeid, + uint32_t pid, + void* msg, + size_t msg_len); + + static void globalConfigChange( + cpg_handle_t handle, + const struct cpg_name *group, + const struct cpg_address *members, size_t nMembers, + const struct cpg_address *left, size_t nLeft, + const struct cpg_address *joined, size_t nJoined + ); + + // Old versions for openais static void globalDeliver( cpg_handle_t handle, struct cpg_name *group, @@ -158,18 +219,13 @@ class Cpg : public sys::IOHandle { struct cpg_address *joined, int nJoined ); - bool isFlowControlEnabled(); - void waitForFlowControl(); - cpg_handle_t handle; Handler& handler; bool isShutdown; + Name group; + sys::Mutex dispatchLock; }; -std::ostream& operator <<(std::ostream& out, const cpg_name& name); -std::ostream& operator <<(std::ostream& out, const Cpg::Id& id); -std::ostream& operator <<(std::ostream& out, const std::pair<cpg_address*,int> addresses); - inline bool operator==(const cpg_name& a, const cpg_name& b) { return a.length==b.length && strncmp(a.value, b.value, a.length) == 0; } @@ -177,5 +233,4 @@ inline bool operator!=(const cpg_name& a, const cpg_name& b) { return !(a == b); }} // namespace qpid::cluster - #endif /*!CPG_H*/ diff --git a/cpp/src/qpid/cluster/Decoder.cpp b/cpp/src/qpid/cluster/Decoder.cpp new file mode 100644 index 0000000000..23ba372d78 --- /dev/null +++ b/cpp/src/qpid/cluster/Decoder.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/cluster/Decoder.h" +#include "qpid/cluster/EventFrame.h" +#include "qpid/framing/ClusterConnectionDeliverCloseBody.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/AMQFrame.h" + + +namespace qpid { +namespace cluster { + +void Decoder::decode(const EventHeader& eh, const char* data) { + sys::Mutex::ScopedLock l(lock); + assert(eh.getType() == DATA); // Only handle connection data events. + const char* cp = static_cast<const char*>(data); + framing::Buffer buf(const_cast<char*>(cp), eh.getSize()); + framing::FrameDecoder& decoder = map[eh.getConnectionId()]; + if (decoder.decode(buf)) { // Decoded a frame + framing::AMQFrame frame(decoder.getFrame()); + while (decoder.decode(buf)) { + callback(EventFrame(eh, frame)); + frame = decoder.getFrame(); + } + // Set read-credit on the last frame ending in this event. + // Credit will be given when this frame is processed. + callback(EventFrame(eh, frame, 1)); + } + else { + // We must give 1 unit read credit per event. + // This event does not complete any frames so + // send an empty frame with the read credit. + callback(EventFrame(eh, framing::AMQFrame(), 1)); + } +} + +void Decoder::erase(const ConnectionId& c) { + sys::Mutex::ScopedLock l(lock); + map.erase(c); +} + +framing::FrameDecoder& Decoder::get(const ConnectionId& c) { + sys::Mutex::ScopedLock l(lock); + return map[c]; +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/Decoder.h b/cpp/src/qpid/cluster/Decoder.h new file mode 100644 index 0000000000..2e2af2868f --- /dev/null +++ b/cpp/src/qpid/cluster/Decoder.h @@ -0,0 +1,59 @@ +#ifndef QPID_CLUSTER_DECODER_H +#define QPID_CLUSTER_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/cluster/types.h" +#include "qpid/framing/FrameDecoder.h" +#include "qpid/sys/Mutex.h" +#include <boost/function.hpp> +#include <map> + +namespace qpid { +namespace cluster { + +class EventFrame; +class EventHeader; + +/** + * A map of decoders for connections. + */ +class Decoder +{ + public: + typedef boost::function<void(const EventFrame&)> FrameHandler; + + Decoder(FrameHandler fh) : callback(fh) {} + void decode(const EventHeader& eh, const char* data); + void erase(const ConnectionId&); + framing::FrameDecoder& get(const ConnectionId& c); + + private: + typedef std::map<ConnectionId, framing::FrameDecoder> Map; + sys::Mutex lock; + Map map; + void process(const EventFrame&); + FrameHandler callback; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_DECODER_H*/ diff --git a/cpp/src/qpid/cluster/ErrorCheck.cpp b/cpp/src/qpid/cluster/ErrorCheck.cpp new file mode 100644 index 0000000000..d66db8551d --- /dev/null +++ b/cpp/src/qpid/cluster/ErrorCheck.cpp @@ -0,0 +1,155 @@ +/* + * + * 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/cluster/ErrorCheck.h" +#include "qpid/cluster/EventFrame.h" +#include "qpid/cluster/ClusterMap.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/framing/ClusterErrorCheckBody.h" +#include "qpid/framing/ClusterConfigChangeBody.h" +#include "qpid/log/Statement.h" + +#include <algorithm> + +namespace qpid { +namespace cluster { + +using namespace std; +using namespace framing; +using namespace framing::cluster; + +ErrorCheck::ErrorCheck(Cluster& c) + : cluster(c), mcast(c.getMulticast()), frameSeq(0), type(ERROR_TYPE_NONE), connection(0) +{} + +void ErrorCheck::error( + Connection& c, ErrorType t, framing::SequenceNumber seq, const MemberSet& ms, + const std::string& msg) +{ + // Detected a local error, inform cluster and set error state. + assert(t != ERROR_TYPE_NONE); // Must be an error. + assert(type == ERROR_TYPE_NONE); // Can't be called when already in an error state. + type = t; + unresolved = ms; + frameSeq = seq; + connection = &c; + message = msg; + QPID_LOG(debug, cluster<< (type == ERROR_TYPE_SESSION ? " channel" : " connection") + << " error " << frameSeq << " on " << c + << " must be resolved with: " << unresolved + << ": " << message); + mcast.mcastControl( + ClusterErrorCheckBody(ProtocolVersion(), type, frameSeq), cluster.getId()); + // If there are already frames queued up by a previous error, review + // them with respect to this new error. + for (FrameQueue::iterator i = frames.begin(); i != frames.end(); i = review(i)) + ; +} + +void ErrorCheck::delivered(const EventFrame& e) { + frames.push_back(e); + review(frames.end()-1); +} + +// Review a frame in the queue with respect to the current error. +ErrorCheck::FrameQueue::iterator ErrorCheck::review(const FrameQueue::iterator& i) { + FrameQueue::iterator next = i+1; + if(!isUnresolved() || !i->frame.getBody() || !i->frame.getMethod()) + return next; // Only interested in control frames while unresolved. + const AMQMethodBody* method = i->frame.getMethod(); + if (method->isA<const ClusterErrorCheckBody>()) { + const ClusterErrorCheckBody* errorCheck = + static_cast<const ClusterErrorCheckBody*>(method); + + if (errorCheck->getFrameSeq() == frameSeq) { // Addresses current error + next = frames.erase(i); // Drop matching error check controls + if (errorCheck->getType() < type) { // my error is worse than his + QPID_LOG(critical, cluster + << " local error " << frameSeq << " did not occur on member " + << i->getMemberId() + << ": " << message); + throw Exception( + QPID_MSG("local error did not occur on all cluster members " << ": " << message)); + } + else { // his error is worse/same as mine. + QPID_LOG(debug, cluster << " error " << frameSeq + << " resolved with " << i->getMemberId()); + unresolved.erase(i->getMemberId()); + checkResolved(); + } + } + else if (errorCheck->getFrameSeq() < frameSeq && errorCheck->getType() != NONE + && i->connectionId.getMember() != cluster.getId()) + { + // This error occured before the current error so we + // have processed past it. + next = frames.erase(i); // Drop the error check control + respondNone(i->connectionId.getMember(), errorCheck->getType(), + errorCheck->getFrameSeq()); + } + // if errorCheck->getFrameSeq() > frameSeq then leave it in the queue. + } + else if (method->isA<const ClusterConfigChangeBody>()) { + const ClusterConfigChangeBody* configChange = + static_cast<const ClusterConfigChangeBody*>(method); + if (configChange) { + MemberSet members(decodeMemberSet(configChange->getCurrent())); + QPID_LOG(debug, cluster << " apply config change to error " + << frameSeq << ": " << members); + MemberSet intersect; + set_intersection(members.begin(), members.end(), + unresolved.begin(), unresolved.end(), + inserter(intersect, intersect.begin())); + unresolved.swap(intersect); + checkResolved(); + } + } + return next; +} + +void ErrorCheck::checkResolved() { + if (unresolved.empty()) { // No more potentially conflicted members, we're clear. + type = ERROR_TYPE_NONE; + QPID_LOG(debug, cluster << " error " << frameSeq << " resolved."); + } + else + QPID_LOG(debug, cluster << " error " << frameSeq + << " must be resolved with " << unresolved); +} + +EventFrame ErrorCheck::getNext() { + assert(canProcess()); + EventFrame e(frames.front()); + frames.pop_front(); + return e; +} + +void ErrorCheck::respondNone(const MemberId& from, uint8_t type, framing::SequenceNumber frameSeq) { + // Don't respond to non-errors or to my own errors. + if (type == ERROR_TYPE_NONE || from == cluster.getId()) + return; + QPID_LOG(debug, cluster << " error " << frameSeq << " did not occur locally."); + mcast.mcastControl( + ClusterErrorCheckBody(ProtocolVersion(), ERROR_TYPE_NONE, frameSeq), + cluster.getId() + ); +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/ErrorCheck.h b/cpp/src/qpid/cluster/ErrorCheck.h new file mode 100644 index 0000000000..de8cedafb3 --- /dev/null +++ b/cpp/src/qpid/cluster/ErrorCheck.h @@ -0,0 +1,90 @@ +#ifndef QPID_CLUSTER_ERRORCHECK_H +#define QPID_CLUSTER_ERRORCHECK_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/cluster/MemberSet.h" +#include "qpid/cluster/Multicaster.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/SequenceNumber.h" +#include <boost/function.hpp> +#include <deque> +#include <set> + +namespace qpid { +namespace cluster { + +class EventFrame; +class Cluster; +class Multicaster; +class Connection; + +/** + * Error checking logic. + * + * When an error occurs queue up frames until we can determine if all + * nodes experienced the error. If not, we shut down. + */ +class ErrorCheck +{ + public: + typedef framing::cluster::ErrorType ErrorType; + typedef framing::SequenceNumber SequenceNumber; + + ErrorCheck(Cluster&); + + /** A local error has occured */ + void error(Connection&, ErrorType, SequenceNumber frameSeq, const MemberSet&, + const std::string& msg); + + /** Called when a frame is delivered */ + void delivered(const EventFrame&); + + /**@pre canProcess **/ + EventFrame getNext(); + + bool canProcess() const { return type == NONE && !frames.empty(); } + + bool isUnresolved() const { return type != NONE; } + + /** Respond to an error check saying we had no error. */ + void respondNone(const MemberId&, uint8_t type, SequenceNumber frameSeq); + + private: + static const ErrorType NONE = framing::cluster::ERROR_TYPE_NONE; + typedef std::deque<EventFrame> FrameQueue; + FrameQueue::iterator review(const FrameQueue::iterator&); + void checkResolved(); + + Cluster& cluster; + Multicaster& mcast; + FrameQueue frames; + MemberSet unresolved; + SequenceNumber frameSeq; + ErrorType type; + Connection* connection; + std::string message; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_ERRORCHECK_H*/ diff --git a/cpp/src/qpid/cluster/Event.cpp b/cpp/src/qpid/cluster/Event.cpp new file mode 100644 index 0000000000..52564990f6 --- /dev/null +++ b/cpp/src/qpid/cluster/Event.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/cluster/types.h" +#include "qpid/cluster/Event.h" +#include "qpid/cluster/Cpg.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/assert.h" +#include <ostream> +#include <iterator> +#include <algorithm> + +namespace qpid { +namespace cluster { + +using framing::Buffer; +using framing::AMQFrame; + +const size_t EventHeader::HEADER_SIZE = + sizeof(uint8_t) + // type + sizeof(uint64_t) + // connection pointer only, CPG provides member ID. + sizeof(uint32_t) // payload size + ; + +EventHeader::EventHeader(EventType t, const ConnectionId& c, size_t s) + : type(t), connectionId(c), size(s) {} + + +Event::Event() {} + +Event::Event(EventType t, const ConnectionId& c, size_t s) + : EventHeader(t,c,s), store(RefCountedBuffer::create(s+HEADER_SIZE)) +{} + +void EventHeader::decode(const MemberId& m, framing::Buffer& buf) { + if (buf.available() <= HEADER_SIZE) + throw Exception("Not enough for multicast header"); + type = (EventType)buf.getOctet(); + if(type != DATA && type != CONTROL) + throw Exception("Invalid multicast event type"); + connectionId = ConnectionId(m, buf.getLongLong()); + size = buf.getLong(); +} + +Event Event::decodeCopy(const MemberId& m, framing::Buffer& buf) { + Event e; + e.decode(m, buf); // Header + if (buf.available() < e.size) + throw Exception("Not enough data for multicast event"); + e.store = RefCountedBuffer::create(e.size + HEADER_SIZE); + memcpy(e.getData(), buf.getPointer() + buf.getPosition(), e.size); + return e; +} + +Event Event::control(const framing::AMQFrame& f, const ConnectionId& cid) { + Event e(CONTROL, cid, f.encodedSize()); + Buffer buf(e); + f.encode(buf); + return e; +} + +Event Event::control(const framing::AMQBody& body, const ConnectionId& cid) { + return control(framing::AMQFrame(body), cid); +} + +iovec Event::toIovec() const { + encodeHeader(); + iovec iov = { const_cast<char*>(getStore()), getStoreSize() }; + return iov; +} + +void EventHeader::encode(Buffer& b) const { + b.putOctet(type); + b.putLongLong(connectionId.getNumber()); + b.putLong(size); +} + +// Encode my header in my buffer. +void Event::encodeHeader () const { + Buffer b(const_cast<char*>(getStore()), HEADER_SIZE); + encode(b); + assert(b.getPosition() == HEADER_SIZE); +} + +Event::operator Buffer() const { + return Buffer(const_cast<char*>(getData()), getSize()); +} + +const AMQFrame& Event::getFrame() const { + assert(type == CONTROL); + if (!frame.getBody()) { + Buffer buf(*this); + QPID_ASSERT(frame.decode(buf)); + } + return frame; +} + +static const char* EVENT_TYPE_NAMES[] = { "data", "control" }; + +std::ostream& operator<< (std::ostream& o, EventType t) { + return o << EVENT_TYPE_NAMES[t]; +} + +std::ostream& operator<< (std::ostream& o, const EventHeader& e) { + return o << "Event[" << e.getConnectionId() << " " << e.getType() + << " " << e.getSize() << " bytes]"; +} + +std::ostream& operator<< (std::ostream& o, const Event& e) { + o << "Event[" << e.getConnectionId() << " "; + if (e.getType() == CONTROL) + o << e.getFrame(); + else + o << " data " << e.getSize() << " bytes"; + return o << "]"; +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/Event.h b/cpp/src/qpid/cluster/Event.h new file mode 100644 index 0000000000..07f74d3ba5 --- /dev/null +++ b/cpp/src/qpid/cluster/Event.h @@ -0,0 +1,116 @@ +#ifndef QPID_CLUSTER_EVENT_H +#define QPID_CLUSTER_EVENT_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/cluster/types.h" +#include "qpid/RefCountedBuffer.h" +#include "qpid/framing/AMQFrame.h" +#include <sys/uio.h> // For iovec +#include <iosfwd> + +#include "qpid/cluster/types.h" + +namespace qpid { + +namespace framing { +class AMQBody; +class AMQFrame; +class Buffer; +} + +namespace cluster { + +/** Header data for a multicast event */ +class EventHeader { + public: + EventHeader(EventType t=DATA, const ConnectionId& c=ConnectionId(), size_t size=0); + void decode(const MemberId& m, framing::Buffer&); + void encode(framing::Buffer&) const; + + EventType getType() const { return type; } + ConnectionId getConnectionId() const { return connectionId; } + MemberId getMemberId() const { return connectionId.getMember(); } + + /** Size of payload data, excluding header. */ + size_t getSize() const { return size; } + /** Size of header + payload. */ + size_t getStoreSize() const { return size + HEADER_SIZE; } + + bool isCluster() const { return connectionId.getNumber() == 0; } + bool isConnection() const { return connectionId.getNumber() != 0; } + bool isControl() const { return type == CONTROL; } + + protected: + static const size_t HEADER_SIZE; + + EventType type; + ConnectionId connectionId; + size_t size; +}; + +/** + * Events are sent to/received from the cluster. + * Refcounted so they can be stored on queues. + */ +class Event : public EventHeader { + public: + Event(); + /** Create an event with a buffer that can hold size bytes plus an event header. */ + Event(EventType t, const ConnectionId& c, size_t); + + /** Create an event copied from delivered data. */ + static Event decodeCopy(const MemberId& m, framing::Buffer&); + + /** Create a control event. */ + static Event control(const framing::AMQBody&, const ConnectionId&); + + /** Create a control event. */ + static Event control(const framing::AMQFrame&, const ConnectionId&); + + // Data excluding header. + char* getData() { return store + HEADER_SIZE; } + const char* getData() const { return store + HEADER_SIZE; } + + // Store including header + char* getStore() { return store; } + const char* getStore() const { return store; } + + const framing::AMQFrame& getFrame() const; + + operator framing::Buffer() const; + + iovec toIovec() const; + + private: + void encodeHeader() const; + + RefCountedBuffer::pointer store; + mutable framing::AMQFrame frame; +}; + +std::ostream& operator << (std::ostream&, const Event&); +std::ostream& operator << (std::ostream&, const EventHeader&); + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_EVENT_H*/ diff --git a/cpp/src/qpid/cluster/EventFrame.cpp b/cpp/src/qpid/cluster/EventFrame.cpp new file mode 100644 index 0000000000..5fbe1fe57c --- /dev/null +++ b/cpp/src/qpid/cluster/EventFrame.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. + * + */ +#include "qpid/cluster/EventFrame.h" +#include "qpid/cluster/Connection.h" + +namespace qpid { +namespace cluster { + +EventFrame::EventFrame() {} + +EventFrame::EventFrame(const EventHeader& e, const framing::AMQFrame& f, int rc) + : connectionId(e.getConnectionId()), frame(f), readCredit(rc), type(e.getType()) +{} + +std::ostream& operator<<(std::ostream& o, const EventFrame& e) { + if (e.frame.getBody()) o << e.frame; + else o << "null-frame"; + o << " " << e.type << " " << e.connectionId; + if (e.readCredit) o << " read-credit=" << e.readCredit; + return o; +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/EventFrame.h b/cpp/src/qpid/cluster/EventFrame.h new file mode 100644 index 0000000000..61447c5525 --- /dev/null +++ b/cpp/src/qpid/cluster/EventFrame.h @@ -0,0 +1,60 @@ +#ifndef QPID_CLUSTER_EVENTFRAME_H +#define QPID_CLUSTER_EVENTFRAME_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/cluster/types.h" +#include "qpid/cluster/Event.h" +#include "qpid/framing/AMQFrame.h" +#include <boost/intrusive_ptr.hpp> +#include <iosfwd> + +namespace qpid { +namespace cluster { + +/** + * A frame decoded from an Event. + */ +struct EventFrame +{ + public: + EventFrame(); + + EventFrame(const EventHeader& e, const framing::AMQFrame& f, int rc=0); + + bool isCluster() const { return connectionId.getNumber() == 0; } + bool isConnection() const { return connectionId.getNumber() != 0; } + bool isLastInEvent() const { return readCredit; } + MemberId getMemberId() const { return connectionId.getMember(); } + + + ConnectionId connectionId; + framing::AMQFrame frame; + int readCredit; ///< last frame in an event, give credit when processed. + EventType type; +}; + +std::ostream& operator<<(std::ostream& o, const EventFrame& e); + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_EVENTFRAME_H*/ diff --git a/cpp/src/qpid/cluster/ExpiryPolicy.cpp b/cpp/src/qpid/cluster/ExpiryPolicy.cpp new file mode 100644 index 0000000000..190eeb7293 --- /dev/null +++ b/cpp/src/qpid/cluster/ExpiryPolicy.cpp @@ -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. + * + */ + +#include "qpid/broker/Message.h" +#include "qpid/cluster/ExpiryPolicy.h" +#include "qpid/cluster/Multicaster.h" +#include "qpid/framing/ClusterMessageExpiredBody.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Timer.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace cluster { + +ExpiryPolicy::ExpiryPolicy(Multicaster& m, const MemberId& id, sys::Timer& t) + : expiryId(0), expiredPolicy(new Expired), mcast(m), memberId(id), timer(t) {} + +struct ExpiryTask : public sys::TimerTask { + ExpiryTask(const boost::intrusive_ptr<ExpiryPolicy>& policy, uint64_t id, sys::AbsTime when) + : TimerTask(when), expiryPolicy(policy), expiryId(id) {} + void fire() { expiryPolicy->sendExpire(expiryId); } + boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; + const uint64_t expiryId; +}; + +void ExpiryPolicy::willExpire(broker::Message& m) { + uint64_t id = expiryId++; + assert(unexpiredById.find(id) == unexpiredById.end()); + assert(unexpiredByMessage.find(&m) == unexpiredByMessage.end()); + unexpiredById[id] = &m; + unexpiredByMessage[&m] = id; + timer.add(new ExpiryTask(this, id, m.getExpiration())); +} + +void ExpiryPolicy::forget(broker::Message& m) { + MessageIdMap::iterator i = unexpiredByMessage.find(&m); + assert(i != unexpiredByMessage.end()); + unexpiredById.erase(i->second); + unexpiredByMessage.erase(i); +} + +bool ExpiryPolicy::hasExpired(broker::Message& m) { + return unexpiredByMessage.find(&m) == unexpiredByMessage.end(); +} + +void ExpiryPolicy::sendExpire(uint64_t id) { + mcast.mcastControl(framing::ClusterMessageExpiredBody(framing::ProtocolVersion(), id), memberId); +} + +void ExpiryPolicy::deliverExpire(uint64_t id) { + IdMessageMap::iterator i = unexpiredById.find(id); + if (i != unexpiredById.end()) { + i->second->setExpiryPolicy(expiredPolicy); // hasExpired() == true; + unexpiredByMessage.erase(i->second); + unexpiredById.erase(i); + } +} + +boost::optional<uint64_t> ExpiryPolicy::getId(broker::Message& m) { + MessageIdMap::iterator i = unexpiredByMessage.find(&m); + return i == unexpiredByMessage.end() ? boost::optional<uint64_t>() : i->second; +} + +bool ExpiryPolicy::Expired::hasExpired(broker::Message&) { return true; } +void ExpiryPolicy::Expired::willExpire(broker::Message&) { } + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/ExpiryPolicy.h b/cpp/src/qpid/cluster/ExpiryPolicy.h new file mode 100644 index 0000000000..bdbe3a61dc --- /dev/null +++ b/cpp/src/qpid/cluster/ExpiryPolicy.h @@ -0,0 +1,89 @@ +#ifndef QPID_CLUSTER_EXPIRYPOLICY_H +#define QPID_CLUSTER_EXPIRYPOLICY_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/cluster/types.h" +#include "qpid/broker/ExpiryPolicy.h" +#include "qpid/sys/Mutex.h" +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/optional.hpp> +#include <map> + +namespace qpid { + +namespace broker { +class Message; +} + +namespace sys { +class Timer; +} + +namespace cluster { +class Multicaster; + +/** + * Cluster expiry policy + */ +class ExpiryPolicy : public broker::ExpiryPolicy +{ + public: + ExpiryPolicy(Multicaster&, const MemberId&, sys::Timer&); + + void willExpire(broker::Message&); + bool hasExpired(broker::Message&); + void forget(broker::Message&); + + // Send expiration notice to cluster. + void sendExpire(uint64_t); + + // Cluster delivers expiry notice. + void deliverExpire(uint64_t); + + void setId(uint64_t id) { expiryId = id; } + uint64_t getId() const { return expiryId; } + + boost::optional<uint64_t> getId(broker::Message&); + + private: + typedef std::map<broker::Message*, uint64_t> MessageIdMap; + typedef std::map<uint64_t, broker::Message*> IdMessageMap; + + struct Expired : public broker::ExpiryPolicy { + bool hasExpired(broker::Message&); + void willExpire(broker::Message&); + }; + + MessageIdMap unexpiredByMessage; + IdMessageMap unexpiredById; + uint64_t expiryId; + boost::intrusive_ptr<Expired> expiredPolicy; + Multicaster& mcast; + MemberId memberId; + sys::Timer& timer; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_EXPIRYPOLICY_H*/ diff --git a/cpp/src/qpid/cluster/FailoverExchange.cpp b/cpp/src/qpid/cluster/FailoverExchange.cpp new file mode 100644 index 0000000000..e01c41494b --- /dev/null +++ b/cpp/src/qpid/cluster/FailoverExchange.cpp @@ -0,0 +1,99 @@ +/* + * + * 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/cluster/FailoverExchange.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/Queue.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/Array.h" +#include <boost/bind.hpp> +#include <algorithm> + +namespace qpid { +namespace cluster { +using namespace std; + +using namespace broker; +using namespace framing; + +const string FailoverExchange::TYPE_NAME("amq.failover"); + +FailoverExchange::FailoverExchange(management::Manageable* parent) : Exchange(TYPE_NAME, parent) { + if (mgmtExchange != 0) + mgmtExchange->set_type(TYPE_NAME); +} + + +void FailoverExchange::setUrls(const vector<Url>& u) { + Lock l(lock); + urls=u; + if (urls.empty()) return; + std::for_each(queues.begin(), queues.end(), + boost::bind(&FailoverExchange::sendUpdate, this, _1)); +} + +string FailoverExchange::getType() const { return TYPE_NAME; } + +bool FailoverExchange::bind(Queue::shared_ptr queue, const string&, const framing::FieldTable*) { + Lock l(lock); + sendUpdate(queue); + return queues.insert(queue).second; +} + +bool FailoverExchange::unbind(Queue::shared_ptr queue, const string&, const framing::FieldTable*) { + Lock l(lock); + return queues.erase(queue); +} + +bool FailoverExchange::isBound(Queue::shared_ptr queue, const string* const, const framing::FieldTable*) { + Lock l(lock); + return queues.find(queue) != queues.end(); +} + +void FailoverExchange::route(Deliverable&, const string& , const framing::FieldTable* ) { + QPID_LOG(warning, "Message received by exchange " << TYPE_NAME << " ignoring"); +} + +void FailoverExchange::sendUpdate(const Queue::shared_ptr& queue) { + // Called with lock held. + if (urls.empty()) return; + framing::Array array(0x95); + for (Urls::const_iterator i = urls.begin(); i != urls.end(); ++i) + array.add(boost::shared_ptr<Str16Value>(new Str16Value(i->str()))); + const ProtocolVersion v; + boost::intrusive_ptr<Message> msg(new Message); + AMQFrame command(MessageTransferBody(v, TYPE_NAME, 1, 0)); + command.setLastSegment(false); + msg->getFrames().append(command); + AMQHeaderBody header; + header.get<MessageProperties>(true)->setContentLength(0); + header.get<MessageProperties>(true)->getApplicationHeaders().setArray(TYPE_NAME, array); + AMQFrame headerFrame(header); + headerFrame.setFirstSegment(false); + msg->getFrames().append(headerFrame); + DeliverableMessage(msg).deliverTo(queue); +} + +}} // namespace cluster diff --git a/cpp/src/qpid/cluster/FailoverExchange.h b/cpp/src/qpid/cluster/FailoverExchange.h new file mode 100644 index 0000000000..738cd2a602 --- /dev/null +++ b/cpp/src/qpid/cluster/FailoverExchange.h @@ -0,0 +1,68 @@ +#ifndef QPID_CLUSTER_FAILOVEREXCHANGE_H +#define QPID_CLUSTER_FAILOVEREXCHANGE_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/Exchange.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/Url.h" + +#include <vector> +#include <set> + +namespace qpid { +namespace cluster { + +/** + * Failover exchange provides failover host list, as specified in AMQP 0-10. + */ +class FailoverExchange : public broker::Exchange +{ + public: + static const std::string TYPE_NAME; + + FailoverExchange(management::Manageable* parent); + + void setUrls(const std::vector<Url>&); + + // Exchange overrides + std::string getType() const; + bool bind(broker::Queue::shared_ptr queue, const std::string& routingKey, const framing::FieldTable* args); + bool unbind(broker::Queue::shared_ptr queue, const std::string& routingKey, const framing::FieldTable* args); + bool isBound(broker::Queue::shared_ptr queue, const std::string* const routingKey, const framing::FieldTable* const args); + void route(broker::Deliverable& msg, const std::string& routingKey, const framing::FieldTable* args); + + private: + void sendUpdate(const broker::Queue::shared_ptr&); + + typedef sys::Mutex::ScopedLock Lock; + typedef std::vector<Url> Urls; + typedef std::set<broker::Queue::shared_ptr> Queues; + + sys::Mutex lock; + Urls urls; + Queues queues; + +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_FAILOVEREXCHANGE_H*/ diff --git a/cpp/src/qpid/cluster/InitialStatusMap.cpp b/cpp/src/qpid/cluster/InitialStatusMap.cpp new file mode 100644 index 0000000000..59338f89d4 --- /dev/null +++ b/cpp/src/qpid/cluster/InitialStatusMap.cpp @@ -0,0 +1,197 @@ +/* + * + * 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 "InitialStatusMap.h" +#include "StoreStatus.h" +#include <algorithm> +#include <boost/bind.hpp> + +namespace qpid { +namespace cluster { + +using namespace std; +using namespace boost; +using namespace framing::cluster; +using namespace framing; + +InitialStatusMap::InitialStatusMap(const MemberId& self_, size_t size_) + : self(self_), completed(), resendNeeded(), size(size_) +{} + +void InitialStatusMap::configChange(const MemberSet& members) { + resendNeeded = false; + bool wasComplete = isComplete(); + if (firstConfig.empty()) firstConfig = members; + MemberSet::const_iterator i = members.begin(); + Map::iterator j = map.begin(); + while (i != members.end() || j != map.end()) { + if (i == members.end()) { // j not in members, member left + Map::iterator k = j++; + map.erase(k); + } + else if (j == map.end()) { // i not in map, member joined + resendNeeded = true; + map[*i] = optional<Status>(); + ++i; + } + else if (*i < j->first) { // i not in map, member joined + resendNeeded = true; + map[*i] = optional<Status>(); + ++i; + } + else if (*i > j->first) { // j not in members, member left + Map::iterator k = j++; + map.erase(k); + } + else { + i++; j++; + } + } + if (resendNeeded) { // Clear all status + for (Map::iterator i = map.begin(); i != map.end(); ++i) + i->second = optional<Status>(); + } + completed = isComplete() && !wasComplete; // Set completed on the transition. +} + +void InitialStatusMap::received(const MemberId& m, const Status& s){ + bool wasComplete = isComplete(); + map[m] = s; + completed = isComplete() && !wasComplete; // Set completed on the transition. +} + +bool InitialStatusMap::notInitialized(const Map::value_type& v) { + return !v.second; +} + +bool InitialStatusMap::isComplete() { + return !map.empty() && find_if(map.begin(), map.end(), ¬Initialized) == map.end() + && (map.size() >= size); +} + +bool InitialStatusMap::transitionToComplete() { + return completed; +} + +bool InitialStatusMap::isResendNeeded() { + bool ret = resendNeeded; + resendNeeded = false; + return ret; +} + +bool InitialStatusMap::isActive(const Map::value_type& v) { + return v.second && v.second->getActive(); +} + +bool InitialStatusMap::hasStore(const Map::value_type& v) { + return v.second && + (v.second->getStoreState() == STORE_STATE_CLEAN_STORE || + v.second->getStoreState() == STORE_STATE_DIRTY_STORE); +} + +bool InitialStatusMap::isUpdateNeeded() { + assert(isComplete()); + // We need an update if there are any active members. + if (find_if(map.begin(), map.end(), &isActive) != map.end()) return true; + + // Otherwise it depends on store status, get my own status: + Map::iterator me = map.find(self); + assert(me != map.end()); + assert(me->second); + switch (me->second->getStoreState()) { + case STORE_STATE_NO_STORE: + case STORE_STATE_EMPTY_STORE: + // If anybody has a store then we need an update. + return find_if(map.begin(), map.end(), &hasStore) != map.end(); + case STORE_STATE_DIRTY_STORE: return true; + case STORE_STATE_CLEAN_STORE: return false; // Use our own store + } + return false; +} + +MemberSet InitialStatusMap::getElders() { + assert(isComplete()); + MemberSet elders; + // Elders are from first config change, active or higher node-id. + for (MemberSet::iterator i = firstConfig.begin(); i != firstConfig.end(); ++i) { + if (map.find(*i) != map.end() && (map[*i]->getActive() || *i > self)) + elders.insert(*i); + } + return elders; +} + +// Get cluster ID from an active member or the youngest newcomer. +framing::Uuid InitialStatusMap::getClusterId() { + assert(isComplete()); + assert(!map.empty()); + Map::iterator i = find_if(map.begin(), map.end(), &isActive); + if (i != map.end()) + return i->second->getClusterId(); // An active member + else + return map.begin()->second->getClusterId(); // Youngest newcomer in node-id order +} + +void checkId(Uuid& expect, const Uuid& actual, const string& msg) { + if (!expect) expect = actual; + assert(expect); + if (expect != actual) + throw Exception(msg); +} + +void InitialStatusMap::checkConsistent() { + assert(isComplete()); + int clean = 0; + int dirty = 0; + int empty = 0; + int none = 0; + int active = 0; + Uuid clusterId; + Uuid shutdownId; + + for (Map::iterator i = map.begin(); i != map.end(); ++i) { + if (i->second->getActive()) ++active; + switch (i->second->getStoreState()) { + case STORE_STATE_NO_STORE: ++none; break; + case STORE_STATE_EMPTY_STORE: ++empty; break; + case STORE_STATE_DIRTY_STORE: + ++dirty; + checkId(clusterId, i->second->getClusterId(), + "Cluster-ID mismatch. Stores belong to different clusters."); + break; + case STORE_STATE_CLEAN_STORE: + ++clean; + checkId(clusterId, i->second->getClusterId(), + "Cluster-ID mismatch. Stores belong to different clusters."); + checkId(shutdownId, i->second->getShutdownId(), + "Shutdown-ID mismatch. Stores were not shut down together"); + break; + } + } + // Can't mix transient and persistent members. + if (none && (clean+dirty+empty)) + throw Exception("Mixing transient and persistent brokers in a cluster"); + // If there are no active members and there are dirty stores there + // must be at least one clean store. + if (!active && dirty && !clean) + throw Exception("Cannot recover, no clean store"); +} + + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/InitialStatusMap.h b/cpp/src/qpid/cluster/InitialStatusMap.h new file mode 100644 index 0000000000..40fd9ee49d --- /dev/null +++ b/cpp/src/qpid/cluster/InitialStatusMap.h @@ -0,0 +1,75 @@ +#ifndef QPID_CLUSTER_INITIALSTATUSMAP_H +#define QPID_CLUSTER_INITIALSTATUSMAP_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 "MemberSet.h" +#include <qpid/framing/ClusterInitialStatusBody.h> +#include <boost/optional.hpp> + +namespace qpid { +namespace cluster { + +/** + * Track status of cluster members during initialization. + */ +class InitialStatusMap +{ + public: + typedef framing::ClusterInitialStatusBody Status; + + InitialStatusMap(const MemberId& self, size_t size); + /** Process a config change. @return true if we need to re-send our status */ + void configChange(const MemberSet& newConfig); + /** @return true if we need to re-send status */ + bool isResendNeeded(); + + /** Process received status */ + void received(const MemberId&, const Status& is); + + /**@return true if the map is complete. */ + bool isComplete(); + /**@return true if the map was completed by the last config change or received. */ + bool transitionToComplete(); + /**@pre isComplete(). @return this node's elders */ + MemberSet getElders(); + /**@pre isComplete(). @return True if we need an update. */ + bool isUpdateNeeded(); + /**@pre isComplete(). @return Cluster-wide cluster ID. */ + framing::Uuid getClusterId(); + /**@pre isComplete(). @throw Exception if there are any inconsistencies. */ + void checkConsistent(); + + private: + typedef std::map<MemberId, boost::optional<Status> > Map; + static bool notInitialized(const Map::value_type&); + static bool isActive(const Map::value_type&); + static bool hasStore(const Map::value_type&); + Map map; + MemberSet firstConfig; + MemberId self; + bool completed, resendNeeded; + size_t size; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_INITIALSTATUSMAP_H*/ diff --git a/cpp/src/qpid/cluster/LockedConnectionMap.h b/cpp/src/qpid/cluster/LockedConnectionMap.h new file mode 100644 index 0000000000..ac744d4f94 --- /dev/null +++ b/cpp/src/qpid/cluster/LockedConnectionMap.h @@ -0,0 +1,65 @@ +#ifndef QPID_CLUSTER_LOCKEDCONNECTIONMAP_H +#define QPID_CLUSTER_LOCKEDCONNECTIONMAP_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/cluster/types.h" +#include "qpid/sys/Mutex.h" +#include "qpid/cluster/Connection.h" + +namespace qpid { +namespace cluster { + +/** + * Thread safe map of connections. + */ +class LockedConnectionMap +{ + public: + void insert(const ConnectionPtr& c) { + sys::Mutex::ScopedLock l(lock); + assert(map.find(c->getId()) == map.end()); + map[c->getId()] = c; + } + + ConnectionPtr getErase(const ConnectionId& c) { + sys::Mutex::ScopedLock l(lock); + Map::iterator i = map.find(c); + if (i != map.end()) { + ConnectionPtr cp = i->second; + map.erase(i); + return cp; + } + else + return 0; + } + + void clear() { sys::Mutex::ScopedLock l(lock); map.clear(); } + + private: + typedef std::map<ConnectionId, ConnectionPtr> Map; + mutable sys::Mutex lock; + Map map; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_LOCKEDCONNECTIONMAP_H*/ diff --git a/cpp/src/qpid/cluster/McastFrameHandler.h b/cpp/src/qpid/cluster/McastFrameHandler.h new file mode 100644 index 0000000000..17e4c2e9f0 --- /dev/null +++ b/cpp/src/qpid/cluster/McastFrameHandler.h @@ -0,0 +1,46 @@ +#ifndef QPID_CLUSTER_MCASTFRAMEHANDLER_H +#define QPID_CLUSTER_MCASTFRAMEHANDLER_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/cluster/types.h" +#include "qpid/cluster/Multicaster.h" +#include "qpid/framing/FrameHandler.h" + +namespace qpid { +namespace cluster { + +/** + * A frame handler that multicasts frames as CONTROL events. + */ +class McastFrameHandler : public framing::FrameHandler +{ + public: + McastFrameHandler(Multicaster& m, const ConnectionId& cid) : mcast(m), connection(cid) {} + void handle(framing::AMQFrame& frame) { mcast.mcastControl(frame, connection); } + private: + Multicaster& mcast; + ConnectionId connection; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_MCASTFRAMEHANDLER_H*/ diff --git a/cpp/src/qpid/cluster/MemberSet.cpp b/cpp/src/qpid/cluster/MemberSet.cpp new file mode 100644 index 0000000000..5dc148609f --- /dev/null +++ b/cpp/src/qpid/cluster/MemberSet.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 "MemberSet.h" +#include <ostream> +#include <algorithm> + +namespace qpid { +namespace cluster { + +MemberSet decodeMemberSet(const std::string& s) { + MemberSet set; + for (std::string::const_iterator i = s.begin(); i < s.end(); i += 8) { + assert(size_t(i-s.begin())+8 <= s.size()); + set.insert(MemberId(std::string(i, i+8))); + } + return set; +} + +MemberSet intersection(const MemberSet& a, const MemberSet& b) +{ + MemberSet intersection; + std::set_intersection(a.begin(), a.end(), + b.begin(), b.end(), + std::inserter(intersection, intersection.begin())); + return intersection; + +} + +std::ostream& operator<<(std::ostream& o, const MemberSet& ms) { + copy(ms.begin(), ms.end(), std::ostream_iterator<MemberId>(o, " ")); + return o; +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/MemberSet.h b/cpp/src/qpid/cluster/MemberSet.h new file mode 100644 index 0000000000..df3df7c319 --- /dev/null +++ b/cpp/src/qpid/cluster/MemberSet.h @@ -0,0 +1,43 @@ +#ifndef QPID_CLUSTER_MEMBERSET_H +#define QPID_CLUSTER_MEMBERSET_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 "types.h" +#include <set> +#include <iosfwd> + +namespace qpid { +namespace cluster { + +typedef std::set<MemberId> MemberSet; + +MemberSet decodeMemberSet(const std::string&); + +MemberSet intersection(const MemberSet& a, const MemberSet& b); + +std::ostream& operator<<(std::ostream& o, const MemberSet& ms); + + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_MEMBERSET_H*/ diff --git a/cpp/src/qpid/cluster/Multicaster.cpp b/cpp/src/qpid/cluster/Multicaster.cpp new file mode 100644 index 0000000000..229d7edb1e --- /dev/null +++ b/cpp/src/qpid/cluster/Multicaster.cpp @@ -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. + * + */ + +#include "qpid/cluster/Multicaster.h" +#include "qpid/cluster/Cpg.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/AMQFrame.h" + +namespace qpid { +namespace cluster { + +Multicaster::Multicaster(Cpg& cpg_, + const boost::shared_ptr<sys::Poller>& poller, + boost::function<void()> onError_) : + onError(onError_), cpg(cpg_), + queue(boost::bind(&Multicaster::sendMcast, this, _1), poller), + ready(false) +{ + queue.start(); +} + +void Multicaster::mcastControl(const framing::AMQBody& body, const ConnectionId& id) { + mcast(Event::control(body, id)); +} + +void Multicaster::mcastControl(const framing::AMQFrame& frame, const ConnectionId& id) { + mcast(Event::control(frame, id)); +} + +void Multicaster::mcastBuffer(const char* data, size_t size, const ConnectionId& id) { + Event e(DATA, id, size); + memcpy(e.getData(), data, size); + mcast(e); +} + +void Multicaster::mcast(const Event& e) { + { + sys::Mutex::ScopedLock l(lock); + if (!ready) { + if (e.isConnection()) holdingQueue.push_back(e); + else { + iovec iov = e.toIovec(); + // FIXME aconway 2009-11-23: configurable retry --cluster-retry + if (!cpg.mcast(&iov, 1)) + throw Exception("CPG flow control error during initialization"); + QPID_LOG(trace, "MCAST (direct) " << e); + } + return; + } + } + QPID_LOG(trace, "MCAST " << e); + queue.push(e); +} + + +Multicaster::PollableEventQueue::Batch::const_iterator Multicaster::sendMcast(const PollableEventQueue::Batch& values) { + try { + PollableEventQueue::Batch::const_iterator i = values.begin(); + while( i != values.end()) { + iovec iov = i->toIovec(); + if (!cpg.mcast(&iov, 1)) { + // cpg didn't send because of CPG flow control. + break; + } + ++i; + } + return i; + } + catch (const std::exception& e) { + QPID_LOG(critical, "Multicast error: " << e.what()); + queue.stop(); + onError(); + return values.end(); + } +} + +void Multicaster::setReady() { + sys::Mutex::ScopedLock l(lock); + ready = true; + std::for_each(holdingQueue.begin(), holdingQueue.end(), boost::bind(&Multicaster::mcast, this, _1)); + holdingQueue.clear(); +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/Multicaster.h b/cpp/src/qpid/cluster/Multicaster.h new file mode 100644 index 0000000000..2db84a9ce0 --- /dev/null +++ b/cpp/src/qpid/cluster/Multicaster.h @@ -0,0 +1,87 @@ +#ifndef QPID_CLUSTER_MULTICASTER_H +#define QPID_CLUSTER_MULTICASTER_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/cluster/types.h" +#include "qpid/cluster/Event.h" +#include "qpid/sys/PollableQueue.h" +#include "qpid/sys/Mutex.h" +#include <boost/shared_ptr.hpp> +#include <deque> + +namespace qpid { + +namespace sys { +class Poller; +} + +namespace cluster { + +class Cpg; + +/** + * Multicast to the cluster. Shared, thread safe object. + * + * Runs in two modes; + * + * initializing: Hold connection mcast events. Multicast cluster + * events directly in the calling thread. This mode is used before + * joining the cluster where the poller may not yet be active and we + * want to hold any connection traffic till we join. + * + * ready: normal operation. Queues all mcasts on a pollable queue, + * multicasts connection and cluster events. + */ +class Multicaster +{ + public: + /** Starts in initializing mode. */ + Multicaster(Cpg& cpg_, + const boost::shared_ptr<sys::Poller>&, + boost::function<void()> onError + ); + void mcastControl(const framing::AMQBody& controlBody, const ConnectionId&); + void mcastControl(const framing::AMQFrame& controlFrame, const ConnectionId&); + void mcastBuffer(const char*, size_t, const ConnectionId&); + void mcast(const Event& e); + + /** Switch to ready mode. */ + void setReady(); + + private: + typedef sys::PollableQueue<Event> PollableEventQueue; + typedef std::deque<Event> PlainEventQueue; + + PollableEventQueue::Batch::const_iterator sendMcast(const PollableEventQueue::Batch& ); + + sys::Mutex lock; + boost::function<void()> onError; + Cpg& cpg; + PollableEventQueue queue; + bool ready; + PlainEventQueue holdingQueue; + std::vector<struct ::iovec> ioVector; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_MULTICASTER_H*/ diff --git a/cpp/src/qpid/cluster/ShadowConnectionOutputHandler.h b/cpp/src/qpid/cluster/NoOpConnectionOutputHandler.h index 6d429535e6..566a82476e 100644 --- a/cpp/src/qpid/cluster/ShadowConnectionOutputHandler.h +++ b/cpp/src/qpid/cluster/NoOpConnectionOutputHandler.h @@ -1,5 +1,5 @@ -#ifndef QPID_CLUSTER_SHADOWCONNECTIONOUTPUTHANDLER_H -#define QPID_CLUSTER_SHADOWCONNECTIONOUTPUTHANDLER_H +#ifndef QPID_CLUSTER_NOOPCONNECTIONOUTPUTHANDLER_H +#define QPID_CLUSTER_NOOPCONNECTIONOUTPUTHANDLER_H /* * @@ -30,17 +30,18 @@ namespace framing { class AMQFrame; } namespace cluster { /** - * Output handler for frames sent to shadow connections. - * Simply discards frames. + * Output handler shadow connections, simply discards frames. */ -class ShadowConnectionOutputHandler : public sys::ConnectionOutputHandler +class NoOpConnectionOutputHandler : public sys::ConnectionOutputHandler { public: virtual void send(framing::AMQFrame&) {} virtual void close() {} + virtual void abort() {} virtual void activateOutput() {} + virtual void giveReadCredit(int32_t) {} }; }} // namespace qpid::cluster -#endif /*!QPID_CLUSTER_SHADOWCONNECTIONOUTPUTHANDLER_H*/ +#endif /*!QPID_CLUSTER_NOOPCONNECTIONOUTPUTHANDLER_H*/ diff --git a/cpp/src/qpid/cluster/Numbering.h b/cpp/src/qpid/cluster/Numbering.h new file mode 100644 index 0000000000..2d2d931384 --- /dev/null +++ b/cpp/src/qpid/cluster/Numbering.h @@ -0,0 +1,70 @@ +#ifndef QPID_CLUSTER_NUMBERING_H +#define QPID_CLUSTER_NUMBERING_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 <vector> + +namespace qpid { +namespace cluster { + +/** + * A set of numbered T, with two way mapping number->T T->number + * Used to construct numberings of objects by code sending and receiving updates. + */ +template <class T> class Numbering +{ + public: + size_t size() const { return byNumber.size(); } + + size_t add(const T& t) { + size_t n = (*this)[t]; // Already in the set? + if (n == size()) { + byObject[t] = n; + byNumber.push_back(t); + } + return n; + } + + void clear() { byObject.clear(); byNumber.clear(); } + + /**@return object at index n or T() if n > size() */ + T operator[](size_t n) const { return(n < size()) ? byNumber[n] : T(); } + + /**@return index of t or size() if t is not in the map */ + size_t operator[](const T& t) const { + typename Map::const_iterator i = byObject.find(t); + return (i != byObject.end()) ? i->second : size(); + } + + bool contains(const T& t) const { return (*this)[t] == size(); } + + private: + typedef std::map<T, size_t> Map; + Map byObject; + std::vector<T> byNumber; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_NUMBERING_H*/ diff --git a/cpp/src/qpid/cluster/OutputInterceptor.cpp b/cpp/src/qpid/cluster/OutputInterceptor.cpp new file mode 100644 index 0000000000..cb75fe5561 --- /dev/null +++ b/cpp/src/qpid/cluster/OutputInterceptor.cpp @@ -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. + * + */ +#include "qpid/cluster/OutputInterceptor.h" +#include "qpid/cluster/Connection.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/framing/ClusterConnectionDeliverDoOutputBody.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/log/Statement.h" +#include <boost/current_function.hpp> + + +namespace qpid { +namespace cluster { + +using namespace framing; +using namespace std; + +NoOpConnectionOutputHandler OutputInterceptor::discardHandler; + +OutputInterceptor::OutputInterceptor(Connection& p, sys::ConnectionOutputHandler& h) + : parent(p), closing(false), next(&h), sendMax(1), sent(0), sentDoOutput(false) +{} + +void OutputInterceptor::send(framing::AMQFrame& f) { + sys::Mutex::ScopedLock l(lock); + next->send(f); +} + +void OutputInterceptor::activateOutput() { + if (parent.isCatchUp()) { + sys::Mutex::ScopedLock l(lock); + next->activateOutput(); + } + else + sendDoOutput(sendMax); +} + +void OutputInterceptor::abort() { + sys::Mutex::ScopedLock l(lock); + if (parent.isLocal()) { + next->abort(); + } +} + +void OutputInterceptor::giveReadCredit(int32_t credit) { + sys::Mutex::ScopedLock l(lock); + next->giveReadCredit(credit); +} + +// Called in write thread when the IO layer has no more data to write. +// We do nothing in the write thread, we run doOutput only on delivery +// of doOutput requests. +bool OutputInterceptor::doOutput() { return false; } + +// Send output up to limit, calculate new limit. +void OutputInterceptor::deliverDoOutput(uint32_t limit) { + sentDoOutput = false; + sendMax = limit; + size_t newLimit = limit; + if (parent.isLocal()) { + size_t buffered = getBuffered(); + if (buffered == 0 && sent == sendMax) // Could have sent more, increase the limit. + newLimit = sendMax*2; + else if (buffered > 0 && sent > 1) // Data left unsent, reduce the limit. + newLimit = sent-1; + } + sent = 0; + while (sent < limit && parent.getBrokerConnection().doOutput()) + ++sent; + if (sent == limit) sendDoOutput(newLimit); +} + +void OutputInterceptor::sendDoOutput(size_t newLimit) { + if (parent.isLocal() && !sentDoOutput && !closing) { + sentDoOutput = true; + parent.getCluster().getMulticast().mcastControl( + ClusterConnectionDeliverDoOutputBody(ProtocolVersion(), newLimit), + parent.getId()); + } +} + +void OutputInterceptor::closeOutput() { + sys::Mutex::ScopedLock l(lock); + closing = true; + next = &discardHandler; +} + +void OutputInterceptor::close() { + sys::Mutex::ScopedLock l(lock); + next->close(); +} + +size_t OutputInterceptor::getBuffered() const { + sys::Mutex::ScopedLock l(lock); + return next->getBuffered(); +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/OutputInterceptor.h b/cpp/src/qpid/cluster/OutputInterceptor.h new file mode 100644 index 0000000000..65bd82a4fc --- /dev/null +++ b/cpp/src/qpid/cluster/OutputInterceptor.h @@ -0,0 +1,79 @@ +#ifndef QPID_CLUSTER_OUTPUTINTERCEPTOR_H +#define QPID_CLUSTER_OUTPUTINTERCEPTOR_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/cluster/NoOpConnectionOutputHandler.h" +#include "qpid/sys/ConnectionOutputHandler.h" +#include "qpid/sys/Mutex.h" +#include "qpid/broker/ConnectionFactory.h" +#include <boost/function.hpp> + +namespace qpid { +namespace framing { class AMQFrame; } +namespace cluster { + +class Connection; + +/** + * Interceptor for connection OutputHandler, manages outgoing message replication. + */ +class OutputInterceptor : public sys::ConnectionOutputHandler { + public: + OutputInterceptor(cluster::Connection& p, sys::ConnectionOutputHandler& h); + + // sys::ConnectionOutputHandler functions + void send(framing::AMQFrame& f); + void abort(); + void activateOutput(); + void giveReadCredit(int32_t); + void close(); + size_t getBuffered() const; + + // Delivery point for doOutput requests. + void deliverDoOutput(uint32_t limit); + // Intercept doOutput requests on Connection. + bool doOutput(); + + void closeOutput(); + + uint32_t getSendMax() const { return sendMax; } + void setSendMax(uint32_t sendMax_) { sendMax=sendMax_; } + + cluster::Connection& parent; + + private: + typedef sys::Mutex::ScopedLock Locker; + + void sendDoOutput(size_t newLimit); + + mutable sys::Mutex lock; + bool closing; + sys::ConnectionOutputHandler* next; + static NoOpConnectionOutputHandler discardHandler; + uint32_t sendMax, sent; + bool sentDoOutput; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_OUTPUTINTERCEPTOR_H*/ diff --git a/cpp/src/qpid/cluster/PollableCondition.cpp b/cpp/src/qpid/cluster/PollableCondition.cpp deleted file mode 100644 index eecf95ff8d..0000000000 --- a/cpp/src/qpid/cluster/PollableCondition.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef QPID_SYS_LINUX_POLLABLECONDITION_CPP -#define QPID_SYS_LINUX_POLLABLECONDITION_CPP - -/* - * - * 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. - * - */ - -// FIXME aconway 2008-08-11: this could be of more general interest, -// move to common lib. -// - -#include "qpid/sys/posix/PrivatePosix.h" -#include "qpid/cluster/PollableCondition.h" -#include "qpid/Exception.h" - -#include <unistd.h> -#include <fcntl.h> - -namespace qpid { -namespace cluster { - -PollableCondition::PollableCondition() : IOHandle(new sys::IOHandlePrivate) { - int fds[2]; - if (::pipe(fds) == -1) - throw ErrnoException(QPID_MSG("Can't create PollableCondition")); - impl->fd = fds[0]; - writeFd = fds[1]; - if (::fcntl(impl->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")); -} - -bool PollableCondition::clear() { - char buf[256]; - ssize_t n; - bool wasSet = false; - while ((n = ::read(impl->fd, buf, sizeof(buf))) > 0) - wasSet = true; - if (n == -1 && errno != EAGAIN) throw ErrnoException(QPID_MSG("Error clearing PollableCondition")); - return wasSet; -} - -void PollableCondition::set() { - static const char dummy=0; - ssize_t n = ::write(writeFd, &dummy, 1); - if (n == -1 && errno != EAGAIN) throw ErrnoException("Error setting PollableCondition"); -} - - -#if 0 -// FIXME aconway 2008-08-12: More efficient Linux implementation using -// eventfd system call. Do a configure.ac test to enable this when -// eventfd is available. - -#include <sys/eventfd.h> - -namespace qpid { -namespace cluster { - -PollableCondition::PollableCondition() : IOHandle(new sys::IOHandlePrivate) { - impl->fd = ::eventfd(0, 0); - if (impl->fd < 0) throw ErrnoException("conditionfd() failed"); -} - -bool PollableCondition::clear() { - char buf[8]; - ssize_t n = ::read(impl->fd, buf, 8); - if (n != 8) throw ErrnoException("read failed on conditionfd"); - return *reinterpret_cast<uint64_t*>(buf); -} - -void PollableCondition::set() { - static const uint64_t value=1; - ssize_t n = ::write(impl->fd, reinterpret_cast<const void*>(&value), 8); - if (n != 8) throw ErrnoException("write failed on conditionfd"); -} - -#endif - -}} // namespace qpid::cluster - -#endif /*!QPID_SYS_LINUX_POLLABLECONDITION_CPP*/ diff --git a/cpp/src/qpid/cluster/PollableQueue.h b/cpp/src/qpid/cluster/PollableQueue.h index 0bba2ba790..65f615d8b6 100644 --- a/cpp/src/qpid/cluster/PollableQueue.h +++ b/cpp/src/qpid/cluster/PollableQueue.h @@ -22,78 +22,53 @@ * */ -#include "qpid/cluster/PollableCondition.h" -#include "qpid/sys/Dispatcher.h" -#include "qpid/sys/Mutex.h" -#include <boost/function.hpp> -#include <boost/bind.hpp> -#include <deque> +#include "qpid/sys/PollableQueue.h" +#include <qpid/log/Statement.h> namespace qpid { - -namespace sys { class Poller; } - namespace cluster { -// FIXME aconway 2008-08-11: this could be of more general interest, -// move to common lib. - /** - * A queue that can be polled by sys::Poller. Any thread can push to - * the queue, on wakeup the poller thread processes all items on the - * queue by passing them to a callback in a batch. + * More convenient version of PollableQueue that handles iterating + * over the batch and error handling. */ -template <class T> -class PollableQueue { - typedef std::deque<T> Queue; - +template <class T> class PollableQueue : public sys::PollableQueue<T> { public: - typedef typename Queue::iterator iterator; - - /** Callback to process a range of items. */ - typedef boost::function<void (const iterator&, const iterator&)> Callback; - - /** When the queue is selected by the poller, values are passed to callback cb. */ - explicit PollableQueue(const Callback& cb); - - /** Push a value onto the queue. Thread safe */ - void push(const T& t) { ScopedLock l(lock); queue.push_back(t); condition.set(); } + typedef boost::function<void (const T&)> Callback; + typedef boost::function<void()> ErrorCallback; + + PollableQueue(Callback f, ErrorCallback err, const std::string& msg, + const boost::shared_ptr<sys::Poller>& poller) + : sys::PollableQueue<T>(boost::bind(&PollableQueue<T>::handleBatch, this, _1), + poller), + callback(f), error(err), message(msg) + {} + + typename sys::PollableQueue<T>::Batch::const_iterator + handleBatch(const typename sys::PollableQueue<T>::Batch& values) { + try { + typename sys::PollableQueue<T>::Batch::const_iterator i = values.begin(); + while (i != values.end() && !this->isStopped()) { + callback(*i); + ++i; + } + return i; + } + catch (const std::exception& e) { + QPID_LOG(error, message << ": " << e.what()); + this->stop(); + error(); + return values.end(); + } + } - /** Start polling. */ - void start(const boost::shared_ptr<sys::Poller>& poller) { handle.startWatch(poller); } - - /** Stop polling. */ - void stop() { handle.stopWatch(); } - private: - typedef sys::Mutex::ScopedLock ScopedLock; - typedef sys::Mutex::ScopedUnlock ScopedUnlock; - - void dispatch(sys::DispatchHandle&); - - sys::Mutex lock; Callback callback; - PollableCondition condition; - sys::DispatchHandle handle; - Queue queue; - Queue batch; + ErrorCallback error; + std::string message; }; -template <class T> PollableQueue<T>::PollableQueue(const Callback& cb) // FIXME aconway 2008-08-12: - : callback(cb), - handle(condition, boost::bind(&PollableQueue<T>::dispatch, this, _1), 0, 0) -{} - -template <class T> void PollableQueue<T>::dispatch(sys::DispatchHandle& h) { - ScopedLock l(lock); // Lock for concurrent push() - batch.clear(); - batch.swap(queue); - condition.clear(); - ScopedUnlock u(lock); - callback(batch.begin(), batch.end()); // Process the batch outside the lock. - h.rewatch(); -} - + }} // namespace qpid::cluster #endif /*!QPID_CLUSTER_POLLABLEQUEUE_H*/ diff --git a/cpp/src/qpid/cluster/PollerDispatch.cpp b/cpp/src/qpid/cluster/PollerDispatch.cpp new file mode 100644 index 0000000000..a839ef863b --- /dev/null +++ b/cpp/src/qpid/cluster/PollerDispatch.cpp @@ -0,0 +1,67 @@ +/* + * + * 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/cluster/PollerDispatch.h" + +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace cluster { + +PollerDispatch::PollerDispatch(Cpg& c, boost::shared_ptr<sys::Poller> p, + boost::function<void()> e) + : cpg(c), poller(p), onError(e), + dispatchHandle(cpg, + boost::bind(&PollerDispatch::dispatch, this, _1), // read + 0, // write + boost::bind(&PollerDispatch::disconnect, this, _1) // disconnect + ), + started(false) +{} + +PollerDispatch::~PollerDispatch() { + if (started) + dispatchHandle.stopWatch(); +} + +void PollerDispatch::start() { + dispatchHandle.startWatch(poller); + started = true; +} + +// Entry point: called by IO to dispatch CPG events. +void PollerDispatch::dispatch(sys::DispatchHandle& h) { + try { + cpg.dispatchAll(); + h.rewatch(); + } catch (const std::exception& e) { + QPID_LOG(critical, "Error in cluster dispatch: " << e.what()); + onError(); + } +} + +// Entry point: called if disconnected from CPG. +void PollerDispatch::disconnect(sys::DispatchHandle& ) { + QPID_LOG(critical, "Disconnected from cluster"); + onError(); +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/PollerDispatch.h b/cpp/src/qpid/cluster/PollerDispatch.h new file mode 100644 index 0000000000..63801e0de9 --- /dev/null +++ b/cpp/src/qpid/cluster/PollerDispatch.h @@ -0,0 +1,60 @@ +#ifndef QPID_CLUSTER_POLLERDISPATCH_H +#define QPID_CLUSTER_POLLERDISPATCH_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/cluster/Cpg.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/DispatchHandle.h" +#include <boost/function.hpp> + +namespace qpid { +namespace cluster { + +/** + * Dispatch CPG events via the poller. + */ +class PollerDispatch { + public: + PollerDispatch(Cpg&, boost::shared_ptr<sys::Poller> poller, + boost::function<void()> onError) ; + + ~PollerDispatch(); + + void start(); + + private: + // Poller callbacks + void dispatch(sys::DispatchHandle&); // Dispatch CPG events. + void disconnect(sys::DispatchHandle&); // CPG was disconnected + + Cpg& cpg; + boost::shared_ptr<sys::Poller> poller; + boost::function<void()> onError; + sys::DispatchHandleRef dispatchHandle; + bool started; + + +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_POLLERDISPATCH_H*/ diff --git a/cpp/src/qpid/cluster/ProxyInputHandler.h b/cpp/src/qpid/cluster/ProxyInputHandler.h new file mode 100644 index 0000000000..228f8d092d --- /dev/null +++ b/cpp/src/qpid/cluster/ProxyInputHandler.h @@ -0,0 +1,57 @@ +#ifndef QPID_CLUSTER_PROXYINPUTHANDLER_H +#define QPID_CLUSTER_PROXYINPUTHANDLER_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/ConnectionInputHandler.h" +#include <boost/intrusive_ptr.hpp> + +namespace qpid { + +namespace framing { class AMQFrame; } + +namespace cluster { + +/** + * Proxies ConnectionInputHandler functions and ensures target.closed() + * is called, on deletion if not before. + */ +class ProxyInputHandler : public sys::ConnectionInputHandler +{ + public: + ProxyInputHandler(boost::intrusive_ptr<cluster::Connection> t) : target(t) {} + ~ProxyInputHandler() { closed(); } + + void received(framing::AMQFrame& f) { target->received(f); } + void closed() { if (target) target->closed(); target = 0; } + void idleOut() { target->idleOut(); } + void idleIn() { target->idleIn(); } + bool doOutput() { return target->doOutput(); } + bool hasOutput() { return target->hasOutput(); } + + private: + boost::intrusive_ptr<cluster::Connection> target; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_PROXYINPUTHANDLER_H*/ diff --git a/cpp/src/qpid/sys/Condition.h b/cpp/src/qpid/cluster/Quorum.h index 961c15e1ee..bbfa473f94 100644 --- a/cpp/src/qpid/sys/Condition.h +++ b/cpp/src/qpid/cluster/Quorum.h @@ -1,5 +1,5 @@ -#ifndef _sys_Condition_h -#define _sys_Condition_h +#ifndef QPID_CLUSTER_QUORUM_H +#define QPID_CLUSTER_QUORUM_H /* * @@ -21,11 +21,12 @@ * under the License. * */ +#include "config.h" -#ifdef USE_APR_PLATFORM -#include "apr/Condition.h" +#if HAVE_LIBCMAN_H +#include "qpid/cluster/Quorum_cman.h" #else -#include "posix/Condition.h" +#include "qpid/cluster/Quorum_null.h" #endif -#endif /*!_sys_Condition_h*/ +#endif /*!QPID_CLUSTER_QUORUM_H*/ diff --git a/cpp/src/qpid/cluster/Quorum_cman.cpp b/cpp/src/qpid/cluster/Quorum_cman.cpp new file mode 100644 index 0000000000..507d9649b9 --- /dev/null +++ b/cpp/src/qpid/cluster/Quorum_cman.cpp @@ -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. + * + */ + +#include "qpid/cluster/Quorum_cman.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/log/Statement.h" +#include "qpid/Options.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/posix/PrivatePosix.h" + +namespace qpid { +namespace cluster { + +namespace { + +boost::function<void()> errorFn; + +void cmanCallbackFn(cman_handle_t handle, void */*privdata*/, int reason, int /*arg*/) { + if (reason == CMAN_REASON_STATECHANGE && !cman_is_quorate(handle)) { + QPID_LOG(critical, "Lost contact with cluster quorum."); + if (errorFn) errorFn(); + cman_stop_notification(handle); + } +} +} + +Quorum::Quorum(boost::function<void()> err) : enable(false), cman(0), cmanFd(0) { + errorFn = err; +} + +Quorum::~Quorum() { + dispatchHandle.reset(); + if (cman) cman_finish(cman); +} + +void Quorum::start(boost::shared_ptr<sys::Poller> p) { + poller = p; + enable = true; + QPID_LOG(debug, "Connecting to quorum service."); + cman = cman_init(0); + if (cman == 0) throw ErrnoException("Can't connect to cman service"); + if (!cman_is_quorate(cman)) { + QPID_LOG(notice, "Waiting for cluster quorum."); + while(!cman_is_quorate(cman)) sys::sleep(5); + } + int err = cman_start_notification(cman, cmanCallbackFn); + if (err != 0) throw ErrnoException("Can't register for cman notifications"); + watch(getFd()); +} + +void Quorum::watch(int fd) { + cmanFd = fd; + dispatchHandle.reset( + new sys::DispatchHandleRef( + sys::PosixIOHandle(cmanFd), + boost::bind(&Quorum::dispatch, this, _1), // read + 0, // write + boost::bind(&Quorum::disconnect, this, _1) // disconnect + )); + dispatchHandle->startWatch(poller); +} + +int Quorum::getFd() { + int fd = cman_get_fd(cman); + if (fd == 0) throw ErrnoException("Can't get cman file descriptor"); + return fd; +} + +void Quorum::dispatch(sys::DispatchHandle&) { + try { + cman_dispatch(cman, CMAN_DISPATCH_ALL); + int fd = getFd(); + if (fd != cmanFd) watch(fd); + } catch (const std::exception& e) { + QPID_LOG(critical, "Error in quorum dispatch: " << e.what()); + errorFn(); + } +} + +void Quorum::disconnect(sys::DispatchHandle&) { + QPID_LOG(critical, "Disconnected from quorum service"); + errorFn(); +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/Quorum_cman.h b/cpp/src/qpid/cluster/Quorum_cman.h new file mode 100644 index 0000000000..130f1baf64 --- /dev/null +++ b/cpp/src/qpid/cluster/Quorum_cman.h @@ -0,0 +1,64 @@ +#ifndef QPID_CLUSTER_QUORUM_CMAN_H +#define QPID_CLUSTER_QUORUM_CMAN_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/DispatchHandle.h> +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> +#include <memory> + +extern "C" { +#include <libcman.h> +} + +namespace qpid { +namespace sys { +class Poller; +} + +namespace cluster { +class Cluster; + +class Quorum { + public: + Quorum(boost::function<void ()> onError); + ~Quorum(); + void start(boost::shared_ptr<sys::Poller>); + + private: + void dispatch(sys::DispatchHandle&); + void disconnect(sys::DispatchHandle&); + int getFd(); + void watch(int fd); + + bool enable; + cman_handle_t cman; + int cmanFd; + std::auto_ptr<sys::DispatchHandleRef> dispatchHandle; + boost::shared_ptr<sys::Poller> poller; +}; + + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_QUORUM_CMAN_H*/ diff --git a/cpp/src/qpid/client/FutureResult.h b/cpp/src/qpid/cluster/Quorum_null.h index e97d80476d..dc27f0a43b 100644 --- a/cpp/src/qpid/client/FutureResult.h +++ b/cpp/src/qpid/cluster/Quorum_null.h @@ -1,3 +1,6 @@ +#ifndef QPID_CLUSTER_QUORUM_NULL_H +#define QPID_CLUSTER_QUORUM_NULL_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -19,29 +22,21 @@ * */ -#ifndef _FutureResult_ -#define _FutureResult_ - -#include <string> -#include "qpid/framing/amqp_framing.h" -#include "FutureCompletion.h" +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> namespace qpid { -namespace client { +namespace cluster { +class Cluster; -class SessionImpl; +/** Null implementation of quorum. */ -///@internal -class FutureResult : public FutureCompletion -{ - std::string result; -public: - const std::string& getResult(SessionImpl& session) const; - void received(const std::string& result); +class Quorum { + public: + Quorum(boost::function<void ()>) {} + void start(boost::shared_ptr<sys::Poller>) {} }; -}} - - +#endif /*!QPID_CLUSTER_QUORUM_NULL_H*/ -#endif +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/RetractClient.cpp b/cpp/src/qpid/cluster/RetractClient.cpp new file mode 100644 index 0000000000..7d9f52fc39 --- /dev/null +++ b/cpp/src/qpid/cluster/RetractClient.cpp @@ -0,0 +1,61 @@ +/* + * + * 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/cluster/RetractClient.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/framing/ClusterConnectionRetractOfferBody.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace cluster { + +using namespace framing; + +namespace { + +struct AutoClose { + client::Connection& connection; + AutoClose(client::Connection& c) : connection(c) {} + ~AutoClose() { connection.close(); } +}; +} + +RetractClient::RetractClient(const Url& u, const client::ConnectionSettings& cs) + : url(u), connectionSettings(cs) +{} + +RetractClient::~RetractClient() { delete this; } + + +void RetractClient::run() { + try { + client::Connection c = UpdateClient::catchUpConnection(); + c.open(url, connectionSettings); + AutoClose ac(c); + AMQFrame retract((ClusterConnectionRetractOfferBody())); + client::ConnectionAccess::getImpl(c)->handle(retract); + } catch (const std::exception& e) { + QPID_LOG(error, " while retracting retract to " << url << ": " << e.what()); + } +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/RetractClient.h b/cpp/src/qpid/cluster/RetractClient.h new file mode 100644 index 0000000000..fb896197cc --- /dev/null +++ b/cpp/src/qpid/cluster/RetractClient.h @@ -0,0 +1,49 @@ +#ifndef QPID_CLUSTER_RETRACTCLIENT_H +#define QPID_CLUSTER_RETRACTCLIENT_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 "qpid/sys/Runnable.h" + + +namespace qpid { +namespace cluster { + +/** + * A client that retracts an offer to a remote broker using AMQP. @see UpdateClient + */ +class RetractClient : public sys::Runnable { + public: + + RetractClient(const Url&, const client::ConnectionSettings&); + ~RetractClient(); + void run(); // Will delete this when finished. + + private: + Url url; + client::ConnectionSettings connectionSettings; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_RETRACTCLIENT_H*/ diff --git a/cpp/src/qpid/cluster/StoreStatus.cpp b/cpp/src/qpid/cluster/StoreStatus.cpp new file mode 100644 index 0000000000..6e412c23f7 --- /dev/null +++ b/cpp/src/qpid/cluster/StoreStatus.cpp @@ -0,0 +1,110 @@ +/* + * + * 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 "StoreStatus.h" +#include "qpid/Exception.h" +#include <boost/filesystem/path.hpp> +#include <boost/filesystem/fstream.hpp> +#include <boost/filesystem/operations.hpp> +#include <fstream> + +namespace qpid { +namespace cluster { + +using framing::Uuid; +using namespace framing::cluster; +namespace fs=boost::filesystem; +using std::ostream; + +StoreStatus::StoreStatus(const std::string& d) + : state(STORE_STATE_NO_STORE), dataDir(d) +{} + +namespace { + +const char* SUBDIR="cluster"; +const char* CLUSTER_ID_FILE="cluster.uuid"; +const char* SHUTDOWN_ID_FILE="shutdown.uuid"; + +Uuid loadUuid(const fs::path& path) { + Uuid ret; + if (exists(path)) { + fs::ifstream i(path); + i >> ret; + } + return ret; +} + +void saveUuid(const fs::path& path, const Uuid& uuid) { + fs::ofstream o(path); + o << uuid; +} + +} // namespace + + +void StoreStatus::load() { + fs::path dir = fs::path(dataDir, fs::native)/SUBDIR; + create_directory(dir); + clusterId = loadUuid(dir/CLUSTER_ID_FILE); + shutdownId = loadUuid(dir/SHUTDOWN_ID_FILE); + + if (clusterId && shutdownId) state = STORE_STATE_CLEAN_STORE; + else if (clusterId) state = STORE_STATE_DIRTY_STORE; + else state = STORE_STATE_EMPTY_STORE; +} + +void StoreStatus::save() { + fs::path dir = fs::path(dataDir, fs::native)/SUBDIR; + create_directory(dir); + saveUuid(dir/CLUSTER_ID_FILE, clusterId); + saveUuid(dir/SHUTDOWN_ID_FILE, shutdownId); +} + +void StoreStatus::dirty(const Uuid& clusterId_) { + clusterId = clusterId_; + shutdownId = Uuid(); + state = STORE_STATE_DIRTY_STORE; + save(); +} + +void StoreStatus::clean(const Uuid& shutdownId_) { + state = STORE_STATE_CLEAN_STORE; + shutdownId = shutdownId_; + save(); +} + +ostream& operator<<(ostream& o, const StoreStatus& s) { + switch (s.getState()) { + case STORE_STATE_NO_STORE: o << "no store"; break; + case STORE_STATE_EMPTY_STORE: o << "empty store"; break; + case STORE_STATE_DIRTY_STORE: + o << "dirty store, cluster-id=" << s.getClusterId(); + break; + case STORE_STATE_CLEAN_STORE: + o << "clean store, cluster-id=" << s.getClusterId() + << " shutdown-id=" << s.getShutdownId(); + break; + } + return o; +} + +}} // namespace qpid::cluster + diff --git a/cpp/src/qpid/cluster/StoreStatus.h b/cpp/src/qpid/cluster/StoreStatus.h new file mode 100644 index 0000000000..522020ed69 --- /dev/null +++ b/cpp/src/qpid/cluster/StoreStatus.h @@ -0,0 +1,64 @@ +#ifndef QPID_CLUSTER_STORESTATE_H +#define QPID_CLUSTER_STORESTATE_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/framing/Uuid.h" +#include "qpid/framing/enum.h" +#include <iosfwd> + +namespace qpid { +namespace cluster { + +/** + * State of the store for cluster purposes. + */ +class StoreStatus +{ + public: + typedef framing::Uuid Uuid; + typedef framing::cluster::StoreState StoreState; + + StoreStatus(const std::string& dir); + + framing::cluster::StoreState getState() const { return state; } + const Uuid& getClusterId() const { return clusterId; } + const Uuid& getShutdownId() const { return shutdownId; } + + void dirty(const Uuid& start); // Start using the store. + void clean(const Uuid& stop); // Stop using the store. + + void load(); + void save(); + + bool hasStore() { return state != framing::cluster::STORE_STATE_NO_STORE; } + + private: + framing::cluster::StoreState state; + Uuid clusterId, shutdownId; + std::string dataDir; +}; + +std::ostream& operator<<(std::ostream&, const StoreStatus&); +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_STORESTATE_H*/ diff --git a/cpp/src/qpid/cluster/UpdateClient.cpp b/cpp/src/qpid/cluster/UpdateClient.cpp new file mode 100644 index 0000000000..b20cc907a2 --- /dev/null +++ b/cpp/src/qpid/cluster/UpdateClient.cpp @@ -0,0 +1,484 @@ +/* + * + * 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/cluster/UpdateClient.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/ClusterMap.h" +#include "qpid/cluster/Connection.h" +#include "qpid/cluster/Decoder.h" +#include "qpid/cluster/ExpiryPolicy.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/Future.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/SessionHandler.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/TxOpVisitor.h" +#include "qpid/broker/DtxAck.h" +#include "qpid/broker/TxAccept.h" +#include "qpid/broker/TxPublish.h" +#include "qpid/broker/RecoveredDequeue.h" +#include "qpid/broker/RecoveredEnqueue.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/ClusterConnectionMembershipBody.h" +#include "qpid/framing/ClusterConnectionShadowReadyBody.h" +#include "qpid/framing/ClusterConnectionSessionStateBody.h" +#include "qpid/framing/ClusterConnectionConsumerStateBody.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/framing/TypeCode.h" +#include "qpid/log/Statement.h" +#include "qpid/Url.h" +#include <boost/bind.hpp> +#include <boost/cast.hpp> +#include <algorithm> + +namespace qpid { +namespace cluster { + +using broker::Broker; +using broker::Exchange; +using broker::Queue; +using broker::QueueBinding; +using broker::Message; +using broker::SemanticState; + +using namespace framing; +namespace arg=client::arg; +using client::SessionBase_0_10Access; + +struct ClusterConnectionProxy : public AMQP_AllProxy::ClusterConnection { + ClusterConnectionProxy(client::Connection c) : + AMQP_AllProxy::ClusterConnection(*client::ConnectionAccess::getImpl(c)) {} + ClusterConnectionProxy(client::AsyncSession s) : + AMQP_AllProxy::ClusterConnection(SessionBase_0_10Access(s).get()->out) {} +}; + +// Create a connection with special version that marks it as a catch-up connection. +client::Connection UpdateClient::catchUpConnection() { + client::Connection c; + client::ConnectionAccess::setVersion(c, ProtocolVersion(0x80 , 0x80 + 10)); + return c; +} + +// Send a control body directly to the session. +void send(client::AsyncSession& s, const AMQBody& body) { + client::SessionBase_0_10Access sb(s); + sb.get()->send(body); +} + +// TODO aconway 2008-09-24: optimization: update connections/sessions in parallel. + +UpdateClient::UpdateClient(const MemberId& updater, const MemberId& updatee, const Url& url, + broker::Broker& broker, const ClusterMap& m, ExpiryPolicy& expiry_, + const Cluster::ConnectionVector& cons, Decoder& decoder_, + const boost::function<void()>& ok, + const boost::function<void(const std::exception&)>& fail, + const client::ConnectionSettings& cs +) + : updaterId(updater), updateeId(updatee), updateeUrl(url), updaterBroker(broker), map(m), + expiry(expiry_), connections(cons), decoder(decoder_), + connection(catchUpConnection()), shadowConnection(catchUpConnection()), + done(ok), failed(fail), connectionSettings(cs) +{} + +UpdateClient::~UpdateClient() {} + +// Reserved exchange/queue name for catch-up, avoid clashes with user queues/exchanges. +const std::string UpdateClient::UPDATE("qpid.cluster-update"); + +void UpdateClient::run() { + try { + connection.open(updateeUrl, connectionSettings); + session = connection.newSession(UPDATE); + update(); + done(); + } catch (const std::exception& e) { + failed(e); + } + delete this; +} + +void UpdateClient::update() { + QPID_LOG(debug, updaterId << " updating state to " << updateeId + << " at " << updateeUrl); + Broker& b = updaterBroker; + b.getExchanges().eachExchange(boost::bind(&UpdateClient::updateExchange, this, _1)); + b.getQueues().eachQueue(boost::bind(&UpdateClient::updateNonExclusiveQueue, this, _1)); + + // Update queue is used to transfer acquired messages that are no + // longer on their original queue. + session.queueDeclare(arg::queue=UPDATE, arg::autoDelete=true); + session.sync(); + std::for_each(connections.begin(), connections.end(), boost::bind(&UpdateClient::updateConnection, this, _1)); + session.queueDelete(arg::queue=UPDATE); + session.close(); + + // Update queue listeners: must come after sessions so consumerNumbering is populated. + b.getQueues().eachQueue(boost::bind(&UpdateClient::updateQueueListeners, this, _1)); + + ClusterConnectionProxy(session).expiryId(expiry.getId()); + ClusterConnectionMembershipBody membership; + map.toMethodBody(membership); + AMQFrame frame(membership); + client::ConnectionAccess::getImpl(connection)->handle(frame); + + connection.close(); + QPID_LOG(debug, updaterId << " update completed to " << updateeId + << " at " << updateeUrl << ": " << membership); +} + +namespace { +template <class T> std::string encode(const T& t) { + std::string encoded; + encoded.resize(t.encodedSize()); + framing::Buffer buf(const_cast<char*>(encoded.data()), encoded.size()); + t.encode(buf); + return encoded; +} +} // namespace + +void UpdateClient::updateExchange(const boost::shared_ptr<Exchange>& ex) { + QPID_LOG(debug, updaterId << " updating exchange " << ex->getName()); + ClusterConnectionProxy(session).exchange(encode(*ex)); +} + +/** Bind a queue to the update exchange and update messges to it + * setting the message possition as needed. + */ +class MessageUpdater { + std::string queue; + bool haveLastPos; + framing::SequenceNumber lastPos; + client::AsyncSession session; + ExpiryPolicy& expiry; + + public: + + MessageUpdater(const string& q, const client::AsyncSession s, ExpiryPolicy& expiry_) : queue(q), haveLastPos(false), session(s), expiry(expiry_) { + session.exchangeBind(queue, UpdateClient::UPDATE); + } + + ~MessageUpdater() { + try { + session.exchangeUnbind(queue, UpdateClient::UPDATE); + } + catch (const std::exception& e) { + // Don't throw in a destructor. + QPID_LOG(error, "Unbinding update queue " << queue << ": " << e.what()); + } + } + + + void updateQueuedMessage(const broker::QueuedMessage& message) { + // Send the queue position if necessary. + if (!haveLastPos || message.position - lastPos != 1) { + ClusterConnectionProxy(session).queuePosition(queue, message.position.getValue()-1); + haveLastPos = true; + } + lastPos = message.position; + + // Send the expiry ID if necessary. + if (message.payload->getProperties<DeliveryProperties>()->getTtl()) { + boost::optional<uint64_t> expiryId = expiry.getId(*message.payload); + if (!expiryId) return; // Message already expired, don't replicate. + ClusterConnectionProxy(session).expiryId(*expiryId); + } + + // We can't send a broker::Message via the normal client API, + // and it would be expensive to copy it into a client::Message + // so we go a bit under the client API covers here. + // + SessionBase_0_10Access sb(session); + // Disable client code that clears the delivery-properties.exchange + sb.get()->setDoClearDeliveryPropertiesExchange(false); + framing::MessageTransferBody transfer( + *message.payload->getFrames().as<framing::MessageTransferBody>()); + transfer.setDestination(UpdateClient::UPDATE); + + sb.get()->send(transfer, message.payload->getFrames(), + !message.payload->isContentReleased()); + if (message.payload->isContentReleased()){ + uint16_t maxFrameSize = sb.get()->getConnection()->getNegotiatedSettings().maxFrameSize; + uint16_t maxContentSize = maxFrameSize - AMQFrame::frameOverhead(); + bool morecontent = true; + for (uint64_t offset = 0; morecontent; offset += maxContentSize) + { + AMQFrame frame((AMQContentBody())); + morecontent = message.payload->getContentFrame(*(message.queue), frame, maxContentSize, offset); + sb.get()->sendRawFrame(frame); + } + } + } + + void updateMessage(const boost::intrusive_ptr<broker::Message>& message) { + updateQueuedMessage(broker::QueuedMessage(0, message, haveLastPos? lastPos.getValue()+1 : 1)); + } +}; + +void UpdateClient::updateQueue(client::AsyncSession& s, const boost::shared_ptr<Queue>& q) { + broker::Exchange::shared_ptr alternateExchange = q->getAlternateExchange(); + s.queueDeclare( + arg::queue = q->getName(), + arg::durable = q->isDurable(), + arg::autoDelete = q->isAutoDelete(), + arg::alternateExchange = alternateExchange ? alternateExchange->getName() : "", + arg::arguments = q->getSettings(), + arg::exclusive = q->hasExclusiveOwner() + ); + MessageUpdater updater(q->getName(), s, expiry); + q->eachMessage(boost::bind(&MessageUpdater::updateQueuedMessage, &updater, _1)); + q->eachBinding(boost::bind(&UpdateClient::updateBinding, this, s, q->getName(), _1)); + ClusterConnectionProxy(s).queuePosition(q->getName(), q->getPosition()); +} + +void UpdateClient::updateExclusiveQueue(const boost::shared_ptr<broker::Queue>& q) { + QPID_LOG(debug, updaterId << " updating exclusive queue " << q->getName() << " on " << shadowSession.getId()); + updateQueue(shadowSession, q); +} + +void UpdateClient::updateNonExclusiveQueue(const boost::shared_ptr<broker::Queue>& q) { + if (!q->hasExclusiveOwner()) { + QPID_LOG(debug, updaterId << " updating queue " << q->getName()); + updateQueue(session, q); + }//else queue will be updated as part of session state of owning session +} + +void UpdateClient::updateBinding(client::AsyncSession& s, const std::string& queue, const QueueBinding& binding) { + s.exchangeBind(queue, binding.exchange, binding.key, binding.args); +} + +void UpdateClient::updateOutputTask(const sys::OutputTask* task) { + const SemanticState::ConsumerImpl* cci = + boost::polymorphic_downcast<const SemanticState::ConsumerImpl*> (task); + SemanticState::ConsumerImpl* ci = const_cast<SemanticState::ConsumerImpl*>(cci); + uint16_t channel = ci->getParent().getSession().getChannel(); + ClusterConnectionProxy(shadowConnection).outputTask(channel, ci->getName()); + QPID_LOG(debug, updaterId << " updating output task " << ci->getName() + << " channel=" << channel); +} + +void UpdateClient::updateConnection(const boost::intrusive_ptr<Connection>& updateConnection) { + QPID_LOG(debug, updaterId << " updating connection " << *updateConnection); + shadowConnection = catchUpConnection(); + + broker::Connection& bc = updateConnection->getBrokerConnection(); + connectionSettings.maxFrameSize = bc.getFrameMax(); + shadowConnection.open(updateeUrl, connectionSettings); + bc.eachSessionHandler(boost::bind(&UpdateClient::updateSession, this, _1)); + // Safe to use decoder here because we are stalled for update. + std::pair<const char*, size_t> fragment = decoder.get(updateConnection->getId()).getFragment(); + bc.getOutputTasks().eachOutput( + boost::bind(&UpdateClient::updateOutputTask, this, _1)); + ClusterConnectionProxy(shadowConnection).shadowReady( + updateConnection->getId().getMember(), + updateConnection->getId().getNumber(), + bc.getUserId(), + string(fragment.first, fragment.second), + updateConnection->getOutput().getSendMax() + ); + shadowConnection.close(); + QPID_LOG(debug, updaterId << " updated connection " << *updateConnection); +} + +void UpdateClient::updateSession(broker::SessionHandler& sh) { + broker::SessionState* ss = sh.getSession(); + if (!ss) return; // no session. + + QPID_LOG(debug, updaterId << " updating session " << &sh.getConnection() + << "[" << sh.getChannel() << "] = " << ss->getId()); + + // Create a client session to update session state. + boost::shared_ptr<client::ConnectionImpl> cimpl = client::ConnectionAccess::getImpl(shadowConnection); + boost::shared_ptr<client::SessionImpl> simpl = cimpl->newSession(ss->getId().getName(), ss->getTimeout(), sh.getChannel()); + simpl->disableAutoDetach(); + client::SessionBase_0_10Access(shadowSession).set(simpl); + AMQP_AllProxy::ClusterConnection proxy(simpl->out); + + // Re-create session state on remote connection. + + QPID_LOG(debug, updaterId << " updating exclusive queues."); + ss->getSessionAdapter().eachExclusiveQueue(boost::bind(&UpdateClient::updateExclusiveQueue, this, _1)); + + QPID_LOG(debug, updaterId << " updating consumers."); + ss->getSemanticState().eachConsumer( + boost::bind(&UpdateClient::updateConsumer, this, _1)); + + QPID_LOG(debug, updaterId << " updating unacknowledged messages."); + broker::DeliveryRecords& drs = ss->getSemanticState().getUnacked(); + std::for_each(drs.begin(), drs.end(), boost::bind(&UpdateClient::updateUnacked, this, _1)); + + updateTxState(ss->getSemanticState()); // Tx transaction state. + + // Adjust command counter for message in progress, will be sent after state update. + boost::intrusive_ptr<Message> inProgress = ss->getMessageInProgress(); + SequenceNumber received = ss->receiverGetReceived().command; + if (inProgress) + --received; + + // Reset command-sequence state. + proxy.sessionState( + ss->senderGetReplayPoint().command, + ss->senderGetCommandPoint().command, + ss->senderGetIncomplete(), + std::max(received, ss->receiverGetExpected().command), + received, + ss->receiverGetUnknownComplete(), + ss->receiverGetIncomplete() + ); + + // Send frames for partial message in progress. + if (inProgress) { + inProgress->getFrames().map(simpl->out); + } + QPID_LOG(debug, updaterId << " updated session " << sh.getSession()->getId()); +} + +void UpdateClient::updateConsumer( + const broker::SemanticState::ConsumerImpl::shared_ptr& ci) +{ + QPID_LOG(debug, updaterId << " updating consumer " << ci->getName() << " on " + << shadowSession.getId()); + + using namespace message; + shadowSession.messageSubscribe( + arg::queue = ci->getQueue()->getName(), + arg::destination = ci->getName(), + arg::acceptMode = ci->isAckExpected() ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE, + arg::acquireMode = ci->isAcquire() ? ACQUIRE_MODE_PRE_ACQUIRED : ACQUIRE_MODE_NOT_ACQUIRED, + arg::exclusive = ci->isExclusive(), + arg::resumeId = ci->getResumeId(), + arg::resumeTtl = ci->getResumeTtl(), + arg::arguments = ci->getArguments() + ); + shadowSession.messageSetFlowMode(ci->getName(), ci->isWindowing() ? FLOW_MODE_WINDOW : FLOW_MODE_CREDIT); + shadowSession.messageFlow(ci->getName(), CREDIT_UNIT_MESSAGE, ci->getMsgCredit()); + shadowSession.messageFlow(ci->getName(), CREDIT_UNIT_BYTE, ci->getByteCredit()); + ClusterConnectionProxy(shadowSession).consumerState( + ci->getName(), + ci->isBlocked(), + ci->isNotifyEnabled(), + ci->position + ); + consumerNumbering.add(ci); + + QPID_LOG(debug, updaterId << " updated consumer " << ci->getName() + << " on " << shadowSession.getId()); +} + +void UpdateClient::updateUnacked(const broker::DeliveryRecord& dr) { + if (!dr.isEnded() && dr.isAcquired() && dr.getMessage().payload) { + // If the message is acquired then it is no longer on the + // updatees queue, put it on the update queue for updatee to pick up. + // + MessageUpdater(UPDATE, shadowSession, expiry).updateQueuedMessage(dr.getMessage()); + } + ClusterConnectionProxy(shadowSession).deliveryRecord( + dr.getQueue()->getName(), + dr.getMessage().position, + dr.getTag(), + dr.getId(), + dr.isAcquired(), + dr.isAccepted(), + dr.isCancelled(), + dr.isComplete(), + dr.isEnded(), + dr.isWindowing(), + dr.getQueue()->isEnqueued(dr.getMessage()), + dr.getCredit() + ); +} + +class TxOpUpdater : public broker::TxOpConstVisitor, public MessageUpdater { + public: + TxOpUpdater(UpdateClient& dc, client::AsyncSession s, ExpiryPolicy& expiry) + : MessageUpdater(UpdateClient::UPDATE, s, expiry), parent(dc), session(s), proxy(s) {} + + void operator()(const broker::DtxAck& ) { + throw InternalErrorException("DTX transactions not currently supported by cluster."); + } + + void operator()(const broker::RecoveredDequeue& rdeq) { + updateMessage(rdeq.getMessage()); + proxy.txEnqueue(rdeq.getQueue()->getName()); + } + + void operator()(const broker::RecoveredEnqueue& renq) { + updateMessage(renq.getMessage()); + proxy.txEnqueue(renq.getQueue()->getName()); + } + + void operator()(const broker::TxAccept& txAccept) { + proxy.txAccept(txAccept.getAcked()); + } + + void operator()(const broker::TxPublish& txPub) { + updateMessage(txPub.getMessage()); + typedef std::list<Queue::shared_ptr> QueueList; + const QueueList& qlist = txPub.getQueues(); + Array qarray(TYPE_CODE_STR8); + for (QueueList::const_iterator i = qlist.begin(); i != qlist.end(); ++i) + qarray.push_back(Array::ValuePtr(new Str8Value((*i)->getName()))); + proxy.txPublish(qarray, txPub.delivered); + } + + private: + UpdateClient& parent; + client::AsyncSession session; + ClusterConnectionProxy proxy; +}; + +void UpdateClient::updateTxState(broker::SemanticState& s) { + QPID_LOG(debug, updaterId << " updating TX transaction state."); + ClusterConnectionProxy proxy(shadowSession); + proxy.accumulatedAck(s.getAccumulatedAck()); + broker::TxBuffer::shared_ptr txBuffer = s.getTxBuffer(); + if (txBuffer) { + proxy.txStart(); + TxOpUpdater updater(*this, shadowSession, expiry); + txBuffer->accept(updater); + proxy.txEnd(); + } +} + +void UpdateClient::updateQueueListeners(const boost::shared_ptr<broker::Queue>& queue) { + queue->getListeners().eachListener( + boost::bind(&UpdateClient::updateQueueListener, this, queue->getName(), _1)); +} + +void UpdateClient::updateQueueListener(std::string& q, + const boost::shared_ptr<broker::Consumer>& c) +{ + const boost::shared_ptr<SemanticState::ConsumerImpl> ci = + boost::dynamic_pointer_cast<SemanticState::ConsumerImpl>(c); + size_t n = consumerNumbering[ci]; + if (n >= consumerNumbering.size()) + throw Exception(QPID_MSG("Unexpected listener on queue " << q)); + ClusterConnectionProxy(session).addQueueListener(q, n); +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/UpdateClient.h b/cpp/src/qpid/cluster/UpdateClient.h new file mode 100644 index 0000000000..29ef5f9df2 --- /dev/null +++ b/cpp/src/qpid/cluster/UpdateClient.h @@ -0,0 +1,119 @@ +#ifndef QPID_CLUSTER_UPDATECLIENT_H +#define QPID_CLUSTER_UPDATECLIENT_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/cluster/ClusterMap.h" +#include "qpid/cluster/Numbering.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/broker/SemanticState.h" +#include "qpid/sys/Runnable.h" +#include <boost/shared_ptr.hpp> + + +namespace qpid { + +class Url; + +namespace broker { + +class Broker; +class Queue; +class Exchange; +class QueueBindings; +class QueueBinding; +class QueuedMessage; +class SessionHandler; +class DeliveryRecord; +class SessionState; +class SemanticState; +class Decoder; + +} // namespace broker + +namespace cluster { + +class Cluster; +class Connection; +class ClusterMap; +class Decoder; +class ExpiryPolicy; + +/** + * A client that updates the contents of a local broker to a remote one using AMQP. + */ +class UpdateClient : public sys::Runnable { + public: + static const std::string UPDATE; // Name for special update queue and exchange. + static client::Connection catchUpConnection(); + + UpdateClient(const MemberId& updater, const MemberId& updatee, const Url&, + broker::Broker& donor, const ClusterMap& map, ExpiryPolicy& expiry, + const std::vector<boost::intrusive_ptr<Connection> >&, Decoder&, + const boost::function<void()>& done, + const boost::function<void(const std::exception&)>& fail, + const client::ConnectionSettings& + ); + + ~UpdateClient(); + void update(); + void run(); // Will delete this when finished. + + void updateUnacked(const broker::DeliveryRecord&); + + private: + void updateQueue(client::AsyncSession&, const boost::shared_ptr<broker::Queue>&); + void updateNonExclusiveQueue(const boost::shared_ptr<broker::Queue>&); + void updateExclusiveQueue(const boost::shared_ptr<broker::Queue>&); + void updateExchange(const boost::shared_ptr<broker::Exchange>&); + void updateMessage(const broker::QueuedMessage&); + void updateMessageTo(const broker::QueuedMessage&, const std::string& queue, client::Session s); + void updateBinding(client::AsyncSession&, const std::string& queue, const broker::QueueBinding& binding); + void updateConnection(const boost::intrusive_ptr<Connection>& connection); + void updateSession(broker::SessionHandler& s); + void updateTxState(broker::SemanticState& s); + void updateOutputTask(const sys::OutputTask* task); + void updateConsumer(const broker::SemanticState::ConsumerImpl::shared_ptr&); + void updateQueueListeners(const boost::shared_ptr<broker::Queue>&); + void updateQueueListener(std::string& q, const boost::shared_ptr<broker::Consumer>& c); + + Numbering<broker::SemanticState::ConsumerImpl::shared_ptr> consumerNumbering; + MemberId updaterId; + MemberId updateeId; + Url updateeUrl; + broker::Broker& updaterBroker; + ClusterMap map; + ExpiryPolicy& expiry; + std::vector<boost::intrusive_ptr<Connection> > connections; + Decoder& decoder; + client::Connection connection, shadowConnection; + client::AsyncSession session, shadowSession; + boost::function<void()> done; + boost::function<void(const std::exception& e)> failed; + client::ConnectionSettings connectionSettings; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_UPDATECLIENT_H*/ diff --git a/cpp/src/qpid/cluster/UpdateExchange.cpp b/cpp/src/qpid/cluster/UpdateExchange.cpp new file mode 100644 index 0000000000..11937f296f --- /dev/null +++ b/cpp/src/qpid/cluster/UpdateExchange.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 "qpid/framing/MessageTransferBody.h" +#include "qpid/broker/Message.h" +#include "UpdateExchange.h" + +namespace qpid { +namespace cluster { + +using framing::MessageTransferBody; +using framing::DeliveryProperties; + +UpdateExchange::UpdateExchange(management::Manageable* parent) + : broker::Exchange(UpdateClient::UPDATE, parent), + broker::FanOutExchange(UpdateClient::UPDATE, parent) {} + + +void UpdateExchange::setProperties(const boost::intrusive_ptr<broker::Message>& msg) { + MessageTransferBody* transfer = msg->getMethod<MessageTransferBody>(); + assert(transfer); + const DeliveryProperties* props = msg->getProperties<DeliveryProperties>(); + assert(props); + if (props->hasExchange()) + transfer->setDestination(props->getExchange()); + else + transfer->clearDestinationFlag(); +} + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/UpdateExchange.h b/cpp/src/qpid/cluster/UpdateExchange.h new file mode 100644 index 0000000000..9d7d9ee5fc --- /dev/null +++ b/cpp/src/qpid/cluster/UpdateExchange.h @@ -0,0 +1,45 @@ +#ifndef QPID_CLUSTER_UPDATEEXCHANGE_H +#define QPID_CLUSTER_UPDATEEXCHANGE_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/cluster/UpdateClient.h" +#include "qpid/broker/FanOutExchange.h" + + +namespace qpid { +namespace cluster { + +/** + * A keyless exchange (like fanout exchange) that does not modify + * delivery-properties.exchange but copies it to the MessageTransfer. + */ +class UpdateExchange : public broker::FanOutExchange +{ + public: + UpdateExchange(management::Manageable* parent); + void setProperties(const boost::intrusive_ptr<broker::Message>&); +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_UPDATEEXCHANGE_H*/ diff --git a/cpp/src/qpid/cluster/UpdateReceiver.h b/cpp/src/qpid/cluster/UpdateReceiver.h new file mode 100644 index 0000000000..cc1ce0da8d --- /dev/null +++ b/cpp/src/qpid/cluster/UpdateReceiver.h @@ -0,0 +1,42 @@ +#ifndef QPID_CLUSTER_UPDATESTATE_H +#define QPID_CLUSTER_UPDATESTATE_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 "Numbering.h" +#include "qpid/broker/SemanticState.h" + +namespace qpid { +namespace cluster { + +/** + * Cluster-wide state used when receiving an update. + */ +class UpdateReceiver { + public: + /** Numbering used to identify Queue listeners as consumers */ + typedef Numbering<boost::shared_ptr<broker::SemanticState::ConsumerImpl> > ConsumerNumbering; + ConsumerNumbering consumerNumbering; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_UPDATESTATE_H*/ diff --git a/cpp/src/qpid/cluster/WatchDogPlugin.cpp b/cpp/src/qpid/cluster/WatchDogPlugin.cpp new file mode 100644 index 0000000000..fc2b830dac --- /dev/null +++ b/cpp/src/qpid/cluster/WatchDogPlugin.cpp @@ -0,0 +1,136 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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 + + The watchdog plug-in will kill the qpidd broker process if it + becomes stuck for longer than a configured interval. + + If the watchdog plugin is loaded and the --watchdog-interval=N + option is set then the broker starts a watchdog process and signals + it every N/2 seconds. + + The watchdog process runs a very simple program that starts a timer + for N seconds, and resets the timer to N seconds whenever it is + signalled by the broker. If the timer ever reaches 0 the watchdog + kills the broker process (with kill -9) and exits. + + This is useful in a cluster setting because in some insttances + (e.g. while resolving an error) it's possible for a stuck process + to hang other cluster members that are waiting for it to send a + message. Using the watchdog, the stuck process is terminated and + removed fromt the cluster allowing other members to continue and + clients of the stuck process to fail over to other members. + +*/ +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/log/Statement.h" +#include "qpid/broker/Broker.h" +#include "qpid/sys/Timer.h" +#include "qpid/sys/Fork.h" +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> + +namespace qpid { +namespace cluster { + +using broker::Broker; + +struct Settings { + Settings() : interval(0) {} + int interval; +}; + +struct WatchDogOptions : public qpid::Options { + Settings& settings; + + WatchDogOptions(Settings& s) : settings(s) { + addOptions() + ("watchdog-interval", optValue(settings.interval, "N"), + "broker is automatically killed if it is hung for more than \ + N seconds. 0 disables watchdog."); + } +}; + +struct WatchDogTask : public sys::TimerTask { + int pid; + sys::Timer& timer; + int interval; + + WatchDogTask(int pid_, sys::Timer& t, int _interval) + : TimerTask(_interval*sys::TIME_SEC/2), pid(pid_), timer(t), interval(_interval) {} + + void fire() { + timer.add (new WatchDogTask(pid, timer, interval)); + QPID_LOG(debug, "Sending keepalive signal to watchdog"); + ::kill(pid, SIGUSR1); + } +}; + +struct WatchDogPlugin : public qpid::Plugin, public qpid::sys::Fork { + Settings settings; + WatchDogOptions options; + Broker* broker; + int watchdogPid; + + WatchDogPlugin() : options(settings), broker(0), watchdogPid(0) {} + + ~WatchDogPlugin() { + if (watchdogPid) ::kill(watchdogPid, SIGTERM); + ::waitpid(watchdogPid, 0, 0); + } + + Options* getOptions() { return &options; } + + void earlyInitialize(qpid::Plugin::Target& target) { + broker = dynamic_cast<Broker*>(&target); + if (broker && settings.interval) { + QPID_LOG(notice, "Starting watchdog process with interval of " << + settings.interval << " seconds"); + fork(); + } + } + + void initialize(Target&) {} + + protected: + + void child() { // Child of fork + const char* watchdog = ::getenv("QPID_WATCHDOG_EXEC"); // For use in tests + if (!watchdog) watchdog=QPID_EXEC_DIR "/qpidd_watchdog"; + std::string interval = boost::lexical_cast<std::string>(settings.interval); + ::execl(watchdog, watchdog, interval.c_str(), NULL); + QPID_LOG(critical, "Failed to exec watchdog program " << watchdog ); + ::kill(::getppid(), SIGKILL); + exit(1); + } + + void parent(int pid) { // Parent of fork + watchdogPid = pid; + broker->getTimer().add( + new WatchDogTask(watchdogPid, broker->getTimer(), settings.interval)); + // TODO aconway 2009-08-10: to be extra safe, we could monitor + // the watchdog child and re-start it if it exits. + } +}; + +static WatchDogPlugin instance; // Static initialization. + +}} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/management-schema.xml b/cpp/src/qpid/cluster/management-schema.xml new file mode 100644 index 0000000000..a6292e9113 --- /dev/null +++ b/cpp/src/qpid/cluster/management-schema.xml @@ -0,0 +1,61 @@ +<schema package="org.apache.qpid.cluster"> + + <!-- + 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. + --> + + <!-- Type information: + +Numeric types with "_wm" suffix are watermarked numbers. These are compound +values containing a current value, and a low and high water mark for the reporting +interval. The low and high water marks are set to the current value at the +beginning of each interval and track the minimum and maximum values of the statistic +over the interval respectively. + +Access rights for configuration elements: + +RO => Read Only +RC => Read/Create, can be set at create time only, read-only thereafter +RW => Read/Write + +If access rights are omitted for a property, they are assumed to be RO. + + --> + + <class name="Cluster"> + <property name="brokerRef" type="objId" references="Broker" access="RC" index="y" parentRef="y"/> + <property name="clusterName" type="sstr" access="RC" desc="Name of cluster this server is a member of"/> + <property name="clusterID" type="sstr" access="RO" desc="Globally unique ID (UUID) for this cluster instance"/> + <property name="memberID" type="sstr" access="RO" desc="ID of this member of the cluster"/> + <property name="publishedURL" type="sstr" access="RC" desc="URL this node advertizes itself as"/> + <property name="clusterSize" type="uint16" access="RO" desc="Number of brokers currently in the cluster"/> + <property name="status" type="sstr" access="RO" desc="Cluster node status (STALLED,ACTIVE,JOINING)"/> + <property name="members" type="lstr" access="RO" desc="List of member URLs delimited by ';'"/> + <property name="memberIDs" type="lstr" access="RO" desc="List of member IDs delimited by ';'"/> + + <method name="stopClusterNode"> + <arg name="brokerId" type="sstr" dir="I"/> + </method> + <method name="stopFullCluster"/> + + </class> + + + +</schema> + diff --git a/cpp/src/qpid/cluster/qpidd_watchdog.cpp b/cpp/src/qpid/cluster/qpidd_watchdog.cpp new file mode 100644 index 0000000000..51c5ed4b3f --- /dev/null +++ b/cpp/src/qpid/cluster/qpidd_watchdog.cpp @@ -0,0 +1,63 @@ +/* + * + * 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 helper executable for WatchDogPlugin.cpp */ + +#include <sys/types.h> +#include <sys/time.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> + +long timeout; + +void killParent(int) { + ::kill(getppid(), SIGKILL); + ::fprintf(stderr, "Watchdog killed unresponsive broker, pid=%d\n", ::getppid()); + ::exit(1); +} + +void resetTimer(int) { + struct ::itimerval itval = { { 0, 0 }, { timeout, 0 } }; + if (::setitimer(ITIMER_REAL, &itval, 0) !=0) { + ::perror("Watchdog failed to set timer"); + killParent(0); + ::exit(1); + } +} + +/** Simple watchdog program: kill parent process if timeout + * expires without a SIGUSR1. + * Will be killed with SIGHUP when parent shuts down. + * Args: timeout in seconds. + */ +int main(int argc, char** argv) { + if(argc != 2 || (timeout = atoi(argv[1])) == 0) { + ::fprintf(stderr, "Usage: %s <timeout_seconds>\n", argv[0]); + ::exit(1); + } + ::signal(SIGUSR1, resetTimer); + ::signal(SIGALRM, killParent); + resetTimer(0); + while (true) { sleep(INT_MAX); } +} diff --git a/cpp/src/qpid/cluster/types.h b/cpp/src/qpid/cluster/types.h new file mode 100644 index 0000000000..c25370b6b6 --- /dev/null +++ b/cpp/src/qpid/cluster/types.h @@ -0,0 +1,83 @@ +#ifndef QPID_CLUSTER_TYPES_H +#define QPID_CLUSTER_TYPES_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 "config.h" +#include "qpid/Url.h" +#include "qpid/sys/IntegerTypes.h" +#include <boost/intrusive_ptr.hpp> +#include <utility> +#include <iosfwd> +#include <string> + +extern "C" { +#if defined (HAVE_OPENAIS_CPG_H) +# include <openais/cpg.h> +#elif defined (HAVE_COROSYNC_CPG_H) +# include <corosync/cpg.h> +#else +# error "No cpg.h header file available" +#endif +} + +namespace qpid { +namespace cluster { + +class Connection; +typedef boost::intrusive_ptr<Connection> ConnectionPtr; + +/** Types of cluster event. */ +enum EventType { DATA, CONTROL }; + +/** first=node-id, second=pid */ +struct MemberId : std::pair<uint32_t, uint32_t> { + MemberId(uint64_t n=0) : std::pair<uint32_t,uint32_t>( n >> 32, n & 0xffffffff) {} + MemberId(uint32_t node, uint32_t pid) : std::pair<uint32_t,uint32_t>(node, pid) {} + MemberId(const cpg_address& caddr) : std::pair<uint32_t,uint32_t>(caddr.nodeid, caddr.pid) {} + MemberId(const std::string&); // Decode from string. + uint32_t getNode() const { return first; } + uint32_t getPid() const { return second; } + operator uint64_t() const { return (uint64_t(first)<<32ull) + second; } + + // AsMethodBody as string, network byte order. + std::string str() const; +}; + +inline bool operator==(const cpg_address& caddr, const MemberId& id) { return id == MemberId(caddr); } + +std::ostream& operator<<(std::ostream&, const MemberId&); + +struct ConnectionId : public std::pair<MemberId, uint64_t> { + ConnectionId(const MemberId& m=MemberId(), uint64_t c=0) : std::pair<MemberId, uint64_t> (m,c) {} + ConnectionId(uint64_t m, uint64_t c) : std::pair<MemberId, uint64_t>(MemberId(m), c) {} + MemberId getMember() const { return first; } + uint64_t getNumber() const { return second; } +}; + +std::ostream& operator<<(std::ostream&, const ConnectionId&); + +std::ostream& operator<<(std::ostream&, EventType); + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_TYPES_H*/ diff --git a/cpp/src/qpid/client/Session.h b/cpp/src/qpid/console/Agent.cpp index bdabd26c82..fa76a13583 100644 --- a/cpp/src/qpid/client/Session.h +++ b/cpp/src/qpid/console/Agent.cpp @@ -1,6 +1,3 @@ -#ifndef QPID_CLIENT_SESSION_H -#define QPID_CLIENT_SESSION_H - /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -21,19 +18,13 @@ * under the License. * */ -#include "qpid/client/Session_0_10.h" - -namespace qpid { -namespace client { - -/** - * Session is an alias for Session_0_10 - * - * \ingroup clientapi - */ -typedef Session_0_10 Session; +#include "qpid/console/Agent.h" -}} // namespace qpid::client +std::ostream& qpid::console::operator<<(std::ostream& o, const Agent& agent) +{ + o << "Agent at bank " << agent.getBrokerBank() << "." << agent.getAgentBank() << + " (" << agent.getLabel() << ")"; + return o; +} -#endif /*!QPID_CLIENT_SESSION_H*/ diff --git a/cpp/src/qpid/console/Broker.cpp b/cpp/src/qpid/console/Broker.cpp new file mode 100644 index 0000000000..d2ff8f819e --- /dev/null +++ b/cpp/src/qpid/console/Broker.cpp @@ -0,0 +1,328 @@ +/* + * + * 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/console/Broker.h" +#include "qpid/console/Object.h" +#include "qpid/console/Value.h" +#include "qpid/console/SessionManager.h" +#include "qpid/console/ConsoleListener.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/SystemInfo.h" + +using namespace qpid::client; +using namespace qpid::console; +using namespace qpid::framing; +using namespace qpid::sys; +using namespace std; + +Broker::Broker(SessionManager& sm, ConnectionSettings& settings) : + sessionManager(sm), connected(false), connectionSettings(settings), + reqsOutstanding(1), syncInFlight(false), topicBound(false), methodObject(0), + connThreadBody(*this), connThread(connThreadBody) +{ + string osName; + string nodeName; + string release; + string version; + string machine; + + sys::SystemInfo::getSystemId(osName, nodeName, release, version, machine); + uint32_t pid = sys::SystemInfo::getParentProcessId(); + + stringstream text; + + text << "qmfc-cpp-" << nodeName << "-" << pid; + amqpSessionId = string(text.str()); + + QPID_LOG(debug, "Broker::Broker: constructed, amqpSessionId=" << amqpSessionId); +} + +Broker::~Broker() +{ + connThreadBody.shutdown(); + connThread.join(); +} + +string Broker::getUrl() const +{ + stringstream url; + url << connectionSettings.host << ":" << connectionSettings.port; + return url.str(); +} + +void Broker::encodeHeader(Buffer& buf, uint8_t opcode, uint32_t seq) const +{ + buf.putOctet('A'); + buf.putOctet('M'); + buf.putOctet('2'); + buf.putOctet(opcode); + buf.putLong (seq); +} + +bool Broker::checkHeader(Buffer& buf, uint8_t *opcode, uint32_t *seq) const +{ + if (buf.getSize() < 8) + return false; + + uint8_t h1 = buf.getOctet(); + uint8_t h2 = buf.getOctet(); + uint8_t h3 = buf.getOctet(); + + *opcode = buf.getOctet(); + *seq = buf.getLong(); + + return h1 == 'A' && h2 == 'M' && h3 == '2'; +} + +void Broker::received(qpid::client::Message& msg) +{ +#define QMF_HEADER_SIZE 8 + string data = msg.getData(); + Buffer inBuffer(const_cast<char*>(data.c_str()), data.size()); + uint8_t opcode; + uint32_t sequence; + + while (inBuffer.available() >= QMF_HEADER_SIZE) { + if (checkHeader(inBuffer, &opcode, &sequence)) { + QPID_LOG(trace, "Broker::received: opcode=" << opcode << " seq=" << sequence); + + if (opcode == 'b') sessionManager.handleBrokerResp(this, inBuffer, sequence); + else if (opcode == 'p') sessionManager.handlePackageInd(this, inBuffer, sequence); + else if (opcode == 'z') sessionManager.handleCommandComplete(this, inBuffer, sequence); + else if (opcode == 'q') sessionManager.handleClassInd(this, inBuffer, sequence); + else if (opcode == 'm') sessionManager.handleMethodResp(this, inBuffer, sequence); + else if (opcode == 'h') sessionManager.handleHeartbeatInd(this, inBuffer, sequence); + else if (opcode == 'e') sessionManager.handleEventInd(this, inBuffer, sequence); + else if (opcode == 's') sessionManager.handleSchemaResp(this, inBuffer, sequence); + else if (opcode == 'c') sessionManager.handleContentInd(this, inBuffer, sequence, true, false); + else if (opcode == 'i') sessionManager.handleContentInd(this, inBuffer, sequence, false, true); + else if (opcode == 'g') sessionManager.handleContentInd(this, inBuffer, sequence, true, true); + } else + return; + } +} + +void Broker::resetAgents() +{ + for (AgentMap::iterator iter = agents.begin(); iter != agents.end(); iter++) { + if (sessionManager.listener != 0) + sessionManager.listener->delAgent(*(iter->second)); + delete iter->second; + } + + agents.clear(); + agents[0x0000000100000000LL] = new Agent(this, 0, "BrokerAgent"); +} + +void Broker::updateAgent(const Object& object) +{ + uint32_t brokerBank = object.attrUint("brokerBank"); + uint32_t agentBank = object.attrUint("agentBank"); + uint64_t agentKey = ((uint64_t) brokerBank << 32) | (uint64_t) agentBank; + AgentMap::iterator iter = agents.find(agentKey); + + if (object.isDeleted()) { + if (iter != agents.end()) { + if (sessionManager.listener != 0) + sessionManager.listener->delAgent(*(iter->second)); + delete iter->second; + agents.erase(iter); + } + } else { + if (iter == agents.end()) { + Agent* agent = new Agent(this, agentBank, object.attrString("label")); + agents[agentKey] = agent; + if (sessionManager.listener != 0) + sessionManager.listener->newAgent(*agent); + } + } +} + +void Broker::ConnectionThread::run() +{ + static const int delayMin(1); + static const int delayMax(128); + static const int delayFactor(2); + int delay(delayMin); + string dest("qmfc"); + + sessionId.generate(); + queueName << "qmfc-" << sessionId; + + while (true) { + try { + broker.topicBound = false; + broker.reqsOutstanding = 1; + connection.open(broker.connectionSettings); + session = connection.newSession(queueName.str()); + subscriptions = new client::SubscriptionManager(session); + + session.queueDeclare(arg::queue=queueName.str(), arg::autoDelete=true, + arg::exclusive=true); + session.exchangeBind(arg::exchange="amq.direct", arg::queue=queueName.str(), + arg::bindingKey=queueName.str()); + + subscriptions->setAcceptMode(ACCEPT_MODE_NONE); + subscriptions->setAcquireMode(ACQUIRE_MODE_PRE_ACQUIRED); + subscriptions->subscribe(broker, queueName.str(), dest); + subscriptions->setFlowControl(dest, FlowControl::unlimited()); + { + Mutex::ScopedLock _lock(connLock); + if (shuttingDown) + return; + operational = true; + broker.resetAgents(); + broker.connected = true; + broker.sessionManager.handleBrokerConnect(&broker); + broker.sessionManager.startProtocol(&broker); + try { + Mutex::ScopedUnlock _unlock(connLock); + subscriptions->run(); + } catch (std::exception) {} + + operational = false; + broker.connected = false; + broker.sessionManager.handleBrokerDisconnect(&broker); + } + delay = delayMin; + connection.close(); + delete subscriptions; + subscriptions = 0; + } catch (std::exception &e) { + QPID_LOG(debug, " outer exception: " << e.what()); + if (delay < delayMax) + delay *= delayFactor; + } + + { + Mutex::ScopedLock _lock(connLock); + if (shuttingDown) + return; + { + Mutex::ScopedUnlock _unlock(connLock); + ::sleep(delay); + } + if (shuttingDown) + return; + } + } +} + +Broker::ConnectionThread::~ConnectionThread() +{ + if (subscriptions != 0) { + delete subscriptions; + } +} + +void Broker::ConnectionThread::sendBuffer(Buffer& buf, uint32_t length, + const string& exchange, const string& routingKey) +{ + { + Mutex::ScopedLock _lock(connLock); + if (!operational) + return; + } + + client::Message msg; + string data; + + buf.getRawData(data, length); + msg.getDeliveryProperties().setRoutingKey(routingKey); + msg.getMessageProperties().setReplyTo(ReplyTo("amq.direct", queueName.str())); + msg.setData(data); + try { + session.messageTransfer(arg::content=msg, arg::destination=exchange); + } catch(std::exception&) {} +} + +void Broker::ConnectionThread::bindExchange(const std::string& exchange, const std::string& key) +{ + { + Mutex::ScopedLock _lock(connLock); + if (!operational) + return; + } + + QPID_LOG(debug, "Broker::ConnectionThread::bindExchange: exchange=" << exchange << " key=" << key); + session.exchangeBind(arg::exchange=exchange, arg::queue=queueName.str(), + arg::bindingKey=key); +} + +void Broker::ConnectionThread::shutdown() +{ + { + Mutex::ScopedLock _lock(connLock); + shuttingDown = true; + } + if (subscriptions) + subscriptions->stop(); +} + +void Broker::waitForStable() +{ + Mutex::ScopedLock l(lock); + if (reqsOutstanding == 0) + return; + syncInFlight = true; + while (reqsOutstanding != 0) { + bool result = cond.wait(lock, AbsTime(now(), TIME_SEC * sessionManager.settings.getTimeout)); + if (!result) + throw(Exception("Timed out waiting for broker to synchronize")); + } +} + +void Broker::incOutstanding() +{ + Mutex::ScopedLock l(lock); + reqsOutstanding++; +} + +void Broker::decOutstanding() +{ + Mutex::ScopedLock l(lock); + reqsOutstanding--; + if (reqsOutstanding == 0) { + if (!topicBound) { + topicBound = true; + for (vector<string>::const_iterator iter = sessionManager.bindingKeyList.begin(); + iter != sessionManager.bindingKeyList.end(); iter++) + connThreadBody.bindExchange("qpid.management", *iter); + } + if (syncInFlight) { + syncInFlight = false; + cond.notify(); + } + } +} + +void Broker::appendAgents(Agent::Vector& agentlist) const +{ + for (AgentMap::const_iterator iter = agents.begin(); iter != agents.end(); iter++) { + agentlist.push_back(iter->second); + } +} + +ostream& qpid::console::operator<<(ostream& o, const Broker& k) +{ + o << "Broker: " << k.connectionSettings.host << ":" << k.connectionSettings.port; + return o; +} diff --git a/cpp/src/qpid/console/ClassKey.cpp b/cpp/src/qpid/console/ClassKey.cpp new file mode 100644 index 0000000000..7a16113bae --- /dev/null +++ b/cpp/src/qpid/console/ClassKey.cpp @@ -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. + * + */ + +#include "qpid/console/ClassKey.h" +#include <string.h> +#include <cstdio> + +using namespace std; +using namespace qpid::console; + +ClassKey::ClassKey(const string& _package, const string& _name, const uint8_t* _hash) : + package(_package), name(_name) +{ + ::memcpy(hash, _hash, HASH_SIZE); +} + +string ClassKey::getHashString() const +{ + char cstr[36]; + ::sprintf(cstr, "%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x", + hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], + hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]); + return string(cstr); +} + +string ClassKey::str() const +{ + string result(package + ":" + name + "(" + getHashString() + ")"); + return result; +} + +bool ClassKey::operator==(const ClassKey& other) const +{ + return ::memcmp(hash, other.hash, HASH_SIZE) == 0 && + name == other.name && + package == other.package; +} + +bool ClassKey::operator!=(const ClassKey& other) const +{ + return !(*this == other); +} + +bool ClassKey::operator<(const ClassKey& other) const +{ + int cmp = ::memcmp(hash, other.hash, HASH_SIZE); + if (cmp != 0) + return cmp < 0; + cmp = name.compare(other.name); + if (cmp != 0) + return cmp < 0; + return package < other.package; +} + +bool ClassKey::operator>(const ClassKey& other) const +{ + int cmp = ::memcmp(hash, other.hash, HASH_SIZE); + if (cmp != 0) + return cmp > 0; + cmp = name.compare(other.name); + if (cmp != 0) + return cmp > 0; + return package > other.package; +} + +bool ClassKey::operator<=(const ClassKey& other) const +{ + return !(*this > other); +} + +bool ClassKey::operator>=(const ClassKey& other) const +{ + return !(*this < other); +} + +void ClassKey::encode(qpid::framing::Buffer& buffer) const +{ + buffer.putShortString(package); + buffer.putShortString(name); + buffer.putBin128(const_cast<uint8_t*>(hash)); +} + +ostream& qpid::console::operator<<(ostream& o, const ClassKey& k) +{ + o << k.str(); + return o; +} diff --git a/cpp/src/qpid/console/Event.cpp b/cpp/src/qpid/console/Event.cpp new file mode 100644 index 0000000000..3e14804b35 --- /dev/null +++ b/cpp/src/qpid/console/Event.cpp @@ -0,0 +1,205 @@ +/* + * + * 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/console/Broker.h" +#include "qpid/console/ClassKey.h" +#include "qpid/console/Schema.h" +#include "qpid/console/Event.h" +#include "qpid/console/Value.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/Buffer.h" + +using namespace qpid::console; +using namespace std; +using qpid::framing::Uuid; +using qpid::framing::FieldTable; + +Event::Event(Broker* _broker, SchemaClass* _schema, qpid::framing::Buffer& buffer) : + broker(_broker), schema(_schema) +{ + timestamp = buffer.getLongLong(); + severity = (Severity) buffer.getOctet(); + for (vector<SchemaArgument*>::const_iterator aIter = schema->arguments.begin(); + aIter != schema->arguments.end(); aIter++) { + SchemaArgument* argument = *aIter; + attributes[argument->name] = argument->decodeValue(buffer); + } +} + +const ClassKey& Event::getClassKey() const +{ + return schema->getClassKey(); +} + +string Event::getSeverityString() const +{ + switch (severity) { + case SEV_EMERGENCY : return string("EMER"); + case SEV_ALERT : return string("ALERT"); + case SEV_CRITICAL : return string("CRIT"); + case SEV_ERROR : return string("ERROR"); + case SEV_WARNING : return string("WARN"); + case SEV_NOTICE : return string("NOTIC"); + case SEV_INFO : return string("INFO"); + case SEV_DEBUG : return string("DEBUG"); + } + return string("<UNKNOWN>"); +} + +ObjectId Event::attrRef(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return ObjectId(); + Value::Ptr val = iter->second; + if (!val->isObjectId()) + return ObjectId(); + return val->asObjectId(); +} + +uint32_t Event::attrUint(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isUint()) + return 0; + return val->asUint(); +} + +int32_t Event::attrInt(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isInt()) + return 0; + return val->asInt(); +} + +uint64_t Event::attrUint64(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isUint64()) + return 0; + return val->asUint64(); +} + +int64_t Event::attrInt64(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isInt64()) + return 0; + return val->asInt64(); +} + +string Event::attrString(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return string(); + Value::Ptr val = iter->second; + if (!val->isString()) + return string(); + return val->asString(); +} + +bool Event::attrBool(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return false; + Value::Ptr val = iter->second; + if (!val->isBool()) + return false; + return val->asBool(); +} + +float Event::attrFloat(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0.0; + Value::Ptr val = iter->second; + if (!val->isFloat()) + return 0.0; + return val->asFloat(); +} + +double Event::attrDouble(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0.0; + Value::Ptr val = iter->second; + if (!val->isDouble()) + return 0.0; + return val->asDouble(); +} + +Uuid Event::attrUuid(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return Uuid(); + Value::Ptr val = iter->second; + if (!val->isUuid()) + return Uuid(); + return val->asUuid(); +} + +FieldTable Event::attrMap(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return FieldTable(); + Value::Ptr val = iter->second; + if (!val->isMap()) + return FieldTable(); + return val->asMap(); +} + + +std::ostream& qpid::console::operator<<(std::ostream& o, const Event& event) +{ + const ClassKey& key = event.getClassKey(); + sys::AbsTime aTime(sys::AbsTime(), sys::Duration(event.getTimestamp())); + o << aTime << " " << event.getSeverityString() << " " << + key.getPackageName() << ":" << key.getClassName() << + " broker=" << event.getBroker()->getUrl(); + + const Object::AttributeMap& attributes = event.getAttributes(); + for (Object::AttributeMap::const_iterator iter = attributes.begin(); + iter != attributes.end(); iter++) { + o << " " << iter->first << "=" << iter->second->str(); + } + return o; +} + + diff --git a/cpp/src/qpid/console/Object.cpp b/cpp/src/qpid/console/Object.cpp new file mode 100644 index 0000000000..6570e293ab --- /dev/null +++ b/cpp/src/qpid/console/Object.cpp @@ -0,0 +1,384 @@ +/* + * + * 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/console/SessionManager.h" +#include "qpid/console/Broker.h" +#include "qpid/console/Object.h" +#include "qpid/console/Schema.h" +#include "qpid/console/ClassKey.h" +#include "qpid/console/Value.h" +#include "qpid/framing/Buffer.h" +#include "qpid/sys/Mutex.h" + +using namespace qpid::console; +using namespace qpid::sys; +using namespace qpid; +using namespace std; +using qpid::framing::Uuid; +using qpid::framing::FieldTable; + +void Object::AttributeMap::addRef(const string& key, const ObjectId& val) +{ + (*this)[key] = Value::Ptr(new RefValue(val)); +} + +void Object::AttributeMap::addUint(const string& key, uint32_t val) +{ + (*this)[key] = Value::Ptr(new UintValue(val)); +} + +void Object::AttributeMap::addInt(const string& key, int32_t val) +{ + (*this)[key] = Value::Ptr(new IntValue(val)); +} + +void Object::AttributeMap::addUint64(const string& key, uint64_t val) +{ + (*this)[key] = Value::Ptr(new Uint64Value(val)); +} + +void Object::AttributeMap::addInt64(const string& key, int64_t val) +{ + (*this)[key] = Value::Ptr(new Int64Value(val)); +} + +void Object::AttributeMap::addString(const string& key, const string& val) +{ + (*this)[key] = Value::Ptr(new StringValue(val)); +} + +void Object::AttributeMap::addBool(const string& key, bool val) +{ + (*this)[key] = Value::Ptr(new BoolValue(val)); +} + +void Object::AttributeMap::addFloat(const string& key, float val) +{ + (*this)[key] = Value::Ptr(new FloatValue(val)); +} + +void Object::AttributeMap::addDouble(const string& key, double val) +{ + (*this)[key] = Value::Ptr(new DoubleValue(val)); +} + +void Object::AttributeMap::addUuid(const string& key, const Uuid& val) +{ + (*this)[key] = Value::Ptr(new UuidValue(val)); +} + +void Object::AttributeMap::addMap(const string& key, const FieldTable& val) +{ + (*this)[key] = Value::Ptr(new MapValue(val)); +} + +Object::Object(Broker* b, SchemaClass* s, framing::Buffer& buffer, bool prop, bool stat) : + broker(b), schema(s), pendingMethod(0) +{ + currentTime = buffer.getLongLong(); + createTime = buffer.getLongLong(); + deleteTime = buffer.getLongLong(); + objectId.decode(buffer); + + if (prop) { + set<string> excludes; + parsePresenceMasks(buffer, excludes); + for (vector<SchemaProperty*>::const_iterator pIter = schema->properties.begin(); + pIter != schema->properties.end(); pIter++) { + SchemaProperty* property = *pIter; + if (excludes.count(property->name) != 0) { + attributes[property->name] = Value::Ptr(new NullValue()); + } else { + attributes[property->name] = property->decodeValue(buffer); + } + } + } + + if (stat) { + for (vector<SchemaStatistic*>::const_iterator sIter = schema->statistics.begin(); + sIter != schema->statistics.end(); sIter++) { + SchemaStatistic* statistic = *sIter; + attributes[statistic->name] = statistic->decodeValue(buffer); + } + } +} + +Object::~Object() {} + +const ClassKey& Object::getClassKey() const +{ + return schema->getClassKey(); +} + +string Object::getIndex() const +{ + string result; + + for (vector<SchemaProperty*>::const_iterator pIter = schema->properties.begin(); + pIter != schema->properties.end(); pIter++) { + SchemaProperty* property = *pIter; + if (property->isIndex) { + AttributeMap::const_iterator vIter = attributes.find(property->name); + if (vIter != attributes.end()) { + if (!result.empty()) + result += ":"; + result += vIter->second->str(); + } + } + } + return result; +} + +void Object::mergeUpdate(const Object& /*updated*/) +{ + // TODO +} + +void Object::invokeMethod(const string name, const AttributeMap& args, MethodResponse& result) +{ + for (vector<SchemaMethod*>::const_iterator iter = schema->methods.begin(); + iter != schema->methods.end(); iter++) { + if ((*iter)->name == name) { + SchemaMethod* method = *iter; + char rawbuffer[65536]; + framing::Buffer buffer(rawbuffer, 65536); + uint32_t sequence = broker->sessionManager.sequenceManager.reserve("method"); + pendingMethod = method; + broker->methodObject = this; + broker->encodeHeader(buffer, 'M', sequence); + objectId.encode(buffer); + schema->key.encode(buffer); + buffer.putShortString(name); + + for (vector<SchemaArgument*>::const_iterator aIter = method->arguments.begin(); + aIter != method->arguments.end(); aIter++) { + SchemaArgument* arg = *aIter; + if (arg->dirInput) { + AttributeMap::const_iterator attr = args.find(arg->name); + if (attr != args.end()) { + ValueFactory::encodeValue(arg->typeCode, attr->second, buffer); + } else { + // TODO Use the default value instead of throwing + throw Exception("Missing arguments in method call"); + } + } + } + + uint32_t length = buffer.getPosition(); + buffer.reset(); + stringstream routingKey; + routingKey << "agent." << objectId.getBrokerBank() << "." << objectId.getAgentBank(); + broker->connThreadBody.sendBuffer(buffer, length, "qpid.management", routingKey.str()); + + { + Mutex::ScopedLock l(broker->lock); + bool ok = true; + while (pendingMethod != 0 && ok) { + ok = broker->cond.wait(broker->lock, AbsTime(now(), broker->sessionManager.settings.methodTimeout * TIME_SEC)); + } + + if (!ok) { + result.code = 0x1001; + result.text.assign("Method call timed out"); + result.arguments.clear(); + } else { + result = methodResponse; + } + } + } + } +} + +void Object::handleMethodResp(framing::Buffer& buffer, uint32_t sequence) +{ + broker->sessionManager.sequenceManager.release(sequence); + methodResponse.code = buffer.getLong(); + buffer.getMediumString(methodResponse.text); + methodResponse.arguments.clear(); + + for (vector<SchemaArgument*>::const_iterator aIter = pendingMethod->arguments.begin(); + aIter != pendingMethod->arguments.end(); aIter++) { + SchemaArgument* arg = *aIter; + if (arg->dirOutput) { + methodResponse.arguments[arg->name] = arg->decodeValue(buffer); + } + } + + { + Mutex::ScopedLock l(broker->lock); + pendingMethod = 0; + broker->cond.notify(); + } +} + +ObjectId Object::attrRef(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return ObjectId(); + Value::Ptr val = iter->second; + if (!val->isObjectId()) + return ObjectId(); + return val->asObjectId(); +} + +uint32_t Object::attrUint(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isUint()) + return 0; + return val->asUint(); +} + +int32_t Object::attrInt(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isInt()) + return 0; + return val->asInt(); +} + +uint64_t Object::attrUint64(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isUint64()) + return 0; + return val->asUint64(); +} + +int64_t Object::attrInt64(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isInt64()) + return 0; + return val->asInt64(); +} + +string Object::attrString(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return string(); + Value::Ptr val = iter->second; + if (!val->isString()) + return string(); + return val->asString(); +} + +bool Object::attrBool(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return false; + Value::Ptr val = iter->second; + if (!val->isBool()) + return false; + return val->asBool(); +} + +float Object::attrFloat(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0.0; + Value::Ptr val = iter->second; + if (!val->isFloat()) + return 0.0; + return val->asFloat(); +} + +double Object::attrDouble(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0.0; + Value::Ptr val = iter->second; + if (!val->isDouble()) + return 0.0; + return val->asDouble(); +} + +Uuid Object::attrUuid(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return Uuid(); + Value::Ptr val = iter->second; + if (!val->isUuid()) + return Uuid(); + return val->asUuid(); +} + +FieldTable Object::attrMap(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return FieldTable(); + Value::Ptr val = iter->second; + if (!val->isMap()) + return FieldTable(); + return val->asMap(); +} + +void Object::parsePresenceMasks(framing::Buffer& buffer, set<string>& excludeList) +{ + excludeList.clear(); + uint8_t bit = 0; + uint8_t mask = 0; + + for (vector<SchemaProperty*>::const_iterator pIter = schema->properties.begin(); + pIter != schema->properties.end(); pIter++) { + SchemaProperty* property = *pIter; + if (property->isOptional) { + if (bit == 0) { + mask = buffer.getOctet(); + bit = 1; + } + if ((mask & bit) == 0) + excludeList.insert(property->name); + if (bit == 0x80) + bit = 0; + else + bit = bit << 1; + } + } +} + +ostream& qpid::console::operator<<(ostream& o, const Object& object) +{ + const ClassKey& key = object.getClassKey(); + o << key.getPackageName() << ":" << key.getClassName() << "[" << object.getObjectId() << "] " << + object.getIndex(); + return o; +} + diff --git a/cpp/src/qpid/console/ObjectId.cpp b/cpp/src/qpid/console/ObjectId.cpp new file mode 100644 index 0000000000..fbaad20d57 --- /dev/null +++ b/cpp/src/qpid/console/ObjectId.cpp @@ -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. + * + */ + +#include "qpid/console/ObjectId.h" +#include "qpid/framing/Buffer.h" + +using namespace qpid::console; +using namespace qpid; +using namespace std; + +ObjectId::ObjectId(framing::Buffer& buffer) +{ + decode(buffer); +} + +void ObjectId::decode(framing::Buffer& buffer) +{ + first = buffer.getLongLong(); + second = buffer.getLongLong(); +} + +void ObjectId::encode(framing::Buffer& buffer) +{ + buffer.putLongLong(first); + buffer.putLongLong(second); +} + +bool ObjectId::operator==(const ObjectId& other) const +{ + return second == other.second && first == other.first; +} + +bool ObjectId::operator!=(const ObjectId& other) const +{ + return !(*this == other); +} + +bool ObjectId::operator<(const ObjectId& other) const +{ + if (first < other.first) + return true; + if (first > other.first) + return false; + return second < other.second; +} + +bool ObjectId::operator>(const ObjectId& other) const +{ + if (first > other.first) + return true; + if (first < other.first) + return false; + return second > other.second; +} + +bool ObjectId::operator<=(const ObjectId& other) const +{ + return !(*this > other); +} + +bool ObjectId::operator>=(const ObjectId& other) const +{ + return !(*this < other); +} + +ostream& qpid::console::operator<<(ostream& o, const ObjectId& id) +{ + o << (int) id.getFlags() << "-" << id.getSequence() << "-" << id.getBrokerBank() << "-" << + id.getAgentBank() << "-" << id.getObject(); + return o; +} + + diff --git a/cpp/src/qpid/sys/Runnable.h b/cpp/src/qpid/console/Package.cpp index fb3927c612..e5d6fa29fd 100644 --- a/cpp/src/qpid/sys/Runnable.h +++ b/cpp/src/qpid/console/Package.cpp @@ -1,5 +1,3 @@ -#ifndef _Runnable_ -#define _Runnable_ /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -21,30 +19,23 @@ * */ -#include <boost/function.hpp> +#include "qpid/console/Package.h" -namespace qpid { -namespace sys { +using namespace qpid::console; -/** - * Interface for objects that can be run, e.g. in a thread. - */ -class Runnable +SchemaClass* Package::getClass(const std::string& className, uint8_t* hash) { - public: - /** Type to represent a runnable as a Functor */ - typedef boost::function0<void> Functor; - - virtual ~Runnable(); - - /** Derived classes override run(). */ - virtual void run() = 0; - - /** Create a functor object that will call this->run(). */ - Functor functor(); -}; - -}} - - -#endif + NameHash key(className, hash); + ClassMap::iterator iter = classes.find(key); + if (iter != classes.end()) + return iter->second; + return 0; +} + +void Package::addClass(const std::string& className, uint8_t* hash, SchemaClass* schemaClass) +{ + NameHash key(className, hash); + ClassMap::iterator iter = classes.find(key); + if (iter == classes.end()) + classes[key] = schemaClass; +} diff --git a/cpp/src/qpid/console/Schema.cpp b/cpp/src/qpid/console/Schema.cpp new file mode 100644 index 0000000000..a3dbd91201 --- /dev/null +++ b/cpp/src/qpid/console/Schema.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 "qpid/console/Schema.h" +#include "qpid/console/Value.h" +#include "qpid/framing/FieldTable.h" + +using namespace qpid::console; +using namespace qpid; +using std::string; +using std::vector; + +SchemaArgument::SchemaArgument(framing::Buffer& buffer, bool forMethod) +{ + framing::FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + typeCode = map.getAsInt("type"); + unit = map.getAsString("unit"); + min = map.getAsInt("min"); + max = map.getAsInt("max"); + maxLen = map.getAsInt("maxlen"); + desc = map.getAsString("desc"); + + dirInput = false; + dirOutput = false; + if (forMethod) { + string dir(map.getAsString("dir")); + if (dir.find('I') != dir.npos || dir.find('i') != dir.npos) + dirInput = true; + if (dir.find('O') != dir.npos || dir.find('o') != dir.npos) + dirOutput = true; + } +} + +Value::Ptr SchemaArgument::decodeValue(framing::Buffer& buffer) +{ + return ValueFactory::newValue(typeCode, buffer); +} + +SchemaProperty::SchemaProperty(framing::Buffer& buffer) +{ + framing::FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + typeCode = map.getAsInt("type"); + accessCode = map.getAsInt("access"); + isIndex = map.getAsInt("index") != 0; + isOptional = map.getAsInt("optional") != 0; + unit = map.getAsString("unit"); + min = map.getAsInt("min"); + max = map.getAsInt("max"); + maxLen = map.getAsInt("maxlen"); + desc = map.getAsString("desc"); +} + +Value::Ptr SchemaProperty::decodeValue(framing::Buffer& buffer) +{ + return ValueFactory::newValue(typeCode, buffer); +} + +SchemaStatistic::SchemaStatistic(framing::Buffer& buffer) +{ + framing::FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + typeCode = map.getAsInt("type"); + unit = map.getAsString("unit"); + desc = map.getAsString("desc"); +} + +Value::Ptr SchemaStatistic::decodeValue(framing::Buffer& buffer) +{ + return ValueFactory::newValue(typeCode, buffer); +} + +SchemaMethod::SchemaMethod(framing::Buffer& buffer) +{ + framing::FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + desc = map.getAsString("desc"); + int argCount = map.getAsInt("argCount"); + + for (int i = 0; i < argCount; i++) + arguments.push_back(new SchemaArgument(buffer, true)); +} + +SchemaMethod::~SchemaMethod() +{ + for (vector<SchemaArgument*>::iterator iter = arguments.begin(); + iter != arguments.end(); iter++) + delete *iter; +} + +SchemaClass::SchemaClass(const uint8_t _kind, const ClassKey& _key, framing::Buffer& buffer) : + kind(_kind), key(_key) +{ + if (kind == KIND_TABLE) { + uint8_t hasSupertype = 0; //buffer.getOctet(); + uint16_t propCount = buffer.getShort(); + uint16_t statCount = buffer.getShort(); + uint16_t methodCount = buffer.getShort(); + + if (hasSupertype) { + string unused; + buffer.getShortString(unused); + buffer.getShortString(unused); + buffer.getLongLong(); + buffer.getLongLong(); + } + + for (uint16_t idx = 0; idx < propCount; idx++) + properties.push_back(new SchemaProperty(buffer)); + for (uint16_t idx = 0; idx < statCount; idx++) + statistics.push_back(new SchemaStatistic(buffer)); + for (uint16_t idx = 0; idx < methodCount; idx++) + methods.push_back(new SchemaMethod(buffer)); + + } else if (kind == KIND_EVENT) { + uint16_t argCount = buffer.getShort(); + + for (uint16_t idx = 0; idx < argCount; idx++) + arguments.push_back(new SchemaArgument(buffer)); + } +} + +SchemaClass::~SchemaClass() +{ + for (vector<SchemaProperty*>::iterator iter = properties.begin(); + iter != properties.end(); iter++) + delete *iter; + for (vector<SchemaStatistic*>::iterator iter = statistics.begin(); + iter != statistics.end(); iter++) + delete *iter; + for (vector<SchemaMethod*>::iterator iter = methods.begin(); + iter != methods.end(); iter++) + delete *iter; + for (vector<SchemaArgument*>::iterator iter = arguments.begin(); + iter != arguments.end(); iter++) + delete *iter; +} + diff --git a/cpp/src/qpid/console/SequenceManager.cpp b/cpp/src/qpid/console/SequenceManager.cpp new file mode 100644 index 0000000000..86ea829749 --- /dev/null +++ b/cpp/src/qpid/console/SequenceManager.cpp @@ -0,0 +1,48 @@ +/* + * + * 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/console/SequenceManager.h" + +using namespace qpid::console; +using namespace qpid::sys; +using std::string; +using std::cout; +using std::endl; + +uint32_t SequenceManager::reserve(const std::string& context) +{ + Mutex::ScopedLock l(lock); + uint32_t result = sequence++; + pending[result] = context; + return result; +} + +std::string SequenceManager::release(uint32_t seq) +{ + Mutex::ScopedLock l(lock); + std::map<uint32_t, string>::iterator iter = pending.find(seq); + if (iter == pending.end()) + return string(); + string result(iter->second); + pending.erase(iter); + return result; +} + diff --git a/cpp/src/qpid/console/SessionManager.cpp b/cpp/src/qpid/console/SessionManager.cpp new file mode 100644 index 0000000000..0285c5f34a --- /dev/null +++ b/cpp/src/qpid/console/SessionManager.cpp @@ -0,0 +1,481 @@ +/* + * + * 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/console/SessionManager.h" +#include "qpid/console/Schema.h" +#include "qpid/console/Agent.h" +#include "qpid/console/ConsoleListener.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/Uuid.h" +#include "qpid/framing/FieldTable.h" + +using namespace qpid::console; +using namespace qpid::sys; +using namespace qpid; +using namespace std; +using qpid::framing::Buffer; +using qpid::framing::FieldTable; + +SessionManager::SessionManager(ConsoleListener* _listener, Settings _settings) : + listener(_listener), settings(_settings) +{ + bindingKeys(); +} + +SessionManager::~SessionManager() +{ + for (vector<Broker*>::iterator iter = brokers.begin(); + iter != brokers.end(); iter++) + delete *iter; +} + +Broker* SessionManager::addBroker(client::ConnectionSettings& settings) +{ + Broker* broker(new Broker(*this, settings)); + { + Mutex::ScopedLock l(brokerListLock); + brokers.push_back(broker); + } + return broker; +} + +void SessionManager::delBroker(Broker* broker) +{ + Mutex::ScopedLock l(brokerListLock); + for (vector<Broker*>::iterator iter = brokers.begin(); + iter != brokers.end(); iter++) + if (*iter == broker) { + brokers.erase(iter); + delete broker; + return; + } +} + +void SessionManager::getPackages(NameVector& packageNames) +{ + allBrokersStable(); + packageNames.clear(); + { + Mutex::ScopedLock l(lock); + for (map<string, Package*>::iterator iter = packages.begin(); + iter != packages.end(); iter++) + packageNames.push_back(iter->first); + } +} + +void SessionManager::getClasses(KeyVector& classKeys, const std::string& packageName) +{ + allBrokersStable(); + classKeys.clear(); + map<string, Package*>::iterator iter = packages.find(packageName); + if (iter == packages.end()) + return; + + Package& package = *(iter->second); + for (Package::ClassMap::const_iterator piter = package.classes.begin(); + piter != package.classes.end(); piter++) { + ClassKey key(piter->second->getClassKey()); + classKeys.push_back(key); + } +} + +SchemaClass& SessionManager::getSchema(const ClassKey& classKey) +{ + allBrokersStable(); + map<string, Package*>::iterator iter = packages.find(classKey.getPackageName()); + if (iter == packages.end()) + throw Exception("Unknown package"); + + Package& package = *(iter->second); + Package::NameHash key(classKey.getClassName(), classKey.getHash()); + Package::ClassMap::iterator cIter = package.classes.find(key); + if (cIter == package.classes.end()) + throw Exception("Unknown class"); + + return *(cIter->second); +} + +void SessionManager::bindPackage(const std::string& packageName) +{ + stringstream key; + key << "console.obj.*.*." << packageName << ".#"; + bindingKeyList.push_back(key.str()); + for (vector<Broker*>::iterator iter = brokers.begin(); iter != brokers.end(); iter++) + (*iter)->addBinding(key.str()); +} + +void SessionManager::bindClass(const ClassKey& classKey) +{ + bindClass(classKey.getPackageName(), classKey.getClassName()); +} + +void SessionManager::bindClass(const std::string& packageName, const std::string& className) +{ + stringstream key; + key << "console.obj.*.*." << packageName << "." << className << ".#"; + bindingKeyList.push_back(key.str()); + for (vector<Broker*>::iterator iter = brokers.begin(); + iter != brokers.end(); iter++) + (*iter)->addBinding(key.str()); +} + +void SessionManager::getAgents(Agent::Vector& agents, Broker* broker) +{ + agents.clear(); + if (broker != 0) { + broker->appendAgents(agents); + } else { + for (vector<Broker*>::iterator iter = brokers.begin(); iter != brokers.end(); iter++) { + (*iter)->appendAgents(agents); + } + } +} + +void SessionManager::getObjects(Object::Vector& objects, const std::string& className, + Broker* _broker, Agent* _agent) +{ + Agent::Vector agentList; + + if (_agent != 0) { + agentList.push_back(_agent); + _agent->getBroker()->waitForStable(); + } else { + if (_broker != 0) { + _broker->appendAgents(agentList); + _broker->waitForStable(); + } else { + allBrokersStable(); + Mutex::ScopedLock _lock(brokerListLock); + for (vector<Broker*>::iterator iter = brokers.begin(); iter != brokers.end(); iter++) { + (*iter)->appendAgents(agentList); + } + } + } + + FieldTable ft; + uint32_t sequence; + ft.setString("_class", className); + + getResult.clear(); + syncSequenceList.clear(); + error = string(); + + if (agentList.empty()) { + objects = getResult; + return; + } + + for (Agent::Vector::iterator iter = agentList.begin(); iter != agentList.end(); iter++) { + Agent* agent = *iter; + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + stringstream routingKey; + routingKey << "agent." << agent->getBrokerBank() << "." << agent->getAgentBank(); + { + Mutex::ScopedLock _lock(lock); + sequence = sequenceManager.reserve("multiget"); + syncSequenceList.insert(sequence); + } + agent->getBroker()->encodeHeader(buffer, 'G', sequence); + ft.encode(buffer); + uint32_t length = buffer.getPosition(); + buffer.reset(); + agent->getBroker()->connThreadBody.sendBuffer(buffer, length, "qpid.management", routingKey.str()); + } + + { + Mutex::ScopedLock _lock(lock); + sys::AbsTime startTime = sys::now(); + while (!syncSequenceList.empty() && error.empty()) { + cv.wait(lock, AbsTime(now(), settings.getTimeout * TIME_SEC)); + sys::AbsTime currTime = sys::now(); + if (sys::Duration(startTime, currTime) > settings.getTimeout * TIME_SEC) + break; + } + } + + objects = getResult; +} + +void SessionManager::bindingKeys() +{ + bindingKeyList.push_back("schema.#"); + if (settings.rcvObjects && settings.rcvEvents && settings.rcvHeartbeats && !settings.userBindings) { + bindingKeyList.push_back("console.#"); + } else { + if (settings.rcvObjects && !settings.userBindings) + bindingKeyList.push_back("console.obj.#"); + else + bindingKeyList.push_back("console.obj.*.*.org.apache.qpid.broker.agent"); + if (settings.rcvEvents) + bindingKeyList.push_back("console.event.#"); + if (settings.rcvHeartbeats) + bindingKeyList.push_back("console.heartbeat"); + } +} + +void SessionManager::allBrokersStable() +{ + Mutex::ScopedLock l(brokerListLock); + for (vector<Broker*>::iterator iter = brokers.begin(); + iter != brokers.end(); iter++) + if ((*iter)->isConnected()) + (*iter)->waitForStable(); +} + +void SessionManager::startProtocol(Broker* broker) +{ + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + broker->encodeHeader(buffer, 'B'); + uint32_t length = 512 - buffer.available(); + buffer.reset(); + broker->connThreadBody.sendBuffer(buffer, length); +} + + +void SessionManager::handleBrokerResp(Broker* broker, Buffer& inBuffer, uint32_t) +{ + framing::Uuid brokerId; + + brokerId.decode(inBuffer); + broker->setBrokerId(brokerId); + + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + uint32_t sequence = sequenceManager.reserve("startup"); + broker->encodeHeader(buffer, 'P', sequence); + uint32_t length = 512 - buffer.available(); + buffer.reset(); + broker->connThreadBody.sendBuffer(buffer, length); + + if (listener != 0) { + listener->brokerInfo(*broker); + } +} + +void SessionManager::handlePackageInd(Broker* broker, Buffer& inBuffer, uint32_t) +{ + string packageName; + inBuffer.getShortString(packageName); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator iter = packages.find(packageName); + if (iter == packages.end()) { + packages[packageName] = new Package(packageName); + if (listener != 0) + listener->newPackage(packageName); + } + } + + broker->incOutstanding(); + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + uint32_t sequence = sequenceManager.reserve("startup"); + broker->encodeHeader(buffer, 'Q', sequence); + buffer.putShortString(packageName); + uint32_t length = 512 - buffer.available(); + buffer.reset(); + broker->connThreadBody.sendBuffer(buffer, length); +} + +void SessionManager::handleCommandComplete(Broker* broker, Buffer& inBuffer, uint32_t sequence) +{ + Mutex::ScopedLock l(lock); + uint32_t resultCode = inBuffer.getLong(); + string resultText; + inBuffer.getShortString(resultText); + string context = sequenceManager.release(sequence); + if (resultCode != 0) + QPID_LOG(debug, "Received error in completion: " << resultCode << " " << resultText); + if (context == "startup") { + broker->decOutstanding(); + } else if (context == "multiget") { + if (syncSequenceList.count(sequence) == 1) { + syncSequenceList.erase(sequence); + if (syncSequenceList.empty()) { + cv.notify(); + } + } + } + // TODO: Other context cases +} + +void SessionManager::handleClassInd(Broker* broker, Buffer& inBuffer, uint32_t) +{ + uint8_t kind; + string packageName; + string className; + uint8_t hash[16]; + + kind = inBuffer.getOctet(); + inBuffer.getShortString(packageName); + inBuffer.getShortString(className); + inBuffer.getBin128(hash); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator pIter = packages.find(packageName); + if (pIter == packages.end() || pIter->second->getClass(className, hash)) + return; + } + + broker->incOutstanding(); + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + uint32_t sequence = sequenceManager.reserve("startup"); + broker->encodeHeader(buffer, 'S', sequence); + buffer.putShortString(packageName); + buffer.putShortString(className); + buffer.putBin128(hash); + uint32_t length = 512 - buffer.available(); + buffer.reset(); + broker->connThreadBody.sendBuffer(buffer, length); +} + +void SessionManager::handleMethodResp(Broker* broker, Buffer& buffer, uint32_t sequence) +{ + if (broker->methodObject) { + broker->methodObject->handleMethodResp(buffer, sequence); + } +} + +void SessionManager::handleHeartbeatInd(Broker* /*broker*/, Buffer& /*inBuffer*/, uint32_t /*sequence*/) +{ +} + +void SessionManager::handleEventInd(Broker* broker, Buffer& buffer, uint32_t /*sequence*/) +{ + string packageName; + string className; + uint8_t hash[16]; + SchemaClass* schemaClass; + + buffer.getShortString(packageName); + buffer.getShortString(className); + buffer.getBin128(hash); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator pIter = packages.find(packageName); + if (pIter == packages.end()) + return; + schemaClass = pIter->second->getClass(className, hash); + if (schemaClass == 0) + return; + } + + Event event(broker, schemaClass, buffer); + + if (listener) + listener->event(event); +} + +void SessionManager::handleSchemaResp(Broker* broker, Buffer& inBuffer, uint32_t sequence) +{ + uint8_t kind; + string packageName; + string className; + uint8_t hash[16]; + + kind = inBuffer.getOctet(); + inBuffer.getShortString(packageName); + inBuffer.getShortString(className); + inBuffer.getBin128(hash); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator pIter = packages.find(packageName); + if (pIter != packages.end() && !pIter->second->getClass(className, hash)) { + ClassKey key(packageName, className, hash); + SchemaClass* schemaClass(new SchemaClass(kind, key, inBuffer)); + pIter->second->addClass(className, hash, schemaClass); + if (listener != 0) { + listener->newClass(schemaClass->getClassKey()); + } + } + } + + sequenceManager.release(sequence); + broker->decOutstanding(); +} + +void SessionManager::handleContentInd(Broker* broker, Buffer& buffer, uint32_t sequence, bool prop, bool stat) +{ + string packageName; + string className; + uint8_t hash[16]; + SchemaClass* schemaClass; + + buffer.getShortString(packageName); + buffer.getShortString(className); + buffer.getBin128(hash); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator pIter = packages.find(packageName); + if (pIter == packages.end()) + return; + schemaClass = pIter->second->getClass(className, hash); + if (schemaClass == 0) + return; + } + + Object object(broker, schemaClass, buffer, prop, stat); + + if (prop && className == "agent" && packageName == "org.apache.qpid.broker") + broker->updateAgent(object); + + { + Mutex::ScopedLock l(lock); + if (syncSequenceList.count(sequence) == 1) { + if (!object.isDeleted()) + getResult.push_back(object); + return; + } + } + + if (listener) { + if (prop) + listener->objectProps(*broker, object); + if (stat) + listener->objectStats(*broker, object); + } +} + +void SessionManager::handleBrokerConnect(Broker* broker) +{ + if (listener != 0) + listener->brokerConnected(*broker); +} + +void SessionManager::handleBrokerDisconnect(Broker* broker) +{ + if (listener != 0) + listener->brokerDisconnected(*broker); +} + diff --git a/cpp/src/qpid/console/Value.cpp b/cpp/src/qpid/console/Value.cpp new file mode 100644 index 0000000000..c30660f1dc --- /dev/null +++ b/cpp/src/qpid/console/Value.cpp @@ -0,0 +1,169 @@ +/* + * + * 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/console/Value.h" +#include "qpid/framing/Buffer.h" + +using namespace qpid; +using namespace qpid::console; +using namespace std; + +string NullValue::str() const +{ + return "<Null>"; +} + +RefValue::RefValue(framing::Buffer& buffer) +{ + uint64_t first = buffer.getLongLong(); + uint64_t second = buffer.getLongLong(); + value.setValue(first, second); +} + +string RefValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string UintValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string IntValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string Uint64Value::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string Int64Value::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +StringValue::StringValue(framing::Buffer& buffer, int tc) +{ + if (tc == 6) + buffer.getShortString(value); + else + buffer.getMediumString(value); +} + +string BoolValue::str() const +{ + return value ? "T" : "F"; +} + +string FloatValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string DoubleValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +UuidValue::UuidValue(framing::Buffer& buffer) +{ + value.decode(buffer); +} + +string MapValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +MapValue::MapValue(framing::Buffer& buffer) +{ + value.decode(buffer); +} + + +Value::Ptr ValueFactory::newValue(int typeCode, framing::Buffer& buffer) +{ + switch (typeCode) { + case 1: return Value::Ptr(new UintValue(buffer.getOctet())); // U8 + case 2: return Value::Ptr(new UintValue(buffer.getShort())); // U16 + case 3: return Value::Ptr(new UintValue(buffer.getLong())); // U32 + case 4: return Value::Ptr(new Uint64Value(buffer.getLongLong())); // U64 + case 6: return Value::Ptr(new StringValue(buffer, 6)); // SSTR + case 7: return Value::Ptr(new StringValue(buffer, 7)); // LSTR + case 8: return Value::Ptr(new Int64Value(buffer.getLongLong())); // ABSTIME + case 9: return Value::Ptr(new Uint64Value(buffer.getLongLong())); // DELTATIME + case 10: return Value::Ptr(new RefValue(buffer)); // REF + case 11: return Value::Ptr(new BoolValue(buffer.getOctet())); // BOOL + case 12: return Value::Ptr(new FloatValue(buffer.getFloat())); // FLOAT + case 13: return Value::Ptr(new DoubleValue(buffer.getDouble())); // DOUBLE + case 14: return Value::Ptr(new UuidValue(buffer)); // UUID + case 15: return Value::Ptr(new MapValue(buffer)); // MAP + case 16: return Value::Ptr(new IntValue(buffer.getOctet())); // S8 + case 17: return Value::Ptr(new IntValue(buffer.getShort())); // S16 + case 18: return Value::Ptr(new IntValue(buffer.getLong())); // S32 + case 19: return Value::Ptr(new Int64Value(buffer.getLongLong())); // S64 + } + + return Value::Ptr(); +} + +void ValueFactory::encodeValue(int typeCode, Value::Ptr value, framing::Buffer& buffer) +{ + switch (typeCode) { + case 1: buffer.putOctet(value->asUint()); return; // U8 + case 2: buffer.putShort(value->asUint()); return; // U16 + case 3: buffer.putLong(value->asUint()); return; // U32 + case 4: buffer.putLongLong(value->asUint64()); return; // U64 + case 6: buffer.putShortString(value->asString()); return; // SSTR + case 7: buffer.putMediumString(value->asString()); return; // LSTR + case 8: buffer.putLongLong(value->asInt64()); return; // ABSTIME + case 9: buffer.putLongLong(value->asUint64()); return; // DELTATIME + case 10: value->asObjectId().encode(buffer); return; // REF + case 11: buffer.putOctet(value->asBool() ? 1 : 0); return; // BOOL + case 12: buffer.putFloat(value->asFloat()); return; // FLOAT + case 13: buffer.putDouble(value->asDouble()); return; // DOUBLE + case 14: value->asUuid().encode(buffer); return; // UUID + case 15: value->asMap().encode(buffer); return; // MAP + case 16: buffer.putOctet(value->asInt()); return; // S8 + case 17: buffer.putShort(value->asInt()); return; // S16 + case 18: buffer.putLong(value->asInt()); return; // S32 + case 19: buffer.putLongLong(value->asInt64()); return; // S64 + } +} diff --git a/cpp/src/qpid/doxygen_mainpage.h b/cpp/src/qpid/doxygen_mainpage.h deleted file mode 100644 index 1502ef536e..0000000000 --- a/cpp/src/qpid/doxygen_mainpage.h +++ /dev/null @@ -1,28 +0,0 @@ -// This header file is just for doxygen documentation purposes. - -/** \mainpage Qpid C++ Developer Kit. - * - * The <a href=http://incubator.apache.org/qpid>Qpid project</a> provides implementations of the <a href="http://amqp.org/">AMQP messaging specification</a> in several programming language. - * - * Qpidc provides APIs and libraries to implement AMQP clients in - * C++. Qpidc clients can interact with any compliant AMQP message - * broker. The Qpid project also provides an AMQP broker daemon called - * qpidd that you can use with your qpidc clients. - * - * See the \ref clientapi "client API reference" to get started. - * - */ - - -/** - * \defgroup clientapi Application API for an AMQP client. - * - * A typical client takes the following steps: - * - Connect to the broker using qpid::client::Connection::open() - * - Create a qpid::client::Session object. - * - * Once a session is created the client can work with the broker: - * - Create and bind queues using the qpid::client::Session commands. - * - Send messages using qpid::client::Session::messageTransfer. - * - Subscribe to queues using qpid::client::SubscriptionManager - */ diff --git a/cpp/src/qpid/framing/AMQBody.h b/cpp/src/qpid/framing/AMQBody.h index f3bf65470c..60ac2d3b7e 100644 --- a/cpp/src/qpid/framing/AMQBody.h +++ b/cpp/src/qpid/framing/AMQBody.h @@ -22,8 +22,11 @@ * */ #include "qpid/framing/amqp_types.h" - +#include "qpid/RefCounted.h" +#include "qpid/framing/BodyFactory.h" +#include <boost/intrusive_ptr.hpp> #include <ostream> +#include "qpid/CommonImportExport.h" namespace qpid { namespace framing { @@ -43,16 +46,20 @@ struct AMQBodyConstVisitor { virtual void visit(const AMQMethodBody&) = 0; }; -class AMQBody -{ +class AMQBody : public RefCounted { public: - virtual ~AMQBody(); + AMQBody() {} + QPID_COMMON_EXTERN virtual ~AMQBody(); + + // Make AMQBody copyable even though RefCounted. + AMQBody(const AMQBody&) : RefCounted() {} + AMQBody& operator=(const AMQBody&) { return *this; } virtual uint8_t type() const = 0; virtual void encode(Buffer& buffer) const = 0; virtual void decode(Buffer& buffer, uint32_t=0) = 0; - virtual uint32_t size() const = 0; + virtual uint32_t encodedSize() const = 0; virtual void print(std::ostream& out) const = 0; virtual void accept(AMQBodyConstVisitor&) const = 0; @@ -62,9 +69,10 @@ class AMQBody /** Match if same type and same class/method ID for methods */ static bool match(const AMQBody& , const AMQBody& ); + virtual boost::intrusive_ptr<AMQBody> clone() const = 0; }; -std::ostream& operator<<(std::ostream& out, const AMQBody& body) ; +QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& out, const AMQBody& body) ; enum BodyTypes { METHOD_BODY = 1, diff --git a/cpp/src/qpid/framing/AMQCommandControlBody.h b/cpp/src/qpid/framing/AMQCommandControlBody.h index 388fb48299..d12b70a168 100644 --- a/cpp/src/qpid/framing/AMQCommandControlBody.h +++ b/cpp/src/qpid/framing/AMQCommandControlBody.h @@ -43,7 +43,7 @@ template <class T> class AMQCommandControlBody : public AMQBody, public T virtual void decode(Buffer& buffer, uint32_t=0) { Codec::decode(buffer.getIterator(), static_cast<T&>(*this)); } - virtual uint32_t size() const { + virtual uint32_t encodedSize() const { Codec::size(buffer.getIterator(), static_cast<const T&>(*this)); } diff --git a/cpp/src/qpid/framing/AMQContentBody.cpp b/cpp/src/qpid/framing/AMQContentBody.cpp index 59f3619ef2..72f7d9978e 100644 --- a/cpp/src/qpid/framing/AMQContentBody.cpp +++ b/cpp/src/qpid/framing/AMQContentBody.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "AMQContentBody.h" +#include "qpid/framing/AMQContentBody.h" #include <iostream> qpid::framing::AMQContentBody::AMQContentBody(){ @@ -27,7 +27,7 @@ qpid::framing::AMQContentBody::AMQContentBody(){ qpid::framing::AMQContentBody::AMQContentBody(const string& _data) : data(_data){ } -uint32_t qpid::framing::AMQContentBody::size() const{ +uint32_t qpid::framing::AMQContentBody::encodedSize() const{ return data.size(); } void qpid::framing::AMQContentBody::encode(Buffer& buffer) const{ @@ -39,6 +39,8 @@ void qpid::framing::AMQContentBody::decode(Buffer& buffer, uint32_t _size){ void qpid::framing::AMQContentBody::print(std::ostream& out) const { - out << "content (" << size() << " bytes)"; - out << " " << data.substr(0,16) << "..."; + out << "content (" << encodedSize() << " bytes)"; + const size_t max = 32; + out << " " << data.substr(0, max); + if (data.size() > max) out << "..."; } diff --git a/cpp/src/qpid/framing/AMQContentBody.h b/cpp/src/qpid/framing/AMQContentBody.h index 5d530a1b9a..69813b221c 100644 --- a/cpp/src/qpid/framing/AMQContentBody.h +++ b/cpp/src/qpid/framing/AMQContentBody.h @@ -18,9 +18,10 @@ * under the License. * */ -#include "amqp_types.h" -#include "AMQBody.h" -#include "Buffer.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Buffer.h" +#include "qpid/CommonImportExport.h" #ifndef _AMQContentBody_ #define _AMQContentBody_ @@ -33,17 +34,18 @@ class AMQContentBody : public AMQBody string data; public: - AMQContentBody(); - AMQContentBody(const string& data); + QPID_COMMON_EXTERN AMQContentBody(); + QPID_COMMON_EXTERN AMQContentBody(const string& data); inline virtual ~AMQContentBody(){} - inline uint8_t type() const { return CONTENT_BODY; }; - inline const string& getData() const { return data; } - inline string& getData() { return data; } - uint32_t size() const; - void encode(Buffer& buffer) const; - void decode(Buffer& buffer, uint32_t size); - void print(std::ostream& out) const; - void accept(AMQBodyConstVisitor& v) const { v.visit(*this); } + QPID_COMMON_EXTERN inline uint8_t type() const { return CONTENT_BODY; }; + QPID_COMMON_EXTERN inline const string& getData() const { return data; } + QPID_COMMON_EXTERN inline string& getData() { return data; } + QPID_COMMON_EXTERN uint32_t encodedSize() const; + QPID_COMMON_EXTERN void encode(Buffer& buffer) const; + QPID_COMMON_EXTERN void decode(Buffer& buffer, uint32_t size); + QPID_COMMON_EXTERN void print(std::ostream& out) const; + QPID_COMMON_EXTERN void accept(AMQBodyConstVisitor& v) const { v.visit(*this); } + QPID_COMMON_EXTERN boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } }; } diff --git a/cpp/src/qpid/framing/AMQDataBlock.h b/cpp/src/qpid/framing/AMQDataBlock.h index 9b6fdfd966..7f0d0dc2b5 100644 --- a/cpp/src/qpid/framing/AMQDataBlock.h +++ b/cpp/src/qpid/framing/AMQDataBlock.h @@ -18,7 +18,7 @@ * under the License. * */ -#include "Buffer.h" +#include "qpid/framing/Buffer.h" #ifndef _AMQDataBlock_ #define _AMQDataBlock_ @@ -32,7 +32,7 @@ public: virtual ~AMQDataBlock() {} virtual void encode(Buffer& buffer) const = 0; virtual bool decode(Buffer& buffer) = 0; - virtual uint32_t size() const = 0; + virtual uint32_t encodedSize() const = 0; }; } diff --git a/cpp/src/qpid/framing/AMQFrame.cpp b/cpp/src/qpid/framing/AMQFrame.cpp index c1fc647b52..5c5920d786 100644 --- a/cpp/src/qpid/framing/AMQFrame.cpp +++ b/cpp/src/qpid/framing/AMQFrame.cpp @@ -18,33 +18,66 @@ * under the License. * */ -#include "AMQFrame.h" +#include "qpid/framing/AMQFrame.h" -#include "qpid/framing/variant.h" #include "qpid/framing/AMQMethodBody.h" #include "qpid/framing/reply_exceptions.h" - +#include "qpid/framing/BodyFactory.h" +#include "qpid/framing/MethodBodyFactory.h" #include <boost/format.hpp> - #include <iostream> namespace qpid { namespace framing { +void AMQFrame::init() { + bof = eof = bos = eos = true; + subchannel=0; + channel=0; + encodedSizeCache = 0; +} + +AMQFrame::AMQFrame(const boost::intrusive_ptr<AMQBody>& b) : body(b) { init(); } + +AMQFrame::AMQFrame(const AMQBody& b) : body(b.clone()) { init(); } + AMQFrame::~AMQFrame() {} -void AMQFrame::setBody(const AMQBody& b) { body = new BodyHolder(b); } +AMQBody* AMQFrame::getBody() { + // Non-const AMQBody* may be used to modify the body. + encodedSizeCache = 0; + return body.get(); +} + +const AMQBody* AMQFrame::getBody() const { + return body.get(); +} -void AMQFrame::setMethod(ClassId c, MethodId m) { body = new BodyHolder(c,m); } +void AMQFrame::setMethod(ClassId c, MethodId m) { + encodedSizeCache = 0; + body = MethodBodyFactory::create(c,m); +} -uint32_t AMQFrame::size() const { - return frameOverhead() + body->size(); +uint32_t AMQFrame::encodedSize() const { + if (!encodedSizeCache) { + encodedSizeCache = frameOverhead() + body->encodedSize(); + if (body->getMethod()) + encodedSizeCache += sizeof(ClassId)+sizeof(MethodId); + } + return encodedSizeCache; } uint32_t AMQFrame::frameOverhead() { return 12 /*frame header*/; } +uint16_t AMQFrame::DECODE_SIZE_MIN=4; + +uint16_t AMQFrame::decodeSize(char* data) { + Buffer buf(data+2, DECODE_SIZE_MIN); + return buf.getShort(); +} + void AMQFrame::encode(Buffer& buffer) const { //set track first (controls on track 0, everything else on 1): @@ -53,20 +86,27 @@ void AMQFrame::encode(Buffer& buffer) const uint8_t flags = (bof ? 0x08 : 0) | (eof ? 0x04 : 0) | (bos ? 0x02 : 0) | (eos ? 0x01 : 0); buffer.putOctet(flags); buffer.putOctet(getBody()->type()); - buffer.putShort(size()); + buffer.putShort(encodedSize()); buffer.putOctet(0); buffer.putOctet(0x0f & track); buffer.putShort(channel); buffer.putLong(0); + const AMQMethodBody* method=getMethod(); + if (method) { + buffer.putOctet(method->amqpClassId()); + buffer.putOctet(method->amqpMethodId()); + } body->encode(buffer); } bool AMQFrame::decode(Buffer& buffer) -{ +{ if(buffer.available() < frameOverhead()) return false; buffer.record(); + encodedSizeCache = 0; + uint32_t start = buffer.getPosition(); uint8_t flags = buffer.getOctet(); uint8_t framing_version = (flags & 0xc0) >> 6; if (framing_version != 0) @@ -98,8 +138,24 @@ bool AMQFrame::decode(Buffer& buffer) buffer.restore(); return false; } - body = new BodyHolder(); - body->decode(type,buffer, body_size); + + switch(type) + { + case 0://CONTROL + case METHOD_BODY: { + ClassId c = buffer.getOctet(); + MethodId m = buffer.getOctet(); + body = MethodBodyFactory::create(c, m); + break; + } + case HEADER_BODY: body = BodyFactory::create<AMQHeaderBody>(); break; + case CONTENT_BODY: body = BodyFactory::create<AMQContentBody>(); break; + case HEARTBEAT_BODY: body = BodyFactory::create<AMQHeartbeatBody>(); break; + default: + throw IllegalArgumentException(QPID_MSG("Invalid frame type " << type)); + } + body->decode(buffer, body_size); + encodedSizeCache = buffer.getPosition() - start; return true; } diff --git a/cpp/src/qpid/framing/AMQFrame.h b/cpp/src/qpid/framing/AMQFrame.h index 5a8e55f9d2..29e368b671 100644 --- a/cpp/src/qpid/framing/AMQFrame.h +++ b/cpp/src/qpid/framing/AMQFrame.h @@ -21,50 +21,33 @@ * under the License. * */ -#include "AMQDataBlock.h" -#include "AMQHeaderBody.h" -#include "AMQContentBody.h" -#include "AMQHeartbeatBody.h" -#include "ProtocolVersion.h" -#include "BodyHolder.h" - +#include "qpid/framing/AMQDataBlock.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/AMQHeartbeatBody.h" +#include "qpid/framing/ProtocolVersion.h" #include <boost/intrusive_ptr.hpp> #include <boost/cast.hpp> +#include "qpid/CommonImportExport.h" namespace qpid { namespace framing { -class BodyHolder; - class AMQFrame : public AMQDataBlock { public: - AMQFrame(boost::intrusive_ptr<BodyHolder> b=0) : body(b) { init(); } - AMQFrame(const AMQBody& b) { setBody(b); init(); } - ~AMQFrame(); - - template <class InPlace> - AMQFrame(const InPlace& ip, typename EnableInPlace<InPlace>::type* =0) { - init(); setBody(ip); - } + QPID_COMMON_EXTERN AMQFrame(const boost::intrusive_ptr<AMQBody>& b=0); + QPID_COMMON_EXTERN AMQFrame(const AMQBody& b); + QPID_COMMON_EXTERN ~AMQFrame(); ChannelId getChannel() const { return channel; } void setChannel(ChannelId c) { channel = c; } - boost::intrusive_ptr<BodyHolder> getHolder() { return body; } - - AMQBody* getBody() { return body ? body->get() : 0; } - const AMQBody* getBody() const { return body ? body->get() : 0; } + QPID_COMMON_EXTERN AMQBody* getBody(); + QPID_COMMON_EXTERN const AMQBody* getBody() const; - AMQMethodBody* getMethod() { return getBody()->getMethod(); } - const AMQMethodBody* getMethod() const { return getBody()->getMethod(); } - - void setBody(const AMQBody& b); - - template <class InPlace> - typename EnableInPlace<InPlace>::type setBody(const InPlace& ip) { - body = new BodyHolder(ip); - } + AMQMethodBody* getMethod() { return getBody() ? getBody()->getMethod() : 0; } + const AMQMethodBody* getMethod() const { return getBody() ? getBody()->getMethod() : 0; } void setMethod(ClassId c, MethodId m); @@ -76,9 +59,9 @@ class AMQFrame : public AMQDataBlock return boost::polymorphic_downcast<const T*>(getBody()); } - void encode(Buffer& buffer) const; - bool decode(Buffer& buffer); - uint32_t size() const; + QPID_COMMON_EXTERN void encode(Buffer& buffer) const; + QPID_COMMON_EXTERN bool decode(Buffer& buffer); + QPID_COMMON_EXTERN uint32_t encodedSize() const; // 0-10 terminology: first/last frame (in segment) first/last segment (in assembly) @@ -104,20 +87,25 @@ class AMQFrame : public AMQDataBlock bool getEos() const { return eos; } void setEos(bool isEos) { eos = isEos; } - static uint32_t frameOverhead(); + static uint16_t DECODE_SIZE_MIN; + QPID_COMMON_EXTERN static uint32_t frameOverhead(); + /** Must point to at least DECODE_SIZE_MIN bytes of data */ + static uint16_t decodeSize(char* data); + private: - void init() { bof = eof = bos = eos = true; subchannel=0; channel=0; } + void init(); - boost::intrusive_ptr<BodyHolder> body; + boost::intrusive_ptr<AMQBody> body; uint16_t channel : 16; uint8_t subchannel : 8; bool bof : 1; bool eof : 1; bool bos : 1; bool eos : 1; + mutable uint32_t encodedSizeCache; }; -std::ostream& operator<<(std::ostream&, const AMQFrame&); +QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const AMQFrame&); }} // namespace qpid::framing diff --git a/cpp/src/qpid/framing/AMQHeaderBody.cpp b/cpp/src/qpid/framing/AMQHeaderBody.cpp index 724c288705..14218f1b45 100644 --- a/cpp/src/qpid/framing/AMQHeaderBody.cpp +++ b/cpp/src/qpid/framing/AMQHeaderBody.cpp @@ -18,12 +18,12 @@ * under the License. * */ -#include "AMQHeaderBody.h" +#include "qpid/framing/AMQHeaderBody.h" #include "qpid/Exception.h" #include "qpid/log/Statement.h" -uint32_t qpid::framing::AMQHeaderBody::size() const { - return properties.size(); +uint32_t qpid::framing::AMQHeaderBody::encodedSize() const { + return properties.encodedSize(); } void qpid::framing::AMQHeaderBody::encode(Buffer& buffer) const { @@ -52,7 +52,7 @@ uint64_t qpid::framing::AMQHeaderBody::getContentLength() const void qpid::framing::AMQHeaderBody::print(std::ostream& out) const { - out << "header (" << size() << " bytes)"; + out << "header (" << encodedSize() << " bytes)"; out << "; properties={"; properties.print(out); out << "}"; diff --git a/cpp/src/qpid/framing/AMQHeaderBody.h b/cpp/src/qpid/framing/AMQHeaderBody.h index c69a768291..8d96e35720 100644 --- a/cpp/src/qpid/framing/AMQHeaderBody.h +++ b/cpp/src/qpid/framing/AMQHeaderBody.h @@ -21,11 +21,12 @@ * under the License. * */ -#include "amqp_types.h" -#include "AMQBody.h" -#include "Buffer.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Buffer.h" #include "qpid/framing/DeliveryProperties.h" #include "qpid/framing/MessageProperties.h" +#include "qpid/CommonImportExport.h" #include <iostream> #include <boost/optional.hpp> @@ -34,16 +35,14 @@ namespace qpid { namespace framing { -enum DeliveryMode { TRANSIENT = 1, PERSISTENT = 2}; - class AMQHeaderBody : public AMQBody { template <class T> struct OptProps { boost::optional<T> props; }; template <class Base, class T> struct PropSet : public Base, public OptProps<T> { - uint32_t size() const { + uint32_t encodedSize() const { const boost::optional<T>& p=this->OptProps<T>::props; - return (p ? p->size() : 0) + Base::size(); + return (p ? p->encodedSize() : 0) + Base::encodedSize(); } void encode(Buffer& buffer) const { const boost::optional<T>& p=this->OptProps<T>::props; @@ -68,7 +67,7 @@ class AMQHeaderBody : public AMQBody }; struct Empty { - uint32_t size() const { return 0; } + uint32_t encodedSize() const { return 0; } void encode(Buffer&) const {}; bool decode(Buffer&, uint32_t, uint16_t) const { return false; }; void print(std::ostream&) const {} @@ -83,12 +82,12 @@ public: inline uint8_t type() const { return HEADER_BODY; } - uint32_t size() const; - void encode(Buffer& buffer) const; - void decode(Buffer& buffer, uint32_t size); - uint64_t getContentLength() const; - void print(std::ostream& out) const; - void accept(AMQBodyConstVisitor&) const; + QPID_COMMON_EXTERN uint32_t encodedSize() const; + QPID_COMMON_EXTERN void encode(Buffer& buffer) const; + QPID_COMMON_EXTERN void decode(Buffer& buffer, uint32_t size); + QPID_COMMON_EXTERN uint64_t getContentLength() const; + QPID_COMMON_EXTERN void print(std::ostream& out) const; + QPID_COMMON_EXTERN void accept(AMQBodyConstVisitor&) const; template <class T> T* get(bool create) { boost::optional<T>& p=properties.OptProps<T>::props; @@ -99,6 +98,8 @@ public: template <class T> const T* get() const { return properties.OptProps<T>::props.get_ptr(); } + + boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } }; }} diff --git a/cpp/src/qpid/framing/AMQHeartbeatBody.cpp b/cpp/src/qpid/framing/AMQHeartbeatBody.cpp index 140ce2e794..477616221c 100644 --- a/cpp/src/qpid/framing/AMQHeartbeatBody.cpp +++ b/cpp/src/qpid/framing/AMQHeartbeatBody.cpp @@ -19,7 +19,7 @@ * */ -#include "AMQHeartbeatBody.h" +#include "qpid/framing/AMQHeartbeatBody.h" #include <iostream> qpid::framing::AMQHeartbeatBody::~AMQHeartbeatBody() {} diff --git a/cpp/src/qpid/framing/AMQHeartbeatBody.h b/cpp/src/qpid/framing/AMQHeartbeatBody.h index a2701c3398..9b1fe8a4c1 100644 --- a/cpp/src/qpid/framing/AMQHeartbeatBody.h +++ b/cpp/src/qpid/framing/AMQHeartbeatBody.h @@ -18,9 +18,10 @@ * under the License. * */ -#include "amqp_types.h" -#include "AMQBody.h" -#include "Buffer.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Buffer.h" +#include "qpid/CommonImportExport.h" #ifndef _AMQHeartbeatBody_ #define _AMQHeartbeatBody_ @@ -31,13 +32,14 @@ namespace framing { class AMQHeartbeatBody : public AMQBody { public: - virtual ~AMQHeartbeatBody(); - inline uint32_t size() const { return 0; } + QPID_COMMON_EXTERN virtual ~AMQHeartbeatBody(); + inline uint32_t encodedSize() const { return 0; } inline uint8_t type() const { return HEARTBEAT_BODY; } inline void encode(Buffer& ) const {} inline void decode(Buffer& , uint32_t /*size*/) {} - virtual void print(std::ostream& out) const; + QPID_COMMON_EXTERN virtual void print(std::ostream& out) const; void accept(AMQBodyConstVisitor& v) const { v.visit(*this); } + boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } }; } diff --git a/cpp/src/qpid/framing/AMQMethodBody.cpp b/cpp/src/qpid/framing/AMQMethodBody.cpp index 924d906d43..594af4c6dc 100644 --- a/cpp/src/qpid/framing/AMQMethodBody.cpp +++ b/cpp/src/qpid/framing/AMQMethodBody.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "AMQMethodBody.h" +#include "qpid/framing/AMQMethodBody.h" namespace qpid { namespace framing { diff --git a/cpp/src/qpid/framing/AMQMethodBody.h b/cpp/src/qpid/framing/AMQMethodBody.h index da28ee3aa9..c634180712 100644 --- a/cpp/src/qpid/framing/AMQMethodBody.h +++ b/cpp/src/qpid/framing/AMQMethodBody.h @@ -21,13 +21,13 @@ * under the License. * */ -#include "amqp_types.h" -#include "AMQBody.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/AMQBody.h" #include "qpid/framing/ProtocolVersion.h" -#include "qpid/shared_ptr.h" +#include "qpid/CommonImportExport.h" +#include <boost/shared_ptr.hpp> #include <ostream> - #include <assert.h> namespace qpid { @@ -40,7 +40,7 @@ class MethodBodyConstVisitor; class AMQMethodBody : public AMQBody { public: AMQMethodBody() {} - virtual ~AMQMethodBody(); + QPID_COMMON_EXTERN virtual ~AMQMethodBody(); virtual void accept(MethodBodyConstVisitor&) const = 0; @@ -54,7 +54,7 @@ class AMQMethodBody : public AMQBody { return amqpClassId()==T::CLASS_ID && amqpMethodId()==T::METHOD_ID; } - virtual uint32_t size() const = 0; + virtual uint32_t encodedSize() const = 0; virtual uint8_t type() const { return METHOD_BODY; } virtual bool isSync() const { return false; /*only ModelMethods can have the sync flag set*/ } diff --git a/cpp/src/qpid/framing/AccumulatedAck.cpp b/cpp/src/qpid/framing/AccumulatedAck.cpp index 2d3ecf3f6a..2e6433a82f 100644 --- a/cpp/src/qpid/framing/AccumulatedAck.cpp +++ b/cpp/src/qpid/framing/AccumulatedAck.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "AccumulatedAck.h" +#include "qpid/framing/AccumulatedAck.h" #include <assert.h> #include <iostream> diff --git a/cpp/src/qpid/framing/AccumulatedAck.h b/cpp/src/qpid/framing/AccumulatedAck.h index ea78b797e0..8e241b4ba1 100644 --- a/cpp/src/qpid/framing/AccumulatedAck.h +++ b/cpp/src/qpid/framing/AccumulatedAck.h @@ -25,8 +25,9 @@ #include <functional> #include <list> #include <ostream> -#include "SequenceNumber.h" -#include "SequenceNumberSet.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/SequenceNumberSet.h" +#include "qpid/CommonImportExport.h" namespace qpid { namespace framing { @@ -58,17 +59,17 @@ namespace qpid { */ std::list<Range> ranges; - explicit AccumulatedAck(SequenceNumber r = SequenceNumber()); - void update(SequenceNumber firstTag, SequenceNumber lastTag); - void consolidate(); - void clear(); - bool covers(SequenceNumber tag) const; + QPID_COMMON_EXTERN explicit AccumulatedAck(SequenceNumber r = SequenceNumber()); + QPID_COMMON_EXTERN void update(SequenceNumber firstTag, SequenceNumber lastTag); + QPID_COMMON_EXTERN void consolidate(); + QPID_COMMON_EXTERN void clear(); + QPID_COMMON_EXTERN bool covers(SequenceNumber tag) const; void collectRanges(SequenceNumberSet& set) const; - void update(const SequenceNumber cumulative, const SequenceNumberSet& range); + QPID_COMMON_EXTERN void update(const SequenceNumber cumulative, const SequenceNumberSet& range); void operator()(SequenceNumber first, SequenceNumber last) { update(first, last); } }; - std::ostream& operator<<(std::ostream&, const Range&); - std::ostream& operator<<(std::ostream&, const AccumulatedAck&); + QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const Range&); + QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const AccumulatedAck&); } } diff --git a/cpp/src/qpid/framing/Array.cpp b/cpp/src/qpid/framing/Array.cpp index f0b6331ff3..d95e0d167d 100644 --- a/cpp/src/qpid/framing/Array.cpp +++ b/cpp/src/qpid/framing/Array.cpp @@ -18,9 +18,9 @@ * under the License. * */ -#include "Array.h" -#include "Buffer.h" -#include "FieldValue.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldValue.h" #include "qpid/Exception.h" #include "qpid/framing/reply_exceptions.h" #include <assert.h> @@ -28,25 +28,26 @@ namespace qpid { namespace framing { -Array::Array() : typeOctet(0xF0/*void*/) {} +Array::Array() : type(TYPE_CODE_VOID) {} -Array::Array(uint8_t type) : typeOctet(type) {} +Array::Array(TypeCode t) : type(t) {} + +Array::Array(uint8_t t) : type(typeCode(t)) {} Array::Array(const std::vector<std::string>& in) { - typeOctet = 0xA4; + type = TYPE_CODE_STR16; for (std::vector<std::string>::const_iterator i = in.begin(); i != in.end(); ++i) { - ValuePtr value(new StringValue(*i)); + ValuePtr value(new Str16Value(*i)); values.push_back(value); } } - -uint32_t Array::size() const { +uint32_t Array::encodedSize() const { //note: size is only included when used as a 'top level' type uint32_t len(4/*size*/ + 1/*type*/ + 4/*count*/); for(ValueVector::const_iterator i = values.begin(); i != values.end(); ++i) { - len += (*i)->getData().size(); + len += (*i)->getData().encodedSize(); } return len; } @@ -55,18 +56,18 @@ int Array::count() const { return values.size(); } -std::ostream& operator<<(std::ostream& out, const Array& t) { - out << "{"; - for(Array::ValueVector::const_iterator i = t.values.begin(); i != t.values.end(); ++i) { - if (i != t.values.begin()) out << ", "; - out << *(i->get()); +std::ostream& operator<<(std::ostream& out, const Array& a) { + out << typeName(a.getType()) << "{"; + for(Array::ValueVector::const_iterator i = a.values.begin(); i != a.values.end(); ++i) { + if (i != a.values.begin()) out << ", "; + (*i)->print(out); } return out << "}"; } void Array::encode(Buffer& buffer) const{ - buffer.putLong(size() - 4);//size added only when array is a top-level type - buffer.putOctet(typeOctet); + buffer.putLong(encodedSize() - 4);//size added only when array is a top-level type + buffer.putOctet(type); buffer.putLong(count()); for (ValueVector::const_iterator i = values.begin(); i!=values.end(); ++i) { (*i)->getData().encode(buffer); @@ -74,6 +75,7 @@ void Array::encode(Buffer& buffer) const{ } void Array::decode(Buffer& buffer){ + values.clear(); uint32_t size = buffer.getLong();//size added only when array is a top-level type uint32_t available = buffer.available(); if (available < size) { @@ -81,21 +83,21 @@ void Array::decode(Buffer& buffer){ << size << " bytes but only " << available << " available")); } if (size) { - typeOctet = buffer.getOctet(); + type = TypeCode(buffer.getOctet()); uint32_t count = buffer.getLong(); FieldValue dummy; - dummy.setType(typeOctet); + dummy.setType(type); available = buffer.available(); - if (available < count * dummy.getData().size()) { + if (available < count * dummy.getData().encodedSize()) { throw IllegalArgumentException(QPID_MSG("Not enough data for array, expected " - << count << " items of " << dummy.getData().size() + << count << " items of " << dummy.getData().encodedSize() << " bytes each but only " << available << " bytes available")); } for (uint32_t i = 0; i < count; i++) { ValuePtr value(new FieldValue); - value->setType(typeOctet); + value->setType(type); value->getData().decode(buffer); values.push_back(ValuePtr(value)); } @@ -104,7 +106,7 @@ void Array::decode(Buffer& buffer){ bool Array::operator==(const Array& x) const { - if (typeOctet != x.typeOctet) return false; + if (type != x.type) return false; if (values.size() != x.values.size()) return false; for (ValueVector::const_iterator i = values.begin(), j = x.values.begin(); i != values.end(); ++i, ++j) { @@ -114,12 +116,13 @@ bool Array::operator==(const Array& x) const { return true; } -void Array::add(ValuePtr value) -{ - if (typeOctet != value->getType()) { - throw IllegalArgumentException(QPID_MSG("Wrong type of value, expected " << typeOctet)); +void Array::insert(iterator i, ValuePtr value) { + if (type != value->getType()) { + // FIXME aconway 2008-10-31: put meaningful strings in this message. + throw Exception(QPID_MSG("Wrong type of value in Array, expected " << type + << " but found " << TypeCode(value->getType()))); } - values.push_back(value); + values.insert(i, value); } diff --git a/cpp/src/qpid/framing/Array.h b/cpp/src/qpid/framing/Array.h deleted file mode 100644 index 2cbd4c0b29..0000000000 --- a/cpp/src/qpid/framing/Array.h +++ /dev/null @@ -1,78 +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 <iostream> -#include <vector> -#include <boost/shared_ptr.hpp> -#include <map> -#include "amqp_types.h" -#include "FieldValue.h" - -#ifndef _Array_ -#define _Array_ - -namespace qpid { -namespace framing { - -class Buffer; - -class Array -{ - public: - typedef boost::shared_ptr<FieldValue> ValuePtr; - typedef std::vector<ValuePtr> ValueVector; - - uint32_t size() const; - void encode(Buffer& buffer) const; - void decode(Buffer& buffer); - - int count() const; - bool operator==(const Array& other) const; - - Array(); - Array(uint8_t type); - //creates a longstr array - Array(const std::vector<std::string>& in); - - void add(ValuePtr value); - - template <class T> - void collect(std::vector<T>& out) const - { - for (ValueVector::const_iterator i = values.begin(); i != values.end(); ++i) { - out.push_back((*i)->get<T>()); - } - } - - private: - uint8_t typeOctet; - ValueVector values; - - ValueVector::const_iterator begin() const { return values.begin(); } - ValueVector::const_iterator end() const { return values.end(); } - - friend std::ostream& operator<<(std::ostream& out, const Array& body); -}; - -} -} - - -#endif diff --git a/cpp/src/qpid/framing/Blob.cpp b/cpp/src/qpid/framing/Blob.cpp index 388d4b64ef..0c8316f3d2 100644 --- a/cpp/src/qpid/framing/Blob.cpp +++ b/cpp/src/qpid/framing/Blob.cpp @@ -18,7 +18,7 @@ * */ -#include "Blob.h" +#include "qpid/framing/Blob.h" namespace qpid { diff --git a/cpp/src/qpid/framing/Blob.h b/cpp/src/qpid/framing/Blob.h index 5c84384ad7..e69de29bb2 100644 --- a/cpp/src/qpid/framing/Blob.h +++ b/cpp/src/qpid/framing/Blob.h @@ -1,197 +0,0 @@ -#ifndef QPID_FRAMING_BLOB_H -#define QPID_FRAMING_BLOB_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/static_assert.hpp> -#include <boost/aligned_storage.hpp> -#include <boost/checked_delete.hpp> -#include <boost/utility/typed_in_place_factory.hpp> -#include <boost/type_traits/is_base_and_derived.hpp> -#include <boost/utility/enable_if.hpp> -#include <boost/version.hpp> - -#include <new> - -#include <assert.h> - - -namespace qpid { -namespace framing { - -using boost::in_place; -using boost::typed_in_place_factory_base; - -/** 0-arg typed_in_place_factory, missing in pre-1.35 boost. */ -#if (BOOST_VERSION < 103500) -template <class T> -struct typed_in_place_factory0 : public typed_in_place_factory_base { - typedef T value_type ; - void apply ( void* address ) const { new (address) T(); } -}; - -/** 0-arg in_place<T>() function, missing from boost. */ -template<class T> -typed_in_place_factory0<T> in_place() { return typed_in_place_factory0<T>(); } -#endif - -template <class T, class R=void> -struct EnableInPlace - : public boost::enable_if<boost::is_base_and_derived< - typed_in_place_factory_base, T>, - R> -{}; - -template <class T, class R=void> -struct DisableInPlace - : public boost::disable_if<boost::is_base_and_derived< - typed_in_place_factory_base, T>, - R> -{}; - -template <class T> struct BlobHelper { - static void destroy(void* ptr) { static_cast<T*>(ptr)->~T(); } - static void copy(void* dest, const void* src) { - new (dest) T(*static_cast<const T*>(src)); - } -}; - -template <> struct BlobHelper<void> { - static void destroy(void*); - static void copy(void* to, const void* from); -}; - -/** - * A Blob is a chunk of memory which can contain a single object at - * a time-arbitrary type, provided sizeof(T)<=blob.size(). Using Blobs - * ensures proper construction and destruction of its contents, - * and proper copying between Blobs, but nothing else. - * - * In particular you must ensure that the Blob is big enough for its - * contents and must know the type of object in the Blob to cast get(). - * - * If BaseType is specified then only an object that can be - * static_cast to BaseType may be stored in the Blob. - */ -template <size_t Size, class BaseType=void> -class Blob -{ - boost::aligned_storage<Size> store; - BaseType* basePtr; - - void (*destroy)(void*); - void (*copy)(void*, const void*); - - template <class T>void setType() { - BOOST_STATIC_ASSERT(sizeof(T) <= Size); - destroy=&BlobHelper<T>::destroy; - copy=&BlobHelper<T>::copy; - // Base pointer may be offeset from store.address() - basePtr = reinterpret_cast<T*>(store.address()); - } - - void initialize() { - destroy=&BlobHelper<void>::destroy; - copy=&BlobHelper<void>::copy; - basePtr=0; - } - - template<class Factory> - typename EnableInPlace<Factory>::type apply(const Factory& factory) - { - typedef typename Factory::value_type T; - assert(empty()); - factory.apply(store.address()); - setType<T>(); - } - - void assign(const Blob& b) { - assert(empty()); - if (b.empty()) return; - b.copy(this->store.address(), b.store.address()); - copy = b.copy; - destroy = b.destroy; - basePtr = reinterpret_cast<BaseType*>( - ((char*)this)+ ((const char*)(b.basePtr) - (const char*)(&b))); - } - - public: - /** Construct an empty Blob. */ - Blob() { initialize(); } - - /** Copy a Blob. */ - Blob(const Blob& b) { initialize(); assign(b); } - - /** Construct from in_place constructor. */ - template<class InPlace> - Blob(const InPlace & expr, typename EnableInPlace<InPlace>::type* =0) { - initialize(); apply(expr); - } - - /** Construct by copying an object constructor. */ - template<class T> - Blob(const T & t, typename DisableInPlace<T>::type* =0) { - initialize(); apply(in_place<T>(t)); - } - - ~Blob() { clear(); } - - /** Assign from another Blob. */ - Blob& operator=(const Blob& b) { - clear(); - assign(b); - return *this; - } - - /** Assign from an in_place constructor expression. */ - template<class InPlace> - typename EnableInPlace<InPlace,Blob&>::type operator=(const InPlace& expr) { - clear(); apply(expr); return *this; - } - - /** Assign from an object of type T. */ - template <class T> - typename DisableInPlace<T, Blob&>::type operator=(const T& x) { - clear(); apply(in_place<T>(x)); return *this; - } - - /** Get pointer to Blob contents, returns 0 if empty. */ - BaseType* get() { return basePtr; } - - /** Get pointer to Blob contents, returns 0 if empty. */ - const BaseType* get() const { return basePtr; } - - /** Destroy the object in the Blob making it empty. */ - void clear() { - void (*oldDestroy)(void*) = destroy; - initialize(); - oldDestroy(store.address()); - } - - bool empty() const { return destroy==BlobHelper<void>::destroy; } - - static size_t size() { return Size; } -}; - -}} // namespace qpid::framing - - -#endif /*!QPID_FRAMING_BLOB_H*/ diff --git a/cpp/src/qpid/sys/Thread.h b/cpp/src/qpid/framing/BodyFactory.h index 3188e45341..6a8d9b1988 100644 --- a/cpp/src/qpid/sys/Thread.h +++ b/cpp/src/qpid/framing/BodyFactory.h @@ -1,5 +1,5 @@ -#ifndef _sys_Thread_h -#define _sys_Thread_h +#ifndef QPID_FRAMING_BODYFACTORY_H +#define QPID_FRAMING_BODYFACTORY_H /* * @@ -21,34 +21,27 @@ * under the License. * */ -#include <boost/shared_ptr.hpp> -namespace qpid { -namespace sys { - -class Runnable; -class ThreadPrivate; +#include <boost/intrusive_ptr.hpp> -class Thread -{ - boost::shared_ptr<ThreadPrivate> impl; +namespace qpid { +namespace framing { +/** + * Indirect creation of body types to allow centralized changes to + * memory management strategy. + */ +class BodyFactory { public: - Thread(); - explicit Thread(qpid::sys::Runnable*); - explicit Thread(qpid::sys::Runnable&); - - void join(); - - unsigned long id(); - - static Thread current(); + template <class BodyType> static boost::intrusive_ptr<BodyType> create() { + return new BodyType; + } - /** ID of current thread for logging. - * Workaround for broken Thread::current() in APR - */ - static unsigned long logId() { return current().id(); } + template <class BodyType> static boost::intrusive_ptr<BodyType> copy(const BodyType& body) { + return new BodyType(body); + } }; -}} -#endif /*!_sys_Thread_h*/ +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_BODYFACTORY_H*/ diff --git a/cpp/src/qpid/framing/BodyHandler.cpp b/cpp/src/qpid/framing/BodyHandler.cpp index ffbcf33a95..e2128596ed 100644 --- a/cpp/src/qpid/framing/BodyHandler.cpp +++ b/cpp/src/qpid/framing/BodyHandler.cpp @@ -18,11 +18,11 @@ * under the License. * */ -#include "BodyHandler.h" -#include "AMQMethodBody.h" -#include "AMQHeaderBody.h" -#include "AMQContentBody.h" -#include "AMQHeartbeatBody.h" +#include "qpid/framing/BodyHandler.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/AMQHeartbeatBody.h" #include <boost/cast.hpp> #include "qpid/framing/reply_exceptions.h" diff --git a/cpp/src/qpid/framing/BodyHolder.cpp b/cpp/src/qpid/framing/BodyHolder.cpp index 1b2f74de2c..e69de29bb2 100644 --- a/cpp/src/qpid/framing/BodyHolder.cpp +++ b/cpp/src/qpid/framing/BodyHolder.cpp @@ -1,76 +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 "BodyHolder.h" -#include "AMQMethodBody.h" -#include "AMQHeaderBody.h" -#include "AMQContentBody.h" -#include "AMQHeartbeatBody.h" -#include "Buffer.h" -#include "qpid/framing/reply_exceptions.h" - -namespace qpid { -namespace framing { - - -// BodyHolder::operator=(const AMQBody&) is defined -// in generated file BodyHolder_gen.cpp - - -void BodyHolder::encode(Buffer& b) const { - const AMQMethodBody* method=getMethod(); - if (method) { - b.putOctet(method->amqpClassId()); - b.putOctet(method->amqpMethodId()); - method->encode(b); - } - else - get()->encode(b); -} - -void BodyHolder::decode(uint8_t type, Buffer& buffer, uint32_t size) { - switch(type) - { - case 0://CONTROL - case METHOD_BODY: { - ClassId c = buffer.getOctet(); - MethodId m = buffer.getOctet(); - setMethod(c, m); - break; - } - case HEADER_BODY: *this=in_place<AMQHeaderBody>(); break; - case CONTENT_BODY: *this=in_place<AMQContentBody>(); break; - case HEARTBEAT_BODY: *this=in_place<AMQHeartbeatBody>(); break; - default: - throw IllegalArgumentException(QPID_MSG("Invalid frame type " << type)); - } - get()->decode(buffer, size); -} - -uint32_t BodyHolder::size() const { - const AMQMethodBody* method=getMethod(); - if (method) - return sizeof(ClassId)+sizeof(MethodId)+method->size(); - else - return get()->size(); -} - -}} // namespace qpid::framing - diff --git a/cpp/src/qpid/framing/BodyHolder.h b/cpp/src/qpid/framing/BodyHolder.h index f843961a32..e69de29bb2 100644 --- a/cpp/src/qpid/framing/BodyHolder.h +++ b/cpp/src/qpid/framing/BodyHolder.h @@ -1,88 +0,0 @@ -#ifndef QPID_FRAMING_BODYHOLDER_H -#define QPID_FRAMING_BODYHOLDER_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/framing/AMQBody.h" -#include "qpid/framing/Blob.h" -#include "qpid/framing/MaxMethodBodySize.h" // Generated file. -#include "qpid/framing/amqp_types.h" -#include "qpid/RefCounted.h" - - -namespace qpid { -namespace framing { - -class AMQMethodBody; -class AMQBody; -class Buffer; - -/** - * Holder for arbitrary frame body. - */ -class BodyHolder : public RefCounted -{ - public: - // default copy, assign dtor ok. - BodyHolder() {} - BodyHolder(const AMQBody& b) { setBody(b); } - BodyHolder(ClassId c, MethodId m) { setMethod(c,m); } - - /** Construct from an in_place constructor expression. */ - template <class InPlace> - BodyHolder(const InPlace& ip, typename EnableInPlace<InPlace>::type* =0) - : blob(ip) {} - - void setBody(const AMQBody& b); - - /** Assign from an in_place constructor expression. */ - template <class InPlace> - typename EnableInPlace<InPlace,BodyHolder&>::type - operator=(const InPlace& ip) { blob=ip; return *this; } - - /** Assign by copying. */ - template <class T> - typename DisableInPlace<T,BodyHolder&>::type operator=(const T& x) - { blob=in_place<T>(x); return *this; } - - /** Set to method with ClassId c, MethodId m. */ - void setMethod(ClassId c, MethodId m); - - void encode(Buffer&) const; - void decode(uint8_t frameType, Buffer&, uint32_t=0); - uint32_t size() const; - - /** Return body pointer or 0 if empty. */ - AMQBody* get() { return blob.get(); } - const AMQBody* get() const { return blob.get(); } - - /** Return method pointer or 0 if not a method. */ - AMQMethodBody* getMethod() { return get()->getMethod(); } - const AMQMethodBody* getMethod() const { return get()->getMethod(); } - - private: - Blob<MAX_METHOD_BODY_SIZE, AMQBody> blob; -}; - -}} // namespace qpid::framing - -#endif /*!QPID_FRAMING_BODYHOLDER_H*/ diff --git a/cpp/src/qpid/framing/Buffer.cpp b/cpp/src/qpid/framing/Buffer.cpp index 9c089fd0f8..051e7a2362 100644 --- a/cpp/src/qpid/framing/Buffer.cpp +++ b/cpp/src/qpid/framing/Buffer.cpp @@ -18,8 +18,8 @@ * under the License. * */ -#include "Buffer.h" -#include "FieldTable.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldTable.h" #include <string.h> #include <boost/format.hpp> namespace qpid { @@ -51,12 +51,14 @@ void Buffer::reset(){ void Buffer::putOctet(uint8_t i){ data[position++] = i; + assert(position <= size); } void Buffer::putShort(uint16_t i){ uint16_t b = i; data[position++] = (uint8_t) (0xFF & (b >> 8)); data[position++] = (uint8_t) (0xFF & b); + assert(position <= size); } void Buffer::putLong(uint32_t i){ @@ -65,6 +67,7 @@ void Buffer::putLong(uint32_t i){ data[position++] = (uint8_t) (0xFF & (b >> 16)); data[position++] = (uint8_t) (0xFF & (b >> 8)); data[position++] = (uint8_t) (0xFF & b); + assert(position <= size); } void Buffer::putLongLong(uint64_t i){ @@ -76,6 +79,7 @@ void Buffer::putLongLong(uint64_t i){ void Buffer::putInt8(int8_t i){ data[position++] = (uint8_t) i; + assert(position <= size); } void Buffer::putInt16(int16_t i){ @@ -110,19 +114,22 @@ void Buffer::putDouble(double f){ putLongLong (val.i); } -void Buffer::putBin128(uint8_t* b){ +void Buffer::putBin128(const uint8_t* b){ memcpy (data + position, b, 16); position += 16; } uint8_t Buffer::getOctet(){ - return (uint8_t) data[position++]; + uint8_t octet = static_cast<uint8_t>(data[position++]); + assert(position <= size); + return octet; } uint16_t Buffer::getShort(){ uint16_t hi = (unsigned char) data[position++]; hi = hi << 8; hi |= (unsigned char) data[position++]; + assert(position <= size); return hi; } @@ -131,6 +138,7 @@ uint32_t Buffer::getLong(){ uint32_t b = (unsigned char) data[position++]; uint32_t c = (unsigned char) data[position++]; uint32_t d = (unsigned char) data[position++]; + assert(position <= size); a = a << 24; a |= b << 16; a |= c << 8; @@ -146,7 +154,9 @@ uint64_t Buffer::getLongLong(){ } int8_t Buffer::getInt8(){ - return (int8_t) data[position++]; + int8_t i = static_cast<int8_t>(data[position++]); + assert(position <= size); + return i; } int16_t Buffer::getInt16(){ @@ -220,14 +230,16 @@ void Buffer::putUInt<8>(uint64_t i) { } void Buffer::putShortString(const string& s){ - uint8_t len = s.length(); + size_t slen = s.length(); + uint8_t len = slen < 0x100 ? (uint8_t) slen : 0xFF; putOctet(len); s.copy(data + position, len); position += len; } void Buffer::putMediumString(const string& s){ - uint16_t len = s.length(); + size_t slen = s.length(); + uint16_t len = slen < 0x10000 ? (uint16_t) slen : 0xFFFF; putShort(len); s.copy(data + position, len); position += len; diff --git a/cpp/src/qpid/framing/Buffer.h b/cpp/src/qpid/framing/Buffer.h deleted file mode 100644 index a27b15cac0..0000000000 --- a/cpp/src/qpid/framing/Buffer.h +++ /dev/null @@ -1,132 +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 "amqp_types.h" -#include "qpid/Exception.h" -#include <boost/iterator/iterator_facade.hpp> - -#ifndef _Buffer_ -#define _Buffer_ - -namespace qpid { -namespace framing { - -struct OutOfBounds : qpid::Exception {}; - -class Content; -class FieldTable; - -class Buffer -{ - uint32_t size; - char* data; - uint32_t position; - uint32_t r_position; - - void checkAvailable(uint32_t count) { if (position + count > size) throw OutOfBounds(); } - - public: - - /** Buffer input/output iterator. - * Supports using an amqp_0_10::Codec with a framing::Buffer. - */ - class Iterator : public boost::iterator_facade< - Iterator, char, boost::random_access_traversal_tag> - { - public: - Iterator(Buffer& b) : buffer(&b) {} - - private: - friend class boost::iterator_core_access; - char& dereference() const { return buffer->data[buffer->position]; } - void increment() { ++buffer->position; } - bool equal(const Iterator& x) const { return buffer == x.buffer; } - - Buffer* buffer; - }; - - friend class Iterator; - - Buffer(char* data=0, uint32_t size=0); - - void record(); - void restore(bool reRecord = false); - void reset(); - - uint32_t available() { return size - position; } - uint32_t getSize() { return size; } - uint32_t getPosition() { return position; } - Iterator getIterator() { return Iterator(*this); } - - void putOctet(uint8_t i); - void putShort(uint16_t i); - void putLong(uint32_t i); - void putLongLong(uint64_t i); - void putInt8(int8_t i); - void putInt16(int16_t i); - void putInt32(int32_t i); - void putInt64(int64_t i); - void putFloat(float f); - void putDouble(double f); - void putBin128(uint8_t* b); - - uint8_t getOctet(); - uint16_t getShort(); - uint32_t getLong(); - uint64_t getLongLong(); - int8_t getInt8(); - int16_t getInt16(); - int32_t getInt32(); - int64_t getInt64(); - float getFloat(); - double getDouble(); - - template <int n> - uint64_t getUInt(); - - template <int n> - void putUInt(uint64_t); - - void putShortString(const string& s); - void putMediumString(const string& s); - void putLongString(const string& s); - void getShortString(string& s); - void getMediumString(string& s); - void getLongString(string& s); - void getBin128(uint8_t* b); - - void putRawData(const string& s); - void getRawData(string& s, uint32_t size); - - void putRawData(const uint8_t* data, size_t size); - void getRawData(uint8_t* data, size_t size); - - template <class T> void put(const T& data) { data.encode(*this); } - template <class T> void get(T& data) { data.decode(*this); } - - void dump(std::ostream&) const; -}; - -std::ostream& operator<<(std::ostream&, const Buffer&); - -}} // namespace qpid::framing - - -#endif diff --git a/cpp/src/qpid/framing/ChannelHandler.h b/cpp/src/qpid/framing/ChannelHandler.h index 69aaeac492..ddab204578 100644 --- a/cpp/src/qpid/framing/ChannelHandler.h +++ b/cpp/src/qpid/framing/ChannelHandler.h @@ -21,8 +21,8 @@ * under the License. * */ -#include "FrameHandler.h" -#include "AMQFrame.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/AMQFrame.h" namespace qpid { namespace framing { diff --git a/cpp/src/qpid/framing/Endian.cpp b/cpp/src/qpid/framing/Endian.cpp new file mode 100644 index 0000000000..5acc3c459f --- /dev/null +++ b/cpp/src/qpid/framing/Endian.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 "qpid/framing/Endian.h" + +namespace qpid { +namespace framing { + +Endian::Endian() : littleEndian(!testBigEndian()) {} + +bool Endian::testBigEndian() +{ + uint16_t a = 1; + uint16_t b; + uint8_t* p = (uint8_t*) &b; + p[0] = 0xFF & (a >> 8); + p[1] = 0xFF & (a); + return a == b; +} + +uint8_t* Endian::convertIfRequired(uint8_t* const octets, int width) +{ + if (instance.littleEndian) { + for (int i = 0; i < (width/2); i++) { + uint8_t temp = octets[i]; + octets[i] = octets[width - (1 + i)]; + octets[width - (1 + i)] = temp; + } + } + return octets; +} + +const Endian Endian::instance; + +}} // namespace qpid::framing diff --git a/cpp/src/qpid/sys/IOHandle.h b/cpp/src/qpid/framing/Endian.h index d06512da58..077d5a3e9b 100644 --- a/cpp/src/qpid/sys/IOHandle.h +++ b/cpp/src/qpid/framing/Endian.h @@ -1,5 +1,5 @@ -#ifndef _sys_IOHandle_h -#define _sys_IOHandle_h +#ifndef QPID_FRAMING_ENDIAN_H +#define QPID_FRAMING_ENDIAN_H /* * @@ -22,24 +22,25 @@ * */ +#include "qpid/sys/IntegerTypes.h" + namespace qpid { -namespace sys { +namespace framing { /** - * This is a class intended to abstract the Unix concept of file descriptor or the Windows concept of HANDLE + * Conversion utility for little-endian platforms that need to convert + * to and from network ordered octet sequences */ -class PollerHandle; -class IOHandlePrivate; -class IOHandle { - friend class PollerHandle; - -protected: - IOHandlePrivate* const impl; - - IOHandle(IOHandlePrivate*); - virtual ~IOHandle(); +class Endian +{ + public: + static uint8_t* convertIfRequired(uint8_t* const octets, int width); + private: + const bool littleEndian; + Endian(); + static const Endian instance; + static bool testBigEndian(); }; +}} // namespace qpid::framing -}} - -#endif // _sys_IOHandle_h +#endif /*!QPID_FRAMING_ENDIAN_H*/ diff --git a/cpp/src/qpid/framing/FieldTable.cpp b/cpp/src/qpid/framing/FieldTable.cpp index bd20c10c37..e2e91e450a 100644 --- a/cpp/src/qpid/framing/FieldTable.cpp +++ b/cpp/src/qpid/framing/FieldTable.cpp @@ -18,9 +18,11 @@ * under the License. * */ -#include "FieldTable.h" -#include "Buffer.h" -#include "FieldValue.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/Endian.h" +#include "qpid/framing/FieldValue.h" #include "qpid/Exception.h" #include "qpid/framing/reply_exceptions.h" #include <assert.h> @@ -28,13 +30,25 @@ namespace qpid { namespace framing { +FieldTable::FieldTable(const FieldTable& ft) +{ + *this = ft; +} + +FieldTable& FieldTable::operator=(const FieldTable& ft) +{ + clear(); + values = ft.values; + return *this; +} + FieldTable::~FieldTable() {} -uint32_t FieldTable::size() const { +uint32_t FieldTable::encodedSize() const { uint32_t len(4/*size field*/ + 4/*count field*/); for(ValueMap::const_iterator i = values.begin(); i != values.end(); ++i) { // shortstr_len_byte + key size + value size - len += 1 + (i->first).size() + (i->second)->size(); + len += 1 + (i->first).size() + (i->second)->encodedSize(); } return len; } @@ -69,17 +83,38 @@ void FieldTable::setString(const std::string& name, const std::string& value){ values[name] = ValuePtr(new Str16Value(value)); } -void FieldTable::setInt(const std::string& name, int value){ +void FieldTable::setInt(const std::string& name, const int value){ values[name] = ValuePtr(new IntegerValue(value)); } -void FieldTable::setTimestamp(const std::string& name, uint64_t value){ +void FieldTable::setInt64(const std::string& name, const int64_t value){ + values[name] = ValuePtr(new Integer64Value(value)); +} + +void FieldTable::setTimestamp(const std::string& name, const uint64_t value){ values[name] = ValuePtr(new TimeValue(value)); } -void FieldTable::setTable(const std::string& name, const FieldTable& value){ +void FieldTable::setUInt64(const std::string& name, const uint64_t value){ + values[name] = ValuePtr(new Unsigned64Value(value)); +} + +void FieldTable::setTable(const std::string& name, const FieldTable& value) +{ values[name] = ValuePtr(new FieldTableValue(value)); } +void FieldTable::setArray(const std::string& name, const Array& value) +{ + values[name] = ValuePtr(new ArrayValue(value)); +} + +void FieldTable::setFloat(const std::string& name, const float value){ + values[name] = ValuePtr(new FloatValue(value)); +} + +void FieldTable::setDouble(const std::string& name, double value){ + values[name] = ValuePtr(new DoubleValue(value)); +} FieldTable::ValuePtr FieldTable::get(const std::string& name) const { @@ -105,24 +140,54 @@ T getValue(const FieldTable::ValuePtr value) return value->get<T>(); } -std::string FieldTable::getString(const std::string& name) const { +std::string FieldTable::getAsString(const std::string& name) const { return getValue<std::string>(get(name)); } -int FieldTable::getInt(const std::string& name) const { +int FieldTable::getAsInt(const std::string& name) const { return getValue<int>(get(name)); } +uint64_t FieldTable::getAsUInt64(const std::string& name) const { + return static_cast<uint64_t>( getValue<int64_t>(get(name))); +} + +int64_t FieldTable::getAsInt64(const std::string& name) const { + return getValue<int64_t>(get(name)); +} + +bool FieldTable::getTable(const std::string& name, FieldTable& value) const { + return getEncodedValue<FieldTable>(get(name), value); +} + +bool FieldTable::getArray(const std::string& name, Array& value) const { + return getEncodedValue<Array>(get(name), value); +} + +template <class T, int width, uint8_t typecode> +bool getRawFixedWidthValue(FieldTable::ValuePtr vptr, T& value) +{ + if (vptr && vptr->getType() == typecode) { + value = vptr->get<T>(); + return true; + } + return false; +} + +bool FieldTable::getFloat(const std::string& name, float& value) const { + return getRawFixedWidthValue<float, 4, 0x23>(get(name), value); +} + +bool FieldTable::getDouble(const std::string& name, double& value) const { + return getRawFixedWidthValue<double, 8, 0x33>(get(name), value); +} + //uint64_t FieldTable::getTimestamp(const std::string& name) const { // return getValue<uint64_t>(name); //} -// -//void FieldTable::getTable(const std::string& name, FieldTable& value) const { -// value = getValue<FieldTable>(name); -//} -void FieldTable::encode(Buffer& buffer) const{ - buffer.putLong(size() - 4); +void FieldTable::encode(Buffer& buffer) const { + buffer.putLong(encodedSize() - 4); buffer.putLong(values.size()); for (ValueMap::const_iterator i = values.begin(); i!=values.end(); ++i) { buffer.putShortString(i->first); @@ -131,11 +196,12 @@ void FieldTable::encode(Buffer& buffer) const{ } void FieldTable::decode(Buffer& buffer){ + clear(); uint32_t len = buffer.getLong(); if (len) { uint32_t available = buffer.available(); if (available < len) - throw IllegalArgumentException(QPID_MSG("Not enough data for field table.")); + throw IllegalArgumentException(QPID_MSG("Not enough data for field table.")); uint32_t count = buffer.getLong(); uint32_t leftover = available - len; while(buffer.available() > leftover && count--){ @@ -149,7 +215,6 @@ void FieldTable::decode(Buffer& buffer){ } } - bool FieldTable::operator==(const FieldTable& x) const { if (values.size() != x.values.size()) return false; for (ValueMap::const_iterator i = values.begin(); i != values.end(); ++i) { @@ -160,10 +225,22 @@ bool FieldTable::operator==(const FieldTable& x) const { return true; } -//void FieldTable::erase(const std::string& name) -//{ -// values.erase(values.find(name)); -//} +void FieldTable::erase(const std::string& name) +{ + if (values.find(name) != values.end()) + values.erase(name); +} + +std::pair<FieldTable::ValueMap::iterator, bool> FieldTable::insert(const ValueMap::value_type& value) +{ + return values.insert(value); +} + +FieldTable::ValueMap::iterator FieldTable::insert(ValueMap::iterator position, const ValueMap::value_type& value) +{ + return values.insert(position, value); +} + } } diff --git a/cpp/src/qpid/framing/FieldTable.h b/cpp/src/qpid/framing/FieldTable.h deleted file mode 100644 index 3c65d31aee..0000000000 --- a/cpp/src/qpid/framing/FieldTable.h +++ /dev/null @@ -1,101 +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 <iostream> -#include <vector> -#include <boost/shared_ptr.hpp> -#include <map> -#include "amqp_types.h" - -#ifndef _FieldTable_ -#define _FieldTable_ - -namespace qpid { - /** - * The framing namespace contains classes that are used to create, - * send and receive the basic packets from which AMQP is built. - */ -namespace framing { - -class FieldValue; -class Buffer; - -/** - * A set of name-value pairs. (See the AMQP spec for more details on - * AMQP field tables). - * - * \ingroup clientapi - */ -class FieldTable -{ - public: - typedef boost::shared_ptr<FieldValue> ValuePtr; - typedef std::map<std::string, ValuePtr> ValueMap; - typedef ValueMap::iterator iterator; - - ~FieldTable(); - uint32_t size() const; - void encode(Buffer& buffer) const; - void decode(Buffer& buffer); - - int count() const; - void set(const std::string& name, const ValuePtr& value); - ValuePtr get(const std::string& name) const; - - void setString(const std::string& name, const std::string& value); - void setInt(const std::string& name, int value); - void setTimestamp(const std::string& name, uint64_t value); - void setTable(const std::string& name, const FieldTable& value); - //void setDecimal(string& name, xxx& value); - - std::string getString(const std::string& name) const; - int getInt(const std::string& name) const; -// uint64_t getTimestamp(const std::string& name) const; -// void getTable(const std::string& name, FieldTable& value) const; -// //void getDecimal(string& name, xxx& value); -// //void erase(const std::string& name); - - - bool operator==(const FieldTable& other) const; - - // Map-like interface. - // TODO: may need to duplicate into versions that return mutable iterator - ValueMap::const_iterator begin() const { return values.begin(); } - ValueMap::const_iterator end() const { return values.end(); } - ValueMap::const_iterator find(const std::string& s) const { return values.find(s); } - - // ### Hack Alert - - ValueMap::iterator getValues() { return values.begin(); } - - private: - ValueMap values; - - friend std::ostream& operator<<(std::ostream& out, const FieldTable& body); -}; - -//class FieldNotFoundException{}; -//class UnknownFieldName : public FieldNotFoundException{}; -//class IncorrectFieldType : public FieldNotFoundException{}; -} -} - - -#endif diff --git a/cpp/src/qpid/framing/FieldValue.cpp b/cpp/src/qpid/framing/FieldValue.cpp index 681f20a793..5bac931b83 100644 --- a/cpp/src/qpid/framing/FieldValue.cpp +++ b/cpp/src/qpid/framing/FieldValue.cpp @@ -18,8 +18,11 @@ * under the License. * */ -#include "FieldValue.h" -#include "Buffer.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/Endian.h" +#include "qpid/framing/List.h" #include "qpid/framing/reply_exceptions.h" namespace qpid { @@ -33,53 +36,60 @@ uint8_t FieldValue::getType() void FieldValue::setType(uint8_t type) { typeOctet = type; - - uint8_t lenType = typeOctet >> 4; - switch(lenType){ - case 0: - data.reset(new FixedWidthValue<1>()); - break; - case 1: - data.reset(new FixedWidthValue<2>()); - break; - case 2: - data.reset(new FixedWidthValue<4>()); - break; - case 3: - data.reset(new FixedWidthValue<8>()); - break; - case 4: - data.reset(new FixedWidthValue<16>()); - break; - case 5: - data.reset(new FixedWidthValue<32>()); - break; - case 6: - data.reset(new FixedWidthValue<64>()); - break; - case 7: - data.reset(new FixedWidthValue<128>()); - break; - case 8: - data.reset(new VariableWidthValue<1>()); - break; - case 9: - data.reset(new VariableWidthValue<2>()); - break; - case 0xA: - data.reset(new VariableWidthValue<4>()); - break; - case 0xC: - data.reset(new FixedWidthValue<5>()); - break; - case 0xD: - data.reset(new FixedWidthValue<9>()); - break; - case 0xF: - data.reset(new FixedWidthValue<0>()); - break; - default: - throw IllegalArgumentException(QPID_MSG("Unknown field table value type: " << (int)typeOctet)); + if (typeOctet == 0xA8) { + data.reset(new EncodedValue<FieldTable>()); + } else if (typeOctet == 0xA9) { + data.reset(new EncodedValue<List>()); + } else if (typeOctet == 0xAA) { + data.reset(new EncodedValue<Array>()); + } else { + uint8_t lenType = typeOctet >> 4; + switch(lenType){ + case 0: + data.reset(new FixedWidthValue<1>()); + break; + case 1: + data.reset(new FixedWidthValue<2>()); + break; + case 2: + data.reset(new FixedWidthValue<4>()); + break; + case 3: + data.reset(new FixedWidthValue<8>()); + break; + case 4: + data.reset(new FixedWidthValue<16>()); + break; + case 5: + data.reset(new FixedWidthValue<32>()); + break; + case 6: + data.reset(new FixedWidthValue<64>()); + break; + case 7: + data.reset(new FixedWidthValue<128>()); + break; + case 8: + data.reset(new VariableWidthValue<1>()); + break; + case 9: + data.reset(new VariableWidthValue<2>()); + break; + case 0xA: + data.reset(new VariableWidthValue<4>()); + break; + case 0xC: + data.reset(new FixedWidthValue<5>()); + break; + case 0xD: + data.reset(new FixedWidthValue<9>()); + break; + case 0xF: + data.reset(new FixedWidthValue<0>()); + break; + default: + throw IllegalArgumentException(QPID_MSG("Unknown field table value type: " << (int)typeOctet)); + } } } @@ -102,10 +112,10 @@ bool FieldValue::operator==(const FieldValue& v) const *data == *v.data; } -StringValue::StringValue(const std::string& v) : +Str8Value::Str8Value(const std::string& v) : FieldValue( - 0xA4, - new VariableWidthValue<4>( + TYPE_CODE_STR8, + new VariableWidthValue<1>( reinterpret_cast<const uint8_t*>(v.data()), reinterpret_cast<const uint8_t*>(v.data()+v.size()))) { @@ -129,16 +139,77 @@ Struct32Value::Struct32Value(const std::string& v) : IntegerValue::IntegerValue(int v) : FieldValue(0x21, new FixedWidthValue<4>(v)) +{} + +FloatValue::FloatValue(float v) : + FieldValue(0x23, new FixedWidthValue<4>(Endian::convertIfRequired(reinterpret_cast<uint8_t*>(&v), 4))) +{} + +DoubleValue::DoubleValue(double v) : + FieldValue(0x33, new FixedWidthValue<8>(Endian::convertIfRequired(reinterpret_cast<uint8_t*>(&v), 8))) +{} + +Integer64Value::Integer64Value(int64_t v) : + FieldValue(0x31, new FixedWidthValue<8>(v)) +{} + +Unsigned64Value::Unsigned64Value(uint64_t v) : + FieldValue(0x32, new FixedWidthValue<8>(v)) +{} + + +TimeValue::TimeValue(uint64_t v) : + FieldValue(0x38, new FixedWidthValue<8>(v)) { } -TimeValue::TimeValue(uint64_t v) : - FieldValue(0x32, new FixedWidthValue<8>(v)) +FieldTableValue::FieldTableValue(const FieldTable& f) : FieldValue(0xa8, new EncodedValue<FieldTable>(f)) +{ +} + +ListValue::ListValue(const List& l) : FieldValue(0xa9, new EncodedValue<List>(l)) +{ +} + +ArrayValue::ArrayValue(const Array& a) : FieldValue(0xaa, new EncodedValue<Array>(a)) { } -FieldTableValue::FieldTableValue(const FieldTable&) : FieldValue() +VoidValue::VoidValue() : FieldValue(0xf0, new FixedWidthValue<0>()) {} + +BoolValue::BoolValue(bool b) : + FieldValue(0x08, new FixedWidthValue<1>(b)) +{} + +Unsigned8Value::Unsigned8Value(uint8_t v) : + FieldValue(0x02, new FixedWidthValue<1>(v)) +{} +Unsigned16Value::Unsigned16Value(uint16_t v) : + FieldValue(0x12, new FixedWidthValue<2>(v)) +{} +Unsigned32Value::Unsigned32Value(uint32_t v) : + FieldValue(0x22, new FixedWidthValue<4>(v)) +{} + +Integer8Value::Integer8Value(int8_t v) : + FieldValue(0x01, new FixedWidthValue<1>(v)) +{} +Integer16Value::Integer16Value(int16_t v) : + FieldValue(0x11, new FixedWidthValue<2>(v)) +{} + +void FieldValue::print(std::ostream& out) const { + data->print(out); + out << TypeCode(typeOctet) << '('; + if (data->convertsToString()) out << data->getString(); + else if (data->convertsToInt()) out << data->getInt(); + else data->print(out); + out << ')'; +} + +uint8_t* FieldValue::convertIfRequired(uint8_t* const octets, int width) { + return Endian::convertIfRequired(octets, width); } }} diff --git a/cpp/src/qpid/framing/FieldValue.h b/cpp/src/qpid/framing/FieldValue.h deleted file mode 100644 index a4c20bf415..0000000000 --- a/cpp/src/qpid/framing/FieldValue.h +++ /dev/null @@ -1,241 +0,0 @@ -#ifndef _framing_FieldValue_h -#define _framing_FieldValue_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/Exception.h" -#include "Buffer.h" -#include "amqp_types.h" - -#include "assert.h" - -#include <iostream> -#include <memory> -#include <vector> - -namespace qpid { -namespace framing { - -/** - * Exception that is the base exception for all field table errors. - * - * \ingroup clientapi - */ -class FieldValueException : public qpid::Exception {}; - -/** - * Exception thrown when we can't perform requested conversion - * - * \ingroup clientapi - */ -struct InvalidConversionException : public FieldValueException { - InvalidConversionException() {} -}; - -/** - * Value that can appear in an AMQP field table - * - * \ingroup clientapi - */ -class FieldValue { - public: - /* - * Abstract type for content of different types - */ - class Data { - public: - virtual ~Data() {}; - virtual uint32_t size() const = 0; - virtual void encode(Buffer& buffer) = 0; - virtual void decode(Buffer& buffer) = 0; - virtual bool operator==(const Data&) const = 0; - - virtual bool convertsToInt() const { return false; } - virtual bool convertsToString() const { return false; } - virtual int64_t getInt() const { throw InvalidConversionException();} - virtual std::string getString() const { throw InvalidConversionException(); } - - virtual void print(std::ostream& out) const = 0; - }; - - FieldValue(): data(0) {}; - // Default assignment operator is fine - void setType(uint8_t type); - uint8_t getType(); - Data& getData() { return *data; } - uint32_t size() const { return 1 + data->size(); }; - bool empty() const { return data.get() == 0; } - void encode(Buffer& buffer); - void decode(Buffer& buffer); - bool operator==(const FieldValue&) const; - bool operator!=(const FieldValue& v) const { return !(*this == v); } - void print(std::ostream& out) const { out << "(0x" << std::hex << int(typeOctet) << ")"; data->print(out); } - - template <typename T> bool convertsTo() const { return false; } - template <typename T> T get() const { throw InvalidConversionException(); } - - protected: - FieldValue(uint8_t t, Data* d): typeOctet(t), data(d) {} - - private: - uint8_t typeOctet; - std::auto_ptr<Data> data; -}; - -template <> -inline bool FieldValue::convertsTo<int>() const { return data->convertsToInt(); } - -template <> -inline bool FieldValue::convertsTo<std::string>() const { return data->convertsToString(); } - -template <> -inline int FieldValue::get<int>() const { return data->getInt(); } - -template <> -inline std::string FieldValue::get<std::string>() const { return data->getString(); } - -inline std::ostream& operator<<(std::ostream& out, const FieldValue& v) { - v.print(out); - return out; -} - -template <int width> -class FixedWidthValue : public FieldValue::Data { - uint8_t octets[width]; - - public: - FixedWidthValue() {} - FixedWidthValue(const uint8_t (&data)[width]) : octets(data) {} - FixedWidthValue(uint64_t v) - { - for (int i = width; i > 0; --i) { - octets[i-1] = (uint8_t) (0xFF & v); v >>= 8; - } - octets[0] = (uint8_t) (0xFF & v); - } - - uint32_t size() const { return width; } - void encode(Buffer& buffer) { buffer.putRawData(octets, width); } - void decode(Buffer& buffer) { buffer.getRawData(octets, width); } - bool operator==(const Data& d) const { - const FixedWidthValue<width>* rhs = dynamic_cast< const FixedWidthValue<width>* >(&d); - if (rhs == 0) return false; - else return std::equal(&octets[0], &octets[width], &rhs->octets[0]); - } - - bool convertsToInt() const { return true; } - int64_t getInt() const - { - int64_t v = 0; - for (int i = 0; i < width-1; ++i) { - v |= octets[i]; v <<= 8; - } - v |= octets[width-1]; - return v; - } - - void print(std::ostream& o) const { o << "F" << width << ":"; }; -}; - -template <> -class FixedWidthValue<0> : public FieldValue::Data { - public: - // Implicit default constructor is fine - uint32_t size() const { return 0; } - void encode(Buffer&) {}; - void decode(Buffer&) {}; - bool operator==(const Data& d) const { - const FixedWidthValue<0>* rhs = dynamic_cast< const FixedWidthValue<0>* >(&d); - return rhs != 0; - } - void print(std::ostream& o) const { o << "F0"; }; -}; - -template <int lenwidth> -class VariableWidthValue : public FieldValue::Data { - std::vector<uint8_t> octets; - - public: - VariableWidthValue() {} - VariableWidthValue(const std::vector<uint8_t>& data) : octets(data) {} - VariableWidthValue(const uint8_t* start, const uint8_t* end) : octets(start, end) {} - uint32_t size() const { return lenwidth + octets.size(); } - void encode(Buffer& buffer) { - buffer.putUInt<lenwidth>(octets.size()); - buffer.putRawData(&octets[0], octets.size()); - }; - void decode(Buffer& buffer) { - uint32_t len = buffer.getUInt<lenwidth>(); - octets.resize(len); - buffer.getRawData(&octets[0], len); - } - bool operator==(const Data& d) const { - const VariableWidthValue<lenwidth>* rhs = dynamic_cast< const VariableWidthValue<lenwidth>* >(&d); - if (rhs == 0) return false; - else return octets==rhs->octets; - } - - bool convertsToString() const { return true; } - std::string getString() const { return std::string(octets.begin(), octets.end()); } - - void print(std::ostream& o) const { o << "V" << lenwidth << ":" << octets.size() << ":"; }; -}; - -/* - * Basic string value encodes as iso-8859-15 with 32 bit length - */ -class StringValue : public FieldValue { - public: - StringValue(const std::string& v); -}; - -class Str16Value : public FieldValue { - public: - Str16Value(const std::string& v); -}; - -class Struct32Value : public FieldValue { - public: - Struct32Value(const std::string& v); -}; - - -/* - * Basic integer value encodes as signed 32 bit - */ -class IntegerValue : public FieldValue { - public: - IntegerValue(int v); -}; - -class TimeValue : public FieldValue { - public: - TimeValue(uint64_t v); -}; - -class FieldTableValue : public FieldValue { - public: - FieldTableValue(const FieldTable&); -}; - -}} // qpid::framing - -#endif diff --git a/cpp/src/qpid/framing/FrameDecoder.cpp b/cpp/src/qpid/framing/FrameDecoder.cpp new file mode 100644 index 0000000000..90cbbd84a1 --- /dev/null +++ b/cpp/src/qpid/framing/FrameDecoder.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/framing/FrameDecoder.h" +#include "qpid/framing/Buffer.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/reply_exceptions.h" +#include <algorithm> +#include <string.h> + +namespace qpid { +namespace framing { + +namespace { +/** Append up to n bytes from start of buf to end of bytes. */ +void append(std::vector<char>& bytes, Buffer& buffer, size_t n) { + size_t oldSize = bytes.size(); + if ((n = std::min(n, size_t(buffer.available()))) == 0) + return; + bytes.resize(oldSize+n); + char* p = &bytes[oldSize]; + buffer.getRawData(reinterpret_cast<uint8_t*>(p), n); +} +} + +bool FrameDecoder::decode(Buffer& buffer) { + if (buffer.available() == 0) return false; + if (fragment.empty()) { + if (frame.decode(buffer)) // Decode from buffer + return true; + else // Store fragment + append(fragment, buffer, buffer.available()); + } + else { // Already have a fragment + // Get enough data to decode the frame size. + if (fragment.size() < AMQFrame::DECODE_SIZE_MIN) { + append(fragment, buffer, AMQFrame::DECODE_SIZE_MIN - fragment.size()); + } + if (fragment.size() >= AMQFrame::DECODE_SIZE_MIN) { + uint16_t size = AMQFrame::decodeSize(&fragment[0]); + if (size <= fragment.size()) + throw FramingErrorException(QPID_MSG("Frame size " << size << " is too small.")); + append(fragment, buffer, size-fragment.size()); + Buffer b(&fragment[0], fragment.size()); + if (frame.decode(b)) { + assert(b.available() == 0); + fragment.clear(); + return true; + } + } + } + return false; +} + +void FrameDecoder::setFragment(const char* data, size_t size) { + fragment.resize(size); + ::memcpy(&fragment[0], data, size); +} + +std::pair<const char*, size_t> FrameDecoder::getFragment() const { + return std::pair<const char*, size_t>(&fragment[0], fragment.size()); +} + +}} // namespace qpid::framing diff --git a/cpp/src/qpid/framing/FrameDecoder.h b/cpp/src/qpid/framing/FrameDecoder.h new file mode 100644 index 0000000000..26bed6c447 --- /dev/null +++ b/cpp/src/qpid/framing/FrameDecoder.h @@ -0,0 +1,52 @@ +#ifndef QPID_FRAMING_FRAMEDECODER_H +#define QPID_FRAMING_FRAMEDECODER_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/framing/AMQFrame.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace framing { + +/** + * Decode a frame from buffer. If buffer does not contain a complete + * frame, caches the fragment for the next call to decode. + */ +class FrameDecoder +{ + public: + QPID_COMMON_EXTERN bool decode(Buffer& buffer); + const AMQFrame& getFrame() const { return frame; } + AMQFrame& getFrame() { return frame; } + + void setFragment(const char*, size_t); + std::pair<const char*, size_t> getFragment() const; + + private: + std::vector<char> fragment; + AMQFrame frame; + +}; +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_FRAMEDECODER_H*/ diff --git a/cpp/src/qpid/framing/FrameHandler.h b/cpp/src/qpid/framing/FrameHandler.h index 457968c35e..fa1fb535ef 100644 --- a/cpp/src/qpid/framing/FrameHandler.h +++ b/cpp/src/qpid/framing/FrameHandler.h @@ -20,7 +20,7 @@ * under the License. * */ -#include "Handler.h" +#include "qpid/framing/Handler.h" namespace qpid { namespace framing { diff --git a/cpp/src/qpid/framing/FrameSet.cpp b/cpp/src/qpid/framing/FrameSet.cpp index 5326ab7c14..c03dd39458 100644 --- a/cpp/src/qpid/framing/FrameSet.cpp +++ b/cpp/src/qpid/framing/FrameSet.cpp @@ -19,7 +19,7 @@ * */ -#include "FrameSet.h" +#include "qpid/framing/FrameSet.h" #include "qpid/framing/all_method_bodies.h" #include "qpid/framing/frame_functors.h" #include "qpid/framing/MessageProperties.h" @@ -33,7 +33,7 @@ FrameSet::FrameSet(const SequenceNumber& _id) : id(_id),contentSize(0),recalcula void FrameSet::append(const AMQFrame& part) { parts.push_back(part); - recalculateSize = true; + recalculateSize = true; } bool FrameSet::isComplete() const @@ -52,6 +52,11 @@ const AMQMethodBody* FrameSet::getMethod() const return parts.empty() ? 0 : parts[0].getMethod(); } +AMQMethodBody* FrameSet::getMethod() +{ + return parts.empty() ? 0 : parts[0].getMethod(); +} + const AMQHeaderBody* FrameSet::getHeaders() const { return parts.size() < 2 ? 0 : parts[1].castBody<AMQHeaderBody>(); diff --git a/cpp/src/qpid/framing/FrameSet.h b/cpp/src/qpid/framing/FrameSet.h index 82987910a7..398a709353 100644 --- a/cpp/src/qpid/framing/FrameSet.h +++ b/cpp/src/qpid/framing/FrameSet.h @@ -23,6 +23,7 @@ #include "qpid/framing/amqp_framing.h" #include "qpid/framing/AMQFrame.h" #include "qpid/framing/SequenceNumber.h" +#include "qpid/CommonImportExport.h" #ifndef _FrameSet_ #define _FrameSet_ @@ -44,19 +45,21 @@ class FrameSet public: typedef boost::shared_ptr<FrameSet> shared_ptr; - FrameSet(const SequenceNumber& id); - void append(const AMQFrame& part); - bool isComplete() const; + QPID_COMMON_EXTERN FrameSet(const SequenceNumber& id); + QPID_COMMON_EXTERN void append(const AMQFrame& part); + QPID_COMMON_EXTERN bool isComplete() const; - uint64_t getContentSize() const; - void getContent(std::string&) const; - std::string getContent() const; + QPID_COMMON_EXTERN uint64_t getContentSize() const; + + QPID_COMMON_EXTERN void getContent(std::string&) const; + QPID_COMMON_EXTERN std::string getContent() const; bool isContentBearing() const; - const AMQMethodBody* getMethod() const; - const AMQHeaderBody* getHeaders() const; - AMQHeaderBody* getHeaders(); + QPID_COMMON_EXTERN const AMQMethodBody* getMethod() const; + QPID_COMMON_EXTERN AMQMethodBody* getMethod(); + QPID_COMMON_EXTERN const AMQHeaderBody* getHeaders() const; + QPID_COMMON_EXTERN AMQHeaderBody* getHeaders(); template <class T> bool isA() const { const AMQMethodBody* method = getMethod(); @@ -68,11 +71,19 @@ public: return (method && method->isA<T>()) ? dynamic_cast<const T*>(method) : 0; } + template <class T> T* as() { + AMQMethodBody* method = getMethod(); + return (method && method->isA<T>()) ? dynamic_cast<T*>(method) : 0; + } + template <class T> const T* getHeaderProperties() const { const AMQHeaderBody* header = getHeaders(); return header ? header->get<T>() : 0; } + Frames::const_iterator begin() const { return parts.begin(); } + Frames::const_iterator end() const { return parts.end(); } + const SequenceNumber& getId() const { return id; } template <class P> void remove(P predicate) { diff --git a/cpp/src/qpid/framing/Handler.h b/cpp/src/qpid/framing/Handler.h index e07a803e17..fa8db36f49 100644 --- a/cpp/src/qpid/framing/Handler.h +++ b/cpp/src/qpid/framing/Handler.h @@ -21,7 +21,7 @@ * under the License. * */ -#include "qpid/shared_ptr.h" +#include <boost/shared_ptr.hpp> #include <boost/type_traits/remove_reference.hpp> #include <assert.h> diff --git a/cpp/src/qpid/framing/HeaderProperties.h b/cpp/src/qpid/framing/HeaderProperties.h index d66c1d00d6..8b1828daec 100644 --- a/cpp/src/qpid/framing/HeaderProperties.h +++ b/cpp/src/qpid/framing/HeaderProperties.h @@ -18,8 +18,8 @@ * under the License. * */ -#include "amqp_types.h" -#include "Buffer.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/Buffer.h" #ifndef _HeaderProperties_ #define _HeaderProperties_ @@ -33,7 +33,7 @@ namespace framing { public: inline virtual ~HeaderProperties(){} virtual uint8_t classId() const = 0; - virtual uint32_t size() const = 0; + virtual uint32_t encodedSize() const = 0; virtual void encode(Buffer& buffer) const = 0; virtual void decode(Buffer& buffer, uint32_t size) = 0; }; diff --git a/cpp/src/qpid/framing/InitiationHandler.cpp b/cpp/src/qpid/framing/InitiationHandler.cpp index eceeaf4abc..7ded505a47 100644 --- a/cpp/src/qpid/framing/InitiationHandler.cpp +++ b/cpp/src/qpid/framing/InitiationHandler.cpp @@ -19,6 +19,6 @@ * */ -#include "InitiationHandler.h" +#include "qpid/framing/InitiationHandler.h" qpid::framing::InitiationHandler::~InitiationHandler() {} diff --git a/cpp/src/qpid/framing/InitiationHandler.h b/cpp/src/qpid/framing/InitiationHandler.h index 16a6b502e8..5dfcc6b468 100644 --- a/cpp/src/qpid/framing/InitiationHandler.h +++ b/cpp/src/qpid/framing/InitiationHandler.h @@ -23,7 +23,7 @@ #ifndef _InitiationHandler_ #define _InitiationHandler_ -#include "ProtocolInitiation.h" +#include "qpid/framing/ProtocolInitiation.h" namespace qpid { namespace framing { diff --git a/cpp/src/qpid/framing/InputHandler.h b/cpp/src/qpid/framing/InputHandler.h index 3a6d786a24..3efb23632a 100644 --- a/cpp/src/qpid/framing/InputHandler.h +++ b/cpp/src/qpid/framing/InputHandler.h @@ -21,7 +21,7 @@ * */ -#include "FrameHandler.h" +#include "qpid/framing/FrameHandler.h" #include <boost/noncopyable.hpp> namespace qpid { diff --git a/cpp/src/qpid/framing/IsInSequenceSet.h b/cpp/src/qpid/framing/IsInSequenceSet.h new file mode 100644 index 0000000000..fe10c1b9fa --- /dev/null +++ b/cpp/src/qpid/framing/IsInSequenceSet.h @@ -0,0 +1,51 @@ +#ifndef QPID_FRAMING_ISINSEQUENCESET_H +#define QPID_FRAMING_ISINSEQUENCESET_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/framing/SequenceSet.h" + +namespace qpid { +namespace framing { +/** + * Functor to test whether values are in a sequence set. This is a + * stateful functor that requires the values to be supplied in order + * and takes advantage of that ordering to avoid multiple scans. + */ +class IsInSequenceSet +{ + public: + IsInSequenceSet(const SequenceSet& s) : set(s), i(set.rangesBegin()) {} + + bool operator()(const SequenceNumber& n) { + while (i != set.rangesEnd() && i->end() <= n) ++i; + return i != set.rangesEnd() && i->begin() <= n; + } + + private: + const SequenceSet& set; + SequenceSet::RangeIterator i; +}; + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_ISINSEQUENCESET_H*/ diff --git a/cpp/src/qpid/framing/List.cpp b/cpp/src/qpid/framing/List.cpp new file mode 100644 index 0000000000..bde7dabbac --- /dev/null +++ b/cpp/src/qpid/framing/List.cpp @@ -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. + * + */ +#include "qpid/framing/List.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" + +namespace qpid { +namespace framing { + +uint32_t List::encodedSize() const +{ + uint32_t len(4/*size*/ + 4/*count*/); + for(Values::const_iterator i = values.begin(); i != values.end(); ++i) { + len += (*i)->encodedSize(); + } + return len; +} + +void List::encode(Buffer& buffer) const +{ + buffer.putLong(encodedSize() - 4); + buffer.putLong(size()); + for (Values::const_iterator i = values.begin(); i!=values.end(); ++i) { + (*i)->encode(buffer); + } +} + +void List::decode(Buffer& buffer) +{ + values.clear(); + uint32_t size = buffer.getLong(); + uint32_t available = buffer.available(); + if (available < size) { + throw IllegalArgumentException(QPID_MSG("Not enough data for list, expected " + << size << " bytes but only " << available << " available")); + } + if (size) { + uint32_t count = buffer.getLong(); + for (uint32_t i = 0; i < count; i++) { + ValuePtr value(new FieldValue); + value->decode(buffer); + values.push_back(value); + } + } +} + + +bool List::operator==(const List& other) const { + return values.size() == other.values.size() && + std::equal(values.begin(), values.end(), other.values.begin()); +} + +std::ostream& operator<<(std::ostream& out, const List& l) +{ + out << "{"; + for(List::Values::const_iterator i = l.values.begin(); i != l.values.end(); ++i) { + if (i != l.values.begin()) out << ", "; + (*i)->print(out); + } + return out << "}"; +} + +}} // namespace qpid::framing diff --git a/cpp/src/qpid/sys/Monitor.h b/cpp/src/qpid/framing/MethodBodyFactory.h index 1d9835675c..607ec9d959 100644 --- a/cpp/src/qpid/sys/Monitor.h +++ b/cpp/src/qpid/framing/MethodBodyFactory.h @@ -1,5 +1,5 @@ -#ifndef _sys_Monitor_h -#define _sys_Monitor_h +#ifndef QPID_FRAMING_METHODBODYFACTORY_H +#define QPID_FRAMING_METHODBODYFACTORY_H /* * @@ -21,31 +21,24 @@ * under the License. * */ - -#include <sys/errno.h> -#include "Condition.h" +#include "qpid/framing/amqp_types.h" +#include <boost/intrusive_ptr.hpp> namespace qpid { -namespace sys { +namespace framing { + +class AMQMethodBody; /** - * A monitor is a condition variable and a mutex + * Functions to create instances of AMQMethodBody sub-classes. + * Note: MethodBodyFactory.cpp file is generated by rubygen. */ -class Monitor : public Mutex, public Condition { +class MethodBodyFactory +{ public: - using Condition::wait; - inline void wait(); - inline bool wait(const AbsTime& absoluteTime); + static boost::intrusive_ptr<AMQMethodBody> create(ClassId c, MethodId m); }; +}} // namespace qpid::framing -void Monitor::wait() { - Condition::wait(*this); -} - -bool Monitor::wait(const AbsTime& absoluteTime) { - return Condition::wait(*this, absoluteTime); -} - -}} -#endif /*!_sys_Monitor_h*/ +#endif /*!QPID_FRAMING_METHODBODYFACTORY_H*/ diff --git a/cpp/src/qpid/framing/MethodContent.h b/cpp/src/qpid/framing/MethodContent.h index 737c0d6b7b..b290a0c140 100644 --- a/cpp/src/qpid/framing/MethodContent.h +++ b/cpp/src/qpid/framing/MethodContent.h @@ -22,7 +22,7 @@ #define _MethodContent_ #include <string> -#include "AMQHeaderBody.h" +#include "qpid/framing/AMQHeaderBody.h" namespace qpid { namespace framing { diff --git a/cpp/src/qpid/framing/ModelMethod.h b/cpp/src/qpid/framing/ModelMethod.h index 8e4361e761..d99bd06cfa 100644 --- a/cpp/src/qpid/framing/ModelMethod.h +++ b/cpp/src/qpid/framing/ModelMethod.h @@ -21,7 +21,7 @@ * under the License. * */ -#include "AMQMethodBody.h" +#include "qpid/framing/AMQMethodBody.h" #include "qpid/framing/Header.h" namespace qpid { @@ -35,7 +35,7 @@ public: virtual ~ModelMethod() {} virtual void encodeHeader(Buffer& buffer) const { header.encode(buffer); } virtual void decodeHeader(Buffer& buffer, uint32_t size=0) { header.decode(buffer, size); } - virtual uint32_t headerSize() const { return header.size(); } + virtual uint32_t headerSize() const { return header.encodedSize(); } virtual bool isSync() const { return header.getSync(); } virtual void setSync(bool on) const { header.setSync(on); } Header& getHeader() { return header; } diff --git a/cpp/src/qpid/framing/OutputHandler.h b/cpp/src/qpid/framing/OutputHandler.h index 6f4b27fb72..88c95589da 100644 --- a/cpp/src/qpid/framing/OutputHandler.h +++ b/cpp/src/qpid/framing/OutputHandler.h @@ -22,7 +22,7 @@ * */ #include <boost/noncopyable.hpp> -#include "FrameHandler.h" +#include "qpid/framing/FrameHandler.h" namespace qpid { namespace framing { diff --git a/cpp/src/qpid/framing/ProtocolInitiation.cpp b/cpp/src/qpid/framing/ProtocolInitiation.cpp index 50617de017..e617015d64 100644 --- a/cpp/src/qpid/framing/ProtocolInitiation.cpp +++ b/cpp/src/qpid/framing/ProtocolInitiation.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "ProtocolInitiation.h" +#include "qpid/framing/ProtocolInitiation.h" namespace qpid { namespace framing { diff --git a/cpp/src/qpid/framing/ProtocolInitiation.h b/cpp/src/qpid/framing/ProtocolInitiation.h index 43e32da4cf..c519bc2442 100644 --- a/cpp/src/qpid/framing/ProtocolInitiation.h +++ b/cpp/src/qpid/framing/ProtocolInitiation.h @@ -18,10 +18,11 @@ * under the License. * */ -#include "amqp_types.h" -#include "Buffer.h" -#include "AMQDataBlock.h" -#include "ProtocolVersion.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/AMQDataBlock.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/CommonImportExport.h" #ifndef _ProtocolInitiation_ #define _ProtocolInitiation_ @@ -35,20 +36,20 @@ private: ProtocolVersion version; public: - ProtocolInitiation(); - ProtocolInitiation(uint8_t major, uint8_t minor); - ProtocolInitiation(ProtocolVersion p); - virtual ~ProtocolInitiation(); - virtual void encode(Buffer& buffer) const; - virtual bool decode(Buffer& buffer); - inline virtual uint32_t size() const { return 8; } + QPID_COMMON_EXTERN ProtocolInitiation(); + QPID_COMMON_EXTERN ProtocolInitiation(uint8_t major, uint8_t minor); + QPID_COMMON_EXTERN ProtocolInitiation(ProtocolVersion p); + QPID_COMMON_EXTERN virtual ~ProtocolInitiation(); + QPID_COMMON_EXTERN virtual void encode(Buffer& buffer) const; + QPID_COMMON_EXTERN virtual bool decode(Buffer& buffer); + inline virtual uint32_t encodedSize() const { return 8; } inline uint8_t getMajor() const { return version.getMajor(); } inline uint8_t getMinor() const { return version.getMinor(); } inline ProtocolVersion getVersion() const { return version; } bool operator==(ProtocolVersion v) const { return v == getVersion(); } }; -std::ostream& operator<<(std::ostream& o, const framing::ProtocolInitiation& pi); +QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& o, const framing::ProtocolInitiation& pi); } diff --git a/cpp/src/qpid/framing/ProtocolVersion.cpp b/cpp/src/qpid/framing/ProtocolVersion.cpp index 7a96bfa925..c63cddb4cc 100644 --- a/cpp/src/qpid/framing/ProtocolVersion.cpp +++ b/cpp/src/qpid/framing/ProtocolVersion.cpp @@ -18,7 +18,7 @@ * under the License. * */ -#include "ProtocolVersion.h" +#include "qpid/framing/ProtocolVersion.h" #include <sstream> using namespace qpid::framing; diff --git a/cpp/src/qpid/framing/ProtocolVersion.h b/cpp/src/qpid/framing/ProtocolVersion.h deleted file mode 100644 index 9a7ebec491..0000000000 --- a/cpp/src/qpid/framing/ProtocolVersion.h +++ /dev/null @@ -1,57 +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. - * - */ -#ifndef _ProtocolVersion_ -#define _ProtocolVersion_ - -#include "amqp_types.h" - -namespace qpid -{ -namespace framing -{ - -class ProtocolVersion -{ -private: - uint8_t major_; - uint8_t minor_; - -public: - explicit ProtocolVersion(uint8_t _major=0, uint8_t _minor=0) - : major_(_major), minor_(_minor) {} - - uint8_t getMajor() const { return major_; } - void setMajor(uint8_t major) { major_ = major; } - uint8_t getMinor() const { return minor_; } - void setMinor(uint8_t minor) { minor_ = minor; } - const std::string toString() const; - - ProtocolVersion& operator=(ProtocolVersion p); - - bool operator==(ProtocolVersion p) const; - bool operator!=(ProtocolVersion p) const { return ! (*this == p); } -}; - -} // namespace framing -} // namespace qpid - - -#endif // ifndef _ProtocolVersion_ diff --git a/cpp/src/qpid/framing/Proxy.cpp b/cpp/src/qpid/framing/Proxy.cpp index 6b37fb368d..452fb13b01 100644 --- a/cpp/src/qpid/framing/Proxy.cpp +++ b/cpp/src/qpid/framing/Proxy.cpp @@ -16,17 +16,23 @@ * */ -#include "Proxy.h" -#include "AMQFrame.h" +#include "qpid/framing/Proxy.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/log/Statement.h" namespace qpid { namespace framing { -Proxy::Proxy(FrameHandler& h) : out(&h) {} +Proxy::Proxy(FrameHandler& h) : out(&h), sync(false) {} Proxy::~Proxy() {} void Proxy::send(const AMQBody& b) { + if (sync) { + const AMQMethodBody* m = dynamic_cast<const AMQMethodBody*>(&b); + if (m) m->setSync(sync); + } AMQFrame f(b); out->handle(f); } @@ -39,4 +45,7 @@ FrameHandler& Proxy::getHandler() { return *out; } void Proxy::setHandler(FrameHandler& f) { out=&f; } +Proxy::ScopedSync::ScopedSync(Proxy& p) : proxy(p) { proxy.sync = true; } +Proxy::ScopedSync::~ScopedSync() { proxy.sync = false; } + }} // namespace qpid::framing diff --git a/cpp/src/qpid/framing/Proxy.h b/cpp/src/qpid/framing/Proxy.h index 3dc082097a..0884e9cbd2 100644 --- a/cpp/src/qpid/framing/Proxy.h +++ b/cpp/src/qpid/framing/Proxy.h @@ -19,8 +19,10 @@ * */ -#include "FrameHandler.h" -#include "ProtocolVersion.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/ProtocolVersion.h" + +#include "qpid/CommonImportExport.h" namespace qpid { namespace framing { @@ -33,18 +35,26 @@ class AMQBody; class Proxy { public: - Proxy(FrameHandler& h); - virtual ~Proxy(); + class ScopedSync + { + Proxy& proxy; + public: + QPID_COMMON_EXTERN ScopedSync(Proxy& p); + QPID_COMMON_EXTERN ~ScopedSync(); + }; - void send(const AMQBody&); + QPID_COMMON_EXTERN Proxy(FrameHandler& h); + QPID_COMMON_EXTERN virtual ~Proxy(); - ProtocolVersion getVersion() const; + QPID_COMMON_EXTERN void send(const AMQBody&); - FrameHandler& getHandler(); - void setHandler(FrameHandler&); + QPID_COMMON_EXTERN ProtocolVersion getVersion() const; + QPID_COMMON_EXTERN FrameHandler& getHandler(); + QPID_COMMON_EXTERN void setHandler(FrameHandler&); private: FrameHandler* out; + bool sync; }; }} // namespace qpid::framing diff --git a/cpp/src/qpid/framing/SendContent.cpp b/cpp/src/qpid/framing/SendContent.cpp index a62e4eeb72..a26c19037b 100644 --- a/cpp/src/qpid/framing/SendContent.cpp +++ b/cpp/src/qpid/framing/SendContent.cpp @@ -19,7 +19,7 @@ * */ -#include "SendContent.h" +#include "qpid/framing/SendContent.h" qpid::framing::SendContent::SendContent(FrameHandler& h, uint16_t mfs, uint efc) : handler(h), maxFrameSize(mfs), @@ -34,13 +34,13 @@ void qpid::framing::SendContent::operator()(const AMQFrame& f) real frame size, hence substract -1 from frameOverhead()*/ uint16_t maxContentSize = maxFrameSize - (AMQFrame::frameOverhead() - 1); const AMQContentBody* body(f.castBody<AMQContentBody>()); - if (body->size() > maxContentSize) { + if (body->encodedSize() > maxContentSize) { uint32_t offset = 0; - for (int chunk = body->size() / maxContentSize; chunk > 0; chunk--) { - sendFragment(*body, offset, maxContentSize, first && offset == 0, last && offset + maxContentSize == body->size()); + for (int chunk = body->encodedSize() / maxContentSize; chunk > 0; chunk--) { + sendFragment(*body, offset, maxContentSize, first && offset == 0, last && offset + maxContentSize == body->encodedSize()); offset += maxContentSize; } - uint32_t remainder = body->size() % maxContentSize; + uint32_t remainder = body->encodedSize() % maxContentSize; if (remainder) { sendFragment(*body, offset, remainder, first && offset == 0, last); } @@ -53,8 +53,7 @@ void qpid::framing::SendContent::operator()(const AMQFrame& f) void qpid::framing::SendContent::sendFragment(const AMQContentBody& body, uint32_t offset, uint16_t size, bool first, bool last) const { - AMQFrame fragment(in_place<AMQContentBody>( - body.getData().substr(offset, size))); + AMQFrame fragment((AMQContentBody(body.getData().substr(offset, size)))); setFlags(fragment, first, last); handler.handle(fragment); } diff --git a/cpp/src/qpid/framing/SendContent.h b/cpp/src/qpid/framing/SendContent.h index dcd5202b3e..745c948c9e 100644 --- a/cpp/src/qpid/framing/SendContent.h +++ b/cpp/src/qpid/framing/SendContent.h @@ -22,6 +22,7 @@ #include "qpid/framing/amqp_framing.h" #include "qpid/framing/AMQFrame.h" #include "qpid/framing/FrameHandler.h" +#include "qpid/CommonImportExport.h" #ifndef _SendContent_ #define _SendContent_ @@ -44,8 +45,8 @@ class SendContent void sendFragment(const AMQContentBody& body, uint32_t offset, uint16_t size, bool first, bool last) const; void setFlags(AMQFrame& f, bool first, bool last) const; public: - SendContent(FrameHandler& _handler, uint16_t _maxFrameSize, uint frameCount); - void operator()(const AMQFrame& f); + QPID_COMMON_EXTERN SendContent(FrameHandler& _handler, uint16_t _maxFrameSize, uint frameCount); + QPID_COMMON_EXTERN void operator()(const AMQFrame& f); }; } diff --git a/cpp/src/qpid/framing/SequenceNumber.cpp b/cpp/src/qpid/framing/SequenceNumber.cpp index 7caaf5440b..41cb236629 100644 --- a/cpp/src/qpid/framing/SequenceNumber.cpp +++ b/cpp/src/qpid/framing/SequenceNumber.cpp @@ -19,67 +19,13 @@ * */ -#include "SequenceNumber.h" -#include "Buffer.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/Buffer.h" #include <ostream> using qpid::framing::SequenceNumber; using qpid::framing::Buffer; -SequenceNumber::SequenceNumber() : value(0) {} - -SequenceNumber::SequenceNumber(uint32_t v) : value((int32_t) v) {} - -bool SequenceNumber::operator==(const SequenceNumber& other) const -{ - return value == other.value; -} - -bool SequenceNumber::operator!=(const SequenceNumber& other) const -{ - return !(value == other.value); -} - - -SequenceNumber& SequenceNumber::operator++() -{ - value = value + 1; - return *this; -} - -const SequenceNumber SequenceNumber::operator++(int) -{ - SequenceNumber old(value); - value = value + 1; - return old; -} - -SequenceNumber& SequenceNumber::operator--() -{ - value = value - 1; - return *this; -} - -bool SequenceNumber::operator<(const SequenceNumber& other) const -{ - return (value - other.value) < 0; -} - -bool SequenceNumber::operator>(const SequenceNumber& other) const -{ - return other < *this; -} - -bool SequenceNumber::operator<=(const SequenceNumber& other) const -{ - return *this == other || *this < other; -} - -bool SequenceNumber::operator>=(const SequenceNumber& other) const -{ - return *this == other || *this > other; -} - void SequenceNumber::encode(Buffer& buffer) const { buffer.putLong(value); @@ -90,19 +36,13 @@ void SequenceNumber::decode(Buffer& buffer) value = buffer.getLong(); } -uint32_t SequenceNumber::size() const { +uint32_t SequenceNumber::encodedSize() const { return 4; } namespace qpid { namespace framing { -int32_t operator-(const SequenceNumber& a, const SequenceNumber& b) -{ - int32_t result = a.value - b.value; - return result; -} - std::ostream& operator<<(std::ostream& o, const SequenceNumber& n) { return o << n.getValue(); } diff --git a/cpp/src/qpid/framing/SequenceNumber.h b/cpp/src/qpid/framing/SequenceNumber.h deleted file mode 100644 index aacd77501b..0000000000 --- a/cpp/src/qpid/framing/SequenceNumber.h +++ /dev/null @@ -1,75 +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. - * - */ -#ifndef _framing_SequenceNumber_h -#define _framing_SequenceNumber_h - -#include "amqp_types.h" -#include <iosfwd> - -namespace qpid { -namespace framing { - -class Buffer; - -/** - * 4-byte sequence number that 'wraps around'. - */ -class SequenceNumber -{ - int32_t value; - - public: - SequenceNumber(); - SequenceNumber(uint32_t v); - - SequenceNumber& operator++();//prefix ++ - const SequenceNumber operator++(int);//postfix ++ - SequenceNumber& operator--();//prefix ++ - bool operator==(const SequenceNumber& other) const; - bool operator!=(const SequenceNumber& other) const; - bool operator<(const SequenceNumber& other) const; - bool operator>(const SequenceNumber& other) const; - bool operator<=(const SequenceNumber& other) const; - bool operator>=(const SequenceNumber& other) const; - uint32_t getValue() const { return (uint32_t) value; } - operator uint32_t() const { return (uint32_t) value; } - - friend int32_t operator-(const SequenceNumber& a, const SequenceNumber& b); - - void encode(Buffer& buffer) const; - void decode(Buffer& buffer); - uint32_t size() const; - - template <class S> void serialize(S& s) { s(value); } -}; - -struct Window -{ - SequenceNumber hwm; - SequenceNumber lwm; -}; - -std::ostream& operator<<(std::ostream& o, const SequenceNumber& n); - -}} // namespace qpid::framing - - -#endif diff --git a/cpp/src/qpid/framing/SequenceNumberSet.cpp b/cpp/src/qpid/framing/SequenceNumberSet.cpp index afab9033e5..e9d78f3c17 100644 --- a/cpp/src/qpid/framing/SequenceNumberSet.cpp +++ b/cpp/src/qpid/framing/SequenceNumberSet.cpp @@ -19,7 +19,7 @@ * */ -#include "SequenceNumberSet.h" +#include "qpid/framing/SequenceNumberSet.h" using namespace qpid::framing; @@ -33,6 +33,7 @@ void SequenceNumberSet::encode(Buffer& buffer) const void SequenceNumberSet::decode(Buffer& buffer) { + clear(); uint16_t count = (buffer.getShort() / 4); for (uint16_t i = 0; i < count; i++) { push_back(SequenceNumber(buffer.getLong())); diff --git a/cpp/src/qpid/framing/SequenceNumberSet.h b/cpp/src/qpid/framing/SequenceNumberSet.h index 666307f9d9..c8356c8163 100644 --- a/cpp/src/qpid/framing/SequenceNumberSet.h +++ b/cpp/src/qpid/framing/SequenceNumberSet.h @@ -22,11 +22,12 @@ #define _framing_SequenceNumberSet_h #include <ostream> -#include "amqp_types.h" -#include "Buffer.h" -#include "SequenceNumber.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/SequenceNumber.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/InlineVector.h" +#include "qpid/CommonImportExport.h" namespace qpid { namespace framing { @@ -41,8 +42,8 @@ public: void encode(Buffer& buffer) const; void decode(Buffer& buffer); uint32_t encodedSize() const; - SequenceNumberSet condense() const; - void addRange(const SequenceNumber& start, const SequenceNumber& end); + QPID_COMMON_EXTERN SequenceNumberSet condense() const; + QPID_COMMON_EXTERN void addRange(const SequenceNumber& start, const SequenceNumber& end); template <class T> void processRanges(T& t) const @@ -58,7 +59,7 @@ public: } } - friend std::ostream& operator<<(std::ostream&, const SequenceNumberSet&); + friend QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const SequenceNumberSet&); }; diff --git a/cpp/src/qpid/framing/SequenceSet.cpp b/cpp/src/qpid/framing/SequenceSet.cpp index 9610b9180c..dcfb4689b6 100644 --- a/cpp/src/qpid/framing/SequenceSet.cpp +++ b/cpp/src/qpid/framing/SequenceSet.cpp @@ -19,8 +19,8 @@ * */ -#include "SequenceSet.h" -#include "Buffer.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/framing/Buffer.h" #include "qpid/framing/reply_exceptions.h" using namespace qpid::framing; @@ -46,6 +46,7 @@ void SequenceSet::encode(Buffer& buffer) const void SequenceSet::decode(Buffer& buffer) { + clear(); uint16_t size = buffer.getShort(); uint16_t count = size / RANGE_SIZE;//number of ranges if (size % RANGE_SIZE) @@ -56,7 +57,7 @@ void SequenceSet::decode(Buffer& buffer) } } -uint32_t SequenceSet::size() const { +uint32_t SequenceSet::encodedSize() const { return 2 /*size field*/ + (rangesSize() * RANGE_SIZE); } diff --git a/cpp/src/qpid/framing/SequenceSet.h b/cpp/src/qpid/framing/SequenceSet.h deleted file mode 100644 index 99e7cb4b21..0000000000 --- a/cpp/src/qpid/framing/SequenceSet.h +++ /dev/null @@ -1,68 +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. - * - */ -#ifndef _framing_SequenceSet_h -#define _framing_SequenceSet_h - -#include "SequenceNumber.h" -#include "qpid/RangeSet.h" - -namespace qpid { -namespace framing { -class Buffer; - -class SequenceSet : public RangeSet<SequenceNumber> { - public: - SequenceSet() {} - explicit SequenceSet(const RangeSet<SequenceNumber>& r) - : RangeSet<SequenceNumber>(r) {} - explicit SequenceSet(const SequenceNumber& s) { add(s); } - SequenceSet(const SequenceNumber& start, const SequenceNumber finish) { add(start,finish); } - - - void encode(Buffer& buffer) const; - void decode(Buffer& buffer); - uint32_t size() const; - - bool contains(const SequenceNumber& s) const; - void add(const SequenceNumber& s); - void add(const SequenceNumber& start, const SequenceNumber& finish); // Closed range - void add(const SequenceSet& set); - void remove(const SequenceNumber& s); - void remove(const SequenceNumber& start, const SequenceNumber& finish); // Closed range - void remove(const SequenceSet& set); - - template <class T> void for_each(T& t) const { - for (RangeIterator i = rangesBegin(); i != rangesEnd(); i++) - t(i->first(), i->last()); - } - - template <class T> void for_each(const T& t) const { - for (RangeIterator i = rangesBegin(); i != rangesEnd(); i++) - t(i->first(), i->last()); - } - - friend std::ostream& operator<<(std::ostream&, const SequenceSet&); -}; - -}} // namespace qpid::framing - - -#endif diff --git a/cpp/src/qpid/framing/StructHelper.h b/cpp/src/qpid/framing/StructHelper.h deleted file mode 100644 index e3dce4f5ec..0000000000 --- a/cpp/src/qpid/framing/StructHelper.h +++ /dev/null @@ -1,56 +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. - * - */ -#ifndef _StructHelper_ -#define _StructHelper_ - -#include "qpid/Exception.h" -#include "Buffer.h" - -#include <stdlib.h> // For alloca - -namespace qpid { -namespace framing { - -class StructHelper -{ -public: - - template <class T> void encode(const T t, std::string& data) { - uint32_t size = t.bodySize() + 2/*type*/; - data.resize(size); - Buffer wbuffer(const_cast<char*>(data.data()), size); - wbuffer.putShort(T::TYPE); - t.encodeStructBody(wbuffer); - } - - template <class T> void decode(T& t, const std::string& data) { - Buffer rbuffer(const_cast<char*>(data.data()), data.length()); - uint16_t type = rbuffer.getShort(); - if (type == T::TYPE) { - t.decodeStructBody(rbuffer); - } else { - throw Exception("Type code does not match"); - } - } -}; - -}} -#endif diff --git a/cpp/src/qpid/framing/TransferContent.cpp b/cpp/src/qpid/framing/TransferContent.cpp index 42af2027eb..837d7d346a 100644 --- a/cpp/src/qpid/framing/TransferContent.cpp +++ b/cpp/src/qpid/framing/TransferContent.cpp @@ -19,20 +19,17 @@ * */ -#include "TransferContent.h" +#include "qpid/framing/TransferContent.h" namespace qpid { namespace framing { -TransferContent::TransferContent(const std::string& data, - const std::string& routingKey, - const std::string& exchange) -{ +TransferContent::TransferContent(const std::string& data, const std::string& key) { setData(data); - if (routingKey.size()) getDeliveryProperties().setRoutingKey(routingKey); - if (exchange.size()) getDeliveryProperties().setExchange(exchange); + if (!key.empty()) getDeliveryProperties().setRoutingKey(key); } + AMQHeaderBody TransferContent::getHeader() const { return header; diff --git a/cpp/src/qpid/framing/TransferContent.h b/cpp/src/qpid/framing/TransferContent.h index 7630421bd4..5fe1a513a9 100644 --- a/cpp/src/qpid/framing/TransferContent.h +++ b/cpp/src/qpid/framing/TransferContent.h @@ -21,11 +21,12 @@ #ifndef _TransferContent_ #define _TransferContent_ -#include "FrameSet.h" -#include "MethodContent.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MethodContent.h" #include "qpid/Exception.h" #include "qpid/framing/MessageProperties.h" #include "qpid/framing/DeliveryProperties.h" +#include "qpid/CommonImportExport.h" namespace qpid { namespace framing { @@ -36,29 +37,27 @@ class TransferContent : public MethodContent AMQHeaderBody header; std::string data; public: - TransferContent(const std::string& data = std::string(), - const std::string& routingKey = std::string(), - const std::string& exchange = std::string()); + QPID_COMMON_EXTERN TransferContent(const std::string& data = std::string(), const std::string& key=std::string()); ///@internal - AMQHeaderBody getHeader() const; + QPID_COMMON_EXTERN AMQHeaderBody getHeader() const; - void setData(const std::string&); - const std::string& getData() const; - std::string& getData(); + QPID_COMMON_EXTERN void setData(const std::string&); + QPID_COMMON_EXTERN const std::string& getData() const; + QPID_COMMON_EXTERN std::string& getData(); - void appendData(const std::string&); + QPID_COMMON_EXTERN void appendData(const std::string&); - bool hasMessageProperties() const; - MessageProperties& getMessageProperties(); - const MessageProperties& getMessageProperties() const; + QPID_COMMON_EXTERN bool hasMessageProperties() const; + QPID_COMMON_EXTERN MessageProperties& getMessageProperties(); + QPID_COMMON_EXTERN const MessageProperties& getMessageProperties() const; - bool hasDeliveryProperties() const; - DeliveryProperties& getDeliveryProperties(); - const DeliveryProperties& getDeliveryProperties() const; + QPID_COMMON_EXTERN bool hasDeliveryProperties() const; + QPID_COMMON_EXTERN DeliveryProperties& getDeliveryProperties(); + QPID_COMMON_EXTERN const DeliveryProperties& getDeliveryProperties() const; ///@internal - void populate(const FrameSet& frameset); + QPID_COMMON_EXTERN void populate(const FrameSet& frameset); }; }} diff --git a/cpp/src/qpid/framing/Uuid.cpp b/cpp/src/qpid/framing/Uuid.cpp index e8d8d517dd..432c7ab94e 100644 --- a/cpp/src/qpid/framing/Uuid.cpp +++ b/cpp/src/qpid/framing/Uuid.cpp @@ -16,7 +16,9 @@ * */ -#include "Uuid.h" +#include "qpid/framing/Uuid.h" + +#include "qpid/sys/uuid.h" #include "qpid/Exception.h" #include "qpid/framing/Buffer.h" #include "qpid/framing/reply_exceptions.h" @@ -28,6 +30,37 @@ using namespace std; static const size_t UNPARSED_SIZE=36; +Uuid::Uuid(bool unique) { + if (unique) { + generate(); + } else { + clear(); + } +} + +Uuid::Uuid(const uint8_t* data) { + assign(data); +} + +void Uuid::assign(const uint8_t* data) { + // This const cast is for Solaris which has a + // uuid_copy that takes a non const 2nd argument + uuid_copy(c_array(), const_cast<uint8_t*>(data)); +} + +void Uuid::generate() { + uuid_generate(c_array()); +} + +void Uuid::clear() { + uuid_clear(c_array()); +} + +// Force int 0/!0 to false/true; avoids compile warnings. +bool Uuid::isNull() const { + return !!uuid_is_null(data()); +} + void Uuid::encode(Buffer& buf) const { buf.putRawData(data(), size()); } @@ -52,7 +85,7 @@ istream& operator>>(istream& in, Uuid& uuid) { return in; } -std::string Uuid::str() { +std::string Uuid::str() const { std::ostringstream os; os << *this; return os.str(); diff --git a/cpp/src/qpid/framing/Uuid.h b/cpp/src/qpid/framing/Uuid.h deleted file mode 100644 index e3e309a56c..0000000000 --- a/cpp/src/qpid/framing/Uuid.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef QPID_FRAMING_UUID_H -#define QPID_FRAMING_UUID_H - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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/array.hpp> - -#include <ostream> -#include <istream> - -#include <uuid/uuid.h> - -namespace qpid { -namespace framing { - -class Buffer; - -/** - * A UUID is represented as a boost::array of 16 bytes. - * - * Full value semantics, operators ==, < etc. are provided by - * boost::array so Uuid can be the key type in a map etc. - */ -struct Uuid : public boost::array<uint8_t, 16> { - /** If unique is true, generate a unique ID else a null ID. */ - Uuid(bool unique=false) { if (unique) generate(); else clear(); } - - /** Copy from 16 bytes of data. */ - Uuid(uint8_t* data) { assign(data); } - - /** Copy from 16 bytes of data. */ - void assign(uint8_t* data) { - uuid_copy(c_array(), data); - } - - /** Set to a new unique identifier. */ - void generate() { uuid_generate(c_array()); } - - /** Set to all zeros. */ - void clear() { uuid_clear(c_array()); } - - /** Test for null (all zeros). */ - bool isNull() { - return uuid_is_null(data()); - } - - // Default op= and copy ctor are fine. - // boost::array gives us ==, < etc. - - void encode(framing::Buffer& buf) const; - - void decode(framing::Buffer& buf); - - /** String value in format 1b4e28ba-2fa1-11d2-883f-b9a761bde3fb. */ - std::string str(); - - template <class S> void serialize(S& s) { - s.raw(begin(), size()); - } -}; - -/** Print in format 1b4e28ba-2fa1-11d2-883f-b9a761bde3fb. */ -std::ostream& operator<<(std::ostream&, Uuid); - -/** Read from format 1b4e28ba-2fa1-11d2-883f-b9a761bde3fb. */ -std::istream& operator>>(std::istream&, Uuid&); - -}} // namespace qpid::framing - - - -#endif /*!QPID_FRAMING_UUID_H*/ diff --git a/cpp/src/qpid/framing/amqp_framing.h b/cpp/src/qpid/framing/amqp_framing.h index 4e4747c3f4..3a8b39afb5 100644 --- a/cpp/src/qpid/framing/amqp_framing.h +++ b/cpp/src/qpid/framing/amqp_framing.h @@ -18,15 +18,15 @@ * under the License. * */ -#include "amqp_types.h" -#include "AMQFrame.h" -#include "AMQBody.h" -#include "BodyHandler.h" -#include "AMQMethodBody.h" -#include "AMQHeaderBody.h" -#include "AMQContentBody.h" -#include "AMQHeartbeatBody.h" -#include "InputHandler.h" -#include "OutputHandler.h" -#include "ProtocolInitiation.h" -#include "ProtocolVersion.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/BodyHandler.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/AMQHeartbeatBody.h" +#include "qpid/framing/InputHandler.h" +#include "qpid/framing/OutputHandler.h" +#include "qpid/framing/ProtocolInitiation.h" +#include "qpid/framing/ProtocolVersion.h" diff --git a/cpp/src/qpid/framing/amqp_types.h b/cpp/src/qpid/framing/amqp_types.h deleted file mode 100644 index 8b10aa3069..0000000000 --- a/cpp/src/qpid/framing/amqp_types.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef AMQP_TYPES_H -#define AMQP_TYPES_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. - * - */ - -/** \file - * Definitions and forward declarations of all types used - * in AMQP messages. - */ - -#include <string> -#ifdef _WINDOWS -#include "windows.h" -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef unsigned __int64 uint64_t; -#endif -#ifndef _WINDOWS -#include <stdint.h> -#endif - -namespace qpid { -namespace framing { - -using std::string; -typedef uint8_t FrameType; -typedef uint16_t ChannelId; -typedef uint32_t BatchOffset; -typedef uint8_t ClassId; -typedef uint8_t MethodId; -typedef uint16_t ReplyCode; - -// Types represented by classes. -class Content; -class FieldTable; -class SequenceNumberSet; -class Uuid; - -// Useful constants - -/** Maximum channel ID used by broker. Reserve high bit for internal use.*/ -const ChannelId CHANNEL_MAX=(ChannelId(~1))>>1; -const ChannelId CHANNEL_HIGH_BIT= ChannelId(~CHANNEL_MAX); - -// Forward declare class types -class FramingContent; -class FieldTable; -class SequenceNumberSet; -class SequenceSet; -class Uuid; - -}} // namespace qpid::framing -#endif diff --git a/cpp/src/qpid/framing/frame_functors.h b/cpp/src/qpid/framing/frame_functors.h index 8ae3d15b4f..d2064d6a57 100644 --- a/cpp/src/qpid/framing/frame_functors.h +++ b/cpp/src/qpid/framing/frame_functors.h @@ -36,7 +36,7 @@ class SumFrameSize uint64_t size; public: SumFrameSize() : size(0) {} - void operator()(const AMQFrame& f) { size += f.size(); } + void operator()(const AMQFrame& f) { size += f.encodedSize(); } uint64_t getSize() { return size; } }; @@ -45,7 +45,7 @@ class SumBodySize uint64_t size; public: SumBodySize() : size(0) {} - void operator()(const AMQFrame& f) { size += f.getBody()->size(); } + void operator()(const AMQFrame& f) { size += f.getBody()->encodedSize(); } uint64_t getSize() { return size; } }; diff --git a/cpp/src/qpid/log/Logger.cpp b/cpp/src/qpid/log/Logger.cpp index 54df54bb94..939e2502cc 100644 --- a/cpp/src/qpid/log/Logger.cpp +++ b/cpp/src/qpid/log/Logger.cpp @@ -16,20 +16,19 @@ * */ -#include "Logger.h" -#include "Options.h" +#include "qpid/log/Logger.h" +#include "qpid/log/Options.h" +#include "qpid/log/SinkOptions.h" #include "qpid/memory.h" #include "qpid/sys/Thread.h" +#include "qpid/sys/Time.h" #include <boost/pool/detail/singleton.hpp> #include <boost/bind.hpp> #include <boost/function.hpp> -#include <boost/scoped_ptr.hpp> #include <algorithm> #include <sstream> -#include <fstream> #include <iomanip> #include <stdexcept> -#include <syslog.h> #include <time.h> @@ -44,45 +43,6 @@ inline void Logger::enable_unlocked(Statement* s) { s->enabled=selector.isEnabled(s->level, s->function); } -struct OstreamOutput : public Logger::Output { - OstreamOutput(std::ostream& o) : out(&o) {} - - OstreamOutput(const string& file) - : out(new ofstream(file.c_str(), ios_base::out | ios_base::app)), - mine(out) - { - if (!out->good()) - throw std::runtime_error("Can't open log file: "+file); - } - - void log(const Statement&, const std::string& m) { - *out << m << flush; - } - - ostream* out; - boost::scoped_ptr<ostream> mine; -}; - -struct SyslogOutput : public Logger::Output { - SyslogOutput(const Options& opts) - : name(opts.syslogName), facility(opts.syslogFacility.value) - { - ::openlog(name.c_str(), LOG_PID, facility); - } - - ~SyslogOutput() { - ::closelog(); - } - - void log(const Statement& s, const std::string& m) - { - syslog(LevelTraits::priority(s.level), "%s", m.c_str()); - } - - std::string name; - int facility; -}; - Logger& Logger::instance() { return boost::details::pool::singleton_default<Logger>::instance(); } @@ -114,25 +74,7 @@ void Logger::log(const Statement& s, const std::string& msg) { if (!prefix.empty()) os << prefix << ": "; if (flags&TIME) - { - const char * month_abbrevs[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; - time_t rawtime; - struct tm * timeinfo; - - time ( & rawtime ); - timeinfo = localtime ( &rawtime ); - char time_string[100]; - sprintf ( time_string, - "%d-%s-%02d %02d:%02d:%02d", - 1900 + timeinfo->tm_year, - month_abbrevs[timeinfo->tm_mon], - timeinfo->tm_mday, - timeinfo->tm_hour, - timeinfo->tm_min, - timeinfo->tm_sec - ); - os << time_string << " "; - } + qpid::sys::outputFormattedNow(os); if (flags&LEVEL) os << LevelTraits::name(s.level) << " "; if (flags&THREAD) @@ -159,25 +101,6 @@ void Logger::output(std::auto_ptr<Output> out) { outputs.push_back(out.release()); } -void Logger::output(std::ostream& out) { - output(make_auto_ptr<Output>(new OstreamOutput(out))); -} - -void Logger::syslog(const Options& opts) { - output(make_auto_ptr<Output>(new SyslogOutput(opts))); -} - -void Logger::output(const std::string& name, const Options& opts) { - if (name=="stderr") - output(clog); - else if (name=="stdout") - output(cout); - else if (name=="syslog") - syslog(opts); - else - output(make_auto_ptr<Output>(new OstreamOutput(name))); -} - void Logger::clear() { select(Selector()); // locked format(0); // locked @@ -212,16 +135,15 @@ void Logger::add(Statement& s) { } void Logger::configure(const Options& opts) { + options = opts; clear(); Options o(opts); if (o.trace) o.selectors.push_back("trace+"); format(o); select(Selector(o)); - void (Logger::* outputFn)(const std::string&, const Options&) = &Logger::output; - for_each(o.outputs.begin(), o.outputs.end(), - boost::bind(outputFn, this, _1, boost::cref(o))); setPrefix(opts.prefix); + options.sinkOptions->setup(this); } void Logger::setPrefix(const std::string& p) { prefix = p; } diff --git a/cpp/src/qpid/log/Logger.h b/cpp/src/qpid/log/Logger.h deleted file mode 100644 index fa38678bba..0000000000 --- a/cpp/src/qpid/log/Logger.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef LOGGER_H -#define LOGGER_H - -/* - * 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 "Selector.h" -#include "qpid/sys/Mutex.h" -#include <boost/ptr_container/ptr_vector.hpp> -#include <boost/noncopyable.hpp> -#include <set> - -namespace qpid { -namespace log { - -class Options; - -/** - * Central logging agent. - * - * Thread safe, singleton. - */ -class Logger : private boost::noncopyable { - public: - /** Flags indicating what to include in the log output */ - enum FormatFlag { FILE=1, LINE=2, FUNCTION=4, LEVEL=8, TIME=16, THREAD=32}; - - /** Interface for log output destination. - * - * Implementations must be thread safe. - */ - class Output { - public: - Output(); - virtual ~Output(); - /** Receives the statemnt of origin and formatted message to log. */ - virtual void log(const Statement&, const std::string&) =0; - }; - - static Logger& instance(); - - Logger(); - ~Logger(); - - /** Select the messages to be logged. */ - void select(const Selector& s); - - /** Set the formatting flags, bitwise OR of FormatFlag values. */ - void format(int formatFlags); - - /** Set format flags from options object. - *@returns computed flags. - */ - int format(const Options&); - - /** Configure logger from Options */ - void configure(const Options& o); - - /** Add a statement. */ - void add(Statement& s); - - /** Log a message. */ - void log(const Statement&, const std::string&); - - /** Add an ostream to outputs. - * - * The ostream must not be destroyed while the Logger might - * still be using it. This is the case for std streams cout, - * cerr, clog. - */ - void output(std::ostream&); - - /** Add syslog to outputs. */ - void syslog(const Options&); - - /** Add an output. - *@param name a file name or one of the special tokens: - *stdout, stderr, syslog. - */ - void output(const std::string& name, const Options&); - - /** Add an output destination for messages */ - void output(std::auto_ptr<Output> out); - - /** Set a prefix for all messages */ - void setPrefix(const std::string& prefix); - - /** Reset the logger to it's original state. */ - void clear(); - - - private: - typedef boost::ptr_vector<Output> Outputs; - typedef std::set<Statement*> Statements; - - sys::Mutex lock; - inline void enable_unlocked(Statement* s); - - Statements statements; - Outputs outputs; - Selector selector; - int flags; - std::string prefix; -}; - -}} // namespace qpid::log - - -#endif /*!LOGGER_H*/ diff --git a/cpp/src/qpid/log/Options.cpp b/cpp/src/qpid/log/Options.cpp index e28f82648e..24ef413cbc 100644 --- a/cpp/src/qpid/log/Options.cpp +++ b/cpp/src/qpid/log/Options.cpp @@ -16,116 +16,39 @@ * */ -#include "Options.h" -#include "Statement.h" +#include "qpid/log/Options.h" +#include "qpid/log/SinkOptions.h" +#include "qpid/log/Statement.h" #include "qpid/Options.h" #include <map> #include <string> #include <algorithm> -#include <syslog.h> namespace qpid { namespace log { using namespace std; -namespace { - -class SyslogFacilities { - public: - typedef map<string, int> ByName; - typedef map<int, string> ByValue; - - SyslogFacilities() { - struct NameValue { const char* name; int value; }; - NameValue nameValue[] = { - { "AUTH", LOG_AUTH }, - { "AUTHPRIV", LOG_AUTHPRIV }, - { "CRON", LOG_CRON }, - { "DAEMON", LOG_DAEMON }, - { "FTP", LOG_FTP }, - { "KERN", LOG_KERN }, - { "LOCAL0", LOG_LOCAL0 }, - { "LOCAL1", LOG_LOCAL1 }, - { "LOCAL2", LOG_LOCAL2 }, - { "LOCAL3", LOG_LOCAL3 }, - { "LOCAL4", LOG_LOCAL4 }, - { "LOCAL5", LOG_LOCAL5 }, - { "LOCAL6", LOG_LOCAL6 }, - { "LOCAL7", LOG_LOCAL7 }, - { "LPR", LOG_LPR }, - { "MAIL", LOG_MAIL }, - { "NEWS", LOG_NEWS }, - { "SYSLOG", LOG_SYSLOG }, - { "USER", LOG_USER }, - { "UUCP", LOG_UUCP } - }; - for (size_t i = 0; i < sizeof(nameValue)/sizeof(nameValue[0]); ++i) { - byName.insert(ByName::value_type(nameValue[i].name, nameValue[i].value)); - // Recognise with and without LOG_ prefix e.g.: AUTH and LOG_AUTH - byName.insert(ByName::value_type(string("LOG_")+nameValue[i].name, nameValue[i].value)); - byValue.insert(ByValue::value_type(nameValue[i].value, string("LOG_")+nameValue[i].name)); - } - }; - - int value(const string& name) const { - string key(name); - transform(key.begin(), key.end(), key.begin(), ::toupper); - ByName::const_iterator i = byName.find(key); - if (i == byName.end()) - throw Exception("Not a valid syslog facility: " + name); - return i->second; - } - - string name(int value) const { - ByValue::const_iterator i = byValue.find(value); - if (i == byValue.end()) - throw Exception("Not a valid syslog value: " + value); - return i->second; - } - - private: - ByName byName; - ByValue byValue; -}; - -} - -ostream& operator<<(ostream& o, const SyslogFacility& f) { - return o << SyslogFacilities().name(f.value); -} - -istream& operator>>(istream& i, SyslogFacility& f) { - std::string name; - i >> name; - f.value = SyslogFacilities().value(name); - return i; -} - -namespace { -std::string basename(const std::string path) { - size_t i = path.find_last_of('/'); - return path.substr((i == std::string::npos) ? 0 : i+1); -} -} - -Options::Options(const std::string& argv0, const std::string& name) : - qpid::Options(name), - time(true), level(true), thread(false), source(false), function(false), trace(false), - syslogName(basename(argv0)), syslogFacility(LOG_DAEMON) +Options::Options(const std::string& argv0_, const std::string& name_) : + qpid::Options(name_), + argv0(argv0_), + name(name_), + time(true), + level(true), + thread(false), + source(false), + function(false), + trace(false), + sinkOptions (SinkOptions::create(argv0_)) { - outputs.push_back("stderr"); - selectors.push_back("error+"); + selectors.push_back("notice+"); ostringstream levels; levels << LevelTraits::name(Level(0)); for (int i = 1; i < LevelTraits::COUNT; ++i) levels << " " << LevelTraits::name(Level(i)); - + addOptions() - ("log-output", optValue(outputs, "FILE"), "Send log output to FILE. " - "FILE can be a file name or one of the special values:\n" - "stderr, stdout, syslog") ("trace,t", optValue(trace), "Enables all logging" ) ("log-enable", optValue(selectors, "RULE"), ("Enables logging for selected levels and components. " @@ -143,9 +66,42 @@ Options::Options(const std::string& argv0, const std::string& name) : ("log-thread", optValue(thread,"yes|no"), "Include thread ID in log messages") ("log-function", optValue(function,"yes|no"), "Include function signature in log messages") ("log-prefix", optValue(prefix,"STRING"), "Prefix to append to all log messages") - ("syslog-name", optValue(syslogName, "NAME"), "Name to use in syslog messages") - ("syslog-facility", optValue(syslogFacility,"LOG_XXX"), "Facility to use in syslog messages") ; -} + add(*sinkOptions); +} + +Options::Options(const Options &o) : + qpid::Options(o.name), + argv0(o.argv0), + name(o.name), + selectors(o.selectors), + time(o.time), + level(o.level), + thread(o.thread), + source(o.source), + function(o.function), + trace(o.trace), + prefix(o.prefix), + sinkOptions (SinkOptions::create(o.argv0)) +{ + *sinkOptions = *o.sinkOptions; +} + +Options& Options::operator=(const Options& x) { + if (this != &x) { + argv0 = x.argv0; + name = x.name; + selectors = x.selectors; + time = x.time; + level= x.level; + thread = x.thread; + source = x.source; + function = x.function; + trace = x.trace; + prefix = x.prefix; + *sinkOptions = *x.sinkOptions; + } + return *this; +} }} // namespace qpid::log diff --git a/cpp/src/qpid/log/Options.h b/cpp/src/qpid/log/Options.h deleted file mode 100644 index 13fca38f9d..0000000000 --- a/cpp/src/qpid/log/Options.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef OPTIONS_H -#define OPTIONS_H - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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/Options.h" -#include <iosfwd> - -namespace qpid { -namespace log { - -/** Provides << and >> operators to convert syslog facility values to/from strings. */ -struct SyslogFacility { - int value; - SyslogFacility(int i=0) : value(i) {} -}; - -std::ostream& operator<<(std::ostream&, const SyslogFacility&); -std::istream& operator>>(std::istream&, SyslogFacility&); - -/** Logging options for config parser. */ -struct Options : public qpid::Options { - /** Pass argv[0] for use in syslog output */ - Options(const std::string& argv0, - const std::string& name="Logging options"); - - std::vector<std::string> selectors; - std::vector<std::string> outputs; - bool time, level, thread, source, function; - bool trace; - std::string syslogName; - SyslogFacility syslogFacility; - std::string prefix; -}; - - -}} // namespace qpid::log - - - -#endif /*!OPTIONS_H*/ diff --git a/cpp/src/qpid/framing/amqp_types_full.h b/cpp/src/qpid/log/OstreamOutput.cpp index d0aaf28cb4..9b6ec1f8aa 100644 --- a/cpp/src/qpid/framing/amqp_types_full.h +++ b/cpp/src/qpid/log/OstreamOutput.cpp @@ -1,6 +1,3 @@ -#ifndef _framing_amqp_types_decl_h -#define _framing_amqp_types_decl_h - /* * * Copyright (c) 2006 The Apache Software Foundation @@ -19,20 +16,26 @@ * */ -/** \file - * Definitions and full declarations of all types used - * in AMQP messages. - * - * It's better to include amqp_types.h in another header instead of this file - * unless the header actually needs the full declarations. Including - * full declarations when forward declarations would increase compile - * times. - */ +#include "qpid/log/OstreamOutput.h" +#include <stdexcept> + +using namespace std; + +namespace qpid { +namespace log { + +OstreamOutput::OstreamOutput(std::ostream& o) : out(&o) {} + +OstreamOutput::OstreamOutput(const std::string& file) + : out(new ofstream(file.c_str(), ios_base::out | ios_base::app)), + mine(out) +{ + if (!out->good()) + throw std::runtime_error("Can't open log file: "+file); +} -#include "amqp_types.h" -#include "Array.h" -#include "FieldTable.h" -#include "SequenceSet.h" -#include "Uuid.h" +void OstreamOutput::log(const Statement&, const std::string& m) { + *out << m << flush; +} -#endif /*!_framing_amqp_types_decl_h*/ +}} // namespace qpid::log diff --git a/cpp/src/qpid/log/OstreamOutput.h b/cpp/src/qpid/log/OstreamOutput.h new file mode 100644 index 0000000000..12fd4ce425 --- /dev/null +++ b/cpp/src/qpid/log/OstreamOutput.h @@ -0,0 +1,41 @@ +#ifndef QPID_LOG_OSTREAMOUTPUT_H +#define QPID_LOG_OSTREAMOUTPUT_H + +/* + * 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/log/Logger.h" +#include <boost/scoped_ptr.hpp> +#include <fstream> +#include <ostream> + +namespace qpid { +namespace log { + +/** + * OstreamOutput is a reusable logging sink that directs logging to a C++ + * ostream. + */ +class OstreamOutput : public qpid::log::Logger::Output { +public: + QPID_COMMON_EXTERN OstreamOutput(std::ostream& o); + QPID_COMMON_EXTERN OstreamOutput(const std::string& file); + + virtual void log(const Statement&, const std::string& m); + +private: + std::ostream* out; + boost::scoped_ptr<std::ostream> mine; +}; + +}} // namespace qpid::log + +#endif /*!QPID_LOG_OSTREAMOUTPUT_H*/ diff --git a/cpp/src/qpid/log/Selector.cpp b/cpp/src/qpid/log/Selector.cpp index 994421d0ff..a4bc580470 100644 --- a/cpp/src/qpid/log/Selector.cpp +++ b/cpp/src/qpid/log/Selector.cpp @@ -16,10 +16,11 @@ * */ -#include "Selector.h" -#include "Options.h" +#include "qpid/log/Selector.h" +#include "qpid/log/Options.h" #include <boost/bind.hpp> #include <algorithm> +#include <string.h> namespace qpid { namespace log { @@ -52,12 +53,13 @@ Selector::Selector(const Options& opt){ boost::bind(&Selector::enable, this, _1)); } -bool Selector::isEnabled(Level level, const std::string& function) { +bool Selector::isEnabled(Level level, const char* function) { + const char* functionEnd = function+::strlen(function); for (std::vector<std::string>::iterator i=substrings[level].begin(); i != substrings[level].end(); ++i) { - if (function.find(*i) != std::string::npos) + if (std::search(function, functionEnd, i->begin(), i->end()) != functionEnd) return true; } return false; diff --git a/cpp/src/qpid/log/Selector.h b/cpp/src/qpid/log/Selector.h deleted file mode 100644 index 7c98bc6f8f..0000000000 --- a/cpp/src/qpid/log/Selector.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef SELECTOR_H -#define SELECTOR_H - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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 "Statement.h" -#include <vector> - -namespace qpid { -namespace log { -class Options; - -/** - * A selector identifies the set of log messages to enable. - * - * Thread object unsafe, pass-by-value type. - */ -class Selector { - public: - /** Empty selector selects nothing */ - Selector() {} - - /** Set selector from Options */ - Selector(const Options&); - - /** Equavlient to: Selector s; s.enable(l, s) */ - Selector(Level l, const std::string& s=std::string()) { - enable(l,s); - } - - Selector(const std::string& enableStr) { enable(enableStr); } - /** - * Enable messages with level in levels where the file - * name contains substring. Empty string matches all. - */ - void enable(Level level, const std::string& substring=std::string()) { - substrings[level].push_back(substring); - } - - /** Enable based on a 'level[+]:file' string */ - void enable(const std::string& enableStr); - - /** True if level is enabld for file. */ - bool isEnabled(Level level, const std::string& function); - - private: - std::vector<std::string> substrings[LevelTraits::COUNT]; -}; - - -}} // namespace qpid::log - - -#endif /*!SELECTOR_H*/ diff --git a/cpp/src/qpid/log/Statement.cpp b/cpp/src/qpid/log/Statement.cpp index c2b286f1e7..6a32b50096 100644 --- a/cpp/src/qpid/log/Statement.cpp +++ b/cpp/src/qpid/log/Statement.cpp @@ -16,12 +16,11 @@ * */ -#include "Statement.h" -#include "Logger.h" +#include "qpid/log/Statement.h" +#include "qpid/log/Logger.h" #include <boost/bind.hpp> #include <stdexcept> #include <algorithm> -#include <syslog.h> #include <ctype.h> namespace qpid { @@ -67,11 +66,6 @@ const char* names[LevelTraits::COUNT] = { "trace", "debug", "info", "notice", "warning", "error", "critical" }; -int priorities[LevelTraits::COUNT] = { - LOG_DEBUG, LOG_DEBUG, LOG_INFO, LOG_NOTICE, - LOG_WARNING, LOG_ERR, LOG_CRIT -}; - } // namespace Level LevelTraits::level(const char* name) { @@ -86,8 +80,4 @@ const char* LevelTraits::name(Level l) { return names[l]; } -int LevelTraits::priority(Level l) { - return priorities[l]; -} - }} // namespace qpid::log diff --git a/cpp/src/qpid/log/Statement.h b/cpp/src/qpid/log/Statement.h deleted file mode 100644 index f765df1cf4..0000000000 --- a/cpp/src/qpid/log/Statement.h +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef STATEMENT_H -#define STATEMENT_H - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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/Msg.h" - -#include <boost/current_function.hpp> - -namespace qpid { -namespace log { - -/** Debugging severity levels - * - trace: High-volume debugging messages. - * - debug: Debugging messages. - * - info: Informational messages. - * - notice: Normal but significant condition. - * - warning: Warn of a possible problem. - * - error: A definite error has occured. - * - critical: System in danger of severe failure. - */ -enum Level { trace, debug, info, notice, warning, error, critical }; -struct LevelTraits { - static const int COUNT=critical+1; - - /** Get level from string name. - *@exception if name invalid. - */ - static Level level(const char* name); - - /** Get level from string name. - *@exception if name invalid. - */ - static Level level(const std::string& name) { - return level(name.c_str()); - } - - /** String name of level */ - static const char* name(Level); - - /** Syslog priority of level */ - static int priority(Level); -}; - -/** POD struct representing a logging statement in source code. */ -struct Statement { - bool enabled; - const char* file; - int line; - const char* function; - Level level; - - void log(const std::string& message); - - struct Initializer { - Initializer(Statement& s); - Statement& statement; - }; -}; - -///@internal static initializer for a Statement. -#define QPID_LOG_STATEMENT_INIT(level) \ - { 0, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION, (::qpid::log::level) } - -/** - * Like QPID_LOG but computes an additional boolean test expression - * to determine if the message should be logged. Evaluation of both - * the test and message expressions occurs only if the requested log level - * is enabled. - *@param LEVEL severity Level for message, should be one of: - * debug, info, notice, warning, error, critical. NB no qpid::log:: prefix. - *@param TEST message is logged only if expression TEST evaluates to true. - *@param MESSAGE any object with an @eostream operator<<, or a sequence - * like of ostreamable objects separated by @e<<. - */ -#define QPID_LOG_IF(LEVEL, TEST, MESSAGE) \ - do { \ - using ::qpid::log::Statement; \ - static Statement stmt_= QPID_LOG_STATEMENT_INIT(LEVEL); \ - static Statement::Initializer init_(stmt_); \ - if (stmt_.enabled && (TEST)) \ - stmt_.log(::qpid::Msg() << MESSAGE); \ - } while(0) - -/** - * Macro for log statements. Example of use: - * @code - * QPID_LOG(debug, "There are " << foocount << " foos in the bar."); - * QPID_LOG(error, boost::format("Dohickey %s exploded") % dohicky.name()); - * @endcode - * - * All code with logging statements should be built with - * -DQPID_COMPONENT=<component name> - * where component name is the name of the component this file belongs to. - * - * You can subscribe to log messages by level, by component, by filename - * or a combination @see Configuration. - - *@param LEVEL severity Level for message, should be one of: - * debug, info, notice, warning, error, critical. NB no qpid::log:: prefix. - *@param MESSAGE any object with an @eostream operator<<, or a sequence - * like of ostreamable objects separated by @e<<. - */ -#define QPID_LOG(LEVEL, MESSAGE) QPID_LOG_IF(LEVEL, true, MESSAGE); - -}} // namespace qpid::log - - - - -#endif /*!STATEMENT_H*/ - diff --git a/cpp/src/qpid/log/posix/SinkOptions.cpp b/cpp/src/qpid/log/posix/SinkOptions.cpp new file mode 100644 index 0000000000..292e9147f6 --- /dev/null +++ b/cpp/src/qpid/log/posix/SinkOptions.cpp @@ -0,0 +1,215 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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/log/posix/SinkOptions.h" +#include "qpid/log/SinkOptions.h" +#include "qpid/log/Logger.h" +#include "qpid/log/OstreamOutput.h" +#include "qpid/memory.h" +#include "qpid/Exception.h" +#include <iostream> +#include <map> +#include <string> +#include <syslog.h> + +using std::string; +using qpid::Exception; + +namespace { + +// SyslogFacilities maps from syslog values to the text equivalents. +class SyslogFacilities { +public: + typedef std::map<string, int> ByName; + typedef std::map<int, string> ByValue; + + SyslogFacilities() { + struct NameValue { const char* name; int value; }; + NameValue nameValue[] = { + { "AUTH", LOG_AUTH }, +#ifdef HAVE_LOG_AUTHPRIV + { "AUTHPRIV", LOG_AUTHPRIV }, +#endif + { "CRON", LOG_CRON }, + { "DAEMON", LOG_DAEMON }, +#ifdef HAVE_LOG_FTP + { "FTP", LOG_FTP }, +#endif + { "KERN", LOG_KERN }, + { "LOCAL0", LOG_LOCAL0 }, + { "LOCAL1", LOG_LOCAL1 }, + { "LOCAL2", LOG_LOCAL2 }, + { "LOCAL3", LOG_LOCAL3 }, + { "LOCAL4", LOG_LOCAL4 }, + { "LOCAL5", LOG_LOCAL5 }, + { "LOCAL6", LOG_LOCAL6 }, + { "LOCAL7", LOG_LOCAL7 }, + { "LPR", LOG_LPR }, + { "MAIL", LOG_MAIL }, + { "NEWS", LOG_NEWS }, + { "SYSLOG", LOG_SYSLOG }, + { "USER", LOG_USER }, + { "UUCP", LOG_UUCP } + }; + for (size_t i = 0; i < sizeof(nameValue)/sizeof(nameValue[0]); ++i) { + byName.insert(ByName::value_type(nameValue[i].name, nameValue[i].value)); + // Recognise with and without LOG_ prefix e.g.: AUTH and LOG_AUTH + byName.insert(ByName::value_type(string("LOG_")+nameValue[i].name, nameValue[i].value)); + byValue.insert(ByValue::value_type(nameValue[i].value, string("LOG_")+nameValue[i].name)); + } + } + + int value(const string& name) const { + string key(name); + std::transform(key.begin(), key.end(), key.begin(), ::toupper); + ByName::const_iterator i = byName.find(key); + if (i == byName.end()) + throw Exception("Not a valid syslog facility: " + name); + return i->second; + } + + string name(int value) const { + ByValue::const_iterator i = byValue.find(value); + if (i == byValue.end()) + throw Exception("Not a valid syslog value: " + value); + return i->second; + } + + private: + ByName byName; + ByValue byValue; +}; + +// 'priorities' maps qpid log levels to syslog priorities. They are in +// order of qpid log levels and must map to: +// "trace", "debug", "info", "notice", "warning", "error", "critical" +static int priorities[qpid::log::LevelTraits::COUNT] = { + LOG_DEBUG, LOG_DEBUG, LOG_INFO, LOG_NOTICE, + LOG_WARNING, LOG_ERR, LOG_CRIT +}; + +std::string basename(const std::string path) { + size_t i = path.find_last_of('/'); + return path.substr((i == std::string::npos) ? 0 : i+1); +} + +} // namespace + +namespace qpid { +namespace log { +namespace posix { + +std::ostream& operator<<(std::ostream& o, const SyslogFacility& f) { + return o << SyslogFacilities().name(f.value); +} + +std::istream& operator>>(std::istream& i, SyslogFacility& f) { + std::string name; + i >> name; + f.value = SyslogFacilities().value(name); + return i; +} + +class SyslogOutput : public qpid::log::Logger::Output { +public: + SyslogOutput(const std::string& logName, const SyslogFacility& logFacility) + : name(logName), facility(logFacility.value) + { + ::openlog(name.c_str(), LOG_PID, facility); + } + + virtual ~SyslogOutput() { + ::closelog(); + } + + virtual void log(const Statement& s, const std::string& m) + { + syslog(priorities[s.level], "%s", m.c_str()); + } + +private: + std::string name; + int facility; +}; + +SinkOptions::SinkOptions(const std::string& argv0) + : qpid::log::SinkOptions(), + logToStderr(true), + logToStdout(false), + logToSyslog(false), + syslogName(basename(argv0)), + syslogFacility(LOG_DAEMON) { + + addOptions() + ("log-to-stderr", optValue(logToStderr, "yes|no"), "Send logging output to stderr") + ("log-to-stdout", optValue(logToStdout, "yes|no"), "Send logging output to stdout") + ("log-to-file", optValue(logFile, "FILE"), "Send log output to FILE.") + ("log-to-syslog", optValue(logToSyslog, "yes|no"), "Send logging output to syslog;\n\tcustomize using --syslog-name and --syslog-facility") + ("syslog-name", optValue(syslogName, "NAME"), "Name to use in syslog messages") + ("syslog-facility", optValue(syslogFacility,"LOG_XXX"), "Facility to use in syslog messages") + ; + +} + +qpid::log::SinkOptions& SinkOptions::operator=(const qpid::log::SinkOptions& rhs) { + const SinkOptions *prhs = dynamic_cast<const SinkOptions*>(&rhs); + if (this != prhs) { + logToStderr = prhs->logToStderr; + logToStdout = prhs->logToStdout; + logToSyslog = prhs->logToSyslog; + logFile = prhs->logFile; + syslogName = prhs->syslogName; + syslogFacility.value = prhs->syslogFacility.value; + } + return *this; +} + +void SinkOptions::detached(void) { + if (logToStderr && !logToStdout && !logToSyslog) { + logToStderr = false; + logToSyslog = true; + } +} + +// The Logger acting on these options calls setup() to request any +// Sinks be set up and fed back to the logger. +void SinkOptions::setup(qpid::log::Logger *logger) { + if (logToStderr) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(std::clog))); + if (logToStdout) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(std::cout))); + + if (logFile.length() > 0) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(logFile))); + + if (logToSyslog) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new SyslogOutput(syslogName, syslogFacility))); + +} + +} // namespace qpid::log::posix + +SinkOptions* SinkOptions::create(const std::string& argv0) { + return new qpid::log::posix::SinkOptions (argv0); +} + +}} // namespace qpid::log diff --git a/cpp/src/qpid/log/posix/SinkOptions.h b/cpp/src/qpid/log/posix/SinkOptions.h new file mode 100644 index 0000000000..d929c29025 --- /dev/null +++ b/cpp/src/qpid/log/posix/SinkOptions.h @@ -0,0 +1,64 @@ +#ifndef QPID_LOG_POSIX_SINKOPTIONS_H +#define QPID_LOG_POSIX_SINKOPTIONS_H + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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/log/SinkOptions.h" +#include <string> + +namespace qpid { +namespace log { +namespace posix { + +/** + * Provides a type that can be passed to << and >> operators to convert + * syslog facility values to/from strings. + */ +struct SyslogFacility { + int value; + SyslogFacility(int i=0) : value(i) {} +}; + +struct SinkOptions : public qpid::log::SinkOptions { + SinkOptions(const std::string& argv0); + virtual ~SinkOptions() {} + + virtual qpid::log::SinkOptions& operator=(const qpid::log::SinkOptions& rhs); + + // This allows the caller to indicate that there's no normal outputs + // available. For example, when running as a daemon. In these cases, the + // platform's "syslog"-type output should replace the default stderr + // unless some other sink has been selected. + virtual void detached(void); + + // The Logger acting on these options calls setup() to request any + // Sinks be set up and fed back to the logger. + virtual void setup(qpid::log::Logger *logger); + + bool logToStderr; + bool logToStdout; + bool logToSyslog; + std::string logFile; + std::string syslogName; + SyslogFacility syslogFacility; +}; + +}}} // namespace qpid::log::posix + +#endif /*!QPID_LOG_POSIX_SINKOPTIONS_H*/ diff --git a/cpp/src/qpid/log/windows/SinkOptions.cpp b/cpp/src/qpid/log/windows/SinkOptions.cpp new file mode 100644 index 0000000000..28f4b267e0 --- /dev/null +++ b/cpp/src/qpid/log/windows/SinkOptions.cpp @@ -0,0 +1,148 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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/log/windows/SinkOptions.h" +#include "qpid/log/SinkOptions.h" +#include "qpid/log/Logger.h" +#include "qpid/log/OstreamOutput.h" +#include "qpid/memory.h" +#include "qpid/Exception.h" +#include <iostream> +#include <map> +#include <string> + +#include <windows.h> + +using qpid::Exception; + +namespace qpid { +namespace log { +namespace windows { + +namespace { + +// 'eventTypes' maps qpid log levels to Windows event types. They are in +// order of qpid log levels and must map to: +// "trace", "debug", "info", "notice", "warning", "error", "critical" +static int eventTypes[qpid::log::LevelTraits::COUNT] = { + EVENTLOG_INFORMATION_TYPE, /* trace */ + EVENTLOG_INFORMATION_TYPE, /* debug */ + EVENTLOG_INFORMATION_TYPE, /* info */ + EVENTLOG_INFORMATION_TYPE, /* notice */ + EVENTLOG_WARNING_TYPE, /* warning */ + EVENTLOG_ERROR_TYPE, /* error */ + EVENTLOG_ERROR_TYPE /* critical */ +}; + +} // namespace + +class EventLogOutput : public qpid::log::Logger::Output { +public: + EventLogOutput(const std::string& sourceName) : logHandle(0) + { + logHandle = OpenEventLog(0, "Application"); + } + + virtual ~EventLogOutput() { + if (logHandle) + CloseEventLog(logHandle); + } + + virtual void log(const Statement& s, const std::string& m) + { + if (logHandle) { + const char *msg = m.c_str(); + ReportEvent(logHandle, + eventTypes[s.level], + 0, /* category unused */ + 0, /* event id */ + 0, /* user security id */ + 1, /* number of strings */ + 0, /* no event-specific data */ + &msg, + 0); + } + } + +private: + HANDLE logHandle; +}; + +SinkOptions::SinkOptions(const std::string& argv0) + : qpid::log::SinkOptions(), + logToStderr(true), + logToStdout(false), + logToEventLog(false), + eventSource("Application") +{ + addOptions() + ("log-to-stderr", optValue(logToStderr, "yes|no"), "Send logging output to stderr") + ("log-to-stdout", optValue(logToStdout, "yes|no"), "Send logging output to stdout") + ("log-to-file", optValue(logFile, "FILE"), "Send log output to FILE.") + ("log-to-eventlog", optValue(logToEventLog, "yes|no"), "Send logging output to event log;\n\tcustomize using --syslog-name and --syslog-facility") + ("eventlog-source-name", optValue(eventSource, "Application"), "Event source to log to") + ; + +} + +qpid::log::SinkOptions& SinkOptions::operator=(const qpid::log::SinkOptions& rhs) { + const SinkOptions *prhs = dynamic_cast<const SinkOptions*>(&rhs); + if (this != prhs) { + logToStderr = prhs->logToStderr; + logToStdout = prhs->logToStdout; + logToEventLog = prhs->logToEventLog; + eventSource = prhs->eventSource; + logFile = prhs->logFile; + } + return *this; +} + +void SinkOptions::detached(void) { + if (logToStderr && !logToStdout && !logToEventLog) { + logToStderr = false; + logToEventLog = true; + } +} + +// The Logger acting on these options calls setup() to request any +// Sinks be set up and fed back to the logger. +void SinkOptions::setup(qpid::log::Logger *logger) { + if (logToStderr) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(std::clog))); + if (logToStdout) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(std::cout))); + + if (logFile.length() > 0) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(logFile))); + + if (logToEventLog) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new EventLogOutput(eventSource))); + +} + +} // namespace windows + +SinkOptions* SinkOptions::create(const std::string& argv0) { + return new qpid::log::windows::SinkOptions (argv0); +} + +}} // namespace qpid::log diff --git a/cpp/src/qpid/log/windows/SinkOptions.h b/cpp/src/qpid/log/windows/SinkOptions.h new file mode 100644 index 0000000000..605822fd46 --- /dev/null +++ b/cpp/src/qpid/log/windows/SinkOptions.h @@ -0,0 +1,54 @@ +#ifndef QPID_LOG_WINDOWS_SINKOPTIONS_H +#define QPID_LOG_WINDOWS_SINKOPTIONS_H + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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/log/SinkOptions.h" +#include <string> + +namespace qpid { +namespace log { +namespace windows { + +struct SinkOptions : public qpid::log::SinkOptions { + QPID_COMMON_EXTERN SinkOptions(const std::string& argv0); + virtual ~SinkOptions() {} + + QPID_COMMON_EXTERN virtual qpid::log::SinkOptions& operator=(const qpid::log::SinkOptions& rhs); + + // This allows the caller to indicate that there's no normal outputs + // available. For example, when running as a service. In these cases, the + // platform's "syslog"-type output should replace the default stderr + // unless some other sink has been selected. + QPID_COMMON_EXTERN virtual void detached(void); + + // The Logger acting on these options calls setup() to request any + // Sinks be set up and fed back to the logger. + QPID_COMMON_EXTERN virtual void setup(qpid::log::Logger *logger); + + bool logToStderr; + bool logToStdout; + bool logToEventLog; + std::string eventSource; + std::string logFile; +}; + +}}} // namespace qpid::log::windows + +#endif /*!QPID_LOG_WINDOWS_SINKOPTIONS_H*/ diff --git a/cpp/src/qpid/management/Args.h b/cpp/src/qpid/management/Args.h deleted file mode 100644 index da1fb033b9..0000000000 --- a/cpp/src/qpid/management/Args.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef _Args_ -#define _Args_ - -// -// 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 management { - -class Args -{ - public: - - virtual ~Args (void) = 0; - -}; - -inline Args::~Args (void) {} - -class ArgsNone : public Args -{ -}; - -}} - - -#endif /*!_Args_*/ diff --git a/cpp/src/qpid/client/MessageQueue.h b/cpp/src/qpid/management/IdAllocator.h index ab6d351ba7..6f49d6d13f 100644 --- a/cpp/src/qpid/client/MessageQueue.h +++ b/cpp/src/qpid/management/IdAllocator.h @@ -1,3 +1,6 @@ +#ifndef QPID_MANAGEMENT_IDALLOCATOR_H +#define QPID_MANAGEMENT_IDALLOCATOR_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -19,32 +22,21 @@ * */ -#ifndef _MessageQueue_ -#define _MessageQueue_ -#include <iostream> -#include "qpid/sys/BlockingQueue.h" -#include "MessageListener.h" +#include "qpid/management/Manageable.h" namespace qpid { -namespace client { +namespace management { /** - * A MessageListener implementation that queues up - * messages. - * - * \ingroup clientapi + * Interface through which plugins etc can control the mgmt object id + * allocation for special cases */ -class MessageQueue : public sys::BlockingQueue<Message>, public MessageListener +struct IdAllocator { - public: - void received(Message& msg) - { - push(msg); - } + virtual uint64_t getIdFor(Manageable* object) = 0; + virtual ~IdAllocator() {} }; -} -} - +}} // namespace qpid::management -#endif +#endif /*!QPID_MANAGEMENT_IDALLOCATOR_H*/ diff --git a/cpp/src/qpid/management/Manageable.cpp b/cpp/src/qpid/management/Manageable.cpp index 0f3fbab55c..a3593e73e3 100644 --- a/cpp/src/qpid/management/Manageable.cpp +++ b/cpp/src/qpid/management/Manageable.cpp @@ -17,26 +17,31 @@ // under the License. // -#include "Manageable.h" +#include "qpid/management/Manageable.h" using namespace qpid::management; +using std::string; -std::string Manageable::StatusText (status_t status) +string Manageable::StatusText (status_t status, string text) { + if ((status & STATUS_USER) == STATUS_USER) + return text; + switch (status) { case STATUS_OK : return "OK"; case STATUS_UNKNOWN_OBJECT : return "UnknownObject"; case STATUS_UNKNOWN_METHOD : return "UnknownMethod"; case STATUS_NOT_IMPLEMENTED : return "NotImplemented"; - case STATUS_INVALID_PARAMETER : return "InvalidParameter"; + case STATUS_PARAMETER_INVALID : return "InvalidParameter"; case STATUS_FEATURE_NOT_IMPLEMENTED : return "FeatureNotImplemented"; + case STATUS_FORBIDDEN : return "Forbidden"; } return "??"; } -Manageable::status_t Manageable::ManagementMethod (uint32_t, Args&) +Manageable::status_t Manageable::ManagementMethod (uint32_t, Args&, std::string&) { return STATUS_UNKNOWN_METHOD; } diff --git a/cpp/src/qpid/management/Manageable.h b/cpp/src/qpid/management/Manageable.h deleted file mode 100644 index e2b8980465..0000000000 --- a/cpp/src/qpid/management/Manageable.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef _Manageable_ -#define _Manageable_ - -// -// 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 "ManagementObject.h" -#include "Args.h" -#include <string> - -namespace qpid { -namespace management { - -class Manageable -{ - public: - - virtual ~Manageable (void) = 0; - - // status_t is a type used to pass completion status from the method handler. - // - typedef uint32_t status_t; - static std::string StatusText (status_t status); - - static const status_t STATUS_OK = 0; - static const status_t STATUS_UNKNOWN_OBJECT = 1; - static const status_t STATUS_UNKNOWN_METHOD = 2; - static const status_t STATUS_NOT_IMPLEMENTED = 3; - static const status_t STATUS_INVALID_PARAMETER = 4; - static const status_t STATUS_FEATURE_NOT_IMPLEMENTED = 5; - - // Every "Manageable" object must hold a reference to exactly one - // management object. This object is always of a class derived from - // the pure-virtual "ManagementObject". - // - // This accessor function returns a pointer to the management object. - // - virtual ManagementObject* GetManagementObject (void) const = 0; - - // Every "Manageable" object must implement ManagementMethod. This - // function is called when a remote management client invokes a method - // on this object. The input and output arguments are specific to the - // method being called and must be down-cast to the appropriate sub class - // before use. - virtual status_t ManagementMethod (uint32_t methodId, Args& args); -}; - -inline Manageable::~Manageable (void) {} - -}} - -#endif /*!_Manageable_*/ diff --git a/cpp/src/qpid/management/ManagementAgent.cpp b/cpp/src/qpid/management/ManagementAgent.cpp new file mode 100644 index 0000000000..b5ed4ed405 --- /dev/null +++ b/cpp/src/qpid/management/ManagementAgent.cpp @@ -0,0 +1,1295 @@ +/* + * + * 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/ManagementAgent.h" +#include "qpid/management/ManagementObject.h" +#include "qpid/management/IdAllocator.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/log/Statement.h" +#include <qpid/broker/Message.h> +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/sys/Time.h" +#include "qpid/broker/ConnectionState.h" +#include "qpid/broker/AclModule.h" +#include <list> +#include <iostream> +#include <fstream> + +using boost::intrusive_ptr; +using qpid::framing::Uuid; +using namespace qpid::framing; +using namespace qpid::management; +using namespace qpid::broker; +using namespace qpid::sys; +using namespace std; +namespace _qmf = qmf::org::apache::qpid::broker; + +ManagementAgent::RemoteAgent::~RemoteAgent () +{ + QPID_LOG(trace, "Remote Agent removed bank=[" << brokerBank << "." << agentBank << "]"); + if (mgmtObject != 0) { + mgmtObject->resourceDestroy(); + agent.deleteObjectNowLH(mgmtObject->getObjectId()); + } +} + +ManagementAgent::ManagementAgent () : + threadPoolSize(1), interval(10), broker(0), startTime(uint64_t(Duration(now()))) +{ + nextObjectId = 1; + brokerBank = 1; + bootSequence = 1; + nextRemoteBank = 10; + nextRequestSequence = 1; + clientWasAdded = false; +} + +ManagementAgent::~ManagementAgent () +{ + { + Mutex::ScopedLock lock (userLock); + + // Reset the shared pointers to exchanges. If this is not done now, the exchanges + // will stick around until dExchange and mExchange are implicitely destroyed (long + // after this destructor completes). Those exchanges hold references to management + // objects that will be invalid. + dExchange.reset(); + mExchange.reset(); + + moveNewObjectsLH(); + for (ManagementObjectMap::iterator iter = managementObjects.begin (); + iter != managementObjects.end (); + iter++) { + ManagementObject* object = iter->second; + delete object; + } + managementObjects.clear(); + } +} + +void ManagementAgent::configure(const string& _dataDir, uint16_t _interval, + qpid::broker::Broker* _broker, int _threads) +{ + dataDir = _dataDir; + interval = _interval; + broker = _broker; + timer = &_broker->getTimer(); + threadPoolSize = _threads; + ManagementObject::maxThreads = threadPoolSize; + timer->add (new Periodic(*this, interval)); + + // Get from file or generate and save to file. + if (dataDir.empty()) + { + uuid.generate(); + QPID_LOG (info, "ManagementAgent has no data directory, generated new broker ID: " + << uuid); + } + else + { + string filename(dataDir + "/.mbrokerdata"); + ifstream inFile(filename.c_str ()); + + if (inFile.good()) + { + inFile >> uuid; + inFile >> bootSequence; + inFile >> nextRemoteBank; + inFile.close(); + QPID_LOG (debug, "ManagementAgent restored broker ID: " << uuid); + + // if sequence goes beyond a 12-bit field, skip zero and wrap to 1. + bootSequence++; + if (bootSequence & 0xF000) + bootSequence = 1; + writeData(); + } + else + { + uuid.generate(); + QPID_LOG (info, "ManagementAgent generated broker ID: " << uuid); + writeData(); + } + + QPID_LOG (debug, "ManagementAgent boot sequence: " << bootSequence); + } +} + +void ManagementAgent::writeData () +{ + string filename (dataDir + "/.mbrokerdata"); + ofstream outFile (filename.c_str ()); + + if (outFile.good()) + { + outFile << uuid << " " << bootSequence << " " << nextRemoteBank << endl; + outFile.close(); + } +} + +void ManagementAgent::setExchange (qpid::broker::Exchange::shared_ptr _mexchange, + qpid::broker::Exchange::shared_ptr _dexchange) +{ + mExchange = _mexchange; + dExchange = _dexchange; +} + +void ManagementAgent::registerClass (const string& packageName, + const string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall) +{ + Mutex::ScopedLock lock(userLock); + PackageMap::iterator pIter = findOrAddPackageLH(packageName); + addClassLH(ManagementItem::CLASS_KIND_TABLE, pIter, className, md5Sum, schemaCall); +} + +void ManagementAgent::registerEvent (const string& packageName, + const string& eventName, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall) +{ + Mutex::ScopedLock lock(userLock); + PackageMap::iterator pIter = findOrAddPackageLH(packageName); + addClassLH(ManagementItem::CLASS_KIND_EVENT, pIter, eventName, md5Sum, schemaCall); +} + +ObjectId ManagementAgent::addObject(ManagementObject* object, + uint64_t persistId, + bool publishNow) +{ + Mutex::ScopedLock lock (addLock); + uint16_t sequence; + uint64_t objectNum; + + if (persistId == 0) { + sequence = bootSequence; + objectNum = nextObjectId++; + } else { + sequence = 0; + objectNum = persistId; + } + + ObjectId objId(0 /*flags*/ , sequence, brokerBank, 0, objectNum); + + object->setObjectId(objId); + newManagementObjects[objId] = object; + + if (publishNow) { +#define IMM_BUFSIZE 65536 + char rawBuf[IMM_BUFSIZE]; + Buffer msgBuffer(rawBuf, IMM_BUFSIZE); + + encodeHeader(msgBuffer, 'c'); + object->writeProperties(msgBuffer); + uint32_t contentSize = msgBuffer.getPosition(); + stringstream key; + key << "console.obj.1.0." << object->getPackageName() << "." << object->getClassName(); + msgBuffer.reset(); + sendBuffer(msgBuffer, contentSize, mExchange, key.str()); + QPID_LOG(trace, "SEND Immediate ContentInd to=" << key.str()); + } + + return objId; +} + +void ManagementAgent::raiseEvent(const ManagementEvent& event, severity_t severity) +{ + Mutex::ScopedLock lock (userLock); + Buffer outBuffer(eventBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + uint8_t sev = (severity == SEV_DEFAULT) ? event.getSeverity() : (uint8_t) severity; + + encodeHeader(outBuffer, 'e'); + outBuffer.putShortString(event.getPackageName()); + outBuffer.putShortString(event.getEventName()); + outBuffer.putBin128(event.getMd5Sum()); + outBuffer.putLongLong(uint64_t(Duration(now()))); + outBuffer.putOctet(sev); + event.encode(outBuffer); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBuffer(outBuffer, outLen, mExchange, + "console.event.1.0." + event.getPackageName() + "." + event.getEventName()); +} + +ManagementAgent::Periodic::Periodic (ManagementAgent& _agent, uint32_t _seconds) + : TimerTask (qpid::sys::Duration ((_seconds ? _seconds : 1) * qpid::sys::TIME_SEC)), agent(_agent) {} + +ManagementAgent::Periodic::~Periodic () {} + +void ManagementAgent::Periodic::fire () +{ + agent.timer->add (new Periodic (agent, agent.interval)); + agent.periodicProcessing (); +} + +void ManagementAgent::clientAdded (const std::string& routingKey) +{ + if (routingKey.find("console") != 0) + return; + + clientWasAdded = true; + for (RemoteAgentMap::iterator aIter = remoteAgents.begin(); + aIter != remoteAgents.end(); + aIter++) { + char localBuffer[16]; + Buffer outBuffer(localBuffer, 16); + uint32_t outLen; + + encodeHeader(outBuffer, 'x'); + outLen = outBuffer.getPosition(); + outBuffer.reset(); + sendBuffer(outBuffer, outLen, dExchange, aIter->second->routingKey); + QPID_LOG(trace, "SEND ConsoleAddedIndication to=" << aIter->second->routingKey); + } +} + +void ManagementAgent::encodeHeader (Buffer& buf, uint8_t opcode, uint32_t seq) +{ + buf.putOctet ('A'); + buf.putOctet ('M'); + buf.putOctet ('2'); + buf.putOctet (opcode); + buf.putLong (seq); +} + +bool ManagementAgent::checkHeader (Buffer& buf, uint8_t *opcode, uint32_t *seq) +{ + uint8_t h1 = buf.getOctet(); + uint8_t h2 = buf.getOctet(); + uint8_t h3 = buf.getOctet(); + + *opcode = buf.getOctet(); + *seq = buf.getLong(); + + return h1 == 'A' && h2 == 'M' && h3 == '2'; +} + +void ManagementAgent::sendBuffer(Buffer& buf, + uint32_t length, + qpid::broker::Exchange::shared_ptr exchange, + string routingKey) +{ + if (exchange.get() == 0) + return; + + intrusive_ptr<Message> msg(new Message()); + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange->getName (), 0, 0))); + AMQFrame header((AMQHeaderBody())); + AMQFrame content((AMQContentBody())); + + content.castBody<AMQContentBody>()->decode(buf, length); + + method.setEof(false); + header.setBof(false); + header.setEof(false); + content.setBof(false); + + msg->getFrames().append(method); + msg->getFrames().append(header); + + MessageProperties* props = + msg->getFrames().getHeaders()->get<MessageProperties>(true); + props->setContentLength(length); + msg->getFrames().append(content); + + DeliverableMessage deliverable (msg); + try { + exchange->route(deliverable, routingKey, 0); + } catch(exception&) {} +} + +void ManagementAgent::moveNewObjectsLH() +{ + Mutex::ScopedLock lock (addLock); + for (ManagementObjectMap::iterator iter = newManagementObjects.begin (); + iter != newManagementObjects.end (); + iter++) + managementObjects[iter->first] = iter->second; + newManagementObjects.clear(); +} + +void ManagementAgent::periodicProcessing (void) +{ +#define BUFSIZE 65536 +#define HEADROOM 4096 + Mutex::ScopedLock lock (userLock); + char msgChars[BUFSIZE]; + uint32_t contentSize; + string routingKey; + list<pair<ObjectId, ManagementObject*> > deleteList; + + uint64_t uptime = uint64_t(Duration(now())) - startTime; + static_cast<_qmf::Broker*>(broker->GetManagementObject())->set_uptime(uptime); + + moveNewObjectsLH(); + + // + // Clear the been-here flag on all objects in the map. + // + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second; + object->setFlags(0); + if (clientWasAdded) { + object->setForcePublish(true); + } + } + + clientWasAdded = false; + + // + // Process the entire object map. + // + for (ManagementObjectMap::iterator baseIter = managementObjects.begin(); + baseIter != managementObjects.end(); + baseIter++) { + ManagementObject* baseObject = baseIter->second; + uint32_t pcount = 0; + uint32_t scount = 0; + + // + // Skip until we find a base object requiring a sent message. + // + if (baseObject->getFlags() == 1 || + (!baseObject->getConfigChanged() && + !baseObject->getInstChanged() && + !baseObject->getForcePublish() && + !baseObject->isDeleted())) + continue; + + Buffer msgBuffer(msgChars, BUFSIZE); + for (ManagementObjectMap::iterator iter = baseIter; + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second; + if (baseObject->isSameClass(*object) && object->getFlags() == 0) { + object->setFlags(1); + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + if (object->getConfigChanged() || object->getForcePublish() || object->isDeleted()) { + encodeHeader(msgBuffer, 'c'); + object->writeProperties(msgBuffer); + pcount++; + } + + if (object->hasInst() && (object->getInstChanged() || object->getForcePublish())) { + encodeHeader(msgBuffer, 'i'); + object->writeStatistics(msgBuffer); + scount++; + } + + if (object->isDeleted()) + deleteList.push_back(pair<ObjectId, ManagementObject*>(iter->first, object)); + object->setForcePublish(false); + + if (msgBuffer.available() < HEADROOM) + break; + } + } + + contentSize = BUFSIZE - msgBuffer.available(); + if (contentSize > 0) { + msgBuffer.reset(); + stringstream key; + key << "console.obj.1.0." << baseObject->getPackageName() << "." << baseObject->getClassName(); + sendBuffer(msgBuffer, contentSize, mExchange, key.str()); + QPID_LOG(trace, "SEND Multicast ContentInd to=" << key.str() << " props=" << pcount << " stats=" << scount); + } + } + + // Delete flagged objects + for (list<pair<ObjectId, ManagementObject*> >::reverse_iterator iter = deleteList.rbegin(); + iter != deleteList.rend(); + iter++) { + delete iter->second; + managementObjects.erase(iter->first); + } + + if (!deleteList.empty()) { + deleteList.clear(); + deleteOrphanedAgentsLH(); + } + + { + Buffer msgBuffer(msgChars, BUFSIZE); + encodeHeader(msgBuffer, 'h'); + msgBuffer.putLongLong(uint64_t(Duration(now()))); + + contentSize = BUFSIZE - msgBuffer.available (); + msgBuffer.reset (); + routingKey = "console.heartbeat.1.0"; + sendBuffer (msgBuffer, contentSize, mExchange, routingKey); + QPID_LOG(trace, "SEND HeartbeatInd to=" << routingKey); + } +} + +void ManagementAgent::deleteObjectNowLH(const ObjectId& oid) +{ + ManagementObjectMap::iterator iter = managementObjects.find(oid); + if (iter == managementObjects.end()) + return; + ManagementObject* object = iter->second; + if (!object->isDeleted()) + return; + +#define DNOW_BUFSIZE 2048 + char msgChars[DNOW_BUFSIZE]; + uint32_t contentSize; + Buffer msgBuffer(msgChars, DNOW_BUFSIZE); + + encodeHeader(msgBuffer, 'c'); + object->writeProperties(msgBuffer); + contentSize = msgBuffer.getPosition(); + msgBuffer.reset(); + stringstream key; + key << "console.obj.1.0." << object->getPackageName() << "." << object->getClassName(); + sendBuffer(msgBuffer, contentSize, mExchange, key.str()); + QPID_LOG(trace, "SEND Immediate(delete) ContentInd to=" << key.str()); + + managementObjects.erase(oid); +} + +void ManagementAgent::sendCommandComplete (string replyToKey, uint32_t sequence, + uint32_t code, string text) +{ + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader (outBuffer, 'z', sequence); + outBuffer.putLong (code); + outBuffer.putShortString (text); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBuffer (outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND CommandCompleteInd code=" << code << " text=" << text << " to=" << + replyToKey << " seq=" << sequence); +} + +bool ManagementAgent::dispatchCommand (Deliverable& deliverable, + const string& routingKey, + const FieldTable* /*args*/) +{ + Mutex::ScopedLock lock (userLock); + Message& msg = ((DeliverableMessage&) deliverable).getMessage (); + + // Parse the routing key. This management broker should act as though it + // is bound to the exchange to match the following keys: + // + // agent.1.0.# + // broker + // schema.# + + if (routingKey == "broker") { + dispatchAgentCommandLH(msg); + return false; + } + + else if (routingKey.compare(0, 9, "agent.1.0") == 0) { + dispatchAgentCommandLH(msg); + return false; + } + + else if (routingKey.compare(0, 8, "agent.1.") == 0) { + return authorizeAgentMessageLH(msg); + } + + else if (routingKey.compare(0, 7, "schema.") == 0) { + dispatchAgentCommandLH(msg); + return true; + } + + return true; +} + +void ManagementAgent::handleMethodRequestLH (Buffer& inBuffer, string replyToKey, + uint32_t sequence, const ConnectionToken* connToken) +{ + string methodName; + string packageName; + string className; + uint8_t hash[16]; + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + AclModule* acl = broker->getAcl(); + + ObjectId objId(inBuffer); + inBuffer.getShortString(packageName); + inBuffer.getShortString(className); + inBuffer.getBin128(hash); + inBuffer.getShortString(methodName); + + QPID_LOG(trace, "RECV MethodRequest class=" << packageName << ":" << className << "(" << Uuid(hash) << ") method=" << + methodName << " replyTo=" << replyToKey); + + encodeHeader(outBuffer, 'm', sequence); + + DisallowedMethods::const_iterator i = disallowed.find(std::make_pair(className, methodName)); + if (i != disallowed.end()) { + outBuffer.putLong(Manageable::STATUS_FORBIDDEN); + outBuffer.putMediumString(i->second); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBuffer(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND MethodResponse status=FORBIDDEN text=" << i->second << " seq=" << sequence) + return; + } + + if (acl != 0) { + string userId = ((const qpid::broker::ConnectionState*) connToken)->getUserId(); + map<acl::Property, string> params; + params[acl::PROP_SCHEMAPACKAGE] = packageName; + params[acl::PROP_SCHEMACLASS] = className; + + 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(); + sendBuffer(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND MethodResponse status=FORBIDDEN" << " seq=" << sequence) + return; + } + } + + ManagementObjectMap::iterator iter = managementObjects.find(objId); + if (iter == managementObjects.end() || iter->second->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)) { + outBuffer.putLong (Manageable::STATUS_PARAMETER_INVALID); + outBuffer.putMediumString(Manageable::StatusText (Manageable::STATUS_PARAMETER_INVALID)); + } + else + try { + outBuffer.record(); + Mutex::ScopedUnlock u(userLock); + iter->second->doMethod(methodName, inBuffer, outBuffer); + } catch(exception& e) { + outBuffer.restore(); + outBuffer.putLong(Manageable::STATUS_EXCEPTION); + outBuffer.putMediumString(e.what()); + } + } + + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBuffer(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND MethodResponse to=" << replyToKey << " seq=" << sequence); +} + +void ManagementAgent::handleBrokerRequestLH (Buffer&, string replyToKey, uint32_t sequence) +{ + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + QPID_LOG(trace, "RECV BrokerRequest replyTo=" << replyToKey); + + encodeHeader (outBuffer, 'b', sequence); + uuid.encode (outBuffer); + + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBuffer (outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND BrokerResponse to=" << replyToKey); +} + +void ManagementAgent::handlePackageQueryLH (Buffer&, string replyToKey, uint32_t sequence) +{ + QPID_LOG(trace, "RECV PackageQuery replyTo=" << replyToKey); + + for (PackageMap::iterator pIter = packages.begin (); + pIter != packages.end (); + pIter++) + { + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader (outBuffer, 'p', sequence); + encodePackageIndication (outBuffer, pIter); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBuffer (outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND PackageInd package=" << (*pIter).first << " to=" << replyToKey << " seq=" << sequence); + } + + sendCommandComplete (replyToKey, sequence); +} + +void ManagementAgent::handlePackageIndLH (Buffer& inBuffer, string replyToKey, uint32_t sequence) +{ + string packageName; + + inBuffer.getShortString(packageName); + + QPID_LOG(trace, "RECV PackageInd package=" << packageName << " replyTo=" << replyToKey << " seq=" << sequence); + + findOrAddPackageLH(packageName); +} + +void ManagementAgent::handleClassQueryLH(Buffer& inBuffer, string replyToKey, uint32_t sequence) +{ + string packageName; + + inBuffer.getShortString(packageName); + + QPID_LOG(trace, "RECV ClassQuery package=" << packageName << " replyTo=" << replyToKey << " seq=" << sequence); + + 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()) + { + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader(outBuffer, 'q', sequence); + encodeClassIndication(outBuffer, pIter, cIter); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBuffer(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND ClassInd class=" << (*pIter).first << ":" << (*cIter).first.name << + "(" << Uuid((*cIter).first.hash) << ") to=" << replyToKey << " seq=" << sequence); + } + } + } + sendCommandComplete(replyToKey, sequence); +} + +void ManagementAgent::handleClassIndLH (Buffer& inBuffer, string replyToKey, uint32_t) +{ + string packageName; + SchemaClassKey key; + + uint8_t kind = inBuffer.getOctet(); + inBuffer.getShortString(packageName); + inBuffer.getShortString(key.name); + inBuffer.getBin128(key.hash); + + QPID_LOG(trace, "RECV ClassInd class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << + "), replyTo=" << replyToKey); + + 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; + uint32_t sequence = nextRequestSequence++; + + encodeHeader (outBuffer, 'S', sequence); + outBuffer.putShortString(packageName); + outBuffer.putShortString(key.name); + outBuffer.putBin128(key.hash); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBuffer (outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND SchemaRequest class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << + "), to=" << replyToKey << " seq=" << sequence); + + if (cIter != pIter->second.end()) + pIter->second.erase(key); + + pIter->second.insert(pair<SchemaClassKey, SchemaClass>(key, SchemaClass(kind, sequence))); + } +} + +void ManagementAgent::SchemaClass::appendSchema(Buffer& buf) +{ + // If the management package is attached locally (embedded in the broker or + // linked in via plug-in), call the schema handler directly. If the package + // is from a remote management agent, send the stored schema information. + + if (writeSchemaCall != 0) + writeSchemaCall(buf); + else + buf.putRawData(buffer, bufferLen); +} + +void ManagementAgent::handleSchemaRequestLH(Buffer& inBuffer, string replyToKey, uint32_t sequence) +{ + string packageName; + SchemaClassKey key; + + inBuffer.getShortString (packageName); + inBuffer.getShortString (key.name); + inBuffer.getBin128 (key.hash); + + QPID_LOG(trace, "RECV SchemaRequest class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << + "), replyTo=" << replyToKey << " seq=" << sequence); + + 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; + SchemaClass& classInfo = cIter->second; + + if (classInfo.hasSchema()) { + encodeHeader(outBuffer, 's', sequence); + classInfo.appendSchema(outBuffer); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBuffer(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND SchemaResponse to=" << replyToKey << " seq=" << sequence); + } + else + sendCommandComplete(replyToKey, sequence, 1, "Schema not available"); + } + else + sendCommandComplete(replyToKey, sequence, 1, "Class key not found"); + } + else + sendCommandComplete(replyToKey, sequence, 1, "Package not found"); +} + +void ManagementAgent::handleSchemaResponseLH(Buffer& inBuffer, string /*replyToKey*/, uint32_t sequence) +{ + string packageName; + SchemaClassKey key; + + inBuffer.record(); + inBuffer.getOctet(); + inBuffer.getShortString(packageName); + inBuffer.getShortString(key.name); + inBuffer.getBin128(key.hash); + inBuffer.restore(); + + QPID_LOG(trace, "RECV SchemaResponse class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << ")" << " seq=" << sequence); + + PackageMap::iterator pIter = packages.find(packageName); + if (pIter != packages.end()) { + ClassMap& cMap = pIter->second; + ClassMap::iterator cIter = cMap.find(key); + if (cIter != cMap.end() && cIter->second.pendingSequence == sequence) { + size_t length = validateSchema(inBuffer, cIter->second.kind); + if (length == 0) { + QPID_LOG(warning, "Management Agent received invalid schema response: " << packageName << "." << key.name); + cMap.erase(key); + } else { + cIter->second.buffer = (uint8_t*) malloc(length); + cIter->second.bufferLen = length; + inBuffer.getRawData(cIter->second.buffer, cIter->second.bufferLen); + + // Publish a class-indication message + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader(outBuffer, 'q'); + encodeClassIndication(outBuffer, pIter, cIter); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBuffer(outBuffer, outLen, mExchange, "schema.class"); + QPID_LOG(trace, "SEND ClassInd class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << ")" << + " to=schema.class"); + } + } + } +} + +bool ManagementAgent::bankInUse (uint32_t bank) +{ + for (RemoteAgentMap::iterator aIter = remoteAgents.begin(); + aIter != remoteAgents.end(); + aIter++) + if (aIter->second->agentBank == bank) + return true; + return false; +} + +uint32_t ManagementAgent::allocateNewBank () +{ + while (bankInUse (nextRemoteBank)) + nextRemoteBank++; + + uint32_t allocated = nextRemoteBank++; + writeData (); + return allocated; +} + +uint32_t ManagementAgent::assignBankLH (uint32_t requestedBank) +{ + if (requestedBank == 0 || bankInUse (requestedBank)) + return allocateNewBank (); + return requestedBank; +} + +void ManagementAgent::deleteOrphanedAgentsLH() +{ + vector<ObjectId> deleteList; + + for (RemoteAgentMap::iterator aIter = remoteAgents.begin(); aIter != remoteAgents.end(); aIter++) { + ObjectId connectionRef = aIter->first; + bool found = false; + + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + if (iter->first == connectionRef && !iter->second->isDeleted()) { + found = true; + break; + } + } + + if (!found) { + deleteList.push_back(connectionRef); + delete aIter->second; + } + } + + for (vector<ObjectId>::iterator dIter = deleteList.begin(); dIter != deleteList.end(); dIter++) + remoteAgents.erase(*dIter); + + deleteList.clear(); +} + +void ManagementAgent::handleAttachRequestLH (Buffer& inBuffer, string replyToKey, uint32_t sequence, const ConnectionToken* connToken) +{ + string label; + uint32_t requestedBrokerBank, requestedAgentBank; + uint32_t assignedBank; + ObjectId connectionRef = ((const ConnectionState*) connToken)->GetManagementObject()->getObjectId(); + Uuid systemId; + + moveNewObjectsLH(); + deleteOrphanedAgentsLH(); + RemoteAgentMap::iterator aIter = remoteAgents.find(connectionRef); + if (aIter != remoteAgents.end()) { + // There already exists an agent on this session. Reject the request. + sendCommandComplete(replyToKey, sequence, 1, "Connection already has remote agent"); + return; + } + + inBuffer.getShortString(label); + systemId.decode(inBuffer); + requestedBrokerBank = inBuffer.getLong(); + requestedAgentBank = inBuffer.getLong(); + + QPID_LOG(trace, "RECV (Agent)AttachRequest label=" << label << " reqBrokerBank=" << requestedBrokerBank << + " reqAgentBank=" << requestedAgentBank << " replyTo=" << replyToKey << " seq=" << sequence); + + assignedBank = assignBankLH(requestedAgentBank); + + RemoteAgent* agent = new RemoteAgent(*this); + agent->brokerBank = brokerBank; + agent->agentBank = assignedBank; + agent->routingKey = replyToKey; + agent->connectionRef = connectionRef; + agent->mgmtObject = new _qmf::Agent (this, agent); + agent->mgmtObject->set_connectionRef(agent->connectionRef); + agent->mgmtObject->set_label (label); + agent->mgmtObject->set_registeredTo (broker->GetManagementObject()->getObjectId()); + agent->mgmtObject->set_systemId (systemId); + agent->mgmtObject->set_brokerBank (brokerBank); + agent->mgmtObject->set_agentBank (assignedBank); + addObject (agent->mgmtObject, 0, true); + + remoteAgents[connectionRef] = agent; + + QPID_LOG(trace, "Remote Agent registered bank=[" << brokerBank << "." << assignedBank << "] replyTo=" << replyToKey); + + // Send an Attach Response + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader (outBuffer, 'a', sequence); + outBuffer.putLong (brokerBank); + outBuffer.putLong (assignedBank); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBuffer (outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND AttachResponse brokerBank=" << brokerBank << " agentBank=" << assignedBank << + " to=" << replyToKey << " seq=" << sequence); +} + +void ManagementAgent::handleGetQueryLH (Buffer& inBuffer, string replyToKey, uint32_t sequence) +{ + FieldTable ft; + FieldTable::ValuePtr value; + + moveNewObjectsLH(); + + ft.decode(inBuffer); + + QPID_LOG(trace, "RECV GetQuery query=" << ft << " seq=" << sequence); + + value = ft.get("_class"); + if (value.get() == 0 || !value->convertsTo<string>()) { + value = ft.get("_objectid"); + if (value.get() == 0 || !value->convertsTo<string>()) + return; + + ObjectId selector(value->get<string>()); + ManagementObjectMap::iterator iter = managementObjects.find(selector); + if (iter != managementObjects.end()) { + ManagementObject* object = iter->second; + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + if (!object->isDeleted()) { + encodeHeader(outBuffer, 'g', sequence); + object->writeProperties(outBuffer); + object->writeStatistics(outBuffer, true); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBuffer(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND GetResponse to=" << replyToKey << " seq=" << sequence); + } + } + sendCommandComplete(replyToKey, sequence); + return; + } + + string className (value->get<string>()); + + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second; + if (object->getClassName () == className) { + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + if (!object->isDeleted()) { + encodeHeader(outBuffer, 'g', sequence); + object->writeProperties(outBuffer); + object->writeStatistics(outBuffer, true); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBuffer(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND GetResponse to=" << replyToKey << " seq=" << sequence); + } + } + } + + sendCommandComplete(replyToKey, sequence); +} + +bool ManagementAgent::authorizeAgentMessageLH(Message& msg) +{ + Buffer inBuffer (inputBuffer, MA_BUFFER_SIZE); + uint8_t opcode; + uint32_t sequence; + string replyToKey; + + if (msg.encodedSize() > MA_BUFFER_SIZE) + return false; + + msg.encodeContent(inBuffer); + inBuffer.reset(); + + if (!checkHeader(inBuffer, &opcode, &sequence)) + return false; + + if (opcode == 'M') { + // TODO: check method call against ACL list. + AclModule* acl = broker->getAcl(); + if (acl == 0) + return true; + + string userId = ((const qpid::broker::ConnectionState*) msg.getPublisher())->getUserId(); + string packageName; + string className; + uint8_t hash[16]; + string methodName; + + map<acl::Property, string> params; + ObjectId objId(inBuffer); + inBuffer.getShortString(packageName); + inBuffer.getShortString(className); + inBuffer.getBin128(hash); + inBuffer.getShortString(methodName); + + params[acl::PROP_SCHEMAPACKAGE] = packageName; + params[acl::PROP_SCHEMACLASS] = className; + + if (acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, ¶ms)) + return true; + + const framing::MessageProperties* p = + msg.getFrames().getHeaders()->get<framing::MessageProperties>(); + if (p && p->hasReplyTo()) { + const framing::ReplyTo& rt = p->getReplyTo(); + replyToKey = rt.getRoutingKey(); + + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader(outBuffer, 'm', sequence); + outBuffer.putLong(Manageable::STATUS_FORBIDDEN); + outBuffer.putMediumString(Manageable::StatusText(Manageable::STATUS_FORBIDDEN)); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBuffer(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(trace, "SEND MethodResponse status=FORBIDDEN" << " seq=" << sequence) + } + + return false; + } + + return true; +} + +void ManagementAgent::dispatchAgentCommandLH(Message& msg) +{ + Buffer inBuffer(inputBuffer, MA_BUFFER_SIZE); + uint8_t opcode; + uint32_t sequence; + string replyToKey; + + const framing::MessageProperties* p = + msg.getFrames().getHeaders()->get<framing::MessageProperties>(); + if (p && p->hasReplyTo()) { + const framing::ReplyTo& rt = p->getReplyTo(); + replyToKey = rt.getRoutingKey(); + } + else + return; + + if (msg.encodedSize() > MA_BUFFER_SIZE) { + QPID_LOG(debug, "ManagementAgent::dispatchAgentCommandLH: Message too large: " << + msg.encodedSize()); + return; + } + + msg.encodeContent(inBuffer); + uint32_t bufferLen = inBuffer.getPosition(); + inBuffer.reset(); + + while (inBuffer.getPosition() < bufferLen) { + if (!checkHeader(inBuffer, &opcode, &sequence)) + return; + + if (opcode == 'B') handleBrokerRequestLH (inBuffer, replyToKey, sequence); + else if (opcode == 'P') handlePackageQueryLH (inBuffer, replyToKey, sequence); + else if (opcode == 'p') handlePackageIndLH (inBuffer, replyToKey, sequence); + else if (opcode == 'Q') handleClassQueryLH (inBuffer, replyToKey, sequence); + else if (opcode == 'q') handleClassIndLH (inBuffer, replyToKey, sequence); + else if (opcode == 'S') handleSchemaRequestLH (inBuffer, replyToKey, sequence); + else if (opcode == 's') handleSchemaResponseLH (inBuffer, replyToKey, sequence); + else if (opcode == 'A') handleAttachRequestLH (inBuffer, replyToKey, sequence, msg.getPublisher()); + else if (opcode == 'G') handleGetQueryLH (inBuffer, replyToKey, sequence); + else if (opcode == 'M') handleMethodRequestLH (inBuffer, replyToKey, sequence, msg.getPublisher()); + } +} + +ManagementAgent::PackageMap::iterator ManagementAgent::findOrAddPackageLH(string name) +{ + PackageMap::iterator pIter = packages.find (name); + if (pIter != packages.end ()) + return pIter; + + // No such package found, create a new map entry. + pair<PackageMap::iterator, bool> result = + packages.insert(pair<string, ClassMap>(name, ClassMap())); + QPID_LOG (debug, "ManagementAgent added package " << name); + + // Publish a package-indication message + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader (outBuffer, 'p'); + encodePackageIndication (outBuffer, result.first); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBuffer (outBuffer, outLen, mExchange, "schema.package"); + QPID_LOG(trace, "SEND PackageInd package=" << name << " to=schema.package") + + return result.first; +} + +void ManagementAgent::addClassLH(uint8_t kind, + PackageMap::iterator pIter, + const string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall) +{ + SchemaClassKey key; + ClassMap& cMap = pIter->second; + + key.name = className; + memcpy(&key.hash, md5Sum, 16); + + ClassMap::iterator cIter = cMap.find(key); + if (cIter != cMap.end()) + return; + + // No such class found, create a new class with local information. + QPID_LOG (debug, "ManagementAgent added class " << pIter->first << ":" << + key.name); + + cMap.insert(pair<SchemaClassKey, SchemaClass>(key, SchemaClass(kind, schemaCall))); + cIter = cMap.find(key); +} + +void ManagementAgent::encodePackageIndication(Buffer& buf, + PackageMap::iterator pIter) +{ + buf.putShortString((*pIter).first); +} + +void ManagementAgent::encodeClassIndication(Buffer& buf, + PackageMap::iterator pIter, + ClassMap::iterator cIter) +{ + SchemaClassKey key = (*cIter).first; + + buf.putOctet((*cIter).second.kind); + buf.putShortString((*pIter).first); + buf.putShortString(key.name); + buf.putBin128(key.hash); +} + +size_t ManagementAgent::validateSchema(Buffer& inBuffer, uint8_t kind) +{ + if (kind == ManagementItem::CLASS_KIND_TABLE) + return validateTableSchema(inBuffer); + else if (kind == ManagementItem::CLASS_KIND_EVENT) + return validateEventSchema(inBuffer); + return 0; +} + +size_t ManagementAgent::validateTableSchema(Buffer& inBuffer) +{ + uint32_t start = inBuffer.getPosition(); + uint32_t end; + string text; + uint8_t hash[16]; + + try { + inBuffer.record(); + uint8_t kind = inBuffer.getOctet(); + if (kind != ManagementItem::CLASS_KIND_TABLE) + return 0; + + inBuffer.getShortString(text); + inBuffer.getShortString(text); + inBuffer.getBin128(hash); + + uint8_t superType = 0; //inBuffer.getOctet(); + + uint16_t propCount = inBuffer.getShort(); + uint16_t statCount = inBuffer.getShort(); + uint16_t methCount = inBuffer.getShort(); + + if (superType == 1) { + inBuffer.getShortString(text); + inBuffer.getShortString(text); + inBuffer.getBin128(hash); + } + + for (uint16_t idx = 0; idx < propCount + statCount; idx++) { + FieldTable ft; + ft.decode(inBuffer); + } + + for (uint16_t idx = 0; idx < methCount; idx++) { + FieldTable ft; + ft.decode(inBuffer); + if (!ft.isSet("argCount")) + return 0; + int argCount = ft.getAsInt("argCount"); + for (int mIdx = 0; mIdx < argCount; mIdx++) { + FieldTable aft; + aft.decode(inBuffer); + } + } + } catch (exception& /*e*/) { + return 0; + } + + end = inBuffer.getPosition(); + inBuffer.restore(); // restore original position + return end - start; +} + +size_t ManagementAgent::validateEventSchema(Buffer& inBuffer) +{ + uint32_t start = inBuffer.getPosition(); + uint32_t end; + string text; + uint8_t hash[16]; + + try { + inBuffer.record(); + uint8_t kind = inBuffer.getOctet(); + if (kind != ManagementItem::CLASS_KIND_EVENT) + return 0; + + inBuffer.getShortString(text); + inBuffer.getShortString(text); + inBuffer.getBin128(hash); + + uint8_t superType = inBuffer.getOctet(); + + uint16_t argCount = inBuffer.getShort(); + + if (superType == 1) { + inBuffer.getShortString(text); + inBuffer.getShortString(text); + inBuffer.getBin128(hash); + } + for (uint16_t idx = 0; idx < argCount; idx++) { + FieldTable ft; + ft.decode(inBuffer); + } + } catch (exception& /*e*/) { + return 0; + } + + end = inBuffer.getPosition(); + inBuffer.restore(); // restore original position + return end - start; +} + +void ManagementAgent::setAllocator(std::auto_ptr<IdAllocator> a) +{ + Mutex::ScopedLock lock (addLock); + allocator = a; +} + +uint64_t ManagementAgent::allocateId(Manageable* object) +{ + Mutex::ScopedLock lock (addLock); + if (allocator.get()) return allocator->getIdFor(object); + return 0; +} + +void ManagementAgent::disallow(const std::string& className, const std::string& methodName, const std::string& message) { + disallowed[std::make_pair(className, methodName)] = message; +} diff --git a/cpp/src/qpid/management/ManagementBroker.h b/cpp/src/qpid/management/ManagementAgent.h index 151926f526..c64c073d8c 100644 --- a/cpp/src/qpid/management/ManagementBroker.h +++ b/cpp/src/qpid/management/ManagementAgent.h @@ -1,5 +1,5 @@ -#ifndef _ManagementBroker_ -#define _ManagementBroker_ +#ifndef _ManagementAgent_ +#define _ManagementAgent_ /* * @@ -21,62 +21,89 @@ * under the License. * */ +#include "qpid/broker/BrokerImportExport.h" #include "qpid/Options.h" #include "qpid/broker/Exchange.h" -#include "qpid/broker/Timer.h" #include "qpid/framing/Uuid.h" #include "qpid/sys/Mutex.h" +#include "qpid/sys/Timer.h" #include "qpid/broker/ConnectionToken.h" -#include "qpid/agent/ManagementAgent.h" -#include "ManagementObject.h" -#include "Manageable.h" -#include "qpid/management/Agent.h" +#include "qpid/management/ManagementObject.h" +#include "qpid/management/ManagementEvent.h" +#include "qpid/management/Manageable.h" +#include "qmf/org/apache/qpid/broker/Agent.h" #include <qpid/framing/AMQFrame.h> +#include <memory> +#include <string> +#include <map> namespace qpid { namespace management { -class ManagementBroker : public ManagementAgent +struct IdAllocator; + +class ManagementAgent { - private: +private: int threadPoolSize; - public: +public: + typedef enum { + SEV_EMERG = 0, + SEV_ALERT = 1, + SEV_CRIT = 2, + SEV_ERROR = 3, + SEV_WARN = 4, + SEV_NOTE = 5, + SEV_INFO = 6, + SEV_DEBUG = 7, + SEV_DEFAULT = 8 + } severity_t; + - ManagementBroker (); - virtual ~ManagementBroker (); + ManagementAgent (); + virtual ~ManagementAgent (); - void configure (std::string dataDir, uint16_t interval, Manageable* broker, int threadPoolSize); + void configure (const std::string& dataDir, uint16_t interval, + qpid::broker::Broker* broker, int threadPoolSize); void setInterval (uint16_t _interval) { interval = _interval; } - void setExchange (broker::Exchange::shared_ptr mgmtExchange, - broker::Exchange::shared_ptr directExchange); + void setExchange (qpid::broker::Exchange::shared_ptr mgmtExchange, + qpid::broker::Exchange::shared_ptr directExchange); int getMaxThreads () { return threadPoolSize; } - void RegisterClass (std::string packageName, - std::string className, - uint8_t* md5Sum, - ManagementObject::writeSchemaCall_t schemaCall); - uint64_t addObject (ManagementObject* object, - uint32_t persistId = 0, - uint32_t persistBank = 4); - void clientAdded (void); - bool dispatchCommand (broker::Deliverable& msg, + QPID_BROKER_EXTERN void registerClass (const std::string& packageName, + const std::string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall); + QPID_BROKER_EXTERN void registerEvent (const std::string& packageName, + const std::string& eventName, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall); + QPID_BROKER_EXTERN ObjectId addObject (ManagementObject* object, + uint64_t persistId = 0, + bool publishNow = false); + QPID_BROKER_EXTERN void raiseEvent(const ManagementEvent& event, + severity_t severity = SEV_DEFAULT); + QPID_BROKER_EXTERN void clientAdded (const std::string& routingKey); + + bool dispatchCommand (qpid::broker::Deliverable& msg, const std::string& routingKey, const framing::FieldTable* args); - // Stubs for remote management agent calls - void init (std::string, uint16_t, uint16_t, bool) { assert(0); } - uint32_t pollCallbacks (uint32_t) { assert(0); return 0; } - int getSignalFd () { assert(0); return -1; } + const framing::Uuid& getUuid() const { return uuid; } - private: - friend class ManagementAgent; + void setAllocator(std::auto_ptr<IdAllocator> allocator); + uint64_t allocateId(Manageable* object); - struct Periodic : public broker::TimerTask + /** 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); + +private: + struct Periodic : public qpid::sys::TimerTask { - ManagementBroker& broker; + ManagementAgent& agent; - Periodic (ManagementBroker& broker, uint32_t seconds); + Periodic (ManagementAgent& agent, uint32_t seconds); virtual ~Periodic (); void fire (); }; @@ -86,10 +113,13 @@ class ManagementBroker : public ManagementAgent // struct RemoteAgent : public Manageable { - uint32_t objIdBank; + ManagementAgent& agent; + uint32_t brokerBank; + uint32_t agentBank; std::string routingKey; - uint64_t connectionRef; - Agent* mgmtObject; + ObjectId connectionRef; + qmf::org::apache::qpid::broker::Agent* mgmtObject; + RemoteAgent(ManagementAgent& _agent) : agent(_agent) {} ManagementObject* GetManagementObject (void) const { return mgmtObject; } virtual ~RemoteAgent (); }; @@ -97,7 +127,7 @@ class ManagementBroker : public ManagementAgent // TODO: Eventually replace string with entire reply-to structure. reply-to // currently assumes that the exchange is "amq.direct" even though it could // in theory be specified differently. - typedef std::map<uint64_t, RemoteAgent*> RemoteAgentMap; + typedef std::map<ObjectId, RemoteAgent*> RemoteAgentMap; typedef std::vector<std::string> ReplyToVector; // Storage for known schema classes: @@ -128,17 +158,21 @@ class ManagementBroker : public ManagementAgent struct SchemaClass { + uint8_t kind; ManagementObject::writeSchemaCall_t writeSchemaCall; uint32_t pendingSequence; size_t bufferLen; uint8_t* buffer; - SchemaClass () : writeSchemaCall(0), pendingSequence(0), bufferLen(0), buffer(0) {} + SchemaClass(uint8_t _kind, uint32_t seq) : + kind(_kind), writeSchemaCall(0), pendingSequence(seq), bufferLen(0), buffer(0) {} + SchemaClass(uint8_t _kind, ManagementObject::writeSchemaCall_t call) : + kind(_kind), writeSchemaCall(call), pendingSequence(0), bufferLen(0), buffer(0) {} bool hasSchema () { return (writeSchemaCall != 0) || (buffer != 0); } void appendSchema (framing::Buffer& buf); }; - typedef std::map<SchemaClassKey, SchemaClass*, SchemaClassKeyComp> ClassMap; + typedef std::map<SchemaClassKey, SchemaClass, SchemaClassKeyComp> ClassMap; typedef std::map<std::string, ClassMap> PackageMap; RemoteAgentMap remoteAgents; @@ -152,43 +186,55 @@ class ManagementBroker : public ManagementAgent framing::Uuid uuid; sys::Mutex addLock; sys::Mutex userLock; - broker::Timer timer; - broker::Exchange::shared_ptr mExchange; - broker::Exchange::shared_ptr dExchange; + qpid::broker::Exchange::shared_ptr mExchange; + qpid::broker::Exchange::shared_ptr dExchange; std::string dataDir; uint16_t interval; - Manageable* broker; + qpid::broker::Broker* broker; + qpid::sys::Timer* timer; uint16_t bootSequence; - uint32_t localBank; uint32_t nextObjectId; + uint32_t brokerBank; uint32_t nextRemoteBank; uint32_t nextRequestSequence; bool clientWasAdded; + const uint64_t startTime; + + std::auto_ptr<IdAllocator> allocator; + + typedef std::pair<std::string,std::string> MethodName; + typedef std::map<MethodName, std::string> DisallowedMethods; + DisallowedMethods disallowed; + # define MA_BUFFER_SIZE 65536 char inputBuffer[MA_BUFFER_SIZE]; char outputBuffer[MA_BUFFER_SIZE]; + char eventBuffer[MA_BUFFER_SIZE]; void writeData (); - void PeriodicProcessing (void); - void EncodeHeader (framing::Buffer& buf, uint8_t opcode, uint32_t seq = 0); - bool CheckHeader (framing::Buffer& buf, uint8_t *opcode, uint32_t *seq); - void SendBuffer (framing::Buffer& buf, + void periodicProcessing (void); + void deleteObjectNowLH(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 sendBuffer (framing::Buffer& buf, uint32_t length, - broker::Exchange::shared_ptr exchange, + qpid::broker::Exchange::shared_ptr exchange, std::string routingKey); void moveNewObjectsLH(); - void dispatchAgentCommandLH (broker::Message& msg); + bool authorizeAgentMessageLH(qpid::broker::Message& msg); + void dispatchAgentCommandLH(qpid::broker::Message& msg); - PackageMap::iterator FindOrAddPackageLH(std::string name); - void AddClass(PackageMap::iterator pIter, - std::string className, - uint8_t* md5Sum, - ManagementObject::writeSchemaCall_t schemaCall); - void EncodePackageIndication (framing::Buffer& buf, + PackageMap::iterator findOrAddPackageLH(std::string name); + void addClassLH(uint8_t kind, + PackageMap::iterator pIter, + const std::string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall); + void encodePackageIndication (framing::Buffer& buf, PackageMap::iterator pIter); - void EncodeClassIndication (framing::Buffer& buf, + void encodeClassIndication (framing::Buffer& buf, PackageMap::iterator pIter, ClassMap::iterator cIter); bool bankInUse (uint32_t bank); @@ -204,13 +250,15 @@ class ManagementBroker : public ManagementAgent void handleClassIndLH (framing::Buffer& inBuffer, std::string replyToKey, uint32_t sequence); void handleSchemaRequestLH (framing::Buffer& inBuffer, std::string replyToKey, uint32_t sequence); void handleSchemaResponseLH (framing::Buffer& inBuffer, std::string replyToKey, uint32_t sequence); - void handleAttachRequestLH (framing::Buffer& inBuffer, std::string replyToKey, uint32_t sequence, const broker::ConnectionToken* connToken); + void handleAttachRequestLH (framing::Buffer& inBuffer, std::string replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken); void handleGetQueryLH (framing::Buffer& inBuffer, std::string replyToKey, uint32_t sequence); - void handleMethodRequestLH (framing::Buffer& inBuffer, std::string replyToKey, uint32_t sequence); + void handleMethodRequestLH (framing::Buffer& inBuffer, std::string replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken); - size_t ValidateSchema(framing::Buffer&); + size_t validateSchema(framing::Buffer&, uint8_t kind); + size_t validateTableSchema(framing::Buffer&); + size_t validateEventSchema(framing::Buffer&); }; }} -#endif /*!_ManagementBroker_*/ +#endif /*!_ManagementAgent_*/ diff --git a/cpp/src/qpid/management/ManagementBroker.cpp b/cpp/src/qpid/management/ManagementBroker.cpp deleted file mode 100644 index 1bdd8ab836..0000000000 --- a/cpp/src/qpid/management/ManagementBroker.cpp +++ /dev/null @@ -1,933 +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 "ManagementBroker.h" -#include "qpid/broker/DeliverableMessage.h" -#include "qpid/log/Statement.h" -#include <qpid/broker/Message.h> -#include <qpid/broker/MessageDelivery.h> -#include "qpid/framing/MessageTransferBody.h" -#include "qpid/sys/Time.h" -#include "qpid/broker/ConnectionState.h" -#include <list> -#include <iostream> -#include <fstream> - -using boost::intrusive_ptr; -using qpid::framing::Uuid; -using namespace qpid::framing; -using namespace qpid::management; -using namespace qpid::broker; -using namespace qpid::sys; -using namespace std; - -Mutex ManagementAgent::Singleton::lock; -bool ManagementAgent::Singleton::disabled = false; -ManagementAgent* ManagementAgent::Singleton::agent = 0; -int ManagementAgent::Singleton::refCount = 0; - -ManagementAgent::Singleton::Singleton(bool disableManagement) -{ - Mutex::ScopedLock _lock(lock); - if (disableManagement && !disabled) { - disabled = true; - assert(refCount == 0); // can't disable after agent has been allocated - } - if (refCount == 0 && !disabled) - agent = new ManagementBroker(); - refCount++; -} - -ManagementAgent::Singleton::~Singleton() -{ - Mutex::ScopedLock _lock(lock); - refCount--; - if (refCount == 0 && !disabled) { - delete agent; - agent = 0; - } -} - -ManagementAgent* ManagementAgent::Singleton::getInstance() -{ - return agent; -} - -ManagementBroker::RemoteAgent::~RemoteAgent () -{ - if (mgmtObject != 0) - mgmtObject->resourceDestroy(); -} - -ManagementBroker::ManagementBroker () : - threadPoolSize(1), interval(10), broker(0) -{ - localBank = 5; - nextObjectId = 1; - bootSequence = 1; - nextRemoteBank = 10; - nextRequestSequence = 1; - clientWasAdded = false; -} - -ManagementBroker::~ManagementBroker () -{ - timer.stop(); - { - Mutex::ScopedLock lock (userLock); - - // Reset the shared pointers to exchanges. If this is not done now, the exchanges - // will stick around until dExchange and mExchange are implicitely destroyed (long - // after this destructor completes). Those exchanges hold references to management - // objects that will be invalid. - dExchange.reset(); - mExchange.reset(); - - moveNewObjectsLH(); - for (ManagementObjectMap::iterator iter = managementObjects.begin (); - iter != managementObjects.end (); - iter++) { - ManagementObject* object = iter->second; - delete object; - } - managementObjects.clear(); - } -} - -void ManagementBroker::configure(string _dataDir, uint16_t _interval, Manageable* _broker, int _threads) -{ - dataDir = _dataDir; - interval = _interval; - broker = _broker; - threadPoolSize = _threads; - timer.add (intrusive_ptr<TimerTask> (new Periodic(*this, interval))); - - // Get from file or generate and save to file. - if (dataDir.empty()) - { - uuid.generate(); - QPID_LOG (info, "ManagementBroker has no data directory, generated new broker ID: " - << uuid); - } - else - { - string filename(dataDir + "/.mbrokerdata"); - ifstream inFile(filename.c_str ()); - - if (inFile.good()) - { - inFile >> uuid; - inFile >> bootSequence; - inFile >> nextRemoteBank; - inFile.close(); - QPID_LOG (debug, "ManagementBroker restored broker ID: " << uuid); - - bootSequence++; - writeData(); - } - else - { - uuid.generate(); - QPID_LOG (info, "ManagementBroker generated broker ID: " << uuid); - writeData(); - } - - QPID_LOG (debug, "ManagementBroker boot sequence: " << bootSequence); - } -} - -void ManagementBroker::writeData () -{ - string filename (dataDir + "/.mbrokerdata"); - ofstream outFile (filename.c_str ()); - - if (outFile.good()) - { - outFile << uuid << " " << bootSequence << " " << nextRemoteBank << endl; - outFile.close(); - } -} - -void ManagementBroker::setExchange (broker::Exchange::shared_ptr _mexchange, - broker::Exchange::shared_ptr _dexchange) -{ - mExchange = _mexchange; - dExchange = _dexchange; -} - -void ManagementBroker::RegisterClass (string packageName, - string className, - uint8_t* md5Sum, - ManagementObject::writeSchemaCall_t schemaCall) -{ - Mutex::ScopedLock lock(userLock); - PackageMap::iterator pIter = FindOrAddPackageLH(packageName); - AddClass(pIter, className, md5Sum, schemaCall); -} - -uint64_t ManagementBroker::addObject (ManagementObject* object, - uint32_t persistId, - uint32_t persistBank) -{ - Mutex::ScopedLock lock (addLock); - uint64_t objectId; - - if (persistId == 0) - { - objectId = ((uint64_t) bootSequence) << 48 | - ((uint64_t) localBank) << 24 | nextObjectId++; - if ((nextObjectId & 0xFF000000) != 0) - { - nextObjectId = 1; - localBank++; - } - } - else - objectId = ((uint64_t) persistBank) << 24 | persistId; - - object->setObjectId (objectId); - newManagementObjects[objectId] = object; - return objectId; -} - -ManagementBroker::Periodic::Periodic (ManagementBroker& _broker, uint32_t _seconds) - : TimerTask (qpid::sys::Duration ((_seconds ? _seconds : 1) * qpid::sys::TIME_SEC)), broker(_broker) {} - -ManagementBroker::Periodic::~Periodic () {} - -void ManagementBroker::Periodic::fire () -{ - broker.timer.add (intrusive_ptr<TimerTask> (new Periodic (broker, broker.interval))); - broker.PeriodicProcessing (); -} - -void ManagementBroker::clientAdded (void) -{ - Mutex::ScopedLock lock (userLock); - - clientWasAdded = true; - for (RemoteAgentMap::iterator aIter = remoteAgents.begin(); - aIter != remoteAgents.end(); - aIter++) { - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader (outBuffer, 'x'); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, dExchange, aIter->second->routingKey); - } -} - -void ManagementBroker::EncodeHeader (Buffer& buf, uint8_t opcode, uint32_t seq) -{ - buf.putOctet ('A'); - buf.putOctet ('M'); - buf.putOctet ('1'); - buf.putOctet (opcode); - buf.putLong (seq); -} - -bool ManagementBroker::CheckHeader (Buffer& buf, uint8_t *opcode, uint32_t *seq) -{ - uint8_t h1 = buf.getOctet (); - uint8_t h2 = buf.getOctet (); - uint8_t h3 = buf.getOctet (); - - *opcode = buf.getOctet (); - *seq = buf.getLong (); - - return h1 == 'A' && h2 == 'M' && h3 == '1'; -} - -void ManagementBroker::SendBuffer (Buffer& buf, - uint32_t length, - broker::Exchange::shared_ptr exchange, - string routingKey) -{ - if (exchange.get() == 0) - return; - - intrusive_ptr<Message> msg (new Message ()); - AMQFrame method (in_place<MessageTransferBody>( - ProtocolVersion(), exchange->getName (), 0, 0)); - AMQFrame header (in_place<AMQHeaderBody>()); - AMQFrame content(in_place<AMQContentBody>()); - - content.castBody<AMQContentBody>()->decode(buf, length); - - method.setEof (false); - header.setBof (false); - header.setEof (false); - content.setBof (false); - - msg->getFrames().append(method); - msg->getFrames().append(header); - - MessageProperties* props = - msg->getFrames().getHeaders()->get<MessageProperties>(true); - props->setContentLength(length); - msg->getFrames().append(content); - - DeliverableMessage deliverable (msg); - exchange->route (deliverable, routingKey, 0); -} - -void ManagementBroker::moveNewObjectsLH() -{ - Mutex::ScopedLock lock (addLock); - for (ManagementObjectMap::iterator iter = newManagementObjects.begin (); - iter != newManagementObjects.end (); - iter++) - managementObjects[iter->first] = iter->second; - newManagementObjects.clear(); -} - -void ManagementBroker::PeriodicProcessing (void) -{ -#define BUFSIZE 65536 - Mutex::ScopedLock lock (userLock); - char msgChars[BUFSIZE]; - uint32_t contentSize; - string routingKey; - std::list<uint64_t> deleteList; - - { - Buffer msgBuffer(msgChars, BUFSIZE); - EncodeHeader(msgBuffer, 'h'); - msgBuffer.putLongLong(uint64_t(Duration(now()))); - - contentSize = BUFSIZE - msgBuffer.available (); - msgBuffer.reset (); - routingKey = "mgmt." + uuid.str() + ".heartbeat"; - SendBuffer (msgBuffer, contentSize, mExchange, routingKey); - } - - moveNewObjectsLH(); - - if (clientWasAdded) - { - clientWasAdded = false; - for (ManagementObjectMap::iterator iter = managementObjects.begin (); - iter != managementObjects.end (); - iter++) - { - ManagementObject* object = iter->second; - object->setAllChanged (); - } - } - - if (managementObjects.empty ()) - return; - - for (ManagementObjectMap::iterator iter = managementObjects.begin (); - iter != managementObjects.end (); - iter++) - { - ManagementObject* object = iter->second; - - if (object->getConfigChanged () || object->isDeleted ()) - { - Buffer msgBuffer (msgChars, BUFSIZE); - EncodeHeader (msgBuffer, 'c'); - object->writeProperties(msgBuffer); - - contentSize = BUFSIZE - msgBuffer.available (); - msgBuffer.reset (); - routingKey = "mgmt." + uuid.str() + ".prop." + object->getClassName (); - SendBuffer (msgBuffer, contentSize, mExchange, routingKey); - } - - if (object->getInstChanged ()) - { - Buffer msgBuffer (msgChars, BUFSIZE); - EncodeHeader (msgBuffer, 'i'); - object->writeStatistics(msgBuffer); - - contentSize = BUFSIZE - msgBuffer.available (); - msgBuffer.reset (); - routingKey = "mgmt." + uuid.str () + ".stat." + object->getClassName (); - SendBuffer (msgBuffer, contentSize, mExchange, routingKey); - } - - if (object->isDeleted ()) - deleteList.push_back (iter->first); - } - - // Delete flagged objects - for (std::list<uint64_t>::reverse_iterator iter = deleteList.rbegin (); - iter != deleteList.rend (); - iter++) - managementObjects.erase (*iter); - - if (!deleteList.empty()) { - deleteList.clear(); - deleteOrphanedAgentsLH(); - } -} - -void ManagementBroker::sendCommandComplete (string replyToKey, uint32_t sequence, - uint32_t code, string text) -{ - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader (outBuffer, 'z', sequence); - outBuffer.putLong (code); - outBuffer.putShortString (text); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, dExchange, replyToKey); -} - -bool ManagementBroker::dispatchCommand (Deliverable& deliverable, - const string& routingKey, - const FieldTable* /*args*/) -{ - Mutex::ScopedLock lock (userLock); - Message& msg = ((DeliverableMessage&) deliverable).getMessage (); - - // Parse the routing key. This management broker should act as though it - // is bound to the exchange to match the following keys: - // - // agent.<X>.# - // broker.# - // - // where <X> is any non-negative decimal integer less than the lowest remote - // object-id bank. - - if (routingKey == "broker") { - dispatchAgentCommandLH (msg); - return false; - } - - else if (routingKey.compare(0, 6, "agent.") == 0) { - std::string::size_type delim = routingKey.find('.', 6); - if (delim == string::npos) - delim = routingKey.length(); - string bank = routingKey.substr(6, delim - 6); - if ((uint32_t) atoi(bank.c_str()) <= localBank) { - dispatchAgentCommandLH (msg); - return false; - } - } - - return true; -} - -void ManagementBroker::handleMethodRequestLH (Buffer& inBuffer, string replyToKey, uint32_t sequence) -{ - string methodName; - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - uint64_t objId = inBuffer.getLongLong(); - inBuffer.getShortString(methodName); - - EncodeHeader(outBuffer, 'm', sequence); - - ManagementObjectMap::iterator iter = managementObjects.find(objId); - if (iter == managementObjects.end() || iter->second->isDeleted()) { - outBuffer.putLong (Manageable::STATUS_UNKNOWN_OBJECT); - outBuffer.putShortString (Manageable::StatusText (Manageable::STATUS_UNKNOWN_OBJECT)); - } else { - iter->second->doMethod(methodName, inBuffer, outBuffer); - } - - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - SendBuffer(outBuffer, outLen, dExchange, replyToKey); -} - -void ManagementBroker::handleBrokerRequestLH (Buffer&, string replyToKey, uint32_t sequence) -{ - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader (outBuffer, 'b', sequence); - uuid.encode (outBuffer); - - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, dExchange, replyToKey); -} - -void ManagementBroker::handlePackageQueryLH (Buffer&, string replyToKey, uint32_t sequence) -{ - for (PackageMap::iterator pIter = packages.begin (); - pIter != packages.end (); - pIter++) - { - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader (outBuffer, 'p', sequence); - EncodePackageIndication (outBuffer, pIter); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, dExchange, replyToKey); - } - - sendCommandComplete (replyToKey, sequence); -} - -void ManagementBroker::handlePackageIndLH (Buffer& inBuffer, string /*replyToKey*/, uint32_t /*sequence*/) -{ - std::string packageName; - - inBuffer.getShortString(packageName); - FindOrAddPackageLH(packageName); -} - -void ManagementBroker::handleClassQueryLH (Buffer& inBuffer, string replyToKey, uint32_t sequence) -{ - std::string packageName; - - inBuffer.getShortString (packageName); - 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 ()) - { - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader (outBuffer, 'q', sequence); - EncodeClassIndication (outBuffer, pIter, cIter); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, dExchange, replyToKey); - } - } - } - - sendCommandComplete (replyToKey, sequence); -} - -void ManagementBroker::handleClassIndLH (Buffer& inBuffer, string replyToKey, uint32_t) -{ - std::string packageName; - SchemaClassKey key; - - inBuffer.getShortString(packageName); - inBuffer.getShortString(key.name); - inBuffer.getBin128(key.hash); - - PackageMap::iterator pIter = FindOrAddPackageLH(packageName); - ClassMap::iterator cIter = pIter->second.find(key); - if (cIter == pIter->second.end()) { - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - uint32_t sequence = nextRequestSequence++; - - EncodeHeader (outBuffer, 'S', sequence); - outBuffer.putShortString(packageName); - outBuffer.putShortString(key.name); - outBuffer.putBin128(key.hash); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, dExchange, replyToKey); - - SchemaClass* newSchema = new SchemaClass; - newSchema->pendingSequence = sequence; - pIter->second[key] = newSchema; - } -} - -void ManagementBroker::SchemaClass::appendSchema(Buffer& buf) -{ - // If the management package is attached locally (embedded in the broker or - // linked in via plug-in), call the schema handler directly. If the package - // is from a remote management agent, send the stored schema information. - - if (writeSchemaCall != 0) - writeSchemaCall(buf); - else - buf.putRawData(buffer, bufferLen); -} - -void ManagementBroker::handleSchemaRequestLH (Buffer& inBuffer, string replyToKey, uint32_t sequence) -{ - string packageName; - SchemaClassKey key; - - inBuffer.getShortString (packageName); - inBuffer.getShortString (key.name); - inBuffer.getBin128 (key.hash); - - 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; - SchemaClass* classInfo = cIter->second; - - if (classInfo->hasSchema()) { - EncodeHeader(outBuffer, 's', sequence); - classInfo->appendSchema (outBuffer); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset(); - SendBuffer (outBuffer, outLen, dExchange, replyToKey); - } - else - sendCommandComplete (replyToKey, sequence, 1, "Schema not available"); - } - else - sendCommandComplete (replyToKey, sequence, 1, "Class key not found"); - } - else - sendCommandComplete (replyToKey, sequence, 1, "Package not found"); -} - -void ManagementBroker::handleSchemaResponseLH (Buffer& inBuffer, string /*replyToKey*/, uint32_t sequence) -{ - string packageName; - SchemaClassKey key; - - inBuffer.record(); - inBuffer.getShortString (packageName); - inBuffer.getShortString (key.name); - inBuffer.getBin128 (key.hash); - inBuffer.restore(); - - PackageMap::iterator pIter = packages.find(packageName); - if (pIter != packages.end()) { - ClassMap cMap = pIter->second; - ClassMap::iterator cIter = cMap.find(key); - if (cIter != cMap.end() && cIter->second->pendingSequence == sequence) { - size_t length = ValidateSchema(inBuffer); - if (length == 0) - cMap.erase(key); - else { - cIter->second->buffer = (uint8_t*) malloc(length); - cIter->second->bufferLen = length; - inBuffer.getRawData(cIter->second->buffer, cIter->second->bufferLen); - - // Publish a class-indication message - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader (outBuffer, 'q'); - EncodeClassIndication (outBuffer, pIter, cIter); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, mExchange, "mgmt." + uuid.str() + ".schema"); - } - } - } -} - -bool ManagementBroker::bankInUse (uint32_t bank) -{ - for (RemoteAgentMap::iterator aIter = remoteAgents.begin(); - aIter != remoteAgents.end(); - aIter++) - if (aIter->second->objIdBank == bank) - return true; - return false; -} - -uint32_t ManagementBroker::allocateNewBank () -{ - while (bankInUse (nextRemoteBank)) - nextRemoteBank++; - - uint32_t allocated = nextRemoteBank++; - writeData (); - return allocated; -} - -uint32_t ManagementBroker::assignBankLH (uint32_t requestedBank) -{ - if (requestedBank == 0 || bankInUse (requestedBank)) - return allocateNewBank (); - return requestedBank; -} - -void ManagementBroker::deleteOrphanedAgentsLH() -{ - vector<uint64_t> deleteList; - - for (RemoteAgentMap::iterator aIter = remoteAgents.begin(); aIter != remoteAgents.end(); aIter++) { - uint64_t connectionRef = aIter->first; - bool found = false; - - for (ManagementObjectMap::iterator iter = managementObjects.begin (); - iter != managementObjects.end (); - iter++) { - if (iter->first == connectionRef && !iter->second->isDeleted()) { - found = true; - break; - } - } - - if (!found) { - deleteList.push_back(connectionRef); - delete aIter->second; - } - } - - for (vector<uint64_t>::iterator dIter = deleteList.begin(); dIter != deleteList.end(); dIter++) { - - remoteAgents.erase(*dIter); - } - - deleteList.clear(); -} - -void ManagementBroker::handleAttachRequestLH (Buffer& inBuffer, string replyToKey, uint32_t sequence, const ConnectionToken* connToken) -{ - string label; - uint32_t requestedBank; - uint32_t assignedBank; - uint64_t connectionRef = ((const ConnectionState*) connToken)->GetManagementObject()->getObjectId(); - Uuid systemId; - - moveNewObjectsLH(); - deleteOrphanedAgentsLH(); - RemoteAgentMap::iterator aIter = remoteAgents.find(connectionRef); - if (aIter != remoteAgents.end()) { - // There already exists an agent on this session. Reject the request. - sendCommandComplete (replyToKey, sequence, 1, "Connection already has remote agent"); - return; - } - - inBuffer.getShortString (label); - systemId.decode (inBuffer); - requestedBank = inBuffer.getLong (); - assignedBank = assignBankLH (requestedBank); - - RemoteAgent* agent = new RemoteAgent; - agent->objIdBank = assignedBank; - agent->routingKey = replyToKey; - agent->connectionRef = connectionRef; - agent->mgmtObject = new management::Agent (this, agent); - agent->mgmtObject->set_connectionRef(agent->connectionRef); - agent->mgmtObject->set_label (label); - agent->mgmtObject->set_registeredTo (broker->GetManagementObject()->getObjectId()); - agent->mgmtObject->set_systemId (systemId); - agent->mgmtObject->set_objectIdBank (assignedBank); - addObject (agent->mgmtObject); - - remoteAgents[connectionRef] = agent; - - // Send an Attach Response - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader (outBuffer, 'a', sequence); - outBuffer.putLong (assignedBank); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, dExchange, replyToKey); -} - -void ManagementBroker::handleGetQueryLH (Buffer& inBuffer, string replyToKey, uint32_t sequence) -{ - FieldTable ft; - FieldTable::ValuePtr value; - - moveNewObjectsLH(); - - ft.decode(inBuffer); - value = ft.get("_class"); - if (value.get() == 0 || !value->convertsTo<string>()) - { - // TODO: Send completion with an error code - return; - } - - string className (value->get<string>()); - - for (ManagementObjectMap::iterator iter = managementObjects.begin (); - iter != managementObjects.end (); - iter++) - { - ManagementObject* object = iter->second; - if (object->getClassName () == className) - { - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader (outBuffer, 'g', sequence); - object->writeProperties(outBuffer); - object->writeStatistics(outBuffer, true); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, dExchange, replyToKey); - } - } - - sendCommandComplete (replyToKey, sequence); -} - -void ManagementBroker::dispatchAgentCommandLH (Message& msg) -{ - Buffer inBuffer (inputBuffer, MA_BUFFER_SIZE); - uint8_t opcode; - uint32_t sequence; - string replyToKey; - - const framing::MessageProperties* p = - msg.getFrames().getHeaders()->get<framing::MessageProperties>(); - if (p && p->hasReplyTo()) { - const framing::ReplyTo& rt = p->getReplyTo(); - replyToKey = rt.getRoutingKey(); - } - else - return; - - if (msg.encodedSize() > MA_BUFFER_SIZE) { - QPID_LOG(debug, "ManagementBroker::dispatchAgentCommandLH: Message too large: " << - msg.encodedSize()); - return; - } - - msg.encodeContent(inBuffer); - inBuffer.reset(); - - if (!CheckHeader(inBuffer, &opcode, &sequence)) - return; - - if (opcode == 'B') handleBrokerRequestLH (inBuffer, replyToKey, sequence); - else if (opcode == 'P') handlePackageQueryLH (inBuffer, replyToKey, sequence); - else if (opcode == 'p') handlePackageIndLH (inBuffer, replyToKey, sequence); - else if (opcode == 'Q') handleClassQueryLH (inBuffer, replyToKey, sequence); - else if (opcode == 'q') handleClassIndLH (inBuffer, replyToKey, sequence); - else if (opcode == 'S') handleSchemaRequestLH (inBuffer, replyToKey, sequence); - else if (opcode == 's') handleSchemaResponseLH (inBuffer, replyToKey, sequence); - else if (opcode == 'A') handleAttachRequestLH (inBuffer, replyToKey, sequence, msg.getPublisher()); - else if (opcode == 'G') handleGetQueryLH (inBuffer, replyToKey, sequence); - else if (opcode == 'M') handleMethodRequestLH (inBuffer, replyToKey, sequence); -} - -ManagementBroker::PackageMap::iterator ManagementBroker::FindOrAddPackageLH(std::string name) -{ - PackageMap::iterator pIter = packages.find (name); - if (pIter != packages.end ()) - return pIter; - - // No such package found, create a new map entry. - pair<PackageMap::iterator, bool> result = - packages.insert (pair<string, ClassMap> (name, ClassMap ())); - QPID_LOG (debug, "ManagementBroker added package " << name); - - // Publish a package-indication message - Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - EncodeHeader (outBuffer, 'p'); - EncodePackageIndication (outBuffer, result.first); - outLen = MA_BUFFER_SIZE - outBuffer.available (); - outBuffer.reset (); - SendBuffer (outBuffer, outLen, mExchange, "mgmt." + uuid.str() + ".schema.package"); - - return result.first; -} - -void ManagementBroker::AddClass(PackageMap::iterator pIter, - string className, - uint8_t* md5Sum, - ManagementObject::writeSchemaCall_t schemaCall) -{ - SchemaClassKey key; - ClassMap& cMap = pIter->second; - - key.name = className; - memcpy (&key.hash, md5Sum, 16); - - ClassMap::iterator cIter = cMap.find (key); - if (cIter != cMap.end ()) - return; - - // No such class found, create a new class with local information. - QPID_LOG (debug, "ManagementBroker added class " << pIter->first << "." << - key.name); - SchemaClass* classInfo = new SchemaClass; - - classInfo->writeSchemaCall = schemaCall; - cMap[key] = classInfo; - cIter = cMap.find (key); -} - -void ManagementBroker::EncodePackageIndication (Buffer& buf, - PackageMap::iterator pIter) -{ - buf.putShortString ((*pIter).first); -} - -void ManagementBroker::EncodeClassIndication (Buffer& buf, - PackageMap::iterator pIter, - ClassMap::iterator cIter) -{ - SchemaClassKey key = (*cIter).first; - - buf.putShortString ((*pIter).first); - buf.putShortString (key.name); - buf.putBin128 (key.hash); -} - -size_t ManagementBroker::ValidateSchema(Buffer& inBuffer) -{ - uint32_t start = inBuffer.getPosition(); - uint32_t end; - string text; - uint8_t hash[16]; - - inBuffer.record(); - inBuffer.getShortString(text); - inBuffer.getShortString(text); - inBuffer.getBin128(hash); - - uint16_t propCount = inBuffer.getShort(); - uint16_t statCount = inBuffer.getShort(); - uint16_t methCount = inBuffer.getShort(); - uint16_t evntCount = inBuffer.getShort(); - - for (uint16_t idx = 0; idx < propCount + statCount; idx++) { - FieldTable ft; - ft.decode(inBuffer); - } - - for (uint16_t idx = 0; idx < methCount; idx++) { - FieldTable ft; - ft.decode(inBuffer); - int argCount = ft.getInt("argCount"); - for (int mIdx = 0; mIdx < argCount; mIdx++) { - FieldTable aft; - aft.decode(inBuffer); - } - } - - if (evntCount != 0) - return 0; - - end = inBuffer.getPosition(); - inBuffer.restore(); // restore original position - return end - start; -} diff --git a/cpp/src/qpid/management/ManagementExchange.cpp b/cpp/src/qpid/management/ManagementExchange.cpp index 4ccf8e68c9..b90bcd87d8 100644 --- a/cpp/src/qpid/management/ManagementExchange.cpp +++ b/cpp/src/qpid/management/ManagementExchange.cpp @@ -19,7 +19,7 @@ * */ -#include "ManagementExchange.h" +#include "qpid/management/ManagementExchange.h" #include "qpid/log/Statement.h" using namespace qpid::management; @@ -27,14 +27,14 @@ using namespace qpid::broker; using namespace qpid::framing; using namespace qpid::sys; -ManagementExchange::ManagementExchange (const string& _name, Manageable* _parent) : - Exchange (_name, _parent), TopicExchange(_name, _parent) {} +ManagementExchange::ManagementExchange (const string& _name, Manageable* _parent, Broker* b) : + Exchange (_name, _parent, b), TopicExchange(_name, _parent, b) {} ManagementExchange::ManagementExchange (const std::string& _name, bool _durable, const FieldTable& _args, - Manageable* _parent) : - Exchange (_name, _durable, _args, _parent), - TopicExchange(_name, _durable, _args, _parent) {} + Manageable* _parent, Broker* b) : + Exchange (_name, _durable, _args, _parent, b), + TopicExchange(_name, _durable, _args, _parent, b) {} void ManagementExchange::route (Deliverable& msg, const string& routingKey, @@ -56,11 +56,11 @@ bool ManagementExchange::bind (Queue::shared_ptr queue, const string& routingKey, const qpid::framing::FieldTable* args) { - managementAgent->clientAdded (); - return TopicExchange::bind (queue, routingKey, args); + managementAgent->clientAdded(routingKey); + return TopicExchange::bind(queue, routingKey, args); } -void ManagementExchange::setManagmentAgent (ManagementBroker* agent) +void ManagementExchange::setManagmentAgent (ManagementAgent* agent) { managementAgent = agent; } diff --git a/cpp/src/qpid/management/ManagementExchange.h b/cpp/src/qpid/management/ManagementExchange.h index d54db1a74e..3fa4039af7 100644 --- a/cpp/src/qpid/management/ManagementExchange.h +++ b/cpp/src/qpid/management/ManagementExchange.h @@ -22,7 +22,7 @@ #define _ManagementExchange_ #include "qpid/broker/TopicExchange.h" -#include "ManagementBroker.h" +#include "qpid/management/ManagementAgent.h" namespace qpid { namespace broker { @@ -30,15 +30,15 @@ namespace broker { class ManagementExchange : public virtual TopicExchange { private: - management::ManagementBroker* managementAgent; + management::ManagementAgent* managementAgent; public: static const std::string typeName; - ManagementExchange (const string& name, Manageable* _parent = 0); + ManagementExchange (const string& name, Manageable* _parent = 0, Broker* broker = 0); ManagementExchange (const string& _name, bool _durable, const qpid::framing::FieldTable& _args, - Manageable* _parent = 0); + Manageable* _parent = 0, Broker* broker = 0); virtual std::string getType() const { return typeName; } @@ -50,7 +50,7 @@ class ManagementExchange : public virtual TopicExchange const string& routingKey, const qpid::framing::FieldTable* args); - void setManagmentAgent (management::ManagementBroker* agent); + void setManagmentAgent (management::ManagementAgent* agent); virtual ~ManagementExchange(); }; diff --git a/cpp/src/qpid/management/ManagementObject.cpp b/cpp/src/qpid/management/ManagementObject.cpp index 74d9571d10..bcb6159663 100644 --- a/cpp/src/qpid/management/ManagementObject.cpp +++ b/cpp/src/qpid/management/ManagementObject.cpp @@ -19,38 +19,165 @@ * */ -#include "Manageable.h" -#include "ManagementObject.h" -#include "qpid/agent/ManagementAgent.h" +#include "qpid/management/Manageable.h" +#include "qpid/management/ManagementObject.h" #include "qpid/framing/FieldTable.h" +#include "qpid/sys/Thread.h" -using namespace qpid::framing; +#include <stdlib.h> + +using namespace qpid; using namespace qpid::management; -using namespace qpid::sys; +void AgentAttachment::setBanks(uint32_t broker, uint32_t bank) +{ + first = + ((uint64_t) (broker & 0x000fffff)) << 28 | + ((uint64_t) (bank & 0x0fffffff)); +} + +ObjectId::ObjectId(uint8_t flags, uint16_t seq, uint32_t broker, uint32_t bank, uint64_t object) + : agent(0) +{ + first = + ((uint64_t) (flags & 0x0f)) << 60 | + ((uint64_t) (seq & 0x0fff)) << 48 | + ((uint64_t) (broker & 0x000fffff)) << 28 | + ((uint64_t) (bank & 0x0fffffff)); + second = object; +} + +ObjectId::ObjectId(AgentAttachment* _agent, uint8_t flags, uint16_t seq, uint64_t object) + : agent(_agent) +{ + first = + ((uint64_t) (flags & 0x0f)) << 60 | + ((uint64_t) (seq & 0x0fff)) << 48; + second = object; +} + +ObjectId::ObjectId(std::istream& in) : agent(0) +{ + std::string text; + in >> text; + fromString(text); +} + +ObjectId::ObjectId(const std::string& text) : agent(0) +{ + fromString(text); +} + +void ObjectId::fromString(const std::string& text) +{ +#define FIELDS 5 +#if defined (_WIN32) && !defined (atoll) +# define atoll(X) _atoi64(X) +#endif + + std::string copy(text.c_str()); + char* cText; + char* field[FIELDS]; + bool atFieldStart = true; + int idx = 0; + + cText = const_cast<char*>(copy.c_str()); + for (char* cursor = cText; *cursor; cursor++) { + if (atFieldStart) { + if (idx >= FIELDS) + throw Exception("Invalid ObjectId format"); + field[idx++] = cursor; + atFieldStart = false; + } else { + if (*cursor == '-') { + *cursor = '\0'; + atFieldStart = true; + } + } + } + + if (idx != FIELDS) + throw Exception("Invalid ObjectId format"); + + first = (atoll(field[0]) << 60) + + (atoll(field[1]) << 48) + + (atoll(field[2]) << 28) + + atoll(field[3]); + second = atoll(field[4]); +} + + +bool ObjectId::operator==(const ObjectId &other) const +{ + uint64_t otherFirst = agent == 0 ? other.first : other.first & 0xffff000000000000LL; + + return first == otherFirst && second == other.second; +} + +bool ObjectId::operator<(const ObjectId &other) const +{ + uint64_t otherFirst = agent == 0 ? other.first : other.first & 0xffff000000000000LL; + + return (first < otherFirst) || ((first == otherFirst) && (second < other.second)); +} + +void ObjectId::encode(framing::Buffer& buffer) +{ + if (agent == 0) + buffer.putLongLong(first); + else + buffer.putLongLong(first | agent->first); + buffer.putLongLong(second); +} + +void ObjectId::decode(framing::Buffer& buffer) +{ + first = buffer.getLongLong(); + second = buffer.getLongLong(); +} + +namespace qpid { +namespace management { + +std::ostream& operator<<(std::ostream& out, const ObjectId& i) +{ + uint64_t virtFirst = i.first; + if (i.agent) + virtFirst |= i.agent->getFirst(); + + out << ((virtFirst & 0xF000000000000000LL) >> 60) << + "-" << ((virtFirst & 0x0FFF000000000000LL) >> 48) << + "-" << ((virtFirst & 0x0000FFFFF0000000LL) >> 28) << + "-" << (virtFirst & 0x000000000FFFFFFFLL) << + "-" << i.second; + return out; +} + +}} + +int ManagementObject::maxThreads = 1; int ManagementObject::nextThreadIndex = 0; -void ManagementObject::writeTimestamps (Buffer& buf) +void ManagementObject::writeTimestamps (framing::Buffer& buf) { buf.putShortString (getPackageName ()); buf.putShortString (getClassName ()); buf.putBin128 (getMd5Sum ()); - buf.putLongLong (uint64_t (Duration (now ()))); + buf.putLongLong (updateTime); buf.putLongLong (createTime); buf.putLongLong (destroyTime); - buf.putLongLong (objectId); + objectId.encode(buf); } -void ManagementObject::setReference(uint64_t) {} +void ManagementObject::setReference(ObjectId) {} int ManagementObject::getThreadIndex() { - static __thread int thisIndex = -1; + static QPID_TSS int thisIndex = -1; if (thisIndex == -1) { sys::Mutex::ScopedLock mutex(accessLock); thisIndex = nextThreadIndex; - if (nextThreadIndex < agent->getMaxThreads() - 1) + if (nextThreadIndex < maxThreads - 1) nextThreadIndex++; } return thisIndex; } - diff --git a/cpp/src/qpid/management/ManagementObject.h b/cpp/src/qpid/management/ManagementObject.h deleted file mode 100644 index 78d065aac2..0000000000 --- a/cpp/src/qpid/management/ManagementObject.h +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef _ManagementObject_ -#define _ManagementObject_ - -/* - * - * 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/Time.h" -#include "qpid/sys/Mutex.h" -#include <qpid/framing/Buffer.h> -#include <map> - -namespace qpid { -namespace management { - -class Manageable; -class ManagementAgent; - -class ManagementObject -{ - protected: - - uint64_t createTime; - uint64_t destroyTime; - uint64_t objectId; - bool configChanged; - bool instChanged; - bool deleted; - Manageable* coreObject; - sys::Mutex accessLock; - ManagementAgent* agent; - int maxThreads; - - static const uint8_t TYPE_U8 = 1; - static const uint8_t TYPE_U16 = 2; - static const uint8_t TYPE_U32 = 3; - static const uint8_t TYPE_U64 = 4; - static const uint8_t TYPE_SSTR = 6; - static const uint8_t TYPE_LSTR = 7; - static const uint8_t TYPE_ABSTIME = 8; - static const uint8_t TYPE_DELTATIME = 9; - static const uint8_t TYPE_REF = 10; - static const uint8_t TYPE_BOOL = 11; - static const uint8_t TYPE_FLOAT = 12; - static const uint8_t TYPE_DOUBLE = 13; - static const uint8_t TYPE_UUID = 14; - static const uint8_t TYPE_FTABLE = 15; - static const uint8_t TYPE_S8 = 16; - static const uint8_t TYPE_S16 = 17; - static const uint8_t TYPE_S32 = 18; - static const uint8_t TYPE_S64 = 19; - - static const uint8_t ACCESS_RC = 1; - static const uint8_t ACCESS_RW = 2; - static const uint8_t ACCESS_RO = 3; - - static const uint8_t DIR_I = 1; - static const uint8_t DIR_O = 2; - static const uint8_t DIR_IO = 3; - - static const uint8_t FLAG_CONFIG = 0x01; - static const uint8_t FLAG_INDEX = 0x02; - static const uint8_t FLAG_END = 0x80; - - static int nextThreadIndex; - - int getThreadIndex(); - void writeTimestamps (qpid::framing::Buffer& buf); - - public: - typedef void (*writeSchemaCall_t) (qpid::framing::Buffer&); - - ManagementObject (ManagementAgent* _agent, Manageable* _core) : - destroyTime(0), objectId (0), configChanged(true), - instChanged(true), deleted(false), coreObject(_core), agent(_agent) - { createTime = uint64_t (qpid::sys::Duration (qpid::sys::now ())); } - virtual ~ManagementObject () {} - - virtual writeSchemaCall_t getWriteSchemaCall (void) = 0; - virtual void writeProperties(qpid::framing::Buffer& buf) = 0; - virtual void writeStatistics(qpid::framing::Buffer& buf, - bool skipHeaders = false) = 0; - virtual void doMethod (std::string methodName, - qpid::framing::Buffer& inBuf, - qpid::framing::Buffer& outBuf) = 0; - virtual void setReference (uint64_t objectId); - - virtual std::string& getClassName (void) = 0; - virtual std::string& getPackageName (void) = 0; - virtual uint8_t* getMd5Sum (void) = 0; - - void setObjectId (uint64_t oid) { objectId = oid; } - uint64_t getObjectId (void) { return objectId; } - inline bool getConfigChanged (void) { return configChanged; } - virtual bool getInstChanged (void) { return instChanged; } - inline void setAllChanged (void) { - configChanged = true; - instChanged = true; - } - - inline void resourceDestroy (void) { - destroyTime = uint64_t (qpid::sys::Duration (qpid::sys::now ())); - deleted = true; - } - inline bool isDeleted (void) { return deleted; } - inline sys::Mutex& getLock() { return accessLock; } -}; - -typedef std::map<uint64_t,ManagementObject*> ManagementObjectMap; - -}} - - - -#endif /*!_ManagementObject_*/ diff --git a/cpp/src/qpid/messaging/Address.cpp b/cpp/src/qpid/messaging/Address.cpp new file mode 100644 index 0000000000..ff72f62705 --- /dev/null +++ b/cpp/src/qpid/messaging/Address.cpp @@ -0,0 +1,362 @@ +/* + * + * 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 "qpid/framing/Uuid.h" +#include <sstream> +#include <boost/format.hpp> + +namespace qpid { +namespace messaging { + +namespace { +const std::string SUBJECT_DIVIDER = "/"; +const std::string OPTIONS_DIVIDER = ";"; +const std::string SPACE = " "; +const std::string TYPE = "type"; +} +class AddressImpl +{ + public: + std::string name; + std::string subject; + Variant::Map options; + + AddressImpl() {} + AddressImpl(const std::string& n, const std::string& s, const Variant::Map& o) : + name(n), subject(s), options(o) {} +}; + +class AddressParser +{ + public: + AddressParser(const std::string&); + bool parse(Address& address); + private: + const std::string& input; + std::string::size_type current; + static const std::string RESERVED; + + bool readChar(char c); + bool readQuotedString(std::string& s); + bool readQuotedValue(Variant& value); + bool readString(std::string& value, char delimiter); + bool readWord(std::string& word, const std::string& delims = RESERVED); + bool readSimpleValue(Variant& word); + bool readKey(std::string& key); + bool readValue(Variant& value); + bool readKeyValuePair(Variant::Map& map); + bool readMap(Variant& value); + bool readList(Variant& value); + bool readName(std::string& name); + bool readSubject(std::string& subject); + bool error(const std::string& message); + bool eos(); + bool iswhitespace(); + bool in(const std::string& delims); + bool isreserved(); +}; + +Address::Address() : impl(new AddressImpl()) {} +Address::Address(const std::string& address) : impl(new AddressImpl()) +{ + AddressParser parser(address); + parser.parse(*this); +} +Address::Address(const std::string& name, const std::string& subject, const Variant::Map& options, + const std::string& type) + : impl(new AddressImpl(name, subject, options)) { setType(type); } +Address::Address(const Address& a) : + impl(new AddressImpl(a.impl->name, a.impl->subject, a.impl->options)) {} +Address::~Address() { delete impl; } + +Address& Address::operator=(const Address& a) { *impl = *a.impl; return *this; } + + +std::string Address::toStr() const +{ + std::stringstream out; + out << impl->name; + if (!impl->subject.empty()) out << SUBJECT_DIVIDER << impl->subject; + if (!impl->options.empty()) out << OPTIONS_DIVIDER << impl->options; + return out.str(); +} +Address::operator bool() const { return !impl->name.empty(); } +bool Address::operator !() const { return impl->name.empty(); } + +const std::string& Address::getName() const { return impl->name; } +void Address::setName(const std::string& name) { impl->name = name; } +const std::string& Address::getSubject() const { return impl->subject; } +bool Address::hasSubject() const { return !(impl->subject.empty()); } +void Address::setSubject(const std::string& subject) { impl->subject = subject; } +const Variant::Map& Address::getOptions() const { return impl->options; } +Variant::Map& Address::getOptions() { return impl->options; } +void Address::setOptions(const Variant::Map& options) { impl->options = options; } + + +namespace{ +const Variant EMPTY_VARIANT; +const std::string EMPTY_STRING; +const std::string NODE_PROPERTIES="node-properties"; +} + +const Variant& find(const Variant::Map& map, const std::string& key) +{ + Variant::Map::const_iterator i = map.find(key); + if (i == map.end()) return EMPTY_VARIANT; + else return i->second; +} + +std::string Address::getType() const +{ + const Variant& props = getOption(NODE_PROPERTIES); + if (props.getType() == VAR_MAP) { + const Variant& type = find(props.asMap(), TYPE); + if (!type.isVoid()) return type.asString(); + } + return EMPTY_STRING; +} + +void Address::setType(const std::string& type) +{ + Variant& props = impl->options[NODE_PROPERTIES]; + if (props.isVoid()) props = Variant::Map(); + props.asMap()[TYPE] = type; +} + +const Variant& Address::getOption(const std::string& key) const +{ + return find(impl->options, key); +} + +std::ostream& operator<<(std::ostream& out, const Address& address) +{ + out << address.toStr(); + return out; +} + +InvalidAddress::InvalidAddress(const std::string& msg) : Exception(msg) {} + +MalformedAddress::MalformedAddress(const std::string& msg) : Exception(msg) {} + +AddressParser::AddressParser(const std::string& s) : input(s), current(0) {} + +bool AddressParser::error(const std::string& message) +{ + throw MalformedAddress((boost::format("%1%, character %2% of %3%") % message % current % input).str()); +} + +bool AddressParser::parse(Address& address) +{ + std::string name; + if (readName(name)) { + if (name.find('#') == 0) name = qpid::framing::Uuid(true).str() + name; + address.setName(name); + if (readChar('/')) { + std::string subject; + if (readSubject(subject)) { + address.setSubject(subject); + } else { + return error("Expected subject after /"); + } + } + if (readChar(';')) { + Variant options = Variant::Map(); + if (readMap(options)) { + address.setOptions(options.asMap()); + } + } + //skip trailing whitespace + while (!eos() && iswhitespace()) ++current; + return eos() || error("Unexpected chars in address: " + input.substr(current)); + } else { + return input.empty() || error("Expected name"); + } +} + +bool AddressParser::readList(Variant& value) +{ + if (readChar('[')) { + value = Variant::List(); + Variant item; + while (readValue(item)) { + value.asList().push_back(item); + if (!readChar(',')) break; + } + return readChar(']') || error("Unmatched '['!"); + } else { + return false; + } +} + +bool AddressParser::readMap(Variant& value) +{ + if (readChar('{')) { + value = Variant::Map(); + while (readKeyValuePair(value.asMap()) && readChar(',')) {} + return readChar('}') || error("Unmatched '{'!"); + } else { + return false; + } +} + +bool AddressParser::readKeyValuePair(Variant::Map& map) +{ + std::string key; + Variant value; + if (readKey(key)) { + if (readChar(':') && readValue(value)) { + map[key] = value; + return true; + } else { + return error("Bad key-value pair, expected ':'"); + } + } else { + return false; + } +} + +bool AddressParser::readKey(std::string& key) +{ + return readWord(key); +} + +bool AddressParser::readValue(Variant& value) +{ + return readSimpleValue(value) || readQuotedValue(value) || + readMap(value) || readList(value) || error("Expected value"); +} + +bool AddressParser::readString(std::string& value, char delimiter) +{ + if (readChar(delimiter)) { + std::string::size_type start = current++; + while (!eos()) { + if (input.at(current) == delimiter) { + if (current > start) { + value = input.substr(start, current - start); + } else { + value = ""; + } + ++current; + return true; + } else { + ++current; + } + } + return error("Unmatched delimiter"); + } else { + return false; + } +} + +bool AddressParser::readName(std::string& name) +{ + return readQuotedString(name) || readWord(name, "/;"); +} + +bool AddressParser::readSubject(std::string& subject) +{ + return readQuotedString(subject) || readWord(subject, ";"); +} + +bool AddressParser::readQuotedString(std::string& s) +{ + return readString(s, '"') || readString(s, '\''); +} + +bool AddressParser::readQuotedValue(Variant& value) +{ + std::string s; + if (readQuotedString(s)) { + value = s; + return true; + } else { + return false; + } +} + +bool AddressParser::readSimpleValue(Variant& value) +{ + std::string s; + if (readWord(s)) { + value = s; + try { value = value.asInt64(); return true; } catch (const InvalidConversion&) {} + try { value = value.asDouble(); return true; } catch (const InvalidConversion&) {} + return true; + } else { + return false; + } +} + +bool AddressParser::readWord(std::string& value, const std::string& delims) +{ + //skip leading whitespace + while (!eos() && iswhitespace()) ++current; + + //read any number of non-whitespace, non-reserved chars into value + std::string::size_type start = current; + while (!eos() && !iswhitespace() && !in(delims)) ++current; + + if (current > start) { + value = input.substr(start, current - start); + return true; + } else { + return false; + } +} + +bool AddressParser::readChar(char c) +{ + while (!eos()) { + if (iswhitespace()) { + ++current; + } else if (input.at(current) == c) { + ++current; + return true; + } else { + return false; + } + } + return false; +} + +bool AddressParser::iswhitespace() +{ + return ::isspace(input.at(current)); +} + +bool AddressParser::isreserved() +{ + return in(RESERVED); +} + +bool AddressParser::in(const std::string& chars) +{ + return chars.find(input.at(current)) != std::string::npos; +} + +bool AddressParser::eos() +{ + return current >= input.size(); +} + +const std::string AddressParser::RESERVED = "\'\"{}[],:/"; +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/Connection.cpp b/cpp/src/qpid/messaging/Connection.cpp new file mode 100644 index 0000000000..64ca962317 --- /dev/null +++ b/cpp/src/qpid/messaging/Connection.cpp @@ -0,0 +1,96 @@ +/* + * + * 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/Connection.h" +#include "qpid/messaging/ConnectionImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/SessionImpl.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/client/amqp0_10/ConnectionImpl.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<qpid::messaging::Connection> PI; + +} + +namespace messaging { + +using qpid::client::PI; + +Connection Connection::open(const std::string& url, const Variant::Map& options) +{ + //only support amqp 0-10 at present + Connection connection(new qpid::client::amqp0_10::ConnectionImpl(url, options)); + return connection; +} + +Connection::Connection(ConnectionImpl* impl) { PI::ctor(*this, impl); } +Connection::Connection(const Connection& c) : qpid::client::Handle<ConnectionImpl>() { PI::copy(*this, c); } +Connection& Connection::operator=(const Connection& c) { return PI::assign(*this, c); } +Connection::~Connection() { PI::dtor(*this); } + +void Connection::close() { impl->close(); } +Session Connection::newSession(const char* name) { return impl->newSession(false, name); } +Session Connection::newSession(const std::string& name) { return impl->newSession(false, name); } +Session Connection::newSession(bool transactional, const std::string& name) +{ + return impl->newSession(transactional, name); +} +Session Connection::getSession(const std::string& name) const { return impl->getSession(name); } + +InvalidOptionString::InvalidOptionString(const std::string& msg) : Exception(msg) {} + +void parseKeyValuePair(const std::string& in, Variant::Map& out) +{ + std::string::size_type i = in.find('='); + if (i == std::string::npos || i == in.size() || in.find('=', i+1) != std::string::npos) { + throw InvalidOptionString(QPID_MSG("Cannot parse name-value pair from " << in)); + } else { + out[in.substr(0, i)] = in.substr(i+1); + } +} + +void parseOptionString(const std::string& in, Variant::Map& out) +{ + std::string::size_type start = 0; + std::string::size_type i = in.find('&'); + while (i != std::string::npos) { + parseKeyValuePair(in.substr(start, i-start), out); + if (i < in.size()) { + start = i+1; + i = in.find('&', start); + } else { + i = std::string::npos; + } + } + parseKeyValuePair(in.substr(start), out); +} + +Variant::Map parseOptionString(const std::string& in) +{ + Variant::Map map; + parseOptionString(in, map); + return map; +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/ConnectionImpl.h b/cpp/src/qpid/messaging/ConnectionImpl.h new file mode 100644 index 0000000000..4eff68ff9d --- /dev/null +++ b/cpp/src/qpid/messaging/ConnectionImpl.h @@ -0,0 +1,46 @@ +#ifndef QPID_MESSAGING_CONNECTIONIMPL_H +#define QPID_MESSAGING_CONNECTIONIMPL_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 "qpid/RefCounted.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +class Session; + +class ConnectionImpl : public virtual qpid::RefCounted +{ + public: + virtual ~ConnectionImpl() {} + virtual void close() = 0; + virtual Session newSession(bool transactional, const std::string& name) = 0; + virtual Session getSession(const std::string& name) const = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_CONNECTIONIMPL_H*/ diff --git a/cpp/src/qpid/messaging/ListContent.cpp b/cpp/src/qpid/messaging/ListContent.cpp new file mode 100644 index 0000000000..0c3ca5fc62 --- /dev/null +++ b/cpp/src/qpid/messaging/ListContent.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/messaging/ListContent.h" +#include "qpid/messaging/Message.h" +#include "qpid/client/amqp0_10/Codecs.h" + +namespace qpid { +namespace messaging { + +class ListContentImpl : public Variant +{ + Message* msg; + public: + ListContentImpl(Message& m) : Variant(Variant::List()), msg(&m) + { + if (msg->getContent().size()) { + qpid::client::amqp0_10::ListCodec codec; + codec.decode(msg->getContent(), *this); + } + } + + void encode() + { + qpid::client::amqp0_10::ListCodec codec; + codec.encode(*this, msg->getContent()); + } +}; + +ListContent::ListContent(Message& m) : impl(new ListContentImpl(m)) {} +ListContent::~ListContent() { delete impl; } +ListContent& ListContent::operator=(const ListContent& l) { *impl = *l.impl; return *this; } + +ListContent::const_iterator ListContent::begin() const { return impl->asList().begin(); } +ListContent::const_iterator ListContent::end() const { return impl->asList().end(); } +ListContent::const_reverse_iterator ListContent::rbegin() const { return impl->asList().rbegin(); } +ListContent::const_reverse_iterator ListContent::rend() const { return impl->asList().rend(); } + +ListContent::iterator ListContent::begin() { return impl->asList().begin(); } +ListContent::iterator ListContent::end() { return impl->asList().end(); } +ListContent::reverse_iterator ListContent::rbegin() { return impl->asList().rbegin(); } +ListContent::reverse_iterator ListContent::rend() { return impl->asList().rend(); } + +bool ListContent::empty() const { return impl->asList().empty(); } +size_t ListContent::size() const { return impl->asList().size(); } + +const Variant& ListContent::front() const { return impl->asList().front(); } +Variant& ListContent::front() { return impl->asList().front(); } +const Variant& ListContent::back() const { return impl->asList().back(); } +Variant& ListContent::back() { return impl->asList().back(); } + +void ListContent::push_front(const Variant& v) { impl->asList().push_front(v); } +void ListContent::push_back(const Variant& v) { impl->asList().push_back(v); } + +void ListContent::pop_front() { impl->asList().pop_front(); } +void ListContent::pop_back() { impl->asList().pop_back(); } + +ListContent::iterator ListContent::insert(iterator position, const Variant& v) +{ + return impl->asList().insert(position, v); +} +void ListContent::insert(iterator position, size_t n, const Variant& v) +{ + impl->asList().insert(position, n, v); +} +ListContent::iterator ListContent::erase(iterator position) { return impl->asList().erase(position); } +ListContent::iterator ListContent::erase(iterator first, iterator last) { return impl->asList().erase(first, last); } +void ListContent::clear() { impl->asList().clear(); } + +void ListContent::encode() { impl->encode(); } + +const Variant::List& ListContent::asList() const { return impl->asList(); } +Variant::List& ListContent::asList() { return impl->asList(); } + +std::ostream& operator<<(std::ostream& out, const ListContent& m) +{ + out << m.asList(); + return out; +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/ListView.cpp b/cpp/src/qpid/messaging/ListView.cpp new file mode 100644 index 0000000000..b717d157fa --- /dev/null +++ b/cpp/src/qpid/messaging/ListView.cpp @@ -0,0 +1,63 @@ +/* + * + * 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/ListView.h" +#include "qpid/messaging/Message.h" +#include "qpid/client/amqp0_10/Codecs.h" + +namespace qpid { +namespace messaging { + +class ListViewImpl : public Variant +{ + public: + ListViewImpl(const Message& msg) : Variant(Variant::List()) + { + if (msg.getContent().size()) { + qpid::client::amqp0_10::ListCodec codec; + codec.decode(msg.getContent(), *this); + } + } +}; + +ListView::ListView(const Message& m) :impl(new ListViewImpl(m)) {} +ListView::~ListView() { delete impl; } +ListView& ListView::operator=(const ListView& l) { *impl = *l.impl; return *this; } + +ListView::const_iterator ListView::begin() const { return impl->asList().begin(); } +ListView::const_iterator ListView::end() const { return impl->asList().end(); } +ListView::const_reverse_iterator ListView::rbegin() const { return impl->asList().rbegin(); } +ListView::const_reverse_iterator ListView::rend() const { return impl->asList().rend(); } + +bool ListView::empty() const { return impl->asList().empty(); } +size_t ListView::size() const { return impl->asList().size(); } + +const Variant& ListView::front() const { return impl->asList().front(); } +const Variant& ListView::back() const { return impl->asList().back(); } + +const Variant::List& ListView::asList() const { return impl->asList(); } + +std::ostream& operator<<(std::ostream& out, const ListView& m) +{ + out << m.asList(); + return out; +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/MapContent.cpp b/cpp/src/qpid/messaging/MapContent.cpp new file mode 100644 index 0000000000..6dba22be99 --- /dev/null +++ b/cpp/src/qpid/messaging/MapContent.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/messaging/MapContent.h" +#include "qpid/messaging/Message.h" +#include "qpid/client/amqp0_10/Codecs.h" + +namespace qpid { +namespace messaging { + +class MapContentImpl : public Variant +{ + Message* msg; + public: + MapContentImpl(Message& m) : Variant(Variant::Map()), msg(&m) + { + if (msg->getContent().size()) { + qpid::client::amqp0_10::MapCodec codec; + codec.decode(msg->getContent(), *this); + } + } + + void encode() + { + qpid::client::amqp0_10::MapCodec codec; + codec.encode(*this, msg->getContent()); + msg->setContentType(qpid::client::amqp0_10::MapCodec::contentType); + } +}; + +MapContent::MapContent(Message& m) : impl(new MapContentImpl(m)) {} +MapContent::~MapContent() { delete impl; } +MapContent& MapContent::operator=(const MapContent& m) { *impl = *m.impl; return *this; } + +MapContent::const_iterator MapContent::begin() const { return impl->asMap().begin(); } +MapContent::const_iterator MapContent::end() const { return impl->asMap().end(); } +MapContent::const_reverse_iterator MapContent::rbegin() const { return impl->asMap().rbegin(); } +MapContent::const_reverse_iterator MapContent::rend() const { return impl->asMap().rend(); } +MapContent::iterator MapContent::begin() { return impl->asMap().begin(); } +MapContent::iterator MapContent::end() { return impl->asMap().end(); } +MapContent::reverse_iterator MapContent::rbegin() { return impl->asMap().rbegin(); } +MapContent::reverse_iterator MapContent::rend() { return impl->asMap().rend(); } + +bool MapContent::empty() const { return impl->asMap().empty(); } +size_t MapContent::size() const { return impl->asMap().size(); } + +MapContent::const_iterator MapContent::find(const key_type& key) const { return impl->asMap().find(key); } +MapContent::iterator MapContent::find(const key_type& key) { return impl->asMap().find(key); } +const Variant& MapContent::operator[](const key_type& key) const { return impl->asMap()[key]; } +Variant& MapContent::operator[](const key_type& key) { return impl->asMap()[key]; } + +std::pair<MapContent::iterator,bool> MapContent::insert(const value_type& item) { return impl->asMap().insert(item); } +MapContent::iterator MapContent::insert(iterator position, const value_type& item) { return impl->asMap().insert(position, item); } +void MapContent::erase(iterator position) { impl->asMap().erase(position); } +void MapContent::erase(iterator first, iterator last) { impl->asMap().erase(first, last); } +size_t MapContent::erase(const key_type& key) { return impl->asMap().erase(key); } +void MapContent::clear() { impl->asMap().clear(); } + +void MapContent::encode() { impl->encode(); } + +const std::map<MapContent::key_type, Variant>& MapContent::asMap() const { return impl->asMap(); } +std::map<MapContent::key_type, Variant>& MapContent::asMap() { return impl->asMap(); } + + +std::ostream& operator<<(std::ostream& out, const MapContent& m) +{ + out << m.asMap(); + return out; +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/MapView.cpp b/cpp/src/qpid/messaging/MapView.cpp new file mode 100644 index 0000000000..ffa6e91a16 --- /dev/null +++ b/cpp/src/qpid/messaging/MapView.cpp @@ -0,0 +1,63 @@ +/* + * + * 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/MapView.h" +#include "qpid/messaging/Message.h" +#include "qpid/client/amqp0_10/Codecs.h" + +namespace qpid { +namespace messaging { + +class MapViewImpl : public Variant +{ + public: + MapViewImpl(const Message& msg) : Variant(Variant::Map()) + { + if (msg.getContent().size()) { + qpid::client::amqp0_10::MapCodec codec; + codec.decode(msg.getContent(), *this); + } + } +}; + +MapView::MapView(const Message& m) : impl(new MapViewImpl(m)) {} +MapView::~MapView() { delete impl; } +MapView& MapView::operator=(const MapView& m) { *impl = *m.impl; return *this; } + +MapView::const_iterator MapView::begin() const { return impl->asMap().begin(); } +MapView::const_iterator MapView::end() const { return impl->asMap().end(); } +MapView::const_reverse_iterator MapView::rbegin() const { return impl->asMap().rbegin(); } +MapView::const_reverse_iterator MapView::rend() const { return impl->asMap().rend(); } + +bool MapView::empty() const { return impl->asMap().empty(); } +size_t MapView::size() const { return impl->asMap().size(); } + +MapView::const_iterator MapView::find(const key_type& key) const { return impl->asMap().find(key); } +const Variant& MapView::operator[](const key_type& key) const { return impl->asMap()[key]; } + +const std::map<MapView::key_type, Variant>& MapView::asMap() const { return impl->asMap(); } + +std::ostream& operator<<(std::ostream& out, const MapView& m) +{ + out << m.asMap(); + return out; +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/Message.cpp b/cpp/src/qpid/messaging/Message.cpp new file mode 100644 index 0000000000..deb40b6aa3 --- /dev/null +++ b/cpp/src/qpid/messaging/Message.cpp @@ -0,0 +1,58 @@ +/* + * + * 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/Message.h" +#include "qpid/messaging/MessageImpl.h" + +namespace qpid { +namespace messaging { + +Message::Message(const std::string& bytes) : impl(new MessageImpl(bytes)) {} +Message::Message(const char* bytes, size_t count) : impl(new MessageImpl(bytes, count)) {} + +Message::Message(const Message& m) : impl(new MessageImpl(*m.impl)) {} +Message::~Message() { delete impl; } + +Message& Message::operator=(const Message& m) { *impl = *m.impl; return *this; } + +void Message::setReplyTo(const Address& d) { impl->setReplyTo(d); } +const Address& Message::getReplyTo() const { return impl->getReplyTo(); } + +void Message::setSubject(const std::string& s) { impl->setSubject(s); } +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(); } + +const VariantMap& Message::getHeaders() const { return impl->getHeaders(); } +VariantMap& Message::getHeaders() { return impl->getHeaders(); } + +void Message::setContent(const std::string& c) { impl->setBytes(c); } +void Message::setContent(const char* chars, size_t count) { impl->setBytes(chars, count); } +const std::string& Message::getContent() const { return impl->getBytes(); } +std::string& Message::getContent() { return impl->getBytes(); } + +void Message::getContent(std::pair<const char*, size_t>& content) const +{ + content.first = impl->getBytes().data(); + content.second = impl->getBytes().size(); +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/MessageImpl.cpp b/cpp/src/qpid/messaging/MessageImpl.cpp new file mode 100644 index 0000000000..e17fccd64f --- /dev/null +++ b/cpp/src/qpid/messaging/MessageImpl.cpp @@ -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. + * + */ +#include "MessageImpl.h" +#include "qpid/messaging/Message.h" + +namespace qpid { +namespace messaging { + +namespace { +const std::string EMPTY_STRING = ""; +} + +MessageImpl::MessageImpl(const std::string& c) : bytes(c), internalId(0) {} +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::setSubject(const std::string& s) { subject = s; } +const std::string& MessageImpl::getSubject() const { return subject; } + +void MessageImpl::setContentType(const std::string& s) { contentType = s; } +const std::string& MessageImpl::getContentType() const { return contentType; } + +const VariantMap& MessageImpl::getHeaders() const { return headers; } +VariantMap& MessageImpl::getHeaders() { return headers; } + +//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::setInternalId(qpid::framing::SequenceNumber i) { internalId = i; } +qpid::framing::SequenceNumber MessageImpl::getInternalId() { return internalId; } + +MessageImpl& MessageImplAccess::get(Message& msg) +{ + return *msg.impl; +} +const MessageImpl& MessageImplAccess::get(const Message& msg) +{ + return *msg.impl; +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/MessageImpl.h b/cpp/src/qpid/messaging/MessageImpl.h new file mode 100644 index 0000000000..4939cdc5cc --- /dev/null +++ b/cpp/src/qpid/messaging/MessageImpl.h @@ -0,0 +1,82 @@ +#ifndef QPID_MESSAGING_MESSAGEIMPL_H +#define QPID_MESSAGING_MESSAGEIMPL_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 "qpid/messaging/Variant.h" +#include "qpid/framing/SequenceNumber.h" + +namespace qpid { +namespace messaging { + +struct MessageImpl +{ + Address replyTo; + std::string subject; + std::string contentType; + Variant::Map headers; + + std::string bytes; + + qpid::framing::SequenceNumber internalId; + + 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; + + const Variant::Map& getHeaders() const; + Variant::Map& getHeaders(); + + void setBytes(const std::string& bytes); + void setBytes(const char* chars, size_t count); + const std::string& getBytes() const; + std::string& getBytes(); + + void setInternalId(qpid::framing::SequenceNumber id); + qpid::framing::SequenceNumber getInternalId(); + +}; + +class Message; + +/** + * Provides access to the internal MessageImpl for a message which is + * useful when accessing any message state not exposed directly + * through the public API. + */ +struct MessageImplAccess +{ + static MessageImpl& get(Message&); + static const MessageImpl& get(const Message&); +}; + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_MESSAGEIMPL_H*/ diff --git a/cpp/src/qpid/messaging/Receiver.cpp b/cpp/src/qpid/messaging/Receiver.cpp new file mode 100644 index 0000000000..bf9c056db8 --- /dev/null +++ b/cpp/src/qpid/messaging/Receiver.cpp @@ -0,0 +1,53 @@ +/* + * + * 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/Receiver.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/ReceiverImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<qpid::messaging::Receiver> PI; + +} + +namespace messaging { + +using qpid::client::PI; + +Receiver::Receiver(ReceiverImpl* impl) { PI::ctor(*this, impl); } +Receiver::Receiver(const Receiver& s) : qpid::client::Handle<ReceiverImpl>() { PI::copy(*this, s); } +Receiver::~Receiver() { PI::dtor(*this); } +Receiver& Receiver::operator=(const Receiver& s) { return PI::assign(*this, s); } +bool Receiver::get(Message& message, qpid::sys::Duration timeout) { return impl->get(message, timeout); } +Message Receiver::get(qpid::sys::Duration timeout) { return impl->get(timeout); } +bool Receiver::fetch(Message& message, qpid::sys::Duration timeout) { return impl->fetch(message, timeout); } +Message Receiver::fetch(qpid::sys::Duration timeout) { return impl->fetch(timeout); } +void Receiver::setCapacity(uint32_t c) { impl->setCapacity(c); } +uint32_t Receiver::getCapacity() { return impl->getCapacity(); } +uint32_t Receiver::available() { return impl->available(); } +uint32_t Receiver::pendingAck() { return impl->pendingAck(); } +void Receiver::cancel() { impl->cancel(); } +const std::string& Receiver::getName() const { return impl->getName(); } +Session Receiver::getSession() const { return impl->getSession(); } +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/ReceiverImpl.h b/cpp/src/qpid/messaging/ReceiverImpl.h new file mode 100644 index 0000000000..447a505518 --- /dev/null +++ b/cpp/src/qpid/messaging/ReceiverImpl.h @@ -0,0 +1,55 @@ +#ifndef QPID_MESSAGING_RECEIVERIMPL_H +#define QPID_MESSAGING_RECEIVERIMPL_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/RefCounted.h" +#include "qpid/sys/Time.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +class Message; +class MessageListener; +class Session; + +class ReceiverImpl : public virtual qpid::RefCounted +{ + public: + virtual ~ReceiverImpl() {} + virtual bool get(Message& message, qpid::sys::Duration timeout) = 0; + virtual Message get(qpid::sys::Duration timeout) = 0; + virtual bool fetch(Message& message, qpid::sys::Duration timeout) = 0; + virtual Message fetch(qpid::sys::Duration timeout) = 0; + virtual void setCapacity(uint32_t) = 0; + virtual uint32_t getCapacity() = 0; + virtual uint32_t available() = 0; + virtual uint32_t pendingAck() = 0; + virtual void cancel() = 0; + virtual const std::string& getName() const = 0; + virtual Session getSession() const = 0; +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_RECEIVERIMPL_H*/ diff --git a/cpp/src/qpid/messaging/Sender.cpp b/cpp/src/qpid/messaging/Sender.cpp new file mode 100644 index 0000000000..f2303f4126 --- /dev/null +++ b/cpp/src/qpid/messaging/Sender.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/Sender.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/SenderImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<qpid::messaging::Sender> PI; + +} + +namespace messaging { + +using qpid::client::PI; + +Sender::Sender(SenderImpl* impl) { PI::ctor(*this, impl); } +Sender::Sender(const Sender& s) : qpid::client::Handle<SenderImpl>() { PI::copy(*this, s); } +Sender::~Sender() { PI::dtor(*this); } +Sender& Sender::operator=(const Sender& s) { return PI::assign(*this, s); } +void Sender::send(const Message& message) { impl->send(message); } +void Sender::cancel() { impl->cancel(); } +void Sender::setCapacity(uint32_t c) { impl->setCapacity(c); } +uint32_t Sender::getCapacity() { return impl->getCapacity(); } +uint32_t Sender::pending() { return impl->pending(); } +const std::string& Sender::getName() const { return impl->getName(); } +Session Sender::getSession() const { return impl->getSession(); } + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/client/MessageListener.h b/cpp/src/qpid/messaging/SenderImpl.h index 76e1d17445..5f30417f6a 100644 --- a/cpp/src/qpid/client/MessageListener.h +++ b/cpp/src/qpid/messaging/SenderImpl.h @@ -1,3 +1,6 @@ +#ifndef QPID_MESSAGING_SENDERIMPL_H +#define QPID_MESSAGING_SENDERIMPL_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -18,36 +21,30 @@ * under the License. * */ -#include <string> - -#ifndef _MessageListener_ -#define _MessageListener_ - -#include "Message.h" +#include "qpid/RefCounted.h" namespace qpid { namespace client { - - /** - * Implement a subclass of MessageListener and subscribe it using - * the SubscriptionManager to receive messages. - * - * Another way to receive messages is by using a LocalQueue. - * - * \ingroup clientapi - */ - class MessageListener{ - public: - virtual ~MessageListener(); - - /** Called for each message arriving from the broker. Override - * in your own subclass to process messages. - */ - virtual void received(Message& msg) = 0; - }; - } -} - -#endif +namespace messaging { + +class Message; +class Session; + +class SenderImpl : public virtual qpid::RefCounted +{ + public: + virtual ~SenderImpl() {} + virtual void send(const Message& message) = 0; + virtual void cancel() = 0; + virtual void setCapacity(uint32_t) = 0; + virtual uint32_t getCapacity() = 0; + virtual uint32_t pending() = 0; + virtual const std::string& getName() const = 0; + virtual Session getSession() const = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_SENDERIMPL_H*/ diff --git a/cpp/src/qpid/messaging/Session.cpp b/cpp/src/qpid/messaging/Session.cpp new file mode 100644 index 0000000000..99896caad4 --- /dev/null +++ b/cpp/src/qpid/messaging/Session.cpp @@ -0,0 +1,109 @@ +/* + * + * 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/Session.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/SessionImpl.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<qpid::messaging::Session> PI; + +} + +namespace messaging { + +using qpid::client::PI; + +Session::Session(SessionImpl* impl) { PI::ctor(*this, impl); } +Session::Session(const Session& s) : qpid::client::Handle<SessionImpl>() { PI::copy(*this, s); } +Session::~Session() { PI::dtor(*this); } +Session& Session::operator=(const Session& s) { return PI::assign(*this, s); } +void Session::commit() { impl->commit(); } +void Session::rollback() { impl->rollback(); } +void Session::acknowledge() { impl->acknowledge(); } +void Session::reject(Message& m) { impl->reject(m); } +void Session::close() { impl->close(); } + +Sender Session::createSender(const Address& address) +{ + return impl->createSender(address); +} +Receiver Session::createReceiver(const Address& address) +{ + return impl->createReceiver(address); +} + +Sender Session::createSender(const std::string& address) +{ + return impl->createSender(Address(address)); +} +Receiver Session::createReceiver(const std::string& address) +{ + return impl->createReceiver(Address(address)); +} + +void Session::sync() +{ + impl->sync(); +} + +void Session::flush() +{ + impl->flush(); +} + +bool Session::nextReceiver(Receiver& receiver, qpid::sys::Duration timeout) +{ + return impl->nextReceiver(receiver, timeout); +} + + +Receiver Session::nextReceiver(qpid::sys::Duration timeout) +{ + return impl->nextReceiver(timeout); +} + +uint32_t Session::available() { return impl->available(); } +uint32_t Session::pendingAck() { return impl->pendingAck(); } + +Sender Session::getSender(const std::string& name) const +{ + return impl->getSender(name); +} +Receiver Session::getReceiver(const std::string& name) const +{ + return impl->getReceiver(name); +} + +Connection Session::getConnection() const +{ + return impl->getConnection(); +} + +KeyError::KeyError(const std::string& msg) : Exception(msg) {} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/messaging/SessionImpl.h b/cpp/src/qpid/messaging/SessionImpl.h new file mode 100644 index 0000000000..164a6f6bc9 --- /dev/null +++ b/cpp/src/qpid/messaging/SessionImpl.h @@ -0,0 +1,64 @@ +#ifndef QPID_MESSAGING_SESSIONIMPL_H +#define QPID_MESSAGING_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 "qpid/RefCounted.h" +#include <string> +#include "qpid/sys/Time.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +class Address; +class Connection; +class Message; +class Sender; +class Receiver; + +class SessionImpl : public virtual qpid::RefCounted +{ + public: + virtual ~SessionImpl() {} + virtual void commit() = 0; + virtual void rollback() = 0; + virtual void acknowledge() = 0; + virtual void reject(Message&) = 0; + virtual void close() = 0; + virtual void sync() = 0; + virtual void flush() = 0; + virtual Sender createSender(const Address& address) = 0; + virtual Receiver createReceiver(const Address& address) = 0; + virtual bool nextReceiver(Receiver& receiver, qpid::sys::Duration timeout) = 0; + virtual Receiver nextReceiver(qpid::sys::Duration timeout) = 0; + virtual uint32_t available() = 0; + virtual uint32_t pendingAck() = 0; + virtual Sender getSender(const std::string& name) const = 0; + virtual Receiver getReceiver(const std::string& name) const = 0; + virtual Connection getConnection() const = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_SESSIONIMPL_H*/ diff --git a/cpp/src/qpid/messaging/Variant.cpp b/cpp/src/qpid/messaging/Variant.cpp new file mode 100644 index 0000000000..9c2f92f47a --- /dev/null +++ b/cpp/src/qpid/messaging/Variant.cpp @@ -0,0 +1,659 @@ +/* + * + * 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/Variant.h" +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <algorithm> +#include <sstream> + +namespace qpid { +namespace messaging { + +InvalidConversion::InvalidConversion(const std::string& msg) : Exception(msg) {} + + +namespace { +std::string EMPTY; +} + +class VariantImpl +{ + public: + VariantImpl(); + VariantImpl(bool); + VariantImpl(uint8_t); + VariantImpl(uint16_t); + VariantImpl(uint32_t); + VariantImpl(uint64_t); + VariantImpl(int8_t); + VariantImpl(int16_t); + VariantImpl(int32_t); + VariantImpl(int64_t); + VariantImpl(float); + VariantImpl(double); + VariantImpl(const std::string&); + VariantImpl(const Variant::Map&); + VariantImpl(const Variant::List&); + ~VariantImpl(); + + VariantType getType() const; + + bool asBool() const; + uint8_t asUint8() const; + uint16_t asUint16() const; + uint32_t asUint32() const; + uint64_t asUint64() const; + int8_t asInt8() const; + int16_t asInt16() const; + int32_t asInt32() const; + int64_t asInt64() const; + float asFloat() const; + double asDouble() const; + std::string asString() const; + + const Variant::Map& asMap() const; + Variant::Map& asMap(); + const Variant::List& asList() const; + Variant::List& asList(); + + const std::string& getString() const; + std::string& getString(); + + void setEncoding(const std::string&); + const std::string& getEncoding() const; + + bool isEqualTo(VariantImpl&) const; + bool isEquivalentTo(VariantImpl&) const; + + static VariantImpl* create(const Variant&); + private: + const VariantType type; + union { + bool b; + uint8_t ui8; + uint16_t ui16; + uint32_t ui32; + uint64_t ui64; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + float f; + double d; + void* v;//variable width data + } value; + std::string encoding;//optional encoding for variable length data + + std::string getTypeName(VariantType type) const; + template<class T> T convertFromString() const + { + std::string* s = reinterpret_cast<std::string*>(value.v); + try { + return boost::lexical_cast<T>(*s); + } catch(const boost::bad_lexical_cast&) { + throw InvalidConversion(QPID_MSG("Cannot convert " << *s)); + } + } +}; + + +VariantImpl::VariantImpl() : type(VAR_VOID) { value.i64 = 0; } +VariantImpl::VariantImpl(bool b) : type(VAR_BOOL) { value.b = b; } +VariantImpl::VariantImpl(uint8_t i) : type(VAR_UINT8) { value.ui8 = i; } +VariantImpl::VariantImpl(uint16_t i) : type(VAR_UINT16) { value.ui16 = i; } +VariantImpl::VariantImpl(uint32_t i) : type(VAR_UINT32) { value.ui32 = i; } +VariantImpl::VariantImpl(uint64_t i) : type(VAR_UINT64) { value.ui64 = i; } +VariantImpl::VariantImpl(int8_t i) : type(VAR_INT8) { value.i8 = i; } +VariantImpl::VariantImpl(int16_t i) : type(VAR_INT16) { value.i16 = i; } +VariantImpl::VariantImpl(int32_t i) : type(VAR_INT32) { value.i32 = i; } +VariantImpl::VariantImpl(int64_t i) : type(VAR_INT64) { value.i64 = i; } +VariantImpl::VariantImpl(float f) : type(VAR_FLOAT) { value.f = f; } +VariantImpl::VariantImpl(double d) : type(VAR_DOUBLE) { value.d = d; } +VariantImpl::VariantImpl(const std::string& s) : type(VAR_STRING) { value.v = new std::string(s); } +VariantImpl::VariantImpl(const Variant::Map& m) : type(VAR_MAP) { value.v = new Variant::Map(m); } +VariantImpl::VariantImpl(const Variant::List& l) : type(VAR_LIST) { value.v = new Variant::List(l); } + +VariantImpl::~VariantImpl() { + switch (type) { + case VAR_STRING: + delete reinterpret_cast<std::string*>(value.v); + break; + case VAR_MAP: + delete reinterpret_cast<Variant::Map*>(value.v); + break; + case VAR_LIST: + delete reinterpret_cast<Variant::List*>(value.v); + break; + default: + break; + } +} + +VariantType VariantImpl::getType() const { return type; } + +namespace { + +bool same_char(char a, char b) +{ + return toupper(a) == toupper(b); +} + +bool caseInsensitiveMatch(const std::string& a, const std::string& b) +{ + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), &same_char); +} + +const std::string TRUE("True"); +const std::string FALSE("False"); + +bool toBool(const std::string& s) +{ + if (caseInsensitiveMatch(s, TRUE)) return true; + if (caseInsensitiveMatch(s, FALSE)) return false; + try { return boost::lexical_cast<int>(s); } catch(const boost::bad_lexical_cast&) {} + throw InvalidConversion(QPID_MSG("Cannot convert " << s << " to bool")); +} + +template <class T> std::string toString(const T& t) +{ + std::stringstream out; + out << t; + return out.str(); +} + +template <class T> bool equal(const T& a, const T& b) +{ + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); +} + +} + +bool VariantImpl::asBool() const +{ + switch(type) { + case VAR_VOID: return false; + case VAR_BOOL: return value.b; + case VAR_UINT8: return value.ui8; + case VAR_UINT16: return value.ui16; + case VAR_UINT32: return value.ui32; + case VAR_UINT64: return value.ui64; + case VAR_INT8: return value.i8; + case VAR_INT16: return value.i16; + case VAR_INT32: return value.i32; + case VAR_INT64: return value.i64; + case VAR_STRING: return toBool(*reinterpret_cast<std::string*>(value.v)); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_BOOL))); + } +} +uint8_t VariantImpl::asUint8() const +{ + switch(type) { + case VAR_UINT8: return value.ui8; + case VAR_STRING: return convertFromString<uint8_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT8))); + } +} +uint16_t VariantImpl::asUint16() const +{ + switch(type) { + case VAR_UINT8: return value.ui8; + case VAR_UINT16: return value.ui16; + case VAR_STRING: return convertFromString<uint16_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT16))); + } +} +uint32_t VariantImpl::asUint32() const +{ + switch(type) { + case VAR_UINT8: return value.ui8; + case VAR_UINT16: return value.ui16; + case VAR_UINT32: return value.ui32; + case VAR_STRING: return convertFromString<uint32_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT32))); + } +} +uint64_t VariantImpl::asUint64() const +{ + switch(type) { + case VAR_UINT8: return value.ui8; + case VAR_UINT16: return value.ui16; + case VAR_UINT32: return value.ui32; + case VAR_UINT64: return value.ui64; + case VAR_STRING: return convertFromString<uint64_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT64))); + } +} +int8_t VariantImpl::asInt8() const +{ + switch(type) { + case VAR_INT8: return value.i8; + case VAR_STRING: return convertFromString<int8_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT8))); + } +} +int16_t VariantImpl::asInt16() const +{ + switch(type) { + case VAR_INT8: return value.i8; + case VAR_INT16: return value.i16; + case VAR_STRING: return convertFromString<int16_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT16))); + } +} +int32_t VariantImpl::asInt32() const +{ + switch(type) { + case VAR_INT8: return value.i8; + case VAR_INT16: return value.i16; + case VAR_INT32: return value.i32; + case VAR_STRING: return convertFromString<int32_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT32))); + } +} +int64_t VariantImpl::asInt64() const +{ + switch(type) { + case VAR_INT8: return value.i8; + case VAR_INT16: return value.i16; + case VAR_INT32: return value.i32; + case VAR_INT64: return value.i64; + case VAR_STRING: return convertFromString<int64_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT64))); + } +} +float VariantImpl::asFloat() const +{ + switch(type) { + case VAR_FLOAT: return value.f; + case VAR_STRING: return convertFromString<float>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_FLOAT))); + } +} +double VariantImpl::asDouble() const +{ + switch(type) { + case VAR_FLOAT: return value.f; + case VAR_DOUBLE: return value.d; + case VAR_STRING: return convertFromString<double>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_DOUBLE))); + } +} +std::string VariantImpl::asString() const +{ + switch(type) { + case VAR_VOID: return EMPTY; + case VAR_BOOL: return value.b ? TRUE : FALSE; + case VAR_UINT8: return boost::lexical_cast<std::string>((int) value.ui8); + case VAR_UINT16: return boost::lexical_cast<std::string>(value.ui16); + case VAR_UINT32: return boost::lexical_cast<std::string>(value.ui32); + case VAR_UINT64: return boost::lexical_cast<std::string>(value.ui64); + case VAR_INT8: return boost::lexical_cast<std::string>((int) value.i8); + case VAR_INT16: return boost::lexical_cast<std::string>(value.i16); + case VAR_INT32: return boost::lexical_cast<std::string>(value.i32); + case VAR_INT64: return boost::lexical_cast<std::string>(value.i64); + case VAR_DOUBLE: return boost::lexical_cast<std::string>(value.d); + case VAR_FLOAT: return boost::lexical_cast<std::string>(value.f); + case VAR_STRING: return *reinterpret_cast<std::string*>(value.v); + case VAR_LIST: return toString(asList()); + case VAR_MAP: return toString(asMap()); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_STRING))); + } +} + +bool VariantImpl::isEqualTo(VariantImpl& other) const +{ + if (type == other.type) { + switch(type) { + case VAR_VOID: return true; + case VAR_BOOL: return value.b == other.value.b; + case VAR_UINT8: return value.ui8 == other.value.ui8; + case VAR_UINT16: return value.ui16 == other.value.ui16; + case VAR_UINT32: return value.ui32 == other.value.ui32; + case VAR_UINT64: return value.ui64 == other.value.ui64; + case VAR_INT8: return value.i8 == other.value.i8; + case VAR_INT16: return value.i16 == other.value.i16; + case VAR_INT32: return value.i32 == other.value.i32; + case VAR_INT64: return value.i64 == other.value.i64; + case VAR_DOUBLE: return value.d == other.value.d; + case VAR_FLOAT: return value.f == other.value.f; + case VAR_STRING: return *reinterpret_cast<std::string*>(value.v) + == *reinterpret_cast<std::string*>(other.value.v); + case VAR_LIST: return equal(asList(), other.asList()); + case VAR_MAP: return equal(asMap(), other.asMap()); + } + } + return false; +} + +const Variant::Map& VariantImpl::asMap() const +{ + switch(type) { + case VAR_MAP: return *reinterpret_cast<Variant::Map*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_MAP))); + } +} + +Variant::Map& VariantImpl::asMap() +{ + switch(type) { + case VAR_MAP: return *reinterpret_cast<Variant::Map*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_MAP))); + } +} + +const Variant::List& VariantImpl::asList() const +{ + switch(type) { + case VAR_LIST: return *reinterpret_cast<Variant::List*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_LIST))); + } +} + +Variant::List& VariantImpl::asList() +{ + switch(type) { + case VAR_LIST: return *reinterpret_cast<Variant::List*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_LIST))); + } +} + +std::string& VariantImpl::getString() +{ + switch(type) { + case VAR_STRING: return *reinterpret_cast<std::string*>(value.v); + default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required.")); + } +} + +const std::string& VariantImpl::getString() const +{ + switch(type) { + case VAR_STRING: return *reinterpret_cast<std::string*>(value.v); + default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required.")); + } +} + +void VariantImpl::setEncoding(const std::string& s) { encoding = s; } +const std::string& VariantImpl::getEncoding() const { return encoding; } + +std::string VariantImpl::getTypeName(VariantType type) const +{ + switch (type) { + case VAR_VOID: return "void"; + case VAR_BOOL: return "bool"; + case VAR_UINT8: return "uint8"; + case VAR_UINT16: return "uint16"; + case VAR_UINT32: return "uint32"; + case VAR_UINT64: return "uint64"; + case VAR_INT8: return "int8"; + case VAR_INT16: return "int16"; + case VAR_INT32: return "int32"; + case VAR_INT64: return "int64"; + case VAR_FLOAT: return "float"; + case VAR_DOUBLE: return "double"; + case VAR_STRING: return "string"; + case VAR_MAP: return "map"; + case VAR_LIST: return "list"; + } + return "<unknown>";//should never happen +} + +VariantImpl* VariantImpl::create(const Variant& v) +{ + switch (v.getType()) { + case VAR_BOOL: return new VariantImpl(v.asBool()); + case VAR_UINT8: return new VariantImpl(v.asUint8()); + case VAR_UINT16: return new VariantImpl(v.asUint16()); + case VAR_UINT32: return new VariantImpl(v.asUint32()); + case VAR_UINT64: return new VariantImpl(v.asUint64()); + case VAR_INT8: return new VariantImpl(v.asInt8()); + case VAR_INT16: return new VariantImpl(v.asInt16()); + case VAR_INT32: return new VariantImpl(v.asInt32()); + case VAR_INT64: return new VariantImpl(v.asInt64()); + case VAR_FLOAT: return new VariantImpl(v.asFloat()); + case VAR_DOUBLE: return new VariantImpl(v.asDouble()); + case VAR_STRING: return new VariantImpl(v.asString()); + case VAR_MAP: return new VariantImpl(v.asMap()); + case VAR_LIST: return new VariantImpl(v.asList()); + default: return new VariantImpl(); + } +} + +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)) {} +Variant::Variant(uint32_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(uint64_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int8_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int16_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int32_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int64_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(float f) : impl(new VariantImpl(f)) {} +Variant::Variant(double d) : impl(new VariantImpl(d)) {} +Variant::Variant(const std::string& s) : impl(new VariantImpl(s)) {} +Variant::Variant(const char* s) : impl(new VariantImpl(std::string(s))) {} +Variant::Variant(const Map& m) : impl(new VariantImpl(m)) {} +Variant::Variant(const List& l) : impl(new VariantImpl(l)) {} +Variant::Variant(const Variant& v) : impl(VariantImpl::create(v)) {} + +Variant::~Variant() { if (impl) delete impl; } + +void Variant::reset() +{ + if (impl) delete impl; + impl = new VariantImpl(); +} + + +Variant& Variant::operator=(bool b) +{ + if (impl) delete impl; + impl = new VariantImpl(b); + return *this; +} + +Variant& Variant::operator=(uint8_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(uint16_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(uint32_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(uint64_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} + +Variant& Variant::operator=(int8_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(int16_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(int32_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(int64_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} + +Variant& Variant::operator=(float f) +{ + if (impl) delete impl; + impl = new VariantImpl(f); + return *this; +} +Variant& Variant::operator=(double d) +{ + if (impl) delete impl; + impl = new VariantImpl(d); + return *this; +} + +Variant& Variant::operator=(const std::string& s) +{ + if (impl) delete impl; + impl = new VariantImpl(s); + return *this; +} + +Variant& Variant::operator=(const char* s) +{ + if (impl) delete impl; + impl = new VariantImpl(std::string(s)); + return *this; +} + +Variant& Variant::operator=(const Map& m) +{ + if (impl) delete impl; + impl = new VariantImpl(m); + return *this; +} + +Variant& Variant::operator=(const List& l) +{ + if (impl) delete impl; + impl = new VariantImpl(l); + return *this; +} + +Variant& Variant::operator=(const Variant& v) +{ + if (impl) delete impl; + impl = VariantImpl::create(v); + return *this; +} + +VariantType Variant::getType() const { return impl->getType(); } +bool Variant::isVoid() const { return impl->getType() == VAR_VOID; } +bool Variant::asBool() const { return impl->asBool(); } +uint8_t Variant::asUint8() const { return impl->asUint8(); } +uint16_t Variant::asUint16() const { return impl->asUint16(); } +uint32_t Variant::asUint32() const { return impl->asUint32(); } +uint64_t Variant::asUint64() const { return impl->asUint64(); } +int8_t Variant::asInt8() const { return impl->asInt8(); } +int16_t Variant::asInt16() const { return impl->asInt16(); } +int32_t Variant::asInt32() const { return impl->asInt32(); } +int64_t Variant::asInt64() const { return impl->asInt64(); } +float Variant::asFloat() const { return impl->asFloat(); } +double Variant::asDouble() const { return impl->asDouble(); } +std::string Variant::asString() const { return impl->asString(); } +const Variant::Map& Variant::asMap() const { return impl->asMap(); } +Variant::Map& Variant::asMap() { return impl->asMap(); } +const Variant::List& Variant::asList() const { return impl->asList(); } +Variant::List& Variant::asList() { return impl->asList(); } +const std::string& Variant::getString() const { return impl->getString(); } +std::string& Variant::getString() { return impl->getString(); } +void Variant::setEncoding(const std::string& s) { impl->setEncoding(s); } +const std::string& Variant::getEncoding() const { return impl->getEncoding(); } + +Variant::operator bool() const { return asBool(); } +Variant::operator uint8_t() const { return asUint8(); } +Variant::operator uint16_t() const { return asUint16(); } +Variant::operator uint32_t() const { return asUint32(); } +Variant::operator uint64_t() const { return asUint64(); } +Variant::operator int8_t() const { return asInt8(); } +Variant::operator int16_t() const { return asInt16(); } +Variant::operator int32_t() const { return asInt32(); } +Variant::operator int64_t() const { return asInt64(); } +Variant::operator float() const { return asFloat(); } +Variant::operator double() const { return asDouble(); } +Variant::operator const char*() const { return asString().c_str(); } + +std::ostream& operator<<(std::ostream& out, const Variant::Map& map) +{ + out << "{"; + for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) { + if (i != map.begin()) out << ", "; + out << i->first << ":" << i->second; + } + out << "}"; + return out; +} + +std::ostream& operator<<(std::ostream& out, const Variant::List& list) +{ + out << "["; + for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) { + if (i != list.begin()) out << ", "; + out << *i; + } + out << "]"; + return out; +} + +std::ostream& operator<<(std::ostream& out, const Variant& value) +{ + switch (value.getType()) { + case VAR_MAP: + out << value.asMap(); + break; + case VAR_LIST: + out << value.asList(); + break; + case VAR_VOID: + out << "<void>"; + break; + default: + out << value.asString(); + break; + } + return out; +} + +bool operator==(const Variant& a, const Variant& b) +{ + return a.isEqualTo(b); +} + +bool Variant::isEqualTo(const Variant& other) const +{ + return impl->isEqualTo(*other.impl); +} + +}} // namespace qpid::messaging diff --git a/cpp/src/qpid/replication/ReplicatingEventListener.cpp b/cpp/src/qpid/replication/ReplicatingEventListener.cpp new file mode 100644 index 0000000000..b7d52372f4 --- /dev/null +++ b/cpp/src/qpid/replication/ReplicatingEventListener.cpp @@ -0,0 +1,201 @@ +/* + * + * 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/replication/ReplicatingEventListener.h" +#include "qpid/replication/constants.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace replication { + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::replication::constants; + +void ReplicatingEventListener::handle(QueueEvents::Event event) +{ + switch (event.type) { + case QueueEvents::ENQUEUE: + deliverEnqueueMessage(event.msg); + QPID_LOG(debug, "Queuing 'enqueue' event on " << event.msg.queue->getName() << " for replication"); + break; + case QueueEvents::DEQUEUE: + deliverDequeueMessage(event.msg); + QPID_LOG(debug, "Queuing 'dequeue' event from " << event.msg.queue->getName() << " for replication, (from position " + << event.msg.position << ")"); + break; + } +} + +namespace { +const std::string EMPTY; +} + +void ReplicatingEventListener::deliverDequeueMessage(const QueuedMessage& dequeued) +{ + FieldTable headers; + headers.setString(REPLICATION_TARGET_QUEUE, dequeued.queue->getName()); + headers.setInt(REPLICATION_EVENT_TYPE, DEQUEUE); + headers.setInt(DEQUEUED_MESSAGE_POSITION, dequeued.position); + boost::intrusive_ptr<Message> msg(createMessage(headers)); + DeliveryProperties* props = msg->getFrames().getHeaders()->get<DeliveryProperties>(true); + props->setRoutingKey(dequeued.queue->getName()); + route(msg); +} + +void ReplicatingEventListener::deliverEnqueueMessage(const QueuedMessage& enqueued) +{ + boost::intrusive_ptr<Message> msg(cloneMessage(*(enqueued.queue), enqueued.payload)); + FieldTable& headers = msg->getProperties<MessageProperties>()->getApplicationHeaders(); + headers.setString(REPLICATION_TARGET_QUEUE, enqueued.queue->getName()); + headers.setInt(REPLICATION_EVENT_TYPE, ENQUEUE); + headers.setInt(QUEUE_MESSAGE_POSITION,enqueued.position); + route(msg); +} + +void ReplicatingEventListener::route(boost::intrusive_ptr<qpid::broker::Message> msg) +{ + try { + if (exchange) { + DeliverableMessage deliverable(msg); + exchange->route(deliverable, msg->getRoutingKey(), msg->getApplicationHeaders()); + } else if (queue) { + queue->deliver(msg); + } else { + QPID_LOG(error, "Cannot route replication event, neither replication queue nor exchange configured"); + } + } catch (const std::exception& e) { + QPID_LOG(error, "Error enqueing replication event: " << e.what()); + } +} + + +boost::intrusive_ptr<Message> ReplicatingEventListener::createMessage(const FieldTable& headers) +{ + boost::intrusive_ptr<Message> msg(new Message()); + AMQFrame method((MessageTransferBody(ProtocolVersion(), EMPTY, 0, 0))); + AMQFrame header((AMQHeaderBody())); + header.setBof(false); + header.setEof(true); + header.setBos(true); + header.setEos(true); + msg->getFrames().append(method); + msg->getFrames().append(header); + MessageProperties* props = msg->getFrames().getHeaders()->get<MessageProperties>(true); + props->setApplicationHeaders(headers); + return msg; +} + +struct AppendingHandler : FrameHandler +{ + boost::intrusive_ptr<Message> msg; + + AppendingHandler(boost::intrusive_ptr<Message> m) : msg(m) {} + + void handle(AMQFrame& f) + { + msg->getFrames().append(f); + } +}; + +boost::intrusive_ptr<Message> ReplicatingEventListener::cloneMessage(Queue& queue, boost::intrusive_ptr<Message> original) +{ + boost::intrusive_ptr<Message> copy(new Message()); + AMQFrame method((MessageTransferBody(ProtocolVersion(), EMPTY, 0, 0))); + AppendingHandler handler(copy); + handler.handle(method); + + //To avoid modifying original headers, create new frame with + //cloned body: + AMQFrame header(*original->getFrames().getHeaders()); + header.setBof(false); + header.setEof(!original->getFrames().getContentSize());//if there is any content then the header is not the end of the frameset + header.setBos(true); + header.setEos(true); + handler.handle(header); + + original->sendContent(queue, handler, std::numeric_limits<int16_t>::max()); + return copy; +} + +Options* ReplicatingEventListener::getOptions() +{ + return &options; +} + +void ReplicatingEventListener::initialize(Plugin::Target& target) +{ + Broker* broker = dynamic_cast<broker::Broker*>(&target); + if (broker) { + broker->addFinalizer(boost::bind(&ReplicatingEventListener::shutdown, this)); + if (!options.exchange.empty()) { + if (!options.queue.empty()) { + QPID_LOG(warning, "Replication queue option ignored as replication exchange has been specified"); + } + try { + exchange = broker->getExchanges().declare(options.exchange, options.exchangeType).first; + } catch (const UnknownExchangeTypeException&) { + QPID_LOG(error, "Replication disabled due to invalid type: " << options.exchangeType); + } + } else if (!options.queue.empty()) { + if (options.createQueue) { + queue = broker->getQueues().declare(options.queue).first; + } else { + queue = broker->getQueues().find(options.queue); + } + if (queue) { + queue->insertSequenceNumbers(REPLICATION_EVENT_SEQNO); + } else { + QPID_LOG(error, "Replication queue named '" << options.queue << "' does not exist; replication plugin disabled."); + } + } + if (queue || exchange) { + QueueEvents::EventListener callback = boost::bind(&ReplicatingEventListener::handle, this, _1); + broker->getQueueEvents().registerListener(options.name, callback); + QPID_LOG(info, "Registered replicating queue event listener"); + } + } +} + +void ReplicatingEventListener::earlyInitialize(Target&) {} +void ReplicatingEventListener::shutdown() { queue.reset(); exchange.reset(); } + +ReplicatingEventListener::PluginOptions::PluginOptions() : Options("Queue Replication Options"), + exchangeType("direct"), + name("replicator"), + createQueue(false) +{ + addOptions() + ("replication-exchange-name", optValue(exchange, "EXCHANGE"), "Exchange to which events for other queues are routed") + ("replication-exchange-type", optValue(exchangeType, "direct|topic etc"), "Type of exchange to use") + ("replication-queue", optValue(queue, "QUEUE"), "Queue on which events for other queues are recorded") + ("replication-listener-name", optValue(name, "NAME"), "name by which to register the replicating event listener") + ("create-replication-queue", optValue(createQueue), "if set, the replication will be created if it does not exist"); +} + +static ReplicatingEventListener plugin; + +}} // namespace qpid::replication diff --git a/cpp/src/qpid/replication/ReplicatingEventListener.h b/cpp/src/qpid/replication/ReplicatingEventListener.h new file mode 100644 index 0000000000..74418d00e6 --- /dev/null +++ b/cpp/src/qpid/replication/ReplicatingEventListener.h @@ -0,0 +1,78 @@ +#ifndef QPID_REPLICATION_REPLICATINGEVENTLISTENER_H +#define QPID_REPLICATION_REPLICATINGEVENTLISTENER_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/Plugin.h" +#include "qpid/Options.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/SequenceNumber.h" + +namespace qpid { +namespace replication { + +/** + * An event listener plugin that records queue events as messages on a + * replication queue, from where they can be consumed (e.g. by an + * inter-broker link to the corresponding QueueReplicationExchange + * plugin. + */ +class ReplicatingEventListener : public Plugin +{ + public: + Options* getOptions(); + void earlyInitialize(Plugin::Target& target); + void initialize(Plugin::Target& target); + void handle(qpid::broker::QueueEvents::Event); + private: + struct PluginOptions : public Options + { + std::string queue; + std::string exchange; + std::string exchangeType; + std::string name; + bool createQueue; + + PluginOptions(); + }; + + PluginOptions options; + qpid::broker::Queue::shared_ptr queue; + qpid::broker::Exchange::shared_ptr exchange; + + void deliverDequeueMessage(const qpid::broker::QueuedMessage& enqueued); + void deliverEnqueueMessage(const qpid::broker::QueuedMessage& enqueued); + void route(boost::intrusive_ptr<qpid::broker::Message>); + void shutdown(); + + boost::intrusive_ptr<qpid::broker::Message> createMessage(const qpid::framing::FieldTable& headers); + boost::intrusive_ptr<qpid::broker::Message> cloneMessage(qpid::broker::Queue& queue, + boost::intrusive_ptr<qpid::broker::Message> original); +}; + +}} // namespace qpid::replication + +#endif /*!QPID_REPLICATION_REPLICATINGEVENTLISTENER_H*/ diff --git a/cpp/src/qpid/replication/ReplicationExchange.cpp b/cpp/src/qpid/replication/ReplicationExchange.cpp new file mode 100644 index 0000000000..b5911bb71e --- /dev/null +++ b/cpp/src/qpid/replication/ReplicationExchange.cpp @@ -0,0 +1,232 @@ +/* + * + * 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/replication/ReplicationExchange.h" +#include "qpid/replication/constants.h" +#include "qpid/Plugin.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace replication { + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::replication::constants; + +const std::string SEQUENCE_VALUE("qpid.replication-event.sequence"); +ReplicationExchange::ReplicationExchange(const std::string& name, bool durable, + const FieldTable& _args, + QueueRegistry& qr, + Manageable* parent, Broker* broker) + : Exchange(name, durable, _args, parent, broker), queues(qr), sequence(args.getAsInt64(SEQUENCE_VALUE)), init(false) +{ + args.setInt64(SEQUENCE_VALUE, sequence); + if (mgmtExchange != 0) + mgmtExchange->set_type(typeName); +} + +std::string ReplicationExchange::getType() const { return typeName; } + +void ReplicationExchange::route(Deliverable& msg, const std::string& /*routingKey*/, const FieldTable* args) +{ + if (mgmtExchange != 0) { + mgmtExchange->inc_msgReceives(); + mgmtExchange->inc_byteReceives(msg.contentSize()); + } + if (args) { + int eventType = args->getAsInt(REPLICATION_EVENT_TYPE); + if (eventType) { + if (isDuplicate(args)) return; + switch (eventType) { + case ENQUEUE: + handleEnqueueEvent(args, msg); + return; + case DEQUEUE: + handleDequeueEvent(args, msg); + return; + default: + throw IllegalArgumentException(QPID_MSG("Illegal value for " << REPLICATION_EVENT_TYPE << ": " << eventType)); + } + } + } else { + QPID_LOG(warning, "Dropping unexpected message with no headers"); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + } +} + +void ReplicationExchange::handleEnqueueEvent(const FieldTable* args, Deliverable& msg) +{ + std::string queueName = args->getAsString(REPLICATION_TARGET_QUEUE); + Queue::shared_ptr queue = queues.find(queueName); + if (queue) { + + SequenceNumber seqno1(args->getAsInt(QUEUE_MESSAGE_POSITION)); + + // note that queue will ++ before enqueue. + if (queue->getPosition() > --seqno1) // test queue.pos < seqnumber + { + QPID_LOG(error, "Cannot enqueue replicated message. Destination Queue " << queueName << " ahead of source queue"); + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } else { + queue->setPosition(seqno1); + + FieldTable& headers = msg.getMessage().getProperties<MessageProperties>()->getApplicationHeaders(); + headers.erase(REPLICATION_TARGET_QUEUE); + headers.erase(REPLICATION_EVENT_SEQNO); + headers.erase(REPLICATION_EVENT_TYPE); + headers.erase(QUEUE_MESSAGE_POSITION); + msg.deliverTo(queue); + QPID_LOG(debug, "Enqueued replicated message onto " << queueName); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgRoutes(); + mgmtExchange->inc_byteRoutes( msg.contentSize()); + } + } + } else { + QPID_LOG(error, "Cannot enqueue replicated message. Queue " << queueName << " does not exist"); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + } +} + +void ReplicationExchange::handleDequeueEvent(const FieldTable* args, Deliverable& msg) +{ + std::string queueName = args->getAsString(REPLICATION_TARGET_QUEUE); + Queue::shared_ptr queue = queues.find(queueName); + if (queue) { + SequenceNumber position(args->getAsInt(DEQUEUED_MESSAGE_POSITION)); + QueuedMessage dequeued; + if (queue->acquireMessageAt(position, dequeued)) { + queue->dequeue(0, dequeued); + QPID_LOG(debug, "Processed replicated 'dequeue' event from " << queueName << " at position " << position); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgRoutes(); + mgmtExchange->inc_byteRoutes(msg.contentSize()); + } + } else { + QPID_LOG(warning, "Could not acquire message " << position << " from " << queueName); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + } + } else { + QPID_LOG(error, "Cannot process replicated 'dequeue' event. Queue " << queueName << " does not exist"); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + } +} + +bool ReplicationExchange::isDuplicate(const FieldTable* args) +{ + if (!args->get(REPLICATION_EVENT_SEQNO)) return false; + SequenceNumber seqno(args->getAsInt(REPLICATION_EVENT_SEQNO)); + if (!init) { + init = true; + sequence = seqno; + return false; + } else if (seqno > sequence) { + if (seqno - sequence > 1) { + QPID_LOG(error, "Gap in replication event sequence between: " << sequence << " and " << seqno); + } + sequence = seqno; + return false; + } else { + QPID_LOG(info, "Duplicate detected: seqno=" << seqno << " (last seqno=" << sequence << ")"); + return true; + } +} + +bool ReplicationExchange::bind(Queue::shared_ptr /*queue*/, const std::string& /*routingKey*/, const FieldTable* /*args*/) +{ + throw NotImplementedException("Replication exchange does not support bind operation"); +} + +bool ReplicationExchange::unbind(Queue::shared_ptr /*queue*/, const std::string& /*routingKey*/, const FieldTable* /*args*/) +{ + throw NotImplementedException("Replication exchange does not support unbind operation"); +} + +bool ReplicationExchange::isBound(Queue::shared_ptr /*queue*/, const string* const /*routingKey*/, const FieldTable* const /*args*/) +{ + return false; +} + +const std::string ReplicationExchange::typeName("replication"); + + +void ReplicationExchange::encode(Buffer& buffer) const +{ + args.setInt64(std::string(SEQUENCE_VALUE), sequence); + Exchange::encode(buffer); +} + + +struct ReplicationExchangePlugin : Plugin +{ + Broker* broker; + + ReplicationExchangePlugin(); + void earlyInitialize(Plugin::Target& target); + void initialize(Plugin::Target& target); + Exchange::shared_ptr create(const std::string& name, bool durable, + const framing::FieldTable& args, + management::Manageable* parent, + qpid::broker::Broker* broker); +}; + +ReplicationExchangePlugin::ReplicationExchangePlugin() : broker(0) {} + +Exchange::shared_ptr ReplicationExchangePlugin::create(const std::string& name, bool durable, + const framing::FieldTable& args, + management::Manageable* parent, qpid::broker::Broker* broker) +{ + Exchange::shared_ptr e(new ReplicationExchange(name, durable, args, broker->getQueues(), parent, broker)); + return e; +} + + +void ReplicationExchangePlugin::earlyInitialize(Plugin::Target& target) +{ + broker = dynamic_cast<broker::Broker*>(&target); + if (broker) { + ExchangeRegistry::FactoryFunction f = boost::bind(&ReplicationExchangePlugin::create, this, _1, _2, _3, _4, _5); + broker->getExchanges().registerType(ReplicationExchange::typeName, f); + QPID_LOG(info, "Registered replication exchange"); + } +} + +void ReplicationExchangePlugin::initialize(Target&) {} + +static ReplicationExchangePlugin exchangePlugin; + +}} // namespace qpid::replication diff --git a/cpp/src/qpid/replication/ReplicationExchange.h b/cpp/src/qpid/replication/ReplicationExchange.h new file mode 100644 index 0000000000..f0252448f9 --- /dev/null +++ b/cpp/src/qpid/replication/ReplicationExchange.h @@ -0,0 +1,67 @@ +#ifndef QPID_REPLICATION_REPLICATIONEXCHANGE_H +#define QPID_REPLICATION_REPLICATIONEXCHANGE_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/Exchange.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/SequenceNumber.h" + +namespace qpid { +namespace replication { + +/** + * A custom exchange plugin that processes incoming messages + * representing enqueue or dequeue events for particular queues and + * carries out the corresponding action to replicate that on the local + * broker. + */ +class ReplicationExchange : public qpid::broker::Exchange +{ + public: + static const std::string typeName; + + ReplicationExchange(const std::string& name, bool durable, + const qpid::framing::FieldTable& args, + qpid::broker::QueueRegistry& queues, + qpid::management::Manageable* parent = 0, + qpid::broker::Broker* broker = 0); + + std::string getType() const; + + void route(qpid::broker::Deliverable& msg, const std::string& routingKey, const qpid::framing::FieldTable* args); + + bool bind(qpid::broker::Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + bool unbind(qpid::broker::Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + bool isBound(qpid::broker::Queue::shared_ptr queue, const std::string* const routingKey, const qpid::framing::FieldTable* const args); + private: + qpid::broker::QueueRegistry& queues; + qpid::framing::SequenceNumber sequence; + bool init; + + bool isDuplicate(const qpid::framing::FieldTable* args); + void handleEnqueueEvent(const qpid::framing::FieldTable* args, qpid::broker::Deliverable& msg); + void handleDequeueEvent(const qpid::framing::FieldTable* args, qpid::broker::Deliverable& msg); + void encode(framing::Buffer& buffer) const; +}; +}} // namespace qpid::replication + +#endif /*!QPID_REPLICATION_REPLICATIONEXCHANGE_H*/ diff --git a/cpp/src/qpid/replication/constants.h b/cpp/src/qpid/replication/constants.h new file mode 100644 index 0000000000..c5ba7d3d6a --- /dev/null +++ b/cpp/src/qpid/replication/constants.h @@ -0,0 +1,34 @@ +/* + * + * 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 replication { +namespace constants { + +const std::string REPLICATION_EVENT_TYPE("qpid.replication.type"); +const std::string REPLICATION_EVENT_SEQNO("qpid.replication.seqno"); +const std::string REPLICATION_TARGET_QUEUE("qpid.replication.target_queue"); +const std::string DEQUEUED_MESSAGE_POSITION("qpid.replication.message"); +const std::string QUEUE_MESSAGE_POSITION("qpid.replication.queue.position"); + +const int ENQUEUE(1); +const int DEQUEUE(2); + +}}} diff --git a/cpp/src/qpid/shared_ptr.h b/cpp/src/qpid/shared_ptr.h deleted file mode 100644 index 0c933ea6a6..0000000000 --- a/cpp/src/qpid/shared_ptr.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef _common_shared_ptr_h -#define _common_shared_ptr_h - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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 <boost/cast.hpp> - -namespace qpid { - -// Import shared_ptr definitions into qpid namespace and define some -// useful shared_ptr templates for convenience. - -using boost::shared_ptr; -using boost::dynamic_pointer_cast; -using boost::static_pointer_cast; -using boost::const_pointer_cast; -using boost::shared_polymorphic_downcast; - -template <class T> shared_ptr<T> make_shared_ptr(T* ptr) { - return shared_ptr<T>(ptr); -} - -template <class T, class D> -shared_ptr<T> make_shared_ptr(T* ptr, D deleter) { - return shared_ptr<T>(ptr, deleter); -} - -inline void nullDeleter(void const *) {} - -} // namespace qpid - - - -#endif /*!_common_shared_ptr_h*/ diff --git a/cpp/src/qpid/store/CMakeLists.txt b/cpp/src/qpid/store/CMakeLists.txt new file mode 100644 index 0000000000..0d25923175 --- /dev/null +++ b/cpp/src/qpid/store/CMakeLists.txt @@ -0,0 +1,80 @@ +# +# 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. +# + +project(qpidc_store) + +#set (CMAKE_VERBOSE_MAKEFILE ON) # for debugging + +include_directories( ${Boost_INCLUDE_DIR} ) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) +include_directories( ${CMAKE_HOME_DIRECTORY}/include ) + +link_directories( ${Boost_LIBRARY_DIRS} ) + +set (store_SOURCES + MessageStorePlugin.cpp + ) +add_library (store MODULE ${store_SOURCES}) +target_link_libraries (store qpidbroker ${Boost_PROGRAM_OPTIONS_LIBRARY}) +if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties (store PROPERTIES + PREFIX "" + LINK_FLAGS -Wl,--no-undefined) +endif (CMAKE_COMPILER_IS_GNUCXX) + +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + if (MSVC) + add_definitions( + /D "NOMINMAX" + /D "WIN32_LEAN_AND_MEAN" + ) + endif (MSVC) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +set_target_properties (store PROPERTIES VERSION ${qpidc_version}) +install (TARGETS store # RUNTIME + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) + +# Build the MS SQL Storage Provider plugin +set (mssql_default ON) +if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) + set(mssql_default OFF) +endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) +option(BUILD_MSSQL "Build MS SQL Store provider plugin" ${mssql_default}) +if (BUILD_MSSQL) + add_library (mssql_store MODULE + ms-sql/MsSqlProvider.cpp + ms-sql/AmqpTransaction.cpp + ms-sql/BindingRecordset.cpp + ms-sql/BlobAdapter.cpp + ms-sql/BlobEncoder.cpp + ms-sql/BlobRecordset.cpp + ms-sql/DatabaseConnection.cpp + ms-sql/MessageMapRecordset.cpp + ms-sql/MessageRecordset.cpp + ms-sql/Recordset.cpp + ms-sql/State.cpp + ms-sql/VariantHelper.cpp) + target_link_libraries (mssql_store qpidbroker qpidcommon ${Boost_PROGRAM_OPTIONS_LIBRARY}) + install (TARGETS mssql_store # RUNTIME + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) +endif (BUILD_MSSQL) diff --git a/cpp/src/qpid/store/MessageStorePlugin.cpp b/cpp/src/qpid/store/MessageStorePlugin.cpp new file mode 100644 index 0000000000..05b6ef4465 --- /dev/null +++ b/cpp/src/qpid/store/MessageStorePlugin.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. + * + */ + +#include "MessageStorePlugin.h" +#include "StorageProvider.h" +#include "StoreException.h" +#include "qpid/broker/Broker.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/DataDir.h" +#include "qpid/log/Statement.h" + +/* + * The MessageStore pointer given to the Broker points to static storage. + * Thus, it cannot be deleted, especially by the broker. To prevent deletion, + * this no-op deleter is used with the boost::shared_ptr. When the last + * shared_ptr is destroyed, the deleter is called rather than delete(). + */ +namespace { + class NoopDeleter { + public: + NoopDeleter() {} + void operator()(qpid::broker::MessageStore * /*p*/) {} + }; +} + +namespace qpid { +namespace store { + +static MessageStorePlugin static_instance_registers_plugin; + + +MessageStorePlugin::StoreOptions::StoreOptions(const std::string& name) : + qpid::Options(name) +{ + addOptions() + ("storage-provider", qpid::optValue(providerName, "PROVIDER"), + "Name of the storage provider to use.") + ; +} + + +void +MessageStorePlugin::earlyInitialize (qpid::Plugin::Target& target) +{ + qpid::broker::Broker* broker = + dynamic_cast<qpid::broker::Broker*>(&target); + if (0 == broker) + return; // Only listen to Broker targets + + // See if there are any storage provider plugins ready. If not, we can't + // do a message store. + qpid::Plugin::earlyInitAll(*this); + + if (providers.empty()) { + QPID_LOG(warning, + "Message store plugin: No storage providers available."); + provider = providers.end(); + return; + } + if (!options.providerName.empty()) { + // If specific one was chosen, locate it in loaded set of providers. + provider = providers.find(options.providerName); + if (provider == providers.end()) + throw Exception("Message store plugin: storage provider '" + + options.providerName + + "' does not exist."); + } + else { + // No specific provider chosen; if there's only one, use it. Else + // report the need to pick one. + if (providers.size() > 1) { + provider = providers.end(); + throw Exception("Message store plugin: multiple provider plugins " + "loaded; must either load only one or select one " + "using --storage-provider"); + } + provider = providers.begin(); + } + + provider->second->activate(*this); + NoopDeleter d; + boost::shared_ptr<qpid::broker::MessageStore> sp(this, d); + broker->setStore(sp); + target.addFinalizer(boost::bind(&MessageStorePlugin::finalizeMe, this)); +} + +void +MessageStorePlugin::initialize(qpid::Plugin::Target& target) +{ + qpid::broker::Broker* broker = + dynamic_cast<qpid::broker::Broker*>(&target); + if (0 == broker) + return; // Only listen to Broker targets + + // Pass along the initialize step to the provider that's activated. + if (provider != providers.end()) { + provider->second->initialize(*this); + } + // qpid::Plugin::initializeAll(*this); +} + +void +MessageStorePlugin::finalizeMe() +{ + finalize(); // Call finalizers on any Provider plugins +} + +void +MessageStorePlugin::providerAvailable(const std::string name, + StorageProvider *be) +{ + ProviderMap::value_type newSp(name, be); + std::pair<ProviderMap::iterator, bool> inserted = providers.insert(newSp); + if (inserted.second == false) + 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 + */ +void +MessageStorePlugin::create(broker::PersistableQueue& queue, + const framing::FieldTable& args) +{ + if (queue.getName().size() == 0) + { + QPID_LOG(error, + "Cannot create store for empty (null) queue name - " + "ignoring and attempting to continue."); + return; + } + if (queue.getPersistenceId()) { + THROW_STORE_EXCEPTION("Queue already created: " + queue.getName()); + } + provider->second->create(queue, args); +} + +/** + * Destroy a durable queue + */ +void +MessageStorePlugin::destroy(broker::PersistableQueue& queue) +{ + provider->second->destroy(queue); +} + +/** + * Record the existence of a durable exchange + */ +void +MessageStorePlugin::create(const broker::PersistableExchange& exchange, + const framing::FieldTable& args) +{ + if (exchange.getPersistenceId()) { + THROW_STORE_EXCEPTION("Exchange already created: " + exchange.getName()); + } + provider->second->create(exchange, args); +} + +/** + * Destroy a durable exchange + */ +void +MessageStorePlugin::destroy(const broker::PersistableExchange& exchange) +{ + provider->second->destroy(exchange); +} + +/** + * Record a binding + */ +void +MessageStorePlugin::bind(const broker::PersistableExchange& exchange, + const broker::PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args) +{ + provider->second->bind(exchange, queue, key, args); +} + +/** + * Forget a binding + */ +void +MessageStorePlugin::unbind(const broker::PersistableExchange& exchange, + const broker::PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args) +{ + provider->second->unbind(exchange, queue, key, args); +} + +/** + * Record generic durable configuration + */ +void +MessageStorePlugin::create(const broker::PersistableConfig& config) +{ + if (config.getPersistenceId()) { + THROW_STORE_EXCEPTION("Config item already created: " + + config.getName()); + } + provider->second->create(config); +} + +/** + * Destroy generic durable configuration + */ +void +MessageStorePlugin::destroy(const broker::PersistableConfig& config) +{ + provider->second->destroy(config); +} + +/** + * Stores a message before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). + */ +void +MessageStorePlugin::stage(const boost::intrusive_ptr<broker::PersistableMessage>& msg) +{ + if (msg->getPersistenceId() == 0 && !msg->isContentReleased()) { + provider->second->stage(msg); + } +} + +/** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ +void +MessageStorePlugin::destroy(broker::PersistableMessage& msg) +{ + if (msg.getPersistenceId()) + provider->second->destroy(msg); +} + +/** + * Appends content to a previously staged message + */ +void +MessageStorePlugin::appendContent + (const boost::intrusive_ptr<const broker::PersistableMessage>& msg, + const std::string& data) +{ + if (msg->getPersistenceId()) + provider->second->appendContent(msg, data); + else + THROW_STORE_EXCEPTION("Cannot append content. Message not known to store!"); +} + +/** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ +void +MessageStorePlugin::loadContent(const broker::PersistableQueue& queue, + const boost::intrusive_ptr<const broker::PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length) +{ + if (msg->getPersistenceId()) + provider->second->loadContent(queue, msg, data, offset, length); + else + THROW_STORE_EXCEPTION("Cannot load content. Message not known to store!"); +} + +/** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + */ +void +MessageStorePlugin::enqueue(broker::TransactionContext* ctxt, + const boost::intrusive_ptr<broker::PersistableMessage>& msg, + const broker::PersistableQueue& queue) +{ + if (queue.getPersistenceId() == 0) { + THROW_STORE_EXCEPTION("Queue not created: " + queue.getName()); + } + provider->second->enqueue(ctxt, msg, queue); +} + +/** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + */ +void +MessageStorePlugin::dequeue(broker::TransactionContext* ctxt, + const boost::intrusive_ptr<broker::PersistableMessage>& msg, + const broker::PersistableQueue& queue) +{ + provider->second->dequeue(ctxt, msg, queue); +} + +/** + * Flushes all async messages to disk for the specified queue + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + */ +void +MessageStorePlugin::flush(const broker::PersistableQueue& queue) +{ + provider->second->flush(queue); +} + +/** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk. + */ +uint32_t +MessageStorePlugin::outstandingQueueAIO(const broker::PersistableQueue& queue) +{ + return provider->second->outstandingQueueAIO(queue); +} + +std::auto_ptr<broker::TransactionContext> +MessageStorePlugin::begin() +{ + return provider->second->begin(); +} + +std::auto_ptr<broker::TPCTransactionContext> +MessageStorePlugin::begin(const std::string& xid) +{ + return provider->second->begin(xid); +} + +void +MessageStorePlugin::prepare(broker::TPCTransactionContext& ctxt) +{ + provider->second->prepare(ctxt); +} + +void +MessageStorePlugin::commit(broker::TransactionContext& ctxt) +{ + provider->second->commit(ctxt); +} + +void +MessageStorePlugin::abort(broker::TransactionContext& ctxt) +{ + provider->second->abort(ctxt); +} + +void +MessageStorePlugin::collectPreparedXids(std::set<std::string>& xids) +{ + provider->second->collectPreparedXids(xids); +} + +/** + * Request recovery of queue and message state; inherited from Recoverable + */ +void +MessageStorePlugin::recover(broker::RecoveryManager& recoverer) +{ + ExchangeMap exchanges; + QueueMap queues; + MessageMap messages; + MessageQueueMap messageQueueMap; + + provider->second->recoverConfigs(recoverer); + provider->second->recoverExchanges(recoverer, exchanges); + provider->second->recoverQueues(recoverer, queues); + provider->second->recoverBindings(recoverer, exchanges, queues); + provider->second->recoverMessages(recoverer, messages, messageQueueMap); + // Enqueue msgs where needed. + for (MessageQueueMap::const_iterator i = messageQueueMap.begin(); + i != messageQueueMap.end(); + ++i) { + // Locate the message corresponding to the current message Id + MessageMap::const_iterator iMsg = messages.find(i->first); + if (iMsg == messages.end()) { + std::ostringstream oss; + oss << "No matching message trying to re-enqueue message " + << i->first; + THROW_STORE_EXCEPTION(oss.str()); + } + broker::RecoverableMessage::shared_ptr msg = iMsg->second; + // Now for each queue referenced in the queue map, locate it + // and re-enqueue the message. + for (std::vector<uint64_t>::const_iterator j = i->second.begin(); + j != i->second.end(); + ++j) { + // Locate the queue corresponding to the current queue Id + QueueMap::const_iterator iQ = queues.find(*j); + if (iQ == queues.end()) { + std::ostringstream oss; + oss << "No matching queue trying to re-enqueue message " + << " on queue Id " << *j; + THROW_STORE_EXCEPTION(oss.str()); + } + iQ->second->recover(msg); + } + } + + // recoverTransactions() and apply correctly while re-enqueuing +} + +}} // namespace qpid::store diff --git a/cpp/src/qpid/store/MessageStorePlugin.h b/cpp/src/qpid/store/MessageStorePlugin.h new file mode 100644 index 0000000000..529a59401e --- /dev/null +++ b/cpp/src/qpid/store/MessageStorePlugin.h @@ -0,0 +1,284 @@ +#ifndef QPID_STORE_MESSAGESTOREPLUGIN_H +#define QPID_STORE_MESSAGESTOREPLUGIN_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/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 <string> + +using namespace qpid; + +namespace qpid { +namespace store { + +class StorageProvider; + +/** + * @class MessageStorePlugin + * + * MessageStorePlugin is the front end of the persistent message store + * plugin. It is responsible for coordinating recovery, initialization, + * transactions (both local and distributed), flow-to-disk loading and + * unloading and persisting broker state (queues, bindings etc.). + * Actual storage operations are carried out by a message store storage + * provider that implements the qpid::store::StorageProvider interface. + */ +class MessageStorePlugin : + public qpid::Plugin, + public qpid::broker::MessageStore, // Frontend classes + public qpid::Plugin::Target // Provider target + // @TODO Need a mgmt story for this. Maybe allow r/o access to provider store info? public qpid::management::Manageable +{ + public: + MessageStorePlugin() {} + + /** + * @name Methods inherited from qpid::Plugin + */ + //@{ + virtual Options* getOptions() { return &options; } + virtual void earlyInitialize (Plugin::Target& target); + virtual void initialize(Plugin::Target& target); + //@} + + /// Finalizer; calls Target::finalize() to run finalizers on + /// StorageProviders. + void finalizeMe(); + + /** + * Called by StorageProvider instances during the earlyInitialize sequence. + * Each StorageProvider must supply a unique name by which it is known and a + * pointer to itself. + */ + virtual void providerAvailable(const std::string name, StorageProvider *be); + + /** + * @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 + */ + virtual void create(broker::PersistableQueue& queue, + const framing::FieldTable& args); + /** + * Destroy a durable queue + */ + virtual void destroy(broker::PersistableQueue& queue); + + /** + * Record the existence of a durable exchange + */ + virtual void create(const broker::PersistableExchange& exchange, + const framing::FieldTable& args); + /** + * Destroy a durable exchange + */ + virtual void destroy(const broker::PersistableExchange& exchange); + + /** + * Record a binding + */ + virtual void bind(const broker::PersistableExchange& exchange, + const broker::PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args); + + /** + * Forget a binding + */ + virtual void unbind(const broker::PersistableExchange& exchange, + const broker::PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args); + + /** + * Record generic durable configuration + */ + virtual void create(const broker::PersistableConfig& config); + + /** + * Destroy generic durable configuration + */ + virtual void destroy(const broker::PersistableConfig& config); + + /** + * Stores a message before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ + virtual void stage(const boost::intrusive_ptr<broker::PersistableMessage>& msg); + + /** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ + virtual void destroy(broker::PersistableMessage& msg); + + /** + * Appends content to a previously staged message + */ + virtual void appendContent(const boost::intrusive_ptr<const broker::PersistableMessage>& msg, + const std::string& data); + + /** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ + virtual void loadContent(const broker::PersistableQueue& queue, + const boost::intrusive_ptr<const broker::PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + + /** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + * + * @param msg the message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void enqueue(broker::TransactionContext* ctxt, + const boost::intrusive_ptr<broker::PersistableMessage>& msg, + const broker::PersistableQueue& queue); + + /** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + * + * @param msg the message to dequeue + * @param queue the name of the queue from which it is to be dequeued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void dequeue(broker::TransactionContext* ctxt, + const boost::intrusive_ptr<broker::PersistableMessage>& msg, + const broker::PersistableQueue& queue); + + /** + * Flushes all async messages to disk for the specified queue + * + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + * + * @param queue the name of the queue from which it is to be dequeued + */ + virtual void flush(const broker::PersistableQueue& queue); + + /** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk + * + * @param queue the name of the queue to check for outstanding AIO + */ + virtual uint32_t outstandingQueueAIO(const broker::PersistableQueue& queue); + //@} + + /** + * @name Methods inherited from qpid::broker::TransactionalStore + */ + //@{ + std::auto_ptr<broker::TransactionContext> begin(); + + std::auto_ptr<broker::TPCTransactionContext> begin(const std::string& xid); + + void prepare(broker::TPCTransactionContext& ctxt); + + void commit(broker::TransactionContext& ctxt); + + void abort(broker::TransactionContext& ctxt); + + void collectPreparedXids(std::set<std::string>& xids); + //@} + + /** + * Request recovery of queue and message state; inherited from Recoverable + */ + virtual void recover(broker::RecoveryManager& recoverer); + + // inline management::Manageable::status_t ManagementMethod (uint32_t, management::Args&, std::string&) + // { return management::Manageable::STATUS_OK; } + + protected: + + struct StoreOptions : public qpid::Options { + StoreOptions(const std::string& name="Store Options"); + std::string providerName; + }; + StoreOptions options; + + typedef std::map<const std::string, StorageProvider*> ProviderMap; + ProviderMap providers; + ProviderMap::const_iterator provider; + +}; // class MessageStoreImpl + +} // namespace msgstore +} // namespace mrg + +#endif /* QPID_SERIALIZER_H */ diff --git a/cpp/src/qpid/store/StorageProvider.h b/cpp/src/qpid/store/StorageProvider.h new file mode 100644 index 0000000000..1f257e7416 --- /dev/null +++ b/cpp/src/qpid/store/StorageProvider.h @@ -0,0 +1,324 @@ +#ifndef QPID_STORE_STORAGEPROVIDER_H +#define QPID_STORE_STORAGEPROVIDER_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 <stdexcept> +#include <vector> +#include "qpid/Exception.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/broker/MessageStore.h" + +using qpid::broker::PersistableConfig; +using qpid::broker::PersistableExchange; +using qpid::broker::PersistableMessage; +using qpid::broker::PersistableQueue; + +namespace qpid { +namespace store { + +typedef std::map<uint64_t, qpid::broker::RecoverableExchange::shared_ptr> + ExchangeMap; +typedef std::map<uint64_t, qpid::broker::RecoverableQueue::shared_ptr> + QueueMap; +typedef std::map<uint64_t, qpid::broker::RecoverableMessage::shared_ptr> + MessageMap; +// Msg Id -> vector of queue Ids where message is queued +typedef std::map<uint64_t, std::vector<uint64_t> > MessageQueueMap; + +class MessageStorePlugin; + +/** + * @class StorageProvider + * + * StorageProvider defines the interface for the storage provider plugin to the + * Qpid broker persistence store plugin. + * + * @TODO Should StorageProvider also inherit from MessageStore? If so, then + * maybe remove Recoverable from MessageStore's inheritance and move it + * to MessageStorePlugin? In any event, somehow the discardInit() feature + * needs to get added here. + */ +class StorageProvider : public qpid::Plugin, public qpid::broker::MessageStore +{ +public: + + class Exception : public qpid::Exception + { + public: + virtual ~Exception() throw() {} + virtual const char *what() const throw() = 0; + }; + + /** + * @name Methods inherited from qpid::Plugin + */ + //@{ + /** + * Return a pointer to the provider's options. The options will be + * updated during option parsing by the host program; therefore, the + * referenced Options object must remain valid past this function's return. + * + * @return An options group or 0 for no options. Default returns 0. + * Plugin retains ownership of return value. + */ + virtual qpid::Options* getOptions() = 0; + + /** + * Initialize Plugin functionality on a Target, called before + * initializing the target. + * + * StorageProviders should respond only to Targets of class + * qpid::store::MessageStorePlugin and ignore all others. + * + * When called, the provider should invoke the method + * qpid::store::MessageStorePlugin::providerAvailable() to alert the + * message store of StorageProvider's availability. + * + * Called before the target itself is initialized. + */ + virtual void earlyInitialize (Plugin::Target& target) = 0; + + /** + * Initialize StorageProvider functionality. Called after initializing + * the target. + * + * StorageProviders should respond only to Targets of class + * qpid::store::MessageStorePlugin and ignore all others. + * + * Called after the target is fully initialized. + */ + virtual void initialize(Plugin::Target& target) = 0; + //@} + + /** + * Receive notification that this provider is the one that will actively + * handle storage for the target. If the provider is to be used, this + * method will be called after earlyInitialize() and before any + * recovery operations (recovery, in turn, precedes call to initialize()). + * Thus, it is wise to not actually do any database ops from within + * earlyInitialize() - they can wait until activate() is called because + * at that point it is certain the database will be needed. + */ + virtual void activate(MessageStorePlugin &store) = 0; + + /** + * @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 + */ + virtual void create(PersistableQueue& queue, + const qpid::framing::FieldTable& args) = 0; + /** + * Destroy a durable queue + */ + virtual void destroy(PersistableQueue& queue) = 0; + + /** + * Record the existence of a durable exchange + */ + virtual void create(const PersistableExchange& exchange, + const qpid::framing::FieldTable& args) = 0; + /** + * Destroy a durable exchange + */ + virtual void destroy(const PersistableExchange& exchange) = 0; + + /** + * Record a binding + */ + virtual void bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) = 0; + + /** + * Forget a binding + */ + virtual void unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) = 0; + + /** + * Record generic durable configuration + */ + virtual void create(const PersistableConfig& config) = 0; + + /** + * Destroy generic durable configuration + */ + virtual void destroy(const PersistableConfig& config) = 0; + + /** + * Stores a messages before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ + virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg) = 0; + + /** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ + virtual void destroy(PersistableMessage& msg) = 0; + + /** + * Appends content to a previously staged message + */ + virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data) = 0; + + /** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ + virtual void loadContent(const PersistableQueue& queue, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length) = 0; + + /** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) = 0; + + /** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to dequeue + * @param queue the name of the queue from which it is to be dequeued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) = 0; + + /** + * Flushes all async messages to disk for the specified queue + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param queue the name of the queue from which it is to be dequeued + */ + virtual void flush(const qpid::broker::PersistableQueue& queue) = 0; + + /** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk + * + * @param queue the name of the queue to check for outstanding AIO + */ + virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue) = 0; + //@} + + /** + * @TODO This should probably not be here - it's only here because + * MessageStore inherits from Recoverable... maybe move that derivation. + * + * As it is now, we don't use this. Separate recover methods are + * declared below for individual types, which also set up maps of + * messages, queues, transactions for the main store plugin to handle + * properly. + * + * Request recovery of queue and message state. + */ + virtual void recover(qpid::broker::RecoveryManager& /*recoverer*/) {} + + /** + * @name Methods that do the recovery of the various objects that + * were saved. + */ + //@{ + + /** + * Recover bindings. + */ + virtual void recoverConfigs(qpid::broker::RecoveryManager& recoverer) = 0; + virtual void recoverExchanges(qpid::broker::RecoveryManager& recoverer, + ExchangeMap& exchangeMap) = 0; + virtual void recoverQueues(qpid::broker::RecoveryManager& recoverer, + QueueMap& queueMap) = 0; + virtual void recoverBindings(qpid::broker::RecoveryManager& recoverer, + const ExchangeMap& exchangeMap, + const QueueMap& queueMap) = 0; + virtual void recoverMessages(qpid::broker::RecoveryManager& recoverer, + MessageMap& messageMap, + MessageQueueMap& messageQueueMap) = 0; + //@} +}; + +}} // namespace qpid::store + +#endif /* QPID_STORE_STORAGEPROVIDER_H */ diff --git a/cpp/src/qpid/store/StoreException.h b/cpp/src/qpid/store/StoreException.h new file mode 100644 index 0000000000..1dc7f670ec --- /dev/null +++ b/cpp/src/qpid/store/StoreException.h @@ -0,0 +1,49 @@ +#ifndef QPID_STORE_STOREEXCEPTION_H +#define QPID_STORE_STOREEXCEPTION_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 <exception> +#include <boost/format.hpp> +#include "StorageProvider.h" + +namespace qpid { +namespace store { + +class StoreException : public std::exception +{ + std::string text; +public: + StoreException(const std::string& _text) : text(_text) {} + StoreException(const std::string& _text, + const StorageProvider::Exception& cause) + : text(_text + ": " + cause.what()) {} + virtual ~StoreException() throw() {} + virtual const char* what() const throw() { return text.c_str(); } +}; + +#define THROW_STORE_EXCEPTION(MESSAGE) throw qpid::store::StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__)) +#define THROW_STORE_EXCEPTION_2(MESSAGE, EXCEPTION) throw qpid::store::StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__), EXCEPTION) + +}} // namespace qpid::store + +#endif /* QPID_STORE_STOREEXCEPTION_H */ diff --git a/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp b/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp new file mode 100644 index 0000000000..0ecfacfb4b --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp @@ -0,0 +1,89 @@ +/* + * + * 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 "AmqpTransaction.h" +#include "DatabaseConnection.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +AmqpTransaction::AmqpTransaction(std::auto_ptr<DatabaseConnection>& _db) + : db(_db), transDepth(0) +{ +} + +AmqpTransaction::~AmqpTransaction() +{ + if (transDepth > 0) + this->abort(); +} + +void +AmqpTransaction::begin() +{ + _bstr_t beginCmd("BEGIN TRANSACTION"); + _ConnectionPtr c = *db; + c->Execute(beginCmd, NULL, adExecuteNoRecords); + ++transDepth; +} + +void +AmqpTransaction::commit() +{ + if (transDepth > 0) { + _bstr_t commitCmd("COMMIT TRANSACTION"); + _ConnectionPtr c = *db; + c->Execute(commitCmd, NULL, adExecuteNoRecords); + --transDepth; + } +} + +void +AmqpTransaction::abort() +{ + if (transDepth > 0) { + _bstr_t rollbackCmd("ROLLBACK TRANSACTION"); + _ConnectionPtr c = *db; + c->Execute(rollbackCmd, NULL, adExecuteNoRecords); + transDepth = 0; + } +} + +AmqpTPCTransaction::AmqpTPCTransaction(std::auto_ptr<DatabaseConnection>& _db, + const std::string& _xid) + : AmqpTransaction(_db), xid(_xid) +{ +} + +AmqpTPCTransaction::~AmqpTPCTransaction() +{ +} + +void +AmqpTPCTransaction::prepare() +{ + // Intermediate transactions should have already assured integrity of + // the content in the database; just waiting to pull the trigger on the + // outermost transaction. +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/AmqpTransaction.h b/cpp/src/qpid/store/ms-sql/AmqpTransaction.h new file mode 100644 index 0000000000..9b87d0ae15 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/AmqpTransaction.h @@ -0,0 +1,84 @@ +#ifndef QPID_STORE_MSSQL_AMQPTRANSACTION_H +#define QPID_STORE_MSSQL_AMQPTRANSACTION_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/TransactionalStore.h> +#include <string> +#include <memory> + +namespace qpid { +namespace store { +namespace ms_sql { + +class DatabaseConnection; + +/** + * @class AmqpTransaction + * + * Class representing an AMQP transaction. This is used around a set of + * enqueue and dequeue operations that occur when the broker is acting + * on a transaction commit/abort from the client. + */ +class AmqpTransaction : public qpid::broker::TransactionContext { + + std::auto_ptr<DatabaseConnection> db; + + // Since ADO w/ SQLOLEDB can't do nested transaction via its BeginTrans(), + // et al, nested transactions are carried out with direct SQL commands. + // To ensure the state of this is known, keep track of how deeply the + // transactions are nested. + unsigned int transDepth; + +public: + AmqpTransaction(std::auto_ptr<DatabaseConnection>& _db); + virtual ~AmqpTransaction(); + + DatabaseConnection *dbConn() { return db.get(); } + + void begin(); + void commit(); + void abort(); +}; + +/** + * @class AmqpTPCTransaction + * + * Class representing a Two-Phase-Commit (TPC) AMQP transaction. This is + * used around a set of enqueue and dequeue operations that occur when the + * broker is acting on a transaction prepare/commit/abort from the client. + */ +class AmqpTPCTransaction : public AmqpTransaction, + public qpid::broker::TPCTransactionContext { + std::string xid; + +public: + AmqpTPCTransaction(std::auto_ptr<DatabaseConnection>& _db, + const std::string& _xid); + virtual ~AmqpTPCTransaction(); + + void prepare(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_AMQPTRANSACTION_H */ diff --git a/cpp/src/qpid/store/ms-sql/BindingRecordset.cpp b/cpp/src/qpid/store/ms-sql/BindingRecordset.cpp new file mode 100644 index 0000000000..1dc4370312 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/BindingRecordset.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 <qpid/Exception.h> +#include <qpid/log/Statement.h> + +#include "BindingRecordset.h" +#include "BlobAdapter.h" +#include "BlobEncoder.h" +#include "VariantHelper.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +void +BindingRecordset::removeFilter(const std::string& filter) +{ + rs->PutFilter (VariantHelper<std::string>(filter)); + long recs = rs->GetRecordCount(); + if (recs == 0) + return; // Nothing to do + while (recs > 0) { + // Deleting adAffectAll doesn't work as documented; go one by one. + rs->Delete(adAffectCurrent); + if (--recs > 0) + rs->MoveNext(); + } + rs->Update(); +} + +void +BindingRecordset::add(uint64_t exchangeId, + uint64_t queueId, + const std::string& routingKey, + const qpid::framing::FieldTable& args) +{ + VariantHelper<std::string> routingKeyStr(routingKey); + BlobEncoder blob (args); // Marshall field table to a blob + rs->AddNew(); + rs->Fields->GetItem("exchangeId")->Value = exchangeId; + rs->Fields->GetItem("queueId")->Value = queueId; + rs->Fields->GetItem("routingKey")->Value = routingKeyStr; + rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob); + rs->Update(); +} + +void +BindingRecordset::remove(uint64_t exchangeId, + uint64_t queueId, + const std::string& routingKey, + const qpid::framing::FieldTable& /*args*/) +{ + // Look up the affected binding. + std::ostringstream filter; + filter << "exchangeId = " << exchangeId + << " AND queueId = " << queueId + << " AND routingKey = '" << routingKey << "'" << std::ends; + removeFilter(filter.str()); +} + +void +BindingRecordset::removeForExchange(uint64_t exchangeId) +{ + // Look up the affected bindings by the exchange ID + std::ostringstream filter; + filter << "exchangeId = " << exchangeId << std::ends; + removeFilter(filter.str()); +} + +void +BindingRecordset::removeForQueue(uint64_t queueId) +{ + // Look up the affected bindings by the queue ID + std::ostringstream filter; + filter << "queueId = " << queueId << std::ends; + removeFilter(filter.str()); +} + +void +BindingRecordset::recover(broker::RecoveryManager& recoverer, + const store::ExchangeMap& exchMap, + const store::QueueMap& queueMap) +{ + if (rs->BOF && rs->EndOfFile) + return; // Nothing to do + rs->MoveFirst(); + Binding b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + while (!rs->EndOfFile) { + long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = rs->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + store::ExchangeMap::const_iterator exch = exchMap.find(b.exchangeId); + if (exch == exchMap.end()) { + std::ostringstream msg; + msg << "Error recovering bindings; exchange ID " << b.exchangeId + << " not found in exchange map"; + throw qpid::Exception(msg.str()); + } + broker::RecoverableExchange::shared_ptr exchPtr = exch->second; + store::QueueMap::const_iterator q = queueMap.find(b.queueId); + if (q == queueMap.end()) { + std::ostringstream msg; + msg << "Error recovering bindings; queue ID " << b.queueId + << " not found in queue map"; + throw qpid::Exception(msg.str()); + } + broker::RecoverableQueue::shared_ptr qPtr = q->second; + // The recovery manager wants the queue name, so get it from the + // RecoverableQueue. + std::string key(b.routingKey); + exchPtr->bind(qPtr->getName(), key, blob); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +void +BindingRecordset::dump() +{ + Recordset::dump(); + if (rs->EndOfFile && rs->BOF) // No records + return; + rs->MoveFirst(); + + Binding b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + + while (VARIANT_FALSE == rs->EndOfFile) { + QPID_LOG(notice, "exch Id " << b.exchangeId + << ", q Id " << b.queueId + << ", k: " << b.routingKey); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/BindingRecordset.h b/cpp/src/qpid/store/ms-sql/BindingRecordset.h new file mode 100644 index 0000000000..3cb732de75 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/BindingRecordset.h @@ -0,0 +1,88 @@ +#ifndef QPID_STORE_MSSQL_BINDINGRECORDSET_H +#define QPID_STORE_MSSQL_BINDINGRECORDSET_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 <icrsint.h> +#include "Recordset.h" +#include <qpid/store/StorageProvider.h> +#include <qpid/broker/RecoveryManager.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class BindingRecordset + * + * Class for the binding records. + */ +class BindingRecordset : public Recordset { + + class Binding : public CADORecordBinding { + BEGIN_ADO_BINDING(Binding) + ADO_FIXED_LENGTH_ENTRY2(1, adBigInt, exchangeId, FALSE) + ADO_FIXED_LENGTH_ENTRY2(2, adBigInt, queueId, FALSE) + ADO_VARIABLE_LENGTH_ENTRY4(3, adVarChar, routingKey, + sizeof(routingKey), FALSE) + END_ADO_BINDING() + + public: + uint64_t exchangeId; + uint64_t queueId; + char routingKey[256]; + }; + + // Remove all records matching the specified filter/query. + void removeFilter(const std::string& filter); + +public: + // Add a new binding + void add(uint64_t exchangeId, + uint64_t queueId, + const std::string& routingKey, + const qpid::framing::FieldTable& args); + + // Remove a specific binding + void remove(uint64_t exchangeId, + uint64_t queueId, + const std::string& routingKey, + const qpid::framing::FieldTable& args); + + // Remove all bindings for the specified exchange + void removeForExchange(uint64_t exchangeId); + + // Remove all bindings for the specified queue + void removeForQueue(uint64_t queueId); + + // Recover bindings set using exchMap to get from Id to RecoverableExchange. + void recover(qpid::broker::RecoveryManager& recoverer, + const qpid::store::ExchangeMap& exchMap, + const qpid::store::QueueMap& queueMap); + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_BINDINGRECORDSET_H */ diff --git a/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp b/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp new file mode 100644 index 0000000000..1889f34e41 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp @@ -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. + * + */ + +#include "BlobAdapter.h" +#include <qpid/Exception.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +void +BlobAdapter::extractBuff() +{ + // To give a valid Buffer back, lock the safearray, obtaining a pointer to + // the actual data. Record the pointer in the Buffer so the destructor + // knows to unlock the safearray. + if (buff.getPointer() == 0) { + char *blob; + SafeArrayAccessData(this->parray, (void **)&blob); + qpid::framing::Buffer lockedBuff(blob, buff.getSize()); + buff = lockedBuff; + } +} + + +BlobAdapter::~BlobAdapter() +{ + // If buff's pointer is set, the safearray is locked, so unlock it + if (buff.getPointer() != 0) + SafeArrayUnaccessData(this->parray); +} + +BlobAdapter::operator qpid::framing::Buffer& () +{ + extractBuff(); + return buff; +} + +BlobAdapter::operator qpid::framing::FieldTable& () +{ + extractBuff(); + fields.decode(buff); + return fields; +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/BlobAdapter.h b/cpp/src/qpid/store/ms-sql/BlobAdapter.h new file mode 100644 index 0000000000..1c666392bc --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/BlobAdapter.h @@ -0,0 +1,62 @@ +#ifndef QPID_STORE_MSSQL_BLOBADAPTER_H +#define QPID_STORE_MSSQL_BLOBADAPTER_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 <comutil.h> +#include <qpid/framing/Buffer.h> +#include <qpid/framing/FieldTable.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class BlobAdapter + * + * Adapter for accessing a blob (varbinary SQL field) as a qpid::framing::Buffer + * in an exception-safe way. + */ +class BlobAdapter : public _variant_t { +private: + // This Buffer's pointer indicates whether or not a safearray has + // been locked; if it's 0, no locking was done. + qpid::framing::Buffer buff; + qpid::framing::FieldTable fields; + + void extractBuff(); + +public: + // Initialize with the known length of the data that will come. + // Assigning a _variant_t to this object will set up the array to be + // accessed with the operator Buffer&() + BlobAdapter(long blobSize) : _variant_t(), buff(0, blobSize) {} + ~BlobAdapter(); + BlobAdapter& operator=(_variant_t& var_t_Src) + { _variant_t::operator=(var_t_Src); return *this; } + operator qpid::framing::Buffer& (); + operator qpid::framing::FieldTable& (); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_BLOBADAPTER_H */ diff --git a/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp b/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp new file mode 100644 index 0000000000..75d3dc2d86 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp @@ -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. + * + */ + +#include "BlobEncoder.h" +#include <qpid/Exception.h> +#include <qpid/broker/Persistable.h> +#include <qpid/broker/PersistableMessage.h> +#include <boost/intrusive_ptr.hpp> +#include <memory.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +template <class ITEM> void +BlobEncoder::encode(const ITEM &item) +{ + SAFEARRAYBOUND bound[1] = {0, 0}; + bound[0].cElements = item.encodedSize(); + blob = SafeArrayCreate(VT_UI1, 1, bound); + if (S_OK != SafeArrayLock(blob)) { + SafeArrayDestroy(blob); + blob = 0; + throw qpid::Exception("Error locking blob area for persistable item"); + } + try { + qpid::framing::Buffer buff((char *)blob->pvData, bound[0].cElements); + item.encode(buff); + } + catch(...) { + SafeArrayUnlock(blob); + SafeArrayDestroy(blob); + blob = 0; + throw; + } + this->vt = VT_ARRAY | VT_UI1; + this->parray = blob; + SafeArrayUnlock(blob); +} + +template <> void +BlobEncoder::encode(const boost::intrusive_ptr<qpid::broker::PersistableMessage> &item) +{ + // NOTE! If this code changes, verify the recovery code in MessageRecordset + SAFEARRAYBOUND bound[1] = {0, 0}; + bound[0].cElements = item->encodedSize() + sizeof(uint32_t); + blob = SafeArrayCreate(VT_UI1, 1, bound); + if (S_OK != SafeArrayLock(blob)) { + SafeArrayDestroy(blob); + blob = 0; + throw qpid::Exception("Error locking blob area for message"); + } + try { + uint32_t headerSize = item->encodedHeaderSize(); + qpid::framing::Buffer buff((char *)blob->pvData, bound[0].cElements); + buff.putLong(headerSize); + item->encode(buff); + } + catch(...) { + SafeArrayUnlock(blob); + SafeArrayDestroy(blob); + blob = 0; + throw; + } + this->vt = VT_ARRAY | VT_UI1; + this->parray = blob; + SafeArrayUnlock(blob); +} + +template <> void +BlobEncoder::encode(const std::string &item) +{ + SAFEARRAYBOUND bound[1] = {0, 0}; + bound[0].cElements = item.size(); + blob = SafeArrayCreate(VT_UI1, 1, bound); + if (S_OK != SafeArrayLock(blob)) { + SafeArrayDestroy(blob); + blob = 0; + throw qpid::Exception("Error locking blob area for string"); + } + memcpy_s(blob->pvData, item.size(), item.data(), item.size()); + this->vt = VT_ARRAY | VT_UI1; + this->parray = blob; + SafeArrayUnlock(blob); +} + +BlobEncoder::BlobEncoder(const qpid::broker::Persistable &item) : blob(0) +{ + encode(item); +} + +BlobEncoder::BlobEncoder(const boost::intrusive_ptr<qpid::broker::PersistableMessage> &msg) : blob(0) +{ + encode(msg); +} + +BlobEncoder::BlobEncoder(const qpid::framing::FieldTable &fields) : blob(0) +{ + encode(fields); +} + +BlobEncoder::BlobEncoder(const std::string &data) : blob(0) +{ + encode(data); +} + +BlobEncoder::~BlobEncoder() +{ + if (blob) + SafeArrayDestroy(blob); + blob = 0; + this->parray = 0; +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/BlobEncoder.h b/cpp/src/qpid/store/ms-sql/BlobEncoder.h new file mode 100644 index 0000000000..d2b56223c1 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/BlobEncoder.h @@ -0,0 +1,61 @@ +#ifndef QPID_STORE_MSSQL_BLOBENCODER_H +#define QPID_STORE_MSSQL_BLOBENCODER_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 <comutil.h> +#include <string> +#include <boost/intrusive_ptr.hpp> +#include <qpid/broker/Persistable.h> +#include <qpid/broker/PersistableMessage.h> +#include <qpid/framing/Buffer.h> +#include <qpid/framing/FieldTable.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class BlobEncoder + * + * Encodes a blob (varbinary) field from a qpid::broker::Persistable or a + * qpid::framing::FieldTable (both of which can be encoded to + * qpid::framing::Buffer) so it can be passed to ADO methods for writing + * to the database. + */ +class BlobEncoder : public _variant_t { +private: + SAFEARRAY *blob; + + template <class ITEM> void encode(const ITEM &item); + +public: + BlobEncoder(const qpid::broker::Persistable &item); + BlobEncoder(const boost::intrusive_ptr<qpid::broker::PersistableMessage> &msg); + BlobEncoder(const qpid::framing::FieldTable &fields); + BlobEncoder(const std::string& data); + ~BlobEncoder(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_BLOBENCODER_H */ diff --git a/cpp/src/qpid/store/ms-sql/BlobRecordset.cpp b/cpp/src/qpid/store/ms-sql/BlobRecordset.cpp new file mode 100644 index 0000000000..ef1757dbad --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/BlobRecordset.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 <qpid/Exception.h> +#include <qpid/log/Statement.h> + +#include "BlobRecordset.h" +#include "BlobEncoder.h" +#include "VariantHelper.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +void +BlobRecordset::add(const qpid::broker::Persistable& item) +{ + BlobEncoder blob (item); // Marshall item info to a blob + rs->AddNew(); + rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob); + rs->Update(); + uint64_t id = rs->Fields->Item["persistenceId"]->Value; + item.setPersistenceId(id); +} + +void +BlobRecordset::remove(uint64_t id) +{ + // Look up the item by its persistenceId + std::ostringstream filter; + filter << "persistenceId = " << id << std::ends; + rs->PutFilter (VariantHelper<std::string>(filter.str())); + if (!rs->EndOfFile) { + // Delete the record + rs->Delete(adAffectCurrent); + rs->Update(); + } +} + +void +BlobRecordset::remove(const qpid::broker::Persistable& item) +{ + remove(item.getPersistenceId()); +} + +void +BlobRecordset::dump() +{ + Recordset::dump(); +#if 1 + if (rs->EndOfFile && rs->BOF) // No records + return; + + rs->MoveFirst(); + while (!rs->EndOfFile) { + uint64_t id = rs->Fields->Item["persistenceId"]->Value; + QPID_LOG(notice, " -> " << id); + rs->MoveNext(); + } +#else + for (Iterator iter = begin(); iter != end(); ++iter) { + uint64_t id = *iter.first; + QPID_LOG(notice, " -> " << id); + } +#endif +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/BlobRecordset.h b/cpp/src/qpid/store/ms-sql/BlobRecordset.h new file mode 100644 index 0000000000..4d1c338746 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/BlobRecordset.h @@ -0,0 +1,54 @@ +#ifndef QPID_STORE_MSSQL_BLOBRECORDSET_H +#define QPID_STORE_MSSQL_BLOBRECORDSET_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 "Recordset.h" +#include <qpid/broker/Persistable.h> +#include <string> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class BlobRecordset + * + * Class for the "blob" records that record an id, varbinary(max) pair. + */ +class BlobRecordset : public Recordset { +protected: + +public: + void add(const qpid::broker::Persistable& item); + + // Remove a record given its Id. + void remove(uint64_t id); + void remove(const qpid::broker::Persistable& item); + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_BLOBRECORDSET_H */ diff --git a/cpp/src/qpid/store/ms-sql/DatabaseConnection.cpp b/cpp/src/qpid/store/ms-sql/DatabaseConnection.cpp new file mode 100644 index 0000000000..34edec8acd --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/DatabaseConnection.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 "DatabaseConnection.h" +#include "Exception.h" +#include <comdef.h> +namespace { +inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);}; +} + +namespace qpid { +namespace store { +namespace ms_sql { + +DatabaseConnection::DatabaseConnection() : conn(0) +{ +} + +DatabaseConnection::~DatabaseConnection() +{ + close(); +} + +void +DatabaseConnection::open(const std::string& connectString, + const std::string& dbName) +{ + if (conn && conn->State == adStateOpen) + return; + std::string adoConnect = "Provider=SQLOLEDB;" + connectString; + try { + TESTHR(conn.CreateInstance(__uuidof(Connection))); + conn->ConnectionString = adoConnect.c_str(); + conn->Open("", "", "", adConnectUnspecified); + if (dbName.length() > 0) + conn->DefaultDatabase = dbName.c_str(); + } + catch(_com_error &e) { + close(); + throw ADOException("MSSQL can't open " + dbName + " at " + adoConnect, e); + } +} + +void +DatabaseConnection::close() +{ + if (conn && conn->State == adStateOpen) + conn->Close(); + conn = 0; +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/DatabaseConnection.h b/cpp/src/qpid/store/ms-sql/DatabaseConnection.h new file mode 100644 index 0000000000..2b8bbffa90 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/DatabaseConnection.h @@ -0,0 +1,62 @@ +#ifndef QPID_STORE_MSSQL_DATABASECONNECTION_H +#define QPID_STORE_MSSQL_DATABASECONNECTION_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. + * + */ + +// Bring in ADO 2.8 (yes, I know it says "15", but that's it...) +#import "C:\Program Files\Common Files\System\ado\msado15.dll" \ + no_namespace rename("EOF", "EndOfFile") + +#include <string> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class DatabaseConnection + * + * Represents a connection to the SQL database. This class wraps the + * needed _ConnectionPtr for ADO as well as the needed COM initialization + * and cleanup that each thread requires. It is expected that this class + * will be maintained in thread-specific storage so it has no locks. + */ +class DatabaseConnection { +protected: + _ConnectionPtr conn; + +public: + DatabaseConnection(); + ~DatabaseConnection(); + void open(const std::string& connectString, + const std::string& dbName = ""); + void close(); + operator _ConnectionPtr () { return conn; } + + void beginTransaction() { conn->BeginTrans(); } + void commitTransaction() {conn->CommitTrans(); } + void rollbackTransaction() { conn->RollbackTrans(); } +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_DATABASECONNECTION_H */ diff --git a/cpp/src/qpid/store/ms-sql/Exception.h b/cpp/src/qpid/store/ms-sql/Exception.h new file mode 100644 index 0000000000..0da4b24210 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/Exception.h @@ -0,0 +1,62 @@ +#ifndef QPID_STORE_MSSQL_EXCEPTION_H +#define QPID_STORE_MSSQL_EXCEPTION_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 <comdef.h> +#include <qpid/store/StorageProvider.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +class Exception : public qpid::store::StorageProvider::Exception +{ +protected: + std::string text; +public: + Exception(const std::string& _text) : text(_text) {} + virtual ~Exception() {} + virtual const char* what() const throw() { return text.c_str(); } +}; + +class ADOException : public Exception +{ +public: + ADOException(const std::string& _text, _com_error &e) + : Exception(_text) { + text += ": "; + text += e.ErrorMessage(); + IErrorInfo *i = e.ErrorInfo(); + if (i != 0) { + text += ": "; + _bstr_t wmsg = e.Description(); + text += (const char *)wmsg; + i->Release(); + } + } +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_EXCEPTION_H */ diff --git a/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp b/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp new file mode 100644 index 0000000000..a26df59df7 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp @@ -0,0 +1,1061 @@ +/* + * + * 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 <stdlib.h> +#include <string> +#include <windows.h> +#include <qpid/broker/RecoverableQueue.h> +#include <qpid/log/Statement.h> +#include <qpid/store/MessageStorePlugin.h> +#include <qpid/store/StorageProvider.h> +#include "AmqpTransaction.h" +#include "BlobAdapter.h" +#include "BlobRecordset.h" +#include "BindingRecordset.h" +#include "MessageMapRecordset.h" +#include "MessageRecordset.h" +#include "DatabaseConnection.h" +#include "Exception.h" +#include "State.h" +#include "VariantHelper.h" + +// Bring in ADO 2.8 (yes, I know it says "15", but that's it...) +#import "C:\Program Files\Common Files\System\ado\msado15.dll" \ + no_namespace rename("EOF", "EndOfFile") +#include <comdef.h> +namespace { +inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);}; + +// Table names +const std::string TblBinding("tblBinding"); +const std::string TblConfig("tblConfig"); +const std::string TblExchange("tblExchange"); +const std::string TblMessage("tblMessage"); +const std::string TblMessageMap("tblMessageMap"); +const std::string TblQueue("tblQueue"); +} + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class MSSqlProvider + * + * Implements a qpid::store::StorageProvider that uses Microsoft SQL Server as + * the backend data store for Qpid. + */ +class MSSqlProvider : public qpid::store::StorageProvider +{ +protected: + void finalizeMe(); + + void dump(); + +public: + MSSqlProvider(); + ~MSSqlProvider(); + + virtual qpid::Options* getOptions() { return &options; } + + virtual void earlyInitialize (Plugin::Target& target); + virtual void initialize(Plugin::Target& target); + + /** + * Receive notification that this provider is the one that will actively + * handle provider storage for the target. If the provider is to be used, + * this method will be called after earlyInitialize() and before any + * recovery operations (recovery, in turn, precedes call to initialize()). + */ + virtual void activate(MessageStorePlugin &store); + + /** + * @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 + */ + virtual void create(PersistableQueue& queue, + const qpid::framing::FieldTable& args); + /** + * Destroy a durable queue + */ + virtual void destroy(PersistableQueue& queue); + + /** + * Record the existence of a durable exchange + */ + virtual void create(const PersistableExchange& exchange, + const qpid::framing::FieldTable& args); + /** + * Destroy a durable exchange + */ + virtual void destroy(const PersistableExchange& exchange); + + /** + * Record a binding + */ + virtual void bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args); + + /** + * Forget a binding + */ + virtual void unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args); + + /** + * Record generic durable configuration + */ + virtual void create(const PersistableConfig& config); + + /** + * Destroy generic durable configuration + */ + virtual void destroy(const PersistableConfig& config); + + /** + * Stores a messages before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ + virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg); + + /** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ + virtual void destroy(PersistableMessage& msg); + + /** + * Appends content to a previously staged message + */ + virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data); + + /** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ + virtual void loadContent(const qpid::broker::PersistableQueue& queue, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + + /** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + + /** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to dequeue + * @param queue the name of the queue from which it is to be dequeued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + + /** + * Flushes all async messages to disk for the specified queue + * + * Note: this is a no-op for this provider. + * + * @param queue the name of the queue from which it is to be dequeued + */ + virtual void flush(const PersistableQueue& queue) {}; + + /** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk + * + * @param queue the name of the queue to check for outstanding AIO + */ + virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue) + {return 0;} + //@} + + /** + * @name Methods inherited from qpid::broker::TransactionalStore + */ + //@{ + virtual std::auto_ptr<qpid::broker::TransactionContext> begin(); + virtual std::auto_ptr<qpid::broker::TPCTransactionContext> begin(const std::string& xid); + virtual void prepare(qpid::broker::TPCTransactionContext& txn); + virtual void commit(qpid::broker::TransactionContext& txn); + virtual void abort(qpid::broker::TransactionContext& txn); + + // @TODO This maybe should not be in TransactionalStore + virtual void collectPreparedXids(std::set<std::string>& xids) {} + //@} + + virtual void recoverConfigs(qpid::broker::RecoveryManager& recoverer); + virtual void recoverExchanges(qpid::broker::RecoveryManager& recoverer, + ExchangeMap& exchangeMap); + virtual void recoverQueues(qpid::broker::RecoveryManager& recoverer, + QueueMap& queueMap); + virtual void recoverBindings(qpid::broker::RecoveryManager& recoverer, + const ExchangeMap& exchangeMap, + const QueueMap& queueMap); + virtual void recoverMessages(qpid::broker::RecoveryManager& recoverer, + MessageMap& messageMap, + MessageQueueMap& messageQueueMap); + +private: + struct ProviderOptions : public qpid::Options + { + std::string connectString; + std::string catalogName; + + ProviderOptions(const std::string &name) + : qpid::Options(name), + catalogName("QpidStore") + { + const enum { NAMELEN = MAX_COMPUTERNAME_LENGTH + 1 }; + TCHAR myName[NAMELEN]; + DWORD myNameLen = NAMELEN; + GetComputerName(myName, &myNameLen); + connectString = "Data Source="; + connectString += myName; + connectString += "\\SQLEXPRESS;Integrated Security=SSPI"; + addOptions() + ("connect", + qpid::optValue(connectString, "STRING"), + "Connection string for the database to use. Will prepend " + "Provider=SQLOLEDB;") + ("catalog", + qpid::optValue(catalogName, "DB NAME"), + "Catalog (database) name") + ; + } + }; + ProviderOptions options; + + // Each thread has a separate connection to the database and also needs + // to manage its COM initialize/finalize individually. This is done by + // keeping a thread-specific State. + boost::thread_specific_ptr<State> dbState; + + State *initState(); + DatabaseConnection *initConnection(void); + void createDb(_ConnectionPtr conn, const std::string &name); +}; + +static MSSqlProvider static_instance_registers_plugin; + +void +MSSqlProvider::finalizeMe() +{ + dbState.reset(); +} + +MSSqlProvider::MSSqlProvider() + : options("MS SQL Provider options") +{ +} + +MSSqlProvider::~MSSqlProvider() +{ +} + +void +MSSqlProvider::earlyInitialize(Plugin::Target &target) +{ + MessageStorePlugin *store = dynamic_cast<MessageStorePlugin *>(&target); + if (store) { + // If the database init fails, report it and don't register; give + // the rest of the broker a chance to run. + // + // Don't try to initConnection() since that will fail if the + // database doesn't exist. Instead, try to open a connection without + // a database name, then search for the database. There's still a + // chance this provider won't be selected for the store too, so be + // be sure to close the database connection before return to avoid + // leaving a connection up that will not be used. + try { + initState(); // This initializes COM + std::auto_ptr<DatabaseConnection> db(new DatabaseConnection()); + db->open(options.connectString, ""); + _ConnectionPtr conn(*db); + _RecordsetPtr pCatalogs = NULL; + VariantHelper<std::string> catalogName(options.catalogName); + pCatalogs = conn->OpenSchema(adSchemaCatalogs, catalogName); + if (pCatalogs->EndOfFile) { + // Database doesn't exist; create it + QPID_LOG(notice, + "MSSQL: Creating database " + options.catalogName); + createDb(conn, options.catalogName); + } + else { + QPID_LOG(notice, + "MSSQL: Database located: " + options.catalogName); + } + if (pCatalogs) { + if (pCatalogs->State == adStateOpen) + pCatalogs->Close(); + pCatalogs = 0; + } + db->close(); + store->providerAvailable("MSSQL", this); + } + catch (qpid::Exception &e) { + QPID_LOG(error, e.what()); + return; + } + store->addFinalizer(boost::bind(&MSSqlProvider::finalizeMe, this)); + } +} + +void +MSSqlProvider::initialize(Plugin::Target& target) +{ +} + +void +MSSqlProvider::activate(MessageStorePlugin &store) +{ + QPID_LOG(info, "MS SQL Provider is up"); +} + +void +MSSqlProvider::truncateInit(const bool pushDownStoreFiles) +{ +} + +void +MSSqlProvider::create(PersistableQueue& queue, + const qpid::framing::FieldTable& /*args needed for jrnl*/) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsQueues; + try { + db->beginTransaction(); + rsQueues.open(db, TblQueue); + rsQueues.add(queue); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error creating queue " + queue.getName(), e); + } +} + +/** + * Destroy a durable queue + */ +void +MSSqlProvider::destroy(PersistableQueue& queue) +{ + // MessageDeleter class for use with for_each, below. + class MessageDeleter { + BlobRecordset& msgs; + public: + explicit MessageDeleter(BlobRecordset& _msgs) : msgs(_msgs) {} + void operator()(uint64_t msgId) { msgs.remove(msgId); } + }; + + DatabaseConnection *db = initConnection(); + BlobRecordset rsQueues; + BindingRecordset rsBindings; + MessageRecordset rsMessages; + MessageMapRecordset rsMessageMaps; + try { + db->beginTransaction(); + rsQueues.open(db, TblQueue); + rsBindings.open(db, TblBinding); + rsMessages.open(db, TblMessage); + rsMessageMaps.open(db, TblMessageMap); + // Remove bindings first; the queue IDs can't be ripped out from + // under the references in the bindings table. Then remove the + // message->queue entries for the queue, also because the queue can't + // be deleted while there are references to it. If there are messages + // orphaned by removing the queue references, those messages can + // also be deleted. Lastly, the queue record can be removed. + rsBindings.removeForQueue(queue.getPersistenceId()); + std::vector<uint64_t> orphans; + rsMessageMaps.removeForQueue(queue.getPersistenceId(), orphans); + std::for_each(orphans.begin(), orphans.end(), + MessageDeleter(rsMessages)); + rsQueues.remove(queue); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error deleting queue " + queue.getName(), e); + } +} + +/** + * Record the existence of a durable exchange + */ +void +MSSqlProvider::create(const PersistableExchange& exchange, + const qpid::framing::FieldTable& args) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsExchanges; + try { + db->beginTransaction(); + rsExchanges.open(db, TblExchange); + rsExchanges.add(exchange); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error creating exchange " + exchange.getName(), e); + } +} + +/** + * Destroy a durable exchange + */ +void +MSSqlProvider::destroy(const PersistableExchange& exchange) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsExchanges; + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsExchanges.open(db, TblExchange); + rsBindings.open(db, TblBinding); + // Remove bindings first; the exchange IDs can't be ripped out from + // under the references in the bindings table. + rsBindings.removeForExchange(exchange.getPersistenceId()); + rsExchanges.remove(exchange); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error deleting exchange " + exchange.getName(), e); + } +} + +/** + * Record a binding + */ +void +MSSqlProvider::bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) +{ + DatabaseConnection *db = initConnection(); + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsBindings.open(db, TblBinding); + rsBindings.add(exchange.getPersistenceId(), + queue.getPersistenceId(), + key, + args); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error binding exchange " + exchange.getName() + + " to queue " + queue.getName(), e); + } +} + +/** + * Forget a binding + */ +void +MSSqlProvider::unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) +{ + DatabaseConnection *db = initConnection(); + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsBindings.open(db, TblBinding); + rsBindings.remove(exchange.getPersistenceId(), + queue.getPersistenceId(), + key, + args); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error unbinding exchange " + exchange.getName() + + " from queue " + queue.getName(), e); + } +} + +/** + * Record generic durable configuration + */ +void +MSSqlProvider::create(const PersistableConfig& config) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsConfigs; + try { + db->beginTransaction(); + rsConfigs.open(db, TblConfig); + rsConfigs.add(config); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error creating config " + config.getName(), e); + } +} + +/** + * Destroy generic durable configuration + */ +void +MSSqlProvider::destroy(const PersistableConfig& config) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsConfigs; + try { + db->beginTransaction(); + rsConfigs.open(db, TblConfig); + rsConfigs.remove(config); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error deleting config " + config.getName(), e); + } +} + +/** + * Stores a messages before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ +void +MSSqlProvider::stage(const boost::intrusive_ptr<PersistableMessage>& msg) +{ + DatabaseConnection *db = initConnection(); + MessageRecordset rsMessages; + try { + db->beginTransaction(); + rsMessages.open(db, TblMessage); + rsMessages.add(msg); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error staging message", e); + } +} + +/** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ +void +MSSqlProvider::destroy(PersistableMessage& msg) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsMessages; + try { + db->beginTransaction(); + rsMessages.open(db, TblMessage); + rsMessages.remove(msg); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error deleting message", e); + } +} + +/** + * Appends content to a previously staged message + */ +void +MSSqlProvider::appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data) +{ + DatabaseConnection *db = initConnection(); + MessageRecordset rsMessages; + try { + db->beginTransaction(); + rsMessages.open(db, TblMessage); + rsMessages.append(msg, data); + db->commitTransaction(); + } + catch(_com_error &e) { + db->rollbackTransaction(); + throw ADOException("Error appending to message", e); + } +} + +/** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ +void +MSSqlProvider::loadContent(const qpid::broker::PersistableQueue& /*queue*/, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length) +{ + // SQL store keeps all messages in one table, so we don't need the + // queue reference. + DatabaseConnection *db = initConnection(); + MessageRecordset rsMessages; + try { + rsMessages.open(db, TblMessage); + rsMessages.loadContent(msg, data, offset, length); + } + catch(_com_error &e) { + throw ADOException("Error loading message content", e); + } +} + +/** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * @param ctxt The transaction context under which this enqueue happens. + * @param msg The message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + */ +void +MSSqlProvider::enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) +{ + // If this enqueue is in the context of a transaction, use the specified + // transaction to nest a new transaction for this operation. However, if + // this is not in the context of a transaction, then just use the thread's + // DatabaseConnection with a ADO transaction. + DatabaseConnection *db = 0; + AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (ctxt); + if (atxn == 0) { + db = initConnection(); + db->beginTransaction(); + } + else { + (void)initState(); // Ensure this thread is initialized + db = atxn->dbConn(); + try { + atxn->begin(); + } + catch(_com_error &e) { + throw ADOException("Error queuing message", e); + } + } + + MessageRecordset rsMessages; + MessageMapRecordset rsMap; + try { + if (msg->getPersistenceId() == 0) { // Message itself not yet saved + rsMessages.open(db, TblMessage); + rsMessages.add(msg); + } + rsMap.open(db, TblMessageMap); + rsMap.add(msg->getPersistenceId(), queue.getPersistenceId()); + if (atxn) + atxn->commit(); + else + db->commitTransaction(); + } + catch(_com_error &e) { + if (atxn) + atxn->abort(); + else + db->rollbackTransaction(); + throw ADOException("Error queuing message", e); + } + msg->enqueueComplete(); +} + +/** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * @param ctxt The transaction context under which this dequeue happens. + * @param msg The message to dequeue + * @param queue The queue from which it is to be dequeued + */ +void +MSSqlProvider::dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) +{ + // If this dequeue is in the context of a transaction, use the specified + // transaction to nest a new transaction for this operation. However, if + // this is not in the context of a transaction, then just use the thread's + // DatabaseConnection with a ADO transaction. + DatabaseConnection *db = 0; + AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (ctxt); + if (atxn == 0) { + db = initConnection(); + db->beginTransaction(); + } + else { + (void)initState(); // Ensure this thread is initialized + db = atxn->dbConn(); + try { + atxn->begin(); + } + catch(_com_error &e) { + throw ADOException("Error queuing message", e); + } + } + + MessageMapRecordset rsMap; + MessageRecordset rsMessages; + try { + rsMap.open(db, TblMessageMap); + bool more = rsMap.remove(msg->getPersistenceId(), + queue.getPersistenceId()); + if (!more) { + rsMessages.open(db, TblMessage); + rsMessages.remove(msg); + } + if (atxn) + atxn->commit(); + else + db->commitTransaction(); + } + catch(_com_error &e) { + if (atxn) + atxn->abort(); + else + db->rollbackTransaction(); + throw ADOException("Error dequeuing message", e); + } + msg->dequeueComplete(); +} + +std::auto_ptr<qpid::broker::TransactionContext> +MSSqlProvider::begin() +{ + (void)initState(); // Ensure this thread is initialized + + // Transactions are associated with the Connection, so this transaction + // context needs its own connection. At the time of writing, single-phase + // transactions are dealt with completely on one thread, so we really + // could just use the thread-specific DatabaseConnection for this. + // However, that would introduce an ugly, hidden coupling, so play + // it safe and handle this just like a TPC transaction, which actually + // can be prepared and committed/aborted from different threads, + // making it a bad idea to try using the thread-local DatabaseConnection. + std::auto_ptr<DatabaseConnection> db(new DatabaseConnection); + db->open(options.connectString, options.catalogName); + std::auto_ptr<AmqpTransaction> tx(new AmqpTransaction(db)); + tx->begin(); + std::auto_ptr<qpid::broker::TransactionContext> tc(tx); + return tc; +} + +std::auto_ptr<qpid::broker::TPCTransactionContext> +MSSqlProvider::begin(const std::string& xid) +{ + (void)initState(); // Ensure this thread is initialized + std::auto_ptr<DatabaseConnection> db(new DatabaseConnection); + db->open(options.connectString, options.catalogName); + std::auto_ptr<AmqpTPCTransaction> tx(new AmqpTPCTransaction(db, xid)); + tx->begin(); + std::auto_ptr<qpid::broker::TPCTransactionContext> tc(tx); + return tc; +} + +void +MSSqlProvider::prepare(qpid::broker::TPCTransactionContext& txn) +{ + // The inner transactions used for the components of the TPC are done; + // nothing else to do but wait for the commit. +} + +void +MSSqlProvider::commit(qpid::broker::TransactionContext& txn) +{ + (void)initState(); // Ensure this thread is initialized + AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (&txn); + if (atxn == 0) + throw qpid::broker::InvalidTransactionContextException(); + atxn->commit(); +} + +void +MSSqlProvider::abort(qpid::broker::TransactionContext& txn) +{ + (void)initState(); // Ensure this thread is initialized + AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (&txn); + if (atxn == 0) + throw qpid::broker::InvalidTransactionContextException(); + atxn->abort(); +} + +// @TODO Much of this recovery code is way too similar... refactor to +// a recover template method on BlobRecordset. + +void +MSSqlProvider::recoverConfigs(qpid::broker::RecoveryManager& recoverer) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsConfigs; + rsConfigs.open(db, TblConfig); + _RecordsetPtr p = (_RecordsetPtr)rsConfigs; + if (p->BOF && p->EndOfFile) + return; // Nothing to do + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + // Recreate the Config instance and reset its ID. + broker::RecoverableConfig::shared_ptr config = + recoverer.recoverConfig(blob); + config->setPersistenceId(id); + p->MoveNext(); + } +} + +void +MSSqlProvider::recoverExchanges(qpid::broker::RecoveryManager& recoverer, + ExchangeMap& exchangeMap) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsExchanges; + rsExchanges.open(db, TblExchange); + _RecordsetPtr p = (_RecordsetPtr)rsExchanges; + if (p->BOF && p->EndOfFile) + return; // Nothing to do + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + // Recreate the Exchange instance, reset its ID, and remember the + // ones restored for matching up when recovering bindings. + broker::RecoverableExchange::shared_ptr exchange = + recoverer.recoverExchange(blob); + exchange->setPersistenceId(id); + exchangeMap[id] = exchange; + p->MoveNext(); + } +} + +void +MSSqlProvider::recoverQueues(qpid::broker::RecoveryManager& recoverer, + QueueMap& queueMap) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsQueues; + rsQueues.open(db, TblQueue); + _RecordsetPtr p = (_RecordsetPtr)rsQueues; + if (p->BOF && p->EndOfFile) + return; // Nothing to do + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + // Recreate the Queue instance and reset its ID. + broker::RecoverableQueue::shared_ptr queue = + recoverer.recoverQueue(blob); + queue->setPersistenceId(id); + queueMap[id] = queue; + p->MoveNext(); + } +} + +void +MSSqlProvider::recoverBindings(qpid::broker::RecoveryManager& recoverer, + const ExchangeMap& exchangeMap, + const QueueMap& queueMap) +{ + DatabaseConnection *db = initConnection(); + BindingRecordset rsBindings; + rsBindings.open(db, TblBinding); + rsBindings.recover(recoverer, exchangeMap, queueMap); +} + +void +MSSqlProvider::recoverMessages(qpid::broker::RecoveryManager& recoverer, + MessageMap& messageMap, + MessageQueueMap& messageQueueMap) +{ + DatabaseConnection *db = initConnection(); + MessageRecordset rsMessages; + rsMessages.open(db, TblMessage); + rsMessages.recover(recoverer, messageMap); + + MessageMapRecordset rsMessageMaps; + rsMessageMaps.open(db, TblMessageMap); + rsMessageMaps.recover(messageQueueMap); +} + +////////////// Internal Methods + +State * +MSSqlProvider::initState() +{ + State *state = dbState.get(); // See if thread has initialized + if (!state) { + state = new State; + dbState.reset(state); + } + return state; +} + +DatabaseConnection * +MSSqlProvider::initConnection(void) +{ + State *state = initState(); + if (state->dbConn != 0) + return state->dbConn; // And the DatabaseConnection is set up too + std::auto_ptr<DatabaseConnection> db(new DatabaseConnection); + db->open(options.connectString, options.catalogName); + state->dbConn = db.release(); + return state->dbConn; +} + +void +MSSqlProvider::createDb(_ConnectionPtr conn, const std::string &name) +{ + const std::string dbCmd = "CREATE DATABASE " + name; + const std::string useCmd = "USE " + name; + const std::string tableCmd = "CREATE TABLE "; + const std::string colSpecs = + " (persistenceId bigint PRIMARY KEY NOT NULL IDENTITY(1,1)," + " fieldTableBlob varbinary(MAX) NOT NULL)"; + const std::string bindingSpecs = + " (exchangeId bigint REFERENCES tblExchange(persistenceId) NOT NULL," + " queueId bigint REFERENCES tblQueue(persistenceId) NOT NULL," + " routingKey varchar(255)," + " fieldTableBlob varbinary(MAX))"; + const std::string messageMapSpecs = + " (messageId bigint REFERENCES tblMessage(persistenceId) NOT NULL," + " queueId bigint REFERENCES tblQueue(persistenceId) NOT NULL)"; + _variant_t unused; + _bstr_t dbStr = dbCmd.c_str(); + try { + conn->Execute(dbStr, &unused, adExecuteNoRecords); + _bstr_t useStr = useCmd.c_str(); + conn->Execute(useStr, &unused, adExecuteNoRecords); + std::string makeTable = tableCmd + TblQueue + colSpecs; + _bstr_t makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblExchange + colSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblConfig + colSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblMessage + colSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblBinding + bindingSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblMessageMap + messageMapSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + } + catch(_com_error &e) { + throw ADOException("MSSQL can't create " + name, e); + } +} + +void +MSSqlProvider::dump() +{ + // dump all db records to qpid_log + QPID_LOG(notice, "DB Dump: (not dumping anything)"); + // rsQueues.dump(); +} + + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp b/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp new file mode 100644 index 0000000000..68e174a2b0 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp @@ -0,0 +1,155 @@ +/* + * + * 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/Exception.h> +#include <qpid/log/Statement.h> +#include <qpid/store/StorageProvider.h> + +#include "MessageMapRecordset.h" +#include "VariantHelper.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +void +MessageMapRecordset::add(uint64_t messageId, uint64_t queueId) +{ + rs->AddNew(); + rs->Fields->GetItem("messageId")->Value = messageId; + rs->Fields->GetItem("queueId")->Value = queueId; + rs->Update(); +} + +bool +MessageMapRecordset::remove(uint64_t messageId, uint64_t queueId) +{ + // Look up all mappings for the specified message. Then scan + // for the specified queue and keep track of whether or not the + // message exists on any queue we are not looking for a well. + std::ostringstream filter; + filter << "messageId = " << messageId << std::ends; + rs->PutFilter (VariantHelper<std::string>(filter.str())); + if (rs->RecordCount == 0) + return false; + MessageMap m; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&m); + bool moreEntries = false, deleted = false; + rs->MoveFirst(); + // If the desired mapping gets deleted, and we already know there are + // other mappings for the message, don't bother finishing the scan. + while (!rs->EndOfFile && !(deleted && moreEntries)) { + if (m.queueId == queueId) { + rs->Delete(adAffectCurrent); + rs->Update(); + deleted = true; + } + else { + moreEntries = true; + } + rs->MoveNext(); + } + piAdoRecordBinding->Release(); + rs->Filter = ""; + return moreEntries; +} + +void +MessageMapRecordset::removeForQueue(uint64_t queueId, + std::vector<uint64_t>& orphaned) +{ + // Read all the messages queued on queueId and add them to the orphaned + // list. Then remove each one and learn if there are references to it + // from other queues. The ones without references are left in the + // orphaned list, others are removed. + std::ostringstream filter; + filter << "queueId = " << queueId << std::ends; + rs->PutFilter (VariantHelper<std::string>(filter.str())); + MessageMap m; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&m); + while (!rs->EndOfFile) { + orphaned.push_back(m.messageId); + rs->MoveNext(); + } + piAdoRecordBinding->Release(); + rs->Filter = ""; // Remove filter on queueId + rs->Requery(adOptionUnspecified); // Get the entire map again + + // Now delete all the messages on this queue; any message that still has + // references from other queue(s) is removed from orphaned. + for (std::vector<uint64_t>::iterator i = orphaned.begin(); + i != orphaned.end(); + ) { + if (remove(*i, queueId)) + i = orphaned.erase(i); // There are other refs to message *i + else + ++i; + } +} + +void +MessageMapRecordset::recover(MessageQueueMap& msgMap) +{ + if (rs->BOF && rs->EndOfFile) + return; // Nothing to do + rs->MoveFirst(); + MessageMap b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + while (!rs->EndOfFile) { + msgMap[b.messageId].push_back(b.queueId); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +void +MessageMapRecordset::dump() +{ + Recordset::dump(); + if (rs->EndOfFile && rs->BOF) // No records + return; + rs->MoveFirst(); + + MessageMap m; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&m); + + while (!rs->EndOfFile) { + QPID_LOG(notice, "msg " << m.messageId << " on queue " << m.queueId); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h b/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h new file mode 100644 index 0000000000..475137b18b --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h @@ -0,0 +1,77 @@ +#ifndef QPID_STORE_MSSQL_MESSAGEMAPRECORDSET_H +#define QPID_STORE_MSSQL_MESSAGEMAPRECORDSET_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 <icrsint.h> +#include <vector> +#include "Recordset.h" +#include <qpid/broker/RecoveryManager.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class MessageMapRecordset + * + * Class for the message map (message -> queue) records. + */ +class MessageMapRecordset : public Recordset { + + class MessageMap : public CADORecordBinding { + BEGIN_ADO_BINDING(MessageMap) + ADO_FIXED_LENGTH_ENTRY2(1, adBigInt, messageId, FALSE) + ADO_FIXED_LENGTH_ENTRY2(2, adBigInt, queueId, FALSE) + END_ADO_BINDING() + + public: + uint64_t messageId; + uint64_t queueId; + }; + +public: + // Add a new mapping + void add(uint64_t messageId, uint64_t queueId); + + // Remove a specific mapping. Returns true if the message is still + // enqueued on at least one other queue; false if the message no longer + // exists on any other queues. + bool remove(uint64_t messageId, uint64_t queueId); + + // Remove mappings for all messages on a specified queue. If there are + // messages that were only on the specified queue and are, therefore, + // orphaned now, return them in the orphaned vector. The orphaned + // messages can be deleted permanently as they are not referenced on + // any other queues. + void removeForQueue(uint64_t queueId, std::vector<uint64_t>& orphaned); + + // Recover the mappings of message ID -> vector<queue ID>. + void recover(MessageQueueMap& msgMap); + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_MESSAGEMAPRECORDSET_H */ diff --git a/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp b/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp new file mode 100644 index 0000000000..b62a333df6 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/MessageRecordset.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/Exception.h> +#include <qpid/log/Statement.h> + +#include "MessageRecordset.h" +#include "BlobAdapter.h" +#include "BlobEncoder.h" +#include "VariantHelper.h" + +#include <boost/intrusive_ptr.hpp> + +class qpid::broker::PersistableMessage; + +namespace qpid { +namespace store { +namespace ms_sql { + +void +MessageRecordset::add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg) +{ + BlobEncoder blob (msg); // Marshall headers and content to a blob + rs->AddNew(); + rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob); + rs->Update(); + uint64_t id = rs->Fields->Item["persistenceId"]->Value; + msg->setPersistenceId(id); +} + +void +MessageRecordset::append(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + const std::string& data) +{ + // Look up the message by its Id + std::ostringstream filter; + filter << "persistenceId = " << msg->getPersistenceId() << std::ends; + rs->PutFilter (VariantHelper<std::string>(filter.str())); + if (rs->RecordCount == 0) { + throw Exception("Can't append to message not stored in database"); + } + BlobEncoder blob (data); // Marshall string data to a blob + rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob); + rs->Update(); +} + +void +MessageRecordset::remove(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg) +{ + BlobRecordset::remove(msg->getPersistenceId()); +} + +void +MessageRecordset::loadContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length) +{ + // Look up the message by its Id + std::ostringstream filter; + filter << "persistenceId = " << msg->getPersistenceId() << std::ends; + rs->PutFilter (VariantHelper<std::string>(filter.str())); + if (rs->RecordCount == 0) { + throw Exception("Can't load message not stored in database"); + } + + // NOTE! If this code needs to change, please verify the encoding + // code in BlobEncoder. + long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize; + uint32_t headerSize; + const size_t headerFieldLength = sizeof(headerSize); + BlobAdapter blob(headerFieldLength); + blob = + rs->Fields->Item["fieldTableBlob"]->GetChunk((long)headerFieldLength); + headerSize = ((qpid::framing::Buffer&)blob).getLong(); + + // GetChunk always begins reading where the previous GetChunk left off, + // so we can't just tell it to ignore the header and read the data. + // So, read the header plus the offset, plus the desired data, then + // copy the desired data to the supplied string. If this ends up asking + // for more than is available in the field, reduce it to what's there. + long getSize = headerSize + offset + length; + if (getSize + (long)headerFieldLength > blobSize) { + size_t reduce = (getSize + headerFieldLength) - blobSize; + getSize -= reduce; + length -= reduce; + } + BlobAdapter header_plus(getSize); + header_plus = rs->Fields->Item["fieldTableBlob"]->GetChunk(getSize); + uint8_t *throw_away = new uint8_t[headerSize + offset]; + ((qpid::framing::Buffer&)header_plus).getRawData(throw_away, headerSize + offset); + delete throw_away; + ((qpid::framing::Buffer&)header_plus).getRawData(data, length); +} + +void +MessageRecordset::recover(qpid::broker::RecoveryManager& recoverer, + std::map<uint64_t, broker::RecoverableMessage::shared_ptr>& messageMap) +{ + if (rs->BOF && rs->EndOfFile) + return; // Nothing to do + rs->MoveFirst(); + Binding b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + while (!rs->EndOfFile) { + // The blob was written as normal, but with the header length + // prepended in a uint32_t. Due to message staging threshold + // limits, the header may be all that's read in; get it first, + // recover that message header, then see if the rest is needed. + // + // NOTE! If this code needs to change, please verify the encoding + // code in BlobEncoder. + long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize; + uint32_t headerSize; + const size_t headerFieldLength = sizeof(headerSize); + BlobAdapter blob(headerFieldLength); + blob = + rs->Fields->Item["fieldTableBlob"]->GetChunk((long)headerFieldLength); + headerSize = ((qpid::framing::Buffer&)blob).getLong(); + BlobAdapter header(headerSize); + header = rs->Fields->Item["fieldTableBlob"]->GetChunk(headerSize); + broker::RecoverableMessage::shared_ptr msg; + msg = recoverer.recoverMessage(header); + msg->setPersistenceId(b.messageId); + messageMap[b.messageId] = msg; + + // Now, do we need the rest of the content? + long contentLength = blobSize - headerFieldLength - headerSize; + if (msg->loadContent(contentLength)) { + BlobAdapter content(contentLength); + content = + rs->Fields->Item["fieldTableBlob"]->GetChunk(contentLength); + msg->decodeContent(content); + } + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +void +MessageRecordset::dump() +{ + Recordset::dump(); + if (rs->EndOfFile && rs->BOF) // No records + return; + rs->MoveFirst(); + + Binding b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + + while (VARIANT_FALSE == rs->EndOfFile) { + QPID_LOG(notice, "Msg " << b.messageId); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/MessageRecordset.h b/cpp/src/qpid/store/ms-sql/MessageRecordset.h new file mode 100644 index 0000000000..698b2561fe --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/MessageRecordset.h @@ -0,0 +1,85 @@ +#ifndef QPID_STORE_MSSQL_MESSAGERECORDSET_H +#define QPID_STORE_MSSQL_MESSAGERECORDSET_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 <icrsint.h> +#include "BlobRecordset.h" +#include <qpid/broker/PersistableMessage.h> +#include <qpid/broker/RecoveryManager.h> +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class MessageRecordset + * + * Class for storing and recovering messages. Messages are primarily blobs + * and handled similarly. However, messages larger than the staging threshold + * are not contained completely in memory; they're left mostly in the store + * and the header is held in memory. So when the message "blob" is saved, + * an additional size-of-the-header field is prepended to the blob. + * On recovery, the size-of-the-header is used to get only what's needed + * until it's determined if the entire message is to be recovered to memory. + */ +class MessageRecordset : public BlobRecordset { + class Binding : public CADORecordBinding { + BEGIN_ADO_BINDING(Binding) + ADO_FIXED_LENGTH_ENTRY2(1, adBigInt, messageId, FALSE) + END_ADO_BINDING() + + public: + uint64_t messageId; + }; + +public: + // Store a message. Store the header size (4 bytes) then the regular + // blob comprising the message. + void add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg); + + // Append additional content to an existing message. + void append(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + const std::string& data); + + // Remove an existing message + void remove(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg); + + // Load all or part of a stored message. This skips the header parts and + // loads content. + void loadContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + + // Recover messages and save a map of those recovered. + void recover(qpid::broker::RecoveryManager& recoverer, + std::map<uint64_t, broker::RecoverableMessage::shared_ptr>& messageMap); + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_MESSAGERECORDSET_H */ diff --git a/cpp/src/qpid/store/ms-sql/Recordset.cpp b/cpp/src/qpid/store/ms-sql/Recordset.cpp new file mode 100644 index 0000000000..e1a5158c87 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/Recordset.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/Exception.h> +#include <qpid/log/Statement.h> + +#include "Recordset.h" +#include "BlobEncoder.h" +#include "DatabaseConnection.h" +#include "VariantHelper.h" + +namespace { +inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);}; +} + +namespace qpid { +namespace store { +namespace ms_sql { + +#if 0 +Recordset::Iterator::Iterator(Recordset& _rs) : rs(_rs) +{ + rs->MoveFirst(); + setCurrent(); +} + +std::pair<uint64_t, BlobAdapter>& +Recordset::Iterator::dereference() const +{ + return const_cast<std::pair<uint64_t, BlobAdapter> >(current); +} + +void +Recordset::Iterator::increment() +{ + rs->MoveNext(); + setCurrent(); +} + +bool +Recordset::Iterator::equal(const Iterator& x) const +{ + return current.first == x.current.first; +} + +void +Recordset::Iterator::setCurrent() +{ + if (!rs->EndOfFile) { + uint64_t id = rs->Fields->Item["persistenceId"]->Value; + long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = rs->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + current = std::make_pair(id, blob); + } + else { + current.first = 0; + } +} +#endif + +void +Recordset::open(DatabaseConnection* conn, const std::string& table) +{ + _ConnectionPtr p = *conn; + TESTHR(rs.CreateInstance(__uuidof(::Recordset))); + // Client-side cursors needed to get access to newly added + // identity column immediately. Recordsets need this to get the + // persistence ID for the broker objects. + rs->CursorLocation = adUseClient; + rs->Open(table.c_str(), + _variant_t((IDispatch *)p, true), + adOpenStatic, + adLockOptimistic, + adCmdTable); + tableName = table; +} + +void +Recordset::close() +{ + if (rs && rs->State == adStateOpen) + rs->Close(); + rs = 0; +} + +void +Recordset::requery() +{ + // Restore the recordset to reflect all current records. + rs->Filter = ""; + rs->Requery(-1); +} + +void +Recordset::dump() +{ + long count = rs->RecordCount; + QPID_LOG(notice, "DB Dump: " + tableName << + ": " << count << " records"); +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/Recordset.h b/cpp/src/qpid/store/ms-sql/Recordset.h new file mode 100644 index 0000000000..3631838aa8 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/Recordset.h @@ -0,0 +1,101 @@ +#ifndef QPID_STORE_MSSQL_RECORDSET_H +#define QPID_STORE_MSSQL_RECORDSET_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. + * + */ + + +// Bring in ADO 2.8 (yes, I know it says "15", but that's it...) +#import "C:\Program Files\Common Files\System\ado\msado15.dll" \ + no_namespace rename("EOF", "EndOfFile") +#include <comdef.h> +#include <comutil.h> +#include <string> +#if 0 +#include <utility> +#endif + +namespace qpid { +namespace store { +namespace ms_sql { + +class DatabaseConnection; + +/** + * @class Recordset + * + * Represents an ADO Recordset, abstracting out the common operations needed + * on the common tables used that have 2 fields, persistence ID and blob. + */ +class Recordset { +protected: + _RecordsetPtr rs; + DatabaseConnection* dbConn; + std::string tableName; + +public: + +#if 0 + /** + * Iterator support for walking through the recordset. + * If I need to try this again, I'd look at Recordset cloning. + */ + class Iterator : public boost::iterator_facade< + Iterator, std::pair<uint64_t, BlobAdapter>, boost::random_access_traversal_tag> + { + public: + Iterator() : rs(0) { } + Iterator(Recordset& _rs); + + private: + friend class boost::iterator_core_access; + + std::pair<uint64_t, BlobAdapter>& dereference() const; + void increment(); + bool equal(const Iterator& x) const; + + _RecordsetPtr rs; + std::pair<uint64_t, BlobAdapter> current; + + void setCurrent(); + }; + + friend class Iterator; +#endif + + Recordset() : rs(0) {} + virtual ~Recordset() { close(); } + void open(DatabaseConnection* conn, const std::string& table); + void close(); + void requery(); + operator _RecordsetPtr () { return rs; } +#if 0 + Iterator begin() { Iterator iter(*this); return iter; } + Iterator end() { Iterator iter; return iter; } +#endif + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_RECORDSET_H */ diff --git a/cpp/src/qpid/store/ms-sql/State.cpp b/cpp/src/qpid/store/ms-sql/State.cpp new file mode 100644 index 0000000000..720603dd11 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/State.cpp @@ -0,0 +1,45 @@ +/* + * + * 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 "State.h" +#include "DatabaseConnection.h" +#include "Exception.h" +#include <comdef.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +State::State() : dbConn(0) +{ + HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (hr != S_OK && hr != S_FALSE) + throw Exception("Error initializing COM"); +} + +State::~State() +{ + if (dbConn) + delete dbConn; + ::CoUninitialize(); +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/client/AckMode.h b/cpp/src/qpid/store/ms-sql/State.h index b411c322d8..6350bc5bd2 100644 --- a/cpp/src/qpid/client/AckMode.h +++ b/cpp/src/qpid/store/ms-sql/State.h @@ -1,6 +1,5 @@ -#ifndef _client_AckMode_h -#define _client_AckMode_h - +#ifndef QPID_STORE_MSSQL_STATE_H +#define QPID_STORE_MSSQL_STATE_H /* * @@ -24,30 +23,30 @@ */ namespace qpid { -namespace client { +namespace store { +namespace ms_sql { + +class DatabaseConnection; /** - * DEPRECATED - * - * The available acknowledgement modes for Channel (now also deprecated). + * @struct State + * + * Represents a thread's state for accessing ADO and the database. + * Creating an instance of State initializes COM for this thread, and + * destroying it uninitializes COM. There's also a DatabaseConnection + * for this thread's default access to the database. More DatabaseConnections + * can always be created, but State has one that can always be used by + * the thread whose state is represented. + * + * This class is intended to be one-per-thread, so it should be accessed + * via thread-specific storage. */ -enum AckMode { - /** No acknowledgement will be sent, broker can - discard messages as soon as they are delivered - to a consumer using this mode. **/ - NO_ACK = 0, - /** Each message will be automatically - acknowledged as soon as it is delivered to the - application. **/ - AUTO_ACK = 1, - /** Acknowledgements will be sent automatically, - but not for each message. **/ - LAZY_ACK = 2, - /** The application is responsible for explicitly - acknowledging messages. **/ - CLIENT_ACK = 3 +struct State { + State(); + ~State(); + DatabaseConnection *dbConn; }; -}} // namespace qpid::client +}}} // namespace qpid::store::ms_sql -#endif +#endif /* QPID_STORE_MSSQL_STATE_H */ diff --git a/cpp/src/qpid/store/ms-sql/VariantHelper.cpp b/cpp/src/qpid/store/ms-sql/VariantHelper.cpp new file mode 100644 index 0000000000..786724f031 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/VariantHelper.cpp @@ -0,0 +1,59 @@ +/* + * + * 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 "VariantHelper.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +template <class Wrapped> +VariantHelper<Wrapped>::VariantHelper() +{ + var.vt = VT_EMPTY; +} + +template <class Wrapped> +VariantHelper<Wrapped>::operator const _variant_t& () const +{ + return var; +} + +// Specialization for using _variant_t to wrap a std::string +VariantHelper<std::string>::VariantHelper(const std::string &init) +{ + var.SetString(init.c_str()); +} + +VariantHelper<std::string>& +VariantHelper<std::string>::operator=(const std::string &rhs) +{ + var.SetString(rhs.c_str()); + return *this; +} + +VariantHelper<std::string>::operator const _variant_t& () const +{ + return var; +} + +}}} // namespace qpid::store::ms_sql diff --git a/cpp/src/qpid/store/ms-sql/VariantHelper.h b/cpp/src/qpid/store/ms-sql/VariantHelper.h new file mode 100644 index 0000000000..723dbc3b76 --- /dev/null +++ b/cpp/src/qpid/store/ms-sql/VariantHelper.h @@ -0,0 +1,61 @@ +#ifndef QPID_STORE_MSSQL_VARIANTHELPER_H +#define QPID_STORE_MSSQL_VARIANTHELPER_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 <comutil.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class VariantHelper + * + * Class template to wrap the details of working with _variant_t objects. + */ +template <class Wrapped> class VariantHelper { +private: + _variant_t var; + +public: + VariantHelper(); + VariantHelper(const Wrapped &init); + + VariantHelper& operator =(const Wrapped& rhs); + operator const _variant_t& () const; +}; + +// Specialization for using _variant_t to wrap a std::string +template<> class VariantHelper<std::string> { +private: + _variant_t var; + +public: + VariantHelper(const std::string &init); + VariantHelper& operator =(const std::string& rhs); + operator const _variant_t& () const; +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_VARIANTHELPER_H */ diff --git a/cpp/src/qpid/sys/AggregateOutput.cpp b/cpp/src/qpid/sys/AggregateOutput.cpp index 2fad28c381..709d3bc640 100644 --- a/cpp/src/qpid/sys/AggregateOutput.cpp +++ b/cpp/src/qpid/sys/AggregateOutput.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 @@ -25,44 +25,67 @@ namespace qpid { namespace sys { - -void AggregateOutput::activateOutput() -{ - control.activateOutput(); -} + +AggregateOutput::AggregateOutput(OutputControl& c) : busy(false), control(c) {} + +void AggregateOutput::abort() { control.abort(); } + +void AggregateOutput::activateOutput() { control.activateOutput(); } + +void AggregateOutput::giveReadCredit(int32_t credit) { control.giveReadCredit(credit); } bool AggregateOutput::hasOutput() { - for (TaskList::const_iterator i = tasks.begin(); i != tasks.end(); ++i) - if ((*i)->hasOutput()) return true; - return false; + Mutex::ScopedLock l(lock); + return !tasks.empty(); } -bool AggregateOutput::doOutput() -{ - bool result = false; - if (!tasks.empty()) { - if (next >= tasks.size()) next = next % tasks.size(); - - size_t start = next; - //loop until a task generated some output - while (!result) { - result = tasks[next++]->doOutput(); - if (next >= tasks.size()) next = next % tasks.size(); - if (start == next) break; +// Clear the busy flag and notify waiting threads in destructor. +struct ScopedBusy { + bool& flag; + Monitor& monitor; + ScopedBusy(bool& f, Monitor& m) : flag(f), monitor(m) { f = true; } + ~ScopedBusy() { flag = false; monitor.notifyAll(); } +}; + +bool AggregateOutput::doOutput() { + Mutex::ScopedLock l(lock); + ScopedBusy sb(busy, lock); + + while (!tasks.empty()) { + OutputTask* t=tasks.front(); + tasks.pop_front(); + bool didOutput; + { + // Allow concurrent call to addOutputTask. + // removeOutputTask will wait till !busy before removing a task. + Mutex::ScopedUnlock u(lock); + didOutput = t->doOutput(); + } + if (didOutput) { + tasks.push_back(t); + return true; } } - return result; + return false; +} + +void AggregateOutput::addOutputTask(OutputTask* task) { + Mutex::ScopedLock l(lock); + tasks.push_back(task); } -void AggregateOutput::addOutputTask(OutputTask* t) -{ - tasks.push_back(t); +void AggregateOutput::removeOutputTask(OutputTask* task) { + Mutex::ScopedLock l(lock); + while (busy) lock.wait(); + tasks.erase(std::remove(tasks.begin(), tasks.end(), task), tasks.end()); } - -void AggregateOutput::removeOutputTask(OutputTask* t) + +void AggregateOutput::removeAll() { - TaskList::iterator i = std::find(tasks.begin(), tasks.end(), t); - if (i != tasks.end()) tasks.erase(i); + Mutex::ScopedLock l(lock); + while (busy) lock.wait(); + tasks.clear(); } + }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/AggregateOutput.h b/cpp/src/qpid/sys/AggregateOutput.h index 02a53ed50b..71ad713eb7 100644 --- a/cpp/src/qpid/sys/AggregateOutput.h +++ b/cpp/src/qpid/sys/AggregateOutput.h @@ -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 @@ -21,35 +21,58 @@ #ifndef _AggregateOutput_ #define _AggregateOutput_ -#include <vector> -#include "Mutex.h" -#include "OutputControl.h" -#include "OutputTask.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/OutputControl.h" +#include "qpid/sys/OutputTask.h" +#include "qpid/CommonImportExport.h" + +#include <algorithm> +#include <deque> namespace qpid { namespace sys { - class AggregateOutput : public OutputTask, public OutputControl - { - typedef std::vector<OutputTask*> TaskList; - - TaskList tasks; - size_t next; - OutputControl& control; - - public: - AggregateOutput(OutputControl& c) : next(0), control(c) {}; - //this may be called on any thread - void activateOutput(); - //all the following will be called on the same thread - bool doOutput(); - bool hasOutput(); - void addOutputTask(OutputTask* t); - void removeOutputTask(OutputTask* t); - }; - -} -} +/** + * Holds a collection of output tasks, doOutput picks the next one to execute. + * + * Tasks are automatically removed if their doOutput() or hasOutput() returns false. + * + * Thread safe. addOutputTask may be called in one connection thread while + * doOutput is called in another. + */ + +class AggregateOutput : public OutputTask, public OutputControl +{ + typedef std::deque<OutputTask*> TaskList; + + Monitor lock; + TaskList tasks; + bool busy; + OutputControl& control; + + public: + QPID_COMMON_EXTERN AggregateOutput(OutputControl& c); + + // 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. + QPID_COMMON_EXTERN bool doOutput(); + QPID_COMMON_EXTERN bool hasOutput(); + QPID_COMMON_EXTERN void removeOutputTask(OutputTask* t); + QPID_COMMON_EXTERN void removeAll(); + + /** Apply f to each OutputTask* in the tasks list */ + template <class F> void eachOutput(F f) { + Mutex::ScopedLock l(lock); + std::for_each(tasks.begin(), tasks.end(), f); + } +}; + +}} // namespace qpid::sys #endif diff --git a/cpp/src/qpid/sys/AsynchIO.h b/cpp/src/qpid/sys/AsynchIO.h index ff7823e00d..2a41f0a7d1 100644 --- a/cpp/src/qpid/sys/AsynchIO.h +++ b/cpp/src/qpid/sys/AsynchIO.h @@ -21,15 +21,16 @@ * */ -#include "Dispatcher.h" - +#include "qpid/sys/IntegerTypes.h" +#include "qpid/CommonImportExport.h" #include <boost/function.hpp> -#include <deque> +#include <boost/shared_ptr.hpp> namespace qpid { namespace sys { class Socket; +class Poller; /* * Asynchronous acceptor: accepts connections then does a callback with the @@ -39,44 +40,36 @@ class AsynchAcceptor { public: typedef boost::function1<void, const Socket&> Callback; -private: - Callback acceptedCallback; - DispatchHandle handle; - const Socket& socket; - -public: - AsynchAcceptor(const Socket& s, Callback callback); - void start(Poller::shared_ptr poller); - -private: - void readable(DispatchHandle& handle); + QPID_COMMON_EXTERN static AsynchAcceptor* create(const Socket& s, Callback callback); + virtual ~AsynchAcceptor() {}; + virtual void start(boost::shared_ptr<Poller> poller) = 0; }; /* * Asynchronous connector: starts the process of initiating a connection and * invokes a callback when completed or failed. */ -class AsynchConnector : private DispatchHandle { +class AsynchConnector { public: typedef boost::function1<void, const Socket&> ConnectedCallback; - typedef boost::function2<void, int, std::string> FailedCallback; - -private: - ConnectedCallback connCallback; - FailedCallback failCallback; - const Socket& socket; - -public: - AsynchConnector(const Socket& socket, - Poller::shared_ptr poller, - std::string hostname, - uint16_t port, - ConnectedCallback connCb, - FailedCallback failCb = 0); - -private: - void connComplete(DispatchHandle& handle); - void failure(int, std::string); + typedef boost::function3<void, const Socket&, int, const std::string&> FailedCallback; + + // Call create() to allocate a new AsynchConnector object with the + // specified poller, addressing, and callbacks. + // This method is implemented in platform-specific code to + // create a correctly typed object. The platform code also manages + // deletes. To correctly manage heaps when needed, the allocate and + // delete should both be done from the same class/library. + QPID_COMMON_EXTERN static AsynchConnector* create(const Socket& s, + boost::shared_ptr<Poller> poller, + std::string hostname, + uint16_t port, + ConnectedCallback connCb, + FailedCallback failCb); + +protected: + AsynchConnector() {} + virtual ~AsynchConnector() {} }; struct AsynchIOBufferBase { @@ -99,16 +92,14 @@ struct AsynchIOBufferBase { /* * 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. + * 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 + * a callback for when writing is "idle" (ie fd is writable, but nothing + * to write). */ -class AsynchIO : private DispatchHandle { +class AsynchIO { public: typedef AsynchIOBufferBase BufferBase; @@ -118,47 +109,40 @@ public: typedef boost::function2<void, AsynchIO&, const Socket&> ClosedCallback; typedef boost::function1<void, AsynchIO&> BuffersEmptyCallback; typedef boost::function1<void, AsynchIO&> IdleCallback; - -private: - ReadCallback readCallback; - EofCallback eofCallback; - DisconnectCallback disCallback; - ClosedCallback closedCallback; - BuffersEmptyCallback emptyCallback; - IdleCallback idleCallback; - const Socket& socket; - std::deque<BufferBase*> bufferQueue; - std::deque<BufferBase*> writeQueue; - 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; - + typedef boost::function1<void, AsynchIO&> RequestCallback; + + // Call create() to allocate a new AsynchIO object with the specified + // callbacks. This method is implemented in platform-specific code to + // create a correctly typed object. The platform code also manages + // deletes. To correctly manage heaps when needed, the allocate and + // delete should both be done from the same class/library. + QPID_COMMON_EXTERN static AsynchIO* create(const Socket& s, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb = 0, + BuffersEmptyCallback eCb = 0, + IdleCallback iCb = 0); public: - AsynchIO(const Socket& s, - ReadCallback rCb, EofCallback eofCb, DisconnectCallback disCb, - ClosedCallback cCb = 0, BuffersEmptyCallback eCb = 0, IdleCallback iCb = 0); - void queueForDeletion(); - - void start(Poller::shared_ptr poller); - void queueReadBuffer(BufferBase* buff); - void unread(BufferBase* buff); - void queueWrite(BufferBase* buff); - void notifyPendingWrite(); - void queueWriteClose(); - bool writeQueueEmpty() { return writeQueue.empty(); } - BufferBase* getQueuedBuffer(); - -private: - ~AsynchIO(); - void readable(DispatchHandle& handle); - void writeable(DispatchHandle& handle); - void disconnected(DispatchHandle& handle); - void close(DispatchHandle& handle); + virtual void queueForDeletion() = 0; + + virtual void start(boost::shared_ptr<Poller> poller) = 0; + virtual void queueReadBuffer(BufferBase* buff) = 0; + virtual void unread(BufferBase* buff) = 0; + virtual void queueWrite(BufferBase* buff) = 0; + 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; + +protected: + // Derived class manages lifetime; must be constructed using the + // static create() method. Deletes not allowed from outside. + AsynchIO() {} + virtual ~AsynchIO() {} }; }} diff --git a/cpp/src/qpid/sys/AsynchIOHandler.cpp b/cpp/src/qpid/sys/AsynchIOHandler.cpp index 886dbc8f43..f658b7d50f 100644 --- a/cpp/src/qpid/sys/AsynchIOHandler.cpp +++ b/cpp/src/qpid/sys/AsynchIOHandler.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 @@ -19,13 +19,15 @@ * */ -#include "AsynchIOHandler.h" +#include "qpid/sys/AsynchIOHandler.h" #include "qpid/sys/AsynchIO.h" #include "qpid/sys/Socket.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 { @@ -44,7 +46,8 @@ AsynchIOHandler::AsynchIOHandler(std::string id, ConnectionCodec::Factory* f) : factory(f), codec(0), readError(false), - isClient(false) + isClient(false), + readCredit(InfiniteCredit) {} AsynchIOHandler::~AsynchIOHandler() { @@ -70,19 +73,61 @@ void AsynchIOHandler::write(const framing::ProtocolInitiation& data) buff = new Buff; framing::Buffer out(buff->bytes, buff->byteCount); data.encode(out); - buff->dataCount = data.size(); + buff->dataCount = data.encodedSize(); aio->queueWrite(buff); } +void AsynchIOHandler::abort() { + // Don't disconnect if we're already disconnecting + if (!readError) { + aio->requestCallback(boost::bind(&AsynchIOHandler::eof, this, _1)); + } +} + 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(); +} + void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) { if (readError) { 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(); + } + } + } + size_t decoded = 0; if (codec) { // Already initiated try { @@ -99,13 +144,13 @@ void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) { decoded = in.getPosition(); QPID_LOG(debug, "RECV [" << identifier << "] INIT(" << protocolInit << ")"); try { - codec = factory->create(protocolInit.getVersion(), *this, identifier); + codec = factory->create(protocolInit.getVersion(), *this, identifier, 0); if (!codec) { //TODO: may still want to revise this... //send valid version header & close connection. write(framing::ProtocolInitiation(framing::highestProtocolVersion)); readError = true; - aio->queueWriteClose(); + aio->queueWriteClose(); } } catch (const std::exception& e) { QPID_LOG(error, e.what()); @@ -130,11 +175,12 @@ void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) { void AsynchIOHandler::eof(AsynchIO&) { QPID_LOG(debug, "DISCONNECTED [" << identifier << "]"); if (codec) codec->closed(); + readError = true; aio->queueWriteClose(); } void AsynchIOHandler::closedSocket(AsynchIO&, const Socket& s) { - // If we closed with data still to send log a warning + // 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)"); } @@ -154,21 +200,29 @@ void AsynchIOHandler::nobuffs(AsynchIO&) { void AsynchIOHandler::idle(AsynchIO&){ if (isClient && codec == 0) { - codec = factory->create(*this, identifier); + codec = factory->create(*this, identifier, 0); write(framing::ProtocolInitiation(codec->getVersion())); return; } if (codec == 0) return; - if (codec->canEncode()) { - // Try and get a queued buffer if not then construct new one - AsynchIO::BufferBase* buff = aio->getQueuedBuffer(); - if (!buff) buff = new Buff; - size_t encoded=codec->encode(buff->bytes, buff->byteCount); - buff->dataCount = encoded; - aio->queueWrite(buff); - } - if (codec->isClosed()) + try { + if (codec->canEncode()) { + // Try and get a queued buffer if not then construct new one + AsynchIO::BufferBase* buff = aio->getQueuedBuffer(); + if (!buff) buff = new Buff; + size_t encoded=codec->encode(buff->bytes, buff->byteCount); + buff->dataCount = encoded; + aio->queueWrite(buff); + } + if (codec->isClosed()) { + readError = true; + aio->queueWriteClose(); + } + } catch (const std::exception& e) { + QPID_LOG(error, e.what()); + readError = true; aio->queueWriteClose(); + } } }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/AsynchIOHandler.h b/cpp/src/qpid/sys/AsynchIOHandler.h index 26e2cf4c5c..e1885bac79 100644 --- a/cpp/src/qpid/sys/AsynchIOHandler.h +++ b/cpp/src/qpid/sys/AsynchIOHandler.h @@ -9,9 +9,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 @@ -21,8 +21,11 @@ * */ -#include "OutputControl.h" -#include "ConnectionCodec.h" +#include "qpid/sys/OutputControl.h" +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/Mutex.h" +#include "qpid/CommonImportExport.h" namespace qpid { @@ -33,7 +36,7 @@ namespace framing { namespace sys { class AsynchIO; -class AsynchIOBufferBase; +struct AsynchIOBufferBase; class Socket; class AsynchIOHandler : public OutputControl { @@ -43,29 +46,33 @@ class AsynchIOHandler : public OutputControl { ConnectionCodec* codec; bool readError; bool isClient; + AtomicValue<int32_t> readCredit; + static const int32_t InfiniteCredit = -1; + Mutex creditLock; void write(const framing::ProtocolInitiation&); public: - AsynchIOHandler(std::string id, ConnectionCodec::Factory* f); - ~AsynchIOHandler(); - void init(AsynchIO* a, int numBuffs); + QPID_COMMON_EXTERN AsynchIOHandler(std::string id, ConnectionCodec::Factory* f); + QPID_COMMON_EXTERN ~AsynchIOHandler(); + QPID_COMMON_EXTERN void init(AsynchIO* a, int numBuffs); - void setClient() { isClient = true; } + QPID_COMMON_EXTERN void setClient() { isClient = true; } // Output side - void close(); - void activateOutput(); + QPID_COMMON_EXTERN void abort(); + QPID_COMMON_EXTERN void activateOutput(); + QPID_COMMON_EXTERN void giveReadCredit(int32_t credit); // Input side - void readbuff(AsynchIO& aio, AsynchIOBufferBase* buff); - void eof(AsynchIO& aio); - void disconnect(AsynchIO& aio); - + QPID_COMMON_EXTERN void readbuff(AsynchIO& aio, AsynchIOBufferBase* buff); + QPID_COMMON_EXTERN void eof(AsynchIO& aio); + QPID_COMMON_EXTERN void disconnect(AsynchIO& aio); + // Notifications - void nobuffs(AsynchIO& aio); - void idle(AsynchIO& aio); - void closedSocket(AsynchIO& aio, const Socket& s); + QPID_COMMON_EXTERN void nobuffs(AsynchIO& aio); + QPID_COMMON_EXTERN void idle(AsynchIO& aio); + QPID_COMMON_EXTERN void closedSocket(AsynchIO& aio, const Socket& s); }; }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/AtomicCount.h b/cpp/src/qpid/sys/AtomicCount.h index d598b49427..94580c61f3 100644 --- a/cpp/src/qpid/sys/AtomicCount.h +++ b/cpp/src/qpid/sys/AtomicCount.h @@ -20,7 +20,7 @@ */ #include <boost/detail/atomic_count.hpp> -#include "ScopedIncrement.h" +#include "qpid/sys/ScopedIncrement.h" namespace qpid { namespace sys { diff --git a/cpp/src/qpid/sys/AtomicValue_gcc.h b/cpp/src/qpid/sys/AtomicValue_gcc.h index da60edad65..d022b07c1d 100644 --- a/cpp/src/qpid/sys/AtomicValue_gcc.h +++ b/cpp/src/qpid/sys/AtomicValue_gcc.h @@ -57,7 +57,7 @@ class AtomicValue /** If current value == testval then set to newval. Returns true if the swap was performed. */ bool boolCompareAndSwap(T testval, T newval) { return __sync_bool_compare_and_swap(&value, testval, newval); } - T get() const { return const_cast<AtomicValue<T>*>(this)->fetchAndAdd(0); } + T get() const { return const_cast<AtomicValue<T>*>(this)->fetchAndAdd(static_cast<T>(0)); } private: T value; diff --git a/cpp/src/qpid/sys/AtomicValue_mutex.h b/cpp/src/qpid/sys/AtomicValue_mutex.h index 8871addbda..e4d433e7f5 100644 --- a/cpp/src/qpid/sys/AtomicValue_mutex.h +++ b/cpp/src/qpid/sys/AtomicValue_mutex.h @@ -53,6 +53,8 @@ class AtomicValue inline T operator++(int) { return fetchAndAdd(1); } inline T operator--(int) { return fetchAndSub(1); } + AtomicValue& operator=(T newval) { Lock l(lock); value = newval; return *this; } + /** If current value == testval then set to newval. Returns the old value. */ T valueCompareAndSwap(T testval, T newval) { Lock l(lock); diff --git a/cpp/src/qpid/sys/BlockingQueue.h b/cpp/src/qpid/sys/BlockingQueue.h index c6c6291b97..210cb4ad82 100644 --- a/cpp/src/qpid/sys/BlockingQueue.h +++ b/cpp/src/qpid/sys/BlockingQueue.h @@ -22,7 +22,7 @@ * */ -#include "Waitable.h" +#include "qpid/sys/Waitable.h" #include <queue> @@ -66,14 +66,17 @@ public: return true; } - T pop() { + T pop(Duration timeout=TIME_INFINITE) { T result; - bool ok = pop(result); - assert(ok); (void) ok; // Infinite wait. + bool ok = pop(result, timeout); + if (!ok) + throw Exception("Timed out waiting on a blocking queue"); return result; } - /** Push a value onto the queue */ + /** Push a value onto the queue. + * Note it is not an error to push onto a closed queue. + */ void push(const T& t) { Mutex::ScopedLock l(waitable); queue.push(t); diff --git a/cpp/src/qpid/sys/Codec.h b/cpp/src/qpid/sys/Codec.h new file mode 100644 index 0000000000..ace721fbcc --- /dev/null +++ b/cpp/src/qpid/sys/Codec.h @@ -0,0 +1,52 @@ +#ifndef QPID_SYS_CODEC_H +#define QPID_SYS_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. + * + */ +#include <cstddef> + +namespace qpid { +namespace sys { + +/** + * Generic codec interface + */ +class Codec +{ + public: + virtual ~Codec() {} + + /** Decode from buffer, return number of bytes decoded. + * @return may be less than size if there was incomplete + * data at the end of the buffer. + */ + virtual std::size_t decode(const char* buffer, std::size_t size) = 0; + + + /** Encode into buffer, return number of bytes encoded */ + virtual std::size_t encode(const char* buffer, std::size_t size) = 0; + + /** Return true if we have data to encode */ + virtual bool canEncode() = 0; +}; +}} // namespace qpid::sys + +#endif /*!QPID_SYS_CODEC_H*/ diff --git a/cpp/src/qpid/sys/ConnectionCodec.h b/cpp/src/qpid/sys/ConnectionCodec.h index efc6839b60..7231b1daa6 100644 --- a/cpp/src/qpid/sys/ConnectionCodec.h +++ b/cpp/src/qpid/sys/ConnectionCodec.h @@ -21,54 +21,55 @@ * under the License. * */ +#include "qpid/sys/Codec.h" #include "qpid/framing/ProtocolVersion.h" -#include "OutputControl.h" -#include <memory> -#include <map> namespace qpid { namespace sys { +class InputHandlerFactory; +class OutputControl; + /** * Interface of coder/decoder for a connection of a specific protocol * version. */ -class ConnectionCodec { +class ConnectionCodec : public Codec { public: virtual ~ConnectionCodec() {} - /** Decode from buffer, return number of bytes decoded. - * @return may be less than size if there was incomplete - * data at the end of the buffer. - */ - virtual size_t decode(const char* buffer, size_t size) = 0; - - - /** Encode into buffer, return number of bytes encoded */ - virtual size_t encode(const char* buffer, size_t size) = 0; - - /** Return true if we have data to encode */ - virtual bool canEncode() = 0; - /** Network connection was closed from other end. */ virtual void closed() = 0; virtual bool isClosed() const = 0; virtual framing::ProtocolVersion getVersion() const = 0; - + struct Factory { virtual ~Factory() {} + /** Security Strength Factor - indicates the level of security provided + * by the underlying transport. If zero, the transport provides no + * security (e.g. TCP). If non-zero, the transport provides some level + * of security (e.g. SSL). The values for SSF can be interpreted as: + * + * 0 = No protection. + * 1 = Integrity checking only. + * >1 = Supports authentication, integrity and confidentiality. + * The number represents the encryption key length. + */ + /** Return 0 if version unknown */ virtual ConnectionCodec* create( - framing::ProtocolVersion, OutputControl&, const std::string& id + framing::ProtocolVersion, OutputControl&, const std::string& id, + unsigned int conn_ssf ) = 0; /** Return "preferred" codec for outbound connections. */ virtual ConnectionCodec* create( - OutputControl&, const std::string& id + OutputControl&, const std::string& id, + unsigned int conn_ssf ) = 0; }; }; diff --git a/cpp/src/qpid/sys/ConnectionInputHandler.h b/cpp/src/qpid/sys/ConnectionInputHandler.h index a2c18d6d9a..9e85c66924 100644 --- a/cpp/src/qpid/sys/ConnectionInputHandler.h +++ b/cpp/src/qpid/sys/ConnectionInputHandler.h @@ -22,8 +22,8 @@ #define _ConnectionInputHandler_ #include "qpid/framing/InputHandler.h" -#include "OutputTask.h" -#include "TimeoutHandler.h" +#include "qpid/sys/OutputTask.h" +#include "qpid/sys/TimeoutHandler.h" namespace qpid { namespace sys { @@ -33,6 +33,7 @@ namespace sys { public TimeoutHandler, public OutputTask { public: + virtual void closed() = 0; }; diff --git a/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h b/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h index 2b309b5758..9bb7e13686 100644 --- a/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h +++ b/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h @@ -42,7 +42,8 @@ class ConnectionInputHandlerFactory : private boost::noncopyable *@param id identify the connection for management purposes. */ virtual ConnectionInputHandler* create(ConnectionOutputHandler* out, - const std::string& id) = 0; + const std::string& id, + bool isClient) = 0; virtual ~ConnectionInputHandlerFactory(){} }; diff --git a/cpp/src/qpid/sys/ConnectionOutputHandler.h b/cpp/src/qpid/sys/ConnectionOutputHandler.h index 5a60ae4998..421dd7c269 100644 --- a/cpp/src/qpid/sys/ConnectionOutputHandler.h +++ b/cpp/src/qpid/sys/ConnectionOutputHandler.h @@ -22,7 +22,7 @@ #define _ConnectionOutputHandler_ #include "qpid/framing/OutputHandler.h" -#include "OutputControl.h" +#include "qpid/sys/OutputControl.h" namespace qpid { namespace sys { @@ -34,6 +34,7 @@ class ConnectionOutputHandler : public virtual qpid::framing::OutputHandler, pub { public: virtual void close() = 0; + virtual size_t getBuffered() const { return 0; } }; }} diff --git a/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h b/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h new file mode 100644 index 0000000000..95a08d15ae --- /dev/null +++ b/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h @@ -0,0 +1,56 @@ +#ifndef QPID_SYS_CONNECTIONOUTPUTHANDLERPTR_H +#define QPID_SYS_CONNECTIONOUTPUTHANDLERPTR_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/ConnectionOutputHandler.h" + +namespace qpid { +namespace sys { + +/** + * A ConnectionOutputHandler that delegates to another + * ConnectionOutputHandler. Allows the "real" ConnectionOutputHandler + * to be changed without updating all the pointers/references + * using the ConnectionOutputHandlerPtr + */ +class ConnectionOutputHandlerPtr : public ConnectionOutputHandler +{ + public: + ConnectionOutputHandlerPtr(ConnectionOutputHandler* p) : next(p) { assert(next); } + void set(ConnectionOutputHandler* p) { next = p; assert(next); } + ConnectionOutputHandler* get() { return next; } + const ConnectionOutputHandler* get() const { return next; } + + void close() { next->close(); } + 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: + ConnectionOutputHandler* next; +}; +}} // namespace qpid::sys + +#endif /*!QPID_SYS_CONNECTIONOUTPUTHANDLERPTR_H*/ diff --git a/cpp/src/qpid/sys/CopyOnWriteArray.h b/cpp/src/qpid/sys/CopyOnWriteArray.h new file mode 100644 index 0000000000..e4ae3a6094 --- /dev/null +++ b/cpp/src/qpid/sys/CopyOnWriteArray.h @@ -0,0 +1,138 @@ +#ifndef QPID_SYS_COPYONWRITEARRAY_H +#define QPID_SYS_COPYONWRITEARRAY_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 <algorithm> +#include <vector> +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace sys { + +/** + * An array that copies on adding/removing element allowing lock-free + * iteration. + */ +template <class T> +class CopyOnWriteArray +{ +public: + typedef boost::shared_ptr<const std::vector<T> > ConstPtr; + + CopyOnWriteArray() {} + CopyOnWriteArray(const CopyOnWriteArray& c) : array(c.array) {} + + void add(T& t) + { + Mutex::ScopedLock l(lock); + ArrayPtr copy(array ? new std::vector<T>(*array) : new std::vector<T>()); + copy->push_back(t); + array = copy; + } + + bool remove(T& t) + { + Mutex::ScopedLock l(lock); + if (array && std::find(array->begin(), array->end(), t) != array->end()) { + ArrayPtr copy(new std::vector<T>(*array)); + copy->erase(std::find(copy->begin(), copy->end(), t)); + array = copy; + return true; + } else { + return false; + } + } + + bool clear() + { + Mutex::ScopedLock l(lock); + if (array && !array->empty()) { + ArrayPtr copy; + array = copy; + return true; + } else { + return false; + } + } + + template <class F> + bool add_unless(T& t, F f) + { + Mutex::ScopedLock l(lock); + if (array && std::find_if(array->begin(), array->end(), f) != array->end()) { + return false; + } else { + ArrayPtr copy(array ? new std::vector<T>(*array) : new std::vector<T>()); + copy->push_back(t); + array = copy; + return true; + } + } + + template <class F> + bool remove_if(F f) + { + Mutex::ScopedLock l(lock); + if (array && std::find_if(array->begin(), array->end(), f) != array->end()) { + ArrayPtr copy(new std::vector<T>(*array)); + copy->erase(std::remove_if(copy->begin(), copy->end(), f), copy->end()); + array = copy; + return true; + } + return false; + } + + template <class F> + F for_each(F f) + { + ArrayPtr a; + { + Mutex::ScopedLock l(lock); + a = array; + } + if (!a) return f; + return std::for_each(a->begin(), a->end(), f); + } + + ConstPtr snapshot() + { + ConstPtr a; + { + Mutex::ScopedLock l(lock); + a = array; + } + return a; + } + +private: + typedef boost::shared_ptr< std::vector<T> > ArrayPtr; + Mutex lock; + ArrayPtr array; +}; + +}} + + + +#endif /*!QPID_SYS_COPYONWRITEARRAY_H*/ diff --git a/cpp/src/qpid/sys/DeletionManager.h b/cpp/src/qpid/sys/DeletionManager.h index 43154eb98e..c1fea19f30 100644 --- a/cpp/src/qpid/sys/DeletionManager.h +++ b/cpp/src/qpid/sys/DeletionManager.h @@ -54,6 +54,8 @@ struct deleter template <typename H> class DeletionManager { + struct ThreadStatus; + public: // Mark every thread as using the handle - it will be deleted // below after every thread marks the handle as unused @@ -65,6 +67,24 @@ public: // handles get deleted here when no one else // is using them either static void markAllUnusedInThisThread() { + ThreadStatus* threadStatus = getThreadStatus(); + ScopedLock<Mutex> l(threadStatus->lock); + + // The actual deletions will happen here when all the shared_ptr + // ref counts hit 0 (that is when every thread marks the handle unused) + threadStatus->handles.clear(); + } + + static void destroyThreadState() { + ThreadStatus* threadStatus = getThreadStatus(); + allThreadsStatuses.delThreadStatus(threadStatus); + delete threadStatus; + threadStatus = 0; + } + +private: + + static ThreadStatus*& getThreadStatus() { static __thread ThreadStatus* threadStatus = 0; // Thread local vars can't be dynamically constructed so we need @@ -75,14 +95,9 @@ public: allThreadsStatuses.addThreadStatus(threadStatus); } - ScopedLock<Mutex> l(threadStatus->lock); - - // The actual deletions will happen here when all the shared_ptr - // ref counts hit 0 (that is when every thread marks the handle unused) - threadStatus->handles.clear(); + return threadStatus; } -private: typedef boost::shared_ptr<H> shared_ptr; // In theory we know that we never need more handles than the number of @@ -125,6 +140,15 @@ private: statuses.push_back(t); } + void delThreadStatus(ThreadStatus* t) { + ScopedLock<Mutex> l(lock); + typename std::vector<ThreadStatus*>::iterator it = + std::find(statuses.begin(),statuses.end(), t); + if (it != statuses.end()) { + statuses.erase(it); + } + } + void addHandle(shared_ptr h) { ScopedLock<Mutex> l(lock); std::for_each(statuses.begin(), statuses.end(), handleAdder(h)); diff --git a/cpp/src/qpid/sys/DispatchHandle.cpp b/cpp/src/qpid/sys/DispatchHandle.cpp new file mode 100644 index 0000000000..605edabc64 --- /dev/null +++ b/cpp/src/qpid/sys/DispatchHandle.cpp @@ -0,0 +1,337 @@ +/* + * + * 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 <algorithm> + +#include <boost/cast.hpp> + +#include <assert.h> + +namespace qpid { +namespace sys { + +DispatchHandle::DispatchHandle(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb) : + PollerHandle(h), + readableCallback(rCb), + writableCallback(wCb), + disconnectedCallback(dCb), + state(IDLE) +{ +} + + +DispatchHandle::~DispatchHandle() { +} + +void DispatchHandle::startWatch(Poller::shared_ptr poller0) { + bool r = readableCallback; + bool w = writableCallback; + + ScopedLock<Mutex> lock(stateLock); + assert(state == IDLE); + + poller = poller0; + poller->registerHandle(*this); + state = WAITING; + Poller::Direction dir = r ? + ( w ? Poller::INOUT : Poller::INPUT ) : + ( w ? Poller::OUTPUT : Poller::NONE ); + poller->monitorHandle(*this, dir); +} + +void DispatchHandle::rewatch() { + bool r = readableCallback; + bool w = writableCallback; + if (!r && !w) { + return; + } + Poller::Direction dir = r ? + ( w ? Poller::INOUT : Poller::INPUT ) : + ( w ? Poller::OUTPUT : Poller::NONE ); + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->monitorHandle(*this, dir); +} + +void DispatchHandle::rewatchRead() { + if (!readableCallback) { + return; + } + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->monitorHandle(*this, Poller::INPUT); +} + +void DispatchHandle::rewatchWrite() { + if (!writableCallback) { + return; + } + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->monitorHandle(*this, Poller::OUTPUT); +} + +void DispatchHandle::unwatchRead() { + if (!readableCallback) { + return; + } + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->unmonitorHandle(*this, Poller::INPUT); +} + +void DispatchHandle::unwatchWrite() { + if (!writableCallback) { + return; + } + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->unmonitorHandle(*this, Poller::OUTPUT); +} + +void DispatchHandle::unwatch() { + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->unmonitorHandle(*this, Poller::INOUT); +} + +void DispatchHandle::stopWatch() { + ScopedLock<Mutex> lock(stateLock); + switch (state) { + case IDLE: + assert(state != IDLE); + return; + case STOPPING: + assert(state != STOPPING); + return; + case CALLING: + state = STOPPING; + break; + case WAITING: + state = IDLE; + break; + case DELETING: + return; + } + assert(poller); + poller->unregisterHandle(*this); + poller.reset(); +} + +// If we are in the IDLE/STOPPING state we can't do the callback as we've +// not/no longer got the fd registered in any poller +void DispatchHandle::call(Callback iCb) { + assert(iCb); + ScopedLock<Mutex> lock(stateLock); + switch (state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + interruptedCallbacks.push(iCb); + assert(poller); + (void) poller->interrupt(*this); + } +} + +// The slightly strange switch structure +// is to ensure that the lock is released before +// we do the delete +void DispatchHandle::doDelete() { + { + ScopedLock<Mutex> lock(stateLock); + // Ensure that we're no longer watching anything + switch (state) { + case IDLE: + state = DELETING; + break; + case STOPPING: + state = DELETING; + return; + case WAITING: + state = DELETING; + assert(poller); + (void) poller->interrupt(*this); + poller->unregisterHandle(*this); + return; + case CALLING: + state = DELETING; + assert(poller); + poller->unregisterHandle(*this); + return; + case DELETING: + return; + } + } + // If we're IDLE we can do this right away + delete this; +} + +void DispatchHandle::processEvent(Poller::EventType type) { + + // Phase I + { + ScopedLock<Mutex> lock(stateLock); + + switch(state) { + case IDLE: + // Can get here if a non connection thread stops watching + // whilst we were stuck in the above lock + return; + case WAITING: + state = CALLING; + break; + case CALLING: + assert(state!=CALLING); + return; + case STOPPING: + assert(state!=STOPPING); + return; + case DELETING: + // Need to make sure we clean up any pending callbacks in this case + std::swap(callbacks, interruptedCallbacks); + goto saybyebye; + } + + std::swap(callbacks, interruptedCallbacks); + } + + // Do callbacks - whilst we are doing the callbacks we are prevented from processing + // the same handle until we re-enable it. To avoid rentering the callbacks for a single + // handle re-enabling in the callbacks is actually deferred until they are complete. + switch (type) { + case Poller::READABLE: + readableCallback(*this); + break; + case Poller::WRITABLE: + writableCallback(*this); + break; + case Poller::READ_WRITABLE: + readableCallback(*this); + writableCallback(*this); + break; + case Poller::DISCONNECTED: + if (disconnectedCallback) { + disconnectedCallback(*this); + } + break; + case Poller::INTERRUPTED: + { + // We could only be interrupted if we also had a callback to do + assert(callbacks.size() > 0); + // We'll actually do the interrupt below + } + break; + default: + assert(false); + } + + // If we have any callbacks do them now - + // (because we use a copy from before the previous callbacks we won't + // do anything yet that was just added) + while (callbacks.size() > 0) { + Callback cb = callbacks.front(); + assert(cb); + cb(*this); + callbacks.pop(); + } + + { + ScopedLock<Mutex> lock(stateLock); + switch (state) { + case IDLE: + assert(state!=IDLE); + return; + case STOPPING: + state = IDLE; + return; + case WAITING: + assert(state!=WAITING); + return; + case CALLING: + state = WAITING; + return; + case DELETING: + break; + } + } + +saybyebye: + delete this; +} + +}} diff --git a/cpp/src/qpid/sys/DispatchHandle.h b/cpp/src/qpid/sys/DispatchHandle.h new file mode 100644 index 0000000000..115a3c44f7 --- /dev/null +++ b/cpp/src/qpid/sys/DispatchHandle.h @@ -0,0 +1,150 @@ +#ifndef _sys_DispatchHandle_h +#define _sys_DispatchHandle_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/Poller.h" +#include "qpid/sys/Mutex.h" +#include "qpid/CommonImportExport.h" +#include <boost/function.hpp> + +#include <queue> + +namespace qpid { +namespace sys { + +class DispatchHandleRef; +/** + * In order to have your own handle (file descriptor on Unix) watched by the poller + * you need to: + * + * - Subclass IOHandle, in the constructor supply an appropriate + * IOHandlerPrivate object for the platform. + * + * - Construct a DispatchHandle passing it your IOHandle and + * callback functions for read, write and disconnect events. + * + * - Ensure the DispatchHandle is not deleted until the poller is no longer using it. + * TODO: astitcher document DispatchHandleRef to simplify this. + * + * When an event occurs on the handle, the poller calls the relevant callback and + * stops watching that handle. Your callback can call rewatch() or related functions + * to re-enable polling. + */ +class DispatchHandle : public PollerHandle { + friend class DispatchHandleRef; +public: + typedef boost::function1<void, DispatchHandle&> Callback; + typedef std::queue<Callback> CallbackQueue; + +private: + Callback readableCallback; + Callback writableCallback; + Callback disconnectedCallback; + CallbackQueue interruptedCallbacks; + CallbackQueue callbacks; // Double buffer + Poller::shared_ptr poller; + Mutex stateLock; + enum { + IDLE, + STOPPING, + WAITING, + CALLING, + DELETING + } state; + +public: + /** + * Provide a handle to poll and a set of callbacks. Note + * callbacks can be 0, meaning you are not interested in that + * event. + * + *@param h: the handle to watch. The IOHandle encapsulates a + * platfrom-specific handle to an IO object (e.g. a file descriptor + * on Unix.) + *@param rCb Callback called when the handle is readable. + *@param wCb Callback called when the handle is writable. + *@param dCb Callback called when the handle is disconnected. + */ + QPID_COMMON_EXTERN DispatchHandle(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb); + QPID_COMMON_EXTERN ~DispatchHandle(); + + /** Add this DispatchHandle to the poller to be watched. */ + QPID_COMMON_EXTERN void startWatch(Poller::shared_ptr poller); + + /** Resume watching for all non-0 callbacks. */ + QPID_COMMON_EXTERN void rewatch(); + /** Resume watching for read only. */ + QPID_COMMON_EXTERN void rewatchRead(); + + /** Resume watching for write only. */ + QPID_COMMON_EXTERN void rewatchWrite(); + + /** Stop watching temporarily. The DispatchHandle remains + associated with the poller and can be re-activated using + rewatch. */ + QPID_COMMON_EXTERN void unwatch(); + /** Stop watching for read */ + QPID_COMMON_EXTERN void unwatchRead(); + /** Stop watching for write */ + QPID_COMMON_EXTERN void unwatchWrite(); + + /** Stop watching permanently. Disassociates from the poller. */ + QPID_COMMON_EXTERN void stopWatch(); + + /** Interrupt watching this handle and make a serialised callback that respects the + * same exclusivity guarantees as the other callbacks + */ + QPID_COMMON_EXTERN void call(Callback iCb); + +protected: + QPID_COMMON_EXTERN void doDelete(); + +private: + QPID_COMMON_EXTERN void processEvent(Poller::EventType dir); +}; + +class DispatchHandleRef { + DispatchHandle* ref; + +public: + typedef boost::function1<void, DispatchHandle&> Callback; + DispatchHandleRef(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb) : + ref(new DispatchHandle(h, rCb, wCb, dCb)) + {} + + ~DispatchHandleRef() { ref->doDelete(); } + + void startWatch(Poller::shared_ptr poller) { ref->startWatch(poller); } + void rewatch() { ref->rewatch(); } + void rewatchRead() { ref->rewatchRead(); } + void rewatchWrite() { ref->rewatchWrite(); } + void unwatch() { ref->unwatch(); } + void unwatchRead() { ref->unwatchRead(); } + void unwatchWrite() { ref->unwatchWrite(); } + void stopWatch() { ref->stopWatch(); } + void call(Callback iCb) { ref->call(iCb); } +}; + +}} + +#endif // _sys_DispatchHandle_h diff --git a/cpp/src/qpid/sys/Dispatcher.cpp b/cpp/src/qpid/sys/Dispatcher.cpp index 02a62c8e66..5f52dcd990 100644 --- a/cpp/src/qpid/sys/Dispatcher.cpp +++ b/cpp/src/qpid/sys/Dispatcher.cpp @@ -19,9 +19,7 @@ * */ -#include "Dispatcher.h" - -#include <boost/cast.hpp> +#include "qpid/sys/Dispatcher.h" #include <assert.h> @@ -36,404 +34,7 @@ Dispatcher::~Dispatcher() { } void Dispatcher::run() { - do { - Poller::Event event = poller->wait(); - - // If can read/write then dispatch appropriate callbacks - if (event.handle) { - event.process(); - } else { - // Handle shutdown - switch (event.type) { - case Poller::SHUTDOWN: - goto dispatcher_shutdown; - default: - // This should be impossible - assert(false); - } - } - } while (true); - -dispatcher_shutdown: - ; -} - -DispatchHandle::~DispatchHandle() { - stopWatch(); -} - -void DispatchHandle::startWatch(Poller::shared_ptr poller0) { - bool r = readableCallback; - bool w = writableCallback; - - ScopedLock<Mutex> lock(stateLock); - assert(state == IDLE); - - // If no callbacks set then do nothing (that is what we were asked to do!) - // TODO: Maybe this should be an assert instead - if (!r && !w) { - state = INACTIVE; - return; - } - - Poller::Direction d = r ? - (w ? Poller::INOUT : Poller::IN) : - Poller::OUT; - - poller = poller0; - poller->addFd(*this, d); - - state = r ? - (w ? ACTIVE_RW : ACTIVE_R) : - ACTIVE_W; -} - -void DispatchHandle::rewatch() { - bool r = readableCallback; - bool w = writableCallback; - - ScopedLock<Mutex> lock(stateLock); - switch(state) { - case IDLE: - case DELAYED_IDLE: - break; - case DELAYED_R: - case DELAYED_W: - case DELAYED_INACTIVE: - state = r ? - (w ? DELAYED_RW : DELAYED_R) : - DELAYED_W; - break; - case DELAYED_DELETE: - break; - case INACTIVE: - case ACTIVE_R: - case ACTIVE_W: { - assert(poller); - Poller::Direction d = r ? - (w ? Poller::INOUT : Poller::IN) : - Poller::OUT; - poller->modFd(*this, d); - state = r ? - (w ? ACTIVE_RW : ACTIVE_R) : - ACTIVE_W; - break; - } - case DELAYED_RW: - case ACTIVE_RW: - // Don't need to do anything already waiting for readable/writable - break; - } -} - -void DispatchHandle::rewatchRead() { - if (!readableCallback) { - return; - } - - ScopedLock<Mutex> lock(stateLock); - switch(state) { - case IDLE: - case DELAYED_IDLE: - break; - case DELAYED_R: - case DELAYED_RW: - case DELAYED_DELETE: - break; - case DELAYED_W: - state = DELAYED_RW; - break; - case DELAYED_INACTIVE: - state = DELAYED_R; - break; - case ACTIVE_R: - case ACTIVE_RW: - // Nothing to do: already waiting for readable - break; - case INACTIVE: - assert(poller); - poller->modFd(*this, Poller::IN); - state = ACTIVE_R; - break; - case ACTIVE_W: - assert(poller); - poller->modFd(*this, Poller::INOUT); - state = ACTIVE_RW; - break; - } -} - -void DispatchHandle::rewatchWrite() { - if (!writableCallback) { - return; - } - - ScopedLock<Mutex> lock(stateLock); - switch(state) { - case IDLE: - case DELAYED_IDLE: - break; - case DELAYED_W: - case DELAYED_RW: - case DELAYED_DELETE: - break; - case DELAYED_R: - state = DELAYED_RW; - break; - case DELAYED_INACTIVE: - state = DELAYED_W; - break; - case INACTIVE: - assert(poller); - poller->modFd(*this, Poller::OUT); - state = ACTIVE_W; - break; - case ACTIVE_R: - assert(poller); - poller->modFd(*this, Poller::INOUT); - state = ACTIVE_RW; - break; - case ACTIVE_W: - case ACTIVE_RW: - // Nothing to do: already waiting for writable - break; - } -} - -void DispatchHandle::unwatchRead() { - if (!readableCallback) { - return; - } - - ScopedLock<Mutex> lock(stateLock); - switch(state) { - case IDLE: - case DELAYED_IDLE: - break; - case DELAYED_R: - state = DELAYED_INACTIVE; - break; - case DELAYED_RW: - state = DELAYED_W; - break; - case DELAYED_W: - case DELAYED_INACTIVE: - case DELAYED_DELETE: - break; - case ACTIVE_R: - assert(poller); - poller->modFd(*this, Poller::NONE); - state = INACTIVE; - break; - case ACTIVE_RW: - assert(poller); - poller->modFd(*this, Poller::OUT); - state = ACTIVE_W; - break; - case ACTIVE_W: - case INACTIVE: - break; - } -} - -void DispatchHandle::unwatchWrite() { - if (!writableCallback) { - return; - } - - ScopedLock<Mutex> lock(stateLock); - switch(state) { - case IDLE: - case DELAYED_IDLE: - break; - case DELAYED_W: - state = DELAYED_INACTIVE; - break; - case DELAYED_RW: - state = DELAYED_R; - break; - case DELAYED_R: - case DELAYED_INACTIVE: - case DELAYED_DELETE: - break; - case ACTIVE_W: - assert(poller); - poller->modFd(*this, Poller::NONE); - state = INACTIVE; - break; - case ACTIVE_RW: - assert(poller); - poller->modFd(*this, Poller::IN); - state = ACTIVE_R; - break; - case ACTIVE_R: - case INACTIVE: - break; - } -} - -void DispatchHandle::unwatch() { - ScopedLock<Mutex> lock(stateLock); - switch (state) { - case IDLE: - case DELAYED_IDLE: - break; - case DELAYED_R: - case DELAYED_W: - case DELAYED_RW: - case DELAYED_INACTIVE: - state = DELAYED_INACTIVE; - break; - case DELAYED_DELETE: - break; - default: - assert(poller); - poller->modFd(*this, Poller::NONE); - state = INACTIVE; - break; - } -} - -void DispatchHandle::stopWatch() { - ScopedLock<Mutex> lock(stateLock); - switch (state) { - case IDLE: - case DELAYED_IDLE: - case DELAYED_DELETE: - return; - case DELAYED_R: - case DELAYED_W: - case DELAYED_RW: - case DELAYED_INACTIVE: - state = DELAYED_IDLE; - break; - default: - state = IDLE; - break; - } - assert(poller); - poller->delFd(*this); - poller.reset(); -} - -// The slightly strange switch structure -// is to ensure that the lock is released before -// we do the delete -void DispatchHandle::doDelete() { - // Ensure that we're no longer watching anything - stopWatch(); - - // If we're in the middle of a callback defer the delete - { - ScopedLock<Mutex> lock(stateLock); - switch (state) { - case DELAYED_IDLE: - case DELAYED_DELETE: - state = DELAYED_DELETE; - return; - case IDLE: - break; - default: - // Can only get out of stopWatch() in DELAYED_IDLE/DELAYED_DELETE/IDLE states - assert(false); - } - } - // If we're not then do it right away - delete this; -} - -void DispatchHandle::processEvent(Poller::EventType type) { - // Note that we are now doing the callbacks - { - ScopedLock<Mutex> lock(stateLock); - - // Set up to wait for same events next time unless reset - switch(state) { - case ACTIVE_R: - state = DELAYED_R; - break; - case ACTIVE_W: - state = DELAYED_W; - break; - case ACTIVE_RW: - state = DELAYED_RW; - break; - // Can only get here in a DELAYED_* state in the rare case - // that we're already here for reading and we get activated for - // writing and we can write (it might be possible the other way - // round too). In this case we're already processing the handle - // in a different thread in this function so return right away - case DELAYED_R: - case DELAYED_W: - case DELAYED_RW: - case DELAYED_INACTIVE: - case DELAYED_IDLE: - case DELAYED_DELETE: - return; - default: - assert(false); - } - } - - // Do callbacks - whilst we are doing the callbacks we are prevented from processing - // the same handle until we re-enable it. To avoid rentering the callbacks for a single - // handle re-enabling in the callbacks is actually deferred until they are complete. - switch (type) { - case Poller::READABLE: - readableCallback(*this); - break; - case Poller::WRITABLE: - writableCallback(*this); - break; - case Poller::READ_WRITABLE: - readableCallback(*this); - writableCallback(*this); - break; - case Poller::DISCONNECTED: - { - ScopedLock<Mutex> lock(stateLock); - state = DELAYED_INACTIVE; - } - if (disconnectedCallback) { - disconnectedCallback(*this); - } - break; - default: - assert(false); - } - - // If any of the callbacks re-enabled reading/writing then actually - // do it now - { - ScopedLock<Mutex> lock(stateLock); - switch (state) { - case DELAYED_R: - poller->modFd(*this, Poller::IN); - state = ACTIVE_R; - return; - case DELAYED_W: - poller->modFd(*this, Poller::OUT); - state = ACTIVE_W; - return; - case DELAYED_RW: - poller->modFd(*this, Poller::INOUT); - state = ACTIVE_RW; - return; - case DELAYED_INACTIVE: - state = INACTIVE; - return; - case DELAYED_IDLE: - state = IDLE; - return; - default: - // This should be impossible - assert(false); - return; - case DELAYED_DELETE: - break; - } - } - delete this; + poller->run(); } }} diff --git a/cpp/src/qpid/sys/Dispatcher.h b/cpp/src/qpid/sys/Dispatcher.h index 8e34354f9e..e8213d0579 100644 --- a/cpp/src/qpid/sys/Dispatcher.h +++ b/cpp/src/qpid/sys/Dispatcher.h @@ -22,139 +22,21 @@ * */ -#include "Poller.h" -#include "Runnable.h" -#include "Mutex.h" - -#include <memory> -#include <queue> -#include <boost/function.hpp> - -#include <assert.h> - +#include "qpid/sys/Poller.h" +#include "qpid/sys/Runnable.h" +#include "qpid/CommonImportExport.h" namespace qpid { namespace sys { -class DispatchHandleRef; -/** - * In order to have your own handle (file descriptor on Unix) watched by the poller - * you need to: - * - * - Subclass IOHandle, in the constructor supply an appropriate - * IOHandlerPrivate object for the platform. - * - * - Construct a DispatchHandle passing it your IOHandle and - * callback functions for read, write and disconnect events. - * - * - Ensure the DispatchHandle is not deleted until the poller is no longer using it. - * TODO: astitcher document DispatchHandleRef to simplify this. - * - * When an event occurs on the handle, the poller calls the relevant callback and - * stops watching that handle. Your callback can call rewatch() or related functions - * to re-enable polling. - */ -class DispatchHandle : public PollerHandle { - friend class DispatchHandleRef; -public: - typedef boost::function1<void, DispatchHandle&> Callback; - -private: - Callback readableCallback; - Callback writableCallback; - Callback disconnectedCallback; - Poller::shared_ptr poller; - Mutex stateLock; - enum { - IDLE, INACTIVE, ACTIVE_R, ACTIVE_W, ACTIVE_RW, - DELAYED_IDLE, DELAYED_INACTIVE, DELAYED_R, DELAYED_W, DELAYED_RW, - DELAYED_DELETE - } state; - -public: - /** - * Provide a handle to poll and a set of callbacks. Note - * callbacks can be 0, meaning you are not interested in that - * event. - * - *@param h: the handle to watch. The IOHandle encapsulates a - * platfrom-specific handle to an IO object (e.g. a file descriptor - * on Unix.) - *@param rCb Callback called when the handle is readable. - *@param wCb Callback called when the handle is writable. - *@param dCb Callback called when the handle is disconnected. - */ - DispatchHandle(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb) : - PollerHandle(h), - readableCallback(rCb), - writableCallback(wCb), - disconnectedCallback(dCb), - state(IDLE) - {} - - ~DispatchHandle(); - - /** Add this DispatchHandle to the poller to be watched. */ - void startWatch(Poller::shared_ptr poller); - - /** Resume watchingn for all non-0 callbacks. */ - void rewatch(); - /** Resume watchingn for read only. */ - void rewatchRead(); - - /** Resume watchingn for write only. */ - void rewatchWrite(); - - /** Stop watching temporarily. The DispatchHandle remains - associated with the poller and can be re-activated using - rewatch. */ - void unwatch(); - /** Stop watching for read */ - void unwatchRead(); - /** Stop watching for write */ - void unwatchWrite(); - - /** Stop watching permanently. Disassociates from the poller. */ - void stopWatch(); - -protected: - /** Override to get extra processing done when the DispatchHandle is deleted. */ - void doDelete(); - -private: - void processEvent(Poller::EventType dir); -}; - -class DispatchHandleRef { - DispatchHandle* ref; - -public: - typedef boost::function1<void, DispatchHandle&> Callback; - DispatchHandleRef(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb) : - ref(new DispatchHandle(h, rCb, wCb, dCb)) - {} - - ~DispatchHandleRef() { ref->doDelete(); } - - void startWatch(Poller::shared_ptr poller) { ref->startWatch(poller); } - void rewatch() { ref->rewatch(); } - void rewatchRead() { ref->rewatchRead(); } - void rewatchWrite() { ref->rewatchWrite(); } - void unwatch() { ref->unwatch(); } - void unwatchRead() { ref->unwatchRead(); } - void unwatchWrite() { ref->unwatchWrite(); } - void stopWatch() { ref->stopWatch(); } -}; - - class Dispatcher : public Runnable { const Poller::shared_ptr poller; public: - Dispatcher(Poller::shared_ptr poller); - ~Dispatcher(); + QPID_COMMON_EXTERN Dispatcher(Poller::shared_ptr poller); + QPID_COMMON_EXTERN ~Dispatcher(); - void run(); + QPID_COMMON_EXTERN void run(); }; }} diff --git a/cpp/src/qpid/sys/ExceptionHolder.h b/cpp/src/qpid/sys/ExceptionHolder.h deleted file mode 100644 index cfb971411e..0000000000 --- a/cpp/src/qpid/sys/ExceptionHolder.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef QPID_EXCEPTIONHOLDER_H -#define QPID_EXCEPTIONHOLDER_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/memory.h" -#include <boost/shared_ptr.hpp> - - -namespace qpid { -namespace sys { - -struct Raisable { - virtual ~Raisable() {}; - virtual void raise() const=0; - virtual std::string what() const=0; -}; - -/** - * Holder for exceptions. Allows the thread that notices an error condition to - * create an exception and store it to be thrown by another thread. - */ -class ExceptionHolder : public Raisable { - public: - ExceptionHolder() {} - // Use default copy & assign. - - /** Take ownership of ex */ - template <class Ex> ExceptionHolder(Ex* ex) { wrap(ex); } - template <class Ex> ExceptionHolder(const boost::shared_ptr<Ex>& ex) { wrap(ex.release()); } - - template <class Ex> ExceptionHolder& operator=(Ex* ex) { wrap(ex); return *this; } - template <class Ex> ExceptionHolder& operator=(boost::shared_ptr<Ex> ex) { wrap(ex.release()); return *this; } - - void raise() const { if (wrapper.get()) wrapper->raise() ; } - std::string what() const { return wrapper->what(); } - bool empty() const { return !wrapper.get(); } - operator bool() const { return !empty(); } - void reset() { wrapper.reset(); } - - private: - template <class Ex> struct Wrapper : public Raisable { - Wrapper(Ex* ptr) : exception(ptr) {} - void raise() const { throw *exception; } - std::string what() const { return exception->what(); } - boost::shared_ptr<Ex> exception; - }; - template <class Ex> void wrap(Ex* ex) { wrapper.reset(new Wrapper<Ex>(ex)); } - boost::shared_ptr<Raisable> wrapper; -}; - - -}} // namespace qpid::sys - - -#endif /*!QPID_EXCEPTIONHOLDER_H*/ diff --git a/cpp/src/qpid/sys/FileSysDir.h b/cpp/src/qpid/sys/FileSysDir.h new file mode 100755 index 0000000000..ffe7823f0a --- /dev/null +++ b/cpp/src/qpid/sys/FileSysDir.h @@ -0,0 +1,62 @@ +#ifndef QPID_SYS_FILESYSDIR_H +#define QPID_SYS_FILESYSDIR_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 { +namespace sys { + +/** + * @class FileSysDir + * + * Represents a filesystem directory accessible from the local host. + * This class simply checks existence of, and creates, a directory. It could + * be added to later to list contents, etc. + */ +class FileSysDir +{ + const std::string dirPath; + + public: + + FileSysDir (std::string path) : dirPath(path) {} + ~FileSysDir () {} + + /** + * Check to see if the directory exists and is a directory. Throws an + * exception if there is an error checking existence or if the path + * exists but is not a directory. + * + * @retval true if the path exists and is a directory. + * @retval false if the path does not exist. + */ + bool exists (void) const; + + void mkdir(void); + + std::string getPath () { return dirPath; } +}; + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_FILESYSDIR_H*/ diff --git a/cpp/src/qpid/sys/LockFile.h b/cpp/src/qpid/sys/LockFile.h index f06cd6a47d..14a76cbf3e 100644 --- a/cpp/src/qpid/sys/LockFile.h +++ b/cpp/src/qpid/sys/LockFile.h @@ -19,7 +19,44 @@ * */ -#include "posix/LockFile.h" +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <string> + +#include "qpid/CommonImportExport.h" +#include "qpid/sys/IntegerTypes.h" + +namespace qpid { +namespace sys { + +class LockFilePrivate; + +/** + * @class LockFile + * + * LockFile represents a locked file suitable for a coarse-grain system + * lock. For example, the broker uses this to ensure that only one broker + * runs. A common usage idiom is to store the current "owner" process ID + * in the lock file - if the lock file exists, but the stored process ID + * doesn't, the old owner has probably died without cleaning up the lock + * file. + */ +class LockFile : private boost::noncopyable +{ + std::string path; + bool created; + boost::shared_ptr<LockFilePrivate> impl; + +protected: + int read(void*, size_t) const; + int write(void*, size_t) const; + +public: + QPID_COMMON_EXTERN LockFile(const std::string& path_, bool create); + QPID_COMMON_EXTERN ~LockFile(); +}; + +}} /* namespace qpid::sys */ #endif /*!_sys_LockFile_h*/ diff --git a/cpp/src/qpid/sys/LockPtr.h b/cpp/src/qpid/sys/LockPtr.h new file mode 100644 index 0000000000..738a864317 --- /dev/null +++ b/cpp/src/qpid/sys/LockPtr.h @@ -0,0 +1,89 @@ +#ifndef QPID_SYS_LOCKPTR_H +#define QPID_SYS_LOCKPTR_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/noncopyable.hpp> + +namespace qpid { +namespace sys { + +class Mutex; + +/** + * LockPtr is a smart pointer to T. It is constructed from a volatile + * T* and a Lock (by default a Mutex). It const_casts away the + * volatile qualifier and locks the Lock for the duration of its + * + * Used in conjuntion with the "volatile" keyword to get the compiler + * to help enforce correct concurrent use of mutli-threaded objects. + * See ochttp://www.ddj.com/cpp/184403766 for a detailed discussion. + * + * To summarize the convention: + * - Declare thread-safe member functions as volatile. + * - Declare instances of the class that may be called concurrently as volatile. + * - Use LockPtr to cast away the volatile qualifier while taking a lock. + * + * This means that code calling on a concurrently-used object + * (declared volatile) can only call thread-safe (volatile) member + * functions. Code that needs to use thread-unsafe members must use a + * LockPtr, thereby acquiring the lock and making it safe to do so. + * + * A good type-safe pattern is the internally-locked object: + * - It has it's own private lock member. + * - All public functions are thread safe and declared volatile. + * - Any thread-unsafe, non-volatile functions are private. + * - Only member function implementations use LockPtr to access private functions. + * + * This encapsulates all the locking logic inside the class. + * + * One nice feature of this convention is the common case where you + * need a public, locked version of some function foo() and also a + * private unlocked version to avoid recursive locks. They can be declared as + * volatile and non-volatile overloads of the same function: + * + * // public + * void Thing::foo() volatile { LockPtr<Thing>(this, myLock)->foo(); } + * // private + * void Thing::foo() { ... do stuff ...} + */ + +template <class T, class Lock> class LockPtr : public boost::noncopyable { + public: + LockPtr(volatile T* p, Lock& l) : ptr(const_cast<T*>(p)), lock(l) { lock.lock(); } + LockPtr(volatile T* p, volatile Lock& l) : ptr(const_cast<T*>(p)), lock(const_cast<Lock&>(l)) { lock.lock(); } + ~LockPtr() { lock.unlock(); } + + T& operator*() { return *ptr; } + T* operator->() { return ptr; } + + private: + T* ptr; + Lock& lock; +}; + + +}} // namespace qpid::sys + + +#endif /*!QPID_SYS_LOCKPTR_H*/ diff --git a/cpp/src/qpid/sys/Mutex.h b/cpp/src/qpid/sys/Mutex.h deleted file mode 100644 index b4bd3a9b4a..0000000000 --- a/cpp/src/qpid/sys/Mutex.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef _sys_Mutex_h -#define _sys_Mutex_h - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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 { - -/** - * Scoped lock template: calls lock() in ctor, unlock() in dtor. - * L can be any class with lock() and unlock() functions. - */ -template <class L> -class ScopedLock -{ - public: - ScopedLock(L& l) : mutex(l) { l.lock(); } - ~ScopedLock() { mutex.unlock(); } - private: - L& mutex; -}; - -template <class L> -class ScopedUnlock -{ - public: - ScopedUnlock(L& l) : mutex(l) { l.unlock(); } - ~ScopedUnlock() { mutex.lock(); } - private: - L& mutex; -}; - -template <class L> -class ScopedRlock -{ - public: - ScopedRlock(L& l) : mutex(l) { l.rlock(); } - ~ScopedRlock() { mutex.unlock(); } - private: - L& mutex; -}; - -template <class L> -class ScopedWlock -{ - public: - ScopedWlock(L& l) : mutex(l) { l.wlock(); } - ~ScopedWlock() { mutex.unlock(); } - private: - L& mutex; -}; - -template <class L> -class ConditionalScopedLock -{ - public: - ConditionalScopedLock(L& l) : mutex(l) { acquired = l.trylock(); } - ~ConditionalScopedLock() { if (acquired) mutex.unlock(); } - bool lockAcquired() { return acquired; } - private: - L& mutex; - bool acquired; -}; - -}} - -#ifdef USE_APR_PLATFORM -#include "apr/Mutex.h" -#else -#include "posix/Mutex.h" -#endif - -#endif /*!_sys_Mutex_h*/ diff --git a/cpp/src/qpid/sys/OutputControl.h b/cpp/src/qpid/sys/OutputControl.h index d922a0d85c..eae99beb0f 100644 --- a/cpp/src/qpid/sys/OutputControl.h +++ b/cpp/src/qpid/sys/OutputControl.h @@ -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,17 +18,22 @@ * under the License. * */ + +#include "qpid/sys/IntegerTypes.h" + #ifndef _OutputControl_ #define _OutputControl_ namespace qpid { namespace sys { - class OutputControl + class OutputControl { public: virtual ~OutputControl() {} + virtual void abort() = 0; virtual void activateOutput() = 0; + virtual void giveReadCredit(int32_t credit) = 0; }; } diff --git a/cpp/src/qpid/sys/PipeHandle.h b/cpp/src/qpid/sys/PipeHandle.h new file mode 100755 index 0000000000..8aac76996b --- /dev/null +++ b/cpp/src/qpid/sys/PipeHandle.h @@ -0,0 +1,51 @@ +#ifndef _sys_PipeHandle_h +#define _sys_PipeHandle_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 <string> + +// This class is a portability wrapper around pipe fds. +// It currently exists primarily and solely for the purpose of +// integration with single-threaded components that require QMF +// integration through a signalling fd. + +namespace qpid { +namespace sys { + + class PipeHandle { + private: + int writeFd; + int readFd; + public: + QPID_COMMON_EXTERN PipeHandle(bool nonBlocking=true); + QPID_COMMON_EXTERN ~PipeHandle(); + QPID_COMMON_EXTERN int read(void* buf, size_t bufSize); + QPID_COMMON_EXTERN int write(const void* buf, size_t bufSize); + QPID_COMMON_EXTERN int getReadHandle(); + }; + +}} + +#endif /*!_sys_PipeHandle_h*/ diff --git a/cpp/src/qpid/cluster/PollableCondition.h b/cpp/src/qpid/sys/PollableCondition.h index 6bfca6cabe..2eb6f2d947 100644 --- a/cpp/src/qpid/cluster/PollableCondition.h +++ b/cpp/src/qpid/sys/PollableCondition.h @@ -22,39 +22,43 @@ * */ -#include "qpid/sys/IOHandle.h" +#include "qpid/sys/Poller.h" +#include "qpid/CommonImportExport.h" +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> -// FIXME aconway 2008-08-11: this could be of more general interest, -// move to sys namespace in common lib. -// namespace qpid { -namespace cluster { +namespace sys { -/** - * A pollable condition to integrate in-process conditions with IO - * conditions in a polling loop. - * - * Setting the condition makes it readable for a poller. - * - * Writable/disconnected conditions are undefined and should not be - * polled for. - */ -class PollableCondition : public sys::IOHandle { - public: - PollableCondition(); +class PollableConditionPrivate; + +class PollableCondition { +public: + typedef boost::function1<void, PollableCondition&> Callback; + + QPID_COMMON_EXTERN PollableCondition(const Callback& cb, + const boost::shared_ptr<sys::Poller>& poller); - /** Set the condition, triggers readable in a poller. */ - void set(); + QPID_COMMON_EXTERN ~PollableCondition(); - /** Get the current state of the condition, then clear it. - *@return The state of the condition before it was cleared. + /** + * Set the condition. Triggers callback to Callback from Poller. */ - bool clear(); + QPID_COMMON_EXTERN void set(); - private: - int writeFd; + /** + * Clear the condition. Stops callbacks from Poller. + */ + QPID_COMMON_EXTERN void clear(); + + private: + PollableConditionPrivate *impl; + + Callback callback; + boost::shared_ptr<sys::Poller> poller; }; -}} // namespace qpid::cluster + +}} // namespace qpid::sys #endif /*!QPID_SYS_POLLABLECONDITION_H*/ diff --git a/cpp/src/qpid/sys/PollableQueue.h b/cpp/src/qpid/sys/PollableQueue.h new file mode 100644 index 0000000000..0786b21610 --- /dev/null +++ b/cpp/src/qpid/sys/PollableQueue.h @@ -0,0 +1,176 @@ +#ifndef QPID_SYS_POLLABLEQUEUE_H +#define QPID_SYS_POLLABLEQUEUE_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/PollableCondition.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Thread.h" +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <algorithm> +#include <vector> + +namespace qpid { +namespace sys { + +class Poller; + +/** + * A queue whose item processing is dispatched by sys::Poller. + * Any thread can push to the queue; items pushed trigger an event the Poller + * recognizes. When a Poller I/O thread dispatches the event, a + * user-specified callback is invoked with all items on the queue. + */ +template <class T> +class PollableQueue { + public: + typedef std::vector<T> Batch; + typedef T value_type; + + /** + * Callback to process a batch of items from the queue. + * + * @param batch Queue of values to process. Any items remaining + * on return from Callback are put back on the queue. + * @return iterator pointing to the first un-processed item in batch. + * Items from this point up to batch.end() are put back on the queue. + */ + typedef boost::function<typename Batch::const_iterator (const Batch& batch)> Callback; + + /** + * Constructor; sets necessary parameters. + * + * @param cb Callback that will be called to process items on the + * queue. Will be called from a Poller I/O thread. + * @param poller Poller to use for dispatching queue events. + */ + PollableQueue(const Callback& cb, + const boost::shared_ptr<sys::Poller>& poller); + + ~PollableQueue(); + + /** Push a value onto the queue. Thread safe */ + void push(const T& t); + + /** Start polling. */ + void start(); + + /** Stop polling and wait for the current callback, if any, to complete. */ + void stop(); + + /** Are we currently stopped?*/ + bool isStopped() const { ScopedLock l(lock); return stopped; } + + size_t size() { ScopedLock l(lock); return queue.size(); } + bool empty() { ScopedLock l(lock); return queue.empty(); } + + /** + * Allow any queued events to be processed; intended for calling + * after all dispatch threads exit the Poller loop in order to + * ensure clean shutdown with no events left on the queue. + */ + void shutdown(); + + private: + typedef sys::Monitor::ScopedLock ScopedLock; + typedef sys::Monitor::ScopedUnlock ScopedUnlock; + + void dispatch(PollableCondition& cond); + void process(); + + mutable sys::Monitor lock; + Callback callback; + PollableCondition condition; + Batch queue, batch; + Thread dispatcher; + bool stopped; +}; + +template <class T> PollableQueue<T>::PollableQueue( + const Callback& cb, const boost::shared_ptr<sys::Poller>& p) + : callback(cb), + condition(boost::bind(&PollableQueue<T>::dispatch, this, _1), p), + stopped(true) +{ +} + +template <class T> void PollableQueue<T>::start() { + ScopedLock l(lock); + if (!stopped) return; + stopped = false; + if (!queue.empty()) condition.set(); +} + +template <class T> PollableQueue<T>::~PollableQueue() { +} + +template <class T> void PollableQueue<T>::push(const T& t) { + ScopedLock l(lock); + if (queue.empty()) condition.set(); + queue.push_back(t); +} + +template <class T> void PollableQueue<T>::dispatch(PollableCondition& cond) { + ScopedLock l(lock); + assert(dispatcher.id() == 0); + dispatcher = Thread::current(); + process(); + dispatcher = Thread(); + if (queue.empty()) cond.clear(); + if (stopped) lock.notifyAll(); +} + +template <class T> void PollableQueue<T>::process() { + // Called with lock held + while (!stopped && !queue.empty()) { + assert(batch.empty()); + batch.swap(queue); + typename Batch::const_iterator putBack; + { + ScopedUnlock u(lock); // Allow concurrent push to queue. + putBack = callback(batch); + } + // put back unprocessed items. + queue.insert(queue.begin(), putBack, typename Batch::const_iterator(batch.end())); + batch.clear(); + } +} + +template <class T> void PollableQueue<T>::shutdown() { + ScopedLock l(lock); + process(); +} + +template <class T> void PollableQueue<T>::stop() { + ScopedLock l(lock); + if (stopped) return; + condition.clear(); + stopped = true; + // Avoid deadlock if stop is called from the dispatch thread + if (dispatcher.id() != Thread::current().id()) + while (dispatcher.id()) lock.wait(); +} + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_POLLABLEQUEUE_H*/ diff --git a/cpp/src/qpid/sys/Poller.h b/cpp/src/qpid/sys/Poller.h index e39528bdb5..413d4242b8 100644 --- a/cpp/src/qpid/sys/Poller.h +++ b/cpp/src/qpid/sys/Poller.h @@ -22,8 +22,9 @@ * */ -#include "Time.h" - +#include "qpid/sys/Time.h" +#include "qpid/sys/Runnable.h" +#include "qpid/CommonImportExport.h" #include <boost/shared_ptr.hpp> namespace qpid { @@ -37,7 +38,7 @@ namespace sys { */ class PollerHandle; class PollerPrivate; -class Poller { +class Poller : public Runnable { PollerPrivate* const impl; public: @@ -45,8 +46,8 @@ public: enum Direction { NONE = 0, - IN, - OUT, + INPUT, + OUTPUT, INOUT }; @@ -57,7 +58,8 @@ public: READ_WRITABLE, DISCONNECTED, SHUTDOWN, - TIMEOUT + TIMEOUT, + INTERRUPTED }; struct Event { @@ -72,16 +74,31 @@ public: void process(); }; - Poller(); - ~Poller(); + QPID_COMMON_EXTERN Poller(); + QPID_COMMON_EXTERN ~Poller(); /** Note: this function is async-signal safe */ - void shutdown(); + QPID_COMMON_EXTERN void shutdown(); + + // Interrupt waiting for a specific poller handle + // returns true if we could interrupt the handle + // - in this case on return the handle is no longer being monitored, + // but we will receive an event from some invocation of poller::wait + // with the handle and the INTERRUPTED event type + // if it returns false then the handle is not being monitored by the poller + // - This can either be because it has just received an event which has been + // reported and has not been reenabled since. + // - Because it was removed from the monitoring set + // - Or because it is already being interrupted + QPID_COMMON_EXTERN bool interrupt(PollerHandle& handle); + + // Poller run loop + QPID_COMMON_EXTERN void run(); - void addFd(PollerHandle& handle, Direction dir); - void delFd(PollerHandle& handle); - void modFd(PollerHandle& handle, Direction dir); - void rearmFd(PollerHandle& handle); - Event wait(Duration timeout = TIME_INFINITE); + QPID_COMMON_EXTERN void registerHandle(PollerHandle& handle); + QPID_COMMON_EXTERN void unregisterHandle(PollerHandle& handle); + QPID_COMMON_EXTERN void monitorHandle(PollerHandle& handle, Direction dir); + QPID_COMMON_EXTERN void unmonitorHandle(PollerHandle& handle, Direction dir); + QPID_COMMON_EXTERN Event wait(Duration timeout = TIME_INFINITE); }; /** @@ -91,14 +108,15 @@ class IOHandle; class PollerHandlePrivate; class PollerHandle { friend class Poller; + friend class PollerPrivate; friend struct Poller::Event; PollerHandlePrivate* const impl; - virtual void processEvent(Poller::EventType) {}; + QPID_COMMON_EXTERN virtual void processEvent(Poller::EventType) {}; public: - PollerHandle(const IOHandle& h); - virtual ~PollerHandle(); + QPID_COMMON_EXTERN PollerHandle(const IOHandle& h); + QPID_COMMON_EXTERN virtual ~PollerHandle(); }; inline void Poller::Event::process() { diff --git a/cpp/src/qpid/sys/ProtocolFactory.h b/cpp/src/qpid/sys/ProtocolFactory.h index 4aa14d2cf6..b233b2da1a 100644 --- a/cpp/src/qpid/sys/ProtocolFactory.h +++ b/cpp/src/qpid/sys/ProtocolFactory.h @@ -22,9 +22,9 @@ * */ -#include <stdint.h> +#include "qpid/sys/IntegerTypes.h" #include "qpid/SharedObject.h" -#include "ConnectionCodec.h" +#include "qpid/sys/ConnectionCodec.h" #include <boost/function.hpp> namespace qpid { @@ -46,6 +46,7 @@ class ProtocolFactory : public qpid::SharedObject<ProtocolFactory> const std::string& host, int16_t 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 new file mode 100644 index 0000000000..bd19247124 --- /dev/null +++ b/cpp/src/qpid/sys/RdmaIOPlugin.cpp @@ -0,0 +1,353 @@ +/* + * + * 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/framing/AMQP_HighestVersion.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/rdma/RdmaIO.h" +#include "qpid/sys/OutputControl.h" + +#include <boost/bind.hpp> +#include <boost/lexical_cast.hpp> +#include <memory> + +#include <netdb.h> + +using std::auto_ptr; +using std::string; +using std::stringstream; + +namespace qpid { +namespace sys { + +class RdmaIOHandler : public OutputControl { + Rdma::Connection::intrusive_ptr connection; + std::string identifier; + Rdma::AsynchIO* aio; + ConnectionCodec::Factory* factory; + ConnectionCodec* codec; + bool readError; + + void write(const framing::ProtocolInitiation&); + + public: + RdmaIOHandler(Rdma::Connection::intrusive_ptr& c, ConnectionCodec::Factory* f); + ~RdmaIOHandler(); + void init(Rdma::AsynchIO* a); + void start(Poller::shared_ptr poller) {aio->start(poller);} + + // Output side + void close(); + void abort(); + void activateOutput(); + void giveReadCredit(int32_t credit); + void initProtocolOut(); + + // Input side + void readbuff(Rdma::AsynchIO& aio, Rdma::Buffer* buff); + void initProtocolIn(Rdma::Buffer* buff); + + // Notifications + void full(Rdma::AsynchIO& aio); + void idle(Rdma::AsynchIO& aio); + void error(Rdma::AsynchIO& aio); +}; + +RdmaIOHandler::RdmaIOHandler(Rdma::Connection::intrusive_ptr& c, qpid::sys::ConnectionCodec::Factory* f) : + connection(c), + identifier(c->getPeerName()), + factory(f), + codec(0), + readError(false) +{ +} + +void RdmaIOHandler::init(Rdma::AsynchIO* a) { + aio = a; +} + +RdmaIOHandler::~RdmaIOHandler() { + if (codec) + codec->closed(); + delete codec; + + aio->deferDelete(); +} + +void RdmaIOHandler::write(const framing::ProtocolInitiation& data) +{ + QPID_LOG(debug, "Rdma: SENT [" << identifier << "] INIT(" << data << ")"); + Rdma::Buffer* buff = aio->getBuffer(); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void RdmaIOHandler::close() { + aio->queueWriteClose(); +} + +// TODO: Dummy implementation, need to fill this in for heartbeat timeout to work +void RdmaIOHandler::abort() { +} + +void RdmaIOHandler::activateOutput() { + aio->notifyPendingWrite(); +} + +void RdmaIOHandler::idle(Rdma::AsynchIO&) { + // TODO: Shouldn't need this test as idle() should only ever be called when + // the connection is writable anyway + if ( !(aio->writable() && aio->bufferAvailable()) ) { + return; + } + if (codec == 0) return; + if (codec->canEncode()) { + Rdma::Buffer* buff = aio->getBuffer(); + size_t encoded=codec->encode(buff->bytes, buff->byteCount); + buff->dataCount = encoded; + aio->queueWrite(buff); + } + if (codec->isClosed()) + aio->queueWriteClose(); +} + +void RdmaIOHandler::initProtocolOut() { + // We mustn't have already started the conversation + // but we must be able to send + assert( codec == 0 ); + assert( aio->writable() && aio->bufferAvailable() ); + codec = factory->create(*this, identifier, 0); + write(framing::ProtocolInitiation(codec->getVersion())); +} + +void RdmaIOHandler::error(Rdma::AsynchIO&) { + close(); +} + +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 +// smaller than a frame +void RdmaIOHandler::readbuff(Rdma::AsynchIO&, Rdma::Buffer* buff) { + if (readError) { + return; + } + size_t decoded = 0; + try { + if (codec) { + decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount); + }else{ + // Need to start protocol processing + initProtocolIn(buff); + } + }catch(const std::exception& e){ + QPID_LOG(error, e.what()); + readError = true; + aio->queueWriteClose(); + } +} + +void RdmaIOHandler::initProtocolIn(Rdma::Buffer* buff) { + framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); + framing::ProtocolInitiation protocolInit; + size_t decoded = 0; + if (protocolInit.decode(in)) { + decoded = in.getPosition(); + QPID_LOG(debug, "Rdma: RECV [" << identifier << "] INIT(" << protocolInit << ")"); + + codec = factory->create(protocolInit.getVersion(), *this, identifier, 0); + + // If we failed to create the codec then we don't understand the offered protocol version + if (!codec) { + // send valid version header & close connection. + write(framing::ProtocolInitiation(framing::highestProtocolVersion)); + readError = true; + aio->queueWriteClose(); + } + } +} + +class RdmaIOProtocolFactory : public ProtocolFactory { + auto_ptr<Rdma::Listener> listener; + const uint16_t listeningPort; + + public: + RdmaIOProtocolFactory(int16_t port, int backlog); + void accept(Poller::shared_ptr, ConnectionCodec::Factory*); + void connect(Poller::shared_ptr, const string& host, int16_t port, ConnectionCodec::Factory*, ConnectFailedCallback); + + uint16_t getPort() const; + string getHost() const; + + private: + bool request(Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&, ConnectionCodec::Factory*); + void established(Poller::shared_ptr, Rdma::Connection::intrusive_ptr&); + void connected(Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&, ConnectionCodec::Factory*); + void connectionError(Rdma::Connection::intrusive_ptr&, Rdma::ErrorType); + void disconnected(Rdma::Connection::intrusive_ptr&); + void rejected(Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&, ConnectFailedCallback); +}; + +// Static instance to initialise plugin +static class RdmaIOPlugin : public Plugin { + void earlyInitialize(Target&) { + } + + void initialize(Target& target) { + // Check whether we actually have any rdma devices + if ( Rdma::deviceCount() == 0 ) { + QPID_LOG(info, "Rdma: Disabled: no rdma devices found"); + return; + } + + broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); + // Only provide to a Broker + if (broker) { + const broker::Broker::Options& opts = broker->getOptions(); + ProtocolFactory::shared_ptr protocol(new RdmaIOProtocolFactory(opts.port, opts.connectionBacklog)); + QPID_LOG(notice, "Rdma: Listening on RDMA port " << protocol->getPort()); + broker->registerProtocolFactory("rdma", protocol); + } + } +} rdmaPlugin; + +RdmaIOProtocolFactory::RdmaIOProtocolFactory(int16_t port, int /*backlog*/) : + listeningPort(port) +{} + +void RdmaIOProtocolFactory::established(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci) { + RdmaIOHandler* async = ci->getContext<RdmaIOHandler>(); + async->start(poller); +} + +bool RdmaIOProtocolFactory::request(Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp, + ConnectionCodec::Factory* f) { + try { + RdmaIOHandler* async = new RdmaIOHandler(ci, f); + Rdma::AsynchIO* aio = + new Rdma::AsynchIO(ci->getQueuePair(), + cp.maxRecvBufferSize, cp.initialXmitCredit, Rdma::DEFAULT_WR_ENTRIES, + boost::bind(&RdmaIOHandler::readbuff, async, _1, _2), + boost::bind(&RdmaIOHandler::idle, async, _1), + 0, // boost::bind(&RdmaIOHandler::full, async, _1), + boost::bind(&RdmaIOHandler::error, async, _1)); + async->init(aio); + + // Record aio so we can get it back from a connection + ci->addContext(async); + return true; + } catch (const Rdma::Exception& e) { + QPID_LOG(error, "Rdma: Cannot accept new connection (Rdma exception): " << e.what()); + } catch (const std::exception& e) { + QPID_LOG(error, "Rdma: Cannot accept new connection (unknown exception): " << e.what()); + } + + // If we get here we caught an exception so reject connection + return false; +} + +void RdmaIOProtocolFactory::connectionError(Rdma::Connection::intrusive_ptr&, Rdma::ErrorType) { +} + +void RdmaIOProtocolFactory::disconnected(Rdma::Connection::intrusive_ptr& ci) { + // If we've got a connection already tear it down, otherwise ignore + RdmaIOHandler* async = ci->getContext<RdmaIOHandler>(); + if (async) { + async->close(); + } + delete async; +} + +uint16_t RdmaIOProtocolFactory::getPort() const { + return listeningPort; // Immutable no need for lock. +} + +string RdmaIOProtocolFactory::getHost() const { + //return listener.getSockname(); + return ""; +} + +void RdmaIOProtocolFactory::accept(Poller::shared_ptr poller, ConnectionCodec::Factory* fact) { + ::sockaddr_in sin; + + sin.sin_family = AF_INET; + sin.sin_port = htons(listeningPort); + sin.sin_addr.s_addr = INADDR_ANY; + + SocketAddress sa("",boost::lexical_cast<std::string>(listeningPort)); + listener.reset( + new Rdma::Listener(sa, + Rdma::ConnectionParams(65536, Rdma::DEFAULT_WR_ENTRIES), + boost::bind(&RdmaIOProtocolFactory::established, this, poller, _1), + boost::bind(&RdmaIOProtocolFactory::connectionError, this, _1, _2), + boost::bind(&RdmaIOProtocolFactory::disconnected, this, _1), + boost::bind(&RdmaIOProtocolFactory::request, this, _1, _2, fact))); + + listener->start(poller); +} + +// Only used for outgoing connections (in federation) +void RdmaIOProtocolFactory::rejected(Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&, ConnectFailedCallback failed) { + failed(-1, "Connection rejected"); +} + +// Do the same as connection request and established but mark a client too +void RdmaIOProtocolFactory::connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp, + ConnectionCodec::Factory* f) { + (void) request(ci, cp, f); + established(poller, ci); + RdmaIOHandler* async = ci->getContext<RdmaIOHandler>(); + async->initProtocolOut(); +} + +void RdmaIOProtocolFactory::connect( + Poller::shared_ptr poller, + const std::string& host, int16_t port, + ConnectionCodec::Factory* f, + ConnectFailedCallback failed) +{ + SocketAddress sa(host, boost::lexical_cast<std::string>(port)); + Rdma::Connector* c = + new Rdma::Connector( + sa, + Rdma::ConnectionParams(8000, Rdma::DEFAULT_WR_ENTRIES), + boost::bind(&RdmaIOProtocolFactory::connected, this, poller, _1, _2, f), + boost::bind(&RdmaIOProtocolFactory::connectionError, this, _1, _2), + boost::bind(&RdmaIOProtocolFactory::disconnected, this, _1), + boost::bind(&RdmaIOProtocolFactory::rejected, this, _1, _2, failed)); + + c->start(poller); +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/Runnable.cpp b/cpp/src/qpid/sys/Runnable.cpp index 30122c682f..325d87c91b 100644 --- a/cpp/src/qpid/sys/Runnable.cpp +++ b/cpp/src/qpid/sys/Runnable.cpp @@ -16,7 +16,7 @@ * */ -#include "Runnable.h" +#include "qpid/sys/Runnable.h" #include <boost/bind.hpp> namespace qpid { diff --git a/cpp/src/qpid/sys/SystemInfo.h b/cpp/src/qpid/sys/SecurityLayer.h index 73c3ca3c17..52bc40e352 100644 --- a/cpp/src/qpid/sys/SystemInfo.h +++ b/cpp/src/qpid/sys/SecurityLayer.h @@ -1,7 +1,8 @@ -#ifndef QPID_SYS_SYSTEMINFO_H -#define QPID_SYS_SYSTEMINFO_H +#ifndef QPID_SYS_SECURITYLAYER_H +#define QPID_SYS_SECURITYLAYER_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 @@ -9,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 @@ -20,25 +21,22 @@ * under the License. * */ +#include "qpid/sys/Codec.h" namespace qpid { namespace sys { /** - * Retrieve information about the system we are running on. - * Results may be dependent on OS/hardware. + * Defines interface to a SASL negotiated Security Layer (for + * encryption/integrity) */ -class SystemInfo +class SecurityLayer : public Codec { public: - /** Estimate available concurrency, e.g. number of CPU cores. - * -1 means estimate not available on this platform. - */ - static long concurrency(); + virtual void init(Codec*) = 0; + virtual ~SecurityLayer() {} }; }} // namespace qpid::sys - - -#endif /*!QPID_SYS_SYSTEMINFO_H*/ +#endif /*!QPID_SYS_SECURITYLAYER_H*/ diff --git a/cpp/src/qpid/sys/Semaphore.h b/cpp/src/qpid/sys/Semaphore.h index 3efb7ce2df..9d70f89aeb 100644 --- a/cpp/src/qpid/sys/Semaphore.h +++ b/cpp/src/qpid/sys/Semaphore.h @@ -19,7 +19,7 @@ * */ -#include "Monitor.h" +#include "qpid/sys/Monitor.h" namespace qpid { namespace sys { @@ -51,10 +51,22 @@ public: count--; } + void release(uint n) + { + Monitor::ScopedLock l(monitor); + if (count==0) monitor.notifyAll(); + count+=n; + } + void release() { + release(1); + } + + void forceLock() + { Monitor::ScopedLock l(monitor); - if (!count++) monitor.notifyAll(); + count = 0; } private: diff --git a/cpp/src/qpid/sys/Shlib.cpp b/cpp/src/qpid/sys/Shlib.cpp index 8fd3f42cc6..342d726876 100644 --- a/cpp/src/qpid/sys/Shlib.cpp +++ b/cpp/src/qpid/sys/Shlib.cpp @@ -18,7 +18,7 @@ * */ -#include "Shlib.h" +#include "qpid/sys/Shlib.h" #include "qpid/log/Statement.h" diff --git a/cpp/src/qpid/sys/Shlib.h b/cpp/src/qpid/sys/Shlib.h index e2752dc7d6..7f66cfec14 100644 --- a/cpp/src/qpid/sys/Shlib.h +++ b/cpp/src/qpid/sys/Shlib.h @@ -21,10 +21,10 @@ * under the License. * */ - + +#include "qpid/CommonImportExport.h" #include <boost/noncopyable.hpp> #include <iostream> -#include <dlfcn.h> namespace qpid { namespace sys { @@ -41,10 +41,10 @@ class Shlib { Shlib(const std::string& libname) { load(libname.c_str()); } /** Unload shared library. */ - void unload(); + QPID_COMMON_EXTERN void unload(); /** Look up symbol. */ - void* getSymbol(const char* symbol); + QPID_COMMON_EXTERN void* getSymbol(const char* symbol); /** Look up symbol in shared library, cast it to the desired * pointer type, void* by default. @@ -58,7 +58,7 @@ class Shlib { private: void* handle; - void load(const char* libname); + QPID_COMMON_EXTERN void load(const char* libname); }; /** A shared library handle that unloads the shlib in it's dtor */ @@ -67,7 +67,7 @@ class AutoShlib : public Shlib { /** Load shared library */ AutoShlib(const std::string& libname) : Shlib(libname) {} /** Calls unload() */ - ~AutoShlib() throw(); + QPID_COMMON_EXTERN ~AutoShlib() throw(); }; diff --git a/cpp/src/qpid/sys/Socket.h b/cpp/src/qpid/sys/Socket.h index dd7ef9a96d..7b50c42a3c 100644 --- a/cpp/src/qpid/sys/Socket.h +++ b/cpp/src/qpid/sys/Socket.h @@ -22,47 +22,48 @@ * */ -#include "IOHandle.h" - +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/CommonImportExport.h" #include <string> -struct sockaddr; - namespace qpid { namespace sys { class Duration; +class SocketAddress; class Socket : public IOHandle { public: /** Create a socket wrapper for descriptor. */ - Socket(); + QPID_COMMON_EXTERN Socket(); - /** Create an initialized TCP socket */ - void createTcp() const; - /** Set timeout for read and write */ void setTimeout(const Duration& interval) const; - + /** Set socket non blocking */ void setNonblocking() const; - void connect(const std::string& host, uint16_t port) const; + QPID_COMMON_EXTERN void setTcpNoDelay() const; + + QPID_COMMON_EXTERN void connect(const std::string& host, uint16_t port) const; + QPID_COMMON_EXTERN void connect(const SocketAddress&) const; - void close() const; + QPID_COMMON_EXTERN 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. *@return The bound port. */ - int listen(uint16_t port = 0, int backlog = 10) const; - + QPID_COMMON_EXTERN int listen(uint16_t port = 0, int backlog = 10) const; + QPID_COMMON_EXTERN int listen(const SocketAddress&, int backlog = 10) const; + /** Returns the "socket name" ie the address bound to * the near end of the socket */ - std::string getSockname() const; + QPID_COMMON_EXTERN std::string getSockname() const; /** Returns the "peer name" ie the address bound to * the remote end of the socket @@ -73,14 +74,14 @@ public: * Returns an address (host and port) for the remote end of the * socket */ - std::string getPeerAddress() const; + QPID_COMMON_EXTERN std::string getPeerAddress() const; /** * Returns an address (host and port) for the local end of the * socket */ std::string getLocalAddress() const; - uint16_t getLocalPort() const; + QPID_COMMON_EXTERN uint16_t getLocalPort() const; uint16_t getRemotePort() const; /** @@ -92,17 +93,20 @@ public: /** Accept a connection from a socket that is already listening * and has an incoming connection */ - Socket* accept(struct sockaddr *addr, socklen_t *addrlen) const; + QPID_COMMON_EXTERN 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; - - void setTcpNoDelay(bool nodelay) const; + QPID_COMMON_EXTERN int read(void *buf, size_t count) const; + QPID_COMMON_EXTERN int write(const void *buf, size_t count) const; private: + /** Create socket */ + void createSocket(const SocketAddress&) const; + Socket(IOHandlePrivate*); mutable std::string connectname; + mutable bool nonblocking; + mutable bool nodelay; }; }} diff --git a/cpp/src/qpid/sys/SocketAddress.h b/cpp/src/qpid/sys/SocketAddress.h new file mode 100644 index 0000000000..27b9642f2c --- /dev/null +++ b/cpp/src/qpid/sys/SocketAddress.h @@ -0,0 +1,53 @@ +#ifndef _sys_SocketAddress_h +#define _sys_SocketAddress_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 <string> + +struct addrinfo; + +namespace qpid { +namespace sys { + +class SocketAddress { + friend const ::addrinfo& getAddrInfo(const SocketAddress&); + +public: + /** Create a SocketAddress from hostname and port*/ + QPID_COMMON_EXTERN SocketAddress(const std::string& host, const std::string& port); + QPID_COMMON_EXTERN SocketAddress(const SocketAddress&); + QPID_COMMON_EXTERN SocketAddress& operator=(const SocketAddress&); + QPID_COMMON_EXTERN ~SocketAddress(); + + std::string asString() const; + +private: + std::string host; + std::string port; + mutable ::addrinfo* addrInfo; +}; + +}} +#endif /*!_sys_SocketAddress_h*/ diff --git a/cpp/src/qpid/sys/SslPlugin.cpp b/cpp/src/qpid/sys/SslPlugin.cpp new file mode 100644 index 0000000000..c143f1f1d0 --- /dev/null +++ b/cpp/src/qpid/sys/SslPlugin.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/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/sys/ssl/SslIo.h" +#include "qpid/sys/ssl/SslSocket.h" +#include "qpid/broker/Broker.h" +#include "qpid/log/Statement.h" + +#include <boost/bind.hpp> +#include <memory> + + +namespace qpid { +namespace sys { + +struct SslServerOptions : ssl::SslOptions +{ + uint16_t port; + bool clientAuth; + + SslServerOptions() : port(5671), + clientAuth(false) + { + addOptions() + ("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 ProtocolFactory { + const bool tcpNoDelay; + qpid::sys::ssl::SslSocket listener; + const uint16_t listeningPort; + std::auto_ptr<qpid::sys::ssl::SslAcceptor> acceptor; + + public: + SslProtocolFactory(const SslServerOptions&, int backlog, bool nodelay); + void accept(Poller::shared_ptr, ConnectionCodec::Factory*); + void connect(Poller::shared_ptr, const std::string& host, int16_t port, + ConnectionCodec::Factory*, + boost::function2<void, int, std::string> failed); + + uint16_t getPort() const; + std::string getHost() const; + bool supports(const std::string& capability); + + private: + void established(Poller::shared_ptr, const qpid::sys::ssl::SslSocket&, ConnectionCodec::Factory*, + bool isClient); +}; + +// Static instance to initialise plugin +static struct SslPlugin : public Plugin { + SslServerOptions options; + + Options* getOptions() { return &options; } + + ~SslPlugin() { ssl::shutdownNSS(); } + + void earlyInitialize(Target&) { + } + + void initialize(Target& target) { + broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); + // Only provide to a Broker + if (broker) { + if (options.certDbPath.empty()) { + QPID_LOG(info, "SSL plugin not enabled, you must set --ssl-cert-db to enable it."); + } else { + try { + ssl::initNSS(options, true); + + const broker::Broker::Options& opts = broker->getOptions(); + ProtocolFactory::shared_ptr protocol(new SslProtocolFactory(options, + opts.connectionBacklog, opts.tcpNoDelay)); + 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 plugin: " << e.what()); + } + } + } + } +} sslPlugin; + +SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, int backlog, bool nodelay) : + tcpNoDelay(nodelay), listeningPort(listener.listen(options.port, backlog, options.certName, options.clientAuth)) +{} + +void SslProtocolFactory::established(Poller::shared_ptr poller, const qpid::sys::ssl::SslSocket& s, + ConnectionCodec::Factory* f, bool isClient) { + qpid::sys::ssl::SslHandler* async = new qpid::sys::ssl::SslHandler(s.getPeerAddress(), f); + + if (tcpNoDelay) { + s.setTcpNoDelay(tcpNoDelay); + QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress()); + } + + if (isClient) + async->setClient(); + 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, 4); + aio->start(poller); +} + +uint16_t SslProtocolFactory::getPort() const { + return listeningPort; // Immutable no need for lock. +} + +std::string SslProtocolFactory::getHost() const { + return listener.getSockname(); +} + +void SslProtocolFactory::accept(Poller::shared_ptr poller, + ConnectionCodec::Factory* fact) { + acceptor.reset( + new qpid::sys::ssl::SslAcceptor(listener, + boost::bind(&SslProtocolFactory::established, this, poller, _1, fact, false))); + acceptor->start(poller); +} + +void SslProtocolFactory::connect( + Poller::shared_ptr poller, + const std::string& host, int16_t port, + ConnectionCodec::Factory* fact, + ConnectFailedCallback failed) +{ + // Note that the following logic does not cause a memory leak. + // The allocated Socket is freed either by the SslConnector + // upon connection failure or by the SslIoHandle upon connection + // shutdown. The allocated SslConnector frees itself when it + // is no longer needed. + + qpid::sys::ssl::SslSocket* socket = new qpid::sys::ssl::SslSocket(); + new qpid::sys::ssl::SslConnector (*socket, poller, host, port, + boost::bind(&SslProtocolFactory::established, this, poller, _1, fact, true), + failed); +} + +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 diff --git a/cpp/src/qpid/sys/TCPIOPlugin.cpp b/cpp/src/qpid/sys/TCPIOPlugin.cpp index f38cf88e45..39ae12c189 100644 --- a/cpp/src/qpid/sys/TCPIOPlugin.cpp +++ b/cpp/src/qpid/sys/TCPIOPlugin.cpp @@ -19,12 +19,13 @@ * */ -#include "ProtocolFactory.h" -#include "AsynchIOHandler.h" -#include "AsynchIO.h" +#include "qpid/sys/ProtocolFactory.h" +#include "qpid/sys/AsynchIOHandler.h" +#include "qpid/sys/AsynchIO.h" #include "qpid/Plugin.h" #include "qpid/sys/Socket.h" +#include "qpid/sys/Poller.h" #include "qpid/broker/Broker.h" #include "qpid/log/Statement.h" @@ -45,7 +46,7 @@ class AsynchIOProtocolFactory : public ProtocolFactory { void accept(Poller::shared_ptr, ConnectionCodec::Factory*); void connect(Poller::shared_ptr, const std::string& host, int16_t port, ConnectionCodec::Factory*, - boost::function2<void, int, std::string> failed); + ConnectFailedCallback); uint16_t getPort() const; std::string getHost() const; @@ -53,6 +54,7 @@ class AsynchIOProtocolFactory : public ProtocolFactory { private: void established(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*, bool isClient); + void connectFailed(const Socket&, int, const std::string&, ConnectFailedCallback); }; // Static instance to initialise plugin @@ -65,9 +67,10 @@ static class TCPIOPlugin : public Plugin { // Only provide to a Broker if (broker) { const broker::Broker::Options& opts = broker->getOptions(); - ProtocolFactory::shared_ptr protocol(new AsynchIOProtocolFactory(opts.port, opts.connectionBacklog, opts.tcpNoDelay)); - QPID_LOG(info, "Listening on TCP port " << protocol->getPort()); - broker->registerProtocolFactory(protocol); + ProtocolFactory::shared_ptr protocol(new AsynchIOProtocolFactory(opts.port, opts.connectionBacklog, + opts.tcpNoDelay)); + QPID_LOG(notice, "Listening on TCP port " << protocol->getPort()); + broker->registerProtocolFactory("tcp", protocol); } } } tcpPlugin; @@ -81,19 +84,20 @@ void AsynchIOProtocolFactory::established(Poller::shared_ptr poller, const Socke AsynchIOHandler* async = new AsynchIOHandler(s.getPeerAddress(), f); if (tcpNoDelay) { - s.setTcpNoDelay(tcpNoDelay); + s.setTcpNoDelay(); QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress()); } if (isClient) async->setClient(); - AsynchIO* aio = new AsynchIO(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, 4); aio->start(poller); @@ -110,11 +114,20 @@ std::string AsynchIOProtocolFactory::getHost() const { void AsynchIOProtocolFactory::accept(Poller::shared_ptr poller, ConnectionCodec::Factory* fact) { acceptor.reset( - new AsynchAcceptor(listener, + AsynchAcceptor::create(listener, boost::bind(&AsynchIOProtocolFactory::established, this, poller, _1, fact, false))); acceptor->start(poller); } +void AsynchIOProtocolFactory::connectFailed( + const Socket& s, int ec, const std::string& emsg, + ConnectFailedCallback failedCb) +{ + failedCb(ec, emsg); + s.close(); + delete &s; +} + void AsynchIOProtocolFactory::connect( Poller::shared_ptr poller, const std::string& host, int16_t port, @@ -128,9 +141,14 @@ void AsynchIOProtocolFactory::connect( // is no longer needed. Socket* socket = new Socket(); - new AsynchConnector (*socket, poller, host, port, - boost::bind(&AsynchIOProtocolFactory::established, this, poller, _1, fact, true), - failed); + AsynchConnector::create(*socket, + poller, + host, + port, + boost::bind(&AsynchIOProtocolFactory::established, + this, poller, _1, fact, true), + boost::bind(&AsynchIOProtocolFactory::connectFailed, + this, _1, _2, _3, failed)); } }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/Time.h b/cpp/src/qpid/sys/Time.h deleted file mode 100644 index 6501cd0806..0000000000 --- a/cpp/src/qpid/sys/Time.h +++ /dev/null @@ -1,154 +0,0 @@ -#ifndef _sys_Time_h -#define _sys_Time_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 <stdint.h> -#include <limits> -#include <iosfwd> - -namespace qpid { -namespace sys { - -class Duration; - -/** Class to represent an instant in time: - * The time resolution is in nanosecs, and this is held with 64 bits - * giving a total time span from about 25 million years ago to 25 million - * years hence. As an aside the internal time can sensibly be negative - * meaning before the epoch (probably 1/1/1970 although this class doesn't - * care). - * - * The AbsTime class is a value class and so you don't need to add any accessors - * to its internal state. If you think you want to replace its value,i - * You need to construct a new AbsTime and assign it, viz: - * - * AbsTime when = AbsTime::now(); - * ... - * when = AbsTime(when, 2*TIME_SEC); // Advance timer 2 secs - * - * If for some reason you need access to the internal nanosec value you need - * to convert the AbsTime to a Duration and use its conversion to int64_t, viz: - * - * AbsTime now = AbsTime::now(); - * - * int64_t ns = Duration(now); - * - * However note that the nanosecond value that is returned here is not defined to be - * anything in particular and could vary from platform to platform. - * - * There are some sensible operations that are currently missing from AbsTime, but - * nearly all that's needed can be done with a mixture of AbsTimes and Durations. - * - * For example, convenience operators to add a Duration and AbsTime returning an AbsTime - * would fit here (although you can already perform the operation with one of the AbsTime - * constructors). However trying to add 2 AbsTimes doesn't make sense. - */ -class AbsTime { - static int64_t max() { return std::numeric_limits<int64_t>::max(); } - int64_t time_ns; - - friend class Duration; - -public: - inline AbsTime() {} - inline AbsTime(const AbsTime& time0, const Duration& duration); - // Default assignment operation fine - // Default copy constructor fine - - static AbsTime now(); - inline static AbsTime FarFuture(); - bool operator==(const AbsTime& t) const { return t.time_ns == time_ns; } - template <class S> void serialize(S& s) { s(time_ns); } - - friend bool operator<(const AbsTime& a, const AbsTime& b); - friend bool operator>(const AbsTime& a, const AbsTime& b); - friend std::ostream& operator << (std::ostream&, const AbsTime&); -}; - -std::ostream& operator << (std::ostream&, const AbsTime&); - -/** Class to represent the duration between instants of time: - * As AbsTime this class also uses nanosecs for its time - * resolution. For the most part a duration can be dealt with like a - * 64 bit integer, and indeed there is an implicit conversion which - * makes this quite conveient. - */ -class Duration { - static int64_t max() { return std::numeric_limits<int64_t>::max(); } - int64_t nanosecs; - - friend class AbsTime; - -public: - inline Duration(int64_t time0); - inline explicit Duration(const AbsTime& time0); - inline explicit Duration(const AbsTime& start, const AbsTime& finish); - inline operator int64_t() const; -}; - -std::ostream& operator << (std::ostream&, const Duration&); - -AbsTime::AbsTime(const AbsTime& t, const Duration& d) : - time_ns(d == Duration::max() ? max() : t.time_ns+d.nanosecs) -{} - -AbsTime AbsTime::FarFuture() { AbsTime ff; ff.time_ns = max(); return ff;} - -inline AbsTime now() { return AbsTime::now(); } - -inline bool operator<(const AbsTime& a, const AbsTime& b) { return a.time_ns < b.time_ns; } -inline bool operator>(const AbsTime& a, const AbsTime& b) { return a.time_ns > b.time_ns; } - -Duration::Duration(int64_t time0) : - nanosecs(time0) -{} - -Duration::Duration(const AbsTime& time0) : - nanosecs(time0.time_ns) -{} - -Duration::Duration(const AbsTime& start, const AbsTime& finish) : - nanosecs(finish.time_ns - start.time_ns) -{} - -Duration::operator int64_t() const -{ return nanosecs; } - -/** Nanoseconds per second. */ -const Duration TIME_SEC = 1000*1000*1000; -/** Nanoseconds per millisecond */ -const Duration TIME_MSEC = 1000*1000; -/** Nanoseconds per microseconds. */ -const Duration TIME_USEC = 1000; -/** Nanoseconds per nanosecond. */ -const Duration TIME_NSEC = 1; - -/** Value to represent an infinite timeout */ -const Duration TIME_INFINITE = std::numeric_limits<int64_t>::max(); - -/** Time greater than any other time */ -const AbsTime FAR_FUTURE = AbsTime::FarFuture(); - -}} - -#endif /*!_sys_Time_h*/ diff --git a/cpp/src/qpid/sys/Timer.cpp b/cpp/src/qpid/sys/Timer.cpp new file mode 100644 index 0000000000..c18fd93538 --- /dev/null +++ b/cpp/src/qpid/sys/Timer.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/sys/Timer.h" +#include "qpid/sys/Mutex.h" +#include "qpid/log/Statement.h" + +#include <numeric> + +using boost::intrusive_ptr; +using std::max; + +namespace qpid { +namespace sys { + +TimerTask::TimerTask(Duration timeout) : + sortTime(AbsTime::FarFuture()), + period(timeout), + nextFireTime(AbsTime::now(), timeout), + cancelled(false) +{} + +TimerTask::TimerTask(AbsTime time) : + sortTime(AbsTime::FarFuture()), + period(0), + nextFireTime(time), + cancelled(false) +{} + +TimerTask::~TimerTask() {} + +bool TimerTask::readyToFire() const { + return !(nextFireTime > AbsTime::now()); +} + +void TimerTask::fireTask() { + cancelled = true; + fire(); +} + +// This can only be used to setup the next fire time. After the Timer has already fired +void TimerTask::setupNextFire() { + if (period && readyToFire()) { + nextFireTime = max(AbsTime::now(), AbsTime(nextFireTime, period)); + cancelled = false; + } else { + QPID_LOG(error, "Couldn't setup next timer firing: " << Duration(nextFireTime, AbsTime::now()) << "[" << period << "]"); + } +} + +// Only allow tasks to be delayed +void TimerTask::restart() { nextFireTime = max(nextFireTime, AbsTime(AbsTime::now(), period)); } + +void TimerTask::cancel() { + ScopedLock<Mutex> l(callbackLock); + cancelled = true; +} + +Timer::Timer() : + active(false) +{ + start(); +} + +Timer::~Timer() +{ + stop(); +} + +// TODO AStitcher 21/08/09 The threshholds for emitting warnings are a little arbitrary +void Timer::run() +{ + Monitor::ScopedLock l(monitor); + while (active) { + if (tasks.empty()) { + monitor.wait(); + } else { + intrusive_ptr<TimerTask> t = tasks.top(); + tasks.pop(); + assert(!(t->nextFireTime < t->sortTime)); + + // warn on extreme lateness + AbsTime start(AbsTime::now()); + Duration delay(t->sortTime, start); + { + ScopedLock<Mutex> l(t->callbackLock); + if (t->cancelled) { + if (delay > 500 * TIME_MSEC) { + QPID_LOG(debug, "cancelled Timer woken up " << delay / TIME_MSEC << "ms late"); + } + continue; + } else if(Duration(t->nextFireTime, start) >= 0) { + Monitor::ScopedUnlock u(monitor); + t->fireTask(); + // Warn on callback overrun + AbsTime end(AbsTime::now()); + Duration overrun(tasks.top()->nextFireTime, end); + bool late = delay > 50 * TIME_MSEC; + bool overran = overrun > 2 * TIME_MSEC; + if (late) + if (overran) { + QPID_LOG(warning, + "Timer woken up " << delay / TIME_MSEC << "ms late, " + "overrunning by " << overrun / TIME_MSEC << "ms [taking " + << Duration(start, end) << "]"); + } else { + QPID_LOG(warning, "Timer woken up " << delay / TIME_MSEC << "ms late"); + } else if (overran) { + QPID_LOG(warning, + "Timer callback overran by " << overrun / TIME_MSEC << "ms [taking " + << Duration(start, end) << "]"); + } + continue; + } else { + // If the timer was adjusted into the future it might no longer + // be the next event, so push and then get top to make sure + // You can only push events into the future + t->sortTime = t->nextFireTime; + tasks.push(t); + } + } + monitor.wait(tasks.top()->sortTime); + } + } +} + +void Timer::add(intrusive_ptr<TimerTask> task) +{ + Monitor::ScopedLock l(monitor); + task->sortTime = task->nextFireTime; + tasks.push(task); + monitor.notify(); +} + +void Timer::start() +{ + Monitor::ScopedLock l(monitor); + if (!active) { + active = true; + runner = Thread(this); + } +} + +void Timer::stop() +{ + { + Monitor::ScopedLock l(monitor); + if (!active) return; + active = false; + monitor.notifyAll(); + } + runner.join(); +} + +bool operator<(const intrusive_ptr<TimerTask>& a, + const intrusive_ptr<TimerTask>& b) +{ + // Lower priority if time is later + return a.get() && b.get() && a->sortTime > b->sortTime; +} + +}} diff --git a/cpp/src/qpid/sys/Timer.h b/cpp/src/qpid/sys/Timer.h new file mode 100644 index 0000000000..5748503841 --- /dev/null +++ b/cpp/src/qpid/sys/Timer.h @@ -0,0 +1,94 @@ +/* + * + * 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 sys_Timer +#define sys_Timer + +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/RefCounted.h" +#include "qpid/CommonImportExport.h" +#include <memory> +#include <queue> + +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace sys { + +class Timer; + +class TimerTask : public RefCounted { + friend class Timer; + friend bool operator<(const boost::intrusive_ptr<TimerTask>&, + const boost::intrusive_ptr<TimerTask>&); + + AbsTime sortTime; + Duration period; + AbsTime nextFireTime; + Mutex callbackLock; + volatile bool cancelled; + + bool readyToFire() const; + void fireTask(); + +public: + QPID_COMMON_EXTERN TimerTask(Duration period); + QPID_COMMON_EXTERN TimerTask(AbsTime fireTime); + QPID_COMMON_EXTERN virtual ~TimerTask(); + + QPID_COMMON_EXTERN void setupNextFire(); + QPID_COMMON_EXTERN void restart(); + QPID_COMMON_EXTERN void cancel(); + +protected: + // Must be overridden with callback + virtual void fire() = 0; +}; + +// For the priority_queue order +bool operator<(const boost::intrusive_ptr<TimerTask>& a, + const boost::intrusive_ptr<TimerTask>& b); + +class Timer : private Runnable { + qpid::sys::Monitor monitor; + std::priority_queue<boost::intrusive_ptr<TimerTask> > tasks; + qpid::sys::Thread runner; + bool active; + + // Runnable interface + void run(); + +public: + QPID_COMMON_EXTERN Timer(); + QPID_COMMON_EXTERN ~Timer(); + + QPID_COMMON_EXTERN void add(boost::intrusive_ptr<TimerTask> task); + QPID_COMMON_EXTERN void start(); + QPID_COMMON_EXTERN void stop(); +}; + + +}} + + +#endif diff --git a/cpp/src/qpid/sys/alloca.h b/cpp/src/qpid/sys/alloca.h new file mode 100644 index 0000000000..e989670e4f --- /dev/null +++ b/cpp/src/qpid/sys/alloca.h @@ -0,0 +1,39 @@ +#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 + * 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. + * + */ + +#if (defined(_WINDOWS) || defined (WIN32)) && defined(_MSC_VER) +#include <malloc.h> +#ifdef alloc +# undef alloc +#endif +#define alloc _alloc +#ifdef alloca +# undef alloca +#endif +#define alloca _alloca +#endif +#if !defined _WINDOWS && !defined WIN32 +#include <alloca.h> +#endif + +#endif /*!QPID_SYS_ALLOCA_H*/ diff --git a/cpp/src/qpid/sys/apr/APRBase.cpp b/cpp/src/qpid/sys/apr/APRBase.cpp index 724c489303..8bdba66bdc 100644 --- a/cpp/src/qpid/sys/apr/APRBase.cpp +++ b/cpp/src/qpid/sys/apr/APRBase.cpp @@ -20,7 +20,7 @@ */ #include <iostream> #include "qpid/log/Statement.h" -#include "APRBase.h" +#include "qpid/sys/apr/APRBase.h" using namespace qpid::sys; diff --git a/cpp/src/qpid/sys/apr/APRPool.cpp b/cpp/src/qpid/sys/apr/APRPool.cpp index e8b71f6e8a..e221bfc2f1 100644 --- a/cpp/src/qpid/sys/apr/APRPool.cpp +++ b/cpp/src/qpid/sys/apr/APRPool.cpp @@ -19,8 +19,8 @@ * */ -#include "APRPool.h" -#include "APRBase.h" +#include "qpid/sys/apr/APRPool.h" +#include "qpid/sys/apr/APRBase.h" #include <boost/pool/detail/singleton.hpp> using namespace qpid::sys; diff --git a/cpp/src/qpid/sys/apr/Condition.h b/cpp/src/qpid/sys/apr/Condition.h index 5e544219ab..66d465ca75 100644 --- a/cpp/src/qpid/sys/apr/Condition.h +++ b/cpp/src/qpid/sys/apr/Condition.h @@ -22,7 +22,7 @@ * */ -#include "APRPool.h" +#include "qpid/sys/apr/APRPool.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/Time.h" diff --git a/cpp/src/qpid/sys/apr/Mutex.h b/cpp/src/qpid/sys/apr/Mutex.h index 51089c98ff..cb75f5b339 100644 --- a/cpp/src/qpid/sys/apr/Mutex.h +++ b/cpp/src/qpid/sys/apr/Mutex.h @@ -19,8 +19,8 @@ * */ -#include "APRBase.h" -#include "APRPool.h" +#include "qpid/sys/apr/APRBase.h" +#include "qpid/sys/apr/APRPool.h" #include <boost/noncopyable.hpp> #include <apr_thread_mutex.h> diff --git a/cpp/src/qpid/sys/apr/Shlib.cpp b/cpp/src/qpid/sys/apr/Shlib.cpp index b0ba706713..b7ee13a03b 100644 --- a/cpp/src/qpid/sys/apr/Shlib.cpp +++ b/cpp/src/qpid/sys/apr/Shlib.cpp @@ -19,8 +19,8 @@ */ #include "qpid/sys/Shlib.h" -#include "APRBase.h" -#include "APRPool.h" +#include "qpid/sys/apr/APRBase.h" +#include "qpid/sys/apr/APRPool.h" #include <apr_dso.h> namespace qpid { diff --git a/cpp/src/qpid/sys/apr/Socket.cpp b/cpp/src/qpid/sys/apr/Socket.cpp index 577268844a..d9024d11c1 100644 --- a/cpp/src/qpid/sys/apr/Socket.cpp +++ b/cpp/src/qpid/sys/apr/Socket.cpp @@ -22,8 +22,8 @@ #include "qpid/sys/Socket.h" -#include "APRBase.h" -#include "APRPool.h" +#include "qpid/sys/apr/APRBase.h" +#include "qpid/sys/apr/APRPool.h" #include <apr_network_io.h> diff --git a/cpp/src/qpid/sys/apr/Thread.cpp b/cpp/src/qpid/sys/apr/Thread.cpp index 3369ef7eb1..b52d0e6ace 100644 --- a/cpp/src/qpid/sys/apr/Thread.cpp +++ b/cpp/src/qpid/sys/apr/Thread.cpp @@ -19,7 +19,7 @@ * */ -#include "Thread.h" +#include "qpid/sys/apr/Thread.h" #include "qpid/sys/Runnable.h" using namespace qpid::sys; diff --git a/cpp/src/qpid/sys/apr/Thread.h b/cpp/src/qpid/sys/apr/Thread.h index 8cbbc0456e..6cc63db5c9 100644 --- a/cpp/src/qpid/sys/apr/Thread.h +++ b/cpp/src/qpid/sys/apr/Thread.h @@ -22,8 +22,8 @@ * */ -#include "APRPool.h" -#include "APRBase.h" +#include "qpid/sys/apr/APRPool.h" +#include "qpid/sys/apr/APRBase.h" #include <apr_thread_proc.h> #include <apr_portable.h> diff --git a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp new file mode 100644 index 0000000000..454ce62495 --- /dev/null +++ b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp @@ -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. + * + */ +#include "qpid/sys/cyrus/CyrusSecurityLayer.h" +#include <algorithm> +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include <string.h> + +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), + decodeBuffer(maxFrameSize), encodeBuffer(maxFrameSize), encoded(0) +{ + const void* value(0); + int result = sasl_getprop(conn, SASL_MAXOUTBUF, &value); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL encode error: " << sasl_errdetail(conn))); + } + maxInputSize = *(reinterpret_cast<const unsigned*>(value)); +} + +size_t CyrusSecurityLayer::decode(const char* input, size_t size) +{ + size_t inStart = 0; + do { + size_t inSize = std::min(size - inStart, maxInputSize); + int result = sasl_decode(conn, input + inStart, inSize, &decrypted, &decryptedSize); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL decode error: " << sasl_errdetail(conn))); + } + inStart += inSize; + size_t copied = 0; + do { + size_t count = std::min(decryptedSize - copied, decodeBuffer.size - decodeBuffer.position); + ::memcpy(decodeBuffer.data + decodeBuffer.position, decrypted + copied, count); + copied += count; + decodeBuffer.position += count; + size_t decodedSize = codec->decode(decodeBuffer.data, decodeBuffer.position); + if (decodedSize < decodeBuffer.position) { + ::memmove(decodeBuffer.data, decodeBuffer.data + decodedSize, decodeBuffer.position - decodedSize); + } + decodeBuffer.position -= decodedSize; + } while (copied < decryptedSize); + } while (inStart < size); + return size; +} + +size_t CyrusSecurityLayer::encode(const char* buffer, size_t size) +{ + size_t processed = 0;//records how many bytes have been written to buffer + do { + if (!encrypted) { + if (!encoded) { + encodeBuffer.position = 0; + encoded = codec->encode(encodeBuffer.data, encodeBuffer.size); + if (!encoded) break;//nothing more to do + } + + size_t encryptable = std::min(encoded, maxInputSize); + int result = sasl_encode(conn, encodeBuffer.data + encodeBuffer.position, encryptable, &encrypted, &encryptedSize); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL encode error: " << sasl_errdetail(conn))); + } + encodeBuffer.position += encryptable; + encoded -= encryptable; + } + size_t remaining = size - processed; + if (remaining < encryptedSize) { + //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); + processed += remaining; + encrypted += remaining; + encryptedSize -= remaining; + } else { + ::memcpy(const_cast<char*>(buffer + processed), encrypted, encryptedSize); + processed += encryptedSize; + encrypted = 0; + encryptedSize = 0; + } + } while (processed < size); + return processed; +} + +bool CyrusSecurityLayer::canEncode() +{ + return encrypted || codec->canEncode(); +} + +void CyrusSecurityLayer::init(qpid::sys::Codec* c) +{ + codec = c; +} + +CyrusSecurityLayer::DataBuffer::DataBuffer(size_t s) : position(0), size(s) +{ + data = new char[size]; +} + +CyrusSecurityLayer::DataBuffer::~DataBuffer() +{ + delete[] data; +} + +}}} // namespace qpid::sys::cyrus diff --git a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h new file mode 100644 index 0000000000..1645cf1a58 --- /dev/null +++ b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h @@ -0,0 +1,68 @@ +#ifndef QPID_SYS_CYRUS_CYRUSSECURITYLAYER_H +#define QPID_SYS_CYRUS_CYRUSSECURITYLAYER_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/sys/SecurityLayer.h" +#include <sasl/sasl.h> + +namespace qpid { +namespace sys { +namespace cyrus { + + +/** + * Implementation of SASL security layer using cyrus-sasl library + */ +class CyrusSecurityLayer : public qpid::sys::SecurityLayer +{ + public: + CyrusSecurityLayer(sasl_conn_t*, uint16_t maxFrameSize); + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + void init(qpid::sys::Codec*); + private: + struct DataBuffer + { + char* data; + size_t position; + const size_t size; + DataBuffer(size_t); + ~DataBuffer(); + }; + + sasl_conn_t* conn; + const char* decrypted; + unsigned decryptedSize; + const char* encrypted; + unsigned encryptedSize; + qpid::sys::Codec* codec; + size_t maxInputSize; + DataBuffer decodeBuffer; + DataBuffer encodeBuffer; + size_t encoded; +}; +}}} // namespace qpid::sys::cyrus + +#endif /*!QPID_SYS_CYRUS_CYRUSSECURITYLAYER_H*/ diff --git a/cpp/src/qpid/sys/epoll/EpollPoller.cpp b/cpp/src/qpid/sys/epoll/EpollPoller.cpp index 5e20e312e0..d7f64f3b4c 100644 --- a/cpp/src/qpid/sys/epoll/EpollPoller.cpp +++ b/cpp/src/qpid/sys/epoll/EpollPoller.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 @@ -25,18 +25,20 @@ #include "qpid/sys/DeletionManager.h" #include "qpid/sys/posix/check.h" #include "qpid/sys/posix/PrivatePosix.h" +#include "qpid/log/Statement.h" #include <sys/epoll.h> #include <errno.h> +#include <signal.h> #include <assert.h> -#include <vector> +#include <queue> #include <exception> namespace qpid { namespace sys { -// Deletion manager to handle deferring deletion of PollerHandles to when they definitely aren't being used +// Deletion manager to handle deferring deletion of PollerHandles to when they definitely aren't being used DeletionManager<PollerHandlePrivate> PollerHandleDeletionManager; // Instantiate (and define) class static for DeletionManager @@ -45,6 +47,7 @@ DeletionManager<PollerHandlePrivate>::AllThreadsStatuses DeletionManager<PollerH class PollerHandlePrivate { friend class Poller; + friend class PollerPrivate; friend class PollerHandle; enum FDStat { @@ -53,28 +56,36 @@ class PollerHandlePrivate { INACTIVE, HUNGUP, MONITORED_HUNGUP, + INTERRUPTED, + INTERRUPTED_HUNGUP, DELETED }; - int fd; ::__uint32_t events; + const IOHandlePrivate* ioHandle; PollerHandle* pollerHandle; FDStat stat; Mutex lock; - PollerHandlePrivate(int f, PollerHandle* p) : - fd(f), + PollerHandlePrivate(const IOHandlePrivate* h, PollerHandle* p) : events(0), + ioHandle(h), pollerHandle(p), stat(ABSENT) { } + int fd() const { + return toFd(ioHandle); + } + bool isActive() const { return stat == MONITORED || stat == MONITORED_HUNGUP; } void setActive() { - stat = (stat == HUNGUP) ? MONITORED_HUNGUP : MONITORED; + stat = (stat == HUNGUP || stat == INTERRUPTED_HUNGUP) + ? MONITORED_HUNGUP + : MONITORED; } bool isInactive() const { @@ -94,14 +105,27 @@ class PollerHandlePrivate { } bool isHungup() const { - return stat == MONITORED_HUNGUP || stat == HUNGUP; + return + stat == MONITORED_HUNGUP || + stat == HUNGUP || + stat == INTERRUPTED_HUNGUP; } void setHungup() { assert(stat == MONITORED); stat = HUNGUP; } - + + bool isInterrupted() const { + return stat == INTERRUPTED || stat == INTERRUPTED_HUNGUP; + } + + void setInterrupted() { + stat = (stat == MONITORED_HUNGUP || stat == HUNGUP) + ? INTERRUPTED_HUNGUP + : INTERRUPTED; + } + bool isDeleted() const { return stat == DELETED; } @@ -112,13 +136,22 @@ class PollerHandlePrivate { }; PollerHandle::PollerHandle(const IOHandle& h) : - impl(new PollerHandlePrivate(toFd(h.impl), this)) + impl(new PollerHandlePrivate(h.impl, this)) {} PollerHandle::~PollerHandle() { + { ScopedLock<Mutex> l(impl->lock); - if (impl->isActive()) { + if (impl->isDeleted()) { + return; + } + impl->pollerHandle = 0; + if (impl->isInterrupted()) { impl->setDeleted(); + return; + } + assert(impl->isIdle()); + impl->setDeleted(); } PollerHandleDeletionManager.markForDeletion(impl); } @@ -134,7 +167,7 @@ class PollerPrivate { struct ReadablePipe { int fds[2]; - + /** * This encapsulates an always readable pipe which we can add * to the epoll set to force epoll_wait to return @@ -144,31 +177,69 @@ class PollerPrivate { // Just write the pipe's fds to the pipe QPID_POSIX_CHECK(::write(fds[1], fds, 2)); } - + ~ReadablePipe() { ::close(fds[0]); ::close(fds[1]); } - + int getFD() { return fds[0]; } }; - + static ReadablePipe alwaysReadable; - + static int alwaysReadableFd; + + class InterruptHandle: public PollerHandle { + std::queue<PollerHandle*> handles; + + void processEvent(Poller::EventType) { + PollerHandle* handle = handles.front(); + handles.pop(); + assert(handle); + + // Synthesise event + Poller::Event event(handle, Poller::INTERRUPTED); + + // Process synthesised event + event.process(); + } + + public: + InterruptHandle() : + PollerHandle(DummyIOHandle) + {} + + void addHandle(PollerHandle& h) { + handles.push(&h); + } + + PollerHandle* getHandle() { + PollerHandle* handle = handles.front(); + handles.pop(); + return handle; + } + + bool queuedHandles() { + return handles.size() > 0; + } + }; + const int epollFd; bool isShutdown; + InterruptHandle interruptHandle; + ::sigset_t sigMask; static ::__uint32_t directionToEpollEvent(Poller::Direction dir) { switch (dir) { - case Poller::IN: return ::EPOLLIN; - case Poller::OUT: return ::EPOLLOUT; - case Poller::INOUT: return ::EPOLLIN | ::EPOLLOUT; + case Poller::INPUT: return ::EPOLLIN; + case Poller::OUTPUT: return ::EPOLLOUT; + case Poller::INOUT: return ::EPOLLIN | ::EPOLLOUT; default: return 0; } } - + static Poller::EventType epollToDirection(::__uint32_t events) { // POLLOUT & POLLHUP are mutually exclusive really, but at least socketpairs // can give you both! @@ -188,81 +259,161 @@ class PollerPrivate { epollFd(::epoll_create(DefaultFds)), isShutdown(false) { QPID_POSIX_CHECK(epollFd); + ::sigemptyset(&sigMask); + // Add always readable fd into our set (but not listening to it yet) + ::epoll_event epe; + epe.events = 0; + epe.data.u64 = 1; + QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_ADD, alwaysReadableFd, &epe)); } ~PollerPrivate() { // It's probably okay to ignore any errors here as there can't be data loss ::close(epollFd); + + // Need to put the interruptHandle in idle state to delete it + static_cast<PollerHandle&>(interruptHandle).impl->setIdle(); + } + + void resetMode(PollerHandlePrivate& handle); + + void interrupt() { + ::epoll_event epe; + // Use EPOLLONESHOT so we only wake a single thread + epe.events = ::EPOLLIN | ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy + epe.data.ptr = &static_cast<PollerHandle&>(interruptHandle); + QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_MOD, alwaysReadableFd, &epe)); + } + + void interruptAll() { + ::epoll_event epe; + // Not EPOLLONESHOT, so we eventually get all threads + epe.events = ::EPOLLIN; + epe.data.u64 = 2; // Keep valgrind happy + QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_MOD, alwaysReadableFd, &epe)); } }; PollerPrivate::ReadablePipe PollerPrivate::alwaysReadable; +int PollerPrivate::alwaysReadableFd = alwaysReadable.getFD(); -void Poller::addFd(PollerHandle& handle, Direction dir) { +void Poller::registerHandle(PollerHandle& handle) { PollerHandlePrivate& eh = *handle.impl; ScopedLock<Mutex> l(eh.lock); + assert(eh.isIdle()); + ::epoll_event epe; - int op; - - if (eh.isIdle()) { - op = EPOLL_CTL_ADD; - epe.events = PollerPrivate::directionToEpollEvent(dir) | ::EPOLLONESHOT; - } else { - assert(eh.isActive()); - op = EPOLL_CTL_MOD; - epe.events = eh.events | PollerPrivate::directionToEpollEvent(dir); - } + epe.events = ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy epe.data.ptr = &eh; - - QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, op, eh.fd, &epe)); - - // Record monitoring state of this fd - eh.events = epe.events; + + QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_ADD, eh.fd(), &epe)); + eh.setActive(); } -void Poller::delFd(PollerHandle& handle) { +void Poller::unregisterHandle(PollerHandle& handle) { PollerHandlePrivate& eh = *handle.impl; ScopedLock<Mutex> l(eh.lock); assert(!eh.isIdle()); - int rc = ::epoll_ctl(impl->epollFd, EPOLL_CTL_DEL, eh.fd, 0); + + int rc = ::epoll_ctl(impl->epollFd, EPOLL_CTL_DEL, eh.fd(), 0); // Ignore EBADF since deleting a nonexistent fd has the overall required result! // And allows the case where a sloppy program closes the fd and then does the delFd() if (rc == -1 && errno != EBADF) { - QPID_POSIX_CHECK(rc); + QPID_POSIX_CHECK(rc); } + eh.setIdle(); } -// modFd is equivalent to delFd followed by addFd -void Poller::modFd(PollerHandle& handle, Direction dir) { +void PollerPrivate::resetMode(PollerHandlePrivate& eh) { + PollerHandle* ph; + { + ScopedLock<Mutex> l(eh.lock); + assert(!eh.isActive()); + + if (eh.isIdle() || eh.isDeleted()) { + return; + } + + if (eh.events==0) { + eh.setActive(); + return; + } + + if (!eh.isInterrupted()) { + ::epoll_event epe; + epe.events = eh.events | ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy + epe.data.ptr = &eh; + + QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_MOD, eh.fd(), &epe)); + + eh.setActive(); + return; + } + ph = eh.pollerHandle; + } + + PollerHandlePrivate& ihp = *static_cast<PollerHandle&>(interruptHandle).impl; + ScopedLock<Mutex> l(ihp.lock); + interruptHandle.addHandle(*ph); + ihp.setActive(); + interrupt(); +} + +void Poller::monitorHandle(PollerHandle& handle, Direction dir) { PollerHandlePrivate& eh = *handle.impl; ScopedLock<Mutex> l(eh.lock); assert(!eh.isIdle()); - + + ::__uint32_t oldEvents = eh.events; + eh.events |= PollerPrivate::directionToEpollEvent(dir); + + // If no change nothing more to do - avoid unnecessary system call + if (oldEvents==eh.events) { + return; + } + + // If we're not actually listening wait till we are to perform change + if (!eh.isActive()) { + return; + } + ::epoll_event epe; - epe.events = PollerPrivate::directionToEpollEvent(dir) | ::EPOLLONESHOT; + epe.events = eh.events | ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy epe.data.ptr = &eh; - - QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd, &epe)); - - // Record monitoring state of this fd - eh.events = epe.events; - eh.setActive(); + + QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd(), &epe)); } -void Poller::rearmFd(PollerHandle& handle) { +void Poller::unmonitorHandle(PollerHandle& handle, Direction dir) { PollerHandlePrivate& eh = *handle.impl; ScopedLock<Mutex> l(eh.lock); - assert(eh.isInactive()); + assert(!eh.isIdle()); + + ::__uint32_t oldEvents = eh.events; + eh.events &= ~PollerPrivate::directionToEpollEvent(dir); + + // If no change nothing more to do - avoid unnecessary system call + if (oldEvents==eh.events) { + return; + } + + // If we're not actually listening wait till we are to perform change + if (!eh.isActive()) { + return; + } ::epoll_event epe; - epe.events = eh.events; + epe.events = eh.events | ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy epe.data.ptr = &eh; - QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd, &epe)); - - eh.setActive(); + QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd(), &epe)); } void Poller::shutdown() { @@ -273,63 +424,182 @@ void Poller::shutdown() { if (impl->isShutdown) return; - // Don't use any locking here - isshutdown will be visible to all + // Don't use any locking here - isShutdown will be visible to all // after the epoll_ctl() anyway (it's a memory barrier) impl->isShutdown = true; - - // Add always readable fd to epoll (not EPOLLONESHOT) - int fd = impl->alwaysReadable.getFD(); - ::epoll_event epe; - epe.events = ::EPOLLIN; - epe.data.ptr = 0; - QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_ADD, fd, &epe)); + + impl->interruptAll(); +} + +bool Poller::interrupt(PollerHandle& handle) { + { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + if (eh.isIdle() || eh.isDeleted()) { + return false; + } + + if (eh.isInterrupted()) { + return true; + } + + // Stop monitoring handle for read or write + ::epoll_event epe; + epe.events = 0; + epe.data.u64 = 0; // Keep valgrind happy + epe.data.ptr = &eh; + QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd(), &epe)); + + if (eh.isInactive()) { + eh.setInterrupted(); + return true; + } + eh.setInterrupted(); + } + + PollerPrivate::InterruptHandle& ih = impl->interruptHandle; + PollerHandlePrivate& eh = *static_cast<PollerHandle&>(ih).impl; + ScopedLock<Mutex> l(eh.lock); + ih.addHandle(handle); + + impl->interrupt(); + eh.setActive(); + return true; +} + +void Poller::run() { + // Ensure that we exit thread responsibly under all circumstances + try { + // Make sure we can't be interrupted by signals at a bad time + ::sigset_t ss; + ::sigfillset(&ss); + ::pthread_sigmask(SIG_SETMASK, &ss, 0); + + do { + Event event = wait(); + + // If can read/write then dispatch appropriate callbacks + if (event.handle) { + event.process(); + } else { + // Handle shutdown + switch (event.type) { + case SHUTDOWN: + PollerHandleDeletionManager.destroyThreadState(); + return; + default: + // This should be impossible + assert(false); + } + } + } while (true); + } catch (const std::exception& e) { + QPID_LOG(error, "IO worker thread exiting with unhandled exception: " << e.what()); + } + PollerHandleDeletionManager.destroyThreadState(); } Poller::Event Poller::wait(Duration timeout) { + static __thread PollerHandlePrivate* lastReturnedHandle = 0; epoll_event epe; int timeoutMs = (timeout == TIME_INFINITE) ? -1 : timeout / TIME_MSEC; + AbsTime targetTimeout = + (timeout == TIME_INFINITE) ? + FAR_FUTURE : + AbsTime(now(), timeout); + + if (lastReturnedHandle) { + impl->resetMode(*lastReturnedHandle); + lastReturnedHandle = 0; + } - // Repeat until we weren't interupted + // Repeat until we weren't interrupted by signal do { PollerHandleDeletionManager.markAllUnusedInThisThread(); + // Need to run on kernels without epoll_pwait() + // - fortunately in this case we don't really need the atomicity of epoll_pwait() +#if 1 + sigset_t os; + pthread_sigmask(SIG_SETMASK, &impl->sigMask, &os); int rc = ::epoll_wait(impl->epollFd, &epe, 1, timeoutMs); - - if (impl->isShutdown) { - PollerHandleDeletionManager.markAllUnusedInThisThread(); - return Event(0, SHUTDOWN); - } - + pthread_sigmask(SIG_SETMASK, &os, 0); +#else + int rc = ::epoll_pwait(impl->epollFd, &epe, 1, timeoutMs, &impl->sigMask); +#endif + if (rc ==-1 && errno != EINTR) { QPID_POSIX_CHECK(rc); } else if (rc > 0) { assert(rc == 1); - PollerHandlePrivate& eh = *static_cast<PollerHandlePrivate*>(epe.data.ptr); - + void* dataPtr = epe.data.ptr; + + // Check if this is an interrupt + PollerPrivate::InterruptHandle& interruptHandle = impl->interruptHandle; + if (dataPtr == &interruptHandle) { + PollerHandle* wrappedHandle = 0; + { + ScopedLock<Mutex> l(interruptHandle.impl->lock); + if (interruptHandle.impl->isActive()) { + wrappedHandle = interruptHandle.getHandle(); + // If there is an interrupt queued behind this one we need to arm it + // We do it this way so that another thread can pick it up + if (interruptHandle.queuedHandles()) { + impl->interrupt(); + interruptHandle.impl->setActive(); + } else { + interruptHandle.impl->setInactive(); + } + } + } + if (wrappedHandle) { + PollerHandlePrivate& eh = *wrappedHandle->impl; + { + ScopedLock<Mutex> l(eh.lock); + if (!eh.isDeleted()) { + if (!eh.isIdle()) { + eh.setInactive(); + } + lastReturnedHandle = &eh; + assert(eh.pollerHandle == wrappedHandle); + return Event(wrappedHandle, INTERRUPTED); + } + } + PollerHandleDeletionManager.markForDeletion(&eh); + } + continue; + } + + // Check for shutdown + if (impl->isShutdown) { + PollerHandleDeletionManager.markAllUnusedInThisThread(); + return Event(0, SHUTDOWN); + } + + PollerHandlePrivate& eh = *static_cast<PollerHandlePrivate*>(dataPtr); ScopedLock<Mutex> l(eh.lock); - + // the handle could have gone inactive since we left the epoll_wait if (eh.isActive()) { PollerHandle* handle = eh.pollerHandle; + assert(handle); // If the connection has been hungup we could still be readable // (just not writable), allow us to readable until we get here again if (epe.events & ::EPOLLHUP) { if (eh.isHungup()) { + eh.setInactive(); + // Don't set up last Handle so that we don't reset this handle + // on re-entering Poller::wait. This means that we will never + // be set active again once we've returned disconnected, and so + // can never be returned again. return Event(handle, DISCONNECTED); } eh.setHungup(); } else { eh.setInactive(); } + lastReturnedHandle = &eh; return Event(handle, PollerPrivate::epollToDirection(epe.events)); - } else if (eh.isDeleted()) { - // The handle has been deleted whilst still active and so must be removed - // from the poller - int rc = ::epoll_ctl(impl->epollFd, EPOLL_CTL_DEL, eh.fd, 0); - // Ignore EBADF since it's quite likely that we could race with closing the fd - if (rc == -1 && errno != EBADF) { - QPID_POSIX_CHECK(rc); - } } } // We only get here if one of the following: @@ -340,10 +610,12 @@ Poller::Event Poller::wait(Duration timeout) { // The only things we can do here are return a timeout or wait more. // Obviously if we timed out we return timeout; if the wait was meant to // be indefinite then we should never return with a time out so we go again. - // If the wait wasn't indefinite, but we were interrupted then we have to return - // with a timeout as we don't know how long we've waited so far and so we can't - // continue the wait. - if (rc == 0 || timeoutMs != -1) { + // If the wait wasn't indefinite, we check whether we are after the target wait + // time or not + if (timeoutMs == -1) { + continue; + } + if (rc == 0 && now() > targetTimeout) { PollerHandleDeletionManager.markAllUnusedInThisThread(); return Event(0, TIMEOUT); } diff --git a/cpp/src/qpid/sys/posix/AsynchIO.cpp b/cpp/src/qpid/sys/posix/AsynchIO.cpp index 7598eefe83..67b5cf0534 100644 --- a/cpp/src/qpid/sys/posix/AsynchIO.cpp +++ b/cpp/src/qpid/sys/posix/AsynchIO.cpp @@ -21,13 +21,16 @@ #include "qpid/sys/AsynchIO.h" #include "qpid/sys/Socket.h" +#include "qpid/sys/SocketAddress.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/DispatchHandle.h" #include "qpid/sys/Time.h" #include "qpid/log/Statement.h" -#include "check.h" +#include "qpid/sys/posix/check.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 +// 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> @@ -35,18 +38,21 @@ #include <string.h> #include <boost/bind.hpp> +#include <boost/lexical_cast.hpp> using namespace qpid::sys; 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); -} +struct StaticInit { + StaticInit() { + /** + * Make *process* not generate SIGPIPE when writing to closed + * pipe/socket (necessary as default action is to terminate process) + */ + ::signal(SIGPIPE, SIG_IGN); + }; +} init; /* * We keep per thread state to avoid locking overhead. The assumption is that @@ -65,14 +71,37 @@ __thread int64_t threadMaxReadTimeNs = 2 * 1000000; // start at 2ms /* * Asynch Acceptor */ +namespace qpid { +namespace sys { +namespace posix { + +class AsynchAcceptor : public qpid::sys::AsynchAcceptor { +public: + AsynchAcceptor(const Socket& s, AsynchAcceptor::Callback callback); + ~AsynchAcceptor(); + void start(Poller::shared_ptr poller); + +private: + void readable(DispatchHandle& handle); + +private: + AsynchAcceptor::Callback acceptedCallback; + DispatchHandle handle; + const Socket& socket; -AsynchAcceptor::AsynchAcceptor(const Socket& s, Callback callback) : +}; + +AsynchAcceptor::AsynchAcceptor(const Socket& s, + AsynchAcceptor::Callback callback) : acceptedCallback(callback), handle(s, boost::bind(&AsynchAcceptor::readable, this, _1), 0, 0), socket(s) { s.setNonblocking(); - ignoreSigpipe(); +} + +AsynchAcceptor::~AsynchAcceptor() { + handle.stopWatch(); } void AsynchAcceptor::start(Poller::shared_ptr poller) { @@ -89,7 +118,7 @@ void AsynchAcceptor::readable(DispatchHandle& h) { // TODO: Currently we ignore the peers address, perhaps we should // log it or use it for connection acceptance. try { - s = socket.accept(0, 0); + s = socket.accept(); if (s) { acceptedCallback(*s); } else { @@ -97,6 +126,7 @@ void AsynchAcceptor::readable(DispatchHandle& h) { } } catch (const std::exception& e) { QPID_LOG(error, "Could not accept socket: " << e.what()); + break; } } while (true); @@ -104,8 +134,32 @@ void AsynchAcceptor::readable(DispatchHandle& h) { } /* - * Asynch Connector + * POSIX version of AsynchIO TCP socket connector. + * + * The class is implemented in terms of DispatchHandle to allow it to be + * deleted by deleting the contained DispatchHandle. */ +class AsynchConnector : public qpid::sys::AsynchConnector, + private DispatchHandle { + +private: + void connComplete(DispatchHandle& handle); + void failure(int, const std::string&); + +private: + ConnectedCallback connCallback; + FailedCallback failCallback; + std::string errMsg; + const Socket& socket; + +public: + AsynchConnector(const Socket& socket, + Poller::shared_ptr poller, + std::string hostname, + uint16_t port, + ConnectedCallback connCb, + FailedCallback failCb); +}; AsynchConnector::AsynchConnector(const Socket& s, Poller::shared_ptr poller, @@ -122,12 +176,17 @@ AsynchConnector::AsynchConnector(const Socket& s, socket(s) { socket.setNonblocking(); + SocketAddress sa(hostname, boost::lexical_cast<std::string>(port)); try { - socket.connect(hostname, port); - startWatch(poller); + socket.connect(sa); } catch(std::exception& e) { - failure(-1, std::string(e.what())); + // Defer reporting failure + startWatch(poller); + errMsg = e.what(); + DispatchHandle::call(boost::bind(&AsynchConnector::failure, this, -1, errMsg)); + return; } + startWatch(poller); } void AsynchConnector::connComplete(DispatchHandle& h) @@ -139,25 +198,87 @@ void AsynchConnector::connComplete(DispatchHandle& h) connCallback(socket); DispatchHandle::doDelete(); } else { - // TODO: This need to be fixed as strerror isn't thread safe - failure(errCode, std::string(::strerror(errCode))); + failure(errCode, strError(errCode)); } } -void AsynchConnector::failure(int errCode, std::string message) +void AsynchConnector::failure(int errCode, const std::string& message) { - if (failCallback) - failCallback(errCode, message); - - socket.close(); - delete &socket; + failCallback(socket, errCode, message); DispatchHandle::doDelete(); } /* - * Asynch reader/writer + * POSIX version of AsynchIO reader/writer + * + * The class is implemented in terms of DispatchHandle to allow it to be + * deleted by deleting the contained DispatchHandle. */ +class AsynchIO : public qpid::sys::AsynchIO, private DispatchHandle { + +public: + AsynchIO(const Socket& s, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb = 0, + BuffersEmptyCallback eCb = 0, + IdleCallback iCb = 0); + + // Methods inherited from qpid::sys::AsynchIO + + virtual void queueForDeletion(); + + virtual void start(Poller::shared_ptr poller); + virtual void queueReadBuffer(BufferBase* buff); + virtual void unread(BufferBase* buff); + virtual void queueWrite(BufferBase* buff); + virtual void notifyPendingWrite(); + virtual void queueWriteClose(); + virtual bool writeQueueEmpty(); + virtual void startReading(); + virtual void stopReading(); + virtual void requestCallback(RequestCallback); + virtual BufferBase* getQueuedBuffer(); + +private: + ~AsynchIO(); + + // Methods that are callback targets from Dispatcher. + void readable(DispatchHandle& handle); + void writeable(DispatchHandle& handle); + void disconnected(DispatchHandle& handle); + void requestedCall(RequestCallback); + void close(DispatchHandle& handle); + +private: + ReadCallback readCallback; + EofCallback eofCallback; + DisconnectCallback disCallback; + ClosedCallback closedCallback; + BuffersEmptyCallback emptyCallback; + IdleCallback idleCallback; + const Socket& socket; + std::deque<BufferBase*> bufferQueue; + std::deque<BufferBase*> writeQueue; + 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; + /** + * 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, ReadCallback rCb, EofCallback eofCb, DisconnectCallback disCb, ClosedCallback cCb, BuffersEmptyCallback eCb, IdleCallback iCb) : @@ -174,7 +295,8 @@ AsynchIO::AsynchIO(const Socket& s, idleCallback(iCb), socket(s), queuedClose(false), - writePending(false) { + writePending(false), + readingStopped(false) { s.setNonblocking(); } @@ -202,8 +324,11 @@ void AsynchIO::queueReadBuffer(BufferBase* buff) { assert(buff); buff->dataStart = 0; buff->dataCount = 0; + + bool queueWasEmpty = bufferQueue.empty(); bufferQueue.push_back(buff); - DispatchHandle::rewatchRead(); + if (queueWasEmpty && !readingStopped) + DispatchHandle::rewatchRead(); } void AsynchIO::unread(BufferBase* buff) { @@ -212,15 +337,18 @@ void AsynchIO::unread(BufferBase* buff) { memmove(buff->bytes, buff->bytes+buff->dataStart, buff->dataCount); buff->dataStart = 0; } + + bool queueWasEmpty = bufferQueue.empty(); bufferQueue.push_front(buff); - DispatchHandle::rewatchRead(); + if (queueWasEmpty && !readingStopped) + DispatchHandle::rewatchRead(); } void AsynchIO::queueWrite(BufferBase* buff) { assert(buff); // If we've already closed the socket then throw the write away if (queuedClose) { - bufferQueue.push_front(buff); + queueReadBuffer(buff); return; } else { writeQueue.push_front(buff); @@ -229,6 +357,7 @@ void AsynchIO::queueWrite(BufferBase* buff) { DispatchHandle::rewatchWrite(); } +// This can happen outside the callback context void AsynchIO::notifyPendingWrite() { writePending = true; DispatchHandle::rewatchWrite(); @@ -239,6 +368,33 @@ void AsynchIO::queueWriteClose() { DispatchHandle::rewatchWrite(); } +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?) + assert(callback); + DispatchHandle::call(boost::bind(&AsynchIO::requestedCall, this, callback)); +} + +void AsynchIO::requestedCall(RequestCallback callback) { + assert(callback); + callback(*this); +} + /** Return a queued buffer if there are enough * to spare */ @@ -277,6 +433,11 @@ void AsynchIO::readable(DispatchHandle& h) { readTotal += rc; readCallback(*this, buff); + if (readingStopped) { + // We have been flow controlled. + break; + } + if (rc != readCount) { // If we didn't fill the read buffer then time to stop reading break; @@ -303,7 +464,7 @@ void AsynchIO::readable(DispatchHandle& h) { break; } else { // Report error then just treat as a socket disconnect - QPID_LOG(error, "Error reading socket: " << qpid::sys::strError(rc) << "(" << rc << ")" ); + QPID_LOG(error, "Error reading socket: " << qpid::sys::strError(errno) << "(" << errno << ")" ); eofCallback(*this); h.unwatchRead(); break; @@ -427,3 +588,33 @@ void AsynchIO::close(DispatchHandle& h) { } } +} // namespace posix + +AsynchAcceptor* AsynchAcceptor::create(const Socket& s, + Callback callback) +{ + return new posix::AsynchAcceptor(s, callback); +} + +AsynchConnector* AsynchConnector::create(const Socket& s, + Poller::shared_ptr poller, + std::string hostname, + uint16_t port, + ConnectedCallback connCb, + FailedCallback failCb) +{ + return new posix::AsynchConnector(s, poller, hostname, port, connCb, failCb); +} + +AsynchIO* AsynchIO::create(const Socket& s, + AsynchIO::ReadCallback rCb, + AsynchIO::EofCallback eofCb, + AsynchIO::DisconnectCallback disCb, + AsynchIO::ClosedCallback cCb, + AsynchIO::BuffersEmptyCallback eCb, + AsynchIO::IdleCallback iCb) +{ + return new posix::AsynchIO(s, rCb, eofCb, disCb, cCb, eCb, iCb); +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/posix/Condition.h b/cpp/src/qpid/sys/posix/Condition.h deleted file mode 100644 index 86d6500ee9..0000000000 --- a/cpp/src/qpid/sys/posix/Condition.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef _sys_posix_Condition_h -#define _sys_posix_Condition_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 "PrivatePosix.h" - -#include "qpid/sys/Mutex.h" -#include "qpid/sys/Time.h" - -#include <time.h> -#include <sys/errno.h> -#include <boost/noncopyable.hpp> - -namespace qpid { -namespace sys { - -/** - * A condition variable for thread synchronization. - */ -class Condition -{ - public: - inline Condition(); - inline ~Condition(); - inline void wait(Mutex&); - inline bool wait(Mutex&, const AbsTime& absoluteTime); - inline void notify(); - inline void notifyAll(); - - private: - pthread_cond_t condition; -}; - -Condition::Condition() { - QPID_POSIX_ASSERT_THROW_IF(pthread_cond_init(&condition, 0)); -} - -Condition::~Condition() { - QPID_POSIX_ASSERT_THROW_IF(pthread_cond_destroy(&condition)); -} - -void Condition::wait(Mutex& mutex) { - QPID_POSIX_ASSERT_THROW_IF(pthread_cond_wait(&condition, &mutex.mutex)); -} - -bool Condition::wait(Mutex& mutex, const AbsTime& absoluteTime){ - struct timespec ts; - toTimespec(ts, Duration(absoluteTime)); - int status = pthread_cond_timedwait(&condition, &mutex.mutex, &ts); - if (status != 0) { - if (status == ETIMEDOUT) return false; - throw QPID_POSIX_ERROR(status); - } - return true; -} - -void Condition::notify(){ - QPID_POSIX_ASSERT_THROW_IF(pthread_cond_signal(&condition)); -} - -void Condition::notifyAll(){ - QPID_POSIX_ASSERT_THROW_IF(pthread_cond_broadcast(&condition)); -} - -}} -#endif /*!_sys_posix_Condition_h*/ diff --git a/cpp/src/qpid/sys/posix/FileSysDir.cpp b/cpp/src/qpid/sys/posix/FileSysDir.cpp new file mode 100755 index 0000000000..22dc487e74 --- /dev/null +++ b/cpp/src/qpid/sys/posix/FileSysDir.cpp @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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/FileSysDir.h" +#include "qpid/sys/StrError.h" +#include "qpid/Exception.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <cerrno> +#include <unistd.h> + +namespace qpid { +namespace sys { + +bool FileSysDir::exists (void) const +{ + const char *cpath = dirPath.c_str (); + struct stat s; + if (::stat(cpath, &s)) { + if (errno == ENOENT) { + return false; + } + throw qpid::Exception (strError(errno) + + ": Can't check directory: " + dirPath); + } + if (S_ISDIR(s.st_mode)) + return true; + throw qpid::Exception(dirPath + " is not a directory"); +} + +void FileSysDir::mkdir(void) +{ + if (::mkdir(dirPath.c_str(), 0755)) + throw Exception ("Can't create directory: " + dirPath); +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/posix/Fork.cpp b/cpp/src/qpid/sys/posix/Fork.cpp index ec3af620ef..a0d404a16e 100644 --- a/cpp/src/qpid/sys/posix/Fork.cpp +++ b/cpp/src/qpid/sys/posix/Fork.cpp @@ -22,7 +22,9 @@ #include <errno.h> #include <fcntl.h> #include <signal.h> +#include <string.h> #include <sys/stat.h> +#include <sys/select.h> #include <sys/types.h> #include <unistd.h> diff --git a/cpp/src/qpid/sys/posix/IOHandle.cpp b/cpp/src/qpid/sys/posix/IOHandle.cpp index 80b487eadc..9c049ee1de 100644 --- a/cpp/src/qpid/sys/posix/IOHandle.cpp +++ b/cpp/src/qpid/sys/posix/IOHandle.cpp @@ -21,7 +21,7 @@ #include "qpid/sys/IOHandle.h" -#include "PrivatePosix.h" +#include "qpid/sys/posix/PrivatePosix.h" namespace qpid { namespace sys { @@ -31,6 +31,8 @@ int toFd(const IOHandlePrivate* h) return h->fd; } +NullIOHandle DummyIOHandle; + IOHandle::IOHandle(IOHandlePrivate* h) : impl(h) {} diff --git a/cpp/src/qpid/sys/posix/LockFile.cpp b/cpp/src/qpid/sys/posix/LockFile.cpp new file mode 100755 index 0000000000..1862ff6ac9 --- /dev/null +++ b/cpp/src/qpid/sys/posix/LockFile.cpp @@ -0,0 +1,108 @@ +/* + * + * Copyright (c) 2008 The Apache Software Foundation + * + * Licensed 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/LockFile.h" +#include "qpid/sys/posix/PidFile.h" + +#include <string> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "qpid/sys/posix/check.h" + +namespace qpid { +namespace sys { + +class LockFilePrivate { + friend class LockFile; + friend class PidFile; + + int fd; + +public: + LockFilePrivate(int f) : fd(f) {} +}; + +LockFile::LockFile(const std::string& path_, bool create) + : path(path_), created(create) { + + errno = 0; + int flags=create ? O_WRONLY|O_CREAT|O_NOFOLLOW : O_RDWR; + int fd = ::open(path.c_str(), flags, 0644); + if (fd < 0) throw ErrnoException("Cannot open " + path, errno); + if (::lockf(fd, F_TLOCK, 0) < 0) { + ::close(fd); + throw ErrnoException("Cannot lock " + path, errno); + } + impl.reset(new LockFilePrivate(fd)); +} + +LockFile::~LockFile() { + if (impl) { + int f = impl->fd; + if (f >= 0) { + int unused_ret; + unused_ret = ::lockf(f, F_ULOCK, 0); // Suppress warnings about ignoring return value. + ::close(f); + impl->fd = -1; + } + } +} + +int LockFile::read(void* bytes, size_t len) const { + if (!impl) + throw Exception("Lock file not open: " + path); + + ssize_t rc = ::read(impl->fd, bytes, len); + if ((ssize_t)len > rc) { + throw Exception("Cannot read lock file: " + path); + } + return rc; +} + +int LockFile::write(void* bytes, size_t len) const { + if (!impl) + throw Exception("Lock file not open: " + path); + + ssize_t rc = ::write(impl->fd, bytes, len); + if ((ssize_t)len > rc) { + throw Exception("Cannot write lock file: " + path); + } + return rc; +} + +PidFile::PidFile(const std::string& path_, bool create): + LockFile(path_, create) +{} + +pid_t PidFile::readPid(void) const { + pid_t pid; + int desired_read = sizeof(pid_t); + read(&pid, desired_read); + return pid; +} + +void PidFile::writePid(void) { + pid_t pid = getpid(); + int desired_write = sizeof(pid_t); + write(&pid, desired_write); +} + +}} /* namespace qpid::sys */ diff --git a/cpp/src/qpid/sys/posix/LockFile.h b/cpp/src/qpid/sys/posix/LockFile.h deleted file mode 100644 index 027735e759..0000000000 --- a/cpp/src/qpid/sys/posix/LockFile.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef _sys_posix_LockFile_h -#define _sys_posix_LockFile_h - -#include "check.h" - -#include <boost/noncopyable.hpp> -#include <string> -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> - -/* - * - * Copyright (c) 2008 The Apache Software Foundation - * - * Licensed 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 LockFile : private boost::noncopyable { -public: - LockFile(const std::string& path_, bool create): - path(path_), fd(-1), created(create) { - errno = 0; - int flags=create ? O_WRONLY|O_CREAT|O_NOFOLLOW : O_RDWR; - fd = ::open(path.c_str(), flags, 0644); - if (fd < 0) throw ErrnoException("Cannot open " + path, errno); - if (::lockf(fd, F_TLOCK, 0) < 0) throw ErrnoException("Cannot lock " + path, errno); - } - - ~LockFile() { - if (fd >= 0) { - (void) ::lockf(fd, F_ULOCK, 0); // Suppress warnings about ignoring return value. - ::close(fd); - } - } - - std::string path; - int fd; - bool created; -}; - -} -} -#endif /*!_sys_posix_LockFile_h*/ diff --git a/cpp/src/qpid/sys/posix/Mutex.h b/cpp/src/qpid/sys/posix/Mutex.h deleted file mode 100644 index cd5a8affd4..0000000000 --- a/cpp/src/qpid/sys/posix/Mutex.h +++ /dev/null @@ -1,158 +0,0 @@ -#ifndef _sys_posix_Mutex_h -#define _sys_posix_Mutex_h - -/* - * - * Copyright (c) 2006 The Apache Software Foundation - * - * Licensed 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 "check.h" - -#include <pthread.h> -#include <boost/noncopyable.hpp> - -namespace qpid { -namespace sys { - -class Condition; - -/** - * Mutex lock. - */ -class Mutex : private boost::noncopyable { - friend class Condition; - static const pthread_mutexattr_t* getAttribute(); - -public: - typedef ::qpid::sys::ScopedLock<Mutex> ScopedLock; - typedef ::qpid::sys::ScopedUnlock<Mutex> ScopedUnlock; - - inline Mutex(); - inline ~Mutex(); - inline void lock(); - inline void unlock(); - inline bool trylock(); - - -protected: - pthread_mutex_t mutex; -}; - -/** - * RW lock. - */ -class RWlock : private boost::noncopyable { - friend class Condition; - -public: - typedef ::qpid::sys::ScopedRlock<RWlock> ScopedRlock; - typedef ::qpid::sys::ScopedWlock<RWlock> ScopedWlock; - - inline RWlock(); - inline ~RWlock(); - inline void wlock(); // will write-lock - inline void rlock(); // will read-lock - inline void unlock(); - inline void trywlock(); // will write-try - inline void tryrlock(); // will read-try - -protected: - pthread_rwlock_t rwlock; -}; - - -/** - * PODMutex is a POD, can be static-initialized with - * PODMutex m = QPID_PODMUTEX_INITIALIZER - */ -struct PODMutex -{ - typedef ::qpid::sys::ScopedLock<PODMutex> ScopedLock; - - inline void lock(); - inline void unlock(); - inline bool trylock(); - - // Must be public to be a POD: - pthread_mutex_t mutex; -}; - -#define QPID_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER } - -void PODMutex::lock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_lock(&mutex)); -} - -void PODMutex::unlock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_unlock(&mutex)); -} - -bool PODMutex::trylock() { - return pthread_mutex_trylock(&mutex) == 0; -} - -Mutex::Mutex() { - QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_init(&mutex, getAttribute())); -} - -Mutex::~Mutex(){ - QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_destroy(&mutex)); -} - -void Mutex::lock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_lock(&mutex)); -} - -void Mutex::unlock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_unlock(&mutex)); -} - -bool Mutex::trylock() { - return pthread_mutex_trylock(&mutex) == 0; -} - - -RWlock::RWlock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_init(&rwlock, NULL)); -} - -RWlock::~RWlock(){ - QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_destroy(&rwlock)); -} - -void RWlock::wlock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_wrlock(&rwlock)); -} - -void RWlock::rlock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_rdlock(&rwlock)); -} - -void RWlock::unlock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_unlock(&rwlock)); -} - -void RWlock::trywlock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_trywrlock(&rwlock)); -} - -void RWlock::tryrlock() { - QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_tryrdlock(&rwlock)); -} - - -}} -#endif /*!_sys_posix_Mutex_h*/ diff --git a/cpp/src/qpid/sys/posix/PidFile.h b/cpp/src/qpid/sys/posix/PidFile.h new file mode 100644 index 0000000000..fb19d407f4 --- /dev/null +++ b/cpp/src/qpid/sys/posix/PidFile.h @@ -0,0 +1,62 @@ +#ifndef _sys_PidFile_h +#define _sys_PidFile_h + +/* + * + * Copyright (c) 2008 The Apache Software Foundation + * + * Licensed 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/LockFile.h" + +#include "qpid/CommonImportExport.h" +#include "qpid/sys/IntegerTypes.h" + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <string> + +namespace qpid { +namespace sys { + +class PidFile : public LockFile +{ +public: + QPID_COMMON_EXTERN PidFile(const std::string& path_, bool create); + + /** + * Read the process ID from the lock file. This method assumes that + * if there is a process ID in the file, it was written there by + * writePid(); thus, it's at the start of the file. + * + * Throws an exception if there is an error reading the file. + * + * @returns The stored process ID. No validity check is done on it. + */ + QPID_COMMON_EXTERN pid_t readPid(void) const; + + /** + * Write the current process's ID to the lock file. It's written at + * the start of the file and will overwrite any other content that + * may be in the file. + * + * Throws an exception if the write fails. + */ + QPID_COMMON_EXTERN void writePid(void); +}; + +}} /* namespace qpid::sys */ + +#endif /*!_sys_PidFile_h*/ diff --git a/cpp/src/qpid/sys/posix/PipeHandle.cpp b/cpp/src/qpid/sys/posix/PipeHandle.cpp new file mode 100755 index 0000000000..4b19783338 --- /dev/null +++ b/cpp/src/qpid/sys/posix/PipeHandle.cpp @@ -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. +// + +#include "qpid/sys/PipeHandle.h" +#include "qpid/sys/posix/check.h" +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> + +namespace qpid { +namespace sys { + +PipeHandle::PipeHandle(bool nonBlocking) { + + int pair[2]; + pair[0] = pair[1] = -1; + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, pair) == -1) + throw qpid::Exception(QPID_MSG("Creation of pipe failed")); + + writeFd = pair[0]; + readFd = pair[1]; + + // Set the socket to non-blocking + if (nonBlocking) { + int flags = fcntl(readFd, F_GETFL); + fcntl(readFd, F_SETFL, flags | O_NONBLOCK); + } +} + +PipeHandle::~PipeHandle() { + close(readFd); + close(writeFd); +} + +int PipeHandle::read(void* buf, size_t bufSize) { + return ::read(readFd,buf,bufSize); +} + +int PipeHandle::write(const void* buf, size_t bufSize) { + return ::write(writeFd,buf,bufSize); +} + +int PipeHandle::getReadHandle() { + return readFd; +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/posix/PollableCondition.cpp b/cpp/src/qpid/sys/posix/PollableCondition.cpp new file mode 100644 index 0000000000..b22a615a54 --- /dev/null +++ b/cpp/src/qpid/sys/posix/PollableCondition.cpp @@ -0,0 +1,124 @@ +#ifndef QPID_SYS_LINUX_POLLABLECONDITION_CPP +#define QPID_SYS_LINUX_POLLABLECONDITION_CPP + +/* + * + * 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/PollableCondition.h" +#include "qpid/sys/DispatchHandle.h" +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/posix/PrivatePosix.h" +#include "qpid/Exception.h" + +#include <boost/bind.hpp> + +#include <unistd.h> +#include <fcntl.h> + +namespace qpid { +namespace sys { + +class PollableConditionPrivate : public sys::IOHandle { + friend class PollableCondition; + +private: + PollableConditionPrivate(const sys::PollableCondition::Callback& cb, + sys::PollableCondition& parent, + const boost::shared_ptr<sys::Poller>& poller); + ~PollableConditionPrivate(); + + void dispatch(sys::DispatchHandle& h); + void set(); + void clear(); + +private: + PollableCondition::Callback cb; + PollableCondition& parent; + boost::shared_ptr<sys::Poller> poller; + int writeFd; + std::auto_ptr<DispatchHandleRef> handle; +}; + +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) +{ + int fds[2]; + if (::pipe(fds) == -1) + throw ErrnoException(QPID_MSG("Can't create PollableCondition")); + impl->fd = fds[0]; + writeFd = fds[1]; + if (::fcntl(impl->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")); + handle.reset (new DispatchHandleRef( + *this, + boost::bind(&sys::PollableConditionPrivate::dispatch, this, _1), + 0, 0)); + handle->startWatch(poller); + handle->unwatch(); + + // Make the read FD readable + static const char dummy=0; + ssize_t n = ::write(writeFd, &dummy, 1); + if (n == -1 && errno != EAGAIN) + throw ErrnoException("Error setting PollableCondition"); +} + +PollableConditionPrivate::~PollableConditionPrivate() { + handle->stopWatch(); + close(writeFd); +} + +void PollableConditionPrivate::dispatch(sys::DispatchHandle&) { + cb(parent); +} + +void PollableConditionPrivate::set() { + handle->rewatch(); +} + +void PollableConditionPrivate::clear() { + handle->unwatch(); +} + + +PollableCondition::PollableCondition(const Callback& cb, + const boost::shared_ptr<sys::Poller>& poller +) : impl(new PollableConditionPrivate(cb, *this, poller)) +{ +} + +PollableCondition::~PollableCondition() +{ + delete impl; +} + +void PollableCondition::set() { impl->set(); } + +void PollableCondition::clear() { impl->clear(); } + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_LINUX_POLLABLECONDITION_CPP*/ diff --git a/cpp/src/qpid/sys/posix/Shlib.cpp b/cpp/src/qpid/sys/posix/Shlib.cpp index 1552aa06b5..299331103c 100644 --- a/cpp/src/qpid/sys/posix/Shlib.cpp +++ b/cpp/src/qpid/sys/posix/Shlib.cpp @@ -27,11 +27,11 @@ namespace qpid { namespace sys { void Shlib::load(const char* name) { - dlerror(); + ::dlerror(); handle = ::dlopen(name, RTLD_NOW); const char* error = ::dlerror(); if (error) { - throw Exception(QPID_MSG(error)); + throw Exception(QPID_MSG(error << ": " << name)); } } @@ -52,7 +52,7 @@ void* Shlib::getSymbol(const char* name) { void* sym = ::dlsym(handle, name); const char* error = ::dlerror(); if (error) - throw Exception(QPID_MSG(error)); + throw Exception(QPID_MSG(error << ": " << name)); return sym; } diff --git a/cpp/src/qpid/sys/posix/Socket.cpp b/cpp/src/qpid/sys/posix/Socket.cpp index 415d5293ef..7b906f33e8 100644 --- a/cpp/src/qpid/sys/posix/Socket.cpp +++ b/cpp/src/qpid/sys/posix/Socket.cpp @@ -21,8 +21,9 @@ #include "qpid/sys/Socket.h" -#include "check.h" -#include "PrivatePosix.h" +#include "qpid/sys/SocketAddress.h" +#include "qpid/sys/posix/check.h" +#include "qpid/sys/posix/PrivatePosix.h" #include <fcntl.h> #include <sys/types.h> @@ -36,6 +37,7 @@ #include <iostream> #include <boost/format.hpp> +#include <boost/lexical_cast.hpp> namespace qpid { namespace sys { @@ -95,22 +97,33 @@ std::string getService(int fd, bool local) } Socket::Socket() : - IOHandle(new IOHandlePrivate) -{ - createTcp(); -} + IOHandle(new IOHandlePrivate), + nonblocking(false), + nodelay(false) +{} Socket::Socket(IOHandlePrivate* h) : - IOHandle(h) + IOHandle(h), + nonblocking(false), + nodelay(false) {} -void Socket::createTcp() const +void Socket::createSocket(const SocketAddress& sa) const { int& socket = impl->fd; if (socket != -1) Socket::close(); - int s = ::socket (PF_INET, SOCK_STREAM, 0); + int s = ::socket(getAddrInfo(sa).ai_family, getAddrInfo(sa).ai_socktype, 0); if (s < 0) throw QPID_POSIX_ERROR(errno); socket = s; + + try { + if (nonblocking) setNonblocking(); + if (nodelay) setTcpNoDelay(); + } catch (std::exception&) { + ::close(s); + socket = -1; + throw; + } } void Socket::setTimeout(const Duration& interval) const @@ -123,40 +136,42 @@ void Socket::setTimeout(const Duration& interval) const } void Socket::setNonblocking() const { - QPID_POSIX_CHECK(::fcntl(impl->fd, F_SETFL, O_NONBLOCK)); + int& socket = impl->fd; + nonblocking = true; + if (socket != -1) { + QPID_POSIX_CHECK(::fcntl(socket, F_SETFL, O_NONBLOCK)); + } } -namespace { -const char* h_errstr(int e) { - switch (e) { - case HOST_NOT_FOUND: return "Host not found"; - case NO_ADDRESS: return "Name does not have an IP address"; - case TRY_AGAIN: return "A temporary error occurred on an authoritative name server."; - case NO_RECOVERY: return "Non-recoverable name server error"; - default: return "Unknown error"; +void Socket::setTcpNoDelay() const +{ + int& socket = impl->fd; + nodelay = true; + if (socket != -1) { + int flag = 1; + int result = setsockopt(impl->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); + QPID_POSIX_CHECK(result); } } -} void Socket::connect(const std::string& host, uint16_t port) const { - std::stringstream namestream; - namestream << host << ":" << port; - connectname = namestream.str(); + SocketAddress sa(host, boost::lexical_cast<std::string>(port)); + connect(sa); +} + +void Socket::connect(const SocketAddress& addr) const +{ + connectname = addr.asString(); + + createSocket(addr); const int& socket = impl->fd; - struct sockaddr_in name; - name.sin_family = AF_INET; - name.sin_port = htons(port); - // TODO: Be good to make this work for IPv6 as well as IPv4 - // Use more modern lookup functions - struct hostent* hp = gethostbyname ( host.c_str() ); - if (hp == 0) - throw Exception(QPID_MSG("Cannot resolve " << host << ": " << h_errstr(h_errno))); - ::memcpy(&name.sin_addr.s_addr, hp->h_addr_list[0], hp->h_length); - if ((::connect(socket, (struct sockaddr*)(&name), sizeof(name)) < 0) && - (errno != EINPROGRESS)) - throw qpid::Exception(QPID_MSG(strError(errno) << ": " << host << ":" << port)); + // 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)) { + throw Exception(QPID_MSG(strError(errno) << ": " << connectname)); + } } void @@ -170,18 +185,24 @@ Socket::close() const int Socket::listen(uint16_t port, int backlog) const { + SocketAddress sa("", boost::lexical_cast<std::string>(port)); + return listen(sa, backlog); +} + +int Socket::listen(const SocketAddress& sa, int backlog) const +{ + createSocket(sa); + 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 (::bind(socket, getAddrInfo(sa).ai_addr, getAddrInfo(sa).ai_addrlen) < 0) + throw Exception(QPID_MSG("Can't bind to port " << sa.asString() << ": " << strError(errno))); if (::listen(socket, backlog) < 0) - throw Exception(QPID_MSG("Can't listen on port " << port << ": " << strError(errno))); - + throw Exception(QPID_MSG("Can't listen on port " << sa.asString() << ": " << strError(errno))); + + struct sockaddr_in name; socklen_t namelen = sizeof(name); if (::getsockname(socket, (struct sockaddr*)&name, &namelen) < 0) throw QPID_POSIX_ERROR(errno); @@ -189,9 +210,9 @@ int Socket::listen(uint16_t port, int backlog) const return ntohs(name.sin_port); } -Socket* Socket::accept(struct sockaddr *addr, socklen_t *addrlen) const +Socket* Socket::accept() const { - int afd = ::accept(impl->fd, addr, addrlen); + int afd = ::accept(impl->fd, 0, 0); if ( afd >= 0) return new Socket(new IOHandlePrivate(afd)); else if (errno == EAGAIN) @@ -221,9 +242,10 @@ std::string Socket::getPeername() const std::string Socket::getPeerAddress() const { - if (!connectname.empty()) - return std::string (connectname); - return getName(impl->fd, false, true); + if (connectname.empty()) { + connectname = getName(impl->fd, false, true); + } + return connectname; } std::string Socket::getLocalAddress() const @@ -238,7 +260,7 @@ uint16_t Socket::getLocalPort() const uint16_t Socket::getRemotePort() const { - return atoi(getService(impl->fd, true).c_str()); + return std::atoi(getService(impl->fd, true).c_str()); } int Socket::getError() const @@ -252,13 +274,4 @@ int Socket::getError() const return result; } -void Socket::setTcpNoDelay(bool nodelay) const -{ - if (nodelay) { - int flag = 1; - int result = setsockopt(impl->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); - QPID_POSIX_CHECK(result); - } -} - }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/posix/SocketAddress.cpp b/cpp/src/qpid/sys/posix/SocketAddress.cpp new file mode 100644 index 0000000000..cb44f8e41f --- /dev/null +++ b/cpp/src/qpid/sys/posix/SocketAddress.cpp @@ -0,0 +1,97 @@ +/* + * + * 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/SocketAddress.h" + +#include "qpid/sys/posix/check.h" + +#include <sys/socket.h> +#include <string.h> +#include <netdb.h> + +namespace qpid { +namespace sys { + +SocketAddress::SocketAddress(const std::string& host0, const std::string& port0) : + host(host0), + port(port0), + addrInfo(0) +{ +} + +SocketAddress::SocketAddress(const SocketAddress& sa) : + host(sa.host), + port(sa.port), + addrInfo(0) +{ +} + +SocketAddress& SocketAddress::operator=(const SocketAddress& sa) +{ + if (&sa != this) { + host = sa.host; + port = sa.port; + + if (addrInfo) { + ::freeaddrinfo(addrInfo); + addrInfo = 0; + } + } + return *this; +} + +SocketAddress::~SocketAddress() +{ + if (addrInfo) { + ::freeaddrinfo(addrInfo); + } +} + +std::string SocketAddress::asString() const +{ + return host + ":" + port; +} + +const ::addrinfo& getAddrInfo(const SocketAddress& sa) +{ + if (!sa.addrInfo) { + ::addrinfo hints; + ::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; // In order to allow AF_INET6 we'd have to change createTcp() as well + hints.ai_socktype = SOCK_STREAM; + + const char* node = 0; + if (sa.host.empty()) { + hints.ai_flags |= AI_PASSIVE; + } else { + node = sa.host.c_str(); + } + const char* service = sa.port.empty() ? "0" : sa.port.c_str(); + + int n = ::getaddrinfo(node, service, &hints, &sa.addrInfo); + if (n != 0) + throw Exception(QPID_MSG("Cannot resolve " << sa.host << ": " << ::gai_strerror(n))); + } + + return *sa.addrInfo; +} + +}} diff --git a/cpp/src/qpid/sys/posix/SystemInfo.cpp b/cpp/src/qpid/sys/posix/SystemInfo.cpp new file mode 100755 index 0000000000..55737fcfcc --- /dev/null +++ b/cpp/src/qpid/sys/posix/SystemInfo.cpp @@ -0,0 +1,137 @@ +/* + * 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/SystemInfo.h" + +#include "qpid/sys/posix/check.h" + +#include <sys/ioctl.h> +#include <sys/utsname.h> +#include <net/if.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <unistd.h> +#include <iostream> +#include <fstream> +#include <sstream> +#include <netdb.h> + +#ifndef HOST_NAME_MAX +# define HOST_NAME_MAX 256 +#endif + +using namespace std; + +namespace qpid { +namespace sys { + +long SystemInfo::concurrency() { +#ifdef _SC_NPROCESSORS_ONLN // Linux specific. + return sysconf(_SC_NPROCESSORS_ONLN); +#else + return -1; +#endif +} + +bool SystemInfo::getLocalHostname (TcpAddress &address) { + char name[HOST_NAME_MAX]; + if (::gethostname(name, sizeof(name)) != 0) + return false; + address.host = name; + return true; +} + +static const string LOCALHOST("127.0.0.1"); + +void SystemInfo::getLocalIpAddresses (uint16_t port, + std::vector<Address> &addrList) { + int s = ::socket(PF_INET, SOCK_STREAM, 0); + for (int i=1;;i++) { + ::ifreq ifr; + ifr.ifr_ifindex = i; + if (::ioctl (s, SIOCGIFNAME, &ifr) < 0) + break; + /* now ifr.ifr_name is set */ + if (::ioctl (s, SIOCGIFADDR, &ifr) < 0) + continue; + ::sockaddr *saddr = (::sockaddr *) &ifr.ifr_addr; + char dispName[NI_MAXHOST]; + if (int rc=::getnameinfo(saddr, sizeof(ifr.ifr_addr), dispName, sizeof(dispName), 0, 0, NI_NUMERICHOST) != 0) + throw QPID_POSIX_ERROR(rc); + string addr(dispName); + if (addr != LOCALHOST) + addrList.push_back(TcpAddress(addr, port)); + } + if (addrList.empty()) { + addrList.push_back(TcpAddress(LOCALHOST, port)); + } + close (s); +} + +void SystemInfo::getSystemId (std::string &osName, + std::string &nodeName, + std::string &release, + std::string &version, + std::string &machine) +{ + struct utsname _uname; + if (uname (&_uname) == 0) + { + osName = _uname.sysname; + nodeName = _uname.nodename; + release = _uname.release; + version = _uname.version; + machine = _uname.machine; + } +} + +uint32_t SystemInfo::getProcessId() +{ + return (uint32_t) ::getpid(); +} + +uint32_t SystemInfo::getParentProcessId() +{ + return (uint32_t) ::getppid(); +} + +// Linux specific (Solaris has quite different stuff in /proc) +string SystemInfo::getProcessName() +{ + string value; + + ifstream input("/proc/self/status"); + if (input.good()) { + while (!input.eof()) { + string key; + input >> key; + if (key == "Name:") { + input >> value; + break; + } + } + input.close(); + } + + return value; +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/posix/Thread.cpp b/cpp/src/qpid/sys/posix/Thread.cpp index bb5641bc53..a784e63195 100644 --- a/cpp/src/qpid/sys/posix/Thread.cpp +++ b/cpp/src/qpid/sys/posix/Thread.cpp @@ -22,7 +22,7 @@ #include "qpid/sys/Thread.h" #include "qpid/sys/Runnable.h" -#include "check.h" +#include "qpid/sys/posix/check.h" #include <pthread.h> diff --git a/cpp/src/qpid/sys/posix/Time.cpp b/cpp/src/qpid/sys/posix/Time.cpp index 8aa9fd9946..0734abd1df 100644 --- a/cpp/src/qpid/sys/posix/Time.cpp +++ b/cpp/src/qpid/sys/posix/Time.cpp @@ -19,25 +19,46 @@ * */ -#include "PrivatePosix.h" +#include "qpid/sys/posix/PrivatePosix.h" #include "qpid/sys/Time.h" #include <ostream> #include <time.h> #include <stdio.h> #include <sys/time.h> +#include <unistd.h> + +namespace { +int64_t max_abstime() { return std::numeric_limits<int64_t>::max(); } +} namespace qpid { namespace sys { +AbsTime::AbsTime(const AbsTime& t, const Duration& d) : + timepoint(d == Duration::max() ? max_abstime() : t.timepoint+d.nanosecs) +{} + +AbsTime AbsTime::FarFuture() { + AbsTime ff; ff.timepoint = max_abstime(); return ff; +} + AbsTime AbsTime::now() { struct timespec ts; ::clock_gettime(CLOCK_REALTIME, &ts); AbsTime time_now; - time_now.time_ns = toTime(ts).nanosecs; + time_now.timepoint = toTime(ts).nanosecs; return time_now; } +Duration::Duration(const AbsTime& time0) : + nanosecs(time0.timepoint) +{} + +Duration::Duration(const AbsTime& start, const AbsTime& finish) : + nanosecs(finish.timepoint - start.timepoint) +{} + struct timespec& toTimespec(struct timespec& ts, const Duration& t) { ts.tv_sec = t / TIME_SEC; ts.tv_nsec = t % TIME_SEC; @@ -58,25 +79,35 @@ std::ostream& operator<<(std::ostream& o, const Duration& d) { return o << int64_t(d) << "ns"; } -std::ostream& operator<<(std::ostream& o, const AbsTime& t) { - static const char * month_abbrevs[] = { - "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" - }; - struct tm * timeinfo; - time_t rawtime(t.time_ns/TIME_SEC); - timeinfo = localtime (&rawtime); +namespace { +inline std::ostream& outputFormattedTime(std::ostream& o, const ::time_t* time) { + ::tm timeinfo; char time_string[100]; - sprintf ( time_string, - "%d-%s-%02d %02d:%02d:%02d", - 1900 + timeinfo->tm_year, - month_abbrevs[timeinfo->tm_mon], - timeinfo->tm_mday, - timeinfo->tm_hour, - timeinfo->tm_min, - timeinfo->tm_sec - ); + ::strftime(time_string, 100, + "%Y-%m-%d %H:%M:%S", + localtime_r(time, &timeinfo)); return o << time_string; } +} -}} +std::ostream& operator<<(std::ostream& o, const AbsTime& t) { + ::time_t rawtime(t.timepoint/TIME_SEC); + return outputFormattedTime(o, &rawtime); +} +void outputFormattedNow(std::ostream& o) { + ::time_t rawtime; + ::time(&rawtime); + outputFormattedTime(o, &rawtime); + o << " "; +} + +void sleep(int secs) { + ::sleep(secs); +} + +void usleep(uint64_t usecs) { + ::usleep(usecs); +} + +}} diff --git a/cpp/src/qpid/sys/posix/check.h b/cpp/src/qpid/sys/posix/check.h deleted file mode 100644 index f3031b7593..0000000000 --- a/cpp/src/qpid/sys/posix/check.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef _posix_check_h -#define _posix_check_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/Exception.h" - -#include <cerrno> -#include <assert.h> -#include <stdio.h> - -#define QPID_POSIX_ERROR(ERRNO) qpid::Exception(QPID_MSG(qpid::sys::strError(ERRNO))) - -/** THROW QPID_POSIX_ERROR(errno) if RESULT is less than zero */ -#define QPID_POSIX_CHECK(RESULT) \ - if ((RESULT) < 0) throw QPID_POSIX_ERROR((errno)) - -/** Throw a posix error if ERRNO is non-zero */ -#define QPID_POSIX_THROW_IF(ERRNO) \ - do { int e=(ERRNO); if (e) throw QPID_POSIX_ERROR(e); } while(0) - -/** Same as _THROW_IF in a release build, but abort a debug build */ -#ifdef NDEBUG -#define QPID_POSIX_ASSERT_THROW_IF(ERRNO) QPID_POSIX_THROW_IF(ERRNO) -#else -#define QPID_POSIX_ASSERT_THROW_IF(ERRNO) \ - do { int e=(ERRNO); if (e) { errno=e; ::perror(0); assert(0); } } while(0) -#endif - -#endif /*!_posix_check_h*/ diff --git a/cpp/src/qpid/sys/rdma/RdmaClient.cpp b/cpp/src/qpid/sys/rdma/RdmaClient.cpp index afff96b72f..d39f7885a5 100644 --- a/cpp/src/qpid/sys/rdma/RdmaClient.cpp +++ b/cpp/src/qpid/sys/rdma/RdmaClient.cpp @@ -1,4 +1,24 @@ -#include "RdmaIO.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/rdma/RdmaIO.h" #include "qpid/sys/Time.h" #include <netdb.h> @@ -20,18 +40,20 @@ using std::rand; using qpid::sys::Poller; using qpid::sys::Dispatcher; +using qpid::sys::SocketAddress; using qpid::sys::AbsTime; using qpid::sys::Duration; using qpid::sys::TIME_SEC; using qpid::sys::TIME_INFINITE; +namespace qpid { +namespace tests { + // count of messages int64_t smsgs = 0; int64_t sbytes = 0; int64_t rmsgs = 0; int64_t rbytes = 0; -int64_t cmsgs = 0; -int writable = true; int target = 1000000; int msgsize = 200; @@ -42,17 +64,15 @@ Duration fullTestDuration(TIME_INFINITE); vector<char> testString; void write(Rdma::AsynchIO& aio) { - if ((cmsgs - rmsgs) < Rdma::DEFAULT_WR_ENTRIES/2) { - while (writable) { - if (smsgs >= target) - return; - Rdma::Buffer* b = aio.getBuffer(); - std::copy(testString.begin(), testString.end(), b->bytes); - b->dataCount = msgsize; - aio.queueWrite(b); - ++smsgs; - sbytes += b->byteCount; - } + while (aio.writable()) { + if (smsgs >= target) + return; + Rdma::Buffer* b = aio.getBuffer(); + std::copy(testString.begin(), testString.end(), b->bytes); + b->dataCount = msgsize; + aio.queueWrite(b); + ++smsgs; + sbytes += msgsize; } } @@ -62,39 +82,46 @@ void dataError(Rdma::AsynchIO&) { void data(Poller::shared_ptr p, Rdma::AsynchIO& aio, Rdma::Buffer* b) { ++rmsgs; - rbytes += b->byteCount; + rbytes += b->dataCount; // When all messages have been recvd stop if (rmsgs < target) { write(aio); } else { fullTestDuration = std::min(fullTestDuration, Duration(startTime, AbsTime::now())); - if (cmsgs >= target) + if (aio.incompletedWrites() == 0) p->shutdown(); } } -void full(Rdma::AsynchIO&) { - writable = false; +void full(Rdma::AsynchIO& a, Rdma::Buffer* b) { + // Warn as we shouldn't get here anymore + cerr << "!"; + + // Don't need to keep buffer just adjust the counts + --smsgs; + sbytes -= b->dataCount; + + // Give buffer back + a.returnBuffer(b); } void idle(Poller::shared_ptr p, Rdma::AsynchIO& aio) { - writable = true; - ++cmsgs; if (smsgs < target) { write(aio); } else { sendingDuration = std::min(sendingDuration, Duration(startTime, AbsTime::now())); - if (rmsgs >= target && cmsgs >= target) + if (rmsgs >= target && aio.incompletedWrites() == 0) p->shutdown(); } } -void connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci) { +void connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp) { cout << "Connected\n"; Rdma::QueuePair::intrusive_ptr q = ci->getQueuePair(); - Rdma::AsynchIO* aio = new Rdma::AsynchIO(ci->getQueuePair(), msgsize, + Rdma::AsynchIO* aio = new Rdma::AsynchIO(ci->getQueuePair(), + cp.maxRecvBufferSize, cp.initialXmitCredit , Rdma::DEFAULT_WR_ENTRIES, boost::bind(&data, poller, _1, _2), boost::bind(&idle, poller, _1), &full, @@ -111,31 +138,25 @@ void disconnected(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&) p->shutdown(); } -void connectionError(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&) { +void connectionError(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&, const Rdma::ErrorType) { cout << "Connection error\n"; p->shutdown(); } -void rejected(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&) { +void rejected(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&) { cout << "Connection rejected\n"; p->shutdown(); } +}} // namespace qpid::tests + +using namespace qpid::tests; + int main(int argc, char* argv[]) { vector<string> args(&argv[0], &argv[argc]); - ::addrinfo *res; - ::addrinfo hints = {}; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; + string host = args[1]; string port = (args.size() < 3) ? "20079" : args[2]; - int n = ::getaddrinfo(args[1].c_str(), port.c_str(), &hints, &res); - if (n<0) { - cerr << "Can't find information for: " << args[1] << "\n"; - return 1; - } else { - cout << "Connecting to: " << args[1] << ":" << port <<"\n"; - } if (args.size() > 3) msgsize = atoi(args[3].c_str()); @@ -144,19 +165,22 @@ int main(int argc, char* argv[]) { // Make a random message of that size testString.resize(msgsize); for (int i = 0; i < msgsize; ++i) { - testString[i] = 32 + rand() & 0x3f; + testString[i] = 32 + (rand() & 0x3f); } try { boost::shared_ptr<Poller> p(new Poller()); Dispatcher d(p); + SocketAddress sa(host, port); + cout << "Connecting to: " << sa.asString() <<"\n"; Rdma::Connector c( - *res->ai_addr, - boost::bind(&connected, p, _1), - boost::bind(&connectionError, p, _1), + sa, + Rdma::ConnectionParams(msgsize, Rdma::DEFAULT_WR_ENTRIES), + boost::bind(&connected, p, _1, _2), + boost::bind(&connectionError, p, _1, _2), boost::bind(&disconnected, p, _1), - boost::bind(&rejected, p, _1)); + boost::bind(&rejected, p, _1, _2)); c.start(p); d.run(); diff --git a/cpp/src/qpid/sys/rdma/RdmaIO.cpp b/cpp/src/qpid/sys/rdma/RdmaIO.cpp index 755d6f17c4..8d06fccba1 100644 --- a/cpp/src/qpid/sys/rdma/RdmaIO.cpp +++ b/cpp/src/qpid/sys/rdma/RdmaIO.cpp @@ -1,12 +1,41 @@ -#include "RdmaIO.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/rdma/RdmaIO.h" + +#include "qpid/log/Statement.h" + #include <iostream> #include <boost/bind.hpp> +using qpid::sys::SocketAddress; +using qpid::sys::DispatchHandle; +using qpid::sys::Poller; + namespace Rdma { AsynchIO::AsynchIO( QueuePair::intrusive_ptr q, - int s, + int size, + int xCredit, + int rCount, ReadCallback rc, IdleCallback ic, FullCallback fc, @@ -14,10 +43,15 @@ namespace Rdma { ) : qp(q), dataHandle(*qp, boost::bind(&AsynchIO::dataEvent, this, _1), 0, 0), - bufferSize(s), - recvBufferCount(DEFAULT_WR_ENTRIES), - xmitBufferCount(DEFAULT_WR_ENTRIES), + bufferSize(size), + recvCredit(0), + xmitCredit(xCredit), + recvBufferCount(rCount), + xmitBufferCount(xCredit), outstandingWrites(0), + closed(false), + deleting(false), + state(IDLE), readCallback(rc), idleCallback(ic), fullCallback(fc), @@ -29,80 +63,281 @@ namespace Rdma { // Prepost some recv buffers before we go any further for (int i = 0; i<recvBufferCount; ++i) { + // Allocate recv buffer Buffer* b = qp->createBuffer(bufferSize); buffers.push_front(b); b->dataCount = b->byteCount; qp->postRecv(b); } + + for (int i = 0; i<xmitBufferCount; ++i) { + // Allocate xmit buffer + Buffer* b = qp->createBuffer(bufferSize); + buffers.push_front(b); + bufferQueue.push_front(b); + b->dataCount = 0; + b->dataStart = 0; + } } AsynchIO::~AsynchIO() { + // Warn if we are deleting whilst there are still unreclaimed write buffers + if ( outstandingWrites>0 ) + QPID_LOG(error, "RDMA: qp=" << qp << ": Deleting queue before all write buffers finished"); + + // Turn off callbacks (before doing the deletes) + dataHandle.stopWatch(); + // The buffers ptr_deque automatically deletes all the buffers we've allocated + // TODO: It might turn out to be more efficient in high connection loads to reuse the + // buffers rather than having to reregister them all the time (this would be straightforward if all + // connections haver the same buffer size and harder otherwise) } void AsynchIO::start(Poller::shared_ptr poller) { dataHandle.startWatch(poller); } - // TODO: Currently we don't prevent write buffer overrun we just advise - // when to stop writing. - void AsynchIO::queueWrite(Buffer* buff) { - qp->postSend(buff); - ++outstandingWrites; - if (outstandingWrites >= xmitBufferCount) { - fullCallback(*this); + // Mark for deletion/Delete this object when we have no outstanding writes + void AsynchIO::deferDelete() { + State oldState; + State newState; + bool doReturn; + //qpid::sys::ScopedLock<qpid::sys::Mutex> l(stateLock); + // It is safe to assign to deleting here as we either delete ourselves + // before leaving this function or deleting is set on exit + do { + newState = oldState = state.get(); + doReturn = false; + if (outstandingWrites > 0 || oldState != IDLE) { + deleting = true; + doReturn = true; + } else{ + newState = DELETED; // Stop any read callback before the dataHandle.stopWatch() in the destructor + } + } while (!state.boolCompareAndSwap(oldState, newState)); + if (doReturn) { + return; } + delete this; } - void AsynchIO::notifyPendingWrite() { - // Just perform the idle callback (if possible) - if (outstandingWrites < xmitBufferCount) { - idleCallback(*this); + void AsynchIO::queueWrite(Buffer* buff) { + // Make sure we don't overrun our available buffers + // either at our end or the known available at the peers end + if (writable()) { + // TODO: We might want to batch up sending credit + if (recvCredit > 0) { + int creditSent = recvCredit & ~FlagsMask; + qp->postSend(creditSent, buff); + recvCredit -= creditSent; + } else { + qp->postSend(buff); + } + ++outstandingWrites; + --xmitCredit; + } else { + if (fullCallback) { + fullCallback(*this, buff); + } else { + QPID_LOG(error, "RDMA: qp=" << qp << ": Write queue full, but no callback, throwing buffer away"); + returnBuffer(buff); + } } } + // Mark now closed (so we don't accept any more writes or make any idle callbacks) void AsynchIO::queueWriteClose() { + // Don't think we actually need to lock here as transition is 1 way only to closed + closed = true; } - Buffer* AsynchIO::getBuffer() { - qpid::sys::ScopedLock<qpid::sys::Mutex> l(bufferQueueLock); - if (bufferQueue.empty()) { - Buffer* b = qp->createBuffer(bufferSize); - buffers.push_front(b); - b->dataCount = 0; - return b; - } else { - Buffer* b = bufferQueue.front(); - bufferQueue.pop_front(); - b->dataCount = 0; - b->dataStart = 0; - return b; + void AsynchIO::notifyPendingWrite() { + // As notifyPendingWrite can be called on an arbitrary thread it must check whether we are processing or not. + // If we are then we just return as we know that we will eventually do the idle callback anyway. + // + // qpid::sys::ScopedLock<qpid::sys::Mutex> l(stateLock); + // We can get here in any state (as the caller could be in any thread) + State oldState; + State newState; + bool doReturn; + do { + newState = oldState = state.get(); + doReturn = false; + switch (oldState) { + case NOTIFY_WRITE: + case PENDING_NOTIFY: + // We only need to note a pending notify if we're already doing a notify as data processing + // is always followed by write notification processing + newState = PENDING_NOTIFY; + doReturn = true; + break; + case PENDING_DATA: + doReturn = true; + break; + case DATA: + // Only need to return here as data processing will do the idleCallback itself anyway + doReturn = true; + break; + case IDLE: + newState = NOTIFY_WRITE; + break; + case DELETED: + assert(oldState!=DELETED); + doReturn = true; + }; + } while (!state.boolCompareAndSwap(oldState, newState)); + if (doReturn) { + return; + } + + doWriteCallback(); + + // Keep track of what we need to do so that we can release the lock + enum {COMPLETION, NOTIFY, RETURN, EXIT} action; + // If there was pending data whilst we were doing this, process it now + // + // Using NOTIFY_WRITE for both NOTIFY & COMPLETION is a bit strange, but we're making sure we get the + // correct result if we reenter notifyPendingWrite(), in which case we want to + // end up in PENDING_NOTIFY (entering dataEvent doesn't matter as it only checks + // not IDLE) + do { + //qpid::sys::ScopedLock<qpid::sys::Mutex> l(stateLock); + do { + newState = oldState = state.get(); + action = RETURN; // Anything but COMPLETION + switch (oldState) { + case NOTIFY_WRITE: + newState = IDLE; + action = (action == COMPLETION) ? EXIT : RETURN; + break; + case PENDING_DATA: + newState = NOTIFY_WRITE; + action = COMPLETION; + break; + case PENDING_NOTIFY: + newState = NOTIFY_WRITE; + action = NOTIFY; + break; + default: + assert(oldState!=IDLE && oldState!=DATA && oldState!=DELETED); + action = RETURN; + } + } while (!state.boolCompareAndSwap(oldState, newState)); + + // Note we only get here if we were in the PENDING_DATA or PENDING_NOTIFY state + // so that we do need to process completions or notifications now + switch (action) { + case COMPLETION: + processCompletions(); + // Fall through + case NOTIFY: + doWriteCallback(); + break; + case RETURN: + return; + case EXIT: + // If we just processed completions we might need to delete ourselves + if (deleting && outstandingWrites == 0) { + delete this; + } + return; + } + } while (true); + } + + void AsynchIO::dataEvent(qpid::sys::DispatchHandle&) { + // Keep track of writable notifications + // qpid::sys::ScopedLock<qpid::sys::Mutex> l(stateLock); + State oldState; + State newState; + bool doReturn; + do { + newState = oldState = state.get(); + doReturn = false; + // We're already processing a notification + switch (oldState) { + case IDLE: + newState = DATA; + break; + default: + // Can't get here in DATA state as that would violate the serialisation rules + assert( oldState!=DATA ); + newState = PENDING_DATA; + doReturn = true; + } + } while (!state.boolCompareAndSwap(oldState, newState)); + if (doReturn) { + return; } + processCompletions(); + + //qpid::sys::ScopedLock<qpid::sys::Mutex> l(stateLock); + do { + newState = oldState = state.get(); + assert( oldState==DATA ); + newState = NOTIFY_WRITE; + } while (!state.boolCompareAndSwap(oldState, newState)); + + do { + doWriteCallback(); + + // qpid::sys::ScopedLock<qpid::sys::Mutex> l(stateLock); + bool doBreak; + do { + newState = oldState = state.get(); + doBreak = false; + if ( oldState==NOTIFY_WRITE ) { + newState = IDLE; + doBreak = true; + } else { + // Can't get DATA/PENDING_DATA here as dataEvent cannot be reentered + assert( oldState==PENDING_NOTIFY ); + newState = NOTIFY_WRITE; + } + } while (!state.boolCompareAndSwap(oldState, newState)); + if (doBreak) { + break; + } + } while (true); + + // We might need to delete ourselves + if (deleting && outstandingWrites == 0) { + delete this; + } } - void AsynchIO::dataEvent(DispatchHandle&) { + void AsynchIO::processCompletions() { QueuePair::intrusive_ptr q = qp->getNextChannelEvent(); + // Re-enable notification for queue: + // This needs to happen before we could do anything that could generate more work completion + // events (ie the callbacks etc. in the following). + // This can't make us reenter this code as the handle attached to the completion queue will still be + // disabled by the poller until we leave this code + qp->notifyRecv(); + qp->notifySend(); + + int recvEvents = 0; + int sendEvents = 0; + // If no event do nothing if (!q) return; assert(q == qp); - // Re-enable notification for queue - qp->notifySend(); - qp->notifyRecv(); - // Repeat until no more events do { QueuePairEvent e(qp->getNextEvent()); if (!e) - return; + break; ::ibv_wc_status status = e.getEventStatus(); if (status != IBV_WC_SUCCESS) { errorCallback(*this); + // TODO: Probably need to flush queues at this point return; } @@ -111,46 +346,143 @@ namespace Rdma { Buffer* b = e.getBuffer(); QueueDirection dir = e.getDirection(); if (dir == RECV) { - readCallback(*this, b); + ++recvEvents; + + // Get our xmitCredit if it was sent + bool dataPresent = true; + if (e.immPresent() ) { + xmitCredit += (e.getImm() & ~FlagsMask); + dataPresent = ((e.getImm() & IgnoreData) == 0); + } + + // if there was no data sent then the message was only to update our credit + if ( dataPresent ) { + readCallback(*this, b); + } + // At this point the buffer has been consumed so put it back on the recv queue + b->dataStart = 0; + b->dataCount = 0; qp->postRecv(b); + + // Received another message + ++recvCredit; + + // Send recvCredit if it is large enough (it will have got this large because we've not sent anything recently) + if (recvCredit > recvBufferCount/2) { + // TODO: This should use RDMA write with imm as there might not ever be a buffer to receive this message + // but this is a little unlikely, as to get in this state we have to have received messages without sending any + // for a while so its likely we've received an credit update from the far side. + if (writable()) { + Buffer* ob = getBuffer(); + // Have to send something as adapters hate it when you try to transfer 0 bytes + *reinterpret_cast< uint32_t* >(ob->bytes) = htonl(recvCredit); + ob->dataCount = sizeof(uint32_t); + + int creditSent = recvCredit & ~FlagsMask; + qp->postSend(creditSent | IgnoreData, ob); + recvCredit -= creditSent; + ++outstandingWrites; + --xmitCredit; + } else { + QPID_LOG(warning, "RDMA: qp=" << qp << ": Unable to send unsolicited credit"); + } + } } else { + ++sendEvents; { qpid::sys::ScopedLock<qpid::sys::Mutex> l(bufferQueueLock); bufferQueue.push_front(b); } --outstandingWrites; - // TODO: maybe don't call idle unless we're low on write buffers - idleCallback(*this); } } while (true); + + // Not sure if this is expected or not + if (recvEvents == 0 && sendEvents == 0) { + QPID_LOG(debug, "RDMA: qp=" << qp << ": Got channel event with no recv/send completions"); + } + } + + void AsynchIO::doWriteCallback() { + // TODO: maybe don't call idle unless we're low on write buffers + // Keep on calling the idle routine as long as we are writable and we got something to write last call + while (writable()) { + int xc = xmitCredit; + idleCallback(*this); + // Check whether we actually wrote anything + if (xmitCredit == xc) { + QPID_LOG(debug, "RDMA: qp=" << qp << ": Called for data, but got none: xmitCredit=" << xmitCredit); + return; + } + } + } + + Buffer* AsynchIO::getBuffer() { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(bufferQueueLock); + assert(!bufferQueue.empty()); + Buffer* b = bufferQueue.front(); + bufferQueue.pop_front(); + b->dataCount = 0; + b->dataStart = 0; + return b; + } + + void AsynchIO::returnBuffer(Buffer* b) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(bufferQueueLock); + bufferQueue.push_front(b); + b->dataCount = 0; + b->dataStart = 0; + } + + ConnectionManager::ConnectionManager( + ErrorCallback errc, + DisconnectedCallback dc + ) : + ci(Connection::make()), + handle(*ci, boost::bind(&ConnectionManager::event, this, _1), 0, 0), + errorCallback(errc), + disconnectedCallback(dc) + { + ci->nonblocking(); + } + + ConnectionManager::~ConnectionManager() + { + handle.stopWatch(); + } + + void ConnectionManager::start(Poller::shared_ptr poller) { + startConnection(ci); + handle.startWatch(poller); + } + + void ConnectionManager::event(DispatchHandle&) { + connectionEvent(ci); } Listener::Listener( - const sockaddr& src, - ConnectedCallback cc, + const SocketAddress& src, + const ConnectionParams& cp, + EstablishedCallback ec, ErrorCallback errc, DisconnectedCallback dc, ConnectionRequestCallback crc ) : + ConnectionManager(errc, dc), src_addr(src), - ci(Connection::make()), - handle(*ci, boost::bind(&Listener::connectionEvent, this, _1), 0, 0), - connectedCallback(cc), - errorCallback(errc), - disconnectedCallback(dc), - connectionRequestCallback(crc) + checkConnectionParams(cp), + connectionRequestCallback(crc), + establishedCallback(ec) { - ci->nonblocking(); } - void Listener::start(Poller::shared_ptr poller) { + void Listener::startConnection(Connection::intrusive_ptr ci) { ci->bind(src_addr); ci->listen(); - handle.startWatch(poller); } - void Listener::connectionEvent(DispatchHandle&) { + void Listener::connectionEvent(Connection::intrusive_ptr ci) { ConnectionEvent e(ci->getNextEvent()); // If (for whatever reason) there was no event do nothing @@ -161,65 +493,75 @@ namespace Rdma { // you get from CONNECT_REQUEST has the same context info // as its parent listening rdma_cm_id ::rdma_cm_event_type eventType = e.getEventType(); + ::rdma_conn_param conn_param = e.getConnectionParam(); Rdma::Connection::intrusive_ptr id = e.getConnection(); switch (eventType) { case RDMA_CM_EVENT_CONNECT_REQUEST: { - bool accept = true; - // Extract connection parameters and private data from event - ::rdma_conn_param conn_param = e.getConnectionParam(); + // Make sure peer has sent params we can use + if (!conn_param.private_data || conn_param.private_data_len < sizeof(ConnectionParams)) { + id->reject(); + break; + } + ConnectionParams cp = *static_cast<const ConnectionParams*>(conn_param.private_data); + + // Reject if requested msg size is bigger than we allow + if (cp.maxRecvBufferSize > checkConnectionParams.maxRecvBufferSize) { + id->reject(&checkConnectionParams); + break; + } + bool accept = true; if (connectionRequestCallback) - //TODO: pass private data to callback (and accept new private data for accept somehow) - accept = connectionRequestCallback(id); + accept = connectionRequestCallback(id, cp); + if (accept) { // Accept connection - id->accept(conn_param); + cp.initialXmitCredit = checkConnectionParams.initialXmitCredit; + id->accept(conn_param, &cp); } else { - //Reject connection + // Reject connection id->reject(); } - break; } case RDMA_CM_EVENT_ESTABLISHED: - connectedCallback(id); + establishedCallback(id); break; case RDMA_CM_EVENT_DISCONNECTED: disconnectedCallback(id); break; case RDMA_CM_EVENT_CONNECT_ERROR: - errorCallback(id); + errorCallback(id, CONNECT_ERROR); break; default: - std::cerr << "Warning: unexpected response to listen - " << eventType << "\n"; + // Unexpected response + errorCallback(id, UNKNOWN); + //std::cerr << "Warning: unexpected response to listen - " << eventType << "\n"; } } Connector::Connector( - const sockaddr& dst, + const SocketAddress& dst, + const ConnectionParams& cp, ConnectedCallback cc, ErrorCallback errc, DisconnectedCallback dc, RejectedCallback rc ) : + ConnectionManager(errc, dc), dst_addr(dst), - ci(Connection::make()), - handle(*ci, boost::bind(&Connector::connectionEvent, this, _1), 0, 0), - connectedCallback(cc), - errorCallback(errc), - disconnectedCallback(dc), - rejectedCallback(rc) + connectionParams(cp), + rejectedCallback(rc), + connectedCallback(cc) { - ci->nonblocking(); } - void Connector::start(Poller::shared_ptr poller) { + void Connector::startConnection(Connection::intrusive_ptr ci) { ci->resolve_addr(dst_addr); - handle.startWatch(poller); } - void Connector::connectionEvent(DispatchHandle&) { + void Connector::connectionEvent(Connection::intrusive_ptr ci) { ConnectionEvent e(ci->getNextEvent()); // If (for whatever reason) there was no event do nothing @@ -227,6 +569,8 @@ namespace Rdma { return; ::rdma_cm_event_type eventType = e.getEventType(); + ::rdma_conn_param conn_param = e.getConnectionParam(); + Rdma::Connection::intrusive_ptr id = e.getConnection(); switch (eventType) { case RDMA_CM_EVENT_ADDR_RESOLVED: // RESOLVE_ADDR @@ -234,38 +578,46 @@ namespace Rdma { break; case RDMA_CM_EVENT_ADDR_ERROR: // RESOLVE_ADDR - errorCallback(ci); + errorCallback(ci, ADDR_ERROR); break; case RDMA_CM_EVENT_ROUTE_RESOLVED: // RESOLVE_ROUTE: - ci->connect(); + ci->connect(&connectionParams); break; case RDMA_CM_EVENT_ROUTE_ERROR: // RESOLVE_ROUTE: - errorCallback(ci); + errorCallback(ci, ROUTE_ERROR); break; case RDMA_CM_EVENT_CONNECT_ERROR: // CONNECTING - errorCallback(ci); + errorCallback(ci, CONNECT_ERROR); break; case RDMA_CM_EVENT_UNREACHABLE: // CONNECTING - errorCallback(ci); + errorCallback(ci, UNREACHABLE); break; - case RDMA_CM_EVENT_REJECTED: + case RDMA_CM_EVENT_REJECTED: { // CONNECTING - rejectedCallback(ci); + // Extract private data from event + assert(conn_param.private_data && conn_param.private_data_len >= sizeof(ConnectionParams)); + ConnectionParams cp = *static_cast<const ConnectionParams*>(conn_param.private_data); + rejectedCallback(ci, cp); break; - case RDMA_CM_EVENT_ESTABLISHED: + } + case RDMA_CM_EVENT_ESTABLISHED: { // CONNECTING - connectedCallback(ci); + // Extract private data from event + assert(conn_param.private_data && conn_param.private_data_len >= sizeof(ConnectionParams)); + ConnectionParams cp = *static_cast<const ConnectionParams*>(conn_param.private_data); + connectedCallback(ci, cp); break; + } case RDMA_CM_EVENT_DISCONNECTED: // ESTABLISHED disconnectedCallback(ci); break; default: - std::cerr << "Warning: unexpected event in connect: " << eventType << "\n"; + QPID_LOG(warning, "RDMA: Unexpected event in connect: " << eventType); } } } diff --git a/cpp/src/qpid/sys/rdma/RdmaIO.h b/cpp/src/qpid/sys/rdma/RdmaIO.h index 5c8d49607c..12a1b98d24 100644 --- a/cpp/src/qpid/sys/rdma/RdmaIO.h +++ b/cpp/src/qpid/sys/rdma/RdmaIO.h @@ -1,10 +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. + * + */ #ifndef Rdma_Acceptor_h #define Rdma_Acceptor_h -#include "rdma_wrap.h" +#include "qpid/sys/rdma/rdma_wrap.h" +#include "qpid/sys/AtomicValue.h" #include "qpid/sys/Dispatcher.h" +#include "qpid/sys/DispatchHandle.h" #include "qpid/sys/Mutex.h" +#include "qpid/sys/SocketAddress.h" #include <netinet/in.h> @@ -12,32 +35,30 @@ #include <boost/ptr_container/ptr_deque.hpp> #include <deque> -using qpid::sys::DispatchHandle; -using qpid::sys::Poller; - namespace Rdma { class Connection; - typedef boost::function1<void, Rdma::Connection::intrusive_ptr&> ConnectedCallback; - typedef boost::function1<void, Rdma::Connection::intrusive_ptr&> ErrorCallback; - typedef boost::function1<void, Rdma::Connection::intrusive_ptr&> DisconnectedCallback; - typedef boost::function1<bool, Rdma::Connection::intrusive_ptr&> ConnectionRequestCallback; - typedef boost::function1<void, Rdma::Connection::intrusive_ptr&> RejectedCallback; - class AsynchIO { typedef boost::function1<void, AsynchIO&> ErrorCallback; typedef boost::function2<void, AsynchIO&, Buffer*> ReadCallback; typedef boost::function1<void, AsynchIO&> IdleCallback; - typedef boost::function1<void, AsynchIO&> FullCallback; + typedef boost::function2<void, AsynchIO&, Buffer*> FullCallback; QueuePair::intrusive_ptr qp; - DispatchHandle dataHandle; + qpid::sys::DispatchHandleRef dataHandle; int bufferSize; + int recvCredit; + int xmitCredit; int recvBufferCount; int xmitBufferCount; int outstandingWrites; + bool closed; // TODO: Perhaps (probably) this state can be merged with the following... + bool deleting; // TODO: Perhaps (probably) this state can be merged with the following... + enum State { IDLE, DATA, PENDING_DATA, NOTIFY_WRITE, PENDING_NOTIFY, DELETED }; + qpid::sys::AtomicValue<State> state; + //qpid::sys::Mutex stateLock; std::deque<Buffer*> bufferQueue; qpid::sys::Mutex bufferQueueLock; boost::ptr_deque<Buffer> buffers; @@ -48,72 +69,154 @@ namespace Rdma { ErrorCallback errorCallback; public: + // TODO: Instead of specifying a buffer size specify the amount of memory the AsynchIO class can use + // for buffers both read and write (allocate half to each up front) and fail if we cannot allocate that much + // locked memory AsynchIO( QueuePair::intrusive_ptr q, - int s, + int size, + int xCredit, + int rCount, ReadCallback rc, IdleCallback ic, FullCallback fc, ErrorCallback ec ); - ~AsynchIO(); - void start(Poller::shared_ptr poller); + void start(qpid::sys::Poller::shared_ptr poller); + bool writable() const; + bool bufferAvailable() const; void queueWrite(Buffer* buff); void notifyPendingWrite(); void queueWriteClose(); + void deferDelete(); + int incompletedWrites() const; Buffer* getBuffer(); + void returnBuffer(Buffer*); private: - void dataEvent(DispatchHandle& handle); + // Don't let anyone else delete us to make sure there can't be outstanding callbacks + ~AsynchIO(); + + // Constants for the peer-peer command messages + // These are sent in the high bits if the imm data of an rdma message + // The low bits are used to send the credit + const static int FlagsMask = 0x10000000; // Mask for all flag bits - be sure to update this if you add more command bits + const static int IgnoreData = 0x10000000; // Message contains no application data + + void dataEvent(qpid::sys::DispatchHandle& handle); + void processCompletions(); + void doWriteCallback(); }; - class Listener - { - sockaddr src_addr; + inline bool AsynchIO::writable() const { + return (!closed && outstandingWrites < xmitBufferCount && xmitCredit > 0); + } + + inline int AsynchIO::incompletedWrites() const { + return outstandingWrites; + } + + inline bool AsynchIO::bufferAvailable() const { + return !bufferQueue.empty(); + } + // These are the parameters necessary to start the conversation + // * Each peer HAS to allocate buffers of the size of the maximum receive from its peer + // * Each peer HAS to know the initial "credit" it has for transmitting to its peer + struct ConnectionParams { + int maxRecvBufferSize; + int initialXmitCredit ; + + ConnectionParams(int s, int c) : + maxRecvBufferSize(s), + initialXmitCredit(c) + {} + }; + + enum ErrorType { + ADDR_ERROR, + ROUTE_ERROR, + CONNECT_ERROR, + UNREACHABLE, + UNKNOWN + }; + + typedef boost::function2<void, Rdma::Connection::intrusive_ptr&, ErrorType> ErrorCallback; + typedef boost::function1<void, Rdma::Connection::intrusive_ptr&> DisconnectedCallback; + + class ConnectionManager { Connection::intrusive_ptr ci; - DispatchHandle handle; - ConnectedCallback connectedCallback; + qpid::sys::DispatchHandle handle; + + protected: ErrorCallback errorCallback; DisconnectedCallback disconnectedCallback; + + public: + ConnectionManager( + ErrorCallback errc, + DisconnectedCallback dc + ); + + virtual ~ConnectionManager(); + + void start(qpid::sys::Poller::shared_ptr poller); + + private: + void event(qpid::sys::DispatchHandle& handle); + + virtual void startConnection(Connection::intrusive_ptr ci) = 0; + virtual void connectionEvent(Connection::intrusive_ptr ci) = 0; + }; + + typedef boost::function2<bool, Rdma::Connection::intrusive_ptr&, const ConnectionParams&> ConnectionRequestCallback; + typedef boost::function1<void, Rdma::Connection::intrusive_ptr&> EstablishedCallback; + + class Listener : public ConnectionManager + { + qpid::sys::SocketAddress src_addr; + ConnectionParams checkConnectionParams; ConnectionRequestCallback connectionRequestCallback; + EstablishedCallback establishedCallback; public: Listener( - const sockaddr& src, - ConnectedCallback cc, + const qpid::sys::SocketAddress& src, + const ConnectionParams& cp, + EstablishedCallback ec, ErrorCallback errc, DisconnectedCallback dc, ConnectionRequestCallback crc = 0 ); - void start(Poller::shared_ptr poller); private: - void connectionEvent(DispatchHandle& handle); + void startConnection(Connection::intrusive_ptr ci); + void connectionEvent(Connection::intrusive_ptr ci); }; - class Connector + typedef boost::function2<void, Rdma::Connection::intrusive_ptr&, const ConnectionParams&> RejectedCallback; + typedef boost::function2<void, Rdma::Connection::intrusive_ptr&, const ConnectionParams&> ConnectedCallback; + + class Connector : public ConnectionManager { - sockaddr dst_addr; - Connection::intrusive_ptr ci; - DispatchHandle handle; - ConnectedCallback connectedCallback; - ErrorCallback errorCallback; - DisconnectedCallback disconnectedCallback; + qpid::sys::SocketAddress dst_addr; + ConnectionParams connectionParams; RejectedCallback rejectedCallback; + ConnectedCallback connectedCallback; public: Connector( - const sockaddr& dst, + const qpid::sys::SocketAddress& dst, + const ConnectionParams& cp, ConnectedCallback cc, ErrorCallback errc, DisconnectedCallback dc, RejectedCallback rc = 0 ); - void start(Poller::shared_ptr poller); private: - void connectionEvent(DispatchHandle& handle); + void startConnection(Connection::intrusive_ptr ci); + void connectionEvent(Connection::intrusive_ptr ci); }; } diff --git a/cpp/src/qpid/sys/rdma/RdmaServer.cpp b/cpp/src/qpid/sys/rdma/RdmaServer.cpp index f7f739d6c2..4c11ba23eb 100644 --- a/cpp/src/qpid/sys/rdma/RdmaServer.cpp +++ b/cpp/src/qpid/sys/rdma/RdmaServer.cpp @@ -1,4 +1,24 @@ -#include "RdmaIO.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/rdma/RdmaIO.h" #include <arpa/inet.h> @@ -15,19 +35,21 @@ using std::string; using std::cout; using std::cerr; +using qpid::sys::SocketAddress; using qpid::sys::Poller; using qpid::sys::Dispatcher; // All the accepted connections +namespace qpid { +namespace tests { + struct ConRec { Rdma::Connection::intrusive_ptr connection; Rdma::AsynchIO* data; - bool writable; queue<Rdma::Buffer*> queuedWrites; ConRec(Rdma::Connection::intrusive_ptr c) : - connection(c), - writable(true) + connection(c) {} }; @@ -35,50 +57,53 @@ void dataError(Rdma::AsynchIO&) { cout << "Data error:\n"; } +void idle(ConRec* cr, Rdma::AsynchIO& a) { + // Need to make sure full is not called as it would reorder messages + while (!cr->queuedWrites.empty() && a.writable()) { + Rdma::Buffer* buf = cr->queuedWrites.front(); + cr->queuedWrites.pop(); + a.queueWrite(buf); + } +} + void data(ConRec* cr, Rdma::AsynchIO& a, Rdma::Buffer* b) { // Echo data back Rdma::Buffer* buf = a.getBuffer(); std::copy(b->bytes+b->dataStart, b->bytes+b->dataStart+b->dataCount, buf->bytes); buf->dataCount = b->dataCount; - if (cr->queuedWrites.empty() && cr->writable) { + if (cr->queuedWrites.empty()) { + // If can't write then full will be called and push buffer on back of queue a.queueWrite(buf); } else { cr->queuedWrites.push(buf); + // Try to empty queue + idle(cr, a); } } -void full(ConRec* cr, Rdma::AsynchIO&) { - cr->writable = false; -} - -void idle(ConRec* cr, Rdma::AsynchIO& a) { - cr->writable = true; - while (!cr->queuedWrites.empty() && cr->writable) { - Rdma::Buffer* buf = cr->queuedWrites.front(); - cr->queuedWrites.pop(); - a.queueWrite(buf); - } +void full(ConRec* cr, Rdma::AsynchIO&, Rdma::Buffer* buf) { + cr->queuedWrites.push(buf); } void disconnected(Rdma::Connection::intrusive_ptr& ci) { ConRec* cr = ci->getContext<ConRec>(); cr->connection->disconnect(); - delete cr->data; + cr->data->queueWriteClose(); delete cr; cout << "Disconnected: " << cr << "\n"; } -void connectionError(Rdma::Connection::intrusive_ptr& ci) { +void connectionError(Rdma::Connection::intrusive_ptr& ci, Rdma::ErrorType) { ConRec* cr = ci->getContext<ConRec>(); cr->connection->disconnect(); if (cr) { - delete cr->data; + cr->data->queueWriteClose(); delete cr; } cout << "Connection error: " << cr << "\n"; } -bool connectionRequest(Rdma::Connection::intrusive_ptr& ci) { +bool connectionRequest(Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp) { cout << "Incoming connection: "; // For fun reject alternate connection attempts @@ -89,10 +114,11 @@ bool connectionRequest(Rdma::Connection::intrusive_ptr& ci) { if (x) { ConRec* cr = new ConRec(ci); Rdma::AsynchIO* aio = - new Rdma::AsynchIO(ci->getQueuePair(), 8000, + new Rdma::AsynchIO(ci->getQueuePair(), + cp.maxRecvBufferSize, cp.initialXmitCredit, Rdma::DEFAULT_WR_ENTRIES, boost::bind(data, cr, _1, _2), boost::bind(idle, cr, _1), - boost::bind(full, cr, _1), + boost::bind(full, cr, _1, _2), dataError); ci->addContext(cr); cr->data = aio; @@ -112,23 +138,23 @@ void connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci) { cr->data->start(poller); } +}} // namespace qpid::tests + +using namespace qpid::tests; + int main(int argc, char* argv[]) { vector<string> args(&argv[0], &argv[argc]); - ::sockaddr_in sin; - - int port = (args.size() < 2) ? 20079 : atoi(args[1].c_str()); + std::string port = (args.size() < 2) ? "20079" : args[1]; cout << "Listening on port: " << port << "\n"; - sin.sin_family = AF_INET; - sin.sin_port = htons(port); - sin.sin_addr.s_addr = INADDR_ANY; - try { boost::shared_ptr<Poller> p(new Poller()); Dispatcher d(p); - Rdma::Listener a((const sockaddr&)(sin), + SocketAddress sa("", port); + Rdma::Listener a(sa, + Rdma::ConnectionParams(16384, Rdma::DEFAULT_WR_ENTRIES), boost::bind(connected, p, _1), connectionError, disconnected, diff --git a/cpp/src/qpid/sys/rdma/rdma_exception.h b/cpp/src/qpid/sys/rdma/rdma_exception.h index 2773f25917..7867aef2e4 100644 --- a/cpp/src/qpid/sys/rdma/rdma_exception.h +++ b/cpp/src/qpid/sys/rdma/rdma_exception.h @@ -1,3 +1,23 @@ +/* + * + * 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 RDMA_EXCEPTION_H #define RDMA_EXCEPTION_H diff --git a/cpp/src/qpid/sys/rdma/rdma_factories.cpp b/cpp/src/qpid/sys/rdma/rdma_factories.cpp index 53dc4f8935..a5e9ebd23c 100644 --- a/cpp/src/qpid/sys/rdma/rdma_factories.cpp +++ b/cpp/src/qpid/sys/rdma/rdma_factories.cpp @@ -1,4 +1,24 @@ -#include "rdma_factories.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/rdma/rdma_factories.h" namespace Rdma { void acker(::rdma_cm_event* e) throw () { @@ -36,4 +56,9 @@ namespace Rdma { (void) ::ibv_destroy_cq(cq); } + void destroyQp(::ibv_qp* qp) throw () { + if (qp) + (void) ::ibv_destroy_qp(qp); + } + } diff --git a/cpp/src/qpid/sys/rdma/rdma_factories.h b/cpp/src/qpid/sys/rdma/rdma_factories.h index 26a93bb494..783181cf1b 100644 --- a/cpp/src/qpid/sys/rdma/rdma_factories.h +++ b/cpp/src/qpid/sys/rdma/rdma_factories.h @@ -1,7 +1,27 @@ +/* + * + * 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 RDMA_FACTORIES_H #define RDMA_FACTORIES_H -#include "rdma_exception.h" +#include "qpid/sys/rdma/rdma_exception.h" #include <rdma/rdma_cma.h> @@ -15,10 +35,11 @@ namespace Rdma { void deallocPd(::ibv_pd* p) throw (); void destroyCChannel(::ibv_comp_channel* c) throw (); void destroyCq(::ibv_cq* cq) throw (); + void destroyQp(::ibv_qp* qp) throw (); inline boost::shared_ptr< ::rdma_event_channel > mkEChannel() { - return - boost::shared_ptr< ::rdma_event_channel >(::rdma_create_event_channel(), destroyEChannel); + ::rdma_event_channel* c = CHECK_NULL(::rdma_create_event_channel()); + return boost::shared_ptr< ::rdma_event_channel >(c, destroyEChannel); } inline boost::shared_ptr< ::rdma_cm_id > diff --git a/cpp/src/qpid/sys/rdma/rdma_wrap.cpp b/cpp/src/qpid/sys/rdma/rdma_wrap.cpp new file mode 100644 index 0000000000..53e31ca766 --- /dev/null +++ b/cpp/src/qpid/sys/rdma/rdma_wrap.cpp @@ -0,0 +1,185 @@ +/* + * + * 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/rdma/rdma_wrap.h" + +namespace Rdma { + const ::rdma_conn_param DEFAULT_CONNECT_PARAM = { + 0, // .private_data + 0, // .private_data_len + 4, // .responder_resources + 4, // .initiator_depth + 0, // .flow_control + 5, // .retry_count + 7 // .rnr_retry_count + }; + + // This is moderately inefficient so don't use in a critical path + int deviceCount() { + int count; + ::ibv_free_device_list(::ibv_get_device_list(&count)); + return count; + } + + ::rdma_conn_param ConnectionEvent::getConnectionParam() const { + // It's badly documented, but it seems from the librdma source code that all the following + // event types have a valid param.conn + switch (event->event) { + case RDMA_CM_EVENT_CONNECT_REQUEST: + case RDMA_CM_EVENT_ESTABLISHED: + case RDMA_CM_EVENT_REJECTED: + case RDMA_CM_EVENT_DISCONNECTED: + case RDMA_CM_EVENT_CONNECT_ERROR: + return event->param.conn; + default: + ::rdma_conn_param p = {}; + return p; + } + } + + QueuePair::QueuePair(boost::shared_ptr< ::rdma_cm_id > i) : + qpid::sys::IOHandle(new qpid::sys::IOHandlePrivate), + pd(allocPd(i->verbs)), + cchannel(mkCChannel(i->verbs)), + scq(mkCq(i->verbs, DEFAULT_CQ_ENTRIES, 0, cchannel.get())), + rcq(mkCq(i->verbs, DEFAULT_CQ_ENTRIES, 0, cchannel.get())), + outstandingSendEvents(0), + outstandingRecvEvents(0) + { + impl->fd = cchannel->fd; + + // Set cq context to this QueuePair object so we can find + // ourselves again + scq->cq_context = this; + rcq->cq_context = this; + + ::ibv_qp_init_attr qp_attr = {}; + + // TODO: make a default struct for this + qp_attr.cap.max_send_wr = DEFAULT_WR_ENTRIES; + qp_attr.cap.max_send_sge = 4; + qp_attr.cap.max_recv_wr = DEFAULT_WR_ENTRIES; + qp_attr.cap.max_recv_sge = 4; + + qp_attr.send_cq = scq.get(); + qp_attr.recv_cq = rcq.get(); + qp_attr.qp_type = IBV_QPT_RC; + + CHECK(::rdma_create_qp(i.get(), pd.get(), &qp_attr)); + qp = boost::shared_ptr< ::ibv_qp >(i->qp, destroyQp); + + // Set the qp context to this so we can find ourselves again + qp->qp_context = this; + } + + QueuePair::~QueuePair() { + if (outstandingSendEvents > 0) + ::ibv_ack_cq_events(scq.get(), outstandingSendEvents); + if (outstandingRecvEvents > 0) + ::ibv_ack_cq_events(rcq.get(), outstandingRecvEvents); + + // Reset back pointer in case someone else has the qp + qp->qp_context = 0; + } + + void QueuePair::postRecv(Buffer* buf) { + ::ibv_recv_wr rwr = {}; + ::ibv_sge sge; + + sge.addr = (uintptr_t) buf->bytes+buf->dataStart; + sge.length = buf->dataCount; + sge.lkey = buf->mr->lkey; + + rwr.wr_id = reinterpret_cast<uint64_t>(buf); + rwr.sg_list = &sge; + rwr.num_sge = 1; + + ::ibv_recv_wr* badrwr = 0; + CHECK_IBV(::ibv_post_recv(qp.get(), &rwr, &badrwr)); + if (badrwr) + throw std::logic_error("ibv_post_recv(): Bad rwr"); + } + + void QueuePair::postSend(Buffer* buf) { + ::ibv_send_wr swr = {}; + ::ibv_sge sge; + + sge.addr = (uintptr_t) buf->bytes+buf->dataStart; + sge.length = buf->dataCount; + sge.lkey = buf->mr->lkey; + + swr.wr_id = reinterpret_cast<uint64_t>(buf); + swr.opcode = IBV_WR_SEND; + swr.send_flags = IBV_SEND_SIGNALED; + swr.sg_list = &sge; + swr.num_sge = 1; + + ::ibv_send_wr* badswr = 0; + CHECK_IBV(::ibv_post_send(qp.get(), &swr, &badswr)); + if (badswr) + throw std::logic_error("ibv_post_send(): Bad swr"); + } + + void QueuePair::postSend(uint32_t imm, Buffer* buf) { + ::ibv_send_wr swr = {}; + ::ibv_sge sge; + + sge.addr = (uintptr_t) buf->bytes+buf->dataStart; + sge.length = buf->dataCount; + sge.lkey = buf->mr->lkey; + swr.send_flags = IBV_SEND_SIGNALED; + + swr.wr_id = reinterpret_cast<uint64_t>(buf); + swr.imm_data = htonl(imm); + swr.opcode = IBV_WR_SEND_WITH_IMM; + swr.sg_list = &sge; + swr.num_sge = 1; + + ::ibv_send_wr* badswr = 0; + CHECK_IBV(::ibv_post_send(qp.get(), &swr, &badswr)); + if (badswr) + throw std::logic_error("ibv_post_send(): Bad swr"); + } +} + +std::ostream& operator<<(std::ostream& o, ::rdma_cm_event_type t) { +# define CHECK_TYPE(t) case t: o << #t; break; + switch(t) { + CHECK_TYPE(RDMA_CM_EVENT_ADDR_RESOLVED) + CHECK_TYPE(RDMA_CM_EVENT_ADDR_ERROR) + CHECK_TYPE(RDMA_CM_EVENT_ROUTE_RESOLVED) + CHECK_TYPE(RDMA_CM_EVENT_ROUTE_ERROR) + CHECK_TYPE(RDMA_CM_EVENT_CONNECT_REQUEST) + CHECK_TYPE(RDMA_CM_EVENT_CONNECT_RESPONSE) + CHECK_TYPE(RDMA_CM_EVENT_CONNECT_ERROR) + CHECK_TYPE(RDMA_CM_EVENT_UNREACHABLE) + CHECK_TYPE(RDMA_CM_EVENT_REJECTED) + CHECK_TYPE(RDMA_CM_EVENT_ESTABLISHED) + CHECK_TYPE(RDMA_CM_EVENT_DISCONNECTED) + CHECK_TYPE(RDMA_CM_EVENT_DEVICE_REMOVAL) + CHECK_TYPE(RDMA_CM_EVENT_MULTICAST_JOIN) + CHECK_TYPE(RDMA_CM_EVENT_MULTICAST_ERROR) + default: + o << "UNKNOWN_EVENT"; + } +# undef CHECK_TYPE + return o; +} diff --git a/cpp/src/qpid/sys/rdma/rdma_wrap.h b/cpp/src/qpid/sys/rdma/rdma_wrap.h index 421c8fc41b..e11497dc02 100644 --- a/cpp/src/qpid/sys/rdma/rdma_wrap.h +++ b/cpp/src/qpid/sys/rdma/rdma_wrap.h @@ -1,7 +1,27 @@ +/* + * + * 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 RDMA_WRAP_H #define RDMA_WRAP_H -#include "rdma_factories.h" +#include "qpid/sys/rdma/rdma_factories.h" #include <rdma/rdma_cma.h> @@ -11,6 +31,8 @@ #include <fcntl.h> +#include <netdb.h> + #include <vector> #include <algorithm> #include <iostream> @@ -23,15 +45,9 @@ namespace Rdma { const int DEFAULT_BACKLOG = 100; const int DEFAULT_CQ_ENTRIES = 256; const int DEFAULT_WR_ENTRIES = 64; - const ::rdma_conn_param DEFAULT_CONNECT_PARAM = { - 0, // .private_data - 0, // .private_data_len - 4, // .responder_resources - 4, // .initiator_depth - 0, // .flow_control - 5, // .retry_count - 7 // .rnr_retry_count - }; + extern const ::rdma_conn_param DEFAULT_CONNECT_PARAM; + + int deviceCount(); struct Buffer { friend class QueuePair; @@ -95,6 +111,14 @@ namespace Rdma { return dir != NONE; } + bool immPresent() const { + return wc.wc_flags & IBV_WC_WITH_IMM; + } + + uint32_t getImm() const { + return ntohl(wc.imm_data); + } + QueueDirection getDirection() const { return dir; } @@ -117,15 +141,12 @@ 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. - // - // Currently QueuePairs are contained inside Connections and have no - // separate lifetime class QueuePair : public qpid::sys::IOHandle, public qpid::RefCounted { boost::shared_ptr< ::ibv_pd > pd; boost::shared_ptr< ::ibv_comp_channel > cchannel; boost::shared_ptr< ::ibv_cq > scq; boost::shared_ptr< ::ibv_cq > rcq; - boost::shared_ptr< ::rdma_cm_id > id; + boost::shared_ptr< ::ibv_qp > qp; int outstandingSendEvents; int outstandingRecvEvents; @@ -187,6 +208,7 @@ namespace Rdma { void postRecv(Buffer* buf); void postSend(Buffer* buf); + void postSend(uint32_t imm, Buffer* buf); void notifyRecv(); void notifySend(); }; @@ -213,14 +235,7 @@ namespace Rdma { return event->event; } - ::rdma_conn_param getConnectionParam() const { - if (event->event == RDMA_CM_EVENT_CONNECT_REQUEST) { - return event->param.conn; - } else { - ::rdma_conn_param p = {}; - return p; - } - } + ::rdma_conn_param getConnectionParam() const; boost::intrusive_ptr<Connection> getConnection () const { return id; @@ -271,6 +286,11 @@ namespace Rdma { impl->fd = channel->fd; } + ~Connection() { + // Reset the id context in case someone else has it + id->context = 0; + } + // Default destructor fine void ensureQueuePair() { @@ -330,9 +350,9 @@ namespace Rdma { return ConnectionEvent(e); } - void bind(sockaddr& src_addr) const { + void bind(qpid::sys::SocketAddress& src_addr) const { assert(id.get()); - CHECK(::rdma_bind_addr(id.get(), &src_addr)); + CHECK(::rdma_bind_addr(id.get(), getAddrInfo(src_addr).ai_addr)); } void listen(int backlog = DEFAULT_BACKLOG) const { @@ -341,12 +361,11 @@ namespace Rdma { } void resolve_addr( - sockaddr& dst_addr, - sockaddr* src_addr = 0, + qpid::sys::SocketAddress& dst_addr, int timeout_ms = DEFAULT_TIMEOUT) const { assert(id.get()); - CHECK(::rdma_resolve_addr(id.get(), src_addr, &dst_addr, timeout_ms)); + CHECK(::rdma_resolve_addr(id.get(), 0, getAddrInfo(dst_addr).ai_addr, timeout_ms)); } void resolve_route(int timeout_ms = DEFAULT_TIMEOUT) const { @@ -425,52 +444,38 @@ namespace Rdma { return qp; } + + std::string getLocalName() const { + ::sockaddr* addr = ::rdma_get_local_addr(id.get()); + char hostName[NI_MAXHOST]; + char portName[NI_MAXSERV]; + CHECK_IBV(::getnameinfo( + addr, sizeof(::sockaddr_storage), + hostName, sizeof(hostName), + portName, sizeof(portName), + NI_NUMERICHOST | NI_NUMERICSERV)); + std::string r(hostName); + r += ":"; + r += portName; + return r; + } + + std::string getPeerName() const { + ::sockaddr* addr = ::rdma_get_peer_addr(id.get()); + char hostName[NI_MAXHOST]; + char portName[NI_MAXSERV]; + CHECK_IBV(::getnameinfo( + addr, sizeof(::sockaddr_storage), + hostName, sizeof(hostName), + portName, sizeof(portName), + NI_NUMERICHOST | NI_NUMERICSERV)); + std::string r(hostName); + r += ":"; + r += portName; + return r; + } }; - inline QueuePair::QueuePair(boost::shared_ptr< ::rdma_cm_id > i) : - qpid::sys::IOHandle(new qpid::sys::IOHandlePrivate), - pd(allocPd(i->verbs)), - cchannel(mkCChannel(i->verbs)), - scq(mkCq(i->verbs, DEFAULT_CQ_ENTRIES, 0, cchannel.get())), - rcq(mkCq(i->verbs, DEFAULT_CQ_ENTRIES, 0, cchannel.get())), - id(i), - outstandingSendEvents(0), - outstandingRecvEvents(0) - { - impl->fd = cchannel->fd; - - // Set cq context to this QueuePair object so we can find - // ourselves again - scq->cq_context = this; - rcq->cq_context = this; - - ::ibv_qp_init_attr qp_attr = {}; - - // TODO: make a default struct for this - qp_attr.cap.max_send_wr = DEFAULT_WR_ENTRIES; - qp_attr.cap.max_send_sge = 4; - qp_attr.cap.max_recv_wr = DEFAULT_WR_ENTRIES; - qp_attr.cap.max_recv_sge = 4; - - qp_attr.send_cq = scq.get(); - qp_attr.recv_cq = rcq.get(); - qp_attr.qp_type = IBV_QPT_RC; - - CHECK(::rdma_create_qp(id.get(), pd.get(), &qp_attr)); - - // Set the qp context to this so we can find ourselves again - id->qp->qp_context = this; - } - - inline QueuePair::~QueuePair() { - if (outstandingSendEvents > 0) - ::ibv_ack_cq_events(scq.get(), outstandingSendEvents); - if (outstandingRecvEvents > 0) - ::ibv_ack_cq_events(rcq.get(), outstandingRecvEvents); - - ::rdma_destroy_qp(id.get()); - } - inline void QueuePair::notifyRecv() { CHECK_IBV(ibv_req_notify_cq(rcq.get(), 0)); } @@ -479,44 +484,6 @@ namespace Rdma { CHECK_IBV(ibv_req_notify_cq(scq.get(), 0)); } - inline void QueuePair::postRecv(Buffer* buf) { - ::ibv_recv_wr rwr = {}; - ::ibv_sge sge; - - sge.addr = (uintptr_t) buf->bytes+buf->dataStart; - sge.length = buf->dataCount; - sge.lkey = buf->mr->lkey; - - rwr.wr_id = reinterpret_cast<uint64_t>(buf); - rwr.sg_list = &sge; - rwr.num_sge = 1; - - ::ibv_recv_wr* badrwr = 0; - CHECK_IBV(::ibv_post_recv(id->qp, &rwr, &badrwr)); - if (badrwr) - throw std::logic_error("ibv_post_recv(): Bad rwr"); - } - - inline void QueuePair::postSend(Buffer* buf) { - ::ibv_send_wr swr = {}; - ::ibv_sge sge; - - sge.addr = (uintptr_t) buf->bytes+buf->dataStart; - sge.length = buf->dataCount; - sge.lkey = buf->mr->lkey; - - swr.wr_id = reinterpret_cast<uint64_t>(buf); - swr.opcode = IBV_WR_SEND; - swr.send_flags = IBV_SEND_SIGNALED; - swr.sg_list = &sge; - swr.num_sge = 1; - - ::ibv_send_wr* badswr = 0; - CHECK_IBV(::ibv_post_send(id->qp, &swr, &badswr)); - if (badswr) - throw std::logic_error("ibv_post_send(): Bad swr"); - } - inline ConnectionEvent::ConnectionEvent(::rdma_cm_event* e) : id((e->event != RDMA_CM_EVENT_CONNECT_REQUEST) ? Connection::find(e->id) : new Connection(e->id)), @@ -525,26 +492,6 @@ namespace Rdma { {} } -inline std::ostream& operator<<(std::ostream& o, ::rdma_cm_event_type t) { -# define CHECK_TYPE(t) case t: o << #t; break; - switch(t) { - CHECK_TYPE(RDMA_CM_EVENT_ADDR_RESOLVED) - CHECK_TYPE(RDMA_CM_EVENT_ADDR_ERROR) - CHECK_TYPE(RDMA_CM_EVENT_ROUTE_RESOLVED) - CHECK_TYPE(RDMA_CM_EVENT_ROUTE_ERROR) - CHECK_TYPE(RDMA_CM_EVENT_CONNECT_REQUEST) - CHECK_TYPE(RDMA_CM_EVENT_CONNECT_RESPONSE) - CHECK_TYPE(RDMA_CM_EVENT_CONNECT_ERROR) - CHECK_TYPE(RDMA_CM_EVENT_UNREACHABLE) - CHECK_TYPE(RDMA_CM_EVENT_REJECTED) - CHECK_TYPE(RDMA_CM_EVENT_ESTABLISHED) - CHECK_TYPE(RDMA_CM_EVENT_DISCONNECTED) - CHECK_TYPE(RDMA_CM_EVENT_DEVICE_REMOVAL) - CHECK_TYPE(RDMA_CM_EVENT_MULTICAST_JOIN) - CHECK_TYPE(RDMA_CM_EVENT_MULTICAST_ERROR) - } -# undef CHECK_TYPE - return o; -} +std::ostream& operator<<(std::ostream& o, ::rdma_cm_event_type t); #endif // RDMA_WRAP_H diff --git a/cpp/src/qpid/sys/solaris/ECFPoller.cpp b/cpp/src/qpid/sys/solaris/ECFPoller.cpp index fefa21dae1..f12012cbb0 100644 --- a/cpp/src/qpid/sys/solaris/ECFPoller.cpp +++ b/cpp/src/qpid/sys/solaris/ECFPoller.cpp @@ -30,9 +30,11 @@ #include <port.h> #include <poll.h> #include <errno.h> +#include <pthread.h> +#include <signal.h> #include <assert.h> -#include <vector> +#include <queue> #include <exception> @@ -43,11 +45,11 @@ namespace qpid { namespace sys { // Deletion manager to handle deferring deletion of PollerHandles to when they definitely aren't being used -DeletionManager<PollerHandle> PollerHandleDeletionManager; +DeletionManager<PollerHandlePrivate> PollerHandleDeletionManager; // Instantiate (and define) class static for DeletionManager template <> -DeletionManager<PollerHandle>::AllThreadsStatuses DeletionManager<PollerHandle>::allThreadsStatuses(0); +DeletionManager<PollerHandlePrivate>::AllThreadsStatuses DeletionManager<PollerHandlePrivate>::allThreadsStatuses(0); class PollerHandlePrivate { friend class Poller; @@ -58,7 +60,8 @@ class PollerHandlePrivate { MONITORED, INACTIVE, HUNGUP, - MONITORED_HUNGUP + MONITORED_HUNGUP, + DELETED }; int fd; @@ -104,6 +107,14 @@ class PollerHandlePrivate { assert(stat == MONITORED); stat = HUNGUP; } + + bool isDeleted() const { + return stat == DELETED; + } + + void setDeleted() { + stat = DELETED; + } }; PollerHandle::PollerHandle(const IOHandle& h) : @@ -111,11 +122,16 @@ PollerHandle::PollerHandle(const IOHandle& h) : {} PollerHandle::~PollerHandle() { - delete impl; -} - -void PollerHandle::deferDelete() { - PollerHandleDeletionManager.markForDeletion(this); + { + ScopedLock<Mutex> l(impl->lock); + if (impl->isDeleted()) { + return; + } + if (impl->isActive()) { + impl->setDeleted(); + } + } + PollerHandleDeletionManager.markForDeletion(impl); } /** @@ -125,35 +141,82 @@ void PollerHandle::deferDelete() { class PollerPrivate { friend class Poller; - const int portId; + class InterruptHandle: public PollerHandle { + std::queue<PollerHandle*> handles; + + void processEvent(Poller::EventType) { + PollerHandle* handle = handles.front(); + handles.pop(); + assert(handle); + + //Synthesise event + Poller::Event event(handle, Poller::INTERRUPTED); + + //Process synthesised event + event.process(); + } + public: + InterruptHandle() : PollerHandle(DummyIOHandle) {} + + void addHandle(PollerHandle& h) { + handles.push(&h); + } + + PollerHandle *getHandle() { + PollerHandle* handle = handles.front(); + handles.pop(); + return handle; + } + + bool queuedHandles() { + return handles.size() > 0; + } + }; + + const int portId; + bool isShutdown; + InterruptHandle interruptHandle; + static uint32_t directionToPollEvent(Poller::Direction dir) { switch (dir) { - case Poller::IN: return POLLIN; - case Poller::OUT: return POLLOUT; - case Poller::INOUT: return POLLIN | POLLOUT; - default: return 0; + case Poller::INPUT: return POLLIN; + case Poller::OUTPUT: return POLLOUT; + case Poller::INOUT: return POLLIN | POLLOUT; + default: return 0; } } static Poller::EventType pollToDirection(uint32_t events) { uint32_t e = events & (POLLIN | POLLOUT); switch (e) { - case POLLIN: return Poller::READABLE; - case POLLOUT: return Poller::WRITABLE; - case POLLIN | POLLOUT: return Poller::READ_WRITABLE; - default: - return (events & (POLLHUP | POLLERR)) ? - Poller::DISCONNECTED : Poller::INVALID; + case POLLIN: return Poller::READABLE; + case POLLOUT: return Poller::WRITABLE; + case POLLIN | POLLOUT: return Poller::READ_WRITABLE; + default: + return (events & (POLLHUP | POLLERR)) ? + Poller::DISCONNECTED : Poller::INVALID; } } - + PollerPrivate() : - portId(::port_create()) { + portId(::port_create()), + isShutdown(false) { + QPID_POSIX_CHECK(portId); + QPID_LOG(trace, "port_create returned port Id: " << portId); } ~PollerPrivate() { } + + void interrupt() { + //Send an Alarm to the port + //We need to send a nonzero event mask, using POLLHUP, + //nevertheless the wait method will only look for a PORT_ALERT_SET + QPID_LOG(trace, "Sending a port_alert to " << portId); + QPID_POSIX_CHECK(::port_alert(portId, PORT_ALERT_SET, POLLHUP, + &static_cast<PollerHandle&>(interruptHandle))); + } }; void Poller::addFd(PollerHandle& handle, Direction dir) { @@ -177,7 +240,6 @@ void Poller::addFd(PollerHandle& handle, Direction dir) { QPID_LOG(trace, "Poller::addFd(handle=" << &handle << "[" << typeid(&handle).name() << "], fd=" << eh.fd << ")"); - //assert(dynamic_cast<DispatchHandle*>(&handle)); } void Poller::delFd(PollerHandle& handle) { @@ -223,17 +285,56 @@ void Poller::rearmFd(PollerHandle& handle) { } void Poller::shutdown() { - //Send an Alarm to the port - //We need to send a nonzero event mask, using POLLHUP, but - //The wait method will only look for a PORT_ALERT_SET - QPID_POSIX_CHECK(::port_alert(impl->portId, PORT_ALERT_SET, POLLHUP, NULL)); - QPID_LOG(trace, "Poller::shutdown"); + //Allow sloppy code to shut us down more than once + if (impl->isShutdown) + return; + + impl->isShutdown = true; + impl->interrupt(); +} + +bool Poller::interrupt(PollerHandle& handle) { + PollerPrivate::InterruptHandle& ih = impl->interruptHandle; + PollerHandlePrivate& eh = *static_cast<PollerHandle&>(ih).impl; + ScopedLock<Mutex> l(eh.lock); + ih.addHandle(handle); + impl->interrupt(); + eh.setActive(); + return true; +} + +void Poller::run() { + // Make sure we can't be interrupted by signals at a bad time + ::sigset_t ss; + ::sigfillset(&ss); + ::pthread_sigmask(SIG_SETMASK, &ss, 0); + + do { + Event event = wait(); + + // If can read/write then dispatch appropriate callbacks + if (event.handle) { + event.process(); + } else { + // Handle shutdown + switch (event.type) { + case SHUTDOWN: + return; + default: + // This should be impossible + assert(false); + } + } + } while (true); } Poller::Event Poller::wait(Duration timeout) { timespec_t tout; timespec_t* ptout = NULL; port_event_t pe; + + AbsTime targetTimeout = (timeout == TIME_INFINITE) ? FAR_FUTURE : + AbsTime(now(), timeout); if (timeout != TIME_INFINITE) { tout.tv_sec = 0; @@ -243,12 +344,21 @@ Poller::Event Poller::wait(Duration timeout) { do { PollerHandleDeletionManager.markAllUnusedInThisThread(); - QPID_LOG(trace, "About to enter port_get. Thread " - << pthread_self() + QPID_LOG(trace, "About to enter port_get on " << impl->portId + << ". Thread " << pthread_self() << ", timeout=" << timeout); - + + int rc = ::port_get(impl->portId, &pe, ptout); + QPID_LOG(trace, "port_get on " << impl->portId + << " returned " << rc); + + if (impl->isShutdown) { + PollerHandleDeletionManager.markAllUnusedInThisThread(); + return Event(0, SHUTDOWN); + } + if (rc < 0) { switch (errno) { case EINTR: @@ -259,33 +369,61 @@ Poller::Event Poller::wait(Duration timeout) { QPID_POSIX_CHECK(rc); } } else { - //We use alert mode to notify the shutdown of the Poller - if (pe.portev_source == PORT_SOURCE_ALERT) { - return Event(0, SHUTDOWN); - } - if (pe.portev_source == PORT_SOURCE_FD) { - PollerHandle *handle = static_cast<PollerHandle*>(pe.portev_user); - PollerHandlePrivate& eh = *handle->impl; - ScopedLock<Mutex> l(eh.lock); - QPID_LOG(trace, "About to send handle: " << handle); + PollerHandle* handle = static_cast<PollerHandle*>(pe.portev_user); + PollerHandlePrivate& eh = *handle->impl; + ScopedLock<Mutex> l(eh.lock); + + if (eh.isActive()) { + QPID_LOG(trace, "Handle is active"); + //We use alert mode to notify interrupts + if (pe.portev_source == PORT_SOURCE_ALERT && + handle == &impl->interruptHandle) { + QPID_LOG(trace, "Interrupt notified"); + + PollerHandle* wrappedHandle = impl->interruptHandle.getHandle(); + + if (impl->interruptHandle.queuedHandles()) { + impl->interrupt(); + eh.setActive(); + } else { + eh.setInactive(); + } + return Event(wrappedHandle, INTERRUPTED); + } - if (eh.isActive()) { - if (pe.portev_events & POLLHUP) { - if (eh.isHungup()) { - return Event(handle, DISCONNECTED); + if (pe.portev_source == PORT_SOURCE_FD) { + QPID_LOG(trace, "About to send handle: " << handle); + if (pe.portev_events & POLLHUP) { + if (eh.isHungup()) { + return Event(handle, DISCONNECTED); + } + eh.setHungup(); + } else { + eh.setInactive(); } - eh.setHungup(); - } else { - eh.setInactive(); - } - QPID_LOG(trace, "Sending event (thread: " - << pthread_self() << ") for handle " << handle - << ", direction= " - << PollerPrivate::pollToDirection(pe.portev_events)); - return Event(handle, PollerPrivate::pollToDirection(pe.portev_events)); + QPID_LOG(trace, "Sending event (thread: " + << pthread_self() << ") for handle " << handle + << ", direction= " + << PollerPrivate::pollToDirection(pe.portev_events)); + return Event(handle, PollerPrivate::pollToDirection(pe.portev_events)); + } + } else if (eh.isDeleted()) { + //Remove the handle from the poller + int rc = ::port_dissociate(impl->portId, PORT_SOURCE_FD, + (uintptr_t) eh.fd); + if (rc == -1 && errno != EBADFD) { + QPID_POSIX_CHECK(rc); } } } + + if (timeout == TIME_INFINITE) { + continue; + } + if (rc == 0 && now() > targetTimeout) { + PollerHandleDeletionManager.markAllUnusedInThisThread(); + return Event(0, TIMEOUT); + } } while (true); } diff --git a/cpp/src/qpid/sys/solaris/SystemInfo.cpp b/cpp/src/qpid/sys/solaris/SystemInfo.cpp new file mode 100755 index 0000000000..0075a89021 --- /dev/null +++ b/cpp/src/qpid/sys/solaris/SystemInfo.cpp @@ -0,0 +1,123 @@ +/* + * 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/SystemInfo.h" + +#define BSD_COMP +#include <sys/ioctl.h> +#include <netdb.h> +#undef BDS_COMP + + +#include <unistd.h> +#include <net/if.h> +#include <sys/types.h> +#include <sys/utsname.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <errno.h> +#include <limits.h> +#include <procfs.h> +#include <fcntl.h> +#include <sys/types.h> + +using namespace std; + +namespace qpid { +namespace sys { + +long SystemInfo::concurrency() { + return sysconf(_SC_NPROCESSORS_ONLN); +} + +bool SystemInfo::getLocalHostname(TcpAddress &address) { + char name[MAXHOSTNAMELEN]; + if (::gethostname(name, sizeof(name)) != 0) + return false; + address.host = name; + return true; +} + +static const string LOCALHOST("127.0.0.1"); + +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_in *sin = (struct sockaddr_in *) &ifr.lifr_addr; + std::string addr(inet_ntoa(sin->sin_addr)); + if (addr != LOCALHOST) + addrList.push_back(TcpAddress(addr, port)); + } + if (addrList.empty()) { + addrList.push_back(TcpAddress(LOCALHOST, port)); + } + close (s); +} + +void SystemInfo::getSystemId(std::string &osName, + std::string &nodeName, + std::string &release, + std::string &version, + std::string &machine) { + struct utsname _uname; + if (uname (&_uname) == 0) { + osName = _uname.sysname; + nodeName = _uname.nodename; + release = _uname.release; + version = _uname.version; + machine = _uname.machine; + } +} + +uint32_t SystemInfo::getProcessId() +{ + return (uint32_t) ::getpid(); +} + +uint32_t SystemInfo::getParentProcessId() +{ + return (uint32_t) ::getppid(); +} + +string SystemInfo::getProcessName() +{ + psinfo processInfo; + char procfile[PATH_MAX]; + int fd; + string value; + + snprintf(procfile, PATH_MAX, "/proc/%d/psinfo", getProcessId()); + if ((fd = open(procfile, O_RDONLY)) >= 0) { + if (read(fd, (void *) &processInfo, sizeof(processInfo)) == sizeof(processInfo)) { + value = processInfo.pr_fname; + } + } + return value; +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/ssl/SslHandler.cpp b/cpp/src/qpid/sys/ssl/SslHandler.cpp new file mode 100644 index 0000000000..3469f88c0f --- /dev/null +++ b/cpp/src/qpid/sys/ssl/SslHandler.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 "qpid/sys/ssl/SslHandler.h" + +#include "qpid/sys/ssl/SslIo.h" +#include "qpid/sys/ssl/SslSocket.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 { + + +// Buffer definition +struct Buff : public SslIO::BufferBase { + Buff() : + SslIO::BufferBase(new char[65536], 65536) + {} + ~Buff() + { delete [] bytes;} +}; + +SslHandler::SslHandler(std::string id, ConnectionCodec::Factory* f) : + identifier(id), + aio(0), + factory(f), + codec(0), + readError(false), + isClient(false) +{} + +SslHandler::~SslHandler() { + if (codec) + codec->closed(); + delete codec; +} + +void SslHandler::init(SslIO* a, int numBuffs) { + aio = a; + + // Give connection some buffers to use + for (int i = 0; i < numBuffs; i++) { + aio->queueReadBuffer(new Buff); + } +} + +void SslHandler::write(const framing::ProtocolInitiation& data) +{ + QPID_LOG(debug, "SENT [" << identifier << "] INIT(" << data << ")"); + SslIO::BufferBase* buff = aio->getQueuedBuffer(); + if (!buff) + buff = new Buff; + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void SslHandler::abort() { + // TODO: can't implement currently as underlying functionality not implemented + // 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)) { + decoded = in.getPosition(); + QPID_LOG(debug, "RECV [" << identifier << "] INIT(" << protocolInit << ")"); + try { + codec = factory->create(protocolInit.getVersion(), *this, identifier, aio->getKeyLen()); + 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, aio->getKeyLen()); + write(framing::ProtocolInitiation(codec->getVersion())); + return; + } + if (codec == 0) return; + if (codec->canEncode()) { + // Try and get a queued buffer if not then construct new one + SslIO::BufferBase* buff = aio->getQueuedBuffer(); + if (!buff) buff = new Buff; + size_t encoded=codec->encode(buff->bytes, buff->byteCount); + buff->dataCount = encoded; + aio->queueWrite(buff); + } + if (codec->isClosed()) + aio->queueWriteClose(); +} + + +}}} // namespace qpid::sys::ssl diff --git a/cpp/src/qpid/sys/ssl/SslHandler.h b/cpp/src/qpid/sys/ssl/SslHandler.h new file mode 100644 index 0000000000..8f6b8e732a --- /dev/null +++ b/cpp/src/qpid/sys/ssl/SslHandler.h @@ -0,0 +1,76 @@ +#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" + +namespace qpid { + +namespace framing { + class ProtocolInitiation; +} + +namespace sys { +namespace ssl { + +class SslIO; +class SslIOBufferBase; +class SslSocket; + +class SslHandler : public OutputControl { + std::string identifier; + SslIO* aio; + ConnectionCodec::Factory* factory; + ConnectionCodec* codec; + bool readError; + bool isClient; + + void write(const framing::ProtocolInitiation&); + + public: + SslHandler(std::string id, ConnectionCodec::Factory* f); + ~SslHandler(); + void init(SslIO* a, int numBuffs); + + 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 new file mode 100644 index 0000000000..c149d6ea74 --- /dev/null +++ b/cpp/src/qpid/sys/ssl/SslIo.cpp @@ -0,0 +1,439 @@ +/* + * + * 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/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> + +using namespace qpid::sys; +using namespace qpid::sys::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 threadMaxRead = 0; +__thread int threadReadCount = 0; +__thread int threadWriteTotal = 0; +__thread int threadWriteCount = 0; +__thread int64_t threadMaxReadTimeNs = 2 * 1000000; // start at 2ms +} + +/* + * Asynch Acceptor + */ + +SslAcceptor::SslAcceptor(const SslSocket& s, Callback callback) : + acceptedCallback(callback), + handle(s, boost::bind(&SslAcceptor::readable, this, _1), 0, 0), + socket(s) { + + s.setNonblocking(); + ignoreSigpipe(); +} + +SslAcceptor::~SslAcceptor() +{ + handle.stopWatch(); +} + +void SslAcceptor::start(Poller::shared_ptr poller) { + handle.startWatch(poller); +} + +/* + * We keep on accepting as long as there is something to accept + */ +void SslAcceptor::readable(DispatchHandle& h) { + SslSocket* 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(); +} + +/* + * Asynch Connector + */ + +SslConnector::SslConnector(const SslSocket& s, + Poller::shared_ptr poller, + std::string hostname, + uint16_t 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(); +} + +struct deleter +{ + template <typename T> + void operator()(T *ptr){ delete ptr;} +}; + +SslIO::~SslIO() { + std::for_each( bufferQueue.begin(), bufferQueue.end(), deleter()); + std::for_each( writeQueue.begin(), writeQueue.end(), deleter()); +} + +void SslIO::queueForDeletion() { + DispatchHandle::doDelete(); +} + +void SslIO::start(Poller::shared_ptr poller) { + DispatchHandle::startWatch(poller); +} + +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(); +} + +/** 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) { + int readTotal = 0; + 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; + readTotal += 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()) > threadMaxReadTimeNs) { + 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: " << qpid::sys::strError(rc) << "(" << rc << ")" ); + 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; + threadMaxRead = std::max(threadMaxRead, readTotal); + return; +} + +/* + * We carry on writing whilst we have data to write and we can write + */ +void SslIO::writeable(DispatchHandle& h) { + int writeTotal = 0; + 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; + writeTotal += 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); + + // If we've already written more than the max for reading then stop + // (this is to stop writes dominating reads) + if (writeTotal > threadMaxRead) + 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_POSIX_CHECK(rc); + } + } + } 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); + } +} + +int SslIO::getKeyLen() {return socket.getKeyLen();} diff --git a/cpp/src/qpid/sys/ssl/SslIo.h b/cpp/src/qpid/sys/ssl/SslIo.h new file mode 100644 index 0000000000..3162abac40 --- /dev/null +++ b/cpp/src/qpid/sys/ssl/SslIo.h @@ -0,0 +1,171 @@ +#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 <boost/function.hpp> +#include <deque> + +namespace qpid { +namespace sys { +namespace ssl { + +class SslSocket; + +/* + * Asynchronous ssl acceptor: accepts connections then does a callback + * with the accepted fd + */ +class SslAcceptor { +public: + typedef boost::function1<void, const SslSocket&> Callback; + +private: + Callback acceptedCallback; + qpid::sys::DispatchHandle handle; + const SslSocket& socket; + +public: + SslAcceptor(const SslSocket& s, Callback callback); + ~SslAcceptor(); + 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, + uint16_t port, + ConnectedCallback connCb, + FailedCallback failCb = 0); + +private: + void connComplete(DispatchHandle& handle); + void failure(int, std::string); +}; + +struct SslIOBufferBase { + char* const bytes; + const 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; + + +private: + ReadCallback readCallback; + EofCallback eofCallback; + DisconnectCallback disCallback; + ClosedCallback closedCallback; + BuffersEmptyCallback emptyCallback; + IdleCallback idleCallback; + const SslSocket& socket; + std::deque<BufferBase*> bufferQueue; + std::deque<BufferBase*> writeQueue; + 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: + SslIO(const SslSocket& s, + ReadCallback rCb, EofCallback eofCb, DisconnectCallback disCb, + ClosedCallback cCb = 0, BuffersEmptyCallback eCb = 0, IdleCallback iCb = 0); + void queueForDeletion(); + + void start(qpid::sys::Poller::shared_ptr poller); + void queueReadBuffer(BufferBase* buff); + void unread(BufferBase* buff); + void queueWrite(BufferBase* buff); + void notifyPendingWrite(); + void queueWriteClose(); + bool writeQueueEmpty() { return writeQueue.empty(); } + BufferBase* getQueuedBuffer(); + + int getKeyLen(); + +private: + ~SslIO(); + void readable(qpid::sys::DispatchHandle& handle); + void writeable(qpid::sys::DispatchHandle& handle); + void disconnected(qpid::sys::DispatchHandle& handle); + 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 new file mode 100644 index 0000000000..aa8cf127d7 --- /dev/null +++ b/cpp/src/qpid/sys/ssl/SslSocket.cpp @@ -0,0 +1,297 @@ +/* + * + * 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/SslSocket.h" +#include "qpid/sys/ssl/check.h" +#include "qpid/sys/ssl/util.h" +#include "qpid/Exception.h" +#include "qpid/sys/posix/check.h" +#include "qpid/sys/posix/PrivatePosix.h" + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <netdb.h> +#include <cstdlib> +#include <string.h> +#include <iostream> + +#include <private/pprio.h> +#include <nss.h> +#include <pk11pub.h> +#include <ssl.h> +#include <key.h> + +#include <boost/format.hpp> + +namespace qpid { +namespace sys { +namespace ssl { + +namespace { +std::string getName(int fd, bool local, bool includeService = false) +{ + ::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]; + char dispName[NI_MAXHOST]; + if (includeService) { + if (int rc=::getnameinfo((::sockaddr*)&name, namelen, dispName, sizeof(dispName), + servName, sizeof(servName), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + throw QPID_POSIX_ERROR(rc); + return std::string(dispName) + ":" + std::string(servName); + + } else { + if (int rc=::getnameinfo((::sockaddr*)&name, namelen, dispName, sizeof(dispName), 0, 0, NI_NUMERICHOST) != 0) + throw QPID_POSIX_ERROR(rc); + return dispName; + } +} + +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; +} + +} + +SslSocket::SslSocket() : IOHandle(new IOHandlePrivate()), socket(0), 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)); +} + +/** + * This form of the constructor is used with the server-side sockets + * returned from accept. Because we use posix accept rather than + * PR_Accept, we have to reset the handshake. + */ +SslSocket::SslSocket(IOHandlePrivate* ioph, PRFileDesc* model) : IOHandle(ioph), socket(0), prototype(0) +{ + socket = SSL_ImportFD(model, PR_ImportTCPSocket(impl->fd)); + NSS_CHECK(SSL_ResetHandshake(socket, true)); +} + +void SslSocket::setNonblocking() const +{ + PRSocketOptionData option; + option.option = PR_SockOpt_Nonblocking; + option.value.non_blocking = true; + PR_SetSocketOption(socket, &option); +} + +void SslSocket::connect(const std::string& host, uint16_t port) const +{ + std::stringstream namestream; + namestream << host << ":" << port; + connectname = namestream.str(); + + void* arg = SslOptions::global.certName.empty() ? 0 : 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, 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)); +} + +void SslSocket::close() const +{ + if (impl->fd > 0) { + PR_Close(socket); + impl->fd = -1; + } +} + +int SslSocket::listen(uint16_t port, int backlog, const std::string& certName, bool clientAuth) 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 << "'")); + 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); +} + +SslSocket* SslSocket::accept() const +{ + int afd = ::accept(impl->fd, 0, 0); + if ( afd >= 0) { + return new SslSocket(new IOHandlePrivate(afd), prototype); + } else if (errno == EAGAIN) { + return 0; + } else { + throw QPID_POSIX_ERROR(errno); + } +} + +int SslSocket::read(void *buf, size_t count) const +{ + return PR_Read(socket, buf, count); +} + +int SslSocket::write(const void *buf, size_t count) const +{ + return PR_Write(socket, buf, count); +} + +std::string SslSocket::getSockname() const +{ + return getName(impl->fd, true); +} + +std::string SslSocket::getPeername() const +{ + return getName(impl->fd, false); +} + +std::string SslSocket::getPeerAddress() const +{ + if (!connectname.empty()) + return connectname; + return getName(impl->fd, false, true); +} + +std::string SslSocket::getLocalAddress() const +{ + return getName(impl->fd, true, true); +} + +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()); +} + +int SslSocket::getError() const +{ + int result; + socklen_t rSize = sizeof (result); + + if (::getsockopt(impl->fd, SOL_SOCKET, SO_ERROR, &result, &rSize) < 0) + throw QPID_POSIX_ERROR(errno); + + return result; +} + +void SslSocket::setTcpNoDelay(bool nodelay) const +{ + if (nodelay) { + PRSocketOptionData option; + option.option = PR_SockOpt_NoDelay; + option.value.no_delay = true; + PR_SetSocketOption(socket, &option); + } +} + + +/** get the bit length of the current cipher's key */ +int SslSocket::getKeyLen() const +{ + int enabled = 0; + int keySize = 0; + SECStatus rc; + + rc = SSL_SecurityStatus( socket, + &enabled, + NULL, + NULL, + &keySize, + NULL, NULL ); + if (rc == SECSuccess && enabled) { + return keySize; + } + return 0; +} + +}}} // namespace qpid::sys::ssl diff --git a/cpp/src/qpid/sys/ssl/SslSocket.h b/cpp/src/qpid/sys/ssl/SslSocket.h new file mode 100644 index 0000000000..f1f05e7a98 --- /dev/null +++ b/cpp/src/qpid/sys/ssl/SslSocket.h @@ -0,0 +1,119 @@ +#ifndef _sys_ssl_Socket_h +#define _sys_ssl_Socket_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/IOHandle.h" +#include <nspr.h> + +#include <string> + +struct sockaddr; + +namespace qpid { +namespace sys { + +class Duration; + +namespace ssl { + +class SslSocket : public qpid::sys::IOHandle +{ +public: + /** Create a socket wrapper for descriptor. */ + SslSocket(); + + /** Set socket non blocking */ + void setNonblocking() const; + + /** Set tcp-nodelay */ + void setTcpNoDelay(bool nodelay) const; + + void connect(const std::string& host, uint16_t port) 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; + + /** + * Accept a connection from a socket that is already listening + * and has an incoming connection + */ + SslSocket* 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; + + /** Returns the "socket name" ie the address bound to + * the near end of the socket + */ + std::string getSockname() const; + + /** Returns the "peer name" ie the address bound to + * the remote end of the socket + */ + std::string getPeername() const; + + /** + * Returns an address (host and port) for the remote end of the + * socket + */ + std::string getPeerAddress() const; + /** + * Returns an address (host and port) for the local end of the + * socket + */ + std::string getLocalAddress() const; + + uint16_t getLocalPort() const; + uint16_t getRemotePort() const; + + /** + * Returns the error code stored in the socket. This may be used + * to determine the result of a non-blocking connect. + */ + int getError() const; + + int getKeyLen() const; + +private: + mutable std::string connectname; + mutable PRFileDesc* socket; + /** + * 'model' socket, with configuration to use when importing + * accepted sockets for use as ssl sockets. Set on listen(), used + * in accept to pass through to newly created socket instances. + */ + mutable PRFileDesc* prototype; + + SslSocket(IOHandlePrivate* ioph, PRFileDesc* model); +}; + +}}} +#endif /*!_sys_ssl_Socket_h*/ diff --git a/cpp/src/qpid/sys/ssl/check.cpp b/cpp/src/qpid/sys/ssl/check.cpp new file mode 100644 index 0000000000..c5e6005e03 --- /dev/null +++ b/cpp/src/qpid/sys/ssl/check.cpp @@ -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 "qpid/sys/ssl/check.h" +#include <secerr.h> +#include <sslerr.h> +#include <boost/format.hpp> + +using boost::format; +using boost::str; + +namespace qpid { +namespace sys { +namespace ssl { + +const std::string SSL_ERROR_BAD_CERT_DOMAIN_STR = + "Unable to communicate securely with peer: requested domain name does not match the server's certificate."; +const std::string SSL_ERROR_BAD_CERT_ALERT_STR = "SSL peer cannot verify your certificate."; +const std::string SEC_ERROR_BAD_DATABASE_STR = "Security library: bad database."; +const std::string SSL_ERROR_NO_CERTIFICATE_STR = "Unable to find the certificate or key necessary for authentication."; +const std::string SSL_ERROR_UNKNOWN = "Unknown NSS error code."; + +ErrorString::ErrorString() : code(PR_GetError()), buffer(new char[PR_GetErrorTextLength()]), used(PR_GetErrorText(buffer)) {} + +ErrorString::~ErrorString() +{ + delete[] buffer; +} + +std::string ErrorString::getString() const +{ + std::string msg = std::string(buffer, used); + if (!used) { + //seems most of the NSPR/NSS errors don't have text set for + //them, add a few specific ones in here. (TODO: more complete + //list?): + switch (code) { + case SSL_ERROR_BAD_CERT_DOMAIN: msg = SSL_ERROR_BAD_CERT_DOMAIN_STR; break; + case SSL_ERROR_BAD_CERT_ALERT: msg = SSL_ERROR_BAD_CERT_ALERT_STR; break; + case SEC_ERROR_BAD_DATABASE: msg = SEC_ERROR_BAD_DATABASE_STR; break; + case SSL_ERROR_NO_CERTIFICATE: msg = SSL_ERROR_NO_CERTIFICATE_STR; break; + default: msg = SSL_ERROR_UNKNOWN; break; + } + } + return str(format("%1% [%2%]") % msg % code); +} + +std::ostream& operator<<(std::ostream& out, const ErrorString& err) +{ + out << err.getString(); + return out; +} + + +}}} // namespace qpid::sys::ssl diff --git a/cpp/src/qpid/sys/ssl/check.h b/cpp/src/qpid/sys/ssl/check.h new file mode 100644 index 0000000000..984c338a18 --- /dev/null +++ b/cpp/src/qpid/sys/ssl/check.h @@ -0,0 +1,53 @@ +#ifndef QPID_SYS_SSL_CHECK_H +#define QPID_SYS_SSL_CHECK_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 <iostream> +#include <string> +#include <nspr.h> +#include <nss.h> + +namespace qpid { +namespace sys { +namespace ssl { + +class ErrorString +{ + public: + ErrorString(); + ~ErrorString(); + std::string getString() const; + private: + const int code; + char* const buffer; + const size_t used; +}; + +std::ostream& operator<<(std::ostream& out, const ErrorString& err); + +}}} // namespace qpid::sys::ssl + + +#define NSS_CHECK(value) if (value != SECSuccess) { throw Exception(QPID_MSG("Failed: " << qpid::sys::ssl::ErrorString())); } +#define PR_CHECK(value) if (value != PR_SUCCESS) { throw Exception(QPID_MSG("Failed: " << qpid::sys::ssl::ErrorString())); } + +#endif /*!QPID_SYS_SSL_CHECK_H*/ diff --git a/cpp/src/qpid/sys/ssl/util.cpp b/cpp/src/qpid/sys/ssl/util.cpp new file mode 100644 index 0000000000..53326e2f55 --- /dev/null +++ b/cpp/src/qpid/sys/ssl/util.cpp @@ -0,0 +1,118 @@ +/* + * + * 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/util.h" +#include "qpid/sys/ssl/check.h" +#include "qpid/Exception.h" +#include "qpid/sys/SystemInfo.h" + +#include <unistd.h> +#include <nspr.h> +#include <nss.h> +#include <pk11pub.h> +#include <ssl.h> + +#include <iostream> +#include <fstream> +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> + +namespace qpid { +namespace sys { +namespace ssl { + +std::string defaultCertName() +{ + TcpAddress address; + if (SystemInfo::getLocalHostname(address)) { + return address.host; + } else { + return "localhost"; + } +} + +SslOptions::SslOptions() : qpid::Options("SSL Settings"), + certName(defaultCertName()), + exportPolicy(false) +{ + addOptions() + ("ssl-use-export-policy", optValue(exportPolicy), "Use NSS export policy") + ("ssl-cert-password-file", optValue(certPasswordFile, "PATH"), "File containing password to use for accessing certificate database") + ("ssl-cert-db", optValue(certDbPath, "PATH"), "Path to directory containing certificate database") + ("ssl-cert-name", optValue(certName, "NAME"), "Name of the certificate to use"); +} + +SslOptions& SslOptions::operator=(const SslOptions& o) +{ + certDbPath = o.certDbPath; + certName = o.certName; + certPasswordFile = o.certPasswordFile; + exportPolicy = o.exportPolicy; + return *this; +} + +char* promptForPassword(PK11SlotInfo*, PRBool retry, void*) +{ + if (retry) return 0; + //TODO: something else? + return PL_strdup(getpass("Please enter the password for accessing the certificate database:")); +} + +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()); + } +} + +void initNSS(const SslOptions& options, bool server) +{ + SslOptions::global = options; + if (options.certPasswordFile.empty()) { + PK11_SetPasswordFunc(promptForPassword); + } else { + PK11_SetPasswordFunc(readPasswordFromFile); + } + NSS_CHECK(NSS_Init(options.certDbPath.c_str())); + if (options.exportPolicy) { + NSS_CHECK(NSS_SetExportPolicy()); + } else { + NSS_CHECK(NSS_SetDomesticPolicy()); + } + if (server) { + //use defaults for all args, TODO: may want to make this configurable + SSL_ConfigServerSessionIDCache(0, 0, 0, 0); + } +} + +void shutdownNSS() +{ + NSS_Shutdown(); +} + +}}} // namespace qpid::sys::ssl diff --git a/cpp/src/qpid/sys/ssl/util.h b/cpp/src/qpid/sys/ssl/util.h new file mode 100644 index 0000000000..f34adab7be --- /dev/null +++ b/cpp/src/qpid/sys/ssl/util.h @@ -0,0 +1,50 @@ +#ifndef QPID_SYS_SSL_UTIL_H +#define QPID_SYS_SSL_UTIL_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/Options.h" +#include <string> + +namespace qpid { +namespace sys { +namespace ssl { + +struct SslOptions : qpid::Options +{ + static SslOptions global; + + std::string certDbPath; + std::string certName; + std::string certPasswordFile; + bool exportPolicy; + + SslOptions(); + SslOptions& operator=(const SslOptions&); +}; + +void initNSS(const SslOptions& options, bool server = false); +void shutdownNSS(); + +}}} // namespace qpid::sys::ssl + +#endif /*!QPID_SYS_SSL_UTIL_H*/ diff --git a/cpp/src/qpid/broker/BrokerSingleton.cpp b/cpp/src/qpid/sys/uuid.h index 5ba8c9d1e1..804ab34463 100644 --- a/cpp/src/qpid/broker/BrokerSingleton.cpp +++ b/cpp/src/qpid/sys/uuid.h @@ -1,3 +1,6 @@ +#ifndef _sys_uuid_h +#define _sys_uuid_h + /* * * Copyright (c) 2006 The Apache Software Foundation @@ -16,21 +19,10 @@ * */ -#include "BrokerSingleton.h" - -namespace qpid { -namespace broker { - -BrokerSingleton::BrokerSingleton() { - if (broker.get() == 0) - broker = Broker::create(); - boost::intrusive_ptr<Broker>::operator=(broker); -} - -BrokerSingleton::~BrokerSingleton() { - broker->shutdown(); -} - -boost::intrusive_ptr<Broker> BrokerSingleton::broker; +#ifdef _WIN32 +# include "qpid/sys/windows/uuid.h" +#else +# include <uuid/uuid.h> +#endif /* _WIN32 */ -}} // namespace qpid::broker +#endif /* _sys_uuid_h */ diff --git a/cpp/src/qpid/sys/windows/AsynchIO.cpp b/cpp/src/qpid/sys/windows/AsynchIO.cpp new file mode 100644 index 0000000000..e4174ee445 --- /dev/null +++ b/cpp/src/qpid/sys/windows/AsynchIO.cpp @@ -0,0 +1,751 @@ +/* + * + * 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/AsynchIoResult.h" +#include "qpid/sys/windows/IoHandlePrivate.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Time.h" +#include "qpid/log/Statement.h" + +#include "qpid/sys/windows/check.h" + +#include <boost/thread/once.hpp> + +#include <queue> +#include <winsock2.h> +#include <mswsock.h> +#include <windows.h> + +#include <boost/bind.hpp> + +namespace { + + typedef qpid::sys::ScopedLock<qpid::sys::Mutex> QLock; + +/* + * The function pointers for AcceptEx and ConnectEx need to be looked up + * at run time. Make sure this is done only once. + */ +boost::once_flag lookUpAcceptExOnce = BOOST_ONCE_INIT; +LPFN_ACCEPTEX fnAcceptEx = 0; +typedef void (*lookUpFunc)(const qpid::sys::Socket &); + +void lookUpAcceptEx() { + SOCKET h = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + GUID guidAcceptEx = WSAID_ACCEPTEX; + DWORD dwBytes = 0; + WSAIoctl(h, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &guidAcceptEx, + sizeof(guidAcceptEx), + &fnAcceptEx, + sizeof(fnAcceptEx), + &dwBytes, + NULL, + NULL); + closesocket(h); + if (fnAcceptEx == 0) + throw qpid::Exception(QPID_MSG("Failed to look up AcceptEx")); +} + +} + +namespace qpid { +namespace sys { +namespace windows { + +/* + * Asynch Acceptor + * + */ +class AsynchAcceptor : public qpid::sys::AsynchAcceptor { + + friend class AsynchAcceptResult; + +public: + AsynchAcceptor(const Socket& s, AsynchAcceptor::Callback callback); + ~AsynchAcceptor(); + void start(Poller::shared_ptr poller); + +private: + void restart(void); + + AsynchAcceptor::Callback acceptedCallback; + const Socket& socket; +}; + +AsynchAcceptor::AsynchAcceptor(const Socket& s, Callback callback) + : acceptedCallback(callback), + socket(s) { + + s.setNonblocking(); +#if (BOOST_VERSION >= 103500) /* boost 1.35 or later reversed the args */ + boost::call_once(lookUpAcceptExOnce, lookUpAcceptEx); +#else + boost::call_once(lookUpAcceptEx, lookUpAcceptExOnce); +#endif +} + +AsynchAcceptor::~AsynchAcceptor() +{ + socket.close(); +} + +void AsynchAcceptor::start(Poller::shared_ptr poller) { + poller->monitorHandle(PollerHandle(socket), Poller::INPUT); + restart (); +} + +void AsynchAcceptor::restart(void) { + DWORD bytesReceived = 0; // Not used, needed for AcceptEx API + AsynchAcceptResult *result = new AsynchAcceptResult(acceptedCallback, + this, + toSocketHandle(socket)); + BOOL status; + status = ::fnAcceptEx(toSocketHandle(socket), + toSocketHandle(*result->newSocket), + result->addressBuffer, + 0, + AsynchAcceptResult::SOCKADDRMAXLEN, + AsynchAcceptResult::SOCKADDRMAXLEN, + &bytesReceived, + result->overlapped()); + QPID_WINDOWS_CHECK_ASYNC_START(status); +} + + +AsynchAcceptResult::AsynchAcceptResult(AsynchAcceptor::Callback cb, + AsynchAcceptor *acceptor, + SOCKET listener) + : callback(cb), acceptor(acceptor), listener(listener) { + newSocket.reset (new Socket()); +} + +void AsynchAcceptResult::success(size_t /*bytesTransferred*/) { + ::setsockopt (toSocketHandle(*newSocket), + SOL_SOCKET, + SO_UPDATE_ACCEPT_CONTEXT, + (char*)&listener, + sizeof (listener)); + callback(*(newSocket.release())); + acceptor->restart (); + delete this; +} + +void AsynchAcceptResult::failure(int status) { + //if (status != WSA_OPERATION_ABORTED) + // Can there be anything else? ; + delete this; +} + +/* + * AsynchConnector does synchronous connects for now... to do asynch the + * IocpPoller will need some extension to register an event handle as a + * CONNECT-type "direction", the connect completion/result will need an + * event handle to associate with the connecting handle. But there's no + * time for that right now... + */ +class AsynchConnector : public qpid::sys::AsynchConnector { +private: + ConnectedCallback connCallback; + FailedCallback failCallback; + const Socket& socket; + +public: + AsynchConnector(const Socket& socket, + Poller::shared_ptr poller, + std::string hostname, + uint16_t port, + ConnectedCallback connCb, + FailedCallback failCb = 0); +}; + +AsynchConnector::AsynchConnector(const Socket& sock, + Poller::shared_ptr poller, + std::string hostname, + uint16_t port, + ConnectedCallback connCb, + FailedCallback failCb) + : connCallback(connCb), failCallback(failCb), socket(sock) { + try { + socket.connect(hostname, port); + socket.setNonblocking(); + connCallback(socket); + } catch(std::exception& e) { + if (failCallback) + failCallback(socket, -1, std::string(e.what())); + socket.close(); + delete &socket; + } +} + +} // namespace windows + +AsynchAcceptor* AsynchAcceptor::create(const Socket& s, + Callback callback) +{ + return new windows::AsynchAcceptor(s, callback); +} + +AsynchConnector* qpid::sys::AsynchConnector::create(const Socket& s, + Poller::shared_ptr poller, + std::string hostname, + uint16_t port, + ConnectedCallback connCb, + FailedCallback failCb) +{ + return new windows::AsynchConnector(s, + poller, + hostname, + port, + connCb, + failCb); +} + + +/* + * Asynch reader/writer + */ + +namespace windows { + +class AsynchIO : public qpid::sys::AsynchIO { +public: + AsynchIO(const Socket& s, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb = 0, + BuffersEmptyCallback eCb = 0, + IdleCallback iCb = 0); + ~AsynchIO(); + + // Methods inherited from qpid::sys::AsynchIO + + /** + * Notify the object is should delete itself as soon as possible. + */ + virtual void queueForDeletion(); + + /// Take any actions needed to prepare for working with the poller. + virtual void start(Poller::shared_ptr poller); + virtual void queueReadBuffer(BufferBase* buff); + virtual void unread(BufferBase* buff); + virtual void queueWrite(BufferBase* buff); + virtual void notifyPendingWrite(); + virtual void queueWriteClose(); + virtual bool writeQueueEmpty(); + virtual void startReading(); + virtual void stopReading(); + virtual void requestCallback(RequestCallback); + + /** + * getQueuedBuffer returns a buffer from the buffer queue, if one is + * available. + * + * @retval Pointer to BufferBase buffer; 0 if none is available. + */ + virtual BufferBase* getQueuedBuffer(); + +private: + ReadCallback readCallback; + EofCallback eofCallback; + DisconnectCallback disCallback; + ClosedCallback closedCallback; + BuffersEmptyCallback emptyCallback; + IdleCallback idleCallback; + const Socket& socket; + Poller::shared_ptr poller; + + std::deque<BufferBase*> bufferQueue; + std::deque<BufferBase*> writeQueue; + /* The MSVC-supplied deque is not thread-safe; keep locks to serialize + * access to the buffer queue and write queue. + */ + Mutex bufferQueueLock; + + // Number of outstanding I/O operations. + volatile LONG opsInProgress; + // Is there a write in progress? + volatile bool writeInProgress; + // Deletion requested, but there are callbacks in progress. + volatile bool queuedDelete; + // Socket close requested, but there are operations in progress. + volatile bool queuedClose; + +private: + // Dispatch events that have completed. + void notifyEof(void); + void notifyDisconnect(void); + void notifyClosed(void); + void notifyBuffersEmpty(void); + void notifyIdle(void); + + /** + * Initiate a write of the specified buffer. There's no callback for + * write completion to the AsynchIO object. + */ + void startWrite(AsynchIO::BufferBase* buff); + + void close(void); + + /** + * readComplete is called when a read request is complete. + * + * @param result Results of the operation. + */ + void readComplete(AsynchReadResult *result); + + /** + * writeComplete is called when a write request is complete. + * + * @param result Results of the operation. + */ + void writeComplete(AsynchWriteResult *result); + + /** + * Queue of completions to run. This queue enforces the requirement + * from upper layers that only one thread at a time is allowed to act + * on any given connection. Once a thread is busy processing a completion + * on this object, other threads that dispatch completions queue the + * completions here for the in-progress thread to handle when done. + * Thus, any threads can dispatch a completion from the IocpPoller, but + * this class ensures that actual processing at the connection level is + * only on one thread at a time. + */ + std::queue<AsynchIoResult *> completionQueue; + volatile bool working; + Mutex completionLock; + + /** + * Called when there's a completion to process. + */ + void completion(AsynchIoResult *result); +}; + +// This is used to encapsulate pure callbacks into a handle +class CallbackHandle : public IOHandle { +public: + CallbackHandle(AsynchIoResult::Completer completeCb, + AsynchIO::RequestCallback reqCb = 0) : + IOHandle(new IOHandlePrivate (INVALID_SOCKET, completeCb, reqCb)) + {} +}; + +AsynchIO::AsynchIO(const Socket& s, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb, + BuffersEmptyCallback eCb, + IdleCallback iCb) : + + readCallback(rCb), + eofCallback(eofCb), + disCallback(disCb), + closedCallback(cCb), + emptyCallback(eCb), + idleCallback(iCb), + socket(s), + opsInProgress(0), + writeInProgress(false), + queuedDelete(false), + queuedClose(false), + working(false) { +} + +struct deleter +{ + template <typename T> + void operator()(T *ptr){ delete ptr;} +}; + +AsynchIO::~AsynchIO() { + std::for_each( bufferQueue.begin(), bufferQueue.end(), deleter()); + std::for_each( writeQueue.begin(), writeQueue.end(), deleter()); +} + +void AsynchIO::queueForDeletion() { + queuedDelete = true; + if (opsInProgress > 0) { + QPID_LOG(info, "Delete AsynchIO queued; ops in progress"); + // AsynchIOHandler calls this then deletes itself; don't do any more + // callbacks. + readCallback = 0; + eofCallback = 0; + disCallback = 0; + closedCallback = 0; + emptyCallback = 0; + idleCallback = 0; + } + else { + delete this; + } +} + +void AsynchIO::start(Poller::shared_ptr poller0) { + poller = poller0; + poller->monitorHandle(PollerHandle(socket), Poller::INPUT); + if (writeQueue.size() > 0) // Already have data queued for write + notifyPendingWrite(); + startReading(); +} + +void AsynchIO::queueReadBuffer(AsynchIO::BufferBase* buff) { + assert(buff); + buff->dataStart = 0; + buff->dataCount = 0; + QLock l(bufferQueueLock); + bufferQueue.push_back(buff); +} + +void AsynchIO::unread(AsynchIO::BufferBase* buff) { + assert(buff); + if (buff->dataStart != 0) { + memmove(buff->bytes, buff->bytes+buff->dataStart, buff->dataCount); + buff->dataStart = 0; + } + QLock l(bufferQueueLock); + bufferQueue.push_front(buff); +} + +void AsynchIO::queueWrite(AsynchIO::BufferBase* buff) { + assert(buff); + QLock l(bufferQueueLock); + writeQueue.push_back(buff); + if (!writeInProgress) + notifyPendingWrite(); +} + +void AsynchIO::notifyPendingWrite() { + // This method is generally called from a processing thread; transfer + // work on this to an I/O thread. Much of the upper layer code assumes + // that all I/O-related things happen in an I/O thread. + if (poller == 0) // Not really going yet... + return; + + InterlockedIncrement(&opsInProgress); + PollerHandle ph(CallbackHandle(boost::bind(&AsynchIO::completion, this, _1))); + poller->monitorHandle(ph, Poller::OUTPUT); +} + +void AsynchIO::queueWriteClose() { + queuedClose = true; + if (!writeInProgress) + notifyPendingWrite(); +} + +bool AsynchIO::writeQueueEmpty() { + QLock l(bufferQueueLock); + return writeQueue.size() == 0; +} + +/* + * Initiate a read operation. AsynchIO::readComplete() will be + * called when the read is complete and data is available. + */ +void AsynchIO::startReading() { + if (queuedDelete) + return; + + // (Try to) get a buffer; look on the front since there may be an + // "unread" one there with data remaining from last time. + AsynchIO::BufferBase *buff = 0; + { + QLock l(bufferQueueLock); + + if (!bufferQueue.empty()) { + buff = bufferQueue.front(); + assert(buff); + bufferQueue.pop_front(); + } + } + if (buff != 0) { + int readCount = buff->byteCount - buff->dataCount; + AsynchReadResult *result = + new AsynchReadResult(boost::bind(&AsynchIO::completion, this, _1), + buff, + readCount); + DWORD bytesReceived = 0, flags = 0; + InterlockedIncrement(&opsInProgress); + int status = WSARecv(toSocketHandle(socket), + const_cast<LPWSABUF>(result->getWSABUF()), 1, + &bytesReceived, + &flags, + result->overlapped(), + 0); + if (status != 0) { + int error = WSAGetLastError(); + if (error != WSA_IO_PENDING) { + result->failure(error); + result = 0; // result is invalid here + return; + } + } + // On status 0 or WSA_IO_PENDING, completion will handle the rest. + } + else { + notifyBuffersEmpty(); + } + 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 + // work on this to an I/O thread. Much of the upper layer code assumes + // that all I/O-related things happen in an I/O thread. + if (poller == 0) // Not really going yet... + return; + + InterlockedIncrement(&opsInProgress); + PollerHandle ph(CallbackHandle( + boost::bind(&AsynchIO::completion, this, _1), + callback)); + poller->monitorHandle(ph, Poller::INPUT); +} + +/** + * Return a queued buffer if there are enough to spare. + */ +AsynchIO::BufferBase* AsynchIO::getQueuedBuffer() { + QLock l(bufferQueueLock); + // 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); + bufferQueue.pop_back(); + return buff; +} + +void AsynchIO::notifyEof(void) { + if (eofCallback) + eofCallback(*this); +} + +void AsynchIO::notifyDisconnect(void) { + if (disCallback) + disCallback(*this); +} + +void AsynchIO::notifyClosed(void) { + if (closedCallback) + closedCallback(*this, socket); +} + +void AsynchIO::notifyBuffersEmpty(void) { + if (emptyCallback) + emptyCallback(*this); +} + +void AsynchIO::notifyIdle(void) { + if (idleCallback) + idleCallback(*this); +} + +/* + * Asynch reader/writer using overlapped I/O + */ + +void AsynchIO::startWrite(AsynchIO::BufferBase* buff) { + writeInProgress = true; + InterlockedIncrement(&opsInProgress); + int writeCount = buff->byteCount-buff->dataCount; + AsynchWriteResult *result = + new AsynchWriteResult(boost::bind(&AsynchIO::completion, this, _1), + buff, + buff->dataCount); + DWORD bytesSent = 0; + int status = WSASend(toSocketHandle(socket), + const_cast<LPWSABUF>(result->getWSABUF()), 1, + &bytesSent, + 0, + result->overlapped(), + 0); + if (status != 0) { + int error = WSAGetLastError(); + if (error != WSA_IO_PENDING) { + result->failure(error); // Also decrements in-progress count + result = 0; // result is invalid here + return; + } + } + // On status 0 or WSA_IO_PENDING, completion will handle the rest. + return; +} + +/* + * Close the socket and callback to say we've done it + */ +void AsynchIO::close(void) { + socket.close(); + notifyClosed(); +} + +void AsynchIO::readComplete(AsynchReadResult *result) { + int status = result->getStatus(); + size_t bytes = result->getTransferred(); + if (status == 0 && bytes > 0) { + bool restartRead = true; // May not if receiver doesn't want more + if (readCallback) + readCallback(*this, result->getBuff()); + if (restartRead) + startReading(); + } + else { + // No data read, so put the buffer back. It may be partially filled, + // so "unread" it back to the front of the queue. + unread(result->getBuff()); + if (status == 0) + notifyEof(); + else + notifyDisconnect(); + } +} + +/* + * NOTE - this completion is called for completed writes and also when + * a write is desired. The difference is in the buff - if a write is desired + * the buff is 0. + */ +void AsynchIO::writeComplete(AsynchWriteResult *result) { + int status = result->getStatus(); + size_t bytes = result->getTransferred(); + AsynchIO::BufferBase *buff = result->getBuff(); + if (buff != 0) { + writeInProgress = false; + if (status == 0 && bytes > 0) { + if (bytes < result->getRequested()) // Still more to go; resubmit + startWrite(buff); + else + queueReadBuffer(buff); // All done; back to the pool + } + 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? + } + } + + // If there are no writes outstanding, check for more writes to initiate + // (either queued or via idle). The opsInProgress count is handled in + // completion() + if (!writeInProgress) { + bool writing = false; + { + QLock l(bufferQueueLock); + if (writeQueue.size() > 0) { + buff = writeQueue.front(); + assert(buff); + writeQueue.pop_front(); + startWrite(buff); + writing = true; + } + } + if (!writing && !queuedClose) { + notifyIdle(); + } + } + return; +} + +void AsynchIO::completion(AsynchIoResult *result) { + { + ScopedLock<Mutex> l(completionLock); + if (working) { + completionQueue.push(result); + return; + } + + // First thread in with something to do; note we're working then keep + // handling completions. + working = true; + while (result != 0) { + // New scope to unlock temporarily. + { + ScopedUnlock<Mutex> ul(completionLock); + AsynchReadResult *r = dynamic_cast<AsynchReadResult*>(result); + if (r != 0) + readComplete(r); + else { + AsynchWriteResult *w = + dynamic_cast<AsynchWriteResult*>(result); + if (w != 0) + writeComplete(w); + else { + AsynchCallbackRequest *req = + dynamic_cast<AsynchCallbackRequest*>(result); + req->reqCallback(*this); + } + } + delete result; + result = 0; + InterlockedDecrement(&opsInProgress); + } + // Lock is held again. + if (completionQueue.empty()) + continue; + result = completionQueue.front(); + completionQueue.pop(); + } + working = false; + } + // Lock released; ok to close if ops are done and close requested. + // Layer above will call back to queueForDeletion() if it hasn't + // already been done. If it already has, go ahead and delete. + if (opsInProgress == 0) { + if (queuedClose) + // close() may cause a delete; don't trust 'this' on return + close(); + else if (queuedDelete) + delete this; + } +} + +} // namespace windows + +AsynchIO* qpid::sys::AsynchIO::create(const Socket& s, + AsynchIO::ReadCallback rCb, + AsynchIO::EofCallback eofCb, + AsynchIO::DisconnectCallback disCb, + AsynchIO::ClosedCallback cCb, + AsynchIO::BuffersEmptyCallback eCb, + AsynchIO::IdleCallback iCb) +{ + return new qpid::sys::windows::AsynchIO(s, rCb, eofCb, disCb, cCb, eCb, iCb); +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/windows/AsynchIoResult.h b/cpp/src/qpid/sys/windows/AsynchIoResult.h new file mode 100755 index 0000000000..66c89efc11 --- /dev/null +++ b/cpp/src/qpid/sys/windows/AsynchIoResult.h @@ -0,0 +1,204 @@ +#ifndef _windows_asynchIoResult_h +#define _windows_asynchIoResult_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/AsynchIO.h" +#include "qpid/sys/Socket.h" +#include <memory.h> +#include <winsock2.h> +#include <ws2tcpip.h> + +namespace qpid { +namespace sys { +namespace windows { + +/* + * AsynchIoResult defines the class that receives the result of an + * asynchronous I/O operation, either send/recv or accept/connect. + * + * Operation factories should set one of these up before beginning the + * operation. Poller knows how to dispatch completion to this class. + * This class must be subclassed for needed operations; this class provides + * an interface only and cannot be instantiated. + * + * This class is tied to Windows; it inherits from OVERLAPPED so that the + * IocpPoller can cast OVERLAPPED pointers back to AsynchIoResult and call + * the completion handler. + */ +class AsynchResult : private OVERLAPPED { +public: + LPOVERLAPPED overlapped(void) { return this; } + static AsynchResult* from_overlapped(LPOVERLAPPED ol) { + return static_cast<AsynchResult*>(ol); + } + virtual void success (size_t bytesTransferred) { + bytes = bytesTransferred; + status = 0; + complete(); + } + virtual void failure (int error) { + bytes = 0; + status = error; + complete(); + } + size_t getTransferred(void) const { return bytes; } + int getStatus(void) const { return status; } + +protected: + AsynchResult() : bytes(0), status(0) + { memset(overlapped(), 0, sizeof(OVERLAPPED)); } + ~AsynchResult() {} + virtual void complete(void) = 0; + + size_t bytes; + int status; +}; + +class AsynchAcceptor; + +class AsynchAcceptResult : public AsynchResult { + + friend class AsynchAcceptor; + +public: + AsynchAcceptResult(qpid::sys::AsynchAcceptor::Callback cb, + AsynchAcceptor *acceptor, + SOCKET listener); + virtual void success (size_t bytesTransferred); + virtual void failure (int error); + +private: + virtual void complete(void) {} // No-op for this class. + + std::auto_ptr<qpid::sys::Socket> newSocket; + qpid::sys::AsynchAcceptor::Callback callback; + AsynchAcceptor *acceptor; + SOCKET listener; + + // AcceptEx needs a place to write the local and remote addresses + // when accepting the connection. Place those here; get enough for + // IPv6 addresses, even if the socket is IPv4. + enum { SOCKADDRMAXLEN = sizeof sockaddr_in6 + 16, + SOCKADDRBUFLEN = 2 * SOCKADDRMAXLEN }; + char addressBuffer[SOCKADDRBUFLEN]; +}; + +class AsynchIoResult : public AsynchResult { +public: + typedef boost::function1<void, AsynchIoResult *> Completer; + + virtual ~AsynchIoResult() {} + qpid::sys::AsynchIO::BufferBase *getBuff(void) const { return iobuff; } + size_t getRequested(void) const { return requested; } + const WSABUF *getWSABUF(void) const { return &wsabuf; } + +protected: + void setBuff (qpid::sys::AsynchIO::BufferBase *buffer) { iobuff = buffer; } + +protected: + AsynchIoResult(Completer cb, + qpid::sys::AsynchIO::BufferBase *buff, size_t length) + : completionCallback(cb), iobuff(buff), requested(length) {} + + virtual void complete(void) = 0; + WSABUF wsabuf; + Completer completionCallback; + +private: + qpid::sys::AsynchIO::BufferBase *iobuff; + size_t requested; // Number of bytes in original I/O request +}; + +class AsynchReadResult : public AsynchIoResult { + + // complete() updates buffer then does completion callback. + virtual void complete(void) { + getBuff()->dataCount += bytes; + completionCallback(this); + } + +public: + AsynchReadResult(AsynchIoResult::Completer cb, + qpid::sys::AsynchIO::BufferBase *buff, + size_t length) + : AsynchIoResult(cb, buff, length) { + wsabuf.buf = buff->bytes + buff->dataCount; + wsabuf.len = length; + } +}; + +class AsynchWriteResult : public AsynchIoResult { + + // complete() updates buffer then does completion callback. + virtual void complete(void) { + qpid::sys::AsynchIO::BufferBase *b = getBuff(); + b->dataStart += bytes; + b->dataCount -= bytes; + completionCallback(this); + } + +public: + AsynchWriteResult(AsynchIoResult::Completer cb, + qpid::sys::AsynchIO::BufferBase *buff, + size_t length) + : AsynchIoResult(cb, buff, length) { + wsabuf.buf = buff ? buff->bytes : 0; + wsabuf.len = length; + } +}; + +class AsynchWriteWanted : public AsynchWriteResult { + + // complete() just does completion callback; no buffers used. + virtual void complete(void) { + completionCallback(this); + } + +public: + AsynchWriteWanted(AsynchIoResult::Completer cb) + : AsynchWriteResult(cb, 0, 0) { + wsabuf.buf = 0; + wsabuf.len = 0; + } +}; + +class AsynchCallbackRequest : public AsynchIoResult { + // complete() needs to simply call the completionCallback; no buffers. + virtual void complete(void) { + completionCallback(this); + } + +public: + AsynchCallbackRequest(AsynchIoResult::Completer cb, + qpid::sys::AsynchIO::RequestCallback reqCb) + : AsynchIoResult(cb, 0, 0), reqCallback(reqCb) { + wsabuf.buf = 0; + wsabuf.len = 0; + } + + qpid::sys::AsynchIO::RequestCallback reqCallback; +}; + +}}} // qpid::sys::windows + +#endif /*!_windows_asynchIoResult_h*/ diff --git a/cpp/src/qpid/sys/windows/FileSysDir.cpp b/cpp/src/qpid/sys/windows/FileSysDir.cpp new file mode 100644 index 0000000000..88f1637d48 --- /dev/null +++ b/cpp/src/qpid/sys/windows/FileSysDir.cpp @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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/FileSysDir.h" +#include "qpid/sys/StrError.h" +#include "qpid/Exception.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <direct.h> +#include <errno.h> + +namespace qpid { +namespace sys { + +bool FileSysDir::exists (void) const +{ + const char *cpath = dirPath.c_str (); + struct _stat s; + if (::_stat(cpath, &s)) { + if (errno == ENOENT) { + return false; + } + throw qpid::Exception (strError(errno) + + ": Can't check directory: " + dirPath); + } + if (s.st_mode & _S_IFDIR) + return true; + throw qpid::Exception(dirPath + " is not a directory"); +} + +void FileSysDir::mkdir(void) +{ + if (::_mkdir(dirPath.c_str()) == -1) + throw Exception ("Can't create directory: " + dirPath); +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/SystemInfo.cpp b/cpp/src/qpid/sys/windows/IOHandle.cpp index dcc7ad9985..250737cb99 100644..100755 --- a/cpp/src/qpid/sys/SystemInfo.cpp +++ b/cpp/src/qpid/sys/windows/IOHandle.cpp @@ -1,4 +1,5 @@ /* + * * 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 @@ -6,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,18 +19,24 @@ * */ -#include "SystemInfo.h" -#include <unistd.h> +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/windows/IoHandlePrivate.h" +#include <windows.h> namespace qpid { namespace sys { -long SystemInfo::concurrency() { -#ifdef _SC_NPROCESSORS_ONLN // Linux specific. - return sysconf(_SC_NPROCESSORS_ONLN); -#else - return -1; -#endif +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 new file mode 100755 index 0000000000..5943db5cc7 --- /dev/null +++ b/cpp/src/qpid/sys/windows/IoHandlePrivate.h @@ -0,0 +1,61 @@ +#ifndef _sys_windows_IoHandlePrivate_h +#define _sys_windows_IoHandlePrivate_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/AsynchIO.h" +#include "qpid/sys/windows/AsynchIoResult.h" +#include "qpid/CommonImportExport.h" + +#include <winsock2.h> + +namespace qpid { +namespace sys { + +// Private fd related implementation details +// There should be either a valid socket handle or a completer callback. +// Handle is used to associate with poller's iocp; completer is used to +// inject a completion that will very quickly trigger a callback to the +// 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); + +public: + IOHandlePrivate(SOCKET f = INVALID_SOCKET, + windows::AsynchIoResult::Completer cb = 0, + AsynchIO::RequestCallback reqCallback = 0) : + fd(f), event(cb), cbRequest(reqCallback) + {} + + SOCKET fd; + windows::AsynchIoResult::Completer event; + 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 new file mode 100755 index 0000000000..4fcc9155f1 --- /dev/null +++ b/cpp/src/qpid/sys/windows/IocpPoller.cpp @@ -0,0 +1,214 @@ +/* + * + * 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/Poller.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Dispatcher.h" + +#include "qpid/sys/windows/AsynchIoResult.h" +#include "qpid/sys/windows/IoHandlePrivate.h" +#include "qpid/sys/windows/check.h" + +#include <winsock2.h> +#include <windows.h> + +#include <assert.h> +#include <vector> +#include <exception> + +namespace qpid { +namespace sys { + +class PollerHandlePrivate { + friend class Poller; + friend class PollerHandle; + + SOCKET fd; + windows::AsynchIoResult::Completer cb; + AsynchIO::RequestCallback cbRequest; + + PollerHandlePrivate(SOCKET f, + windows::AsynchIoResult::Completer cb0 = 0, + AsynchIO::RequestCallback rcb = 0) + : fd(f), cb(cb0), cbRequest(rcb) + { + } + +}; + +PollerHandle::PollerHandle(const IOHandle& h) : + impl(new PollerHandlePrivate(toSocketHandle(static_cast<const Socket&>(h)), h.impl->event, h.impl->cbRequest)) +{} + +PollerHandle::~PollerHandle() { + delete impl; +} + +/** + * Concrete implementation of Poller to use the Windows I/O Completion + * port (IOCP) facility. + */ +class PollerPrivate { + friend class Poller; + + const HANDLE iocp; + + // The number of threads running the event loop. + volatile LONG threadsRunning; + + // Shutdown request is handled by setting isShutdown and injecting a + // well-formed completion event into the iocp. + bool isShutdown; + + PollerPrivate() : + iocp(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)), + threadsRunning(0), + isShutdown(false) { + QPID_WINDOWS_CHECK_NULL(iocp); + } + + ~PollerPrivate() { + // It's probably okay to ignore any errors here as there can't be + // data loss + ::CloseHandle(iocp); + } +}; + +void Poller::shutdown() { + // Allow sloppy code to shut us down more than once. + if (impl->isShutdown) + return; + ULONG_PTR key = 1; // Tell wait() it's a shutdown, not I/O + PostQueuedCompletionStatus(impl->iocp, 0, key, 0); +} + +bool Poller::interrupt(PollerHandle&) { + return false; // There's no concept of a registered handle. +} + +void Poller::run() { + do { + Poller::Event event = this->wait(); + + // Handle shutdown + switch (event.type) { + case Poller::SHUTDOWN: + return; + break; + case Poller::INVALID: // On any type of success or fail completion + break; + default: + // This should be impossible + assert(false); + } + } while (true); +} + +void Poller::monitorHandle(PollerHandle& handle, Direction dir) { + HANDLE h = (HANDLE)(handle.impl->fd); + if (h != INVALID_HANDLE_VALUE) { + HANDLE iocpHandle = ::CreateIoCompletionPort (h, impl->iocp, 0, 0); + QPID_WINDOWS_CHECK_NULL(iocpHandle); + } + else { + // INPUT is used to request a callback; OUTPUT to request a write + assert(dir == Poller::INPUT || dir == Poller::OUTPUT); + + if (dir == Poller::OUTPUT) { + windows::AsynchWriteWanted *result = + new windows::AsynchWriteWanted(handle.impl->cb); + PostQueuedCompletionStatus(impl->iocp, 0, 0, result->overlapped()); + } + else { + windows::AsynchCallbackRequest *result = + new windows::AsynchCallbackRequest(handle.impl->cb, + handle.impl->cbRequest); + PostQueuedCompletionStatus(impl->iocp, 0, 0, result->overlapped()); + } + } +} + +// All no-ops... +void Poller::unmonitorHandle(PollerHandle& handle, Direction dir) {} +void Poller::registerHandle(PollerHandle& handle) {} +void Poller::unregisterHandle(PollerHandle& handle) {} + +Poller::Event Poller::wait(Duration timeout) { + DWORD timeoutMs = 0; + DWORD numTransferred = 0; + ULONG_PTR completionKey = 0; + OVERLAPPED *overlapped = 0; + windows::AsynchResult *result = 0; + + // Wait for either an I/O operation to finish (thus signaling the + // IOCP handle) or a shutdown request to be made (thus signaling the + // shutdown event). + if (timeout == TIME_INFINITE) + timeoutMs = INFINITE; + else + timeoutMs = static_cast<DWORD>(timeout / TIME_MSEC); + + InterlockedIncrement(&impl->threadsRunning); + bool goodOp = ::GetQueuedCompletionStatus (impl->iocp, + &numTransferred, + &completionKey, + &overlapped, + timeoutMs); + LONG remainingThreads = InterlockedDecrement(&impl->threadsRunning); + if (goodOp) { + // Dequeued a successful completion. If it's a posted packet from + // shutdown() the overlapped ptr is 0 and key is 1. Else downcast + // the OVERLAPPED pointer to an AsynchIoResult and call the + // completion handler. + if (overlapped == 0 && completionKey == 1) { + // If there are other threads still running this wait, re-post + // the completion. + if (remainingThreads > 0) + PostQueuedCompletionStatus(impl->iocp, 0, completionKey, 0); + return Event(0, SHUTDOWN); + } + + result = windows::AsynchResult::from_overlapped(overlapped); + result->success (static_cast<size_t>(numTransferred)); + } + else { + if (overlapped != 0) { + // Dequeued a completion for a failed operation. Downcast back + // to the result object and inform it that the operation failed. + DWORD status = ::GetLastError(); + result = windows::AsynchResult::from_overlapped(overlapped); + result->failure (static_cast<int>(status)); + } + } + return Event(0, INVALID); // TODO - this may need to be changed. + +} + +// Concrete constructors +Poller::Poller() : + impl(new PollerPrivate()) +{} + +Poller::~Poller() { + delete impl; +} + +}} diff --git a/cpp/src/qpid/sys/windows/LockFile.cpp b/cpp/src/qpid/sys/windows/LockFile.cpp new file mode 100755 index 0000000000..048c2d5b18 --- /dev/null +++ b/cpp/src/qpid/sys/windows/LockFile.cpp @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2008 The Apache Software Foundation + * + * Licensed 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/LockFile.h" +#include "qpid/sys/windows/check.h" + +#include <windows.h> + +namespace qpid { +namespace sys { + +class LockFilePrivate { + friend class LockFile; + + HANDLE fd; + +public: + LockFilePrivate(HANDLE f) : fd(f) {} +}; + +LockFile::LockFile(const std::string& path_, bool create) + : path(path_), created(create) { + + HANDLE h = ::CreateFile(path.c_str(), + create ? (GENERIC_READ|GENERIC_WRITE) : GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + 0, /* Default security */ + create ? OPEN_ALWAYS : OPEN_EXISTING, + FILE_FLAG_DELETE_ON_CLOSE, /* Delete file when closed */ + NULL); + if (h == INVALID_HANDLE_VALUE) + throw qpid::Exception(path + ": " + qpid::sys::strError(GetLastError())); + + // Lock up to 4Gb + if (!::LockFile(h, 0, 0, 0xffffffff, 0)) + throw qpid::Exception(path + ": " + qpid::sys::strError(GetLastError())); + impl.reset(new LockFilePrivate(h)); +} + +LockFile::~LockFile() { + if (impl) { + if (impl->fd != INVALID_HANDLE_VALUE) { + ::UnlockFile(impl->fd, 0, 0, 0xffffffff, 0); + ::CloseHandle(impl->fd); + } + } +} + +}} /* namespace qpid::sys */ diff --git a/cpp/src/qpid/sys/windows/PipeHandle.cpp b/cpp/src/qpid/sys/windows/PipeHandle.cpp new file mode 100755 index 0000000000..062458ae5f --- /dev/null +++ b/cpp/src/qpid/sys/windows/PipeHandle.cpp @@ -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. +// + +#include "qpid/sys/PipeHandle.h" +#include "qpid/sys/windows/check.h" +#include <winsock2.h> + +namespace qpid { +namespace sys { + +PipeHandle::PipeHandle(bool nonBlocking) { + + SOCKET listener, pair[2]; + struct sockaddr_in addr; + int err; + int addrlen = sizeof(addr); + pair[0] = pair[1] = INVALID_SOCKET; + if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + err = bind(listener, (const struct sockaddr*) &addr, sizeof(addr)); + if (err == SOCKET_ERROR) { + err = WSAGetLastError(); + closesocket(listener); + throw QPID_WINDOWS_ERROR(err); + } + + err = getsockname(listener, (struct sockaddr*) &addr, &addrlen); + if (err == SOCKET_ERROR) { + err = WSAGetLastError(); + closesocket(listener); + throw QPID_WINDOWS_ERROR(err); + } + + try { + if (listen(listener, 1) == SOCKET_ERROR) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + if ((pair[0] = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + if (connect(pair[0], (const struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + if ((pair[1] = accept(listener, NULL, NULL)) == INVALID_SOCKET) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + + closesocket(listener); + writeFd = pair[0]; + readFd = pair[1]; + } + catch (...) { + closesocket(listener); + if (pair[0] != INVALID_SOCKET) + closesocket(pair[0]); + throw; + } + + // Set the socket to non-blocking + if (nonBlocking) { + unsigned long nonblock = 1; + ioctlsocket(readFd, FIONBIO, &nonblock); + } +} + +PipeHandle::~PipeHandle() { + closesocket(readFd); + closesocket(writeFd); +} + +int PipeHandle::read(void* buf, size_t bufSize) { + return ::recv(readFd, (char *)buf, bufSize, 0); +} + +int PipeHandle::write(const void* buf, size_t bufSize) { + return ::send(writeFd, (const char *)buf, bufSize, 0); +} + +int PipeHandle::getReadHandle() { + return readFd; +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/windows/PollableCondition.cpp b/cpp/src/qpid/sys/windows/PollableCondition.cpp new file mode 100644 index 0000000000..5ccc136bd1 --- /dev/null +++ b/cpp/src/qpid/sys/windows/PollableCondition.cpp @@ -0,0 +1,116 @@ +#ifndef QPID_SYS_WINDOWS_POLLABLECONDITION_CPP +#define QPID_SYS_WINDOWS_POLLABLECONDITION_CPP + +/* + * + * 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/PollableCondition.h" +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/windows/AsynchIoResult.h" +#include "qpid/sys/windows/IoHandlePrivate.h" + +#include <boost/bind.hpp> +#include <windows.h> + +namespace qpid { +namespace sys { + +// PollableConditionPrivate will reuse the IocpPoller's ability to queue +// a completion to the IOCP and have it dispatched to the completer callback +// noted in the IOHandlePrivate when the request is queued. The +// AsynchCallbackRequest object is not really used - we already have the +// desired callback for the user of PollableCondition. +class PollableConditionPrivate : private IOHandle { + friend class PollableCondition; + +private: + PollableConditionPrivate(const sys::PollableCondition::Callback& cb, + sys::PollableCondition& parent, + const boost::shared_ptr<sys::Poller>& poller); + ~PollableConditionPrivate(); + + void poke(); + void dispatch(windows::AsynchIoResult *result); + +private: + PollableCondition::Callback cb; + PollableCondition& parent; + boost::shared_ptr<sys::Poller> poller; + LONG isSet; + LONG armed; +}; + +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), armed(0) +{ +} + +PollableConditionPrivate::~PollableConditionPrivate() +{ +} + +void PollableConditionPrivate::poke() +{ + if (!armed) + return; + + // monitorHandle will queue a completion for the IOCP; when it's handled, a + // poller thread will call back to dispatch() below. + PollerHandle ph(*this); + poller->monitorHandle(ph, Poller::INPUT); +} + +void PollableConditionPrivate::dispatch(windows::AsynchIoResult *result) +{ + delete result; // Poller::monitorHandle() allocates this + cb(parent); +} + + /* PollableCondition */ + +PollableCondition::PollableCondition(const Callback& cb, + const boost::shared_ptr<sys::Poller>& poller) + : impl(new PollableConditionPrivate(cb, *this, poller)) +{ +} + +PollableCondition::~PollableCondition() +{ + delete impl; +} + +void PollableCondition::set() { + // Add one to the set count and poke it to provoke a callback + ::InterlockedIncrement(&impl->isSet); + impl->poke(); +} + +void PollableCondition::clear() { + ::InterlockedExchange(&impl->isSet, 0); +} + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_WINDOWS_POLLABLECONDITION_CPP*/ diff --git a/cpp/src/qpid/sys/windows/Shlib.cpp b/cpp/src/qpid/sys/windows/Shlib.cpp new file mode 100644 index 0000000000..38027de93f --- /dev/null +++ b/cpp/src/qpid/sys/windows/Shlib.cpp @@ -0,0 +1,53 @@ +/* + * 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/Shlib.h" +#include "qpid/Exception.h" +#include "qpid/sys/windows/check.h" +#include <windows.h> + +namespace qpid { +namespace sys { + +void Shlib::load(const char* name) { + HMODULE h = LoadLibrary(name); + if (h == NULL) { + throw QPID_WINDOWS_ERROR(GetLastError()); + } + handle = static_cast<void*>(h); +} + +void Shlib::unload() { + if (handle) { + if (FreeLibrary(static_cast<HMODULE>(handle)) == 0) { + throw QPID_WINDOWS_ERROR(GetLastError()); + } + handle = 0; + } +} + +void* Shlib::getSymbol(const char* name) { + void* sym = GetProcAddress(static_cast<HMODULE>(handle), name); + if (sym == NULL) + throw QPID_WINDOWS_ERROR(GetLastError()); + return sym; +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/windows/Socket.cpp b/cpp/src/qpid/sys/windows/Socket.cpp new file mode 100755 index 0000000000..11fb8b4133 --- /dev/null +++ b/cpp/src/qpid/sys/windows/Socket.cpp @@ -0,0 +1,343 @@ +/* + * + * 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/SocketAddress.h" +#include "qpid/sys/windows/IoHandlePrivate.h" +#include "qpid/sys/windows/check.h" +#include "qpid/sys/Time.h" + +#include <cstdlib> +#include <string.h> + +#include <winsock2.h> + +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +// Need to initialize WinSock. Ideally, this would be a singleton or embedded +// in some one-time initialization function. I tried boost singleton and could +// not get it to compile (and others located in google had the same problem). +// So, this simple static with an interlocked increment will do for known +// use cases at this time. Since this will only shut down winsock at process +// termination, there may be some problems with client programs that also +// expect to load and unload winsock, but we'll see... +// If someone does get an easy-to-use singleton sometime, converting to it +// may be preferable. + +namespace { + +static LONG volatile initialized = 0; + +class WinSockSetup { + // : public boost::details::pool::singleton_default<WinSockSetup> { + +public: + WinSockSetup() { + LONG timesEntered = InterlockedIncrement(&initialized); + if (timesEntered > 1) + return; + err = 0; + WORD wVersionRequested; + WSADATA wsaData; + + /* Request WinSock 2.2 */ + wVersionRequested = MAKEWORD(2, 2); + err = WSAStartup(wVersionRequested, &wsaData); + } + + ~WinSockSetup() { + WSACleanup(); + } + +public: + int error(void) const { return err; } + +protected: + DWORD err; +}; + +static WinSockSetup setup; + +} /* namespace */ + +namespace qpid { +namespace sys { + +namespace { + +std::string getName(SOCKET fd, bool local, bool includeService = false) +{ + sockaddr_in name; // big enough for any socket address + socklen_t namelen = sizeof(name); + if (local) { + QPID_WINSOCK_CHECK(::getsockname(fd, (sockaddr*)&name, &namelen)); + } else { + QPID_WINSOCK_CHECK(::getpeername(fd, (sockaddr*)&name, &namelen)); + } + + char servName[NI_MAXSERV]; + char dispName[NI_MAXHOST]; + if (includeService) { + if (int rc = ::getnameinfo((sockaddr*)&name, namelen, + dispName, sizeof(dispName), + servName, sizeof(servName), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + throw qpid::Exception(QPID_MSG(gai_strerror(rc))); + return std::string(dispName) + ":" + std::string(servName); + } else { + if (int rc = ::getnameinfo((sockaddr*)&name, namelen, + dispName, sizeof(dispName), + 0, 0, + NI_NUMERICHOST) != 0) + throw qpid::Exception(QPID_MSG(gai_strerror(rc))); + return dispName; + } +} + +std::string getService(SOCKET fd, bool local) +{ + sockaddr_in name; // big enough for any socket address + socklen_t namelen = sizeof(name); + + if (local) { + QPID_WINSOCK_CHECK(::getsockname(fd, (sockaddr*)&name, &namelen)); + } else { + QPID_WINSOCK_CHECK(::getpeername(fd, (sockaddr*)&name, &namelen)); + } + + char servName[NI_MAXSERV]; + if (int rc = ::getnameinfo((sockaddr*)&name, namelen, + 0, 0, + servName, sizeof(servName), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + throw qpid::Exception(QPID_MSG(gai_strerror(rc))); + return servName; +} +} // namespace + +Socket::Socket() : + IOHandle(new IOHandlePrivate), + nonblocking(false), + nodelay(false) +{ + SOCKET& socket = impl->fd; + if (socket != INVALID_SOCKET) Socket::close(); + SOCKET s = ::socket (PF_INET, SOCK_STREAM, 0); + if (s == INVALID_SOCKET) throw QPID_WINDOWS_ERROR(WSAGetLastError()); + socket = s; +} + +Socket::Socket(IOHandlePrivate* h) : + IOHandle(h), + nonblocking(false), + nodelay(false) +{} + +void +Socket::createSocket(const SocketAddress& sa) const +{ + SOCKET& socket = impl->fd; + if (socket != INVALID_SOCKET) Socket::close(); + + SOCKET s = ::socket (getAddrInfo(sa).ai_family, + getAddrInfo(sa).ai_socktype, + 0); + if (s == INVALID_SOCKET) throw QPID_WINDOWS_ERROR(WSAGetLastError()); + socket = s; + + try { + if (nonblocking) setNonblocking(); + if (nodelay) setTcpNoDelay(); + } catch (std::exception&) { + closesocket(s); + socket = INVALID_SOCKET; + throw; + } +} + +void Socket::setTimeout(const Duration& interval) const +{ + const SOCKET& socket = impl->fd; + int64_t nanosecs = interval; + nanosecs /= (1000 * 1000); // nsecs -> usec -> msec + int msec = 0; + if (nanosecs > std::numeric_limits<int>::max()) + msec = std::numeric_limits<int>::max(); + else + msec = static_cast<int>(nanosecs); + setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&msec, sizeof(msec)); + setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&msec, sizeof(msec)); +} + +void Socket::setNonblocking() const { + u_long nonblock = 1; + QPID_WINSOCK_CHECK(ioctlsocket(impl->fd, FIONBIO, &nonblock)); +} + +void Socket::connect(const std::string& host, uint16_t port) const +{ + SocketAddress sa(host, boost::lexical_cast<std::string>(port)); + connect(sa); +} + +void +Socket::connect(const SocketAddress& addr) const +{ + const SOCKET& socket = impl->fd; + const addrinfo *addrs = &(getAddrInfo(addr)); + int error = 0; + WSASetLastError(0); + while (addrs != 0) { + if ((::connect(socket, addrs->ai_addr, addrs->ai_addrlen) == 0) || + (WSAGetLastError() == WSAEWOULDBLOCK)) + break; + // Error... save this error code and see if there are other address + // to try before throwing the exception. + error = WSAGetLastError(); + addrs = addrs->ai_next; + } + if (error) + throw qpid::Exception(QPID_MSG(strError(error) << ": " << connectname)); +} + +void +Socket::close() const +{ + SOCKET& socket = impl->fd; + if (socket == INVALID_SOCKET) return; + QPID_WINSOCK_CHECK(closesocket(socket)); + socket = INVALID_SOCKET; +} + + +int Socket::write(const void *buf, size_t count) const +{ + const SOCKET& socket = impl->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 +{ + const SOCKET& socket = impl->fd; + int received = ::recv(socket, (char *)buf, count, 0); + if (received == SOCKET_ERROR) + return -1; + return received; +} + +int Socket::listen(uint16_t port, int backlog) const +{ + const SOCKET& socket = impl->fd; + BOOL yes=1; + QPID_WINSOCK_CHECK(setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes))); + struct sockaddr_in name; + memset(&name, 0, sizeof(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)) == SOCKET_ERROR) + throw Exception(QPID_MSG("Can't bind to port " << port << ": " << strError(WSAGetLastError()))); + if (::listen(socket, backlog) == SOCKET_ERROR) + throw Exception(QPID_MSG("Can't listen on port " << port << ": " << strError(WSAGetLastError()))); + + socklen_t namelen = sizeof(name); + QPID_WINSOCK_CHECK(::getsockname(socket, (struct sockaddr*)&name, &namelen)); + return ntohs(name.sin_port); +} + +Socket* Socket::accept() const +{ + SOCKET afd = ::accept(impl->fd, 0, 0); + if (afd != INVALID_SOCKET) + return new Socket(new IOHandlePrivate(afd)); + else if (WSAGetLastError() == EAGAIN) + return 0; + else throw QPID_WINDOWS_ERROR(WSAGetLastError()); +} + +std::string Socket::getSockname() const +{ + return getName(impl->fd, true); +} + +std::string Socket::getPeername() const +{ + return getName(impl->fd, false); +} + +std::string Socket::getPeerAddress() const +{ + if (!connectname.empty()) + return std::string (connectname); + return getName(impl->fd, false, true); +} + +std::string Socket::getLocalAddress() const +{ + return getName(impl->fd, true, true); +} + +uint16_t Socket::getLocalPort() const +{ + return atoi(getService(impl->fd, true).c_str()); +} + +uint16_t Socket::getRemotePort() const +{ + return atoi(getService(impl->fd, true).c_str()); +} + +int Socket::getError() const +{ + int result; + socklen_t rSize = sizeof (result); + + QPID_WINSOCK_CHECK(::getsockopt(impl->fd, SOL_SOCKET, SO_ERROR, (char *)&result, &rSize)); + return result; +} + +void Socket::setTcpNoDelay() const +{ + int flag = 1; + int result = setsockopt(impl->fd, + IPPROTO_TCP, + TCP_NODELAY, + (char *)&flag, + sizeof(flag)); + QPID_WINSOCK_CHECK(result); + nodelay = true; +} + +inline IOHandlePrivate* IOHandlePrivate::getImpl(const qpid::sys::IOHandle &h) +{ + return h.impl; +} + +SOCKET toSocketHandle(const Socket& s) +{ + return IOHandlePrivate::getImpl(s)->fd; +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/windows/SocketAddress.cpp b/cpp/src/qpid/sys/windows/SocketAddress.cpp new file mode 100644 index 0000000000..a3e03c9be8 --- /dev/null +++ b/cpp/src/qpid/sys/windows/SocketAddress.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 "qpid/sys/SocketAddress.h" + +#include "qpid/sys/windows/check.h" + +#include <ws2tcpip.h> +#include <string.h> + +namespace qpid { +namespace sys { + +SocketAddress::SocketAddress(const std::string& host0, const std::string& port0) : + host(host0), + port(port0), + addrInfo(0) +{ + ::addrinfo hints; + ::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; // In order to allow AF_INET6 we'd have to change createTcp() as well + hints.ai_socktype = SOCK_STREAM; + + const char* node = 0; + if (host.empty()) { + hints.ai_flags |= AI_PASSIVE; + } else { + node = host.c_str(); + } + const char* service = port.empty() ? "0" : port.c_str(); + + int n = ::getaddrinfo(node, service, &hints, &addrInfo); + if (n != 0) + throw Exception(QPID_MSG("Cannot resolve " << host << ": " << ::gai_strerror(n))); +} + +SocketAddress::~SocketAddress() +{ + ::freeaddrinfo(addrInfo); +} + +std::string SocketAddress::asString() const +{ + return host + ":" + port; +} + +const ::addrinfo& getAddrInfo(const SocketAddress& sa) +{ + return *sa.addrInfo; +} + +}} diff --git a/cpp/src/qpid/sys/windows/StrError.cpp b/cpp/src/qpid/sys/windows/StrError.cpp new file mode 100755 index 0000000000..9c1bfcd79c --- /dev/null +++ b/cpp/src/qpid/sys/windows/StrError.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 "qpid/sys/StrError.h" +#include <string> +#include <string.h> +#include <windows.h> + +namespace qpid { +namespace sys { + +std::string strError(int err) { + const size_t bufsize = 512; + char buf[bufsize]; + if (0 == FormatMessage (FORMAT_MESSAGE_MAX_WIDTH_MASK + | FORMAT_MESSAGE_FROM_SYSTEM, + 0, + err, + 0, // Default language + buf, + bufsize, + 0)) + { + strerror_s (buf, bufsize, err); + } + return std::string(buf); +} + +}} diff --git a/cpp/src/qpid/sys/windows/SystemInfo.cpp b/cpp/src/qpid/sys/windows/SystemInfo.cpp new file mode 100755 index 0000000000..ea53fc199c --- /dev/null +++ b/cpp/src/qpid/sys/windows/SystemInfo.cpp @@ -0,0 +1,201 @@ +/* + * 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. + * + */ + +/* GetNativeSystemInfo call requires _WIN32_WINNT 0x0501 or higher */ +#ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0501 +#endif + +#include "qpid/sys/IntegerTypes.h" +#include "qpid/sys/SystemInfo.h" + +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> +#include <tlhelp32.h> + +#ifndef HOST_NAME_MAX +# define HOST_NAME_MAX 256 +#endif + +namespace qpid { +namespace sys { + +long SystemInfo::concurrency() { + SYSTEM_INFO sys_info; + ::GetSystemInfo (&sys_info); + long activeProcessors = 0; + DWORD_PTR mask = sys_info.dwActiveProcessorMask; + while (mask != 0) { + if (mask & 1) + ++activeProcessors; + mask >>= 1; + } + return activeProcessors; +} + +bool SystemInfo::getLocalHostname (TcpAddress &address) { + char name[HOST_NAME_MAX]; + if (::gethostname(name, sizeof(name)) != 0) { + errno = WSAGetLastError(); + return false; + } + address.host = name; + return true; +} + +void SystemInfo::getLocalIpAddresses (uint16_t port, + std::vector<Address> &addrList) { + enum { MAX_URL_INTERFACES = 100 }; + static const std::string LOCALHOST("127.0.0.1"); + + 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(TcpAddress(addr, port)); + } + } + closesocket (s); + } +} + +void SystemInfo::getSystemId (std::string &osName, + std::string &nodeName, + std::string &release, + std::string &version, + std::string &machine) +{ + osName = "Microsoft Windows"; + + char node[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD nodelen = MAX_COMPUTERNAME_LENGTH + 1; + GetComputerName (node, &nodelen); + nodeName = node; + + OSVERSIONINFOEX vinfo; + vinfo.dwOSVersionInfoSize = sizeof(vinfo); + GetVersionEx ((OSVERSIONINFO *)&vinfo); + + SYSTEM_INFO sinfo; + GetNativeSystemInfo(&sinfo); + + switch(vinfo.dwMajorVersion) { + case 5: + switch(vinfo.dwMinorVersion) { + case 0: + release ="2000"; + break; + case 1: + release = "XP"; + break; + case 2: + if (sinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 || + sinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) + release = "XP-64"; + else + release = "Server 2003"; + break; + default: + release = "Windows"; + } + break; + case 6: + if (vinfo.wProductType == VER_NT_SERVER) + release = "Server 2008"; + else + release = "Vista"; + break; + default: + release = "Microsoft Windows"; + } + version = vinfo.szCSDVersion; + + switch(sinfo.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: + machine = "x86-64"; + break; + case PROCESSOR_ARCHITECTURE_IA64: + machine = "IA64"; + break; + case PROCESSOR_ARCHITECTURE_INTEL: + machine = "x86"; + break; + default: + machine = "unknown"; + break; + } +} + +uint32_t SystemInfo::getProcessId() +{ + return static_cast<uint32_t>(::GetCurrentProcessId()); +} + +uint32_t SystemInfo::getParentProcessId() +{ + // Only want info for the current process, so ask for something specific. + // The module info won't be used here but it keeps the snapshot limited to + // the current process so a search through all processes is not needed. + HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0); + if (snap == INVALID_HANDLE_VALUE) + return 0; + PROCESSENTRY32 entry; + entry.dwSize = sizeof(entry); + if (!Process32First(snap, &entry)) + entry.th32ParentProcessID = 0; + CloseHandle(snap); + return static_cast<uint32_t>(entry.th32ParentProcessID); +} + +std::string SystemInfo::getProcessName() +{ + std::string name; + + // Only want info for the current process, so ask for something specific. + // The module info won't be used here but it keeps the snapshot limited to + // the current process so a search through all processes is not needed. + HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0); + if (snap == INVALID_HANDLE_VALUE) + return name; + PROCESSENTRY32 entry; + entry.dwSize = sizeof(entry); + if (!Process32First(snap, &entry)) + entry.szExeFile[0] = '\0'; + CloseHandle(snap); + name = entry.szExeFile; + return name; +} + +}} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/windows/Thread.cpp b/cpp/src/qpid/sys/windows/Thread.cpp new file mode 100755 index 0000000000..fed82e4d54 --- /dev/null +++ b/cpp/src/qpid/sys/windows/Thread.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/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/windows/check.h" + +#include <process.h> +#include <windows.h> + +namespace { +unsigned __stdcall runRunnable(void* p) +{ + static_cast<qpid::sys::Runnable*>(p)->run(); + _endthreadex(0); + return 0; +} +} + +namespace qpid { +namespace sys { + +class ThreadPrivate { + friend class Thread; + + HANDLE threadHandle; + unsigned threadId; + + ThreadPrivate(Runnable* runnable) { + uintptr_t h = _beginthreadex(0, + 0, + runRunnable, + runnable, + 0, + &threadId); + QPID_WINDOWS_CHECK_CRT_NZ(h); + threadHandle = reinterpret_cast<HANDLE>(h); + } + + ThreadPrivate() + : threadHandle(GetCurrentThread()), threadId(GetCurrentThreadId()) {} +}; + +Thread::Thread() {} + +Thread::Thread(Runnable* runnable) : impl(new ThreadPrivate(runnable)) {} + +Thread::Thread(Runnable& runnable) : impl(new ThreadPrivate(&runnable)) {} + +void Thread::join() { + if (impl) { + DWORD status = WaitForSingleObject (impl->threadHandle, INFINITE); + QPID_WINDOWS_CHECK_NOT(status, WAIT_FAILED); + CloseHandle (impl->threadHandle); + impl->threadHandle = 0; + } +} + +unsigned long Thread::id() { + return impl ? impl->threadId : 0; +} + +/* static */ +Thread Thread::current() { + Thread t; + t.impl.reset(new ThreadPrivate()); + return t; +} + +}} /* qpid::sys */ diff --git a/cpp/src/qpid/sys/windows/Time.cpp b/cpp/src/qpid/sys/windows/Time.cpp new file mode 100644 index 0000000000..1d7b94e8d7 --- /dev/null +++ b/cpp/src/qpid/sys/windows/Time.cpp @@ -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. + * + */ + +#include "qpid/sys/Time.h" +#include <ostream> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/thread/thread_time.hpp> +#include <windows.h> + +using namespace boost::posix_time; + +namespace qpid { +namespace sys { + +AbsTime::AbsTime(const AbsTime& t, const Duration& d) { + if (d == Duration::max()) { + timepoint = ptime(max_date_time); + } + else { + time_duration td = microseconds(d.nanosecs / 1000); + timepoint = t.timepoint + td; + } +} + +AbsTime AbsTime::FarFuture() { + AbsTime ff; + ptime maxd(max_date_time); + ff.timepoint = maxd; + return ff; +} + +AbsTime AbsTime::now() { + AbsTime time_now; + time_now.timepoint = boost::get_system_time(); + return time_now; +} + +Duration::Duration(const AbsTime& time0) : nanosecs(0) { + time_period p(ptime(min_date_time), time0.timepoint); + nanosecs = p.length().total_nanoseconds(); +} + +Duration::Duration(const AbsTime& start, const AbsTime& finish) { + time_duration d = finish.timepoint - start.timepoint; + nanosecs = d.total_nanoseconds(); +} + +std::ostream& operator<<(std::ostream& o, const Duration& d) { + return o << int64_t(d) << "ns"; +} + +std::ostream& operator<<(std::ostream& o, const AbsTime& t) { + std::string time_string = to_simple_string(t.timepoint); + return o << time_string; +} + + +void toPtime(ptime& pt, const AbsTime& t) { + pt = t.getPrivate(); +} + +void sleep(int secs) { + ::Sleep(secs * 1000); +} + +void usleep(uint64_t usecs) { + DWORD msecs = usecs / 1000; + if (msecs == 0) + msecs = 1; + ::Sleep(msecs); +} + +void outputFormattedNow(std::ostream& o) { + ::time_t rawtime; + ::tm timeinfo; + char time_string[100]; + + ::time( &rawtime ); + ::localtime_s(&timeinfo, &rawtime); + ::strftime(time_string, 100, + "%Y-%m-%d %H:%M:%S", + &timeinfo); + o << time_string << " "; +} +}} diff --git a/cpp/src/qpid/sys/windows/uuid.cpp b/cpp/src/qpid/sys/windows/uuid.cpp new file mode 100644 index 0000000000..6ff3a3cb8a --- /dev/null +++ b/cpp/src/qpid/sys/windows/uuid.cpp @@ -0,0 +1,59 @@ +/* + * + * 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 <Rpc.h> +#ifdef uuid_t /* Done in rpcdce.h */ +# undef uuid_t +#endif + +#include "qpid/sys/windows/uuid.h" + +#include <string.h> + +void uuid_clear (uuid_t uu) { + UuidCreateNil (reinterpret_cast<UUID*>(uu)); +} + +void uuid_copy (uuid_t dst, const uuid_t src) { + memcpy (dst, src, qpid::sys::UuidSize); +} + +void uuid_generate (uuid_t out) { + UuidCreate (reinterpret_cast<UUID*>(out)); +} + +int uuid_is_null (const uuid_t uu) { + RPC_STATUS unused; + return UuidIsNil ((UUID*)uu, &unused); +} + +int uuid_parse (const char *in, uuid_t uu) { + return UuidFromString ((unsigned char*)in, (UUID*)uu) == RPC_S_OK ? 0 : -1; +} + +void uuid_unparse (const uuid_t uu, char *out) { + unsigned char *formatted; + if (UuidToString((UUID*)uu, &formatted) == RPC_S_OK) { + strncpy_s (out, 36+1, (char*)formatted, _TRUNCATE); + RpcStringFree(&formatted); + } +} + diff --git a/cpp/src/qpid/sys/windows/uuid.h b/cpp/src/qpid/sys/windows/uuid.h new file mode 100644 index 0000000000..c79abe95c6 --- /dev/null +++ b/cpp/src/qpid/sys/windows/uuid.h @@ -0,0 +1,38 @@ +#ifndef _sys_windows_uuid_h +#define _sys_windows_uuid_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" +#include <qpid/sys/IntegerTypes.h> + +namespace qpid { namespace sys { const size_t UuidSize = 16; }} +typedef uint8_t uuid_t[qpid::sys::UuidSize]; + +QPID_COMMON_EXTERN void uuid_clear (uuid_t uu); +QPID_COMMON_EXTERN void uuid_copy (uuid_t dst, const uuid_t src); +QPID_COMMON_EXTERN void uuid_generate (uuid_t out); +QPID_COMMON_EXTERN int uuid_is_null (const uuid_t uu); // Returns 1 if null, else 0 +QPID_COMMON_EXTERN int uuid_parse (const char *in, uuid_t uu); // Returns 0 on success, else -1 +QPID_COMMON_EXTERN void uuid_unparse (const uuid_t uu, char *out); + +#endif /*!_sys_windows_uuid_h*/ diff --git a/cpp/src/qpid/sys/StrError.h b/cpp/src/qpid/xml/XmlBinding.h index 3843f2abe1..cc6b4dca5d 100644 --- a/cpp/src/qpid/sys/StrError.h +++ b/cpp/src/qpid/xml/XmlBinding.h @@ -1,6 +1,3 @@ -#ifndef _sys_StrError_h -#define _sys_StrError_h - /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -21,15 +18,20 @@ * under the License. * */ - +#include <qpid/framing/FieldTable> #include <string> +#ifndef _XmlBinding_ +#define _XmlBinding_ + namespace qpid { -namespace sys { +namespace client { -/** Get the error message for a system number err, e.g. errno. */ -std::string strError(int err); +class XmlBinding : public framing::FieldTable { + public: + setQuery(string query) { setString("xquery", query); } +}; -}} // namespace qpid - -#endif // _sys_StrError_h +} +} +#endif diff --git a/cpp/src/qpid/xml/XmlExchange.cpp b/cpp/src/qpid/xml/XmlExchange.cpp new file mode 100644 index 0000000000..472ca28954 --- /dev/null +++ b/cpp/src/qpid/xml/XmlExchange.cpp @@ -0,0 +1,262 @@ +/* + * + * 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 "config.h" + +#include "qpid/xml/XmlExchange.h" + +#include "qpid/broker/DeliverableMessage.h" + +#include "qpid/log/Statement.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/reply_exceptions.h" + +#include "qpid/Plugin.h" + +#include <xercesc/framework/MemBufInputSource.hpp> + +#include <xqilla/ast/XQGlobalVariable.hpp> + +#include <xqilla/context/ItemFactory.hpp> +#include <xqilla/xqilla-simple.hpp> + +#include <iostream> +#include <sstream> + +using namespace qpid::framing; +using namespace qpid::sys; +using qpid::management::Manageable; +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace qpid { +namespace broker { + + + XmlExchange::XmlExchange(const string& _name, Manageable* _parent, Broker* b) : Exchange(_name, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + +XmlExchange::XmlExchange(const std::string& _name, bool _durable, + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + + + // #### TODO: The Binding should take the query text + // #### only. Consider encapsulating the entire block, including + // #### the if condition. + + +bool XmlExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* bindingArguments) +{ + string queryText = bindingArguments->getAsString("xquery"); + + try { + RWlock::ScopedWlock l(lock); + + XmlBinding::vector& bindings(bindingsMap[routingKey]); + XmlBinding::vector::ConstPtr p = bindings.snapshot(); + if (!p || std::find_if(p->begin(), p->end(), MatchQueue(queue)) == p->end()) { + Query query(xqilla.parse(X(queryText.c_str()))); + XmlBinding::shared_ptr binding(new XmlBinding (routingKey, queue, this, query)); + bindings.add(binding); + QPID_LOG(trace, "Bound successfully with query: " << queryText ); + + binding->parse_message_content = false; + + if (query->getQueryBody()->getStaticAnalysis().areContextFlagsUsed()) { + binding->parse_message_content = true; + } + else { + GlobalVariables &vars = const_cast<GlobalVariables&>(query->getVariables()); + for(GlobalVariables::iterator it = vars.begin(); it != vars.end(); ++it) { + if ((*it)->getStaticAnalysis().areContextFlagsUsed()) { + binding->parse_message_content = true; + break; + } + } + } + + if (mgmtExchange != 0) { + mgmtExchange->inc_bindingCount(); + ((_qmf::Queue*) queue->GetManagementObject())->inc_bindingCount(); + } + } else { + return false; + } + } + catch (XQException& e) { + throw InternalErrorException(QPID_MSG("Could not parse xquery:"+ queryText)); + } + catch (...) { + throw InternalErrorException(QPID_MSG("Unexpected error - Could not parse xquery:"+ queryText)); + } + routeIVE(); + return true; +} + +bool XmlExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* /*args*/) +{ + RWlock::ScopedWlock l(lock); + if (bindingsMap[routingKey].remove_if(MatchQueue(queue))) { + if (mgmtExchange != 0) { + mgmtExchange->dec_bindingCount(); + ((_qmf::Queue*) queue->GetManagementObject())->dec_bindingCount(); + } + return true; + } else { + return false; + } +} + +bool XmlExchange::matches(Query& query, Deliverable& msg, const qpid::framing::FieldTable* args, bool parse_message_content) +{ + string msgContent; + + try { + QPID_LOG(trace, "matches: query is [" << UTF8(query->getQueryText()) << "]"); + + boost::scoped_ptr<DynamicContext> context(query->createDynamicContext()); + if (!context.get()) { + throw InternalErrorException(QPID_MSG("Query context looks munged ...")); + } + + if (parse_message_content) { + + msg.getMessage().getFrames().getContent(msgContent); + + QPID_LOG(trace, "matches: message content is [" << msgContent << "]"); + + XERCES_CPP_NAMESPACE::MemBufInputSource xml((const XMLByte*) msgContent.c_str(), + msgContent.length(), "input" ); + + // This will parse the document using either Xerces or FastXDM, depending + // on your XQilla configuration. FastXDM can be as much as 10x faster. + + Sequence seq(context->parseDocument(xml)); + + if(!seq.isEmpty() && seq.first()->isNode()) { + context->setContextItem(seq.first()); + context->setContextPosition(1); + context->setContextSize(1); + } + } + + if (args) { + FieldTable::ValueMap::const_iterator v = args->begin(); + for(; v != args->end(); ++v) { + // ### TODO: Do types properly + if (v->second->convertsTo<std::string>()) { + QPID_LOG(trace, "XmlExchange, external variable: " << v->first << " = " << v->second->getData().getString().c_str()); + Item::Ptr value = context->getItemFactory()->createString(X(v->second->getData().getString().c_str()), context.get()); + context->setExternalVariable(X(v->first.c_str()), value); + } + } + } + + Result result = query->execute(context.get()); + return result->getEffectiveBooleanValue(context.get(), 0); + } + catch (XQException& e) { + QPID_LOG(warning, "Could not parse XML content (or message headers):" << msgContent); + } + catch (...) { + QPID_LOG(warning, "Unexpected error routing message: " << msgContent); + } + return 0; +} + +// Future optimization: If any query in a binding for a given routing key requires +// message content, parse the message once, and use that parsed form for all bindings. +// +// Future optimization: XQilla does not currently do document projection for data +// accessed via the context item. If there is a single query for a given routing key, +// and it accesses document data, this could be a big win. +// +// Document projection often is not a win if you have multiple queries on the same data. +// But for very large messages, if all these queries are on the first part of the data, +// it could still be a big win. + +void XmlExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* args) +{ + PreRoute pr(msg, this); + try { + XmlBinding::vector::ConstPtr p; + BindingList b(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >); + { + RWlock::ScopedRlock l(lock); + p = bindingsMap[routingKey].snapshot(); + if (!p.get()) return; + } + + for (std::vector<XmlBinding::shared_ptr>::const_iterator i = p->begin(); i != p->end(); i++) { + if (matches((*i)->xquery, msg, args, (*i)->parse_message_content)) { + b->push_back(*i); + } + } + doRoute(msg, b); + } catch (...) { + QPID_LOG(warning, "XMLExchange " << getName() << ": exception routing message with query " << routingKey); + } +} + + +bool XmlExchange::isBound(Queue::shared_ptr queue, const string* const routingKey, const FieldTable* const) +{ + RWlock::ScopedRlock l(lock); + if (routingKey) { + XmlBindingsMap::iterator i = bindingsMap.find(*routingKey); + + if (i == bindingsMap.end()) + return false; + if (!queue) + return true; + XmlBinding::vector::ConstPtr p = i->second.snapshot(); + return p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end(); + } else if (!queue) { + //if no queue or routing key is specified, just report whether any bindings exist + return bindingsMap.size() > 0; + } else { + for (XmlBindingsMap::iterator i = bindingsMap.begin(); i != bindingsMap.end(); i++) { + XmlBinding::vector::ConstPtr p = i->second.snapshot(); + if (p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end()) return true; + } + return false; + } + +} + + +XmlExchange::~XmlExchange() +{ + bindingsMap.clear(); +} + +const std::string XmlExchange::typeName("xml"); + +} +} diff --git a/cpp/src/qpid/broker/XmlExchange.h b/cpp/src/qpid/xml/XmlExchange.h index 883bfceaca..38cb7699b6 100644 --- a/cpp/src/qpid/broker/XmlExchange.h +++ b/cpp/src/qpid/xml/XmlExchange.h @@ -21,10 +21,11 @@ #ifndef _XmlExchange_ #define _XmlExchange_ -#include "Exchange.h" +#include "qpid/broker/Exchange.h" #include "qpid/framing/FieldTable.h" +#include "qpid/sys/CopyOnWriteArray.h" #include "qpid/sys/Monitor.h" -#include "Queue.h" +#include "qpid/broker/Queue.h" #include <xqilla/xqilla-simple.hpp> @@ -36,18 +37,20 @@ namespace qpid { namespace broker { +class Broker; class XmlExchange : public virtual Exchange { typedef boost::shared_ptr<XQQuery> Query; struct XmlBinding : public Exchange::Binding { typedef boost::shared_ptr<XmlBinding> shared_ptr; - typedef std::vector<XmlBinding::shared_ptr> vector; + typedef qpid::sys::CopyOnWriteArray<XmlBinding::shared_ptr> vector; boost::shared_ptr<XQQuery> xquery; + bool parse_message_content; XmlBinding(const std::string& key, const Queue::shared_ptr queue, Exchange* parent, Query query): - Binding(key, queue, parent), xquery(query) {} + Binding(key, queue, parent), xquery(query), parse_message_content(true) {} }; @@ -57,14 +60,14 @@ class XmlExchange : public virtual Exchange { XQilla xqilla; qpid::sys::RWlock lock; - bool matches(Query& query, Deliverable& msg, const qpid::framing::FieldTable* args); + bool matches(Query& query, Deliverable& msg, const qpid::framing::FieldTable* args, bool parse_message_content); public: static const std::string typeName; - XmlExchange(const std::string& name, management::Manageable* parent = 0); + XmlExchange(const std::string& name, management::Manageable* parent = 0, Broker* broker = 0); XmlExchange(const string& _name, bool _durable, - const qpid::framing::FieldTable& _args, management::Manageable* parent = 0); + const qpid::framing::FieldTable& _args, management::Manageable* parent = 0, Broker* broker = 0); virtual std::string getType() const { return typeName; } diff --git a/cpp/src/qpid/xml/XmlExchangePlugin.cpp b/cpp/src/qpid/xml/XmlExchangePlugin.cpp new file mode 100644 index 0000000000..742b878e86 --- /dev/null +++ b/cpp/src/qpid/xml/XmlExchangePlugin.cpp @@ -0,0 +1,69 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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 <sstream> +#include "qpid/acl/Acl.h" +#include "qpid/broker/Broker.h" +#include "qpid/Plugin.h" +#include "qpid/log/Statement.h" + +#include <boost/shared_ptr.hpp> +#include <boost/utility/in_place_factory.hpp> + +#include "qpid/xml/XmlExchange.h" + +namespace qpid { +namespace broker { // ACL uses the acl namespace here - should I? + +using namespace std; +class Broker; + +Exchange::shared_ptr create(const std::string& name, bool durable, + const framing::FieldTable& args, + management::Manageable* parent, + Broker* broker) +{ + Exchange::shared_ptr e(new XmlExchange(name, durable, args, parent, broker)); + return e; +} + + +class XmlExchangePlugin : public Plugin +{ +public: + void earlyInitialize(Plugin::Target& target); + void initialize(Plugin::Target& target); +}; + + +void XmlExchangePlugin::earlyInitialize(Plugin::Target& target) +{ + Broker* broker = dynamic_cast<broker::Broker*>(&target); + if (broker) { + broker->getExchanges().registerType(XmlExchange::typeName, &create); + QPID_LOG(info, "Registered xml exchange"); + } +} + +void XmlExchangePlugin::initialize(Target&) {} + + +static XmlExchangePlugin matchingPlugin; + + +}} // namespace qpid::acl |
