diff options
author | Stephen D. Huston <shuston@apache.org> | 2011-10-21 14:42:12 +0000 |
---|---|---|
committer | Stephen D. Huston <shuston@apache.org> | 2011-10-21 14:42:12 +0000 |
commit | f83677056891e436bf5ba99e79240df2a44528cd (patch) | |
tree | 625bfd644b948e89105630759cf6decb0435354d /cpp/src/qpid/broker | |
parent | ebfd9ff053b04ab379acfc0fefedee5a31b6d8a5 (diff) | |
download | qpid-python-QPID-2519.tar.gz |
Merged out from trunkQPID-2519
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/QPID-2519@1187375 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp/src/qpid/broker')
94 files changed, 4465 insertions, 1466 deletions
diff --git a/cpp/src/qpid/broker/AsyncCompletion.h b/cpp/src/qpid/broker/AsyncCompletion.h new file mode 100644 index 0000000000..fef994438f --- /dev/null +++ b/cpp/src/qpid/broker/AsyncCompletion.h @@ -0,0 +1,201 @@ +#ifndef _AsyncCompletion_ +#define _AsyncCompletion_ + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <boost/intrusive_ptr.hpp> + +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Monitor.h" + +namespace qpid { +namespace broker { + +/** + * Class to implement asynchronous notification of completion. + * + * Use-case: An "initiator" needs to wait for a set of "completers" to + * finish a unit of work before an action can occur. This object + * tracks the progress of the set of completers, and allows the action + * to occur once all completers have signalled that they are done. + * + * The initiator and completers may be running in separate threads. + * + * The initiating thread is the thread that initiates the action, + * i.e. the connection read thread. + * + * A completing thread is any thread that contributes to completion, + * e.g. a store thread that does an async write. + * There may be zero or more completers. + * + * When the work is complete, a callback is invoked. The callback + * may be invoked in the Initiator thread, or one of the Completer + * threads. The callback is passed a flag indicating whether or not + * the callback is running under the context of the Initiator thread. + * + * Use model: + * 1) Initiator thread invokes begin() + * 2) After begin() has been invoked, zero or more Completers invoke + * startCompleter(). Completers may be running in the same or + * different thread as the Initiator, as long as they guarantee that + * startCompleter() is invoked at least once before the Initiator invokes end(). + * 3) Completers may invoke finishCompleter() at any time, even after the + * initiator has invoked end(). finishCompleter() may be called from any + * thread. + * 4) startCompleter()/finishCompleter() calls "nest": for each call to + * startCompleter(), a corresponding call to finishCompleter() must be made. + * Once the last finishCompleter() is called, the Completer must no longer + * reference the completion object. + * 5) The Initiator invokes end() at the point where it has finished + * dispatching work to the Completers, and is prepared for the callback + * handler to be invoked. Note: if there are no outstanding Completers + * pending when the Initiator invokes end(), the callback will be invoked + * directly, and the sync parameter will be set true. This indicates to the + * Initiator that the callback is executing in the context of the end() call, + * and the Initiator is free to optimize the handling of the completion, + * assuming no need for synchronization with Completer threads. + */ + +class AsyncCompletion +{ + public: + + /** Supplied by the Initiator to the end() method, allows for a callback + * when all outstanding completers are done. If the callback cannot be + * made during the end() call, the clone() method must supply a copy of + * this callback object that persists after end() returns. The cloned + * callback object will be used by the last completer thread, and + * released when the callback returns. + */ + class Callback : public RefCounted + { + public: + virtual void completed(bool) = 0; + virtual boost::intrusive_ptr<Callback> clone() = 0; + }; + + private: + mutable qpid::sys::AtomicValue<uint32_t> completionsNeeded; + mutable qpid::sys::Monitor callbackLock; + bool inCallback, active; + + void invokeCallback(bool sync) { + qpid::sys::Mutex::ScopedLock l(callbackLock); + if (active) { + if (callback.get()) { + inCallback = true; + { + qpid::sys::Mutex::ScopedUnlock ul(callbackLock); + callback->completed(sync); + } + inCallback = false; + callback = boost::intrusive_ptr<Callback>(); + callbackLock.notifyAll(); + } + active = false; + } + } + + protected: + /** Invoked when all completers have signalled that they have completed + * (via calls to finishCompleter()). bool == true if called via end() + */ + boost::intrusive_ptr<Callback> callback; + + public: + AsyncCompletion() : completionsNeeded(0), inCallback(false), active(true) {}; + virtual ~AsyncCompletion() { cancel(); } + + + /** True when all outstanding operations have compeleted + */ + bool isDone() + { + return !active; + } + + /** Called to signal the start of an asynchronous operation. The operation + * is considered pending until finishCompleter() is called. + * E.g. called when initiating an async store operation. + */ + void startCompleter() { ++completionsNeeded; } + + /** Called by completer to signal that it has finished the operation started + * when startCompleter() was invoked. + * e.g. called when async write complete. + */ + void finishCompleter() + { + if (--completionsNeeded == 0) { + invokeCallback(false); + } + } + + /** called by initiator before any calls to startCompleter can be done. + */ + void begin() + { + ++completionsNeeded; + } + + /** called by initiator after all potential completers have called + * startCompleter(). + */ + void end(Callback& cb) + { + assert(completionsNeeded.get() > 0); // ensure begin() has been called! + // the following only "decrements" the count if it is 1. This means + // there are no more outstanding completers and we are done. + if (completionsNeeded.boolCompareAndSwap(1, 0)) { + // done! Complete immediately + cb.completed(true); + return; + } + + // the compare-and-swap did not succeed. This means there are + // outstanding completers pending (count > 1). Get a persistent + // Callback object to use when the last completer is done. + // Decrement after setting up the callback ensures that pending + // completers cannot touch the callback until it is ready. + callback = cb.clone(); + if (--completionsNeeded == 0) { + // note that a completer may have completed during the + // callback setup or decrement: + invokeCallback(true); + } + } + + /** may be called by Initiator to cancel the callback. Will wait for + * callback to complete if in progress. + */ + virtual void cancel() { + qpid::sys::Mutex::ScopedLock l(callbackLock); + while (inCallback) callbackLock.wait(); + callback = boost::intrusive_ptr<Callback>(); + active = false; + } +}; + +}} // qpid::broker:: +#endif /*!_AsyncCompletion_*/ diff --git a/cpp/src/qpid/broker/Bridge.cpp b/cpp/src/qpid/broker/Bridge.cpp index 7fbbf4e2c4..c709606c17 100644 --- a/cpp/src/qpid/broker/Bridge.cpp +++ b/cpp/src/qpid/broker/Bridge.cpp @@ -164,6 +164,12 @@ void Bridge::destroy() listener(this); } +bool Bridge::isSessionReady() const +{ + SessionHandler& sessionHandler = conn->getChannel(id); + return sessionHandler.ready(); +} + void Bridge::setPersistenceId(uint64_t pId) const { persistenceId = pId; diff --git a/cpp/src/qpid/broker/Bridge.h b/cpp/src/qpid/broker/Bridge.h index a846254c57..8b4559a871 100644 --- a/cpp/src/qpid/broker/Bridge.h +++ b/cpp/src/qpid/broker/Bridge.h @@ -59,6 +59,8 @@ public: void destroy(); bool isDurable() { return args.i_durable; } + bool isSessionReady() const; + management::ManagementObject* GetManagementObject() const; management::Manageable::status_t ManagementMethod(uint32_t methodId, management::Args& args, diff --git a/cpp/src/qpid/broker/Broker.cpp b/cpp/src/qpid/broker/Broker.cpp index 3c692fc368..ec3cf9d340 100644 --- a/cpp/src/qpid/broker/Broker.cpp +++ b/cpp/src/qpid/broker/Broker.cpp @@ -20,6 +20,7 @@ */ #include "qpid/broker/Broker.h" +#include "qpid/broker/ConnectionState.h" #include "qpid/broker/DirectExchange.h" #include "qpid/broker/FanOutExchange.h" #include "qpid/broker/HeadersExchange.h" @@ -31,12 +32,26 @@ #include "qpid/broker/TopicExchange.h" #include "qpid/broker/Link.h" #include "qpid/broker/ExpiryPolicy.h" +#include "qpid/broker/QueueFlowLimit.h" +#include "qpid/broker/MessageGroupManager.h" #include "qmf/org/apache/qpid/broker/Package.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerCreate.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerDelete.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerQuery.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerEcho.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerGetLogLevel.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerQueueMoveMessages.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerSetLogLevel.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerSetTimestampConfig.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerGetTimestampConfig.h" +#include "qmf/org/apache/qpid/broker/EventExchangeDeclare.h" +#include "qmf/org/apache/qpid/broker/EventExchangeDelete.h" +#include "qmf/org/apache/qpid/broker/EventQueueDeclare.h" +#include "qmf/org/apache/qpid/broker/EventQueueDelete.h" +#include "qmf/org/apache/qpid/broker/EventBind.h" +#include "qmf/org/apache/qpid/broker/EventUnbind.h" +#include "qpid/amqp_0_10/Codecs.h" #include "qpid/management/ManagementDirectExchange.h" #include "qpid/management/ManagementTopicExchange.h" #include "qpid/log/Logger.h" @@ -44,7 +59,9 @@ #include "qpid/log/Statement.h" #include "qpid/log/posix/SinkOptions.h" #include "qpid/framing/AMQFrame.h" +#include "qpid/framing/FieldTable.h" #include "qpid/framing/ProtocolInitiation.h" +#include "qpid/framing/reply_exceptions.h" #include "qpid/framing/Uuid.h" #include "qpid/sys/ProtocolFactory.h" #include "qpid/sys/Poller.h" @@ -76,7 +93,10 @@ using qpid::management::ManagementAgent; using qpid::management::ManagementObject; using qpid::management::Manageable; using qpid::management::Args; +using qpid::management::getManagementExecutionContext; +using qpid::types::Variant; using std::string; +using std::make_pair; namespace _qmf = qmf::org::apache::qpid::broker; @@ -103,7 +123,12 @@ Broker::Options::Options(const std::string& name) : maxSessionRate(0), asyncQueueEvents(false), // Must be false in a cluster. qmf2Support(true), - qmf1Support(true) + qmf1Support(true), + queueFlowStopRatio(80), + queueFlowResumeRatio(70), + queueThresholdEventRatio(80), + defaultMsgGroup("qpid.no-group"), + timestampRcvMsgs(false) // set the 0.10 timestamp delivery property { int c = sys::SystemInfo::concurrency(); workerThreads=c+1; @@ -134,9 +159,14 @@ Broker::Options::Options(const std::string& name) : ("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)") - ("sasl-config", optValue(saslConfigPath, "FILE"), "gets sasl config from nonstandard location") + ("sasl-config", optValue(saslConfigPath, "DIR"), "gets sasl config info from nonstandard location") ("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"); + ("async-queue-events", optValue(asyncQueueEvents, "yes|no"), "Set Queue Events async, used for services like replication") + ("default-flow-stop-threshold", optValue(queueFlowStopRatio, "PERCENT"), "Percent of queue's maximum capacity at which flow control is activated.") + ("default-flow-resume-threshold", optValue(queueFlowResumeRatio, "PERCENT"), "Percent of queue's maximum capacity at which flow control is de-activated.") + ("default-event-threshold-ratio", optValue(queueThresholdEventRatio, "%age of limit"), "The ratio of any specified queue limit at which an event will be raised") + ("default-message-group", optValue(defaultMsgGroup, "GROUP-IDENTIFER"), "Group identifier to assign to messages delivered to a message group queue that do not contain an identifier.") + ("enable-timestamp", optValue(timestampRcvMsgs, "yes|no"), "Add current time to each received message."); } const std::string empty; @@ -166,9 +196,10 @@ Broker::Broker(const Broker::Options& conf) : conf.replayFlushLimit*1024, // convert kb to bytes. conf.replayHardLimit*1024), *this), - queueCleaner(queues, timer), - queueEvents(poller,!conf.asyncQueueEvents), + queueCleaner(queues, &timer), + queueEvents(poller,!conf.asyncQueueEvents), recovery(true), + inCluster(false), clusterUpdatee(false), expiryPolicy(new ExpiryPolicy), connectionCounter(conf.maxConnections), @@ -225,8 +256,11 @@ Broker::Broker(const Broker::Options& conf) : // Early-Initialize plugins Plugin::earlyInitAll(*this); + QueueFlowLimit::setDefaults(conf.queueLimit, conf.queueFlowStopRatio, conf.queueFlowResumeRatio); + MessageGroupManager::setDefaults(conf.defaultMsgGroup); + // If no plugin store module registered itself, set up the null store. - if (NullMessageStore::isNullStore(store.get())) + if (NullMessageStore::isNullStore(store.get())) setStore(); exchanges.declare(empty, DirectExchange::typeName); // Default exchange. @@ -271,6 +305,11 @@ Broker::Broker(const Broker::Options& conf) : else QPID_LOG(info, "Management not enabled"); + // this feature affects performance, so let's be sure that gets logged! + if (conf.timestampRcvMsgs) { + QPID_LOG(notice, "Receive message timestamping is ENABLED."); + } + /** * SASL setup, can fail and terminate startup */ @@ -345,14 +384,14 @@ void Broker::run() { Dispatcher d(poller); int numIOThreads = config.workerThreads; std::vector<Thread> t(numIOThreads-1); - + // Run n-1 io threads for (int i=0; i<numIOThreads-1; ++i) t[i] = Thread(d); - + // 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(); @@ -399,9 +438,9 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, { case _qmf::Broker::METHOD_ECHO : QPID_LOG (debug, "Broker::echo(" - << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_sequence - << ", " - << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_body + << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_sequence + << ", " + << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_body << ")"); status = Manageable::STATUS_OK; break; @@ -409,8 +448,9 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, _qmf::ArgsBrokerConnect& hp= dynamic_cast<_qmf::ArgsBrokerConnect&>(args); - QPID_LOG (debug, "Broker::connect()"); string transport = hp.i_transport.empty() ? TCP_TRANSPORT : hp.i_transport; + QPID_LOG (debug, "Broker::connect() " << hp.i_host << ":" << hp.i_port << "; transport=" << transport << + "; durable=" << (hp.i_durable?"T":"F") << "; authMech=\"" << hp.i_authMechanism << "\""); if (!getProtocolFactory(transport)) { QPID_LOG(error, "Transport '" << transport << "' not supported"); return Manageable::STATUS_NOT_IMPLEMENTED; @@ -427,9 +467,9 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, _qmf::ArgsBrokerQueueMoveMessages& moveArgs= dynamic_cast<_qmf::ArgsBrokerQueueMoveMessages&>(args); QPID_LOG (debug, "Broker::queueMoveMessages()"); - if (queueMoveMessages(moveArgs.i_srcQueue, moveArgs.i_destQueue, moveArgs.i_qty)) + if (queueMoveMessages(moveArgs.i_srcQueue, moveArgs.i_destQueue, moveArgs.i_qty, moveArgs.i_filter)) status = Manageable::STATUS_OK; - else + else return Manageable::STATUS_PARAMETER_INVALID; break; } @@ -443,6 +483,38 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, QPID_LOG (debug, "Broker::getLogLevel()"); status = Manageable::STATUS_OK; break; + case _qmf::Broker::METHOD_CREATE : + { + _qmf::ArgsBrokerCreate& a = dynamic_cast<_qmf::ArgsBrokerCreate&>(args); + createObject(a.i_type, a.i_name, a.i_properties, a.i_strict, getManagementExecutionContext()); + status = Manageable::STATUS_OK; + break; + } + case _qmf::Broker::METHOD_DELETE : + { + _qmf::ArgsBrokerDelete& a = dynamic_cast<_qmf::ArgsBrokerDelete&>(args); + deleteObject(a.i_type, a.i_name, a.i_options, getManagementExecutionContext()); + status = Manageable::STATUS_OK; + break; + } + case _qmf::Broker::METHOD_QUERY : + { + _qmf::ArgsBrokerQuery& a = dynamic_cast<_qmf::ArgsBrokerQuery&>(args); + status = queryObject(a.i_type, a.i_name, a.o_results, getManagementExecutionContext()); + break; + } + case _qmf::Broker::METHOD_GETTIMESTAMPCONFIG: + { + _qmf::ArgsBrokerGetTimestampConfig& a = dynamic_cast<_qmf::ArgsBrokerGetTimestampConfig&>(args); + status = getTimestampConfig(a.o_receive, getManagementExecutionContext()); + break; + } + case _qmf::Broker::METHOD_SETTIMESTAMPCONFIG: + { + _qmf::ArgsBrokerSetTimestampConfig& a = dynamic_cast<_qmf::ArgsBrokerSetTimestampConfig&>(args); + status = setTimestampConfig(a.i_receive, getManagementExecutionContext()); + break; + } default: QPID_LOG (debug, "Broker ManagementMethod not implemented: id=" << methodId << "]"); status = Manageable::STATUS_NOT_IMPLEMENTED; @@ -452,6 +524,240 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, return status; } +namespace +{ +const std::string TYPE_QUEUE("queue"); +const std::string TYPE_EXCHANGE("exchange"); +const std::string TYPE_TOPIC("topic"); +const std::string TYPE_BINDING("binding"); +const std::string DURABLE("durable"); +const std::string AUTO_DELETE("auto-delete"); +const std::string ALTERNATE_EXCHANGE("alternate-exchange"); +const std::string EXCHANGE_TYPE("exchange-type"); +const std::string QUEUE_NAME("queue"); +const std::string EXCHANGE_NAME("exchange"); + +const std::string ATTRIBUTE_TIMESTAMP_0_10("timestamp-0.10"); + +const std::string _TRUE("true"); +const std::string _FALSE("false"); +} + +struct InvalidBindingIdentifier : public qpid::Exception +{ + InvalidBindingIdentifier(const std::string& name) : qpid::Exception(name) {} + std::string getPrefix() const { return "invalid binding"; } +}; + +struct BindingIdentifier +{ + std::string exchange; + std::string queue; + std::string key; + + BindingIdentifier(const std::string& name) + { + std::vector<std::string> path; + split(path, name, "/"); + switch (path.size()) { + case 1: + queue = path[0]; + break; + case 2: + exchange = path[0]; + queue = path[1]; + break; + case 3: + exchange = path[0]; + queue = path[1]; + key = path[2]; + break; + default: + throw InvalidBindingIdentifier(name); + } + } +}; + +struct ObjectAlreadyExists : public qpid::Exception +{ + ObjectAlreadyExists(const std::string& name) : qpid::Exception(name) {} + std::string getPrefix() const { return "object already exists"; } +}; + +struct UnknownObjectType : public qpid::Exception +{ + UnknownObjectType(const std::string& type) : qpid::Exception(type) {} + std::string getPrefix() const { return "unknown object type"; } +}; + +void Broker::createObject(const std::string& type, const std::string& name, + const Variant::Map& properties, bool /*strict*/, const ConnectionState* context) +{ + std::string userId; + std::string connectionId; + if (context) { + userId = context->getUserId(); + connectionId = context->getUrl(); + } + //TODO: implement 'strict' option (check there are no unrecognised properties) + QPID_LOG (debug, "Broker::create(" << type << ", " << name << "," << properties << ")"); + if (type == TYPE_QUEUE) { + bool durable(false); + bool autodelete(false); + std::string alternateExchange; + Variant::Map extensions; + for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + // extract durable, auto-delete and alternate-exchange properties + if (i->first == DURABLE) durable = i->second; + else if (i->first == AUTO_DELETE) autodelete = i->second; + else if (i->first == ALTERNATE_EXCHANGE) alternateExchange = i->second.asString(); + //treat everything else as extension properties + else extensions[i->first] = i->second; + } + framing::FieldTable arguments; + amqp_0_10::translate(extensions, arguments); + + std::pair<boost::shared_ptr<Queue>, bool> result = + createQueue(name, durable, autodelete, 0, alternateExchange, arguments, userId, connectionId); + if (!result.second) { + throw ObjectAlreadyExists(name); + } + } else if (type == TYPE_EXCHANGE || type == TYPE_TOPIC) { + bool durable(false); + std::string exchangeType("topic"); + std::string alternateExchange; + Variant::Map extensions; + for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + // extract durable, auto-delete and alternate-exchange properties + if (i->first == DURABLE) durable = i->second; + else if (i->first == EXCHANGE_TYPE) exchangeType = i->second.asString(); + else if (i->first == ALTERNATE_EXCHANGE) alternateExchange = i->second.asString(); + //treat everything else as extension properties + else extensions[i->first] = i->second; + } + framing::FieldTable arguments; + amqp_0_10::translate(extensions, arguments); + + try { + std::pair<boost::shared_ptr<Exchange>, bool> result = + createExchange(name, exchangeType, durable, alternateExchange, arguments, userId, connectionId); + if (!result.second) { + throw ObjectAlreadyExists(name); + } + } catch (const UnknownExchangeTypeException&) { + throw Exception(QPID_MSG("Invalid exchange type: " << exchangeType)); + } + } else if (type == TYPE_BINDING) { + BindingIdentifier binding(name); + std::string exchangeType("topic"); + Variant::Map extensions; + for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + // extract durable, auto-delete and alternate-exchange properties + if (i->first == EXCHANGE_TYPE) exchangeType = i->second.asString(); + //treat everything else as extension properties + else extensions[i->first] = i->second; + } + framing::FieldTable arguments; + amqp_0_10::translate(extensions, arguments); + + bind(binding.queue, binding.exchange, binding.key, arguments, userId, connectionId); + } else { + throw UnknownObjectType(type); + } +} + +void Broker::deleteObject(const std::string& type, const std::string& name, + const Variant::Map& options, const ConnectionState* context) +{ + std::string userId; + std::string connectionId; + if (context) { + userId = context->getUserId(); + connectionId = context->getUrl(); + } + QPID_LOG (debug, "Broker::delete(" << type << ", " << name << "," << options << ")"); + if (type == TYPE_QUEUE) { + deleteQueue(name, userId, connectionId); + } else if (type == TYPE_EXCHANGE || type == TYPE_TOPIC) { + deleteExchange(name, userId, connectionId); + } else if (type == TYPE_BINDING) { + BindingIdentifier binding(name); + unbind(binding.queue, binding.exchange, binding.key, userId, connectionId); + } else { + throw UnknownObjectType(type); + } + +} + +Manageable::status_t Broker::queryObject(const std::string& type, + const std::string& name, + Variant::Map& results, + const ConnectionState* context) +{ + std::string userId; + std::string connectionId; + if (context) { + userId = context->getUserId(); + connectionId = context->getUrl(); + } + QPID_LOG (debug, "Broker::query(" << type << ", " << name << ")"); + + if (type == TYPE_QUEUE) + return queryQueue( name, userId, connectionId, results ); + + if (type == TYPE_EXCHANGE || + type == TYPE_TOPIC || + type == TYPE_BINDING) + return Manageable::STATUS_NOT_IMPLEMENTED; + + throw UnknownObjectType(type); +} + +Manageable::status_t Broker::queryQueue( const std::string& name, + const std::string& userId, + const std::string& /*connectionId*/, + Variant::Map& results ) +{ + (void) results; + if (acl) { + if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_QUEUE, name, NULL) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue query request from " << userId)); + } + + boost::shared_ptr<Queue> q(queues.find(name)); + if (!q) { + QPID_LOG(error, "Query failed: queue not found, name=" << name); + return Manageable::STATUS_UNKNOWN_OBJECT; + } + q->query( results ); + return Manageable::STATUS_OK;; +} + +Manageable::status_t Broker::getTimestampConfig(bool& receive, + const ConnectionState* context) +{ + std::string name; // none needed for broker + std::string userId = context->getUserId(); + if (acl && !acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_BROKER, name, NULL)) { + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied broker timestamp get request from " << userId)); + } + receive = config.timestampRcvMsgs; + return Manageable::STATUS_OK; +} + +Manageable::status_t Broker::setTimestampConfig(const bool receive, + const ConnectionState* context) +{ + std::string name; // none needed for broker + std::string userId = context->getUserId(); + if (acl && !acl->authorise(userId, acl::ACT_UPDATE, acl::OBJ_BROKER, name, NULL)) { + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied broker timestamp set request from " << userId)); + } + config.timestampRcvMsgs = receive; + QPID_LOG(notice, "Receive message timestamping is " << ((config.timestampRcvMsgs) ? "ENABLED." : "DISABLED.")); + return Manageable::STATUS_OK; +} + void Broker::setLogLevel(const std::string& level) { QPID_LOG(notice, "Changing log level to " << level); @@ -466,7 +772,7 @@ std::string Broker::getLogLevel() const std::vector<std::string>& selectors = qpid::log::Logger::instance().getOptions().selectors; for (std::vector<std::string>::const_iterator i = selectors.begin(); i != selectors.end(); ++i) { if (i != selectors.begin()) level += std::string(","); - level += *i; + level += *i; } return level; } @@ -499,7 +805,7 @@ void Broker::accept() { } void Broker::connect( - const std::string& host, uint16_t port, const std::string& transport, + const std::string& host, const std::string& port, const std::string& transport, boost::function2<void, int, std::string> failed, sys::ConnectionCodec::Factory* f) { @@ -515,13 +821,14 @@ void Broker::connect( { url.throwIfEmpty(); const Address& addr=url[0]; - connect(addr.host, addr.port, addr.protocol, failed, f); + connect(addr.host, boost::lexical_cast<std::string>(addr.port), addr.protocol, failed, f); } uint32_t Broker::queueMoveMessages( const std::string& srcQueue, const std::string& destQueue, - uint32_t qty) + uint32_t qty, + const Variant::Map& filter) { Queue::shared_ptr src_queue = queues.find(srcQueue); if (!src_queue) @@ -530,7 +837,7 @@ uint32_t Broker::queueMoveMessages( if (!dest_queue) return 0; - return src_queue->move(dest_queue, qty); + return src_queue->move(dest_queue, qty, &filter); } @@ -548,9 +855,228 @@ bool Broker::deferDeliveryImpl(const std::string& , void Broker::setClusterTimer(std::auto_ptr<sys::Timer> t) { clusterTimer = t; + queueCleaner.setTimer(clusterTimer.get()); + dtxManager.setTimer(*clusterTimer.get()); } const std::string Broker::TCP_TRANSPORT("tcp"); + +std::pair<boost::shared_ptr<Queue>, bool> Broker::createQueue( + const std::string& name, + bool durable, + bool autodelete, + const OwnershipToken* owner, + const std::string& alternateExchange, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId) +{ + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PROP_PASSIVE, _FALSE)); + params.insert(make_pair(acl::PROP_DURABLE, durable ? _TRUE : _FALSE)); + params.insert(make_pair(acl::PROP_EXCLUSIVE, owner ? _TRUE : _FALSE)); + params.insert(make_pair(acl::PROP_AUTODELETE, 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(userId,acl::ACT_CREATE,acl::OBJ_QUEUE,name,¶ms) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << userId)); + } + + Exchange::shared_ptr alternate; + if (!alternateExchange.empty()) { + alternate = exchanges.get(alternateExchange); + if (!alternate) throw framing::NotFoundException(QPID_MSG("Alternate exchange does not exist: " << alternateExchange)); + } + + std::pair<Queue::shared_ptr, bool> result = queues.declare(name, durable, autodelete, owner, alternate, arguments); + if (result.second) { + //add default binding: + result.first->bind(exchanges.getDefault(), name); + + if (managementAgent.get()) { + //TODO: debatable whether we should raise an event here for + //create when this is a 'declare' event; ideally add a create + //event instead? + managementAgent->raiseEvent( + _qmf::EventQueueDeclare(connectionId, userId, name, + durable, owner, autodelete, + ManagementAgent::toMap(arguments), + "created")); + } + } + return result; +} + +void Broker::deleteQueue(const std::string& name, const std::string& userId, + const std::string& connectionId, QueueFunctor check) +{ + if (acl && !acl->authorise(userId,acl::ACT_DELETE,acl::OBJ_QUEUE,name,NULL)) { + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue delete request from " << userId)); + } + + Queue::shared_ptr queue = queues.find(name); + if (queue) { + if (check) check(queue); + queues.destroy(name); + queue->destroyed(); + } else { + throw framing::NotFoundException(QPID_MSG("Delete failed. No such queue: " << name)); + } + + if (managementAgent.get()) + managementAgent->raiseEvent(_qmf::EventQueueDelete(connectionId, userId, name)); + +} + +std::pair<Exchange::shared_ptr, bool> Broker::createExchange( + const std::string& name, + const std::string& type, + bool durable, + const std::string& alternateExchange, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId) +{ + 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, _FALSE)); + params.insert(make_pair(acl::PROP_DURABLE, durable ? _TRUE : _FALSE)); + if (!acl->authorise(userId,acl::ACT_CREATE,acl::OBJ_EXCHANGE,name,¶ms) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange create request from " << userId)); + } + + Exchange::shared_ptr alternate; + if (!alternateExchange.empty()) { + alternate = exchanges.get(alternateExchange); + if (!alternate) throw framing::NotFoundException(QPID_MSG("Alternate exchange does not exist: " << alternateExchange)); + } + + std::pair<Exchange::shared_ptr, bool> result; + result = exchanges.declare(name, type, durable, arguments); + if (result.second) { + if (alternate) { + result.first->setAlternate(alternate); + alternate->incAlternateUsers(); + } + if (durable) { + store->create(*result.first, arguments); + } + if (managementAgent.get()) { + //TODO: debatable whether we should raise an event here for + //create when this is a 'declare' event; ideally add a create + //event instead? + managementAgent->raiseEvent(_qmf::EventExchangeDeclare(connectionId, + userId, + name, + type, + alternateExchange, + durable, + false, + ManagementAgent::toMap(arguments), + "created")); + } + } + return result; +} + +void Broker::deleteExchange(const std::string& name, const std::string& userId, + const std::string& connectionId) +{ + if (acl) { + if (!acl->authorise(userId,acl::ACT_DELETE,acl::OBJ_EXCHANGE,name,NULL) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange delete request from " << userId)); + } + + if (name.empty()) { + throw framing::InvalidArgumentException(QPID_MSG("Delete not allowed for default exchange")); + } + Exchange::shared_ptr exchange(exchanges.get(name)); + if (!exchange) throw framing::NotFoundException(QPID_MSG("Delete failed. No such exchange: " << name)); + if (exchange->inUseAsAlternate()) throw framing::NotAllowedException(QPID_MSG("Exchange in use as alternate-exchange.")); + if (exchange->isDurable()) store->destroy(*exchange); + if (exchange->getAlternate()) exchange->getAlternate()->decAlternateUsers(); + exchanges.destroy(name); + + if (managementAgent.get()) + managementAgent->raiseEvent(_qmf::EventExchangeDelete(connectionId, userId, name)); + +} + +void Broker::bind(const std::string& queueName, + const std::string& exchangeName, + const std::string& key, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId) +{ + 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(userId,acl::ACT_BIND,acl::OBJ_EXCHANGE,exchangeName,¶ms)) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange bind request from " << userId)); + } + if (exchangeName.empty()) { + throw framing::InvalidArgumentException(QPID_MSG("Bind not allowed for default exchange")); + } + + Queue::shared_ptr queue = queues.find(queueName); + Exchange::shared_ptr exchange = exchanges.get(exchangeName); + if (!queue) { + throw framing::NotFoundException(QPID_MSG("Bind failed. No such queue: " << queueName)); + } else if (!exchange) { + throw framing::NotFoundException(QPID_MSG("Bind failed. No such exchange: " << exchangeName)); + } else { + if (queue->bind(exchange, key, arguments)) { + if (managementAgent.get()) { + managementAgent->raiseEvent(_qmf::EventBind(connectionId, userId, exchangeName, + queueName, key, ManagementAgent::toMap(arguments))); + } + } + } +} + +void Broker::unbind(const std::string& queueName, + const std::string& exchangeName, + const std::string& key, + const std::string& userId, + const std::string& connectionId) +{ + 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(userId,acl::ACT_UNBIND,acl::OBJ_EXCHANGE,exchangeName,¶ms) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange unbind request from " << userId)); + } + if (exchangeName.empty()) { + throw framing::InvalidArgumentException(QPID_MSG("Unbind not allowed for default exchange")); + } + Queue::shared_ptr queue = queues.find(queueName); + Exchange::shared_ptr exchange = exchanges.get(exchangeName); + if (!queue) { + throw framing::NotFoundException(QPID_MSG("Unbind failed. No such queue: " << queueName)); + } else if (!exchange) { + throw framing::NotFoundException(QPID_MSG("Unbind failed. No such exchange: " << exchangeName)); + } else { + if (exchange->unbind(queue, key, 0)) { + if (exchange->isDurable() && queue->isDurable()) { + store->unbind(*exchange, *queue, key, qpid::framing::FieldTable()); + } + if (managementAgent.get()) { + managementAgent->raiseEvent(_qmf::EventUnbind(connectionId, userId, exchangeName, queueName, key)); + } + } + } +} + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Broker.h b/cpp/src/qpid/broker/Broker.h index cd6f81dc70..b3b751be98 100644 --- a/cpp/src/qpid/broker/Broker.h +++ b/cpp/src/qpid/broker/Broker.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 @@ -49,6 +49,7 @@ #include "qpid/framing/ProtocolInitiation.h" #include "qpid/sys/Runnable.h" #include "qpid/sys/Timer.h" +#include "qpid/types/Variant.h" #include "qpid/RefCounted.h" #include "qpid/broker/AclModule.h" #include "qpid/sys/Mutex.h" @@ -57,7 +58,7 @@ #include <string> #include <vector> -namespace qpid { +namespace qpid { namespace sys { class ProtocolFactory; @@ -68,6 +69,7 @@ struct Url; namespace broker { +class ConnectionState; class ExpiryPolicy; class Message; @@ -80,7 +82,7 @@ struct NoSuchTransportException : qpid::Exception }; /** - * A broker instance. + * A broker instance. */ class Broker : public sys::Runnable, public Plugin::Target, public management::Manageable, @@ -116,29 +118,34 @@ public: bool asyncQueueEvents; bool qmf2Support; bool qmf1Support; + uint queueFlowStopRatio; // producer flow control: on + uint queueFlowResumeRatio; // producer flow control: off + uint16_t queueThresholdEventRatio; + std::string defaultMsgGroup; + bool timestampRcvMsgs; 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); + void inc_connectionCount() { + sys::ScopedLock<sys::Mutex> l(connectionCountLock); connectionCount++; - } - void dec_connectionCount() { - sys::ScopedLock<sys::Mutex> l(connectionCountLock); + } + void dec_connectionCount() { + sys::ScopedLock<sys::Mutex> l(connectionCountLock); connectionCount--; } bool allowConnection() { - sys::ScopedLock<sys::Mutex> l(connectionCountLock); + sys::ScopedLock<sys::Mutex> l(connectionCountLock); return (maxConnections <= connectionCount); - } + } }; private: @@ -148,7 +155,20 @@ public: void setStore (); void setLogLevel(const std::string& level); std::string getLogLevel(); - + void createObject(const std::string& type, const std::string& name, + const qpid::types::Variant::Map& properties, bool strict, const ConnectionState* context); + void deleteObject(const std::string& type, const std::string& name, + const qpid::types::Variant::Map& options, const ConnectionState* context); + Manageable::status_t queryObject(const std::string& type, const std::string& name, + qpid::types::Variant::Map& results, const ConnectionState* context); + Manageable::status_t queryQueue( const std::string& name, + const std::string& userId, + const std::string& connectionId, + qpid::types::Variant::Map& results); + Manageable::status_t getTimestampConfig(bool& receive, + const ConnectionState* context); + Manageable::status_t setTimestampConfig(const bool receive, + const ConnectionState* context); boost::shared_ptr<sys::Poller> poller; sys::Timer timer; std::auto_ptr<sys::Timer> clusterTimer; @@ -176,10 +196,10 @@ public: const boost::intrusive_ptr<Message>& msg); std::string federationTag; bool recovery; - bool clusterUpdatee; + bool inCluster, clusterUpdatee; boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; ConnectionCounter connectionCounter; - + public: virtual ~Broker(); @@ -235,7 +255,7 @@ public: QPID_BROKER_EXTERN void accept(); /** Create a connection to another broker. */ - void connect(const std::string& host, uint16_t port, + void connect(const std::string& host, const std::string& port, const std::string& transport, boost::function2<void, int, std::string> failed, sys::ConnectionCodec::Factory* =0); @@ -247,9 +267,10 @@ public: /** Move messages from one queue to another. A zero quantity means to move all messages */ - uint32_t queueMoveMessages( const std::string& srcQueue, + uint32_t queueMoveMessages( const std::string& srcQueue, const std::string& destQueue, - uint32_t qty); + uint32_t qty, + const qpid::types::Variant::Map& filter); boost::shared_ptr<sys::ProtocolFactory> getProtocolFactory(const std::string& name = TCP_TRANSPORT) const; @@ -273,11 +294,20 @@ public: void setRecovery(bool set) { recovery = set; } bool getRecovery() const { return recovery; } - void setClusterUpdatee(bool set) { clusterUpdatee = set; } + /** True of this broker is part of a cluster. + * Only valid after early initialization of plugins is complete. + */ + bool isInCluster() const { return inCluster; } + void setInCluster(bool set) { inCluster = set; } + + /** True if this broker is joining a cluster and in the process of + * receiving a state update. + */ bool isClusterUpdatee() const { return clusterUpdatee; } + void setClusterUpdatee(bool set) { clusterUpdatee = set; } management::ManagementAgent* getManagementAgent() { return managementAgent.get(); } - + ConnectionCounter& getConnectionCounter() {return connectionCounter;} /** @@ -290,6 +320,43 @@ public: const boost::intrusive_ptr<Message>& msg)> deferDelivery; bool isAuthenticating ( ) { return config.auth; } + bool isTimestamping() { return config.timestampRcvMsgs; } + + typedef boost::function1<void, boost::shared_ptr<Queue> > QueueFunctor; + + std::pair<boost::shared_ptr<Queue>, bool> createQueue( + const std::string& name, + bool durable, + bool autodelete, + const OwnershipToken* owner, + const std::string& alternateExchange, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId); + void deleteQueue(const std::string& name, + const std::string& userId, + const std::string& connectionId, + QueueFunctor check = QueueFunctor()); + std::pair<Exchange::shared_ptr, bool> createExchange( + const std::string& name, + const std::string& type, + bool durable, + const std::string& alternateExchange, + const qpid::framing::FieldTable& args, + const std::string& userId, const std::string& connectionId); + void deleteExchange(const std::string& name, const std::string& userId, + const std::string& connectionId); + void bind(const std::string& queue, + const std::string& exchange, + const std::string& key, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId); + void unbind(const std::string& queue, + const std::string& exchange, + const std::string& key, + const std::string& userId, + const std::string& connectionId); }; }} diff --git a/cpp/src/qpid/broker/BrokerImportExport.h b/cpp/src/qpid/broker/BrokerImportExport.h index 4edf8c9844..ee05788063 100644 --- a/cpp/src/qpid/broker/BrokerImportExport.h +++ b/cpp/src/qpid/broker/BrokerImportExport.h @@ -20,14 +20,23 @@ * under the License. */ -#if defined(WIN32) && !defined(QPID_BROKER_STATIC) -#if defined(BROKER_EXPORT) || defined (qpidbroker_EXPORTS) -#define QPID_BROKER_EXTERN __declspec(dllexport) +#if defined(WIN32) && !defined(QPID_DECLARE_STATIC) +# if defined(BROKER_EXPORT) || defined (qpidbroker_EXPORTS) +# define QPID_BROKER_EXTERN __declspec(dllexport) +# else +# define QPID_BROKER_EXTERN __declspec(dllimport) +# endif +# ifdef _MSC_VER +# define QPID_BROKER_CLASS_EXTERN +# define QPID_BROKER_INLINE_EXTERN QPID_BROKER_EXTERN +# else +# define QPID_BROKER_CLASS_EXTERN QPID_BROKER_EXTERN +# define QPID_BROKER_INLINE_EXTERN +# endif #else -#define QPID_BROKER_EXTERN __declspec(dllimport) -#endif -#else -#define QPID_BROKER_EXTERN +# define QPID_BROKER_EXTERN +# define QPID_BROKER_CLASS_EXTERN +# define QPID_BROKER_INLINE_EXTERN #endif #endif diff --git a/cpp/src/qpid/broker/Connection.cpp b/cpp/src/qpid/broker/Connection.cpp index 460799280e..0b3059d26c 100644 --- a/cpp/src/qpid/broker/Connection.cpp +++ b/cpp/src/qpid/broker/Connection.cpp @@ -156,16 +156,7 @@ Connection::~Connection() void Connection::received(framing::AMQFrame& frame) { // Received frame on connection so delay timeout restartTimeout(); - - if (frame.getChannel() == 0 && frame.getMethod()) { - adapter.handle(frame); - } else { - if (adapter.isOpen()) - getChannel(frame.getChannel()).in(frame); - else - close(connection::CLOSE_CODE_FRAMING_ERROR, "Connection not yet open, invalid frame received."); - } - + adapter.handle(frame); if (isLink) //i.e. we are acting as the client to another broker recordFromServer(frame); else @@ -278,8 +269,7 @@ void Connection::setUserId(const string& userId) ConnectionState::setUserId(userId); // In a cluster, the cluster code will raise the connect event // when the connection is replicated to the cluster. - if (!sys::isCluster()) - raiseConnectEvent(); + if (!broker.isInCluster()) raiseConnectEvent(); } void Connection::raiseConnectEvent() { @@ -289,11 +279,11 @@ void Connection::raiseConnectEvent() { } } -void Connection::setFederationLink(bool b) +void Connection::setUserProxyAuth(bool b) { - ConnectionState::setFederationLink(b); + ConnectionState::setUserProxyAuth(b); if (mgmtObject != 0) - mgmtObject->set_federationLink(b); + mgmtObject->set_userProxyAuth(b); } void Connection::close(connection::CloseCode code, const string& text) @@ -332,31 +322,30 @@ void Connection::closed(){ // Physically closed, suspend open sessions. try { while (!channels.empty()) ptr_map_ptr(channels.begin())->handleDetach(); - while (!exclusiveQueues.empty()) { - boost::shared_ptr<Queue> q(exclusiveQueues.front()); - q->releaseExclusiveOwnership(); - if (q->canAutoDelete()) { - Queue::tryAutoDelete(broker, q); - } - exclusiveQueues.erase(exclusiveQueues.begin()); - } } catch(std::exception& e) { QPID_LOG(error, QPID_MSG("While closing connection: " << e.what())); assert(0); } } +void Connection::doIoCallbacks() { + { + ScopedLock<Mutex> l(ioCallbackLock); + // Although IO callbacks execute in the connection thread context, they are + // not cluster safe because they are queued for execution in non-IO threads. + ClusterUnsafeScope cus; + while (!ioCallbacks.empty()) { + boost::function0<void> cb = ioCallbacks.front(); + ioCallbacks.pop(); + ScopedUnlock<Mutex> ul(ioCallbackLock); + cb(); // Lend the IO thread for management processing + } + } +} + 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 - } - } + doIoCallbacks(); if (mgmtClosing) { closed(); close(connection::CLOSE_CODE_CONNECTION_FORCED, "Closed by Management Request"); @@ -476,8 +465,8 @@ void Connection::OutboundFrameTracker::abort() { next->abort(); } void Connection::OutboundFrameTracker::activateOutput() { next->activateOutput(); } void Connection::OutboundFrameTracker::giveReadCredit(int32_t credit) { next->giveReadCredit(credit); } void Connection::OutboundFrameTracker::send(framing::AMQFrame& f) -{ - next->send(f); +{ + next->send(f); con.sent(f); } void Connection::OutboundFrameTracker::wrap(sys::ConnectionOutputHandlerPtr& p) diff --git a/cpp/src/qpid/broker/Connection.h b/cpp/src/qpid/broker/Connection.h index b751848d73..3522d70b35 100644 --- a/cpp/src/qpid/broker/Connection.h +++ b/cpp/src/qpid/broker/Connection.h @@ -125,7 +125,7 @@ class Connection : public sys::ConnectionInputHandler, 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); + void setUserProxyAuth(bool b); /** Connection does not delete the listener. 0 resets. */ void setErrorListener(ErrorListener* l) { errorListener=l; } ErrorListener* getErrorListener() { return errorListener; } @@ -153,13 +153,16 @@ class Connection : public sys::ConnectionInputHandler, void addManagementObject(); const qpid::sys::SecuritySettings& getExternalSecuritySettings() const - { + { return securitySettings; } /** @return true if the initial connection negotiation is complete. */ bool isOpen(); + // Used by cluster during catch-up, see cluster::OutputInterceptor + void doIoCallbacks(); + private: typedef boost::ptr_map<framing::ChannelId, SessionHandler> ChannelMap; typedef std::vector<boost::shared_ptr<Queue> >::iterator queue_iterator; @@ -201,7 +204,7 @@ class Connection : public sys::ConnectionInputHandler, sys::ConnectionOutputHandler* next; }; OutboundFrameTracker outboundTracker; - + void sent(const framing::AMQFrame& f); public: diff --git a/cpp/src/qpid/broker/ConnectionHandler.cpp b/cpp/src/qpid/broker/ConnectionHandler.cpp index 3f97e5b9de..7cd91ae539 100644 --- a/cpp/src/qpid/broker/ConnectionHandler.cpp +++ b/cpp/src/qpid/broker/ConnectionHandler.cpp @@ -26,6 +26,7 @@ #include "qpid/broker/SecureConnection.h" #include "qpid/Url.h" #include "qpid/framing/AllInvoker.h" +#include "qpid/framing/ConnectionStartOkBody.h" #include "qpid/framing/enum.h" #include "qpid/log/Statement.h" #include "qpid/sys/SecurityLayer.h" @@ -63,13 +64,31 @@ void ConnectionHandler::heartbeat() handler->proxy.heartbeat(); } +bool ConnectionHandler::handle(const framing::AMQMethodBody& method) +{ + //Need special handling for start-ok, in order to distinguish + //between null and empty response + if (method.isA<ConnectionStartOkBody>()) { + handler->startOk(dynamic_cast<const ConnectionStartOkBody&>(method)); + return true; + } else { + return invoke(static_cast<AMQP_AllOperations::ConnectionHandler&>(*handler), method); + } +} + void ConnectionHandler::handle(framing::AMQFrame& frame) { AMQMethodBody* method=frame.getBody()->getMethod(); Connection::ErrorListener* errorListener = handler->connection.getErrorListener(); try{ - if (!invoke(static_cast<AMQP_AllOperations::ConnectionHandler&>(*handler.get()), *method)) { + if (method && handle(*method)) { + // This is a connection control frame, nothing more to do. + } else if (isOpen()) { handler->connection.getChannel(frame.getChannel()).in(frame); + } else { + handler->proxy.close( + connection::CLOSE_CODE_FRAMING_ERROR, + "Connection not yet open, invalid frame received."); } }catch(ConnectionException& e){ if (errorListener) errorListener->connectionError(e.what()); @@ -89,13 +108,10 @@ ConnectionHandler::ConnectionHandler(Connection& connection, bool isClient, bool ConnectionHandler::Handler::Handler(Connection& c, bool isClient, bool isShadow) : proxy(c.getOutput()), - connection(c), serverMode(!isClient), acl(0), secured(0), + connection(c), serverMode(!isClient), secured(0), isOpen(false) { if (serverMode) { - - acl = connection.getBroker().getAcl(); - FieldTable properties; Array mechanisms(0x95); @@ -118,13 +134,20 @@ ConnectionHandler::Handler::Handler(Connection& c, bool isClient, bool isShadow) ConnectionHandler::Handler::~Handler() {} -void ConnectionHandler::Handler::startOk(const framing::FieldTable& clientProperties, - const string& mechanism, - const string& response, +void ConnectionHandler::Handler::startOk(const framing::FieldTable& /*clientProperties*/, + const string& /*mechanism*/, + const string& /*response*/, const string& /*locale*/) { + //Need special handling for start-ok, in order to distinguish + //between null and empty response -> should never use this method + assert(false); +} + +void ConnectionHandler::Handler::startOk(const ConnectionStartOkBody& body) +{ try { - authenticator->start(mechanism, response); + authenticator->start(body.getMechanism(), body.hasResponse() ? &body.getResponse() : 0); } catch (std::exception& /*e*/) { management::ManagementAgent* agent = connection.getAgent(); if (agent) { @@ -136,9 +159,14 @@ void ConnectionHandler::Handler::startOk(const framing::FieldTable& clientProper } throw; } + const framing::FieldTable& clientProperties = body.getClientProperties(); connection.setFederationLink(clientProperties.get(QPID_FED_LINK)); - connection.setFederationPeerTag(clientProperties.getAsString(QPID_FED_TAG)); + if (clientProperties.isSet(QPID_FED_TAG)) { + connection.setFederationPeerTag(clientProperties.getAsString(QPID_FED_TAG)); + } if (connection.isFederationLink()) { + AclModule* acl = connection.getBroker().getAcl(); + FieldTable properties; 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; @@ -183,7 +211,7 @@ void ConnectionHandler::Handler::secureOk(const string& response) void ConnectionHandler::Handler::tuneOk(uint16_t /*channelmax*/, uint16_t framemax, uint16_t heartbeat) { - connection.setFrameMax(framemax); + if (framemax) connection.setFrameMax(framemax); connection.setHeartbeatInterval(heartbeat); } @@ -256,7 +284,6 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, false ); // disallow interaction } std::string supportedMechanismsList; - bool requestedMechanismIsSupported = false; Array::const_iterator i; /* @@ -269,11 +296,9 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, if (i != supportedMechanisms.begin()) supportedMechanismsList += SPACE; supportedMechanismsList += (*i)->get<std::string>(); - requestedMechanismIsSupported = true; } } else { - requestedMechanismIsSupported = false; /* The caller has requested a mechanism. If it's available, make sure it ends up at the head of the list. @@ -282,7 +307,6 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, string currentMechanism = (*i)->get<std::string>(); if ( requestedMechanism == currentMechanism ) { - requestedMechanismIsSupported = true; supportedMechanismsList = currentMechanism + SPACE + supportedMechanismsList; } else { if (i != supportedMechanisms.begin()) @@ -292,7 +316,9 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, } } - connection.setFederationPeerTag(serverProperties.getAsString(QPID_FED_TAG)); + if (serverProperties.isSet(QPID_FED_TAG)) { + connection.setFederationPeerTag(serverProperties.getAsString(QPID_FED_TAG)); + } FieldTable ft; ft.setInt(QPID_FED_LINK,1); @@ -301,11 +327,21 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, string response; if (sasl.get()) { const qpid::sys::SecuritySettings& ss = connection.getExternalSecuritySettings(); - response = sasl->start ( requestedMechanism.empty() - ? supportedMechanismsList - : requestedMechanism, - & ss ); - proxy.startOk ( ft, sasl->getMechanism(), response, en_US ); + if (sasl->start ( requestedMechanism.empty() + ? supportedMechanismsList + : requestedMechanism, + response, + & ss )) { + proxy.startOk ( ft, sasl->getMechanism(), response, en_US ); + } else { + //response was null + ConnectionStartOkBody body; + body.setClientProperties(ft); + body.setMechanism(sasl->getMechanism()); + //Don't set response, as none was given + body.setLocale(en_US); + proxy.send(body); + } } else { response = ((char)0) + username + ((char)0) + password; diff --git a/cpp/src/qpid/broker/ConnectionHandler.h b/cpp/src/qpid/broker/ConnectionHandler.h index b32167669e..05c5f00c57 100644 --- a/cpp/src/qpid/broker/ConnectionHandler.h +++ b/cpp/src/qpid/broker/ConnectionHandler.h @@ -26,8 +26,10 @@ #include "qpid/broker/SaslAuthenticator.h" #include "qpid/framing/amqp_types.h" #include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQMethodBody.h" #include "qpid/framing/AMQP_AllOperations.h" #include "qpid/framing/AMQP_AllProxy.h" +#include "qpid/framing/ConnectionStartOkBody.h" #include "qpid/framing/enum.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/ProtocolInitiation.h" @@ -57,12 +59,12 @@ class ConnectionHandler : public framing::FrameHandler Connection& connection; bool serverMode; std::auto_ptr<SaslAuthenticator> authenticator; - AclModule* acl; SecureConnection* secured; bool isOpen; Handler(Connection& connection, bool isClient, bool isShadow=false); ~Handler(); + void startOk(const qpid::framing::ConnectionStartOkBody& body); void startOk(const qpid::framing::FieldTable& clientProperties, const std::string& mechanism, const std::string& response, const std::string& locale); @@ -96,7 +98,7 @@ class ConnectionHandler : public framing::FrameHandler }; std::auto_ptr<Handler> handler; - + bool handle(const qpid::framing::AMQMethodBody& method); public: ConnectionHandler(Connection& connection, bool isClient, bool isShadow=false ); void close(framing::connection::CloseCode code, const std::string& text); diff --git a/cpp/src/qpid/broker/ConnectionState.h b/cpp/src/qpid/broker/ConnectionState.h index 774c37408d..fdd3c4ddc0 100644 --- a/cpp/src/qpid/broker/ConnectionState.h +++ b/cpp/src/qpid/broker/ConnectionState.h @@ -46,6 +46,7 @@ class ConnectionState : public ConnectionToken, public management::Manageable framemax(65535), heartbeat(0), heartbeatmax(120), + userProxyAuth(false), // Can proxy msgs with non-matching auth ids when true (used by federation links & clustering) federationLink(true), clientSupportsThrottling(false), clusterOrderOut(0) @@ -67,8 +68,10 @@ class ConnectionState : public ConnectionToken, public management::Manageable void setUrl(const std::string& _url) { url = _url; } const std::string& getUrl() const { return url; } - void setFederationLink(bool b) { federationLink = b; } - bool isFederationLink() const { return federationLink; } + void setUserProxyAuth(const bool b) { userProxyAuth = b; } + bool isUserProxyAuth() const { return userProxyAuth || federationPeerTag.size() > 0; } // links can proxy msgs with non-matching auth ids + void setFederationLink(bool b) { federationLink = b; } // deprecated - use setFederationPeerTag() instead + bool isFederationLink() const { return federationPeerTag.size() > 0; } void setFederationPeerTag(const std::string& tag) { federationPeerTag = std::string(tag); } const std::string& getFederationPeerTag() const { return federationPeerTag; } std::vector<Url>& getKnownHosts() { return knownHosts; } @@ -79,7 +82,6 @@ class ConnectionState : public ConnectionToken, public management::Manageable Broker& getBroker() { return broker; } Broker& broker; - std::vector<boost::shared_ptr<Queue> > exclusiveQueues; //contained output tasks sys::AggregateOutput outputTasks; @@ -106,6 +108,7 @@ class ConnectionState : public ConnectionToken, public management::Manageable uint16_t heartbeatmax; std::string userId; std::string url; + bool userProxyAuth; bool federationLink; std::string federationPeerTag; std::vector<Url> knownHosts; diff --git a/cpp/src/qpid/broker/Consumer.h b/cpp/src/qpid/broker/Consumer.h index b96443fa7c..2af9b0c121 100644 --- a/cpp/src/qpid/broker/Consumer.h +++ b/cpp/src/qpid/broker/Consumer.h @@ -29,22 +29,33 @@ namespace qpid { namespace broker { class Queue; +class QueueListeners; class Consumer { const bool acquires; - public: - typedef boost::shared_ptr<Consumer> shared_ptr; - + // inListeners allows QueueListeners to efficiently track if this instance is registered + // for notifications without having to search its containers + bool inListeners; + // the name is generated by broker and is unique within broker scope. It is not + // provided or known by the remote Consumer. + const std::string name; + public: + typedef boost::shared_ptr<Consumer> shared_ptr; + framing::SequenceNumber position; - - Consumer(bool preAcquires = true) : acquires(preAcquires) {} + + Consumer(const std::string& _name, bool preAcquires = true) + : acquires(preAcquires), inListeners(false), name(_name), position(0) {} bool preAcquires() const { return acquires; } + const std::string& getName() const { return name; } + 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(){} + friend class QueueListeners; }; }} diff --git a/cpp/src/qpid/broker/Daemon.cpp b/cpp/src/qpid/broker/Daemon.cpp index b30e5f18cb..c36538beb7 100644 --- a/cpp/src/qpid/broker/Daemon.cpp +++ b/cpp/src/qpid/broker/Daemon.cpp @@ -93,11 +93,10 @@ void Daemon::fork() catch (const exception& e) { QPID_LOG(critical, "Unexpected error: " << e.what()); uint16_t port = 0; - int unused_ret; //Supress warning about ignoring return value. - unused_ret = write(pipeFds[1], &port, sizeof(uint16_t)); + (void) write(pipeFds[1], &port, sizeof(uint16_t)); std::string pipeFailureMessage = e.what(); - unused_ret = write ( pipeFds[1], + (void) write ( pipeFds[1], pipeFailureMessage.c_str(), strlen(pipeFailureMessage.c_str()) ); diff --git a/cpp/src/qpid/broker/DeliverableMessage.h b/cpp/src/qpid/broker/DeliverableMessage.h index ce613e7b6e..c8d21001eb 100644 --- a/cpp/src/qpid/broker/DeliverableMessage.h +++ b/cpp/src/qpid/broker/DeliverableMessage.h @@ -29,7 +29,7 @@ namespace qpid { namespace broker { - class DeliverableMessage : public Deliverable{ + class QPID_BROKER_CLASS_EXTERN DeliverableMessage : public Deliverable{ boost::intrusive_ptr<Message> msg; public: QPID_BROKER_EXTERN DeliverableMessage(const boost::intrusive_ptr<Message>& msg); diff --git a/cpp/src/qpid/broker/DeliveryRecord.cpp b/cpp/src/qpid/broker/DeliveryRecord.cpp index 9443eb6ea5..0b8fe95d5e 100644 --- a/cpp/src/qpid/broker/DeliveryRecord.cpp +++ b/cpp/src/qpid/broker/DeliveryRecord.cpp @@ -75,7 +75,7 @@ void DeliveryRecord::deliver(framing::FrameHandler& h, DeliveryId deliveryId, ui { id = deliveryId; if (msg.payload->getRedelivered()){ - msg.payload->getProperties<framing::DeliveryProperties>()->setRedelivered(true); + msg.payload->setRedelivered(); } msg.payload->adjustTtl(); @@ -131,18 +131,20 @@ void DeliveryRecord::committed() const{ void DeliveryRecord::reject() { - Exchange::shared_ptr alternate = queue->getAlternateExchange(); - if (alternate) { - DeliverableMessage delivery(msg.payload); - alternate->route(delivery, msg.payload->getRoutingKey(), msg.payload->getApplicationHeaders()); - QPID_LOG(info, "Routed rejected message from " << queue->getName() << " to " - << alternate->getName()); - } else { - //just drop it - QPID_LOG(info, "Dropping rejected message from " << queue->getName()); + if (acquired && !ended) { + Exchange::shared_ptr alternate = queue->getAlternateExchange(); + if (alternate) { + DeliverableMessage delivery(msg.payload); + alternate->routeWithAlternate(delivery); + QPID_LOG(info, "Routed rejected message from " << queue->getName() << " to " + << alternate->getName()); + } else { + //just drop it + QPID_LOG(info, "Dropping rejected message from " << queue->getName()); + } + dequeue(); + setEnded(); } - - dequeue(); } uint32_t DeliveryRecord::getCredit() const @@ -151,7 +153,7 @@ uint32_t DeliveryRecord::getCredit() const } void DeliveryRecord::acquire(DeliveryIds& results) { - if (queue->acquire(msg)) { + if (queue->acquire(msg, tag)) { acquired = true; results.push_back(id); if (!acceptExpected) { diff --git a/cpp/src/qpid/broker/DeliveryRecord.h b/cpp/src/qpid/broker/DeliveryRecord.h index d388ba94be..5a331357be 100644 --- a/cpp/src/qpid/broker/DeliveryRecord.h +++ b/cpp/src/qpid/broker/DeliveryRecord.h @@ -46,7 +46,7 @@ class DeliveryRecord { QueuedMessage msg; mutable boost::shared_ptr<Queue> queue; - std::string tag; + std::string tag; // name of consumer DeliveryId id; bool acquired : 1; bool acceptExpected : 1; @@ -90,7 +90,7 @@ class DeliveryRecord bool isAcquired() const { return acquired; } bool isComplete() const { return completed; } - bool isRedundant() const { return ended && (!windowing || completed); } + bool isRedundant() const { return ended && (!windowing || completed || cancelled); } bool isCancelled() const { return cancelled; } bool isAccepted() const { return !acceptExpected; } bool isEnded() const { return ended; } diff --git a/cpp/src/qpid/broker/DirectExchange.cpp b/cpp/src/qpid/broker/DirectExchange.cpp index 5b8104c77c..5591539853 100644 --- a/cpp/src/qpid/broker/DirectExchange.cpp +++ b/cpp/src/qpid/broker/DirectExchange.cpp @@ -94,7 +94,7 @@ bool DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, con propagate = bk.fedBinding.delOrigin(queue->getName(), fedOrigin); if (bk.fedBinding.countFedBindings(queue->getName()) == 0) - unbind(queue, routingKey, 0); + unbind(queue, routingKey, args); } else if (fedOp == fedOpReorigin) { /** gather up all the keys that need rebinding in a local vector @@ -124,20 +124,24 @@ bool DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, con return true; } -bool DirectExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* /*args*/) +bool DirectExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args) { + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); bool propagate = false; - QPID_LOG(debug, "Unbind key [" << routingKey << "] from queue " << queue->getName()); - + QPID_LOG(debug, "Unbinding key [" << routingKey << "] from queue " << queue->getName() + << " on exchange " << getName() << " origin=" << fedOrigin << ")" ); { Mutex::ScopedLock l(lock); BoundKey& bk = bindings[routingKey]; if (bk.queues.remove_if(MatchQueue(queue))) { - propagate = bk.fedBinding.delOrigin(); + propagate = bk.fedBinding.delOrigin(queue->getName(), fedOrigin); if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); } + if (bk.queues.empty()) { + bindings.erase(routingKey); + } } else { return false; } diff --git a/cpp/src/qpid/broker/DtxAck.cpp b/cpp/src/qpid/broker/DtxAck.cpp index bca3f90bbe..c558681d62 100644 --- a/cpp/src/qpid/broker/DtxAck.cpp +++ b/cpp/src/qpid/broker/DtxAck.cpp @@ -32,6 +32,10 @@ DtxAck::DtxAck(const qpid::framing::SequenceSet& acked, DeliveryRecords& unacked not1(bind2nd(mem_fun_ref(&DeliveryRecord::coveredBy), &acked))); } +DtxAck::DtxAck(DeliveryRecords& unacked) { + pending = unacked; +} + bool DtxAck::prepare(TransactionContext* ctxt) throw() { try{ diff --git a/cpp/src/qpid/broker/DtxAck.h b/cpp/src/qpid/broker/DtxAck.h index 166147e58d..16c3ff8ba0 100644 --- a/cpp/src/qpid/broker/DtxAck.h +++ b/cpp/src/qpid/broker/DtxAck.h @@ -1,3 +1,6 @@ +#ifndef QPID_BROKER_DTXACK_H +#define QPID_BROKER_DTXACK_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -18,9 +21,6 @@ * under the License. * */ -#ifndef _DtxAck_ -#define _DtxAck_ - #include <algorithm> #include <functional> #include <list> @@ -29,20 +29,21 @@ #include "qpid/broker/TxOp.h" namespace qpid { - namespace broker { - class DtxAck : public TxOp{ - DeliveryRecords pending; +namespace broker { +class DtxAck : public TxOp{ + DeliveryRecords pending; - public: - 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); } - }; - } -} + public: + DtxAck(const framing::SequenceSet& acked, DeliveryRecords& unacked); + DtxAck(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); } + const DeliveryRecords& getPending() const { return pending; } +}; +}} // qpid::broker -#endif +#endif /*!QPID_BROKER_DTXACK_H*/ diff --git a/cpp/src/qpid/broker/DtxBuffer.cpp b/cpp/src/qpid/broker/DtxBuffer.cpp index f1b8169cf7..13177d3b72 100644 --- a/cpp/src/qpid/broker/DtxBuffer.cpp +++ b/cpp/src/qpid/broker/DtxBuffer.cpp @@ -23,8 +23,11 @@ using namespace qpid::broker; using qpid::sys::Mutex; -DtxBuffer::DtxBuffer(const std::string& _xid) - : xid(_xid), ended(false), suspended(false), failed(false), expired(false) {} +DtxBuffer::DtxBuffer( + const std::string& _xid, + bool ended_, bool suspended_, bool failed_, bool expired_) + : xid(_xid), ended(ended_), suspended(suspended_), failed(failed_), expired(expired_) +{} DtxBuffer::~DtxBuffer() {} @@ -34,7 +37,7 @@ void DtxBuffer::markEnded() ended = true; } -bool DtxBuffer::isEnded() +bool DtxBuffer::isEnded() const { Mutex::ScopedLock locker(lock); return ended; @@ -45,7 +48,7 @@ void DtxBuffer::setSuspended(bool isSuspended) suspended = isSuspended; } -bool DtxBuffer::isSuspended() +bool DtxBuffer::isSuspended() const { return suspended; } @@ -58,13 +61,13 @@ void DtxBuffer::fail() ended = true; } -bool DtxBuffer::isRollbackOnly() +bool DtxBuffer::isRollbackOnly() const { Mutex::ScopedLock locker(lock); return failed; } -const std::string& DtxBuffer::getXid() +std::string DtxBuffer::getXid() const { return xid; } @@ -76,8 +79,13 @@ void DtxBuffer::timedout() fail(); } -bool DtxBuffer::isExpired() +bool DtxBuffer::isExpired() const { Mutex::ScopedLock locker(lock); return expired; } + +bool DtxBuffer::isFailed() const +{ + return failed; +} diff --git a/cpp/src/qpid/broker/DtxBuffer.h b/cpp/src/qpid/broker/DtxBuffer.h index 1511cb032f..cabd37647a 100644 --- a/cpp/src/qpid/broker/DtxBuffer.h +++ b/cpp/src/qpid/broker/DtxBuffer.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 @@ -26,31 +26,34 @@ #include "qpid/sys/Mutex.h" namespace qpid { - namespace broker { - class DtxBuffer : public TxBuffer{ - sys::Mutex lock; - const std::string xid; - bool ended; - bool suspended; - bool failed; - bool expired; +namespace broker { +class DtxBuffer : public TxBuffer{ + mutable sys::Mutex lock; + const std::string xid; + bool ended; + bool suspended; + bool failed; + bool expired; - public: - typedef boost::shared_ptr<DtxBuffer> shared_ptr; + public: + typedef boost::shared_ptr<DtxBuffer> shared_ptr; - 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(); - void fail(); - bool isRollbackOnly(); - void timedout(); - bool isExpired(); - const std::string& getXid(); - }; - } + QPID_BROKER_EXTERN DtxBuffer( + const std::string& xid = "", + bool ended=false, bool suspended=false, bool failed=false, bool expired=false); + QPID_BROKER_EXTERN ~DtxBuffer(); + QPID_BROKER_EXTERN void markEnded(); + bool isEnded() const; + void setSuspended(bool suspended); + bool isSuspended() const; + void fail(); + bool isRollbackOnly() const; + void timedout(); + bool isExpired() const; + bool isFailed() const; + std::string getXid() const; +}; +} } diff --git a/cpp/src/qpid/broker/DtxManager.cpp b/cpp/src/qpid/broker/DtxManager.cpp index 3caa41c3f4..febd547478 100644 --- a/cpp/src/qpid/broker/DtxManager.cpp +++ b/cpp/src/qpid/broker/DtxManager.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 @@ -34,7 +34,7 @@ using qpid::ptr_map_ptr; using namespace qpid::broker; using namespace qpid::framing; -DtxManager::DtxManager(qpid::sys::Timer& t) : store(0), timer(t) {} +DtxManager::DtxManager(qpid::sys::Timer& t) : store(0), timer(&t) {} DtxManager::~DtxManager() {} @@ -53,8 +53,8 @@ void DtxManager::recover(const std::string& xid, std::auto_ptr<TPCTransactionCon createWork(xid)->recover(txn, ops); } -bool DtxManager::prepare(const std::string& xid) -{ +bool DtxManager::prepare(const std::string& xid) +{ QPID_LOG(debug, "preparing: " << xid); try { return getWork(xid)->prepare(); @@ -64,8 +64,8 @@ bool DtxManager::prepare(const std::string& xid) } } -bool DtxManager::commit(const std::string& xid, bool onePhase) -{ +bool DtxManager::commit(const std::string& xid, bool onePhase) +{ QPID_LOG(debug, "committing: " << xid); try { bool result = getWork(xid)->commit(onePhase); @@ -77,8 +77,8 @@ bool DtxManager::commit(const std::string& xid, bool onePhase) } } -void DtxManager::rollback(const std::string& xid) -{ +void DtxManager::rollback(const std::string& xid) +{ QPID_LOG(debug, "rolling back: " << xid); try { getWork(xid)->rollback(); @@ -91,7 +91,7 @@ void DtxManager::rollback(const std::string& xid) DtxWorkRecord* DtxManager::getWork(const std::string& xid) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); WorkMap::iterator i = work.find(xid); if (i == work.end()) { throw NotFoundException(QPID_MSG("Unrecognised xid " << xid)); @@ -99,9 +99,14 @@ DtxWorkRecord* DtxManager::getWork(const std::string& xid) return ptr_map_ptr(i); } +bool DtxManager::exists(const std::string& xid) { + Mutex::ScopedLock locker(lock); + return work.find(xid) != work.end(); +} + void DtxManager::remove(const std::string& xid) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); WorkMap::iterator i = work.find(xid); if (i == work.end()) { throw NotFoundException(QPID_MSG("Unrecognised xid " << xid)); @@ -110,14 +115,15 @@ void DtxManager::remove(const std::string& xid) } } -DtxWorkRecord* DtxManager::createWork(std::string xid) +DtxWorkRecord* DtxManager::createWork(const std::string& xid) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); WorkMap::iterator i = work.find(xid); if (i != work.end()) { throw NotAllowedException(QPID_MSG("Xid " << xid << " is already known (use 'join' to add work to an existing xid)")); } else { - return ptr_map_ptr(work.insert(xid, new DtxWorkRecord(xid, store)).first); + std::string ncxid = xid; // Work around const correctness problems in ptr_map. + return ptr_map_ptr(work.insert(ncxid, new DtxWorkRecord(ncxid, store)).first); } } @@ -131,7 +137,7 @@ void DtxManager::setTimeout(const std::string& xid, uint32_t secs) } timeout = intrusive_ptr<DtxTimeout>(new DtxTimeout(secs, *this, xid)); record->setTimeout(timeout); - timer.add(timeout); + timer->add(timeout); } uint32_t DtxManager::getTimeout(const std::string& xid) @@ -142,7 +148,7 @@ uint32_t DtxManager::getTimeout(const std::string& xid) void DtxManager::timedout(const std::string& xid) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); WorkMap::iterator i = work.find(xid); if (i == work.end()) { QPID_LOG(warning, "Transaction timeout failed: no record for xid"); @@ -153,7 +159,7 @@ void DtxManager::timedout(const std::string& xid) } } -DtxManager::DtxCleanup::DtxCleanup(uint32_t _timeout, DtxManager& _mgr, const std::string& _xid) +DtxManager::DtxCleanup::DtxCleanup(uint32_t _timeout, DtxManager& _mgr, const std::string& _xid) : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxCleanup"), mgr(_mgr), xid(_xid) {} void DtxManager::DtxCleanup::fire() diff --git a/cpp/src/qpid/broker/DtxManager.h b/cpp/src/qpid/broker/DtxManager.h index 680b62eeb2..11895695a3 100644 --- a/cpp/src/qpid/broker/DtxManager.h +++ b/cpp/src/qpid/broker/DtxManager.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 @@ -26,8 +26,8 @@ #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" +#include "qpid/ptr_map.h" namespace qpid { namespace broker { @@ -39,22 +39,21 @@ class DtxManager{ { DtxManager& mgr; const std::string& xid; - - DtxCleanup(uint32_t timeout, DtxManager& mgr, const std::string& xid); + + DtxCleanup(uint32_t timeout, DtxManager& mgr, const std::string& xid); void fire(); }; WorkMap work; TransactionalStore* store; qpid::sys::Mutex lock; - qpid::sys::Timer& timer; + qpid::sys::Timer* timer; void remove(const std::string& xid); - DtxWorkRecord* getWork(const std::string& xid); - DtxWorkRecord* createWork(std::string xid); + DtxWorkRecord* createWork(const std::string& xid); public: - DtxManager(qpid::sys::Timer&); + DtxManager(sys::Timer&); ~DtxManager(); void start(const std::string& xid, DtxBuffer::shared_ptr work); void join(const std::string& xid, DtxBuffer::shared_ptr work); @@ -66,6 +65,15 @@ public: uint32_t getTimeout(const std::string& xid); void timedout(const std::string& xid); void setStore(TransactionalStore* store); + void setTimer(sys::Timer& t) { timer = &t; } + + // Used by cluster for replication. + template<class F> void each(F f) const { + for (WorkMap::const_iterator i = work.begin(); i != work.end(); ++i) + f(*ptr_map_ptr(i)); + } + DtxWorkRecord* getWork(const std::string& xid); + bool exists(const std::string& xid); }; } diff --git a/cpp/src/qpid/broker/DtxTimeout.cpp b/cpp/src/qpid/broker/DtxTimeout.cpp index c4c52ec40a..58700846ef 100644 --- a/cpp/src/qpid/broker/DtxTimeout.cpp +++ b/cpp/src/qpid/broker/DtxTimeout.cpp @@ -25,7 +25,7 @@ using namespace qpid::broker; DtxTimeout::DtxTimeout(uint32_t _timeout, DtxManager& _mgr, const std::string& _xid) - : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxTimeout"), timeout(_timeout), mgr(_mgr), xid(_xid) + : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxTimeout-"+_xid), timeout(_timeout), mgr(_mgr), xid(_xid) { } diff --git a/cpp/src/qpid/broker/DtxTimeout.h b/cpp/src/qpid/broker/DtxTimeout.h index 680a210e4f..1fcb4cee2a 100644 --- a/cpp/src/qpid/broker/DtxTimeout.h +++ b/cpp/src/qpid/broker/DtxTimeout.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 @@ -29,7 +29,9 @@ namespace broker { class DtxManager; -struct DtxTimeoutException : public Exception {}; +struct DtxTimeoutException : public Exception { + DtxTimeoutException(const std::string& msg=std::string()) : Exception(msg) {} +}; struct DtxTimeout : public sys::TimerTask { @@ -37,7 +39,7 @@ struct DtxTimeout : public sys::TimerTask DtxManager& mgr; const std::string xid; - DtxTimeout(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 9f33e698db..a413fe418d 100644 --- a/cpp/src/qpid/broker/DtxWorkRecord.cpp +++ b/cpp/src/qpid/broker/DtxWorkRecord.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 @@ -28,19 +28,19 @@ using qpid::sys::Mutex; using namespace qpid::broker; using namespace qpid::framing; -DtxWorkRecord::DtxWorkRecord(const std::string& _xid, TransactionalStore* const _store) : +DtxWorkRecord::DtxWorkRecord(const std::string& _xid, TransactionalStore* const _store) : xid(_xid), store(_store), completed(false), rolledback(false), prepared(false), expired(false) {} -DtxWorkRecord::~DtxWorkRecord() +DtxWorkRecord::~DtxWorkRecord() { - if (timeout.get()) { + if (timeout.get()) { timeout->cancel(); } } bool DtxWorkRecord::prepare() { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); if (check()) { txn = store->begin(xid); if (prepare(txn.get())) { @@ -68,7 +68,7 @@ bool DtxWorkRecord::prepare(TransactionContext* _txn) bool DtxWorkRecord::commit(bool onePhase) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); if (check()) { if (prepared) { //already prepared i.e. 2pc @@ -78,13 +78,13 @@ bool DtxWorkRecord::commit(bool onePhase) store->commit(*txn); txn.reset(); - + std::for_each(work.begin(), work.end(), mem_fn(&TxBuffer::commit)); return true; } else { //1pc commit optimisation, don't need a 2pc transaction context: if (!onePhase) { - throw IllegalStateException(QPID_MSG("Branch with xid " << xid << " has not been prepared, one-phase option required!")); + throw IllegalStateException(QPID_MSG("Branch with xid " << xid << " has not been prepared, one-phase option required!")); } std::auto_ptr<TransactionContext> localtxn = store->begin(); if (prepare(localtxn.get())) { @@ -107,16 +107,16 @@ bool DtxWorkRecord::commit(bool onePhase) void DtxWorkRecord::rollback() { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); check(); abort(); } void DtxWorkRecord::add(DtxBuffer::shared_ptr ops) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); if (expired) { - throw DtxTimeoutException(); + throw DtxTimeoutException(QPID_MSG("Branch with xid " << xid << " has timed out.")); } if (completed) { throw CommandInvalidException(QPID_MSG("Branch with xid " << xid << " has been completed!")); @@ -163,7 +163,7 @@ void DtxWorkRecord::recover(std::auto_ptr<TPCTransactionContext> _txn, DtxBuffer void DtxWorkRecord::timedout() { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); expired = true; rolledback = true; if (!completed) { @@ -175,3 +175,17 @@ void DtxWorkRecord::timedout() } abort(); } + +size_t DtxWorkRecord::indexOf(const DtxBuffer::shared_ptr& buf) { + Work::iterator i = std::find(work.begin(), work.end(), buf); + if (i == work.end()) throw NotFoundException( + QPID_MSG("Can't find DTX buffer for xid: " << buf->getXid())); + return i - work.begin(); +} + +DtxBuffer::shared_ptr DtxWorkRecord::operator[](size_t i) const { + if (i > work.size()) + throw NotFoundException( + QPID_MSG("Can't find DTX buffer " << i << " for xid: " << xid)); + return work[i]; +} diff --git a/cpp/src/qpid/broker/DtxWorkRecord.h b/cpp/src/qpid/broker/DtxWorkRecord.h index aec2d2aed4..331e42fefd 100644 --- a/cpp/src/qpid/broker/DtxWorkRecord.h +++ b/cpp/src/qpid/broker/DtxWorkRecord.h @@ -73,9 +73,19 @@ public: void timedout(); void setTimeout(boost::intrusive_ptr<DtxTimeout> t) { timeout = t; } boost::intrusive_ptr<DtxTimeout> getTimeout() { return timeout; } + std::string getXid() const { return xid; } + bool isCompleted() const { return completed; } + bool isRolledback() const { return rolledback; } + bool isPrepared() const { return prepared; } + bool isExpired() const { return expired; } + + // Used by cluster update; + size_t size() const { return work.size(); } + DtxBuffer::shared_ptr operator[](size_t i) const; + uint32_t getTimeout() const { return timeout? timeout->timeout : 0; } + size_t indexOf(const DtxBuffer::shared_ptr&); }; -} -} +}} // qpid::broker #endif diff --git a/cpp/src/qpid/broker/Exchange.cpp b/cpp/src/qpid/broker/Exchange.cpp index d143471559..d68845062d 100644 --- a/cpp/src/qpid/broker/Exchange.cpp +++ b/cpp/src/qpid/broker/Exchange.cpp @@ -19,16 +19,18 @@ * */ +#include "qpid/broker/Broker.h" +#include "qpid/broker/DeliverableMessage.h" #include "qpid/broker/Exchange.h" #include "qpid/broker/ExchangeRegistry.h" #include "qpid/broker/FedOps.h" -#include "qpid/broker/Broker.h" -#include "qpid/management/ManagementAgent.h" #include "qpid/broker/Queue.h" -#include "qpid/log/Statement.h" #include "qpid/framing/MessageProperties.h" #include "qpid/framing/reply_exceptions.h" -#include "qpid/broker/DeliverableMessage.h" +#include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/sys/ExceptionHolder.h" +#include <stdexcept> using namespace qpid::broker; using namespace qpid::framing; @@ -56,7 +58,7 @@ Exchange::PreRoute::PreRoute(Deliverable& msg, Exchange* _p):parent(_p) { if (parent->sequence){ parent->sequenceNo++; - msg.getMessage().getProperties<MessageProperties>()->getApplicationHeaders().setInt64(qpidMsgSequence,parent->sequenceNo); + msg.getMessage().insertCustomProperty(qpidMsgSequence,parent->sequenceNo); } if (parent->ive) { parent->lastMsg = &( msg.getMessage()); @@ -70,6 +72,36 @@ Exchange::PreRoute::~PreRoute(){ } } +namespace { +/** Store information about an exception to be thrown later. + * If multiple exceptions are stored, save the first of the "most severe" + * exceptions, SESSION is les sever than CONNECTION etc. + */ +class ExInfo { + public: + enum Type { NONE, SESSION, CONNECTION, OTHER }; + + ExInfo(string exchange) : type(NONE), exchange(exchange) {} + void store(Type type_, const qpid::sys::ExceptionHolder& exception_, const boost::shared_ptr<Queue>& queue) { + QPID_LOG(warning, "Exchange " << exchange << " cannot deliver to queue " + << queue->getName() << ": " << exception_.what()); + if (type < type_) { // Replace less severe exception + type = type_; + exception = exception_; + } + } + + void raise() { + exception.raise(); + } + + private: + Type type; + string exchange; + qpid::sys::ExceptionHolder exception; +}; +} + void Exchange::doRoute(Deliverable& msg, ConstBindingList b) { int count = 0; @@ -80,11 +112,25 @@ void Exchange::doRoute(Deliverable& msg, ConstBindingList b) msg.getMessage().blockContentRelease(); } + + ExInfo error(getName()); // Save exception to throw at the end. 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(); + try { + msg.deliverTo((*i)->queue); + if ((*i)->mgmtBinding != 0) + (*i)->mgmtBinding->inc_msgMatched(); + } + catch (const SessionException& e) { + error.store(ExInfo::SESSION, framing::createSessionException(e.code, e.what()),(*i)->queue); + } + catch (const ConnectionException& e) { + error.store(ExInfo::CONNECTION, framing::createConnectionException(e.code, e.what()), (*i)->queue); + } + catch (const std::exception& e) { + error.store(ExInfo::OTHER, qpid::sys::ExceptionHolder(new Exception(e.what())), (*i)->queue); + } } + error.raise(); } if (mgmtExchange != 0) @@ -115,7 +161,7 @@ void Exchange::routeIVE(){ 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) + sequenceNo(0), ive(false), mgmtExchange(0), broker(b), destroyed(false) { if (parent != 0 && broker != 0) { @@ -133,7 +179,7 @@ Exchange::Exchange (const string& _name, Manageable* parent, Broker* b) : Exchange::Exchange(const string& _name, bool _durable, const qpid::framing::FieldTable& _args, Manageable* parent, Broker* b) : name(_name), durable(_durable), alternateUsers(0), persistenceId(0), - args(_args), sequence(false), sequenceNo(0), ive(false), mgmtExchange(0), broker(b) + args(_args), sequence(false), sequenceNo(0), ive(false), mgmtExchange(0), broker(b), destroyed(false) { if (parent != 0 && broker != 0) { @@ -155,7 +201,11 @@ Exchange::Exchange(const string& _name, bool _durable, const qpid::framing::Fiel } ive = _args.get(qpidIVE); - if (ive) QPID_LOG(debug, "Configured exchange " << _name << " with Initial Value"); + if (ive) { + if (broker && broker->isInCluster()) + throw framing::NotImplementedException("Cannot use Initial Value Exchanges in a cluster"); + QPID_LOG(debug, "Configured exchange " << _name << " with Initial Value"); + } } Exchange::~Exchange () @@ -340,5 +390,14 @@ bool Exchange::MatchQueue::operator()(Exchange::Binding::shared_ptr b) } void Exchange::setProperties(const boost::intrusive_ptr<Message>& msg) { - msg->getProperties<DeliveryProperties>()->setExchange(getName()); + msg->setExchange(getName()); +} + +bool Exchange::routeWithAlternate(Deliverable& msg) +{ + route(msg, msg.getMessage().getRoutingKey(), msg.getMessage().getApplicationHeaders()); + if (!msg.delivered && alternate) { + alternate->route(msg, msg.getMessage().getRoutingKey(), msg.getMessage().getApplicationHeaders()); + } + return msg.delivered; } diff --git a/cpp/src/qpid/broker/Exchange.h b/cpp/src/qpid/broker/Exchange.h index 3c8b5ca2cd..b12af9a1dd 100644 --- a/cpp/src/qpid/broker/Exchange.h +++ b/cpp/src/qpid/broker/Exchange.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 @@ -39,7 +39,7 @@ namespace broker { class Broker; class ExchangeRegistry; -class Exchange : public PersistableExchange, public management::Manageable { +class QPID_BROKER_CLASS_EXTERN Exchange : public PersistableExchange, public management::Manageable { public: struct Binding : public management::Manageable { typedef boost::shared_ptr<Binding> shared_ptr; @@ -82,15 +82,15 @@ protected: 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 boost::shared_ptr<Queue> queue; + const boost::shared_ptr<Queue> queue; MatchQueue(boost::shared_ptr<Queue> q); bool operator()(Exchange::Binding::shared_ptr b); }; @@ -133,15 +133,15 @@ protected: /** Returns true if propagation is needed. */ bool delOrigin(const std::string& queueName, const std::string& origin){ - fedBindings[queueName].erase(origin); - return true; - } - - /** Returns true if propagation is needed. */ - bool delOrigin() { - if (localBindings > 0) - localBindings--; - return localBindings == 0; + if (origin.empty()) { // no remote == local binding + if (localBindings > 0) + localBindings--; + return localBindings == 0; + } + size_t match = fedBindings[queueName].erase(origin); + if (fedBindings[queueName].empty()) + fedBindings.erase(queueName); + return match != 0; } uint32_t count() { @@ -149,7 +149,11 @@ protected: } uint32_t countFedBindings(const std::string& queueName) { - return fedBindings[queueName].size(); + // don't use '[]' - it may increase size of fedBindings! + std::map<std::string, originSet>::iterator i; + if ((i = fedBindings.find(queueName)) != fedBindings.end()) + return i->second.size(); + return 0; } }; @@ -162,7 +166,7 @@ public: 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(); + QPID_BROKER_INLINE_EXTERN virtual ~Exchange(); const std::string& getName() const { return name; } bool isDurable() { return durable; } @@ -191,7 +195,7 @@ public: virtual bool isBound(boost::shared_ptr<Queue> 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; } @@ -222,14 +226,20 @@ public: */ void recoveryComplete(ExchangeRegistry& exchanges); + bool routeWithAlternate(Deliverable& message); + + void destroy() { destroyed = true; } + bool isDestroyed() const { return destroyed; } + protected: qpid::sys::Mutex bridgeLock; std::vector<DynamicBridge*> bridgeVector; Broker* broker; + bool destroyed; QPID_BROKER_EXTERN virtual void handleHelloRequest(); void propagateFedOp(const std::string& routingKey, const std::string& tags, - const std::string& op, const std::string& origin, + const std::string& op, const std::string& origin, qpid::framing::FieldTable* extra_args=0); }; diff --git a/cpp/src/qpid/broker/ExchangeRegistry.cpp b/cpp/src/qpid/broker/ExchangeRegistry.cpp index 99b121cbce..1c8d26c4f7 100644 --- a/cpp/src/qpid/broker/ExchangeRegistry.cpp +++ b/cpp/src/qpid/broker/ExchangeRegistry.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 @@ -39,7 +39,7 @@ pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, c return declare(name, type, false, FieldTable()); } -pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, const string& type, +pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, const string& type, bool durable, const FieldTable& args){ RWlock::ScopedWlock locker(lock); ExchangeMap::iterator i = exchanges.find(name); @@ -61,7 +61,7 @@ pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, c }else{ FunctionMap::iterator i = factory.find(type); if (i == factory.end()) { - throw UnknownExchangeTypeException(); + throw UnknownExchangeTypeException(); } else { exchange = i->second(name, durable, args, parent, broker); } @@ -82,6 +82,7 @@ void ExchangeRegistry::destroy(const string& name){ RWlock::ScopedWlock locker(lock); ExchangeMap::iterator i = exchanges.find(name); if (i != exchanges.end()) { + i->second->destroy(); exchanges.erase(i); } } @@ -104,7 +105,7 @@ void ExchangeRegistry::registerType(const std::string& type, FactoryFunction f) } -namespace +namespace { const std::string empty; } diff --git a/cpp/src/qpid/broker/ExpiryPolicy.cpp b/cpp/src/qpid/broker/ExpiryPolicy.cpp index 64a12d918a..62cb3fc116 100644 --- a/cpp/src/qpid/broker/ExpiryPolicy.cpp +++ b/cpp/src/qpid/broker/ExpiryPolicy.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 @@ -27,12 +27,12 @@ namespace broker { ExpiryPolicy::~ExpiryPolicy() {} -void ExpiryPolicy::willExpire(Message&) {} - bool ExpiryPolicy::hasExpired(Message& m) { return m.getExpiration() < sys::AbsTime::now(); } -void ExpiryPolicy::forget(Message&) {} +sys::AbsTime ExpiryPolicy::getCurrentTime() { + return sys::AbsTime::now(); +} }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/ExpiryPolicy.h b/cpp/src/qpid/broker/ExpiryPolicy.h index 40e793bf2c..2caf00ce00 100644 --- a/cpp/src/qpid/broker/ExpiryPolicy.h +++ b/cpp/src/qpid/broker/ExpiryPolicy.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 @@ -26,6 +26,11 @@ #include "qpid/broker/BrokerImportExport.h" namespace qpid { + +namespace sys { +class AbsTime; +} + namespace broker { class Message; @@ -33,13 +38,12 @@ class Message; /** * Default expiry policy. */ -class ExpiryPolicy : public RefCounted +class QPID_BROKER_CLASS_EXTERN 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&); + QPID_BROKER_EXTERN virtual qpid::sys::AbsTime getCurrentTime(); }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Fairshare.cpp b/cpp/src/qpid/broker/Fairshare.cpp index e6bbf86691..313aa746f1 100644 --- a/cpp/src/qpid/broker/Fairshare.cpp +++ b/cpp/src/qpid/broker/Fairshare.cpp @@ -24,6 +24,7 @@ #include "qpid/log/Statement.h" #include <boost/format.hpp> #include <boost/lexical_cast.hpp> +#include <boost/assign/list_of.hpp> namespace qpid { namespace broker { @@ -104,51 +105,80 @@ bool Fairshare::setState(Messages& m, uint priority, uint count) return fairshare && fairshare->setState(priority, count); } -int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::string& key) +int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::vector<std::string>& keys) { - qpid::framing::FieldTable::ValuePtr v = settings.get(key); + qpid::framing::FieldTable::ValuePtr v; + std::vector<std::string>::const_iterator i = keys.begin(); + while (!v && i != keys.end()) { + v = settings.get(*i++); + } + if (!v) { return 0; } else if (v->convertsTo<int>()) { return v->get<int>(); } else if (v->convertsTo<std::string>()){ std::string s = v->get<std::string>(); - try { - return boost::lexical_cast<int>(s); + try { + return boost::lexical_cast<int>(s); } catch(const boost::bad_lexical_cast&) { - QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << s); + QPID_LOG(warning, "Ignoring invalid integer value for " << *i << ": " << s); return 0; } } else { - QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << *v); + QPID_LOG(warning, "Ignoring invalid integer value for " << *i << ": " << *v); return 0; } } -int getSetting(const qpid::framing::FieldTable& settings, const std::string& key, int minvalue, int maxvalue) +int getIntegerSettingForKey(const qpid::framing::FieldTable& settings, const std::string& key) +{ + return getIntegerSetting(settings, boost::assign::list_of<std::string>(key)); +} + +int getSetting(const qpid::framing::FieldTable& settings, const std::vector<std::string>& keys, int minvalue, int maxvalue) +{ + return std::max(minvalue,std::min(getIntegerSetting(settings, keys), maxvalue)); +} + +std::auto_ptr<Fairshare> getFairshareForKey(const qpid::framing::FieldTable& settings, uint levels, const std::string& key) +{ + uint defaultLimit = getIntegerSettingForKey(settings, key); + std::auto_ptr<Fairshare> fairshare(new Fairshare(levels, defaultLimit)); + for (uint i = 0; i < levels; i++) { + std::string levelKey = (boost::format("%1%-%2%") % key % i).str(); + if(settings.isSet(levelKey)) { + fairshare->setLimit(i, getIntegerSettingForKey(settings, levelKey)); + } + } + if (!fairshare->isNull()) { + return fairshare; + } else { + return std::auto_ptr<Fairshare>(); + } +} + +std::auto_ptr<Fairshare> getFairshare(const qpid::framing::FieldTable& settings, + uint levels, + const std::vector<std::string>& keys) { - return std::max(minvalue,std::min(getIntegerSetting(settings, key), maxvalue)); + std::auto_ptr<Fairshare> fairshare; + for (std::vector<std::string>::const_iterator i = keys.begin(); i != keys.end() && !fairshare.get(); ++i) { + fairshare = getFairshareForKey(settings, levels, *i); + } + return fairshare; } std::auto_ptr<Messages> Fairshare::create(const qpid::framing::FieldTable& settings) { + using boost::assign::list_of; std::auto_ptr<Messages> result; - size_t levels = getSetting(settings, "x-qpid-priorities", 1, 100); + size_t levels = getSetting(settings, list_of<std::string>("qpid.priorities")("x-qpid-priorities"), 0, 100); if (levels) { - uint defaultLimit = getIntegerSetting(settings, "x-qpid-fairshare"); - std::auto_ptr<Fairshare> fairshare(new Fairshare(levels, defaultLimit)); - for (uint i = 0; i < levels; i++) { - std::string key = (boost::format("x-qpid-fairshare-%1%") % i).str(); - if(settings.isSet(key)) { - fairshare->setLimit(i, getIntegerSetting(settings, key)); - } - } - - if (fairshare->isNull()) { - result = std::auto_ptr<Messages>(new PriorityQueue(levels)); - } else { - result = fairshare; - } + std::auto_ptr<Fairshare> fairshare = + getFairshare(settings, levels, list_of<std::string>("qpid.fairshare")("x-qpid-fairshare")); + if (fairshare.get()) result = fairshare; + else result = std::auto_ptr<Messages>(new PriorityQueue(levels)); } return result; } diff --git a/cpp/src/qpid/broker/Fairshare.h b/cpp/src/qpid/broker/Fairshare.h index 6c4b87f857..1b25721e0c 100644 --- a/cpp/src/qpid/broker/Fairshare.h +++ b/cpp/src/qpid/broker/Fairshare.h @@ -41,18 +41,18 @@ class Fairshare : public PriorityQueue bool getState(uint& priority, uint& count) const; bool setState(uint priority, uint count); void setLimit(size_t level, uint limit); + bool isNull(); static std::auto_ptr<Messages> create(const qpid::framing::FieldTable& settings); static bool getState(const Messages&, uint& priority, uint& count); static bool setState(Messages&, uint priority, uint count); private: std::vector<uint> limits; - + uint priority; uint count; - + uint currentLevel(); uint nextLevel(); - bool isNull(); bool limitReached(); bool findFrontLevel(uint& p, PriorityLevels&); }; diff --git a/cpp/src/qpid/broker/FanOutExchange.cpp b/cpp/src/qpid/broker/FanOutExchange.cpp index ac2c914a97..5879fa0892 100644 --- a/cpp/src/qpid/broker/FanOutExchange.cpp +++ b/cpp/src/qpid/broker/FanOutExchange.cpp @@ -18,6 +18,7 @@ * under the License. * */ +#include "qpid/log/Statement.h" #include "qpid/broker/FanOutExchange.h" #include "qpid/broker/FedOps.h" #include <algorithm> @@ -65,7 +66,7 @@ bool FanOutExchange::bind(Queue::shared_ptr queue, const string& /*key*/, const } else if (fedOp == fedOpUnbind) { propagate = fedBinding.delOrigin(queue->getName(), fedOrigin); if (fedBinding.countFedBindings(queue->getName()) == 0) - unbind(queue, "", 0); + unbind(queue, "", args); } else if (fedOp == fedOpReorigin) { if (fedBinding.hasLocal()) { propagateFedOp(string(), string(), fedOpBind, string()); @@ -78,12 +79,16 @@ bool FanOutExchange::bind(Queue::shared_ptr queue, const string& /*key*/, const return true; } -bool FanOutExchange::unbind(Queue::shared_ptr queue, const string& /*key*/, const FieldTable* /*args*/) +bool FanOutExchange::unbind(Queue::shared_ptr queue, const string& /*key*/, const FieldTable* args) { + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); bool propagate = false; + QPID_LOG(debug, "Unbinding queue " << queue->getName() + << " from exchange " << getName() << " origin=" << fedOrigin << ")" ); + if (bindings.remove_if(MatchQueue(queue))) { - propagate = fedBinding.delOrigin(); + propagate = fedBinding.delOrigin(queue->getName(), fedOrigin); if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); } diff --git a/cpp/src/qpid/broker/FifoDistributor.cpp b/cpp/src/qpid/broker/FifoDistributor.cpp new file mode 100644 index 0000000000..cdb32d8c8c --- /dev/null +++ b/cpp/src/qpid/broker/FifoDistributor.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/broker/Queue.h" +#include "qpid/broker/FifoDistributor.h" + +using namespace qpid::broker; + +FifoDistributor::FifoDistributor(Messages& container) + : messages(container) {} + +bool FifoDistributor::nextConsumableMessage( Consumer::shared_ptr&, QueuedMessage& next ) +{ + if (!messages.empty()) { + next = messages.front(); // by default, consume oldest msg + return true; + } + return false; +} + +bool FifoDistributor::allocate(const std::string&, const QueuedMessage& ) +{ + // by default, all messages present on the queue may be allocated as they have yet to + // be acquired. + return true; +} + +bool FifoDistributor::nextBrowsableMessage( Consumer::shared_ptr& c, QueuedMessage& next ) +{ + if (!messages.empty() && messages.next(c->position, next)) + return true; + return false; +} + +void FifoDistributor::query(qpid::types::Variant::Map&) const +{ + // nothing to see here.... +} + diff --git a/cpp/src/qpid/broker/IncompleteMessageList.h b/cpp/src/qpid/broker/FifoDistributor.h index a4debd1233..245537ed12 100644 --- a/cpp/src/qpid/broker/IncompleteMessageList.h +++ b/cpp/src/qpid/broker/FifoDistributor.h @@ -1,3 +1,6 @@ +#ifndef _broker_FifoDistributor_h +#define _broker_FifoDistributor_h + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -7,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -18,41 +21,38 @@ * under the License. * */ -#ifndef _IncompleteMessageList_ -#define _IncompleteMessageList_ -#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> +/** Simple MessageDistributor for FIFO Queues - the HEAD message is always the next + * available message for consumption. + */ + +#include "qpid/broker/MessageDistributor.h" namespace qpid { namespace broker { -class IncompleteMessageList +class Messages; + +class FifoDistributor : public MessageDistributor { - typedef std::list< boost::intrusive_ptr<Message> > Messages; + public: + FifoDistributor(Messages& container); - void enqueueComplete(const boost::intrusive_ptr<Message>&); + /** Locking Note: all methods assume the caller is holding the Queue::messageLock + * during the method call. + */ - sys::Monitor lock; - Messages incomplete; - Message::MessageCallback callback; + /** MessageDistributor interface */ -public: - typedef Message::MessageCallback CompletionListener; + bool nextConsumableMessage( Consumer::shared_ptr& consumer, QueuedMessage& next ); + bool allocate(const std::string& consumer, const QueuedMessage& target); + bool nextBrowsableMessage( Consumer::shared_ptr& consumer, QueuedMessage& next ); + void query(qpid::types::Variant::Map&) const; - 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); + private: + Messages& messages; }; - }} #endif diff --git a/cpp/src/qpid/broker/HeadersExchange.cpp b/cpp/src/qpid/broker/HeadersExchange.cpp index 82ac5911ee..4bda70d313 100644 --- a/cpp/src/qpid/broker/HeadersExchange.cpp +++ b/cpp/src/qpid/broker/HeadersExchange.cpp @@ -112,9 +112,14 @@ bool HeadersExchange::bind(Queue::shared_ptr queue, const string& bindingKey, co { Mutex::ScopedLock l(lock); - Binding::shared_ptr binding (new Binding (bindingKey, queue, this, *args)); + //NOTE: do not include the fed op/tags/origin in the + //arguments as when x-match is 'all' these would prevent + //matching (they are internally added properties + //controlling binding propagation but not relevant to + //actual routing) + Binding::shared_ptr binding (new Binding (bindingKey, queue, this, extra_args)); BoundKey bk(binding); - if (bindings.add_unless(bk, MatchArgs(queue, args))) { + if (bindings.add_unless(bk, MatchArgs(queue, &extra_args))) { binding->startManagement(); propagate = bk.fedBinding.addOrigin(queue->getName(), fedOrigin); if (mgmtExchange != 0) { @@ -158,12 +163,13 @@ bool HeadersExchange::bind(Queue::shared_ptr queue, const string& bindingKey, co return true; } -bool HeadersExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable*){ +bool HeadersExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable *args){ bool propagate = false; + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); { Mutex::ScopedLock l(lock); - FedUnbindModifier modifier; + FedUnbindModifier modifier(queue->getName(), fedOrigin); MatchKey match_key(queue, bindingKey); bindings.modify_if(match_key, modifier); propagate = modifier.shouldPropagate; @@ -330,11 +336,7 @@ HeadersExchange::FedUnbindModifier::FedUnbindModifier() : shouldUnbind(false), s bool HeadersExchange::FedUnbindModifier::operator()(BoundKey & bk) { - if ("" == fedOrigin) { - shouldPropagate = bk.fedBinding.delOrigin(); - } else { - shouldPropagate = bk.fedBinding.delOrigin(queueName, fedOrigin); - } + shouldPropagate = bk.fedBinding.delOrigin(queueName, fedOrigin); if (bk.fedBinding.countFedBindings(queueName) == 0) { shouldUnbind = true; diff --git a/cpp/src/qpid/broker/IncompleteMessageList.cpp b/cpp/src/qpid/broker/IncompleteMessageList.cpp deleted file mode 100644 index 34d92fa752..0000000000 --- a/cpp/src/qpid/broker/IncompleteMessageList.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "qpid/broker/IncompleteMessageList.h" - -namespace qpid { -namespace broker { - -IncompleteMessageList::IncompleteMessageList() : - callback(boost::bind(&IncompleteMessageList::enqueueComplete, this, _1)) -{} - -IncompleteMessageList::~IncompleteMessageList() -{ - // No lock here. We are relying on Messsag::reset*CompleteCallback - // to ensure no callbacks are in progress before they return. - 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::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){ - { - 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; - } - } - 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); -} - -}} diff --git a/cpp/src/qpid/broker/LegacyLVQ.cpp b/cpp/src/qpid/broker/LegacyLVQ.cpp index a811a86492..3262e343a3 100644 --- a/cpp/src/qpid/broker/LegacyLVQ.cpp +++ b/cpp/src/qpid/broker/LegacyLVQ.cpp @@ -93,11 +93,7 @@ void LegacyLVQ::removeIf(Predicate p) //purging of an LVQ is not enabled if the broker is clustered //(expired messages will be removed on delivery and consolidated //by key as part of normal LVQ operation). - - //TODO: Is there a neater way to check whether broker is - //clustered? Here we assume that if the clustered timer is the - //same as the regular timer, we are not clustered: - if (!broker || &(broker->getClusterTimer()) == &(broker->getTimer())) + if (!broker || !broker->isInCluster()) MessageMap::removeIf(p); } diff --git a/cpp/src/qpid/broker/Link.cpp b/cpp/src/qpid/broker/Link.cpp index e1091df724..8010bf43e7 100644 --- a/cpp/src/qpid/broker/Link.cpp +++ b/cpp/src/qpid/broker/Link.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 @@ -30,7 +30,6 @@ #include "qpid/framing/enum.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/broker/AclModule.h" -#include "qpid/sys/ClusterSafe.h" using namespace qpid::broker; using qpid::framing::Buffer; @@ -57,8 +56,8 @@ Link::Link(LinkRegistry* _links, string& _password, Broker* _broker, Manageable* parent) - : links(_links), store(_store), host(_host), port(_port), - transport(_transport), + : 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), @@ -97,7 +96,8 @@ void Link::setStateLH (int newState) return; state = newState; - if (mgmtObject == 0) + + if (hideManagement()) return; switch (state) @@ -117,12 +117,12 @@ 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, transport, + broker->connect (host, boost::lexical_cast<std::string>(port), transport, boost::bind (&Link::closed, this, _1, _2)); QPID_LOG (debug, "Inter-broker link connecting to " << host << ":" << port); } catch(std::exception& e) { setStateLH(STATE_WAITING); - if (mgmtObject != 0) + if (!hideManagement()) mgmtObject->set_lastError (e.what()); } } @@ -133,8 +133,7 @@ void Link::established () addr << host << ":" << port; QPID_LOG (info, "Inter-broker link established to " << addr.str()); - // Don't raise the management event in a cluster, other members wont't get this call. - if (!sys::isCluster()) + if (!hideManagement() && agent) agent->raiseEvent(_qmf::EventBrokerLinkUp(addr.str())); { @@ -154,12 +153,11 @@ void Link::closed (int, std::string text) connection = 0; - // Don't raise the management event in a cluster, other members wont't get this call. if (state == STATE_OPERATIONAL) { stringstream addr; addr << host << ":" << port; QPID_LOG (warning, "Inter-broker link disconnected from " << addr.str()); - if (!sys::isCluster()) + if (!hideManagement() && agent) agent->raiseEvent(_qmf::EventBrokerLinkDown(addr.str())); } @@ -172,7 +170,7 @@ void Link::closed (int, std::string text) if (state != STATE_FAILED) { setStateLH(STATE_WAITING); - if (mgmtObject != 0) + if (!hideManagement()) mgmtObject->set_lastError (text); } @@ -221,7 +219,7 @@ 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); @@ -250,6 +248,19 @@ void Link::ioThreadProcessing() return; QPID_LOG(debug, "Link::ioThreadProcessing()"); + // check for bridge session errors and recover + if (!active.empty()) { + Bridges::iterator removed = std::remove_if( + active.begin(), active.end(), !boost::bind(&Bridge::isSessionReady, _1)); + for (Bridges::iterator i = removed; i != active.end(); ++i) { + Bridge::shared_ptr bridge = *i; + bridge->closed(); + bridge->cancel(*connection); + created.push_back(bridge); + } + active.erase(removed, active.end()); + } + //process any pending creates and/or cancellations if (!created.empty()) { for (Bridges::iterator i = created.begin(); i != created.end(); ++i) { @@ -277,9 +288,9 @@ void Link::maintenanceVisit () { Mutex::ScopedLock mutex(lock); - if (connection && updateUrls) { + if (connection && updateUrls) { urls.reset(connection->getKnownHosts()); - QPID_LOG(debug, "Known hosts for peer of inter-broker link: " << urls); + QPID_LOG(debug, "Known hosts for peer of inter-broker link: " << urls); updateUrls = false; } @@ -298,7 +309,7 @@ void Link::maintenanceVisit () } } } - else if (state == STATE_OPERATIONAL && (!created.empty() || !cancellations.empty()) && connection != 0) + else if (state == STATE_OPERATIONAL && (!active.empty() || !created.empty() || !cancellations.empty()) && connection != 0) connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); } @@ -309,7 +320,7 @@ void Link::reconnect(const qpid::Address& a) port = a.port; transport = a.protocol; startConnectionLH(); - if (mgmtObject != 0) { + if (!hideManagement()) { stringstream errorString; errorString << "Failed over to " << a; mgmtObject->set_lastError(errorString.str()); @@ -319,7 +330,7 @@ void Link::reconnect(const qpid::Address& a) bool Link::tryFailover() { Address next; - if (urls.next(next) && + if (urls.next(next) && (next.host != host || next.port != port || next.protocol != transport)) { links->changeAddress(Address(transport, host, port), next); QPID_LOG(debug, "Link failing over to " << host << ":" << port); @@ -329,6 +340,12 @@ bool Link::tryFailover() } } +// Management updates for a linke are inconsistent in a cluster, so they are +// suppressed. +bool Link::hideManagement() const { + return !mgmtObject || ( broker && broker->isInCluster()); +} + uint Link::nextChannel() { Mutex::ScopedLock mutex(lock); @@ -341,7 +358,7 @@ void Link::notifyConnectionForced(const string text) Mutex::ScopedLock mutex(lock); setStateLH(STATE_FAILED); - if (mgmtObject != 0) + if (!hideManagement()) mgmtObject->set_lastError(text); } @@ -363,7 +380,7 @@ Link::shared_ptr Link::decode(LinkRegistry& links, Buffer& buffer) string authMechanism; string username; string password; - + buffer.getShortString(host); port = buffer.getShort(); buffer.getShortString(transport); @@ -375,7 +392,7 @@ Link::shared_ptr Link::decode(LinkRegistry& links, Buffer& buffer) return links.declare(host, port, transport, durable, authMechanism, username, password).first; } -void Link::encode(Buffer& buffer) const +void Link::encode(Buffer& buffer) const { buffer.putShortString(string("link")); buffer.putShortString(host); @@ -387,8 +404,8 @@ void Link::encode(Buffer& buffer) const buffer.putShortString(password); } -uint32_t Link::encodedSize() const -{ +uint32_t Link::encodedSize() const +{ return host.size() + 1 // short-string (host) + 5 // short-string ("link") + 2 // port diff --git a/cpp/src/qpid/broker/Link.h b/cpp/src/qpid/broker/Link.h index 75a680ff5d..4badd8b3a1 100644 --- a/cpp/src/qpid/broker/Link.h +++ b/cpp/src/qpid/broker/Link.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 @@ -85,6 +85,7 @@ namespace qpid { void destroy(); // Called when mgmt deletes this link void ioThreadProcessing(); // Called on connection's IO thread by request bool tryFailover(); // Called during maintenance visit + bool hideManagement() const; public: typedef boost::shared_ptr<Link> shared_ptr; @@ -122,12 +123,12 @@ namespace qpid { void notifyConnectionForced(const std::string text); void setPassive(bool p); - + // PersistableConfig: void setPersistenceId(uint64_t id) const; uint64_t getPersistenceId() const { return persistenceId; } uint32_t encodedSize() const; - void encode(framing::Buffer& buffer) const; + void encode(framing::Buffer& buffer) const; const std::string& getName() const; static Link::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer); @@ -135,6 +136,7 @@ namespace qpid { // Manageable entry points 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 7b1c75db74..e9885f5462 100644 --- a/cpp/src/qpid/broker/LinkRegistry.cpp +++ b/cpp/src/qpid/broker/LinkRegistry.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 @@ -381,7 +381,7 @@ std::string LinkRegistry::createKey(const std::string& host, uint16_t port) { return keystream.str(); } -void LinkRegistry::setPassive(bool p) +void LinkRegistry::setPassive(bool p) { Mutex::ScopedLock locker(lock); passiveChanged = p != passive; diff --git a/cpp/src/qpid/broker/Message.cpp b/cpp/src/qpid/broker/Message.cpp index c589669e5a..d13109dad1 100644 --- a/cpp/src/qpid/broker/Message.cpp +++ b/cpp/src/qpid/broker/Message.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 @@ -30,6 +30,7 @@ #include "qpid/framing/SendContent.h" #include "qpid/framing/SequenceNumber.h" #include "qpid/framing/TypeFilter.h" +#include "qpid/framing/reply_exceptions.h" #include "qpid/log/Statement.h" #include <time.h> @@ -49,27 +50,16 @@ TransferAdapter Message::TRANSFER; 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), - inCallback(false), requiredCredit(0) {} + staged(false), forcePersistentPolicy(false), publisher(0), adapter(0), + expiration(FAR_FUTURE), dequeueCallback(0), + inCallback(false), requiredCredit(0), isManagementMessage(false), copyHeaderOnWrite(false) +{} -Message::Message(const Message& original) : - PersistableMessage(), frames(original.frames), persistenceId(0), redelivered(false), loaded(false), - staged(false), forcePersistentPolicy(false), publisher(0), adapter(0), - expiration(original.expiration), enqueueCallback(0), dequeueCallback(0), - inCallback(false), requiredCredit(0) -{ - setExpiryPolicy(original.expiryPolicy); -} - -Message::~Message() -{ - if (expiryPolicy) - expiryPolicy->forget(*this); -} +Message::~Message() {} void Message::forcePersistent() { + sys::Mutex::ScopedLock l(lock); // only set forced bit if we actually need to force. if (! getAdapter().isPersistent(frames) ){ forcePersistentPolicy = true; @@ -86,7 +76,7 @@ std::string Message::getRoutingKey() const return getAdapter().getRoutingKey(frames); } -std::string Message::getExchangeName() const +std::string Message::getExchangeName() const { return getAdapter().getExchange(frames); } @@ -95,7 +85,7 @@ const boost::shared_ptr<Exchange> Message::getExchange(ExchangeRegistry& registr { if (!exchange) { exchange = registry.get(getExchangeName()); - } + } return exchange; } @@ -106,16 +96,19 @@ bool Message::isImmediate() const const FieldTable* Message::getApplicationHeaders() const { + sys::Mutex::ScopedLock l(lock); return getAdapter().getApplicationHeaders(frames); } std::string Message::getAppId() const { + sys::Mutex::ScopedLock l(lock); return getAdapter().getAppId(frames); } bool Message::isPersistent() const { + sys::Mutex::ScopedLock l(lock); return (getAdapter().isPersistent(frames) || forcePersistentPolicy); } @@ -195,7 +188,7 @@ void Message::decodeContent(framing::Buffer& buffer) } else { //adjust header flags MarkLastSegment f; - frames.map_if(f, TypeFilter<HEADER_BODY>()); + frames.map_if(f, TypeFilter<HEADER_BODY>()); } //mark content loaded loaded = true; @@ -247,7 +240,7 @@ void Message::destroy() 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); @@ -272,7 +265,7 @@ void Message::sendContent(const Queue& queue, framing::FrameHandler& out, uint16 uint16_t maxContentSize = maxFrameSize - AMQFrame::frameOverhead(); bool morecontent = true; for (uint64_t offset = 0; morecontent; offset += maxContentSize) - { + { AMQFrame frame((AMQContentBody())); morecontent = getContentFrame(queue, frame, maxContentSize, offset); out.handle(frame); @@ -290,7 +283,10 @@ void Message::sendHeader(framing::FrameHandler& out, uint16_t /*maxFrameSize*/) { sys::Mutex::ScopedLock l(lock); Relay f(out); - frames.map_if(f, TypeFilter<HEADER_BODY>()); + frames.map_if(f, TypeFilter<HEADER_BODY>()); + //as frame (and pointer to body) has now been passed to handler, + //subsequent modifications should use a copy + copyHeaderOnWrite = true; } // TODO aconway 2007-11-09: Obsolete, remove. Was used to cover over @@ -320,13 +316,14 @@ bool Message::isContentLoaded() const } -namespace +namespace { const std::string X_QPID_TRACE("x-qpid.trace"); } bool Message::isExcluded(const std::vector<std::string>& excludes) const { + sys::Mutex::ScopedLock l(lock); const FieldTable* headers = getApplicationHeaders(); if (headers) { std::string traceStr = headers->getAsString(X_QPID_TRACE); @@ -345,11 +342,30 @@ bool Message::isExcluded(const std::vector<std::string>& excludes) const return false; } +class CloneHeaderBody +{ +public: + void operator()(AMQFrame& f) + { + f.cloneBody(); + } +}; + +AMQHeaderBody* Message::getHeaderBody() +{ + if (copyHeaderOnWrite) { + CloneHeaderBody f; + frames.map_if(f, TypeFilter<HEADER_BODY>()); + copyHeaderOnWrite = false; + } + return frames.getHeaders(); +} + void Message::addTraceId(const std::string& id) { sys::Mutex::ScopedLock l(lock); if (isA<MessageTransferBody>()) { - FieldTable& headers = getProperties<MessageProperties>()->getApplicationHeaders(); + FieldTable& headers = getModifiableProperties<MessageProperties>()->getApplicationHeaders(); std::string trace = headers.getAsString(X_QPID_TRACE); if (trace.empty()) { headers.setString(X_QPID_TRACE, id); @@ -357,13 +373,22 @@ void Message::addTraceId(const std::string& id) trace += ","; trace += id; headers.setString(X_QPID_TRACE, trace); - } + } } } -void Message::setTimestamp(const boost::intrusive_ptr<ExpiryPolicy>& e) +void Message::setTimestamp() +{ + sys::Mutex::ScopedLock l(lock); + DeliveryProperties* props = getModifiableProperties<DeliveryProperties>(); + time_t now = ::time(0); + props->setTimestamp(now); // AMQP-0.10: posix time_t - secs since Epoch +} + +void Message::computeExpiration(const boost::intrusive_ptr<ExpiryPolicy>& e) { - DeliveryProperties* props = getProperties<DeliveryProperties>(); + sys::Mutex::ScopedLock l(lock); + DeliveryProperties* props = getModifiableProperties<DeliveryProperties>(); if (props->getTtl()) { // AMQP requires setting the expiration property to be posix // time_t in seconds. TTL is in milliseconds @@ -372,26 +397,70 @@ void Message::setTimestamp(const boost::intrusive_ptr<ExpiryPolicy>& e) 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); + if (e) { + // Use higher resolution time for the internal expiry calculation. + // Prevent overflow as a signed int64_t + Duration ttl(std::min(props->getTtl() * TIME_MSEC, + (uint64_t) std::numeric_limits<int64_t>::max())); + expiration = AbsTime(e->getCurrentTime(), ttl); + setExpiryPolicy(e); + } } } void Message::adjustTtl() { - DeliveryProperties* props = getProperties<DeliveryProperties>(); + sys::Mutex::ScopedLock l(lock); + DeliveryProperties* props = getModifiableProperties<DeliveryProperties>(); if (props->getTtl()) { - sys::Mutex::ScopedLock l(lock); - sys::Duration d(sys::AbsTime::now(), getExpiration()); - props->setTtl(int64_t(d) > 0 ? int64_t(d)/1000000 : 1); // convert from ns to ms; set to 1 if expired + if (expiration < FAR_FUTURE) { + sys::AbsTime current( + expiryPolicy ? expiryPolicy->getCurrentTime() : sys::AbsTime::now()); + sys::Duration ttl(current, getExpiration()); + // convert from ns to ms; set to 1 if expired + props->setTtl(int64_t(ttl) >= 1000000 ? int64_t(ttl)/1000000 : 1); + } } } +void Message::setRedelivered() +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<framing::DeliveryProperties>()->setRedelivered(true); +} + +void Message::insertCustomProperty(const std::string& key, int64_t value) +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<MessageProperties>()->getApplicationHeaders().setInt64(key,value); +} + +void Message::insertCustomProperty(const std::string& key, const std::string& value) +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<MessageProperties>()->getApplicationHeaders().setString(key,value); +} + +void Message::removeCustomProperty(const std::string& key) +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<MessageProperties>()->getApplicationHeaders().erase(key); +} + +void Message::setExchange(const std::string& exchange) +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<DeliveryProperties>()->setExchange(exchange); +} + +void Message::clearApplicationHeadersFlag() +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<MessageProperties>()->clearApplicationHeadersFlag(); +} + void Message::setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e) { expiryPolicy = e; - if (expiryPolicy) - expiryPolicy->willExpire(*this); } bool Message::hasExpired() @@ -415,30 +484,12 @@ struct ScopedSet { }; } -void Message::allEnqueuesComplete() { - ScopedSet ss(callbackLock, inCallback); - MessageCallback* cb = enqueueCallback; - if (cb && *cb) (*cb)(intrusive_ptr<Message>(this)); -} - void Message::allDequeuesComplete() { ScopedSet ss(callbackLock, inCallback); MessageCallback* cb = dequeueCallback; if (cb && *cb) (*cb)(intrusive_ptr<Message>(this)); } -void Message::setEnqueueCompleteCallback(MessageCallback& cb) { - sys::Mutex::ScopedLock l(callbackLock); - while (inCallback) callbackLock.wait(); - enqueueCallback = &cb; -} - -void Message::resetEnqueueCompleteCallback() { - sys::Mutex::ScopedLock l(callbackLock); - while (inCallback) callbackLock.wait(); - enqueueCallback = 0; -} - void Message::setDequeueCompleteCallback(MessageCallback& cb) { sys::Mutex::ScopedLock l(callbackLock); while (inCallback) callbackLock.wait(); @@ -452,12 +503,11 @@ void Message::resetDequeueCompleteCallback() { } uint8_t Message::getPriority() const { + sys::Mutex::ScopedLock l(lock); return getAdapter().getPriority(frames); } -framing::FieldTable& Message::getOrInsertHeaders() -{ - return getProperties<MessageProperties>()->getApplicationHeaders(); -} +bool Message::getIsManagementMessage() const { return isManagementMessage; } +void Message::setIsManagementMessage(bool b) { isManagementMessage = b; } }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Message.h b/cpp/src/qpid/broker/Message.h index f7dd2734b6..dda45d73e6 100644 --- a/cpp/src/qpid/broker/Message.h +++ b/cpp/src/qpid/broker/Message.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 @@ -29,17 +29,21 @@ #include "qpid/sys/Monitor.h" #include "qpid/sys/Time.h" #include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> #include <boost/shared_ptr.hpp> +#include <memory> #include <string> #include <vector> namespace qpid { - + namespace framing { +class AMQBody; +class AMQHeaderBody; class FieldTable; class SequenceNumber; } - + namespace broker { class ConnectionToken; class Exchange; @@ -51,11 +55,10 @@ class ExpiryPolicy; class Message : public PersistableMessage { public: typedef boost::function<void (const boost::intrusive_ptr<Message>&)> MessageCallback; - + QPID_BROKER_EXTERN Message(const framing::SequenceNumber& id = framing::SequenceNumber()); - QPID_BROKER_EXTERN Message(const Message&); QPID_BROKER_EXTERN ~Message(); - + uint64_t getPersistenceId() const { return persistenceId; } void setPersistenceId(uint64_t _persistenceId) const { persistenceId = _persistenceId; } @@ -75,27 +78,31 @@ public: bool isImmediate() const; QPID_BROKER_EXTERN const framing::FieldTable* getApplicationHeaders() const; QPID_BROKER_EXTERN std::string getAppId() const; - framing::FieldTable& getOrInsertHeaders(); QPID_BROKER_EXTERN bool isPersistent() const; bool requiresAccept(); - QPID_BROKER_EXTERN void setTimestamp(const boost::intrusive_ptr<ExpiryPolicy>& e); + /** determine msg expiration time using the TTL value if present */ + QPID_BROKER_EXTERN void computeExpiration(const boost::intrusive_ptr<ExpiryPolicy>& e); void setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e); bool hasExpired(); sys::AbsTime getExpiration() const { return expiration; } + void setExpiration(sys::AbsTime exp) { expiration = exp; } void adjustTtl(); - - framing::FrameSet& getFrames() { return frames; } - const framing::FrameSet& getFrames() const { return frames; } - - template <class T> T* getProperties() { - qpid::framing::AMQHeaderBody* p = frames.getHeaders(); - return p->get<T>(true); - } + void setRedelivered(); + QPID_BROKER_EXTERN void insertCustomProperty(const std::string& key, int64_t value); + QPID_BROKER_EXTERN void insertCustomProperty(const std::string& key, const std::string& value); + QPID_BROKER_EXTERN void removeCustomProperty(const std::string& key); + void setExchange(const std::string&); + void clearApplicationHeadersFlag(); + /** set the timestamp delivery property to the current time-of-day */ + QPID_BROKER_EXTERN void setTimestamp(); + + framing::FrameSet& getFrames() { return frames; } + const framing::FrameSet& getFrames() const { return frames; } template <class T> const T* getProperties() const { - qpid::framing::AMQHeaderBody* p = frames.getHeaders(); - return p->get<T>(true); + const qpid::framing::AMQHeaderBody* p = frames.getHeaders(); + return p->get<T>(); } template <class T> const T* hasProperties() const { @@ -103,6 +110,11 @@ public: return p->get<T>(); } + template <class T> void eraseProperties() { + qpid::framing::AMQHeaderBody* p = frames.getHeaders(); + p->erase<T>(); + } + template <class T> const T* getMethod() const { return frames.as<T>(); } @@ -135,7 +147,7 @@ public: QPID_BROKER_EXTERN void decodeHeader(framing::Buffer& buffer); QPID_BROKER_EXTERN void decodeContent(framing::Buffer& buffer); - + void QPID_BROKER_EXTERN tryReleaseContent(); void releaseContent(); void releaseContent(MessageStore* s);//deprecated, use 'setStore(store); releaseContent();' instead @@ -149,24 +161,19 @@ public: bool isExcluded(const std::vector<std::string>& excludes) const; void addTraceId(const std::string& id); - - void forcePersistent(); - bool isForcedPersistent(); - - /** Call cb when enqueue is complete, may call immediately. Holds cb by reference. */ - void setEnqueueCompleteCallback(MessageCallback& cb); - void resetEnqueueCompleteCallback(); + void forcePersistent(); + bool isForcedPersistent(); /** Call cb when dequeue is complete, may call immediately. Holds cb by reference. */ void setDequeueCompleteCallback(MessageCallback& cb); void resetDequeueCompleteCallback(); uint8_t getPriority() const; - + bool getIsManagementMessage() const; + void setIsManagementMessage(bool b); private: MessageAdapter& getAdapter() const; - void allEnqueuesComplete(); void allDequeuesComplete(); mutable sys::Mutex lock; @@ -176,7 +183,7 @@ public: bool redelivered; bool loaded; bool staged; - bool forcePersistentPolicy; // used to force message as durable, via a broker policy + bool forcePersistentPolicy; // used to force message as durable, via a broker policy ConnectionToken* publisher; mutable MessageAdapter* adapter; qpid::sys::AbsTime expiration; @@ -187,11 +194,20 @@ public: mutable boost::intrusive_ptr<Message> empty; sys::Monitor callbackLock; - MessageCallback* enqueueCallback; MessageCallback* dequeueCallback; bool inCallback; uint32_t requiredCredit; + bool isManagementMessage; + mutable bool copyHeaderOnWrite; + + /** + * Expects lock to be held + */ + template <class T> T* getModifiableProperties() { + return getHeaderBody()->get<T>(true); + } + qpid::framing::AMQHeaderBody* getHeaderBody(); }; }} diff --git a/cpp/src/qpid/broker/MessageBuilder.h b/cpp/src/qpid/broker/MessageBuilder.h index 75dfd6781d..b99b8efee6 100644 --- a/cpp/src/qpid/broker/MessageBuilder.h +++ b/cpp/src/qpid/broker/MessageBuilder.h @@ -33,7 +33,7 @@ namespace qpid { class Message; class MessageStore; - class MessageBuilder : public framing::FrameHandler{ + class QPID_BROKER_CLASS_EXTERN MessageBuilder : public framing::FrameHandler{ public: QPID_BROKER_EXTERN MessageBuilder(MessageStore* const store); QPID_BROKER_EXTERN void handle(framing::AMQFrame& frame); diff --git a/cpp/src/qpid/broker/MessageDistributor.h b/cpp/src/qpid/broker/MessageDistributor.h new file mode 100644 index 0000000000..090393c160 --- /dev/null +++ b/cpp/src/qpid/broker/MessageDistributor.h @@ -0,0 +1,76 @@ +#ifndef _broker_MessageDistributor_h +#define _broker_MessageDistributor_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. + * + */ + +/** Abstraction used by Queue to determine the next "most desirable" message to provide to + * a particular consuming client + */ + + +#include "qpid/broker/Consumer.h" + +namespace qpid { +namespace broker { + +struct QueuedMessage; + +class MessageDistributor +{ + public: + virtual ~MessageDistributor() {}; + + /** Locking Note: all methods assume the caller is holding the Queue::messageLock + * during the method call. + */ + + /** Determine the next message available for consumption by the consumer + * @param consumer the consumer that needs a message to consume + * @param next set to the next message that the consumer may consume. + * @return true if message is available and next is set + */ + virtual bool nextConsumableMessage( Consumer::shared_ptr& consumer, + QueuedMessage& next ) = 0; + + /** Allow the comsumer to take ownership of the given message. + * @param consumer the name of the consumer that is attempting to acquire the message + * @param qm the message to be acquired, previously returned from nextConsumableMessage() + * @return true if ownership is permitted, false if ownership cannot be assigned. + */ + virtual bool allocate( const std::string& consumer, + const QueuedMessage& target) = 0; + + /** Determine the next message available for browsing by the consumer + * @param consumer the consumer that is browsing the queue + * @param next set to the next message that the consumer may browse. + * @return true if a message is available and next is returned + */ + virtual bool nextBrowsableMessage( Consumer::shared_ptr& consumer, + QueuedMessage& next ) = 0; + + /** hook to add any interesting management state to the status map */ + virtual void query(qpid::types::Variant::Map&) const = 0; +}; + +}} + +#endif diff --git a/cpp/src/qpid/broker/MessageGroupManager.cpp b/cpp/src/qpid/broker/MessageGroupManager.cpp new file mode 100644 index 0000000000..07b05f3b92 --- /dev/null +++ b/cpp/src/qpid/broker/MessageGroupManager.cpp @@ -0,0 +1,411 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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/FieldTable.h" +#include "qpid/types/Variant.h" +#include "qpid/log/Statement.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/MessageGroupManager.h" + +using namespace qpid::broker; + +namespace { + const std::string GROUP_QUERY_KEY("qpid.message_group_queue"); + const std::string GROUP_HEADER_KEY("group_header_key"); + const std::string GROUP_STATE_KEY("group_state"); + const std::string GROUP_ID_KEY("group_id"); + const std::string GROUP_MSG_COUNT("msg_count"); + const std::string GROUP_TIMESTAMP("timestamp"); + const std::string GROUP_CONSUMER("consumer"); +} + + +const std::string MessageGroupManager::qpidMessageGroupKey("qpid.group_header_key"); +const std::string MessageGroupManager::qpidSharedGroup("qpid.shared_msg_group"); +const std::string MessageGroupManager::qpidMessageGroupTimestamp("qpid.group_timestamp"); + + +void MessageGroupManager::unFree( const GroupState& state ) +{ + GroupFifo::iterator pos = freeGroups.find( state.members.front() ); + assert( pos != freeGroups.end() && pos->second == &state ); + freeGroups.erase( pos ); +} + +void MessageGroupManager::own( GroupState& state, const std::string& owner ) +{ + state.owner = owner; + unFree( state ); +} + +void MessageGroupManager::disown( GroupState& state ) +{ + state.owner.clear(); + assert(state.members.size()); + assert(freeGroups.find(state.members.front()) == freeGroups.end()); + freeGroups[state.members.front()] = &state; +} + +const std::string MessageGroupManager::getGroupId( const QueuedMessage& qm ) const +{ + const qpid::framing::FieldTable* headers = qm.payload->getApplicationHeaders(); + if (!headers) return defaultGroupId; + qpid::framing::FieldTable::ValuePtr id = headers->get( groupIdHeader ); + if (!id || !id->convertsTo<std::string>()) return defaultGroupId; + return id->get<std::string>(); +} + + +void MessageGroupManager::enqueued( const QueuedMessage& qm ) +{ + // @todo KAG optimization - store reference to group state in QueuedMessage + // issue: const-ness?? + std::string group( getGroupId(qm) ); + GroupState &state(messageGroups[group]); + state.members.push_back(qm.position); + uint32_t total = state.members.size(); + QPID_LOG( trace, "group queue " << qName << + ": added message to group id=" << group << " total=" << total ); + if (total == 1) { + // newly created group, no owner + state.group = group; + assert(freeGroups.find(qm.position) == freeGroups.end()); + freeGroups[qm.position] = &state; + } +} + + +void MessageGroupManager::acquired( const QueuedMessage& qm ) +{ + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + // issue: const-ness?? + std::string group( getGroupId(qm) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + state.acquired += 1; + QPID_LOG( trace, "group queue " << qName << + ": acquired message in group id=" << group << " acquired=" << state.acquired ); +} + + +void MessageGroupManager::requeued( const QueuedMessage& qm ) +{ + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + // issue: const-ness?? + std::string group( getGroupId(qm) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + assert( state.acquired != 0 ); + state.acquired -= 1; + if (state.acquired == 0 && state.owned()) { + QPID_LOG( trace, "group queue " << qName << + ": consumer name=" << state.owner << " released group id=" << gs->first); + disown(state); + } + QPID_LOG( trace, "group queue " << qName << + ": requeued message to group id=" << group << " acquired=" << state.acquired ); +} + + +void MessageGroupManager::dequeued( const QueuedMessage& qm ) +{ + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + // issue: const-ness?? + std::string group( getGroupId(qm) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + assert( state.members.size() != 0 ); + assert( state.acquired != 0 ); + state.acquired -= 1; + + // likely to be at or near begin() if dequeued in order + bool reFreeNeeded = false; + if (state.members.front() == qm.position) { + if (!state.owned()) { + // will be on the freeGroups list if mgmt is dequeueing rather than a consumer! + // if on freelist, it is indexed by first member, which is about to be removed! + unFree(state); + reFreeNeeded = true; + } + state.members.pop_front(); + } else { + GroupState::PositionFifo::iterator pos = state.members.begin() + 1; + GroupState::PositionFifo::iterator end = state.members.end(); + while (pos != end) { + if (*pos == qm.position) { + state.members.erase(pos); + break; + } + ++pos; + } + } + + uint32_t total = state.members.size(); + if (total == 0) { + QPID_LOG( trace, "group queue " << qName << ": deleting group id=" << gs->first); + messageGroups.erase( gs ); + } else if (state.acquired == 0 && state.owned()) { + QPID_LOG( trace, "group queue " << qName << + ": consumer name=" << state.owner << " released group id=" << gs->first); + disown(state); + } else if (reFreeNeeded) { + disown(state); + } + QPID_LOG( trace, "group queue " << qName << + ": dequeued message from group id=" << group << " total=" << total ); +} + +bool MessageGroupManager::nextConsumableMessage( Consumer::shared_ptr& c, QueuedMessage& next ) +{ + if (messages.empty()) + return false; + + if (!freeGroups.empty()) { + framing::SequenceNumber nextFree = freeGroups.begin()->first; + if (nextFree < c->position) { // next free group's msg is older than current position + bool ok = messages.find(nextFree, next); + (void) ok; assert( ok ); + } else { + if (!messages.next( c->position, next )) + return false; // shouldn't happen - should find nextFree + } + } else { // no free groups available + if (!messages.next( c->position, next )) + return false; + } + + do { + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + std::string group( getGroupId( next ) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + if (!state.owned() || state.owner == c->getName()) { + return true; + } + } while (messages.next( next.position, next )); + return false; +} + + +bool MessageGroupManager::allocate(const std::string& consumer, const QueuedMessage& qm) +{ + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + std::string group( getGroupId(qm) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + + if (!state.owned()) { + own( state, consumer ); + QPID_LOG( trace, "group queue " << qName << + ": consumer name=" << consumer << " has acquired group id=" << gs->first); + return true; + } + return state.owner == consumer; +} + +bool MessageGroupManager::nextBrowsableMessage( Consumer::shared_ptr& c, QueuedMessage& next ) +{ + // browse: allow access to any available msg, regardless of group ownership (?ok?) + if (!messages.empty() && messages.next(c->position, next)) + return true; + return false; +} + +void MessageGroupManager::query(qpid::types::Variant::Map& status) const +{ + /** Add a description of the current state of the message groups for this queue. + FORMAT: + { "qpid.message_group_queue": + { "group_header_key" : "<KEY>", + "group_state" : + [ { "group_id" : "<name>", + "msg_count" : <int>, + "timestamp" : <absTime>, + "consumer" : <consumer name> }, + {...} // one for each known group + ] + } + } + **/ + + assert(status.find(GROUP_QUERY_KEY) == status.end()); + qpid::types::Variant::Map state; + qpid::types::Variant::List groups; + + state[GROUP_HEADER_KEY] = groupIdHeader; + for (GroupMap::const_iterator g = messageGroups.begin(); + g != messageGroups.end(); ++g) { + qpid::types::Variant::Map info; + info[GROUP_ID_KEY] = g->first; + info[GROUP_MSG_COUNT] = g->second.members.size(); + info[GROUP_TIMESTAMP] = 0; /** @todo KAG - NEED HEAD MSG TIMESTAMP */ + info[GROUP_CONSUMER] = g->second.owner; + groups.push_back(info); + } + state[GROUP_STATE_KEY] = groups; + status[GROUP_QUERY_KEY] = state; +} + + +boost::shared_ptr<MessageGroupManager> MessageGroupManager::create( const std::string& qName, + Messages& messages, + const qpid::framing::FieldTable& settings ) +{ + boost::shared_ptr<MessageGroupManager> empty; + + if (settings.isSet(qpidMessageGroupKey)) { + + // @todo: remove once "sticky" consumers are supported - see QPID-3347 + if (!settings.isSet(qpidSharedGroup)) { + QPID_LOG( error, "Only shared groups are supported in this version of the broker. Use '--shared-groups' in qpid-config." ); + return empty; + } + + std::string headerKey = settings.getAsString(qpidMessageGroupKey); + if (headerKey.empty()) { + QPID_LOG( error, "A Message Group header key must be configured, queue=" << qName); + return empty; + } + unsigned int timestamp = settings.getAsInt(qpidMessageGroupTimestamp); + + boost::shared_ptr<MessageGroupManager> manager( new MessageGroupManager( headerKey, qName, messages, timestamp ) ); + + QPID_LOG( debug, "Configured Queue '" << qName << + "' for message grouping using header key '" << headerKey << "'" << + " (timestamp=" << timestamp << ")"); + return manager; + } + return empty; +} + +std::string MessageGroupManager::defaultGroupId; +void MessageGroupManager::setDefaults(const std::string& groupId) // static +{ + defaultGroupId = groupId; +} + +/** Cluster replication: + + state map format: + + { "group-state": [ {"name": <group-name>, + "owner": <consumer-name>-or-empty, + "acquired-ct": <acquired count>, + "positions": [Seqnumbers, ... ]}, + {...} + ] + } +*/ + +namespace { + const std::string GROUP_NAME("name"); + const std::string GROUP_OWNER("owner"); + const std::string GROUP_ACQUIRED_CT("acquired-ct"); + const std::string GROUP_POSITIONS("positions"); + const std::string GROUP_STATE("group-state"); +} + + +/** Runs on UPDATER to snapshot current state */ +void MessageGroupManager::getState(qpid::framing::FieldTable& state ) const +{ + using namespace qpid::framing; + state.clear(); + framing::Array groupState(TYPE_CODE_MAP); + for (GroupMap::const_iterator g = messageGroups.begin(); + g != messageGroups.end(); ++g) { + + framing::FieldTable group; + group.setString(GROUP_NAME, g->first); + group.setString(GROUP_OWNER, g->second.owner); + group.setInt(GROUP_ACQUIRED_CT, g->second.acquired); + framing::Array positions(TYPE_CODE_UINT32); + for (GroupState::PositionFifo::const_iterator p = g->second.members.begin(); + p != g->second.members.end(); ++p) + positions.push_back(framing::Array::ValuePtr(new IntegerValue( *p ))); + group.setArray(GROUP_POSITIONS, positions); + groupState.push_back(framing::Array::ValuePtr(new FieldTableValue(group))); + } + state.setArray(GROUP_STATE, groupState); + + QPID_LOG(debug, "Queue \"" << qName << "\": replicating message group state, key=" << groupIdHeader); +} + + +/** called on UPDATEE to set state from snapshot */ +void MessageGroupManager::setState(const qpid::framing::FieldTable& state) +{ + using namespace qpid::framing; + messageGroups.clear(); + //consumers.clear(); + freeGroups.clear(); + + framing::Array groupState(TYPE_CODE_MAP); + + bool ok = state.getArray(GROUP_STATE, groupState); + if (!ok) { + QPID_LOG(error, "Unable to find message group state information for queue \"" << + qName << "\": cluster inconsistency error!"); + return; + } + + for (framing::Array::const_iterator g = groupState.begin(); + g != groupState.end(); ++g) { + framing::FieldTable group; + ok = framing::getEncodedValue<FieldTable>(*g, group); + if (!ok) { + QPID_LOG(error, "Invalid message group state information for queue \"" << + qName << "\": table encoding error!"); + return; + } + MessageGroupManager::GroupState state; + if (!group.isSet(GROUP_NAME) || !group.isSet(GROUP_OWNER) || !group.isSet(GROUP_ACQUIRED_CT)) { + QPID_LOG(error, "Invalid message group state information for queue \"" << + qName << "\": fields missing error!"); + return; + } + state.group = group.getAsString(GROUP_NAME); + state.owner = group.getAsString(GROUP_OWNER); + state.acquired = group.getAsInt(GROUP_ACQUIRED_CT); + framing::Array positions(TYPE_CODE_UINT32); + ok = group.getArray(GROUP_POSITIONS, positions); + if (!ok) { + QPID_LOG(error, "Invalid message group state information for queue \"" << + qName << "\": position encoding error!"); + return; + } + + for (Array::const_iterator p = positions.begin(); p != positions.end(); ++p) + state.members.push_back((*p)->getIntegerValue<uint32_t, 4>()); + messageGroups[state.group] = state; + if (!state.owned()) { + assert(state.members.size()); + freeGroups[state.members.front()] = &messageGroups[state.group]; + } + } + + QPID_LOG(debug, "Queue \"" << qName << "\": message group state replicated, key =" << groupIdHeader) +} diff --git a/cpp/src/qpid/broker/MessageGroupManager.h b/cpp/src/qpid/broker/MessageGroupManager.h new file mode 100644 index 0000000000..6c81ec14d1 --- /dev/null +++ b/cpp/src/qpid/broker/MessageGroupManager.h @@ -0,0 +1,107 @@ +#ifndef _broker_MessageGroupManager_h +#define _broker_MessageGroupManager_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. + * + */ + +/* for managing message grouping on Queues */ + +#include "qpid/broker/StatefulQueueObserver.h" +#include "qpid/broker/MessageDistributor.h" + + +namespace qpid { +namespace broker { + +class QueueObserver; +class MessageDistributor; + +class MessageGroupManager : public StatefulQueueObserver, public MessageDistributor +{ + static std::string defaultGroupId; // assigned if no group id header present + + const std::string groupIdHeader; // msg header holding group identifier + const unsigned int timestamp; // mark messages with timestamp if set + Messages& messages; // parent Queue's in memory message container + const std::string qName; // name of parent queue (for logs) + + struct GroupState { + // note: update getState()/setState() when changing this object's state implementation + typedef std::deque<framing::SequenceNumber> PositionFifo; + + std::string group; // group identifier + std::string owner; // consumer with outstanding acquired messages + uint32_t acquired; // count of outstanding acquired messages + PositionFifo members; // msgs belonging to this group + + GroupState() : acquired(0) {} + bool owned() const {return !owner.empty();} + }; + typedef std::map<std::string, struct GroupState> GroupMap; + typedef std::map<framing::SequenceNumber, struct GroupState *> GroupFifo; + + GroupMap messageGroups; // index: group name + GroupFifo freeGroups; // ordered by oldest free msg + //Consumers consumers; // index: consumer name + + static const std::string qpidMessageGroupKey; + static const std::string qpidSharedGroup; // if specified, one group can be consumed by multiple receivers + static const std::string qpidMessageGroupTimestamp; + + const std::string getGroupId( const QueuedMessage& qm ) const; + void unFree( const GroupState& state ); + void own( GroupState& state, const std::string& owner ); + void disown( GroupState& state ); + + public: + + static QPID_BROKER_EXTERN void setDefaults(const std::string& groupId); + static boost::shared_ptr<MessageGroupManager> create( const std::string& qName, + Messages& messages, + const qpid::framing::FieldTable& settings ); + + MessageGroupManager(const std::string& header, const std::string& _qName, + Messages& container, unsigned int _timestamp=0 ) + : StatefulQueueObserver(std::string("MessageGroupManager:") + header), + groupIdHeader( header ), timestamp(_timestamp), messages(container), qName(_qName) {} + + // QueueObserver iface + void enqueued( const QueuedMessage& qm ); + void acquired( const QueuedMessage& qm ); + void requeued( const QueuedMessage& qm ); + void dequeued( const QueuedMessage& qm ); + void consumerAdded( const Consumer& ) {}; + void consumerRemoved( const Consumer& ) {}; + void getState(qpid::framing::FieldTable& state ) const; + void setState(const qpid::framing::FieldTable&); + + // MessageDistributor iface + bool nextConsumableMessage(Consumer::shared_ptr& c, QueuedMessage& next); + bool allocate(const std::string& c, const QueuedMessage& qm); + bool nextBrowsableMessage(Consumer::shared_ptr& c, QueuedMessage& next); + void query(qpid::types::Variant::Map&) const; + + bool match(const qpid::types::Variant::Map*, const QueuedMessage&) const; +}; + +}} + +#endif diff --git a/cpp/src/qpid/broker/Messages.h b/cpp/src/qpid/broker/Messages.h index 0d75417640..448f17432a 100644 --- a/cpp/src/qpid/broker/Messages.h +++ b/cpp/src/qpid/broker/Messages.h @@ -32,7 +32,8 @@ struct QueuedMessage; /** * This interface abstracts out the access to the messages held for - * delivery by a Queue instance. + * delivery by a Queue instance. Note the the assumption at present is + * that all locking is done in the Queue itself. */ class Messages { @@ -75,7 +76,6 @@ class Messages * @return true if there is another message, false otherwise. */ virtual bool next(const framing::SequenceNumber&, QueuedMessage&) = 0; - /** * Note: Caller is responsible for ensuring that there is a front * (e.g. empty() returns false) diff --git a/cpp/src/qpid/broker/NullMessageStore.cpp b/cpp/src/qpid/broker/NullMessageStore.cpp index dc8615d58b..43f600eaf1 100644 --- a/cpp/src/qpid/broker/NullMessageStore.cpp +++ b/cpp/src/qpid/broker/NullMessageStore.cpp @@ -126,21 +126,25 @@ std::auto_ptr<TPCTransactionContext> NullMessageStore::begin(const std::string& void NullMessageStore::prepare(TPCTransactionContext& ctxt) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock); prepared.insert(DummyCtxt::getXid(ctxt)); } void NullMessageStore::commit(TransactionContext& ctxt) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock); prepared.erase(DummyCtxt::getXid(ctxt)); } void NullMessageStore::abort(TransactionContext& ctxt) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock); prepared.erase(DummyCtxt::getXid(ctxt)); } void NullMessageStore::collectPreparedXids(std::set<std::string>& out) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock); out.insert(prepared.begin(), prepared.end()); } diff --git a/cpp/src/qpid/broker/NullMessageStore.h b/cpp/src/qpid/broker/NullMessageStore.h index e148ec4d51..c6f402662e 100644 --- a/cpp/src/qpid/broker/NullMessageStore.h +++ b/cpp/src/qpid/broker/NullMessageStore.h @@ -25,6 +25,7 @@ #include "qpid/broker/BrokerImportExport.h" #include "qpid/broker/MessageStore.h" #include "qpid/broker/Queue.h" +#include "qpid/sys/Mutex.h" #include <boost/intrusive_ptr.hpp> @@ -34,10 +35,11 @@ namespace broker { /** * A null implementation of the MessageStore interface */ -class NullMessageStore : public MessageStore +class QPID_BROKER_CLASS_EXTERN NullMessageStore : public MessageStore { std::set<std::string> prepared; uint64_t nextPersistenceId; + qpid::sys::Mutex lock; public: QPID_BROKER_EXTERN NullMessageStore(); diff --git a/cpp/src/qpid/broker/PersistableMessage.cpp b/cpp/src/qpid/broker/PersistableMessage.cpp index e5fbb71cbd..7ba28eb293 100644 --- a/cpp/src/qpid/broker/PersistableMessage.cpp +++ b/cpp/src/qpid/broker/PersistableMessage.cpp @@ -34,7 +34,6 @@ class MessageStore; PersistableMessage::~PersistableMessage() {} PersistableMessage::PersistableMessage() : - asyncEnqueueCounter(0), asyncDequeueCounter(0), store(0) {} @@ -68,24 +67,6 @@ 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(); -} bool PersistableMessage::isStoredOnQueue(PersistableQueue::shared_ptr queue){ if (store && (queue->getPersistenceId()!=0)) { @@ -109,12 +90,7 @@ void PersistableMessage::addToSyncList(PersistableQueue::shared_ptr queue, Messa void PersistableMessage::enqueueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store) { addToSyncList(queue, _store); - enqueueAsync(); -} - -void PersistableMessage::enqueueAsync() { - sys::ScopedLock<sys::Mutex> l(asyncEnqueueLock); - asyncEnqueueCounter++; + enqueueStart(); } bool PersistableMessage::isDequeueComplete() { diff --git a/cpp/src/qpid/broker/PersistableMessage.h b/cpp/src/qpid/broker/PersistableMessage.h index 96fb922c1a..d29c2c45b4 100644 --- a/cpp/src/qpid/broker/PersistableMessage.h +++ b/cpp/src/qpid/broker/PersistableMessage.h @@ -31,6 +31,7 @@ #include "qpid/framing/amqp_types.h" #include "qpid/sys/Mutex.h" #include "qpid/broker/PersistableQueue.h" +#include "qpid/broker/AsyncCompletion.h" namespace qpid { namespace broker { @@ -43,18 +44,19 @@ class MessageStore; class PersistableMessage : public Persistable { 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 - * count is incremented; when that enqueue completes it is - * decremented. Thus when it is 0, there are no outstanding - * enqueues. + * "Ingress" messages == messages sent _to_ the broker. + * Tracks the number of outstanding asynchronous operations that must + * complete before an inbound message can be considered fully received by the + * broker. E.g. all enqueues have completed, the message has been written + * to store, credit has been replenished, etc. Once all outstanding + * operations have completed, the transfer of this message from the client + * may be considered complete. */ - int asyncEnqueueCounter; + AsyncCompletion ingressCompletion; /** * Tracks the number of outstanding asynchronous dequeue @@ -65,7 +67,6 @@ class PersistableMessage : public Persistable */ int asyncDequeueCounter; - void enqueueAsync(); void dequeueAsync(); syncList synclist; @@ -80,8 +81,6 @@ class PersistableMessage : public Persistable 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; @@ -115,9 +114,12 @@ class PersistableMessage : public Persistable virtual QPID_BROKER_EXTERN bool isPersistent() const = 0; - QPID_BROKER_EXTERN bool isEnqueueComplete(); + /** track the progress of a message received by the broker - see ingressCompletion above */ + QPID_BROKER_INLINE_EXTERN bool isIngressComplete() { return ingressCompletion.isDone(); } + QPID_BROKER_INLINE_EXTERN AsyncCompletion& getIngressCompletion() { return ingressCompletion; } - QPID_BROKER_EXTERN void enqueueComplete(); + QPID_BROKER_INLINE_EXTERN void enqueueStart() { ingressCompletion.startCompleter(); } + QPID_BROKER_INLINE_EXTERN void enqueueComplete() { ingressCompletion.finishCompleter(); } QPID_BROKER_EXTERN void enqueueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store); @@ -133,7 +135,6 @@ class PersistableMessage : public Persistable bool isStoredOnQueue(PersistableQueue::shared_ptr queue); void addToSyncList(PersistableQueue::shared_ptr queue, MessageStore* _store); - }; }} diff --git a/cpp/src/qpid/broker/Queue.cpp b/cpp/src/qpid/broker/Queue.cpp index 27c1cc4ad7..4627b1409a 100644 --- a/cpp/src/qpid/broker/Queue.cpp +++ b/cpp/src/qpid/broker/Queue.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 @@ -31,7 +31,10 @@ #include "qpid/broker/MessageStore.h" #include "qpid/broker/NullMessageStore.h" #include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/QueueFlowLimit.h" #include "qpid/broker/ThresholdAlerts.h" +#include "qpid/broker/FifoDistributor.h" +#include "qpid/broker/MessageGroupManager.h" #include "qpid/StringUtils.h" #include "qpid/log/Statement.h" @@ -41,6 +44,7 @@ #include "qpid/sys/ClusterSafe.h" #include "qpid/sys/Monitor.h" #include "qpid/sys/Time.h" +#include "qpid/types/Variant.h" #include "qmf/org/apache/qpid/broker/ArgsQueuePurge.h" #include "qmf/org/apache/qpid/broker/ArgsQueueReroute.h" @@ -64,7 +68,7 @@ using std::mem_fun; namespace _qmf = qmf::org::apache::qpid::broker; -namespace +namespace { const std::string qpidMaxSize("qpid.max_size"); const std::string qpidMaxCount("qpid.max_count"); @@ -86,16 +90,16 @@ const int ENQUEUE_ONLY=1; const int ENQUEUE_AND_DEQUEUE=2; } -Queue::Queue(const string& _name, bool _autodelete, +Queue::Queue(const string& _name, bool _autodelete, MessageStore* const _store, const OwnershipToken* const _owner, Manageable* parent, Broker* b) : - name(_name), + name(_name), autodelete(_autodelete), store(_store), - owner(_owner), + owner(_owner), consumerCount(0), exclusive(0), noLocal(false), @@ -110,7 +114,8 @@ Queue::Queue(const string& _name, bool _autodelete, broker(b), deleted(false), barrier(*this), - autoDeleteTimeout(0) + autoDeleteTimeout(0), + allocator(new FifoDistributor( *messages )) { if (parent != 0 && broker != 0) { ManagementAgent* agent = broker->getManagementAgent(); @@ -163,13 +168,8 @@ void Queue::deliver(boost::intrusive_ptr<Message> msg){ //drop message QPID_LOG(info, "Dropping excluded message from " << getName()); } else { - // if no store then mark as enqueued - if (!enqueue(0, msg)){ - push(msg); - msg->enqueueComplete(); - }else { - push(msg); - } + enqueue(0, msg); + push(msg); QPID_LOG(debug, "Message " << msg << " enqueued on " << name); } } @@ -183,11 +183,10 @@ void Queue::recover(boost::intrusive_ptr<Message>& msg){ if (policy.get()) policy->recoverEnqueued(msg); push(msg, true); - if (store){ + if (store){ // setup synclist for recovered messages, so they don't get re-stored on lastNodeFailure - msg->addToSyncList(shared_from_this(), store); + msg->addToSyncList(shared_from_this(), store); } - msg->enqueueComplete(); // mark the message as enqueued if (store && (!msg->isContentLoaded() || msg->checkContentReleasable())) { //content has not been loaded, need to ensure that lazy loading mode is set: @@ -211,14 +210,13 @@ void Queue::process(boost::intrusive_ptr<Message>& msg){ void Queue::requeue(const QueuedMessage& msg){ assertClusterSafe(); QueueListeners::NotificationSet copy; - { + { Mutex::ScopedLock locker(messageLock); if (!isEnqueued(msg)) return; - msg.payload->enqueueComplete(); // mark the message as enqueued messages->reinsert(msg); listeners.populate(copy); - // for persistLastNode - don't force a message twice to disk, but force it if no force before + // 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() ){ @@ -226,16 +224,17 @@ void Queue::requeue(const QueuedMessage& msg){ enqueue(0, payload); } } + observeRequeue(msg, locker); } copy.notify(); } -bool Queue::acquireMessageAt(const SequenceNumber& position, QueuedMessage& message) +bool Queue::acquireMessageAt(const SequenceNumber& position, QueuedMessage& message) { Mutex::ScopedLock locker(messageLock); assertClusterSafe(); QPID_LOG(debug, "Attempting to acquire message at " << position); - if (messages->remove(position, message)) { + if (acquire(position, message, locker)) { QPID_LOG(debug, "Acquired message at " << position << " from " << name); return true; } else { @@ -244,9 +243,24 @@ bool Queue::acquireMessageAt(const SequenceNumber& position, QueuedMessage& mess } } -bool Queue::acquire(const QueuedMessage& msg) { - QueuedMessage copy = msg; - return acquireMessageAt(msg.position, copy); +bool Queue::acquire(const QueuedMessage& msg, const std::string& consumer) +{ + Mutex::ScopedLock locker(messageLock); + assertClusterSafe(); + QPID_LOG(debug, consumer << " attempting to acquire message at " << msg.position); + + if (!allocator->allocate( consumer, msg )) { + QPID_LOG(debug, "Not permitted to acquire msg at " << msg.position << " from '" << name); + return false; + } + + QueuedMessage copy(msg); + if (acquire( msg.position, copy, locker)) { + QPID_LOG(debug, "Acquired message at " << msg.position << " from " << name); + return true; + } + QPID_LOG(debug, "Could not acquire message at " << msg.position << " from " << name << "; no message at that position"); + return false; } void Queue::notifyListener() @@ -262,7 +276,7 @@ void Queue::notifyListener() set.notify(); } -bool Queue::getNextMessage(QueuedMessage& m, Consumer::shared_ptr c) +bool Queue::getNextMessage(QueuedMessage& m, Consumer::shared_ptr& c) { checkNotDeleted(); if (c->preAcquires()) { @@ -274,52 +288,71 @@ bool Queue::getNextMessage(QueuedMessage& m, Consumer::shared_ptr c) case NO_MESSAGES: default: return false; - } + } } else { return browseNextMessage(m, c); } } -Queue::ConsumeCode Queue::consumeNextMessage(QueuedMessage& m, Consumer::shared_ptr 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 << "'"); + QueuedMessage msg; + + if (!allocator->nextConsumableMessage(c, msg)) { // no next available + QPID_LOG(debug, "No messages available to dispatch to consumer " << + c->getName() << " on queue '" << name << "'"); listeners.addListener(c); return NO_MESSAGES; - } else { - QueuedMessage msg = messages->front(); - if (msg.payload->hasExpired()) { - QPID_LOG(debug, "Message expired from queue '" << name << "'"); - popAndDequeue(); - continue; - } + } - if (c->filter(msg.payload)) { - if (c->accept(msg.payload)) { - m = msg; - pop(); - 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 CANT_CONSUME; - } + if (msg.payload->hasExpired()) { + QPID_LOG(debug, "Message expired from queue '" << name << "'"); + c->position = msg.position; + acquire( msg.position, msg, locker); + dequeue( 0, msg ); + continue; + } + + // a message is available for this consumer - can the consumer use it? + + if (c->filter(msg.payload)) { + if (c->accept(msg.payload)) { + bool ok = allocator->allocate( c->getName(), msg ); // inform allocator + (void) ok; assert(ok); + ok = acquire( msg.position, msg, locker); + (void) ok; assert(ok); + m = msg; + c->position = m.position; + return CONSUMED; } else { - //consumer will never want this message - QPID_LOG(debug, "Consumer doesn't want message from '" << name << "'"); + //message(s) are available but consumer hasn't got enough credit + QPID_LOG(debug, "Consumer can't currently accept message from '" << name << "'"); return CANT_CONSUME; - } + } + } else { + //consumer will never want this message + QPID_LOG(debug, "Consumer doesn't want message from '" << name << "'"); + c->position = msg.position; + return CANT_CONSUME; } } } - -bool Queue::browseNextMessage(QueuedMessage& m, Consumer::shared_ptr c) +bool Queue::browseNextMessage(QueuedMessage& m, Consumer::shared_ptr& c) { - QueuedMessage msg(this); - while (seek(msg, c)) { + while (true) { + Mutex::ScopedLock locker(messageLock); + QueuedMessage msg; + + if (!allocator->nextBrowsableMessage(c, msg)) { // no next available + QPID_LOG(debug, "No browsable messages available for consumer " << + c->getName() << " on queue '" << name << "'"); + listeners.addListener(c); + return false; + } + if (c->filter(msg.payload) && !msg.payload->hasExpired()) { if (c->accept(msg.payload)) { //consumer wants the message @@ -333,8 +366,8 @@ bool Queue::browseNextMessage(QueuedMessage& m, Consumer::shared_ptr c) } } else { //consumer will never want this message, continue seeking - c->position = msg.position; QPID_LOG(debug, "Browser skipping message from '" << name << "'"); + c->position = msg.position; } } return false; @@ -364,61 +397,71 @@ bool Queue::dispatch(Consumer::shared_ptr c) } } -// Find the next message -bool Queue::seek(QueuedMessage& msg, Consumer::shared_ptr c) { - Mutex::ScopedLock locker(messageLock); - if (messages->next(c->position, msg)) { - return true; - } else { - listeners.addListener(c); - return false; - } -} - -QueuedMessage Queue::find(SequenceNumber pos) const { +bool Queue::find(SequenceNumber pos, QueuedMessage& msg) const { Mutex::ScopedLock locker(messageLock); - QueuedMessage msg; - messages->find(pos, msg); - return msg; + if (messages->find(pos, msg)) + return true; + return false; } void Queue::consume(Consumer::shared_ptr c, bool requestExclusive){ assertClusterSafe(); - Mutex::ScopedLock locker(consumerLock); - if(exclusive) { - throw ResourceLockedException( - QPID_MSG("Queue " << getName() << " has an exclusive consumer. No more consumers allowed.")); - } else if(requestExclusive) { - if(consumerCount) { + { + Mutex::ScopedLock locker(consumerLock); + if(exclusive) { throw ResourceLockedException( - QPID_MSG("Queue " << getName() << " already has consumers. Exclusive access denied.")); - } else { - exclusive = c->getSession(); + QPID_MSG("Queue " << getName() << " has an exclusive consumer. No more consumers allowed.")); + } else if(requestExclusive) { + if(consumerCount) { + throw ResourceLockedException( + QPID_MSG("Queue " << getName() << " already has consumers. Exclusive access denied.")); + } else { + exclusive = c->getSession(); + } + } + consumerCount++; + if (mgmtObject != 0) + mgmtObject->inc_consumerCount (); + //reset auto deletion timer if necessary + if (autoDeleteTimeout && autoDeleteTask) { + autoDeleteTask->cancel(); } } - consumerCount++; - if (mgmtObject != 0) - mgmtObject->inc_consumerCount (); - //reset auto deletion timer if necessary - if (autoDeleteTimeout && autoDeleteTask) { - autoDeleteTask->cancel(); + Mutex::ScopedLock locker(messageLock); + for (Observers::const_iterator i = observers.begin(); i != observers.end(); ++i) { + try{ + (*i)->consumerAdded(*c); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of new consumer for queue " << getName() << ": " << e.what()); + } } } void Queue::cancel(Consumer::shared_ptr c){ removeListener(c); - Mutex::ScopedLock locker(consumerLock); - consumerCount--; - if(exclusive) exclusive = 0; - if (mgmtObject != 0) - mgmtObject->dec_consumerCount (); + { + Mutex::ScopedLock locker(consumerLock); + consumerCount--; + if(exclusive) exclusive = 0; + if (mgmtObject != 0) + mgmtObject->dec_consumerCount (); + } + Mutex::ScopedLock locker(messageLock); + for (Observers::const_iterator i = observers.begin(); i != observers.end(); ++i) { + try{ + (*i)->consumerRemoved(*c); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of removed consumer for queue " << getName() << ": " << e.what()); + } + } } QueuedMessage Queue::get(){ Mutex::ScopedLock locker(messageLock); QueuedMessage msg(this); - messages->pop(msg); + if (messages->pop(msg)) + observeAcquire(msg, locker); return msg; } @@ -432,22 +475,135 @@ bool collect_if_expired(std::deque<QueuedMessage>& expired, QueuedMessage& messa } } -void Queue::purgeExpired() +/** + *@param lapse: time since the last purgeExpired + */ +void Queue::purgeExpired(qpid::sys::Duration lapse) { //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) { + //attempt is less than one per second. + int count = dequeueSincePurge.get(); + dequeueSincePurge -= count; + int seconds = int64_t(lapse)/qpid::sys::TIME_SEC; + if (seconds == 0 || count / seconds < 1) { std::deque<QueuedMessage> expired; { Mutex::ScopedLock locker(messageLock); - messages->removeIf(boost::bind(&collect_if_expired, expired, _1)); + messages->removeIf(boost::bind(&collect_if_expired, boost::ref(expired), _1)); + } + + for (std::deque<QueuedMessage>::const_iterator i = expired.begin(); + i != expired.end(); ++i) { + { + Mutex::ScopedLock locker(messageLock); + observeAcquire(*i, locker); + } + dequeue( 0, *i ); } - for_each(expired.begin(), expired.end(), bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); } } + +namespace { + // for use with purge/move below - collect messages that match a given filter + // + class MessageFilter + { + public: + static const std::string typeKey; + static const std::string paramsKey; + static MessageFilter *create( const ::qpid::types::Variant::Map *filter ); + virtual bool match( const QueuedMessage& ) const { return true; } + virtual ~MessageFilter() {} + protected: + MessageFilter() {}; + }; + const std::string MessageFilter::typeKey("filter_type"); + const std::string MessageFilter::paramsKey("filter_params"); + + // filter by message header string value exact match + class HeaderMatchFilter : public MessageFilter + { + public: + /* Config: + { 'filter_type' : 'header_match_str', + 'filter_params' : { 'header_key' : "<header name>", + 'header_value' : "<value to match>" + } + } + */ + static const std::string typeKey; + static const std::string headerKey; + static const std::string valueKey; + HeaderMatchFilter( const std::string& _header, const std::string& _value ) + : MessageFilter (), header(_header), value(_value) {} + bool match( const QueuedMessage& msg ) const + { + const qpid::framing::FieldTable* headers = msg.payload->getApplicationHeaders(); + if (!headers) return false; + FieldTable::ValuePtr h = headers->get(header); + if (!h || !h->convertsTo<std::string>()) return false; + return h->get<std::string>() == value; + } + private: + const std::string header; + const std::string value; + }; + const std::string HeaderMatchFilter::typeKey("header_match_str"); + const std::string HeaderMatchFilter::headerKey("header_key"); + const std::string HeaderMatchFilter::valueKey("header_value"); + + // factory to create correct filter based on map + MessageFilter* MessageFilter::create( const ::qpid::types::Variant::Map *filter ) + { + using namespace qpid::types; + if (filter && !filter->empty()) { + Variant::Map::const_iterator i = filter->find(MessageFilter::typeKey); + if (i != filter->end()) { + + if (i->second.asString() == HeaderMatchFilter::typeKey) { + Variant::Map::const_iterator p = filter->find(MessageFilter::paramsKey); + if (p != filter->end() && p->second.getType() == VAR_MAP) { + Variant::Map::const_iterator k = p->second.asMap().find(HeaderMatchFilter::headerKey); + Variant::Map::const_iterator v = p->second.asMap().find(HeaderMatchFilter::valueKey); + if (k != p->second.asMap().end() && v != p->second.asMap().end()) { + std::string headerKey(k->second.asString()); + std::string value(v->second.asString()); + QPID_LOG(debug, "Message filtering by header value configured. key: " << headerKey << " value: " << value ); + return new HeaderMatchFilter( headerKey, value ); + } + } + } + } + QPID_LOG(error, "Ignoring unrecognized message filter: '" << *filter << "'"); + } + return new MessageFilter(); + } + + // used by removeIf() to collect all messages matching a filter, maximum match count is + // optional. + struct Collector { + const uint32_t maxMatches; + MessageFilter& filter; + std::deque<QueuedMessage> matches; + Collector(MessageFilter& filter, uint32_t max) + : maxMatches(max), filter(filter) {} + bool operator() (QueuedMessage& qm) + { + if (maxMatches == 0 || matches.size() < maxMatches) { + if (filter.match( qm )) { + matches.push_back(qm); + return true; + } + } + return false; + } + }; + +} // end namespace + + /** * purge - for purging all or some messages on a queue * depending on the purge_request @@ -459,63 +615,77 @@ void Queue::purgeExpired() * The dest exchange may be supplied to re-route messages through the exchange. * It is safe to re-route messages such that they arrive back on the same queue, * even if the queue is ordered by priority. + * + * An optional filter can be supplied that will be applied against each message. The + * message is purged only if the filter matches. See MessageDistributor for more detail. */ -uint32_t Queue::purge(const uint32_t purge_request, boost::shared_ptr<Exchange> dest) +uint32_t Queue::purge(const uint32_t purge_request, boost::shared_ptr<Exchange> dest, + const qpid::types::Variant::Map *filter) { - Mutex::ScopedLock locker(messageLock); - uint32_t purge_count = purge_request; // only comes into play if >0 - std::deque<DeliverableMessage> rerouteQueue; + std::auto_ptr<MessageFilter> mf(MessageFilter::create(filter)); + Collector c(*mf.get(), 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()) { + Mutex::ScopedLock locker(messageLock); + messages->removeIf( boost::bind<bool>(boost::ref(c), _1) ); + for (std::deque<QueuedMessage>::iterator qmsg = c.matches.begin(); + qmsg != c.matches.end(); ++qmsg) { + // Update observers and message state: + observeAcquire(*qmsg, locker); + dequeue(0, *qmsg); + // now reroute if necessary if (dest.get()) { - // - // If there is a destination exchange, stage the messages onto a reroute queue - // so they don't wind up getting purged more than once. - // - DeliverableMessage msg(messages->front().payload); - rerouteQueue.push_back(msg); + assert(qmsg->payload); + DeliverableMessage dmsg(qmsg->payload); + dest->routeWithAlternate(dmsg); } - popAndDequeue(); - count++; } - - // - // Re-route purged messages into the destination exchange. Note that there's no need - // to test dest.get() here because if it is NULL, the rerouteQueue will be empty. - // - while (!rerouteQueue.empty()) { - DeliverableMessage msg(rerouteQueue.front()); - rerouteQueue.pop_front(); - dest->route(msg, msg.getMessage().getRoutingKey(), - msg.getMessage().getApplicationHeaders()); - } - - return count; + return c.matches.size(); } -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 +uint32_t Queue::move(const Queue::shared_ptr destq, uint32_t qty, + const qpid::types::Variant::Map *filter) +{ + std::auto_ptr<MessageFilter> mf(MessageFilter::create(filter)); + Collector c(*mf.get(), qty); - while((!qty || move_count--) && !messages->empty()) { - QueuedMessage qmsg = messages->front(); - boost::intrusive_ptr<Message> msg = qmsg.payload; - destq->deliver(msg); // deliver message to the destination queue - pop(); - dequeue(0, qmsg); - count++; + Mutex::ScopedLock locker(messageLock); + messages->removeIf( boost::bind<bool>(boost::ref(c), _1) ); + + for (std::deque<QueuedMessage>::iterator qmsg = c.matches.begin(); + qmsg != c.matches.end(); ++qmsg) { + // Update observers and message state: + observeAcquire(*qmsg, locker); + dequeue(0, *qmsg); + // and move to destination Queue. + assert(qmsg->payload); + destq->deliver(qmsg->payload); } - return count; + return c.matches.size(); } -void Queue::pop() +/** Acquire the front (oldest) message from the in-memory queue. + * assumes messageLock held by caller + */ +void Queue::pop(const Mutex::ScopedLock& locker) { assertClusterSafe(); - messages->pop(); - ++dequeueTracker; + QueuedMessage msg; + if (messages->pop(msg)) { + observeAcquire(msg, locker); + ++dequeueSincePurge; + } +} + +/** Acquire the message at the given position, return true and msg if acquire succeeds */ +bool Queue::acquire(const qpid::framing::SequenceNumber& position, QueuedMessage& msg, + const Mutex::ScopedLock& locker) +{ + if (messages->remove(position, msg)) { + observeAcquire(msg, locker); + ++dequeueSincePurge; + return true; + } + return false; } void Queue::push(boost::intrusive_ptr<Message>& msg, bool isRecovery){ @@ -524,13 +694,15 @@ void Queue::push(boost::intrusive_ptr<Message>& msg, bool isRecovery){ QueuedMessage removed; bool dequeueRequired = false; { - Mutex::ScopedLock locker(messageLock); + Mutex::ScopedLock locker(messageLock); QueuedMessage qm(this, msg, ++sequence); - if (insertSeqNo) msg->getOrInsertHeaders().setInt64(seqNoKey, sequence); - + if (insertSeqNo) msg->insertCustomProperty(seqNoKey, sequence); + dequeueRequired = messages->push(qm, removed); + if (dequeueRequired) + observeAcquire(removed, locker); listeners.populate(copy); - enqueued(qm); + observeEnqueue(qm, locker); } copy.notify(); if (dequeueRequired) { @@ -546,7 +718,7 @@ void Queue::push(boost::intrusive_ptr<Message>& msg, bool isRecovery){ void isEnqueueComplete(uint32_t* result, const QueuedMessage& message) { - if (message.payload->isEnqueueComplete()) (*result)++; + if (message.payload->isIngressComplete()) (*result)++; } /** function only provided for unit tests, or code not in critical message path */ @@ -606,7 +778,7 @@ void Queue::setLastNodeFailure() } -// return true if store exists, +// return true if store exists, bool Queue::enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message>& msg, bool suppressPolicyCheck) { ScopedUse u(barrier); @@ -620,24 +792,21 @@ bool Queue::enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message>& 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)); + for_each(dequeues.begin(), dequeues.end(), boost::bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); } if (inLastNodeFailure && persistLastNode){ msg->forcePersistent(); } - + if (traceId.size()) { - //copy on write: take deep copy of message before modifying it - //as the frames may already be available for delivery on other - //threads - boost::intrusive_ptr<Message> copy(new Message(*msg)); - msg = copy; msg->addTraceId(traceId); } if ((msg->isPersistent() || msg->checkContentReleasable()) && store) { - msg->enqueueAsync(shared_from_this(), store); //increment to async counter -- for message sent to more than one queue + // mark the message as being enqueued - the store MUST CALL msg->enqueueComplete() + // when it considers the message stored. + msg->enqueueAsync(shared_from_this(), store); boost::intrusive_ptr<PersistableMessage> pmsg = boost::static_pointer_cast<PersistableMessage>(msg); store->enqueue(ctxt, pmsg, *this); return true; @@ -654,10 +823,10 @@ bool Queue::enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message>& msg void Queue::enqueueAborted(boost::intrusive_ptr<Message> msg) { Mutex::ScopedLock locker(messageLock); - if (policy.get()) policy->enqueueAborted(msg); + if (policy.get()) policy->enqueueAborted(msg); } -// return true if store exists, +// return true if store exists, bool Queue::dequeue(TransactionContext* ctxt, const QueuedMessage& msg) { ScopedUse u(barrier); @@ -666,8 +835,8 @@ bool Queue::dequeue(TransactionContext* ctxt, const QueuedMessage& msg) { Mutex::ScopedLock locker(messageLock); if (!isEnqueued(msg)) return false; - if (!ctxt) { - dequeued(msg); + if (!ctxt) { + observeDequeue(msg, locker); } } // This check prevents messages which have been forced persistent on one queue from dequeuing @@ -687,7 +856,7 @@ bool Queue::dequeue(TransactionContext* ctxt, const QueuedMessage& msg) void Queue::dequeueCommitted(const QueuedMessage& msg) { Mutex::ScopedLock locker(messageLock); - dequeued(msg); + observeDequeue(msg, locker); if (mgmtObject != 0) { mgmtObject->inc_msgTxnDequeues(); mgmtObject->inc_byteTxnDequeues(msg.payload->contentSize()); @@ -695,21 +864,23 @@ void Queue::dequeueCommitted(const QueuedMessage& msg) } /** - * Removes a message from the in-memory delivery queue as well - * dequeing it from the logical (and persistent if applicable) queue + * Removes the first (oldest) message from the in-memory delivery queue as well dequeing + * it from the logical (and persistent if applicable) queue */ -void Queue::popAndDequeue() +void Queue::popAndDequeue(const Mutex::ScopedLock& held) { - QueuedMessage msg = messages->front(); - pop(); - dequeue(0, msg); + if (!messages->empty()) { + QueuedMessage msg = messages->front(); + pop(held); + dequeue(0, msg); + } } /** * Updates policy and management when a message has been dequeued, * expects messageLock to be held */ -void Queue::dequeued(const QueuedMessage& msg) +void Queue::observeDequeue(const QueuedMessage& msg, const Mutex::ScopedLock&) { if (policy.get()) policy->dequeued(msg); mgntDeqStats(msg.payload); @@ -722,6 +893,33 @@ void Queue::dequeued(const QueuedMessage& msg) } } +/** updates queue observers when a message has become unavailable for transfer, + * expects messageLock to be held + */ +void Queue::observeAcquire(const QueuedMessage& msg, const Mutex::ScopedLock&) +{ + for (Observers::const_iterator i = observers.begin(); i != observers.end(); ++i) { + try{ + (*i)->acquired(msg); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of message removal for queue " << getName() << ": " << e.what()); + } + } +} + +/** updates queue observers when a message has become re-available for transfer, + * expects messageLock to be held + */ +void Queue::observeRequeue(const QueuedMessage& msg, const Mutex::ScopedLock&) +{ + for (Observers::const_iterator i = observers.begin(); i != observers.end(); ++i) { + try{ + (*i)->requeued(msg); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of message requeue for queue " << getName() << ": " << e.what()); + } + } +} void Queue::create(const FieldTable& _settings) { @@ -729,7 +927,7 @@ void Queue::create(const FieldTable& _settings) if (store) { store->create(*this, _settings); } - configure(_settings); + configureImpl(_settings); } @@ -742,8 +940,8 @@ int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::stri return v->get<int>(); } else if (v->convertsTo<std::string>()){ std::string s = v->get<std::string>(); - try { - return boost::lexical_cast<int>(s); + try { + return boost::lexical_cast<int>(s); } catch(const boost::bad_lexical_cast&) { QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << s); return 0; @@ -754,15 +952,45 @@ int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::stri } } -void Queue::configure(const FieldTable& _settings, bool recovering) +bool getBoolSetting(const qpid::framing::FieldTable& settings, const std::string& key) { + qpid::framing::FieldTable::ValuePtr v = settings.get(key); + if (!v) { + return false; + } else if (v->convertsTo<int>()) { + return v->get<int>() != 0; + } else if (v->convertsTo<std::string>()){ + std::string s = v->get<std::string>(); + if (s == "True") return true; + if (s == "true") return true; + if (s == "False") return false; + if (s == "false") return false; + try { + return boost::lexical_cast<bool>(s); + } catch(const boost::bad_lexical_cast&) { + QPID_LOG(warning, "Ignoring invalid boolean value for " << key << ": " << s); + return false; + } + } else { + QPID_LOG(warning, "Ignoring invalid boolean value for " << key << ": " << *v); + return false; + } +} + +void Queue::configure(const FieldTable& _settings) +{ + settings = _settings; + configureImpl(settings); +} +void Queue::configureImpl(const FieldTable& _settings) +{ eventMode = _settings.getAsInt(qpidQueueEventGeneration); if (eventMode && broker) { broker->getQueueEvents().observe(*this, eventMode == ENQUEUE_ONLY); } - if (QueuePolicy::getType(_settings) == QueuePolicy::FLOW_TO_DISK && + if (QueuePolicy::getType(_settings) == QueuePolicy::FLOW_TO_DISK && (!store || NullMessageStore::isNullStore(store) || (broker && !(broker->getQueueEvents().isSync())) )) { if ( NullMessageStore::isNullStore(store)) { QPID_LOG(warning, "Flow to disk not valid for non-persisted queue:" << getName()); @@ -776,32 +1004,43 @@ void Queue::configure(const FieldTable& _settings, bool recovering) setPolicy(QueuePolicy::createQueuePolicy(getName(), _settings)); } if (broker && broker->getManagementAgent()) { - ThresholdAlerts::observe(*this, *(broker->getManagementAgent()), _settings); + ThresholdAlerts::observe(*this, *(broker->getManagementAgent()), _settings, broker->getOptions().queueThresholdEventRatio); } //set this regardless of owner to allow use of no-local with exclusive consumers also - noLocal = _settings.get(qpidNoLocal); + noLocal = getBoolSetting(_settings, qpidNoLocal); QPID_LOG(debug, "Configured queue " << getName() << " with no-local=" << noLocal); std::string lvqKey = _settings.getAsString(qpidLastValueQueueKey); if (lvqKey.size()) { QPID_LOG(debug, "Configured queue " << getName() << " as Last Value Queue with key " << lvqKey); messages = std::auto_ptr<Messages>(new MessageMap(lvqKey)); - } else if (_settings.get(qpidLastValueQueueNoBrowse)) { + allocator = boost::shared_ptr<MessageDistributor>(new FifoDistributor( *messages )); + } else if (getBoolSetting(_settings, qpidLastValueQueueNoBrowse)) { QPID_LOG(debug, "Configured queue " << getName() << " as Legacy Last Value Queue with 'no-browse' on"); messages = LegacyLVQ::updateOrReplace(messages, qpidVQMatchProperty, true, broker); - } else if (_settings.get(qpidLastValueQueue)) { + allocator = boost::shared_ptr<MessageDistributor>(new FifoDistributor( *messages )); + } else if (getBoolSetting(_settings, qpidLastValueQueue)) { QPID_LOG(debug, "Configured queue " << getName() << " as Legacy Last Value Queue"); messages = LegacyLVQ::updateOrReplace(messages, qpidVQMatchProperty, false, broker); + allocator = boost::shared_ptr<MessageDistributor>(new FifoDistributor( *messages )); } else { std::auto_ptr<Messages> m = Fairshare::create(_settings); if (m.get()) { messages = m; + allocator = boost::shared_ptr<MessageDistributor>(new FifoDistributor( *messages )); QPID_LOG(debug, "Configured queue " << getName() << " as priority queue."); + } else { // default (FIFO) queue type + // override default message allocator if message groups configured. + boost::shared_ptr<MessageGroupManager> mgm(MessageGroupManager::create( getName(), *messages, _settings)); + if (mgm) { + allocator = mgm; + addObserver(mgm); + } } } - - persistLastNode= _settings.get(qpidPersistLastNode); + + persistLastNode = getBoolSetting(_settings, qpidPersistLastNode); if (persistLastNode) QPID_LOG(debug, "Configured queue to Persist data if cluster fails to one node for: " << getName()); traceId = _settings.getAsString(qpidTraceIdentity); @@ -809,32 +1048,32 @@ void Queue::configure(const FieldTable& _settings, bool recovering) if (excludeList.size()) { split(traceExclude, excludeList, ", "); } - QPID_LOG(debug, "Configured queue " << getName() << " with qpid.trace.id='" << traceId + 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>()); autoDeleteTimeout = getIntegerSetting(_settings, qpidAutoDeleteTimeout); - if (autoDeleteTimeout) - QPID_LOG(debug, "Configured queue " << getName() << " with qpid.auto_delete_timeout=" << autoDeleteTimeout); + if (autoDeleteTimeout) + QPID_LOG(debug, "Configured queue " << getName() << " with qpid.auto_delete_timeout=" << autoDeleteTimeout); - if (mgmtObject != 0) + if (mgmtObject != 0) { mgmtObject->set_arguments(ManagementAgent::toMap(_settings)); + } - if ( isDurable() && ! getPersistenceId() && ! recovering ) - store->create(*this, _settings); + QueueFlowLimit::observe(*this, _settings); } -void Queue::destroy() +void Queue::destroyed() { + unbind(broker->getExchanges()); if (alternateExchange.get()) { Mutex::ScopedLock locker(messageLock); while(!messages->empty()){ DeliverableMessage msg(messages->front().payload); - alternateExchange->route(msg, msg.getMessage().getRoutingKey(), - msg.getMessage().getApplicationHeaders()); - popAndDequeue(); + alternateExchange->routeWithAlternate(msg); + popAndDequeue(locker); } alternateExchange->decAlternateUsers(); } @@ -846,6 +1085,7 @@ void Queue::destroy() store = 0;//ensure we make no more calls to the store for this queue } if (autoDeleteTask) autoDeleteTask = boost::intrusive_ptr<TimerTask>(); + notifyDeleted(); } void Queue::notifyDeleted() @@ -865,9 +1105,9 @@ void Queue::bound(const string& exchange, const string& key, bindings.add(exchange, key, args); } -void Queue::unbind(ExchangeRegistry& exchanges, Queue::shared_ptr shared_ref) +void Queue::unbind(ExchangeRegistry& exchanges) { - bindings.unbind(exchanges, shared_ref); + bindings.unbind(exchanges, shared_from_this()); } void Queue::setPolicy(std::auto_ptr<QueuePolicy> _policy) @@ -880,9 +1120,9 @@ const QueuePolicy* Queue::getPolicy() return policy.get(); } -uint64_t Queue::getPersistenceId() const -{ - return persistenceId; +uint64_t Queue::getPersistenceId() const +{ + return persistenceId; } void Queue::setPersistenceId(uint64_t _persistenceId) const @@ -896,11 +1136,11 @@ void Queue::setPersistenceId(uint64_t _persistenceId) const persistenceId = _persistenceId; } -void Queue::encode(Buffer& buffer) const +void Queue::encode(Buffer& buffer) const { buffer.putShortString(name); buffer.put(settings); - if (policy.get()) { + if (policy.get()) { buffer.put(*policy); } buffer.putShortString(alternateExchange.get() ? alternateExchange->getName() : std::string("")); @@ -914,13 +1154,14 @@ uint32_t Queue::encodedSize() const + (policy.get() ? (*policy).encodedSize() : 0); } -Queue::shared_ptr Queue::decode ( QueueRegistry& queues, Buffer& buffer, bool recovering ) +Queue::shared_ptr Queue::restore( QueueRegistry& queues, Buffer& buffer ) { 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, recovering ); + FieldTable settings; + buffer.get(settings); + boost::shared_ptr<Exchange> alternate; + std::pair<Queue::shared_ptr, bool> result = queues.declare(name, true, false, 0, alternate, settings, true); if (result.first->policy.get() && buffer.available() >= result.first->policy->encodedSize()) { buffer.get ( *(result.first->policy) ); } @@ -952,11 +1193,10 @@ boost::shared_ptr<Exchange> Queue::getAlternateExchange() void tryAutoDeleteImpl(Broker& broker, Queue::shared_ptr queue) { - if (broker.getQueues().destroyIf(queue->getName(), + if (broker.getQueues().destroyIf(queue->getName(), boost::bind(boost::mem_fn(&Queue::canAutoDelete), queue))) { QPID_LOG(debug, "Auto-deleting " << queue->getName()); - queue->unbind(broker.getExchanges(), queue); - queue->destroy(); + queue->destroyed(); } } @@ -965,7 +1205,7 @@ struct AutoDeleteTask : qpid::sys::TimerTask Broker& broker; Queue::shared_ptr queue; - AutoDeleteTask(Broker& b, Queue::shared_ptr q, AbsTime fireTime) + AutoDeleteTask(Broker& b, Queue::shared_ptr q, AbsTime fireTime) : qpid::sys::TimerTask(fireTime, "DelayedAutoDeletion"), broker(b), queue(q) {} void fire() @@ -983,27 +1223,27 @@ void Queue::tryAutoDelete(Broker& broker, Queue::shared_ptr queue) if (queue->autoDeleteTimeout && queue->canAutoDelete()) { AbsTime time(now(), Duration(queue->autoDeleteTimeout * TIME_SEC)); queue->autoDeleteTask = boost::intrusive_ptr<qpid::sys::TimerTask>(new AutoDeleteTask(broker, queue, time)); - broker.getClusterTimer().add(queue->autoDeleteTask); + broker.getClusterTimer().add(queue->autoDeleteTask); QPID_LOG(debug, "Timed auto-delete for " << queue->getName() << " initiated"); } else { tryAutoDeleteImpl(broker, queue); } } -bool Queue::isExclusiveOwner(const OwnershipToken* const o) const -{ +bool Queue::isExclusiveOwner(const OwnershipToken* const o) const +{ Mutex::ScopedLock locker(ownershipLock); - return o == owner; + return o == owner; } -void Queue::releaseExclusiveOwnership() -{ +void Queue::releaseExclusiveOwnership() +{ Mutex::ScopedLock locker(ownershipLock); - owner = 0; + owner = 0; } -bool Queue::setExclusiveOwner(const OwnershipToken* const o) -{ +bool Queue::setExclusiveOwner(const OwnershipToken* const o) +{ //reset auto deletion timer if necessary if (autoDeleteTimeout && autoDeleteTask) { autoDeleteTask->cancel(); @@ -1012,25 +1252,25 @@ bool Queue::setExclusiveOwner(const OwnershipToken* const o) if (owner) { return false; } else { - owner = o; + owner = o; return true; } } -bool Queue::hasExclusiveOwner() const -{ +bool Queue::hasExclusiveOwner() const +{ Mutex::ScopedLock locker(ownershipLock); - return owner != 0; + return owner != 0; } -bool Queue::hasExclusiveConsumer() const -{ - return exclusive; +bool Queue::hasExclusiveConsumer() const +{ + return exclusive; } void Queue::setExternalQueueStore(ExternalQueueStore* inst) { - if (externalQueueStore!=inst && externalQueueStore) - delete externalQueueStore; + if (externalQueueStore!=inst && externalQueueStore) + delete externalQueueStore; externalQueueStore = inst; if (inst) { @@ -1055,7 +1295,7 @@ Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, str case _qmf::Queue::METHOD_PURGE : { _qmf::ArgsQueuePurge& purgeArgs = (_qmf::ArgsQueuePurge&) args; - purge(purgeArgs.i_request); + purge(purgeArgs.i_request, boost::shared_ptr<Exchange>(), &purgeArgs.i_filter); status = Manageable::STATUS_OK; } break; @@ -1076,7 +1316,7 @@ Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, str } } - purge(rerouteArgs.i_request, dest); + purge(rerouteArgs.i_request, dest, &rerouteArgs.i_filter); status = Manageable::STATUS_OK; } break; @@ -1085,6 +1325,14 @@ Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, str return status; } + +void Queue::query(qpid::types::Variant::Map& results) const +{ + Mutex::ScopedLock locker(messageLock); + /** @todo add any interesting queue state into results */ + if (allocator) allocator->query(results); +} + void Queue::setPosition(SequenceNumber n) { Mutex::ScopedLock locker(messageLock); sequence = n; @@ -1119,7 +1367,10 @@ void Queue::insertSequenceNumbers(const std::string& key) QPID_LOG(debug, "Inserting sequence numbers as " << key); } -void Queue::enqueued(const QueuedMessage& m) +/** updates queue observers and state when a message has become available for transfer, + * expects messageLock to be held + */ +void Queue::observeEnqueue(const QueuedMessage& m, const Mutex::ScopedLock&) { for (Observers::iterator i = observers.begin(); i != observers.end(); ++i) { try { @@ -1142,7 +1393,8 @@ void Queue::updateEnqueued(const QueuedMessage& m) if (policy.get()) { policy->recoverEnqueued(payload); } - enqueued(m); + Mutex::ScopedLock locker(messageLock); + observeEnqueue(m, locker); } else { QPID_LOG(warning, "Queue informed of enqueued message that has no payload"); } @@ -1166,6 +1418,7 @@ void Queue::checkNotDeleted() void Queue::addObserver(boost::shared_ptr<QueueObserver> observer) { + Mutex::ScopedLock locker(messageLock); observers.insert(observer); } @@ -1175,6 +1428,32 @@ void Queue::flush() if (u.acquired && store) store->flush(*this); } + +bool Queue::bind(boost::shared_ptr<Exchange> exchange, const std::string& key, + const qpid::framing::FieldTable& arguments) +{ + if (exchange->bind(shared_from_this(), key, &arguments)) { + bound(exchange->getName(), key, arguments); + if (exchange->isDurable() && isDurable()) { + store->bind(*exchange, *this, key, arguments); + } + return true; + } else { + return false; + } +} + + +const Broker* Queue::getBroker() +{ + return broker; +} + +void Queue::setDequeueSincePurge(uint32_t value) { + dequeueSincePurge = value; +} + + Queue::UsageBarrier::UsageBarrier(Queue& q) : parent(q), count(0) {} bool Queue::UsageBarrier::acquire() diff --git a/cpp/src/qpid/broker/Queue.h b/cpp/src/qpid/broker/Queue.h index 12a3d273be..59ae41e768 100644 --- a/cpp/src/qpid/broker/Queue.h +++ b/cpp/src/qpid/broker/Queue.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 @@ -32,9 +32,9 @@ #include "qpid/broker/QueueBindings.h" #include "qpid/broker/QueueListeners.h" #include "qpid/broker/QueueObserver.h" -#include "qpid/broker/RateTracker.h" #include "qpid/framing/FieldTable.h" +#include "qpid/sys/AtomicValue.h" #include "qpid/sys/Monitor.h" #include "qpid/sys/Timer.h" #include "qpid/management/Manageable.h" @@ -59,7 +59,7 @@ class MessageStore; class QueueEvents; class QueueRegistry; class TransactionContext; -class Exchange; +class MessageDistributor; /** * The brokers representation of an amqp queue. Messages are @@ -74,13 +74,13 @@ class Queue : public boost::enable_shared_from_this<Queue>, { Queue& parent; uint count; - + UsageBarrier(Queue&); bool acquire(); void release(); void destroy(); }; - + struct ScopedUse { UsageBarrier& barrier; @@ -88,7 +88,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, ScopedUse(UsageBarrier& b) : barrier(b), acquired(barrier.acquire()) {} ~ScopedUse() { if (acquired) barrier.release(); } }; - + typedef std::set< boost::shared_ptr<QueueObserver> > Observers; enum ConsumeCode {NO_MESSAGES=0, CANT_CONSUME=1, CONSUMED=2}; @@ -119,7 +119,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, boost::shared_ptr<Exchange> alternateExchange; framing::SequenceNumber sequence; qmf::org::apache::qpid::broker::Queue* mgmtObject; - RateTracker dequeueTracker; + sys::AtomicValue<uint32_t> dequeueSincePurge; // Count dequeues since last purge. int eventMode; Observers observers; bool insertSeqNo; @@ -129,26 +129,36 @@ class Queue : public boost::enable_shared_from_this<Queue>, UsageBarrier barrier; int autoDeleteTimeout; boost::intrusive_ptr<qpid::sys::TimerTask> autoDeleteTask; + boost::shared_ptr<MessageDistributor> allocator; void push(boost::intrusive_ptr<Message>& msg, bool isRecovery=false); void setPolicy(std::auto_ptr<QueuePolicy> policy); - 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); + 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 removeListener(Consumer::shared_ptr); bool isExcluded(boost::intrusive_ptr<Message>& msg); - void enqueued(const QueuedMessage& msg); - void dequeued(const QueuedMessage& msg); - void pop(); - void popAndDequeue(); - QueuedMessage getFront(); + /** update queue observers, stats, policy, etc when the messages' state changes. Lock + * must be held by caller */ + void observeEnqueue(const QueuedMessage& msg, const sys::Mutex::ScopedLock& lock); + void observeAcquire(const QueuedMessage& msg, const sys::Mutex::ScopedLock& lock); + void observeRequeue(const QueuedMessage& msg, const sys::Mutex::ScopedLock& lock); + void observeDequeue(const QueuedMessage& msg, const sys::Mutex::ScopedLock& lock); + + /** modify the Queue's message container - assumes messageLock held */ + void pop(const sys::Mutex::ScopedLock& held); // acquire front msg + void popAndDequeue(const sys::Mutex::ScopedLock& held); // acquire and dequeue front msg + // acquire message @ position, return true and set msg if acquire succeeds + bool acquire(const qpid::framing::SequenceNumber& position, QueuedMessage& msg, + const sys::Mutex::ScopedLock& held); + void forcePersistent(QueuedMessage& msg); int getEventMode(); + void configureImpl(const qpid::framing::FieldTable& settings); inline void mgntEnqStats(const boost::intrusive_ptr<Message>& msg) { @@ -172,8 +182,9 @@ class Queue : public boost::enable_shared_from_this<Queue>, } } } - + void checkNotDeleted(); + void notifyDeleted(); public: @@ -182,29 +193,50 @@ class Queue : public boost::enable_shared_from_this<Queue>, typedef std::vector<shared_ptr> vector; QPID_BROKER_EXTERN Queue(const std::string& name, - bool autodelete = false, - MessageStore* const store = 0, + bool autodelete = false, + MessageStore* const store = 0, const OwnershipToken* const owner = 0, management::Manageable* parent = 0, Broker* broker = 0); QPID_BROKER_EXTERN ~Queue(); + /** allow the Consumer to consume or browse the next available message */ QPID_BROKER_EXTERN bool dispatch(Consumer::shared_ptr); - void create(const qpid::framing::FieldTable& settings); + /** allow the Consumer to acquire a message that it has browsed. + * @param msg - message to be acquired. + * @return false if message is no longer available for acquire. + */ + QPID_BROKER_EXTERN bool acquire(const QueuedMessage& msg, const std::string& consumer); - // "recovering" means we are doing a MessageStore recovery. - QPID_BROKER_EXTERN void configure(const qpid::framing::FieldTable& settings, - bool recovering = false); - void destroy(); - void notifyDeleted(); + /** + * Used to configure a new queue and create a persistent record + * for it in store if required. + */ + QPID_BROKER_EXTERN void create(const qpid::framing::FieldTable& settings); + + /** + * Used to reconfigure a recovered queue (does not create + * persistent record in store). + */ + QPID_BROKER_EXTERN void configure(const qpid::framing::FieldTable& settings); + void destroyed(); QPID_BROKER_EXTERN void bound(const std::string& exchange, const std::string& key, const qpid::framing::FieldTable& args); - QPID_BROKER_EXTERN void unbind(ExchangeRegistry& exchanges, - Queue::shared_ptr shared_ref); + //TODO: get unbind out of the public interface; only there for purposes of one unit test + QPID_BROKER_EXTERN void unbind(ExchangeRegistry& exchanges); + /** + * Bind self to specified exchange, and record that binding for unbinding on delete. + */ + bool bind(boost::shared_ptr<Exchange> exchange, const std::string& key, + const qpid::framing::FieldTable& arguments=qpid::framing::FieldTable()); - QPID_BROKER_EXTERN bool acquire(const QueuedMessage& msg); + /** Acquire the message at the given position if it is available for acquire. Not to + * be used by clients, but used by the broker for queue management. + * @param message - set to the acquired message if true returned. + * @return true if the message has been acquired. + */ QPID_BROKER_EXTERN bool acquireMessageAt(const qpid::framing::SequenceNumber& position, QueuedMessage& message); /** @@ -233,11 +265,14 @@ class Queue : public boost::enable_shared_from_this<Queue>, bool exclusive = false); QPID_BROKER_EXTERN void cancel(Consumer::shared_ptr c); - uint32_t purge(const uint32_t purge_request=0, boost::shared_ptr<Exchange> dest=boost::shared_ptr<Exchange>()); //defaults to all messages - QPID_BROKER_EXTERN void purgeExpired(); + uint32_t purge(const uint32_t purge_request=0, //defaults to all messages + boost::shared_ptr<Exchange> dest=boost::shared_ptr<Exchange>(), + const ::qpid::types::Variant::Map *filter=0); + QPID_BROKER_EXTERN void purgeExpired(sys::Duration); //move qty # of messages to destination Queue destq - uint32_t move(const Queue::shared_ptr destq, uint32_t qty); + uint32_t move(const Queue::shared_ptr destq, uint32_t qty, + const qpid::types::Variant::Map *filter=0); QPID_BROKER_EXTERN uint32_t getMessageCount() const; QPID_BROKER_EXTERN uint32_t getEnqueueCompleteMessageCount() const; @@ -276,8 +311,8 @@ class Queue : public boost::enable_shared_from_this<Queue>, * 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. - */ + * clustered broker. + */ void updateEnqueued(const QueuedMessage& msg); /** @@ -288,14 +323,14 @@ class Queue : public boost::enable_shared_from_this<Queue>, * accepted it). */ bool isEnqueued(const QueuedMessage& msg); - + /** - * Gets the next available message + * Acquires the next available (oldest) message */ QPID_BROKER_EXTERN QueuedMessage get(); - /** Get the message at position pos */ - QPID_BROKER_EXTERN QueuedMessage find(framing::SequenceNumber pos) const; + /** Get the message at position pos, returns true if found and sets msg */ + QPID_BROKER_EXTERN bool find(framing::SequenceNumber pos, QueuedMessage& msg ) const; const QueuePolicy* getPolicy(); @@ -309,8 +344,13 @@ class Queue : public boost::enable_shared_from_this<Queue>, void encode(framing::Buffer& buffer) const; uint32_t encodedSize() const; - // "recovering" means we are doing a MessageStore recovery. - static Queue::shared_ptr decode(QueueRegistry& queues, framing::Buffer& buffer, bool recovering = false ); + /** + * Restores a queue from encoded data (used in recovery) + * + * Note: restored queue will be neither auto-deleted or have an + * exclusive owner + */ + static Queue::shared_ptr restore(QueueRegistry& queues, framing::Buffer& buffer); static void tryAutoDelete(Broker& broker, Queue::shared_ptr); virtual void setExternalQueueStore(ExternalQueueStore* inst); @@ -319,6 +359,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, management::ManagementObject* GetManagementObject (void) const; management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + void query(::qpid::types::Variant::Map&) const; /** Apply f to each Message on the queue. */ template <class F> void eachMessage(F f) { @@ -331,6 +372,11 @@ class Queue : public boost::enable_shared_from_this<Queue>, bindings.eachBinding(f); } + /** Apply f to each Observer on the queue */ + template <class F> void eachObserver(F f) { + std::for_each<Observers::iterator, F>(observers.begin(), observers.end(), f); + } + /** Set the position sequence number for the next message on the queue. * Must be >= the current sequence number. * Used by cluster to replicate queues. @@ -358,6 +404,11 @@ class Queue : public boost::enable_shared_from_this<Queue>, void recoverPrepared(boost::intrusive_ptr<Message>& msg); void flush(); + + const Broker* getBroker(); + + uint32_t getDequeueSincePurge() { return dequeueSincePurge.get(); } + void setDequeueSincePurge(uint32_t value); }; } } diff --git a/cpp/src/qpid/broker/QueueCleaner.cpp b/cpp/src/qpid/broker/QueueCleaner.cpp index 3499ea8a4d..838bc28be8 100644 --- a/cpp/src/qpid/broker/QueueCleaner.cpp +++ b/cpp/src/qpid/broker/QueueCleaner.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 @@ -27,7 +27,7 @@ namespace qpid { namespace broker { -QueueCleaner::QueueCleaner(QueueRegistry& q, sys::Timer& t) : queues(q), timer(t) {} +QueueCleaner::QueueCleaner(QueueRegistry& q, sys::Timer* t) : queues(q), timer(t) {} QueueCleaner::~QueueCleaner() { @@ -36,10 +36,16 @@ QueueCleaner::~QueueCleaner() void QueueCleaner::start(qpid::sys::Duration p) { + period = p; task = new Task(*this, p); - timer.add(task); + timer->add(task); } +void QueueCleaner::setTimer(qpid::sys::Timer* timer) { + this->timer = timer; +} + + QueueCleaner::Task::Task(QueueCleaner& p, qpid::sys::Duration d) : sys::TimerTask(d,"QueueCleaner"), parent(p) {} void QueueCleaner::Task::fire() @@ -65,9 +71,9 @@ void QueueCleaner::fired() std::vector<Queue::shared_ptr> copy; CollectQueues collect(©); queues.eachQueue(collect); - std::for_each(copy.begin(), copy.end(), boost::bind(&Queue::purgeExpired, _1)); + std::for_each(copy.begin(), copy.end(), boost::bind(&Queue::purgeExpired, _1, period)); task->setupNextFire(); - timer.add(task); + timer->add(task); } diff --git a/cpp/src/qpid/broker/QueueCleaner.h b/cpp/src/qpid/broker/QueueCleaner.h index 11c2d180ac..ffebfe3e1b 100644 --- a/cpp/src/qpid/broker/QueueCleaner.h +++ b/cpp/src/qpid/broker/QueueCleaner.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 @@ -35,14 +35,15 @@ class QueueRegistry; class QueueCleaner { public: - QPID_BROKER_EXTERN QueueCleaner(QueueRegistry& queues, sys::Timer& timer); + QPID_BROKER_EXTERN QueueCleaner(QueueRegistry& queues, sys::Timer* timer); QPID_BROKER_EXTERN ~QueueCleaner(); - QPID_BROKER_EXTERN void start(qpid::sys::Duration period); + QPID_BROKER_EXTERN void start(sys::Duration period); + QPID_BROKER_EXTERN void setTimer(sys::Timer* timer); private: class Task : public sys::TimerTask { public: - Task(QueueCleaner& parent, qpid::sys::Duration duration); + Task(QueueCleaner& parent, sys::Duration duration); void fire(); private: QueueCleaner& parent; @@ -50,7 +51,8 @@ class QueueCleaner boost::intrusive_ptr<sys::TimerTask> task; QueueRegistry& queues; - sys::Timer& timer; + sys::Timer* timer; + sys::Duration period; void fired(); }; diff --git a/cpp/src/qpid/broker/QueueEvents.cpp b/cpp/src/qpid/broker/QueueEvents.cpp index 2c540ff1ad..c66bdabf0f 100644 --- a/cpp/src/qpid/broker/QueueEvents.cpp +++ b/cpp/src/qpid/broker/QueueEvents.cpp @@ -129,6 +129,10 @@ class EventGenerator : public QueueObserver { if (!enqueueOnly) manager.dequeued(m); } + + void acquired(const QueuedMessage&) {}; + void requeued(const QueuedMessage&) {}; + private: QueueEvents& manager; const bool enqueueOnly; diff --git a/cpp/src/qpid/broker/QueueFlowLimit.cpp b/cpp/src/qpid/broker/QueueFlowLimit.cpp new file mode 100644 index 0000000000..f15bb45c01 --- /dev/null +++ b/cpp/src/qpid/broker/QueueFlowLimit.cpp @@ -0,0 +1,410 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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/QueueFlowLimit.h" +#include "qpid/broker/Broker.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" +#include "qpid/sys/Mutex.h" +#include "qpid/broker/SessionState.h" +#include "qpid/sys/ClusterSafe.h" + +#include "qmf/org/apache/qpid/broker/Queue.h" + +#include <sstream> + +using namespace qpid::broker; +using namespace qpid::framing; + +namespace { + /** ensure that the configured flow control stop and resume values are + * valid with respect to the maximum queue capacity, and each other + */ + template <typename T> + void validateFlowConfig(T max, T& stop, T& resume, const std::string& type, const std::string& queue) + { + if (resume > stop) { + throw InvalidArgumentException(QPID_MSG("Queue \"" << queue << "\": qpid.flow_resume_" << type + << "=" << resume + << " must be less than qpid.flow_stop_" << type + << "=" << stop)); + } + if (resume == 0) resume = stop; + if (max != 0 && (max < stop)) { + throw InvalidArgumentException(QPID_MSG("Queue \"" << queue << "\": qpid.flow_stop_" << type + << "=" << stop + << " must be less than qpid.max_" << type + << "=" << max)); + } + } + + /** extract a capacity value as passed in an argument map + */ + uint64_t getCapacity(const FieldTable& settings, const std::string& key, uint64_t defaultValue) + { + FieldTable::ValuePtr v = settings.get(key); + + int64_t result = 0; + + if (!v) return defaultValue; + if (v->getType() == 0x23) { + QPID_LOG(debug, "Value for " << key << " specified as float: " << v->get<float>()); + } else if (v->getType() == 0x33) { + QPID_LOG(debug, "Value for " << key << " specified as double: " << v->get<double>()); + } else if (v->convertsTo<int64_t>()) { + result = v->get<int64_t>(); + QPID_LOG(debug, "Got integer value for " << key << ": " << result); + if (result >= 0) return result; + } else if (v->convertsTo<string>()) { + string s(v->get<string>()); + QPID_LOG(debug, "Got string value for " << key << ": " << s); + std::istringstream convert(s); + if (convert >> result && result >= 0) return result; + } + + QPID_LOG(warning, "Cannot convert " << key << " to unsigned integer, using default (" << defaultValue << ")"); + return defaultValue; + } +} + + + +QueueFlowLimit::QueueFlowLimit(Queue *_queue, + uint32_t _flowStopCount, uint32_t _flowResumeCount, + uint64_t _flowStopSize, uint64_t _flowResumeSize) + : StatefulQueueObserver(std::string("QueueFlowLimit")), queue(_queue), queueName("<unknown>"), + flowStopCount(_flowStopCount), flowResumeCount(_flowResumeCount), + flowStopSize(_flowStopSize), flowResumeSize(_flowResumeSize), + flowStopped(false), count(0), size(0), queueMgmtObj(0), broker(0) +{ + uint32_t maxCount(0); + uint64_t maxSize(0); + + if (queue) { + queueName = _queue->getName(); + if (queue->getPolicy()) { + maxSize = _queue->getPolicy()->getMaxSize(); + maxCount = _queue->getPolicy()->getMaxCount(); + } + broker = queue->getBroker(); + queueMgmtObj = dynamic_cast<_qmfBroker::Queue*> (queue->GetManagementObject()); + if (queueMgmtObj) { + queueMgmtObj->set_flowStopped(isFlowControlActive()); + } + } + validateFlowConfig( maxCount, flowStopCount, flowResumeCount, "count", queueName ); + validateFlowConfig( maxSize, flowStopSize, flowResumeSize, "size", queueName ); + QPID_LOG(info, "Queue \"" << queueName << "\": Flow limit created: flowStopCount=" << flowStopCount + << ", flowResumeCount=" << flowResumeCount + << ", flowStopSize=" << flowStopSize << ", flowResumeSize=" << flowResumeSize ); +} + + +QueueFlowLimit::~QueueFlowLimit() +{ + sys::Mutex::ScopedLock l(indexLock); + if (!index.empty()) { + // we're gone - release all pending msgs + for (std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> >::iterator itr = index.begin(); + itr != index.end(); ++itr) + if (itr->second) + try { + itr->second->getIngressCompletion().finishCompleter(); + } catch (...) {} // ignore - not safe for a destructor to throw. + index.clear(); + } +} + + +void QueueFlowLimit::enqueued(const QueuedMessage& msg) +{ + sys::Mutex::ScopedLock l(indexLock); + + ++count; + size += msg.payload->contentSize(); + + if (!flowStopped) { + if (flowStopCount && count > flowStopCount) { + flowStopped = true; + QPID_LOG(info, "Queue \"" << queueName << "\": has reached " << flowStopCount << " enqueued messages. Producer flow control activated." ); + } else if (flowStopSize && size > flowStopSize) { + flowStopped = true; + QPID_LOG(info, "Queue \"" << queueName << "\": has reached " << flowStopSize << " enqueued bytes. Producer flow control activated." ); + } + if (flowStopped && queueMgmtObj) { + queueMgmtObj->set_flowStopped(true); + queueMgmtObj->inc_flowStoppedCount(); + } + } + + if (flowStopped || !index.empty()) { + // ignore flow control if we are populating the queue due to cluster replication: + if (broker && broker->isClusterUpdatee()) { + QPID_LOG(trace, "Queue \"" << queueName << "\": ignoring flow control for msg pos=" << msg.position); + return; + } + QPID_LOG(trace, "Queue \"" << queueName << "\": setting flow control for msg pos=" << msg.position); + msg.payload->getIngressCompletion().startCompleter(); // don't complete until flow resumes + bool unique; + unique = index.insert(std::pair<framing::SequenceNumber, boost::intrusive_ptr<Message> >(msg.position, msg.payload)).second; + // Like this to avoid tripping up unused variable warning when NDEBUG set + if (!unique) assert(unique); + } +} + + + +void QueueFlowLimit::dequeued(const QueuedMessage& msg) +{ + sys::Mutex::ScopedLock l(indexLock); + + if (count > 0) { + --count; + } else { + throw Exception(QPID_MSG("Flow limit count underflow on dequeue. Queue=" << queueName)); + } + + uint64_t _size = msg.payload->contentSize(); + if (_size <= size) { + size -= _size; + } else { + throw Exception(QPID_MSG("Flow limit size underflow on dequeue. Queue=" << queueName)); + } + + if (flowStopped && + (flowResumeSize == 0 || size < flowResumeSize) && + (flowResumeCount == 0 || count < flowResumeCount)) { + flowStopped = false; + if (queueMgmtObj) + queueMgmtObj->set_flowStopped(false); + QPID_LOG(info, "Queue \"" << queueName << "\": has drained below the flow control resume level. Producer flow control deactivated." ); + } + + if (!index.empty()) { + if (!flowStopped) { + // flow enabled - release all pending msgs + for (std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> >::iterator itr = index.begin(); + itr != index.end(); ++itr) + if (itr->second) + itr->second->getIngressCompletion().finishCompleter(); + index.clear(); + } else { + // even if flow controlled, we must release this msg as it is being dequeued + std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> >::iterator itr = index.find(msg.position); + if (itr != index.end()) { // this msg is flow controlled, release it: + msg.payload->getIngressCompletion().finishCompleter(); + index.erase(itr); + } + } + } +} + + +void QueueFlowLimit::encode(Buffer& buffer) const +{ + buffer.putLong(flowStopCount); + buffer.putLong(flowResumeCount); + buffer.putLongLong(flowStopSize); + buffer.putLongLong(flowResumeSize); + buffer.putLong(count); + buffer.putLongLong(size); +} + + +void QueueFlowLimit::decode ( Buffer& buffer ) +{ + flowStopCount = buffer.getLong(); + flowResumeCount = buffer.getLong(); + flowStopSize = buffer.getLongLong(); + flowResumeSize = buffer.getLongLong(); + count = buffer.getLong(); + size = buffer.getLongLong(); +} + + +uint32_t QueueFlowLimit::encodedSize() const { + return sizeof(uint32_t) + // flowStopCount + sizeof(uint32_t) + // flowResumecount + sizeof(uint64_t) + // flowStopSize + sizeof(uint64_t) + // flowResumeSize + sizeof(uint32_t) + // count + sizeof(uint64_t); // size +} + + +const std::string QueueFlowLimit::flowStopCountKey("qpid.flow_stop_count"); +const std::string QueueFlowLimit::flowResumeCountKey("qpid.flow_resume_count"); +const std::string QueueFlowLimit::flowStopSizeKey("qpid.flow_stop_size"); +const std::string QueueFlowLimit::flowResumeSizeKey("qpid.flow_resume_size"); +uint64_t QueueFlowLimit::defaultMaxSize; +uint QueueFlowLimit::defaultFlowStopRatio; +uint QueueFlowLimit::defaultFlowResumeRatio; + + +void QueueFlowLimit::setDefaults(uint64_t maxQueueSize, uint flowStopRatio, uint flowResumeRatio) +{ + defaultMaxSize = maxQueueSize; + defaultFlowStopRatio = flowStopRatio; + defaultFlowResumeRatio = flowResumeRatio; + + /** @todo KAG: Verify valid range on Broker::Options instead of here */ + if (flowStopRatio > 100 || flowResumeRatio > 100) + throw InvalidArgumentException(QPID_MSG("Default queue flow ratios must be between 0 and 100, inclusive:" + << " flowStopRatio=" << flowStopRatio + << " flowResumeRatio=" << flowResumeRatio)); + if (flowResumeRatio > flowStopRatio) + throw InvalidArgumentException(QPID_MSG("Default queue flow stop ratio must be >= flow resume ratio:" + << " flowStopRatio=" << flowStopRatio + << " flowResumeRatio=" << flowResumeRatio)); +} + + +void QueueFlowLimit::observe(Queue& queue, const qpid::framing::FieldTable& settings) +{ + QueueFlowLimit *ptr = createLimit( &queue, settings ); + if (ptr) { + boost::shared_ptr<QueueFlowLimit> observer(ptr); + queue.addObserver(observer); + } +} + +/** returns ptr to a QueueFlowLimit, else 0 if no limit */ +QueueFlowLimit *QueueFlowLimit::createLimit(Queue *queue, const qpid::framing::FieldTable& settings) +{ + std::string type(QueuePolicy::getType(settings)); + + if (type == QueuePolicy::RING || type == QueuePolicy::RING_STRICT) { + // The size of a RING queue is limited by design - no need for flow control. + return 0; + } + + if (settings.get(flowStopCountKey) || settings.get(flowStopSizeKey) || + settings.get(flowResumeCountKey) || settings.get(flowResumeSizeKey)) { + // user provided (some) flow settings manually... + uint32_t flowStopCount = getCapacity(settings, flowStopCountKey, 0); + uint32_t flowResumeCount = getCapacity(settings, flowResumeCountKey, 0); + uint64_t flowStopSize = getCapacity(settings, flowStopSizeKey, 0); + uint64_t flowResumeSize = getCapacity(settings, flowResumeSizeKey, 0); + if (flowStopCount == 0 && flowStopSize == 0) { // disable flow control + return 0; + } + return new QueueFlowLimit(queue, flowStopCount, flowResumeCount, flowStopSize, flowResumeSize); + } + + if (defaultFlowStopRatio) { // broker has a default ratio setup... + uint64_t maxByteCount = getCapacity(settings, QueuePolicy::maxSizeKey, defaultMaxSize); + uint64_t flowStopSize = (uint64_t)(maxByteCount * (defaultFlowStopRatio/100.0) + 0.5); + uint64_t flowResumeSize = (uint64_t)(maxByteCount * (defaultFlowResumeRatio/100.0)); + uint32_t maxMsgCount = getCapacity(settings, QueuePolicy::maxCountKey, 0); // no size by default + uint32_t flowStopCount = (uint32_t)(maxMsgCount * (defaultFlowStopRatio/100.0) + 0.5); + uint32_t flowResumeCount = (uint32_t)(maxMsgCount * (defaultFlowResumeRatio/100.0)); + + return new QueueFlowLimit(queue, flowStopCount, flowResumeCount, flowStopSize, flowResumeSize); + } + return 0; +} + +/* Cluster replication */ + +namespace { + /** pack a set of sequence number ranges into a framing::Array */ + void buildSeqRangeArray(qpid::framing::Array *seqs, + const qpid::framing::SequenceNumber& first, + const qpid::framing::SequenceNumber& last) + { + seqs->push_back(qpid::framing::Array::ValuePtr(new Unsigned32Value(first))); + seqs->push_back(qpid::framing::Array::ValuePtr(new Unsigned32Value(last))); + } +} + +/** Runs on UPDATER to snapshot current state */ +void QueueFlowLimit::getState(qpid::framing::FieldTable& state ) const +{ + sys::Mutex::ScopedLock l(indexLock); + state.clear(); + + framing::SequenceSet ss; + if (!index.empty()) { + /* replicate the set of messages pending flow control */ + for (std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> >::const_iterator itr = index.begin(); + itr != index.end(); ++itr) { + ss.add(itr->first); + } + framing::Array seqs(TYPE_CODE_UINT32); + typedef boost::function<void(framing::SequenceNumber, framing::SequenceNumber)> arrayBuilder; + ss.for_each((arrayBuilder)boost::bind(&buildSeqRangeArray, &seqs, _1, _2)); + state.setArray("pendingMsgSeqs", seqs); + } + QPID_LOG(debug, "Queue \"" << queueName << "\": flow limit replicating pending msgs, range=" << ss); +} + + +/** called on UPDATEE to set state from snapshot */ +void QueueFlowLimit::setState(const qpid::framing::FieldTable& state) +{ + sys::Mutex::ScopedLock l(indexLock); + index.clear(); + + framing::SequenceSet fcmsg; + framing::Array seqArray(TYPE_CODE_UINT32); + if (state.getArray("pendingMsgSeqs", seqArray)) { + assert((seqArray.count() & 0x01) == 0); // must be even since they are sequence ranges + framing::Array::const_iterator i = seqArray.begin(); + while (i != seqArray.end()) { + framing::SequenceNumber first((*i)->getIntegerValue<uint32_t, 4>()); + ++i; + framing::SequenceNumber last((*i)->getIntegerValue<uint32_t, 4>()); + ++i; + fcmsg.add(first, last); + for (SequenceNumber seq = first; seq <= last; ++seq) { + QueuedMessage msg; + queue->find(seq, msg); // fyi: may not be found if msg is acquired & unacked + bool unique; + unique = index.insert(std::pair<framing::SequenceNumber, boost::intrusive_ptr<Message> >(seq, msg.payload)).second; + // Like this to avoid tripping up unused variable warning when NDEBUG set + if (!unique) assert(unique); + } + } + } + + flowStopped = index.size() != 0; + if (queueMgmtObj) { + queueMgmtObj->set_flowStopped(isFlowControlActive()); + } + QPID_LOG(debug, "Queue \"" << queueName << "\": flow limit replicated the pending msgs, range=" << fcmsg) +} + + +namespace qpid { + namespace broker { + +std::ostream& operator<<(std::ostream& out, const QueueFlowLimit& f) +{ + out << "; flowStopCount=" << f.flowStopCount << ", flowResumeCount=" << f.flowResumeCount; + out << "; flowStopSize=" << f.flowStopSize << ", flowResumeSize=" << f.flowResumeSize; + return out; +} + + } +} + diff --git a/cpp/src/qpid/broker/QueueFlowLimit.h b/cpp/src/qpid/broker/QueueFlowLimit.h new file mode 100644 index 0000000000..ad8a2720ef --- /dev/null +++ b/cpp/src/qpid/broker/QueueFlowLimit.h @@ -0,0 +1,132 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#ifndef _QueueFlowLimit_ +#define _QueueFlowLimit_ + +#include <list> +#include <set> +#include <iostream> +#include <memory> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/broker/StatefulQueueObserver.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/Mutex.h" + +namespace qmf { +namespace org { +namespace apache { +namespace qpid { +namespace broker { + class Queue; +}}}}} +namespace _qmfBroker = qmf::org::apache::qpid::broker; + +namespace qpid { +namespace broker { + +class Broker; + +/** + * Producer flow control: when level is > flowStop*, flow control is ON. + * then level is < flowResume*, flow control is OFF. If == 0, flow control + * is not used. If both byte and msg count thresholds are set, then + * passing _either_ level may turn flow control ON, but _both_ must be + * below level before flow control will be turned OFF. + */ + class QueueFlowLimit : public StatefulQueueObserver +{ + static uint64_t defaultMaxSize; + static uint defaultFlowStopRatio; + static uint defaultFlowResumeRatio; + + Queue *queue; + std::string queueName; + + uint32_t flowStopCount; + uint32_t flowResumeCount; + uint64_t flowStopSize; + uint64_t flowResumeSize; + bool flowStopped; // true = producers held in flow control + + // current queue utilization + uint32_t count; + uint64_t size; + + public: + static QPID_BROKER_EXTERN const std::string flowStopCountKey; + static QPID_BROKER_EXTERN const std::string flowResumeCountKey; + static QPID_BROKER_EXTERN const std::string flowStopSizeKey; + static QPID_BROKER_EXTERN const std::string flowResumeSizeKey; + + QPID_BROKER_EXTERN virtual ~QueueFlowLimit(); + + /** the queue has added QueuedMessage. Returns true if flow state changes */ + QPID_BROKER_EXTERN void enqueued(const QueuedMessage&); + /** the queue has removed QueuedMessage. Returns true if flow state changes */ + QPID_BROKER_EXTERN void dequeued(const QueuedMessage&); + /** ignored */ + QPID_BROKER_EXTERN void acquired(const QueuedMessage&) {}; + QPID_BROKER_EXTERN void requeued(const QueuedMessage&) {}; + + /** for clustering: */ + QPID_BROKER_EXTERN void getState(qpid::framing::FieldTable&) const; + QPID_BROKER_EXTERN void setState(const qpid::framing::FieldTable&); + + uint32_t getFlowStopCount() const { return flowStopCount; } + uint32_t getFlowResumeCount() const { return flowResumeCount; } + uint64_t getFlowStopSize() const { return flowStopSize; } + uint64_t getFlowResumeSize() const { return flowResumeSize; } + + uint32_t getFlowCount() const { return count; } + uint64_t getFlowSize() const { return size; } + bool isFlowControlActive() const { return flowStopped; } + bool monitorFlowControl() const { return flowStopCount || flowStopSize; } + + void encode(framing::Buffer& buffer) const; + void decode(framing::Buffer& buffer); + uint32_t encodedSize() const; + + static QPID_BROKER_EXTERN void observe(Queue& queue, const qpid::framing::FieldTable& settings); + static QPID_BROKER_EXTERN void setDefaults(uint64_t defaultMaxSize, uint defaultFlowStopRatio, uint defaultFlowResumeRatio); + + friend QPID_BROKER_EXTERN std::ostream& operator<<(std::ostream&, const QueueFlowLimit&); + + protected: + // msgs waiting for flow to become available. + std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> > index; + mutable qpid::sys::Mutex indexLock; + + _qmfBroker::Queue *queueMgmtObj; + + const Broker *broker; + + QPID_BROKER_EXTERN QueueFlowLimit(Queue *queue, + uint32_t flowStopCount, uint32_t flowResumeCount, + uint64_t flowStopSize, uint64_t flowResumeSize); + static QPID_BROKER_EXTERN QueueFlowLimit *createLimit(Queue *queue, const qpid::framing::FieldTable& settings); +}; + +}} + + +#endif diff --git a/cpp/src/qpid/broker/QueueListeners.cpp b/cpp/src/qpid/broker/QueueListeners.cpp index 4d2c57d6b4..591f4443bb 100644 --- a/cpp/src/qpid/broker/QueueListeners.cpp +++ b/cpp/src/qpid/broker/QueueListeners.cpp @@ -26,19 +26,25 @@ namespace broker { void QueueListeners::addListener(Consumer::shared_ptr c) { - if (c->preAcquires()) { - add(consumers, c); - } else { - add(browsers, c); + if (!c->inListeners) { + if (c->acquires) { + add(consumers, c); + } else { + add(browsers, c); + } + c->inListeners = true; } } void QueueListeners::removeListener(Consumer::shared_ptr c) { - if (c->preAcquires()) { - remove(consumers, c); - } else { - remove(browsers, c); + if (c->inListeners) { + if (c->acquires) { + remove(consumers, c); + } else { + remove(browsers, c); + } + c->inListeners = false; } } @@ -46,18 +52,20 @@ void QueueListeners::populate(NotificationSet& set) { if (consumers.size()) { set.consumer = consumers.front(); - consumers.erase(consumers.begin()); + consumers.pop_front(); + set.consumer->inListeners = false; } else { - // Don't swap the vectors, hang on to the memory allocated. + // Don't swap the deques, hang on to the memory allocated. set.browsers = browsers; browsers.clear(); + for (Listeners::iterator i = set.browsers.begin(); i != set.browsers.end(); i++) + (*i)->inListeners = false; } } 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); + listeners.push_back(c); } void QueueListeners::remove(Listeners& listeners, Consumer::shared_ptr c) @@ -73,9 +81,7 @@ void QueueListeners::NotificationSet::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(); + return c->inListeners; } void QueueListeners::ListenerSet::notifyAll() diff --git a/cpp/src/qpid/broker/QueueListeners.h b/cpp/src/qpid/broker/QueueListeners.h index 59d1c84ca4..0659499253 100644 --- a/cpp/src/qpid/broker/QueueListeners.h +++ b/cpp/src/qpid/broker/QueueListeners.h @@ -22,7 +22,7 @@ * */ #include "qpid/broker/Consumer.h" -#include <vector> +#include <deque> namespace qpid { namespace broker { @@ -40,7 +40,7 @@ namespace broker { class QueueListeners { public: - typedef std::vector<Consumer::shared_ptr> Listeners; + typedef std::deque<Consumer::shared_ptr> Listeners; class NotificationSet { diff --git a/cpp/src/qpid/broker/QueueObserver.h b/cpp/src/qpid/broker/QueueObserver.h index a711213dee..b58becd2ae 100644 --- a/cpp/src/qpid/broker/QueueObserver.h +++ b/cpp/src/qpid/broker/QueueObserver.h @@ -24,18 +24,52 @@ namespace qpid { namespace broker { -class QueuedMessage; +struct QueuedMessage; +class Consumer; + /** - * Interface for notifying classes who want to act as 'observers' of a - * queue of particular events. + * Interface for notifying classes who want to act as 'observers' of a queue of particular + * events. + * + * The events that are monitored reflect the relationship between a particular message and + * the queue it has been delivered to. A message can be considered in one of three states + * with respect to the queue: + * + * 1) "Available" - available for transfer to consumers (i.e. for browse or acquire), + * + * 2) "Acquired" - owned by a particular consumer, no longer available to other consumers + * (by either browse or acquire), but still considered on the queue. + * + * 3) "Dequeued" - removed from the queue and no longer available to any consumer. + * + * The queue events that are observable are: + * + * "Enqueued" - the message is "Available" - on the queue for transfer to any consumer + * (e.g. browse or acquire) + * + * "Acquired" - - a consumer has claimed exclusive access to it. It is no longer available + * for other consumers to browse or acquire, but it is not yet considered dequeued as it + * may be requeued by the consumer. + * + * "Requeued" - a previously-acquired message is released by its owner: it is put back on + * the queue at its original position and returns to the "Available" state. + * + * "Dequeued" - a message is no longer queued. At this point, the queue no longer tracks + * the message, and the broker considers the consumer's transaction complete. */ class QueueObserver { public: virtual ~QueueObserver() {} + + // note: the Queue will hold the messageLock while calling these methods! virtual void enqueued(const QueuedMessage&) = 0; virtual void dequeued(const QueuedMessage&) = 0; - private: + virtual void acquired(const QueuedMessage&) = 0; + virtual void requeued(const QueuedMessage&) = 0; + virtual void consumerAdded( const Consumer& ) {}; + virtual void consumerRemoved( const Consumer& ) {}; + private: }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/QueuePolicy.cpp b/cpp/src/qpid/broker/QueuePolicy.cpp index 4168221ad0..0c245700af 100644 --- a/cpp/src/qpid/broker/QueuePolicy.cpp +++ b/cpp/src/qpid/broker/QueuePolicy.cpp @@ -117,30 +117,30 @@ void QueuePolicy::update(FieldTable& settings) settings.setString(typeKey, type); } -uint32_t QueuePolicy::getCapacity(const FieldTable& settings, const std::string& key, uint32_t defaultValue) +template <typename T> +T getCapacity(const FieldTable& settings, const std::string& key, T defaultValue) { FieldTable::ValuePtr v = settings.get(key); - int32_t result = 0; + T result = 0; if (!v) return defaultValue; if (v->getType() == 0x23) { QPID_LOG(debug, "Value for " << key << " specified as float: " << v->get<float>()); } else if (v->getType() == 0x33) { QPID_LOG(debug, "Value for " << key << " specified as double: " << v->get<double>()); - } else if (v->convertsTo<int>()) { - result = v->get<int>(); + } else if (v->convertsTo<T>()) { + result = v->get<T>(); QPID_LOG(debug, "Got integer value for " << key << ": " << result); if (result >= 0) return result; } else if (v->convertsTo<string>()) { string s(v->get<string>()); QPID_LOG(debug, "Got string value for " << key << ": " << s); std::istringstream convert(s); - if (convert >> result && result >= 0) return result; + if (convert >> result && result >= 0 && convert.eof()) return result; } - QPID_LOG(warning, "Cannot convert " << key << " to unsigned integer, using default (" << defaultValue << ")"); - return defaultValue; + throw IllegalArgumentException(QPID_MSG("Cannot convert " << key << " to unsigned integer: " << *v)); } std::string QueuePolicy::getType(const FieldTable& settings) @@ -247,7 +247,7 @@ bool RingQueuePolicy::checkLimit(boost::intrusive_ptr<Message> m) { // If the message is bigger than the queue size, give up - if (m->contentSize() > getMaxSize()) { + if (getMaxSize() && m->contentSize() > getMaxSize()) { QPID_LOG(debug, "Message too large for ring queue " << name << " [" << *this << "] " << ": message size = " << m->contentSize() << " bytes" @@ -269,8 +269,7 @@ bool RingQueuePolicy::checkLimit(boost::intrusive_ptr<Message> m) do { QueuedMessage oldest = queue.front(); - - if (oldest.queue->acquire(oldest) || !strict) { + if (oldest.queue->acquireMessageAt(oldest.position, oldest) || !strict) { queue.pop_front(); pendingDequeues.push_back(oldest); QPID_LOG(debug, "Ring policy triggered in " << name @@ -320,8 +319,8 @@ std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(const qpid::framing::F std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(const std::string& name, const qpid::framing::FieldTable& settings) { - uint32_t maxCount = getCapacity(settings, maxCountKey, 0); - uint32_t maxSize = getCapacity(settings, maxSizeKey, defaultMaxSize); + uint32_t maxCount = getCapacity<int32_t>(settings, maxCountKey, 0); + uint64_t maxSize = getCapacity<int64_t>(settings, maxSizeKey, defaultMaxSize); if (maxCount || maxSize) { return createQueuePolicy(name, maxCount, maxSize, getType(settings)); } else { diff --git a/cpp/src/qpid/broker/QueuePolicy.h b/cpp/src/qpid/broker/QueuePolicy.h index 3cdd63784d..ec7f846704 100644 --- a/cpp/src/qpid/broker/QueuePolicy.h +++ b/cpp/src/qpid/broker/QueuePolicy.h @@ -43,8 +43,7 @@ class QueuePolicy uint32_t count; uint64_t size; bool policyExceeded; - - static uint32_t getCapacity(const qpid::framing::FieldTable& settings, const std::string& key, uint32_t defaultValue); + protected: uint64_t getCurrentQueueSize() const { return size; } diff --git a/cpp/src/qpid/broker/QueueRegistry.cpp b/cpp/src/qpid/broker/QueueRegistry.cpp index ea2531dae7..135a3543d9 100644 --- a/cpp/src/qpid/broker/QueueRegistry.cpp +++ b/cpp/src/qpid/broker/QueueRegistry.cpp @@ -21,6 +21,7 @@ #include "qpid/broker/Queue.h" #include "qpid/broker/QueueRegistry.h" #include "qpid/broker/QueueEvents.h" +#include "qpid/broker/Exchange.h" #include "qpid/log/Statement.h" #include <sstream> #include <assert.h> @@ -36,7 +37,13 @@ QueueRegistry::~QueueRegistry(){} std::pair<Queue::shared_ptr, bool> QueueRegistry::declare(const string& declareName, bool durable, - bool autoDelete, const OwnershipToken* owner) + bool autoDelete, const OwnershipToken* owner, + boost::shared_ptr<Exchange> alternate, + const qpid::framing::FieldTable& arguments, + bool recovering/*true if this declare is a + result of recovering queue + definition from persistente + record*/) { RWlock::ScopedWlock locker(lock); string name = declareName.empty() ? generateName() : declareName; @@ -45,6 +52,17 @@ QueueRegistry::declare(const string& declareName, bool durable, if (i == queues.end()) { Queue::shared_ptr queue(new Queue(name, autoDelete, durable ? store : 0, owner, parent, broker)); + if (alternate) { + queue->setAlternateExchange(alternate);//need to do this *before* create + alternate->incAlternateUsers(); + } + if (!recovering) { + //apply settings & create persistent record if required + queue->create(arguments); + } else { + //i.e. recovering a queue for which we already have a persistent record + queue->configure(arguments); + } queues[name] = queue; if (lastNode) queue->setLastNodeFailure(); diff --git a/cpp/src/qpid/broker/QueueRegistry.h b/cpp/src/qpid/broker/QueueRegistry.h index 57859fe639..8a32a64f05 100644 --- a/cpp/src/qpid/broker/QueueRegistry.h +++ b/cpp/src/qpid/broker/QueueRegistry.h @@ -24,6 +24,7 @@ #include "qpid/broker/BrokerImportExport.h" #include "qpid/sys/Mutex.h" #include "qpid/management/Manageable.h" +#include "qpid/framing/FieldTable.h" #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <algorithm> @@ -34,6 +35,7 @@ namespace broker { class Queue; class QueueEvents; +class Exchange; class OwnershipToken; class Broker; class MessageStore; @@ -60,7 +62,10 @@ class QueueRegistry { const std::string& name, bool durable = false, bool autodelete = false, - const OwnershipToken* owner = 0); + const OwnershipToken* owner = 0, + boost::shared_ptr<Exchange> alternateExchange = boost::shared_ptr<Exchange>(), + const qpid::framing::FieldTable& args = framing::FieldTable(), + bool recovering = false); /** * Destroy the named queue. diff --git a/cpp/src/qpid/broker/RateTracker.cpp b/cpp/src/qpid/broker/RateTracker.cpp deleted file mode 100644 index 048349b658..0000000000 --- a/cpp/src/qpid/broker/RateTracker.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -#include "qpid/broker/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 deleted file mode 100644 index 0c20b37312..0000000000 --- a/cpp/src/qpid/broker/RateTracker.h +++ /dev/null @@ -1,57 +0,0 @@ -#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/RecoveredDequeue.cpp b/cpp/src/qpid/broker/RecoveredDequeue.cpp index 38cb8043c9..cd6735328f 100644 --- a/cpp/src/qpid/broker/RecoveredDequeue.cpp +++ b/cpp/src/qpid/broker/RecoveredDequeue.cpp @@ -43,7 +43,6 @@ void RecoveredDequeue::commit() throw() void RecoveredDequeue::rollback() throw() { - msg->enqueueComplete(); queue->process(msg); } diff --git a/cpp/src/qpid/broker/RecoveredEnqueue.cpp b/cpp/src/qpid/broker/RecoveredEnqueue.cpp index 6263c63e3d..6d2eaee6c4 100644 --- a/cpp/src/qpid/broker/RecoveredEnqueue.cpp +++ b/cpp/src/qpid/broker/RecoveredEnqueue.cpp @@ -36,7 +36,6 @@ bool RecoveredEnqueue::prepare(TransactionContext*) throw(){ } void RecoveredEnqueue::commit() throw(){ - msg->enqueueComplete(); queue->process(msg); } diff --git a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp index 2f04943581..d08409695e 100644 --- a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp +++ b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp @@ -113,7 +113,7 @@ RecoverableExchange::shared_ptr RecoveryManagerImpl::recoverExchange(framing::Bu RecoverableQueue::shared_ptr RecoveryManagerImpl::recoverQueue(framing::Buffer& buffer) { - Queue::shared_ptr queue = Queue::decode(queues, buffer, true); + Queue::shared_ptr queue = Queue::restore(queues, buffer); try { Exchange::shared_ptr exchange = exchanges.getDefault(); if (exchange) { @@ -252,7 +252,6 @@ void RecoverableMessageImpl::dequeue(DtxBuffer::shared_ptr buffer, Queue::shared void RecoverableMessageImpl::enqueue(DtxBuffer::shared_ptr buffer, Queue::shared_ptr queue) { - msg->enqueueComplete(); // recoved nmessage to enqueued in store already buffer->enlist(TxOp::shared_ptr(new RecoveredEnqueue(queue, msg))); } diff --git a/cpp/src/qpid/broker/SaslAuthenticator.cpp b/cpp/src/qpid/broker/SaslAuthenticator.cpp index acdb4934d4..d7adbd68ab 100644 --- a/cpp/src/qpid/broker/SaslAuthenticator.cpp +++ b/cpp/src/qpid/broker/SaslAuthenticator.cpp @@ -30,6 +30,7 @@ #include <boost/format.hpp> #if HAVE_SASL +#include <sys/stat.h> #include <sasl/sasl.h> #include "qpid/sys/cyrus/CyrusSecurityLayer.h" using qpid::sys::cyrus::CyrusSecurityLayer; @@ -57,7 +58,7 @@ public: NullAuthenticator(Connection& connection, bool encrypt); ~NullAuthenticator(); void getMechanisms(framing::Array& mechanisms); - void start(const std::string& mechanism, const std::string& response); + void start(const std::string& mechanism, const std::string* response); void step(const std::string&) {} std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); }; @@ -81,7 +82,7 @@ public: ~CyrusAuthenticator(); void init(); void getMechanisms(framing::Array& mechanisms); - void start(const std::string& mechanism, const std::string& response); + void start(const std::string& mechanism, const std::string* response); void step(const std::string& response); void getError(std::string& error); void getUid(std::string& uid) { getUsername(uid); } @@ -98,11 +99,33 @@ void SaslAuthenticator::init(const std::string& saslName, std::string const & sa // Check if we have a version of SASL that supports sasl_set_path() #if (SASL_VERSION_FULL >= ((2<<16)|(1<<8)|22)) // If we are not given a sasl path, do nothing and allow the default to be used. - if ( ! saslConfigPath.empty() ) { - int code = sasl_set_path(SASL_PATH_TYPE_CONFIG, - const_cast<char *>(saslConfigPath.c_str())); + if ( saslConfigPath.empty() ) { + QPID_LOG ( info, "SASL: no config path set - using default." ); + } + else { + struct stat st; + + // Make sure the directory exists and we can read up to it. + if ( ::stat ( saslConfigPath.c_str(), & st) ) { + // Note: not using strerror() here because I think its messages are a little too hazy. + if ( errno == ENOENT ) + throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: no such directory: " << saslConfigPath ) ); + if ( errno == EACCES ) + throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: cannot read parent of: " << saslConfigPath ) ); + // catch-all stat failure + throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: cannot stat: " << saslConfigPath ) ); + } + + // Make sure the directory is readable. + if ( ::access ( saslConfigPath.c_str(), R_OK ) ) { + throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: directory not readable:" << saslConfigPath ) ); + } + + // This shouldn't fail now, but check anyway. + int code = sasl_set_path(SASL_PATH_TYPE_CONFIG, const_cast<char *>(saslConfigPath.c_str())); if(SASL_OK != code) throw Exception(QPID_MSG("SASL: sasl_set_path failed [" << code << "] " )); + QPID_LOG(info, "SASL: config path set to " << saslConfigPath ); } #endif @@ -164,7 +187,7 @@ void NullAuthenticator::getMechanisms(Array& mechanisms) mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("PLAIN")));//useful for testing } -void NullAuthenticator::start(const string& mechanism, const string& response) +void NullAuthenticator::start(const string& mechanism, const string* response) { if (encrypt) { #if HAVE_SASL @@ -180,16 +203,16 @@ void NullAuthenticator::start(const string& mechanism, const string& response) } } if (mechanism == "PLAIN") { // Old behavior - if (response.size() > 0) { + if (response && response->size() > 0) { string uid; - string::size_type i = response.find((char)0); - if (i == 0 && response.size() > 1) { + 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); + 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); + 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 @@ -376,18 +399,22 @@ void CyrusAuthenticator::getMechanisms(Array& mechanisms) } } -void CyrusAuthenticator::start(const string& mechanism, const string& response) +void CyrusAuthenticator::start(const string& mechanism, const string* response) { const char *challenge; unsigned int challenge_len; - QPID_LOG(debug, "SASL: Starting authentication with mechanism: " << mechanism); + // This should be at same debug level as mech list in getMechanisms(). + QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); int code = sasl_server_start(sasl_conn, mechanism.c_str(), - response.c_str(), response.length(), + (response ? response->c_str() : 0), (response ? response->size() : 0), &challenge, &challenge_len); processAuthenticationStep(code, challenge, challenge_len); + qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); + if ( cnxMgmt ) + cnxMgmt->set_saslMechanism(mechanism); } void CyrusAuthenticator::step(const string& response) @@ -424,10 +451,12 @@ void CyrusAuthenticator::processAuthenticationStep(int code, const char *challen client.secure(challenge_str); } else { std::string uid; + //save error detail before trying to retrieve username as error in doing so will overwrite it + std::string errordetail = sasl_errdetail(sasl_conn); if (!getUsername(uid)) { - QPID_LOG(info, "SASL: Authentication failed (no username available):" << sasl_errdetail(sasl_conn)); + QPID_LOG(info, "SASL: Authentication failed (no username available yet):" << errordetail); } else { - QPID_LOG(info, "SASL: Authentication failed for " << uid << ":" << sasl_errdetail(sasl_conn)); + QPID_LOG(info, "SASL: Authentication failed for " << uid << ":" << errordetail); } // TODO: Change to more specific exceptions, when they are @@ -459,6 +488,9 @@ std::auto_ptr<SecurityLayer> CyrusAuthenticator::getSecurityLayer(uint16_t maxFr if (ssf) { securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize)); } + qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); + if ( cnxMgmt ) + cnxMgmt->set_saslSsf(ssf); return securityLayer; } diff --git a/cpp/src/qpid/broker/SaslAuthenticator.h b/cpp/src/qpid/broker/SaslAuthenticator.h index cfbe1a0cd1..4e5d43214c 100644 --- a/cpp/src/qpid/broker/SaslAuthenticator.h +++ b/cpp/src/qpid/broker/SaslAuthenticator.h @@ -41,7 +41,7 @@ class SaslAuthenticator public: virtual ~SaslAuthenticator() {} virtual void getMechanisms(framing::Array& mechanisms) = 0; - virtual void start(const std::string& mechanism, const std::string& response) = 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 bool getUsername(std::string&) { return false; }; diff --git a/cpp/src/qpid/broker/SemanticState.cpp b/cpp/src/qpid/broker/SemanticState.cpp index c91cfba2f8..fbcb21eab9 100644 --- a/cpp/src/qpid/broker/SemanticState.cpp +++ b/cpp/src/qpid/broker/SemanticState.cpp @@ -70,14 +70,12 @@ SemanticState::SemanticState(DeliveryAdapter& da, SessionContext& ss) deliveryAdapter(da), tagGenerator("sgen"), dtxSelected(false), - authMsg(getSession().getBroker().getOptions().auth && !getSession().getConnection().isFederationLink()), + authMsg(getSession().getBroker().getOptions().auth && !getSession().getConnection().isUserProxyAuth()), userID(getSession().getConnection().getUserId()), userName(getSession().getConnection().getUserId().substr(0,getSession().getConnection().getUserId().find('@'))), isDefaultRealm(userID.find('@') != std::string::npos && getSession().getBroker().getOptions().realm == userID.substr(userID.find('@')+1,userID.size())), closeComplete(false) -{ - acl = getSession().getBroker().getAcl(); -} +{} SemanticState::~SemanticState() { closed(); @@ -88,7 +86,7 @@ void SemanticState::closed() { //prevent requeued messages being redelivered to consumers for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { disable(i->second); - } + } if (dtxBuffer.get()) { dtxBuffer->fail(); } @@ -107,16 +105,24 @@ bool SemanticState::exists(const string& consumerTag){ return consumers.find(consumerTag) != consumers.end(); } -void SemanticState::consume(const string& tag, +namespace { + const std::string SEPARATOR("::"); +} + +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) { - ConsumerImpl::shared_ptr c(new ConsumerImpl(this, tag, queue, ackRequired, acquire, exclusive, resumeId, resumeTtl, arguments)); + // "tag" is only guaranteed to be unique to this session (see AMQP 0-10 Message.subscribe, destination). + // Create a globally unique name so the broker can identify individual consumers + std::string name = session.getSessionId().str() + SEPARATOR + tag; + ConsumerImpl::shared_ptr c(new ConsumerImpl(this, name, queue, ackRequired, acquire, exclusive, tag, resumeId, resumeTtl, arguments)); queue->consume(c, exclusive);//may throw exception consumers[tag] = c; } -void SemanticState::cancel(const string& tag){ +bool SemanticState::cancel(const string& tag) +{ ConsumerImplMap::iterator i = consumers.find(tag); if (i != consumers.end()) { cancel(i->second); @@ -124,7 +130,13 @@ void SemanticState::cancel(const string& tag){ //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)); - + //can also remove any records that are now redundant + DeliveryRecords::iterator removed = + remove_if(unacked.begin(), unacked.end(), bind(&DeliveryRecord::isRedundant, _1)); + unacked.erase(removed, unacked.end()); + return true; + } else { + return false; } } @@ -167,8 +179,8 @@ void SemanticState::startDtx(const std::string& xid, DtxManager& mgr, bool join) if (!dtxSelected) { throw CommandInvalidException(QPID_MSG("Session has not been selected for use with dtx")); } - dtxBuffer = DtxBuffer::shared_ptr(new DtxBuffer(xid)); - txBuffer = boost::static_pointer_cast<TxBuffer>(dtxBuffer); + dtxBuffer.reset(new DtxBuffer(xid)); + txBuffer = dtxBuffer; if (join) { mgr.join(xid, dtxBuffer); } else { @@ -194,7 +206,7 @@ void SemanticState::endDtx(const std::string& xid, bool fail) dtxBuffer->fail(); } else { dtxBuffer->markEnded(); - } + } dtxBuffer.reset(); } @@ -236,7 +248,7 @@ void SemanticState::resumeDtx(const std::string& xid) checkDtxTimeout(); dtxBuffer->setSuspended(false); - txBuffer = boost::static_pointer_cast<TxBuffer>(dtxBuffer); + txBuffer = dtxBuffer; } void SemanticState::checkDtxTimeout() @@ -254,31 +266,33 @@ void SemanticState::record(const DeliveryRecord& delivery) const std::string QPID_SYNC_FREQUENCY("qpid.sync_frequency"); -SemanticState::ConsumerImpl::ConsumerImpl(SemanticState* _parent, - const string& _name, - Queue::shared_ptr _queue, +SemanticState::ConsumerImpl::ConsumerImpl(SemanticState* _parent, + const string& _name, + Queue::shared_ptr _queue, bool ack, bool _acquire, bool _exclusive, + const string& _tag, const string& _resumeId, uint64_t _resumeTtl, const framing::FieldTable& _arguments -) : - Consumer(_acquire), - parent(_parent), - name(_name), - queue(_queue), - ackExpected(ack), +) : + Consumer(_name, _acquire), + parent(_parent), + queue(_queue), + ackExpected(ack), acquire(_acquire), - blocked(true), + blocked(true), windowing(true), + windowActive(false), exclusive(_exclusive), resumeId(_resumeId), + tag(_tag), resumeTtl(_resumeTtl), arguments(_arguments), - msgCredit(0), + msgCredit(0), byteCredit(0), notifyEnabled(true), syncFrequency(_arguments.getAsInt(QPID_SYNC_FREQUENCY)), @@ -289,10 +303,10 @@ SemanticState::ConsumerImpl::ConsumerImpl(SemanticState* _parent, { 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, + mgmtObject = new _qmf::Subscription(agent, this, ms , queue->GetManagementObject()->getObjectId(), getTag(), !acquire, ackExpected, exclusive, ManagementAgent::toMap(arguments)); agent->addObject (mgmtObject); mgmtObject->set_creditMode("WINDOW"); @@ -324,16 +338,16 @@ bool SemanticState::ConsumerImpl::deliver(QueuedMessage& msg) { assertClusterSafe(); allocateCredit(msg.payload); - DeliveryRecord record(msg, queue, name, acquire, !ackExpected, windowing); + DeliveryRecord record(msg, queue, getTag(), 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(record); - } - if (acquire && !ackExpected) { - queue->dequeue(0, msg); + } + if (acquire && !ackExpected) { // auto acquire && auto accept + queue->dequeue(0 /*ctxt*/, msg); + record.setEnded(); } if (mgmtObject) { mgmtObject->inc_delivered(); } return true; @@ -351,7 +365,7 @@ bool SemanticState::ConsumerImpl::accept(intrusive_ptr<Message> msg) // 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; } @@ -363,7 +377,7 @@ struct ConsumerName { }; ostream& operator<<(ostream& o, const ConsumerName& pc) { - return o << pc.consumer.getName() << " on " + return o << pc.consumer.getTag() << " on " << pc.consumer.getParent().getSession().getSessionId(); } } @@ -372,7 +386,7 @@ void SemanticState::ConsumerImpl::allocateCredit(intrusive_ptr<Message>& msg) { assertClusterSafe(); uint32_t originalMsgCredit = msgCredit; - uint32_t originalByteCredit = byteCredit; + uint32_t originalByteCredit = byteCredit; if (msgCredit != 0xFFFFFFFF) { msgCredit--; } @@ -382,7 +396,7 @@ void SemanticState::ConsumerImpl::allocateCredit(intrusive_ptr<Message>& msg) QPID_LOG(debug, "Credit allocated for " << ConsumerName(*this) << ", was " << " bytes: " << originalByteCredit << " msgs: " << originalMsgCredit << " now bytes: " << byteCredit << " msgs: " << msgCredit); - + } bool SemanticState::ConsumerImpl::checkCredit(intrusive_ptr<Message>& msg) @@ -396,7 +410,7 @@ bool SemanticState::ConsumerImpl::checkCredit(intrusive_ptr<Message>& msg) return enoughCredit; } -SemanticState::ConsumerImpl::~ConsumerImpl() +SemanticState::ConsumerImpl::~ConsumerImpl() { if (mgmtObject != 0) mgmtObject->resourceDestroy (); @@ -414,7 +428,7 @@ void SemanticState::unsubscribe(ConsumerImpl::shared_ptr c) Queue::shared_ptr queue = c->getQueue(); if(queue) { queue->cancel(c); - if (queue->canAutoDelete() && !queue->hasExclusiveOwner()) { + if (queue->canAutoDelete() && !queue->hasExclusiveOwner()) { Queue::tryAutoDelete(session.getBroker(), queue); } } @@ -456,23 +470,23 @@ const std::string nullstring; } void SemanticState::route(intrusive_ptr<Message> msg, Deliverable& strategy) { - msg->setTimestamp(getSession().getBroker().getExpiryPolicy()); - + msg->computeExpiration(getSession().getBroker().getExpiryPolicy()); + std::string exchangeName = msg->getExchangeName(); - if (!cacheExchange || cacheExchange->getName() != exchangeName) + if (!cacheExchange || cacheExchange->getName() != exchangeName || cacheExchange->isDestroyed()) 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 || (isDefaultRealm && id == userName))) { 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)); } + AclModule* acl = getSession().getBroker().getAcl(); if (acl && acl->doTransferAcl()) { if (!acl->authorise(getSession().getConnection().getUserId(),acl::ACT_PUBLISH,acl::OBJ_EXCHANGE,exchangeName, msg->getRoutingKey() )) @@ -484,7 +498,7 @@ void SemanticState::route(intrusive_ptr<Message> msg, Deliverable& strategy) { if (!strategy.delivered) { //TODO:if discard-unroutable, just drop it - //TODO:else if accept-mode is explicit, reject 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()); @@ -513,7 +527,7 @@ void SemanticState::ConsumerImpl::requestDispatch() } bool SemanticState::complete(DeliveryRecord& delivery) -{ +{ ConsumerImplMap::iterator i = consumers.find(delivery.getTag()); if (i != consumers.end()) { i->second->complete(delivery); @@ -525,7 +539,7 @@ void SemanticState::ConsumerImpl::complete(DeliveryRecord& delivery) { if (!delivery.isComplete()) { delivery.complete(); - if (windowing) { + if (windowing && windowActive) { if (msgCredit != 0xFFFFFFFF) msgCredit++; if (byteCredit != 0xFFFFFFFF) byteCredit += delivery.getCredit(); } @@ -541,7 +555,7 @@ void SemanticState::recover(bool requeue) unacked.clear(); for_each(copy.rbegin(), copy.rend(), mem_fun_ref(&DeliveryRecord::requeue)); }else{ - for_each(unacked.begin(), unacked.end(), boost::bind(&DeliveryRecord::redeliver, _1, this)); + for_each(unacked.begin(), unacked.end(), boost::bind(&DeliveryRecord::redeliver, _1, this)); //unconfirmed messages re redelivered and therefore have their //id adjusted, confirmed messages are not and so the ordering //w.r.t id is lost @@ -554,50 +568,61 @@ void SemanticState::deliver(DeliveryRecord& msg, bool sync) return deliveryAdapter.deliver(msg, sync); } -SemanticState::ConsumerImpl& SemanticState::find(const std::string& destination) +const SemanticState::ConsumerImpl::shared_ptr SemanticState::find(const std::string& destination) const { - ConsumerImplMap::iterator i = consumers.find(destination); - if (i == consumers.end()) { - throw NotFoundException(QPID_MSG("Unknown destination " << destination)); + ConsumerImpl::shared_ptr consumer; + if (!find(destination, consumer)) { + throw NotFoundException(QPID_MSG("Unknown destination " << destination << " session=" << session.getSessionId())); } else { - return *(i->second); + return consumer; + } +} + +bool SemanticState::find(const std::string& destination, ConsumerImpl::shared_ptr& consumer) const +{ + // @todo KAG gsim: shouldn't the consumers map be locked???? + ConsumerImplMap::const_iterator i = consumers.find(destination); + if (i == consumers.end()) { + return false; } + consumer = i->second; + return true; } void SemanticState::setWindowMode(const std::string& destination) { - find(destination).setWindowMode(); + find(destination)->setWindowMode(); } void SemanticState::setCreditMode(const std::string& destination) { - find(destination).setCreditMode(); + find(destination)->setCreditMode(); } void SemanticState::addByteCredit(const std::string& destination, uint32_t value) { - ConsumerImpl& c = find(destination); - c.addByteCredit(value); - c.requestDispatch(); + ConsumerImpl::shared_ptr c = find(destination); + c->addByteCredit(value); + c->requestDispatch(); } void SemanticState::addMessageCredit(const std::string& destination, uint32_t value) { - ConsumerImpl& c = find(destination); - c.addMessageCredit(value); - c.requestDispatch(); + ConsumerImpl::shared_ptr c = find(destination); + c->addMessageCredit(value); + c->requestDispatch(); } void SemanticState::flush(const std::string& destination) { - find(destination).flush(); + find(destination)->flush(); } void SemanticState::stop(const std::string& destination) { - find(destination).stop(); + find(destination)->stop(); } void SemanticState::ConsumerImpl::setWindowMode() @@ -621,6 +646,7 @@ void SemanticState::ConsumerImpl::setCreditMode() void SemanticState::ConsumerImpl::addByteCredit(uint32_t value) { assertClusterSafe(); + if (windowing) windowActive = true; if (byteCredit != 0xFFFFFFFF) { if (value == 0xFFFFFFFF) byteCredit = value; else byteCredit += value; @@ -630,6 +656,7 @@ void SemanticState::ConsumerImpl::addByteCredit(uint32_t value) void SemanticState::ConsumerImpl::addMessageCredit(uint32_t value) { assertClusterSafe(); + if (windowing) windowActive = true; if (msgCredit != 0xFFFFFFFF) { if (value == 0xFFFFFFFF) msgCredit = value; else msgCredit += value; @@ -650,7 +677,8 @@ void SemanticState::ConsumerImpl::flush() { while(haveCredit() && queue->dispatch(shared_from_this())) ; - stop(); + msgCredit = 0; + byteCredit = 0; } void SemanticState::ConsumerImpl::stop() @@ -658,6 +686,7 @@ void SemanticState::ConsumerImpl::stop() assertClusterSafe(); msgCredit = 0; byteCredit = 0; + windowActive = false; } Queue::shared_ptr SemanticState::getQueue(const string& name) const { @@ -673,7 +702,7 @@ Queue::shared_ptr SemanticState::getQueue(const string& name) const { } AckRange SemanticState::findRange(DeliveryId first, DeliveryId last) -{ +{ return DeliveryRecord::findRange(unacked, first, last); } @@ -691,14 +720,21 @@ void SemanticState::release(DeliveryId first, DeliveryId last, bool setRedeliver DeliveryRecords::reverse_iterator start(range.end); DeliveryRecords::reverse_iterator end(range.start); for_each(start, end, boost::bind(&DeliveryRecord::release, _1, setRedelivered)); + + DeliveryRecords::iterator removed = + remove_if(range.start, range.end, bind(&DeliveryRecord::isRedundant, _1)); + unacked.erase(removed, range.end); } void SemanticState::reject(DeliveryId first, DeliveryId last) { AckRange range = findRange(first, last); for_each(range.start, range.end, mem_fun_ref(&DeliveryRecord::reject)); - //need to remove the delivery records as well - unacked.erase(range.start, range.end); + //may need to remove the delivery records as well + for (DeliveryRecords::iterator i = range.start; i != unacked.end() && i->getId() <= last; ) { + if (i->isRedundant()) i = unacked.erase(i); + else i++; + } } bool SemanticState::ConsumerImpl::doOutput() @@ -761,13 +797,13 @@ void SemanticState::accepted(const SequenceSet& commands) { //in transactional mode, don't dequeue or remove, just //maintain set of acknowledged messages: accumulatedAck.add(commands); - + if (dtxBuffer.get()) { //if enlisted in a dtx, copy the relevant slice from //unacked and record it against that transaction TxOp::shared_ptr txAck(new DtxAck(accumulatedAck, unacked)); accumulatedAck.clear(); - dtxBuffer->enlist(txAck); + dtxBuffer->enlist(txAck); //mark the relevant messages as 'ended' in unacked //if the messages are already completed, they can be @@ -789,7 +825,6 @@ void SemanticState::accepted(const SequenceSet& commands) { } void SemanticState::completed(const SequenceSet& commands) { - assertClusterSafe(); DeliveryRecords::iterator removed = remove_if(unacked.begin(), unacked.end(), isInSequenceSetAnd(commands, @@ -800,7 +835,6 @@ void SemanticState::completed(const SequenceSet& commands) { void SemanticState::attached() { - assertClusterSafe(); for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { i->second->enableNotify(); session.getConnection().outputTasks.addOutputTask(i->second.get()); @@ -810,7 +844,6 @@ void SemanticState::attached() void SemanticState::detached() { - assertClusterSafe(); for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { i->second->disableNotify(); session.getConnection().outputTasks.removeOutputTask(i->second.get()); diff --git a/cpp/src/qpid/broker/SemanticState.h b/cpp/src/qpid/broker/SemanticState.h index b2e648410a..6d88dd56d9 100644 --- a/cpp/src/qpid/broker/SemanticState.h +++ b/cpp/src/qpid/broker/SemanticState.h @@ -65,7 +65,7 @@ class SessionContext; * * Message delivery is driven by ConsumerImpl::doOutput(), which is * called when a client's socket is ready to write data. - * + * */ class SemanticState : private boost::noncopyable { public: @@ -75,14 +75,15 @@ class SemanticState : private boost::noncopyable { { mutable qpid::sys::Mutex lock; SemanticState* const parent; - const std::string name; const boost::shared_ptr<Queue> queue; const bool ackExpected; const bool acquire; bool blocked; bool windowing; + bool windowActive; bool exclusive; std::string resumeId; + const std::string tag; // <destination> from AMQP 0-10 Message.subscribe command uint64_t resumeTtl; framing::FieldTable arguments; uint32_t msgCredit; @@ -99,15 +100,16 @@ class SemanticState : private boost::noncopyable { public: typedef boost::shared_ptr<ConsumerImpl> shared_ptr; - ConsumerImpl(SemanticState* parent, + ConsumerImpl(SemanticState* parent, const std::string& name, boost::shared_ptr<Queue> queue, bool ack, bool acquire, bool exclusive, - const std::string& resumeId, uint64_t resumeTtl, const framing::FieldTable& arguments); + const std::string& tag, 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); + bool deliver(QueuedMessage& msg); + bool filter(boost::intrusive_ptr<Message> msg); + bool accept(boost::intrusive_ptr<Message> msg); void disableNotify(); void enableNotify(); @@ -122,15 +124,13 @@ class SemanticState : private boost::noncopyable { void addMessageCredit(uint32_t value); void flush(); void stop(); - void complete(DeliveryRecord&); + void complete(DeliveryRecord&); boost::shared_ptr<Queue> getQueue() const { return queue; } bool isBlocked() const { return blocked; } bool setBlocked(bool set) { std::swap(set, blocked); return set; } bool doOutput(); - std::string getName() const { return name; } - bool isAckExpected() const { return ackExpected; } bool isAcquire() const { return acquire; } bool isWindowing() const { return windowing; } @@ -138,6 +138,7 @@ class SemanticState : private boost::noncopyable { uint32_t getMsgCredit() const { return msgCredit; } uint32_t getByteCredit() const { return byteCredit; } std::string getResumeId() const { return resumeId; }; + const std::string& getTag() const { return tag; } uint64_t getResumeTtl() const { return resumeTtl; } const framing::FieldTable& getArguments() const { return arguments; } @@ -148,9 +149,10 @@ class SemanticState : private boost::noncopyable { management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); }; + typedef std::map<std::string, DtxBuffer::shared_ptr> DtxBufferMap; + private: typedef std::map<std::string, ConsumerImpl::shared_ptr> ConsumerImplMap; - typedef std::map<std::string, DtxBuffer::shared_ptr> DtxBufferMap; SessionContext& session; DeliveryAdapter& deliveryAdapter; @@ -163,7 +165,6 @@ class SemanticState : private boost::noncopyable { DtxBufferMap suspendedXids; framing::SequenceSet accumulatedAck; boost::shared_ptr<Exchange> cacheExchange; - AclModule* acl; const bool authMsg; const std::string userID; const std::string userName; @@ -181,14 +182,16 @@ class SemanticState : private boost::noncopyable { void disable(ConsumerImpl::shared_ptr); public: + SemanticState(DeliveryAdapter&, SessionContext&); ~SemanticState(); SessionContext& getSession() { return session; } const SessionContext& getSession() const { return session; } - ConsumerImpl& find(const std::string& destination); - + const ConsumerImpl::shared_ptr find(const std::string& destination) const; + bool find(const std::string& destination, ConsumerImpl::shared_ptr&) const; + /** * Get named queue, never returns 0. * @return: named queue @@ -196,16 +199,16 @@ class SemanticState : private boost::noncopyable { * @exception: ConnectionException if name="" and session has no default. */ boost::shared_ptr<Queue> getQueue(const std::string& name) const; - + bool exists(const std::string& consumerTag); - void consume(const std::string& destination, - boost::shared_ptr<Queue> queue, + void consume(const std::string& destination, + boost::shared_ptr<Queue> queue, bool ackRequired, bool acquire, bool exclusive, const std::string& resumeId=std::string(), uint64_t resumeTtl=0, const framing::FieldTable& = framing::FieldTable()); - void cancel(const std::string& tag); + bool cancel(const std::string& tag); void setWindowMode(const std::string& destination); void setCreditMode(const std::string& destination); @@ -218,12 +221,13 @@ class SemanticState : private boost::noncopyable { void commit(MessageStore* const store); void rollback(); void selectDtx(); + bool getDtxSelected() const { return dtxSelected; } void startDtx(const std::string& xid, DtxManager& mgr, bool join); void endDtx(const std::string& xid, bool fail); void suspendDtx(const std::string& xid); void resumeDtx(const std::string& xid); void recover(bool requeue); - void deliver(DeliveryRecord& message, bool sync); + 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); @@ -244,9 +248,12 @@ class SemanticState : private boost::noncopyable { DeliveryRecords& getUnacked() { return unacked; } framing::SequenceSet getAccumulatedAck() const { return accumulatedAck; } TxBuffer::shared_ptr getTxBuffer() const { return txBuffer; } + DtxBuffer::shared_ptr getDtxBuffer() const { return dtxBuffer; } void setTxBuffer(const TxBuffer::shared_ptr& txb) { txBuffer = txb; } + void setDtxBuffer(const DtxBuffer::shared_ptr& dtxb) { dtxBuffer = dtxb; txBuffer = dtxb; } void setAccumulatedAck(const framing::SequenceSet& s) { accumulatedAck = s; } void record(const DeliveryRecord& delivery); + DtxBufferMap& getSuspendedXids() { return suspendedXids; } }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionAdapter.cpp b/cpp/src/qpid/broker/SessionAdapter.cpp index 3d62e73185..63c4b660b2 100644 --- a/cpp/src/qpid/broker/SessionAdapter.cpp +++ b/cpp/src/qpid/broker/SessionAdapter.cpp @@ -24,6 +24,7 @@ #include "qpid/log/Statement.h" #include "qpid/framing/SequenceSet.h" #include "qpid/management/ManagementAgent.h" +#include "qpid/broker/SessionState.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" @@ -64,53 +65,56 @@ void SessionAdapter::ExchangeHandlerImpl::declare(const string& exchange, const const string& alternateExchange, bool passive, bool durable, bool /*autoDelete*/, const FieldTable& args){ - 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 UnauthorizedAccessException(QPID_MSG("ACL denied exchange declare request from " << getConnection().getUserId())); - } - //TODO: implement autoDelete Exchange::shared_ptr alternate; if (!alternateExchange.empty()) { alternate = getBroker().getExchanges().get(alternateExchange); } if(passive){ + AclModule* acl = getBroker().getAcl(); + if (acl) { + //TODO: why does a passive declare require create + //permission? The purpose of the passive flag is to state + //that the exchange should *not* created. For + //authorisation a passive declare is similar to + //exchange-query. + 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, _TRUE)); + params.insert(make_pair(acl::PROP_DURABLE, durable ? _TRUE : _FALSE)); + if (!acl->authorise(getConnection().getUserId(),acl::ACT_CREATE,acl::OBJ_EXCHANGE,exchange,¶ms) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange create request from " << getConnection().getUserId())); + } Exchange::shared_ptr actual(getBroker().getExchanges().get(exchange)); checkType(actual, type); checkAlternate(actual, alternate); - }else{ + }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 (alternate) { - response.first->setAlternate(alternate); - alternate->incAlternateUsers(); - } - if (durable) { - getBroker().getStore().create(*response.first, args); - } - } else { + std::pair<Exchange::shared_ptr, bool> response = + getBroker().createExchange(exchange, type, durable, alternateExchange, args, + getConnection().getUserId(), getConnection().getUrl()); + if (!response.second) { + //exchange already there, not created checkType(response.first, type); checkAlternate(response.first, alternate); + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventExchangeDeclare(getConnection().getUrl(), + getConnection().getUserId(), + exchange, + type, + alternateExchange, + durable, + false, + ManagementAgent::toMap(args), + "existing")); } - - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventExchangeDeclare(getConnection().getUrl(), getConnection().getUserId(), exchange, type, - alternateExchange, durable, false, ManagementAgent::toMap(args), - response.second ? "created" : "existing")); - }catch(UnknownExchangeTypeException& /*e*/){ - throw CommandInvalidException(QPID_MSG("Exchange type not implemented: " << type)); + throw NotFoundException(QPID_MSG("Exchange type not implemented: " << type)); } } } @@ -134,22 +138,8 @@ void SessionAdapter::ExchangeHandlerImpl::checkAlternate(Exchange::shared_ptr ex 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 UnauthorizedAccessException(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)); + //TODO: implement if-unused + getBroker().deleteExchange(name, getConnection().getUserId(), getConnection().getUrl()); } ExchangeQueryResult SessionAdapter::ExchangeHandlerImpl::query(const string& name) @@ -169,67 +159,19 @@ ExchangeQueryResult SessionAdapter::ExchangeHandlerImpl::query(const string& nam } void SessionAdapter::ExchangeHandlerImpl::bind(const string& queueName, - const string& exchangeName, const string& routingKey, - const FieldTable& arguments) + const string& exchangeName, const string& routingKey, + 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)); - - if (!acl->authorise(getConnection().getUserId(),acl::ACT_BIND,acl::OBJ_EXCHANGE,exchangeName,¶ms)) - throw UnauthorizedAccessException(QPID_MSG("ACL denied exchange bind request from " << getConnection().getUserId())); - } - - Queue::shared_ptr queue = getQueue(queueName); - Exchange::shared_ptr exchange = getBroker().getExchanges().get(exchangeName); - if(exchange){ - string exchangeRoutingKey = routingKey.empty() && queueName.empty() ? queue->getName() : routingKey; - if (exchange->bind(queue, exchangeRoutingKey, &arguments)) { - queue->bound(exchangeName, routingKey, arguments); - 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, ManagementAgent::toMap(arguments))); - } - }else{ - throw NotFoundException("Bind failed. No such exchange: " + exchangeName); - } + getBroker().bind(queueName, exchangeName, routingKey, arguments, + getConnection().getUserId(), getConnection().getUrl()); } void SessionAdapter::ExchangeHandlerImpl::unbind(const string& queueName, const string& exchangeName, const string& routingKey) { - 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 UnauthorizedAccessException(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); - - Exchange::shared_ptr exchange = getBroker().getExchanges().get(exchangeName); - 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)) { - 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)); - } + getBroker().unbind(queueName, exchangeName, routingKey, + getConnection().getUserId(), getConnection().getUrl()); } ExchangeBoundResult SessionAdapter::ExchangeHandlerImpl::bound(const std::string& exchangeName, @@ -332,52 +274,42 @@ 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<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 UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << getConnection().getUserId())); - } - - Exchange::shared_ptr alternate; - if (!alternateExchange.empty()) { - alternate = getBroker().getExchanges().get(alternateExchange); - } +{ Queue::shared_ptr queue; if (passive && !name.empty()) { - queue = getQueue(name); + AclModule* acl = getBroker().getAcl(); + if (acl) { + //TODO: why does a passive declare require create + //permission? The purpose of the passive flag is to state + //that the queue should *not* created. For + //authorisation a passive declare is similar to + //queue-query (or indeed a qmf query). + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PROP_PASSIVE, _TRUE)); + 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 UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << getConnection().getUserId())); + } + 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 ? &session : 0); + std::pair<Queue::shared_ptr, bool> queue_created = + getBroker().createQueue(name, durable, + autoDelete, + exclusive ? &session : 0, + alternateExchange, + arguments, + getConnection().getUserId(), + getConnection().getUrl()); queue = queue_created.first; assert(queue); if (queue_created.second) { // This is a new queue - if (alternate) { - queue->setAlternateExchange(alternate); - alternate->incAlternateUsers(); - } - - //apply settings & create persistent record if required - try { queue_created.first->create(arguments); } - catch (...) { getBroker().getQueues().destroy(name); throw; } - - //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); @@ -386,21 +318,20 @@ void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& if (exclusive && queue->setExclusiveOwner(&session)) { exclusiveQueues.push_back(queue); } + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventQueueDeclare(getConnection().getUrl(), getConnection().getUserId(), + name, durable, exclusive, autoDelete, ManagementAgent::toMap(arguments), + "existing")); } - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventQueueDeclare(getConnection().getUrl(), getConnection().getUserId(), - name, durable, exclusive, autoDelete, ManagementAgent::toMap(arguments), - queue_created.second ? "created" : "existing")); } - if (exclusive && !queue->isExclusiveOwner(&session)) + 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) @@ -409,40 +340,32 @@ void SessionAdapter::QueueHandlerImpl::purge(const string& queue){ throw UnauthorizedAccessException(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::ACT_DELETE,acl::OBJ_QUEUE,queue,NULL) ) - throw UnauthorizedAccessException(QPID_MSG("ACL denied queue delete request from " << getConnection().getUserId())); - } +} - Queue::shared_ptr q = getQueue(queue); - if (q->hasExclusiveOwner() && !q->isExclusiveOwner(&session)) +void SessionAdapter::QueueHandlerImpl::checkDelete(Queue::shared_ptr queue, bool ifUnused, bool ifEmpty) +{ + if (queue->hasExclusiveOwner() && !queue->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){ - throw PreconditionFailedException("Queue in use."); - }else{ + << queue->getName() << "; it is exclusive to another session")); + } else if(ifEmpty && queue->getMessageCount() > 0) { + throw PreconditionFailedException(QPID_MSG("Cannot delete queue " + << queue->getName() << "; queue not empty")); + } else if(ifUnused && queue->getConsumerCount() > 0) { + throw PreconditionFailedException(QPID_MSG("Cannot delete queue " + << queue->getName() << "; queue in use")); + } else if (queue->isExclusiveOwner(&session)) { //remove the queue from the list of exclusive queues if necessary - if(q->isExclusiveOwner(&getConnection())){ - QueueVector::iterator i = std::find(getConnection().exclusiveQueues.begin(), getConnection().exclusiveQueues.end(), q); - if(i < getConnection().exclusiveQueues.end()) getConnection().exclusiveQueues.erase(i); - } - 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)); - q->notifyDeleted(); - } + QueueVector::iterator i = std::find(exclusiveQueues.begin(), + exclusiveQueues.end(), + queue); + if (i < exclusiveQueues.end()) exclusiveQueues.erase(i); + } +} + +void SessionAdapter::QueueHandlerImpl::delete_(const string& queue, bool ifUnused, bool ifEmpty) +{ + getBroker().deleteQueue(queue, getConnection().getUserId(), getConnection().getUrl(), + boost::bind(&SessionAdapter::QueueHandlerImpl::checkDelete, this, _1, ifUnused, ifEmpty)); } SessionAdapter::MessageHandlerImpl::MessageHandlerImpl(SemanticState& s) : @@ -508,7 +431,9 @@ SessionAdapter::MessageHandlerImpl::subscribe(const string& queueName, void SessionAdapter::MessageHandlerImpl::cancel(const string& destination ) { - state.cancel(destination); + if (!state.cancel(destination)) { + throw NotFoundException(QPID_MSG("No such subscription: " << destination)); + } ManagementAgent* agent = getBroker().getManagementAgent(); if (agent) @@ -587,7 +512,12 @@ framing::MessageResumeResult SessionAdapter::MessageHandlerImpl::resume(const st -void SessionAdapter::ExecutionHandlerImpl::sync() {} //essentially a no-op +void SessionAdapter::ExecutionHandlerImpl::sync() +{ + session.addPendingExecutionSync(); + /** @todo KAG - need a generic mechanism to allow a command to returning "not completed" status back to SessionState */ + +} void SessionAdapter::ExecutionHandlerImpl::result(const SequenceNumber& /*commandId*/, const string& /*value*/) { diff --git a/cpp/src/qpid/broker/SessionAdapter.h b/cpp/src/qpid/broker/SessionAdapter.h index ca27fb6e1d..8987c4812f 100644 --- a/cpp/src/qpid/broker/SessionAdapter.h +++ b/cpp/src/qpid/broker/SessionAdapter.h @@ -138,6 +138,7 @@ class Queue; bool isLocal(const ConnectionToken* t) const; void destroyExclusiveQueues(); + void checkDelete(boost::shared_ptr<Queue> queue, bool ifUnused, bool ifEmpty); template <class F> void eachExclusiveQueue(F f) { std::for_each(exclusiveQueues.begin(), exclusiveQueues.end(), f); diff --git a/cpp/src/qpid/broker/SessionContext.h b/cpp/src/qpid/broker/SessionContext.h index afbbb2cc22..253ce8dcf2 100644 --- a/cpp/src/qpid/broker/SessionContext.h +++ b/cpp/src/qpid/broker/SessionContext.h @@ -46,6 +46,7 @@ class SessionContext : public OwnershipToken, public sys::OutputControl virtual Broker& getBroker() = 0; virtual uint16_t getChannel() const = 0; virtual const SessionId& getSessionId() const = 0; + virtual void addPendingExecutionSync() = 0; }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionHandler.cpp b/cpp/src/qpid/broker/SessionHandler.cpp index 69b364ad7b..752fa55535 100644 --- a/cpp/src/qpid/broker/SessionHandler.cpp +++ b/cpp/src/qpid/broker/SessionHandler.cpp @@ -40,11 +40,6 @@ SessionHandler::SessionHandler(Connection& c, ChannelId ch) SessionHandler::~SessionHandler() {} -namespace { -ClassId classId(AMQMethodBody* m) { return m ? m->amqpMethodId() : 0; } -MethodId methodId(AMQMethodBody* m) { return m ? m->amqpClassId() : 0; } -} // namespace - 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); diff --git a/cpp/src/qpid/broker/SessionState.cpp b/cpp/src/qpid/broker/SessionState.cpp index 1ca7b6dfc1..1ab17e9893 100644 --- a/cpp/src/qpid/broker/SessionState.cpp +++ b/cpp/src/qpid/broker/SessionState.cpp @@ -25,6 +25,7 @@ #include "qpid/broker/SessionManager.h" #include "qpid/broker/SessionHandler.h" #include "qpid/broker/RateFlowcontrol.h" +#include "qpid/sys/ClusterSafe.h" #include "qpid/sys/Timer.h" #include "qpid/framing/AMQContentBody.h" #include "qpid/framing/AMQHeaderBody.h" @@ -60,9 +61,9 @@ SessionState::SessionState( semanticState(*this, *this), adapter(semanticState), msgBuilder(&broker.getStore()), - enqueuedOp(boost::bind(&SessionState::enqueued, this, _1)), mgmtObject(0), - rateFlowcontrol(0) + rateFlowcontrol(0), + asyncCommandCompleter(new AsyncCommandCompleter(this)) { uint32_t maxRate = broker.getOptions().maxSessionRate; if (maxRate) { @@ -95,6 +96,7 @@ void SessionState::addManagementObject() { } SessionState::~SessionState() { + asyncCommandCompleter->cancel(); semanticState.closed(); if (mgmtObject != 0) mgmtObject->resourceDestroy (); @@ -125,6 +127,7 @@ bool SessionState::isLocal(const ConnectionToken* t) const void SessionState::detach() { QPID_LOG(debug, getId() << ": detached on broker."); + asyncCommandCompleter->detached(); disableOutput(); handler = 0; if (mgmtObject != 0) @@ -145,6 +148,7 @@ void SessionState::attach(SessionHandler& h) { mgmtObject->set_connectionRef (h.getConnection().GetManagementObject()->getObjectId()); mgmtObject->set_channelId (h.getChannel()); } + asyncCommandCompleter->attached(); } void SessionState::abort() { @@ -202,15 +206,17 @@ Manageable::status_t SessionState::ManagementMethod (uint32_t methodId, } void SessionState::handleCommand(framing::AMQMethodBody* method, const SequenceNumber& id) { + currentCommandComplete = true; // assumed, can be overridden by invoker method (this sucks). Invoker::Result invocation = invoke(adapter, *method); - receiverCompleted(id); + if (currentCommandComplete) 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()) { - incomplete.process(enqueuedOp, true); + + if (method->isSync() && currentCommandComplete) { sendAcceptAndCompletion(); } } @@ -253,23 +259,14 @@ void SessionState::handleContent(AMQFrame& frame, const SequenceNumber& id) header.setEof(false); msg->getFrames().append(header); } + if (broker.isTimestamping()) + msg->setTimestamp(); msg->setPublisher(&getConnection()); + msg->getIngressCompletion().begin(); semanticState.handle(msg); msgBuilder.end(); - - if (msg->isEnqueueComplete()) { - enqueued(msg); - } else { - incomplete.add(msg); - } - - //hold up execution until async enqueue is complete - if (msg->getFrames().getMethod()->isSync()) { - incomplete.process(enqueuedOp, true); - sendAcceptAndCompletion(); - } else { - incomplete.process(enqueuedOp, false); - } + IncompleteIngressMsgXfer xfer(this, msg); + msg->getIngressCompletion().end(xfer); // allows msg to complete xfer } // Handle producer session flow control @@ -319,11 +316,41 @@ void SessionState::sendAcceptAndCompletion() sendCompletion(); } -void SessionState::enqueued(boost::intrusive_ptr<Message> msg) +/** Invoked when the given inbound message is finished being processed + * by all interested parties (eg. it is done being enqueued to all queues, + * its credit has been accounted for, etc). At this point, msg is considered + * by this receiver as 'completed' (as defined by AMQP 0_10) + */ +void SessionState::completeRcvMsg(SequenceNumber id, + bool requiresAccept, + bool requiresSync) { - receiverCompleted(msg->getCommandId()); - if (msg->requiresAccept()) - accepted.add(msg->getCommandId()); + // Mark this as a cluster-unsafe scope since it can be called in + // journal threads or connection threads as part of asynchronous + // command completion. + sys::ClusterUnsafeScope cus; + + bool callSendCompletion = false; + receiverCompleted(id); + if (requiresAccept) + // will cause msg's seq to appear in the next message.accept we send. + accepted.add(id); + + // Are there any outstanding Execution.Sync commands pending the + // completion of this msg? If so, complete them. + while (!pendingExecutionSyncs.empty() && + receiverGetIncomplete().front() >= pendingExecutionSyncs.front()) { + const SequenceNumber id = pendingExecutionSyncs.front(); + pendingExecutionSyncs.pop(); + QPID_LOG(debug, getId() << ": delayed execution.sync " << id << " is completed."); + receiverCompleted(id); + callSendCompletion = true; // likely peer is pending for this completion. + } + + // if the sender has requested immediate notification of the completion... + if (requiresSync || callSendCompletion) { + sendAcceptAndCompletion(); + } } void SessionState::handleIn(AMQFrame& frame) { @@ -396,4 +423,176 @@ framing::AMQP_ClientProxy& SessionState::getClusterOrderProxy() { return handler->getClusterOrderProxy(); } + +// Current received command is an execution.sync command. +// Complete this command only when all preceding commands have completed. +// (called via the invoker() in handleCommand() above) +void SessionState::addPendingExecutionSync() +{ + SequenceNumber syncCommandId = receiverGetCurrent(); + if (receiverGetIncomplete().front() < syncCommandId) { + currentCommandComplete = false; + pendingExecutionSyncs.push(syncCommandId); + asyncCommandCompleter->flushPendingMessages(); + QPID_LOG(debug, getId() << ": delaying completion of execution.sync " << syncCommandId); + } +} + + +/** factory for creating a reference-counted IncompleteIngressMsgXfer object + * which will be attached to a message that will be completed asynchronously. + */ +boost::intrusive_ptr<AsyncCompletion::Callback> +SessionState::IncompleteIngressMsgXfer::clone() +{ + // Optimization: this routine is *only* invoked when the message needs to be asynchronously completed. + // If the client is pending the message.transfer completion, flush now to force immediate write to journal. + if (requiresSync) + msg->flush(); + else { + // otherwise, we need to track this message in order to flush it if an execution.sync arrives + // before it has been completed (see flushPendingMessages()) + pending = true; + completerContext->addPendingMessage(msg); + } + + return boost::intrusive_ptr<SessionState::IncompleteIngressMsgXfer>(new SessionState::IncompleteIngressMsgXfer(*this)); +} + + +/** Invoked by the asynchronous completer associated with a received + * msg that is pending Completion. May be invoked by the IO thread + * (sync == true), or some external thread (!sync). + */ +void SessionState::IncompleteIngressMsgXfer::completed(bool sync) +{ + if (pending) completerContext->deletePendingMessage(id); + if (!sync) { + /** note well: this path may execute in any thread. It is safe to access + * the scheduledCompleterContext, since *this has a shared pointer to it. + * but not session! + */ + session = 0; + QPID_LOG(debug, ": async completion callback scheduled for msg seq=" << id); + completerContext->scheduleMsgCompletion(id, requiresAccept, requiresSync); + } else { + // this path runs directly from the ac->end() call in handleContent() above, + // so *session is definately valid. + if (session->isAttached()) { + QPID_LOG(debug, ": receive completed for msg seq=" << id); + session->completeRcvMsg(id, requiresAccept, requiresSync); + } + } + completerContext = boost::intrusive_ptr<AsyncCommandCompleter>(); +} + + +/** Scheduled from an asynchronous command's completed callback to run on + * the IO thread. + */ +void SessionState::AsyncCommandCompleter::schedule(boost::intrusive_ptr<AsyncCommandCompleter> ctxt) +{ + ctxt->completeCommands(); +} + + +/** Track an ingress message that is pending completion */ +void SessionState::AsyncCommandCompleter::addPendingMessage(boost::intrusive_ptr<Message> msg) +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + std::pair<SequenceNumber, boost::intrusive_ptr<Message> > item(msg->getCommandId(), msg); + bool unique = pendingMsgs.insert(item).second; + if (!unique) { + assert(false); + } +} + + +/** pending message has completed */ +void SessionState::AsyncCommandCompleter::deletePendingMessage(SequenceNumber id) +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + pendingMsgs.erase(id); +} + + +/** done when an execution.sync arrives */ +void SessionState::AsyncCommandCompleter::flushPendingMessages() +{ + std::map<SequenceNumber, boost::intrusive_ptr<Message> > copy; + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + pendingMsgs.swap(copy); // we've only tracked these in case a flush is needed, so nuke 'em now. + } + // drop lock, so it is safe to call "flush()" + for (std::map<SequenceNumber, boost::intrusive_ptr<Message> >::iterator i = copy.begin(); + i != copy.end(); ++i) { + i->second->flush(); + } +} + + +/** mark an ingress Message.Transfer command as completed. + * This method must be thread safe - it may run on any thread. + */ +void SessionState::AsyncCommandCompleter::scheduleMsgCompletion(SequenceNumber cmd, + bool requiresAccept, + bool requiresSync) +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + + if (session && isAttached) { + MessageInfo msg(cmd, requiresAccept, requiresSync); + completedMsgs.push_back(msg); + if (completedMsgs.size() == 1) { + session->getConnection().requestIOProcessing(boost::bind(&schedule, + session->asyncCommandCompleter)); + } + } +} + + +/** Cause the session to complete all completed commands. + * Executes on the IO thread. + */ +void SessionState::AsyncCommandCompleter::completeCommands() +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + + // when session is destroyed, it clears the session pointer via cancel(). + if (session && session->isAttached()) { + for (std::vector<MessageInfo>::iterator msg = completedMsgs.begin(); + msg != completedMsgs.end(); ++msg) { + session->completeRcvMsg(msg->cmd, msg->requiresAccept, msg->requiresSync); + } + } + completedMsgs.clear(); +} + + +/** cancel any pending calls to scheduleComplete */ +void SessionState::AsyncCommandCompleter::cancel() +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + session = 0; +} + + +/** inform the completer that the session has attached, + * allows command completion scheduling from any thread */ +void SessionState::AsyncCommandCompleter::attached() +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + isAttached = true; +} + + +/** inform the completer that the session has detached, + * disables command completion scheduling from any thread */ +void SessionState::AsyncCommandCompleter::detached() +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + isAttached = false; +} + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionState.h b/cpp/src/qpid/broker/SessionState.h index be79eb0eab..506af85c47 100644 --- a/cpp/src/qpid/broker/SessionState.h +++ b/cpp/src/qpid/broker/SessionState.h @@ -30,13 +30,15 @@ #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/AsyncCompletion.h" #include "qpid/broker/MessageBuilder.h" #include "qpid/broker/SessionContext.h" #include "qpid/broker/SemanticState.h" +#include "qpid/sys/Monitor.h" #include <boost/noncopyable.hpp> #include <boost/scoped_ptr.hpp> +#include <boost/intrusive_ptr.hpp> #include <set> #include <vector> @@ -123,6 +125,10 @@ class SessionState : public qpid::SessionState, const SessionId& getSessionId() const { return getId(); } + // Used by ExecutionHandler sync command processing. Notifies + // the SessionState of a received Execution.Sync command. + void addPendingExecutionSync(); + // Used to delay creation of management object for sessions // belonging to inter-broker bridges void addManagementObject(); @@ -130,7 +136,10 @@ class SessionState : public qpid::SessionState, private: void handleCommand(framing::AMQMethodBody* method, const framing::SequenceNumber& id); void handleContent(framing::AMQFrame& frame, const framing::SequenceNumber& id); - void enqueued(boost::intrusive_ptr<Message> msg); + + // indicate that the given ingress msg has been completely received by the + // broker, and the msg's message.transfer command can be considered completed. + void completeRcvMsg(SequenceNumber id, bool requiresAccept, bool requiresSync); void handleIn(framing::AMQFrame& frame); void handleOut(framing::AMQFrame& frame); @@ -156,8 +165,6 @@ class SessionState : public qpid::SessionState, SemanticState semanticState; SessionAdapter adapter; MessageBuilder msgBuilder; - IncompleteMessageList incomplete; - IncompleteMessageList::CompletionListener enqueuedOp; qmf::org::apache::qpid::broker::Session* mgmtObject; qpid::framing::SequenceSet accepted; @@ -166,6 +173,110 @@ class SessionState : public qpid::SessionState, boost::scoped_ptr<RateFlowcontrol> rateFlowcontrol; boost::intrusive_ptr<sys::TimerTask> flowControlTimer; + // sequence numbers for pending received Execution.Sync commands + std::queue<SequenceNumber> pendingExecutionSyncs; + bool currentCommandComplete; + + /** This class provides a context for completing asynchronous commands in a thread + * safe manner. Asynchronous commands save their completion state in this class. + * This class then schedules the completeCommands() method in the IO thread. + * While running in the IO thread, completeCommands() may safely complete all + * saved commands without the risk of colliding with other operations on this + * SessionState. + */ + class AsyncCommandCompleter : public RefCounted { + private: + SessionState *session; + bool isAttached; + qpid::sys::Mutex completerLock; + + // special-case message.transfer commands for optimization + struct MessageInfo { + SequenceNumber cmd; // message.transfer command id + bool requiresAccept; + bool requiresSync; + MessageInfo(SequenceNumber c, bool a, bool s) + : cmd(c), requiresAccept(a), requiresSync(s) {} + }; + std::vector<MessageInfo> completedMsgs; + // If an ingress message does not require a Sync, we need to + // hold a reference to it in case an Execution.Sync command is received and we + // have to manually flush the message. + std::map<SequenceNumber, boost::intrusive_ptr<Message> > pendingMsgs; + + /** complete all pending commands, runs in IO thread */ + void completeCommands(); + + /** for scheduling a run of "completeCommands()" on the IO thread */ + static void schedule(boost::intrusive_ptr<AsyncCommandCompleter>); + + public: + AsyncCommandCompleter(SessionState *s) : session(s), isAttached(s->isAttached()) {}; + ~AsyncCommandCompleter() {}; + + /** track a message pending ingress completion */ + void addPendingMessage(boost::intrusive_ptr<Message> m); + void deletePendingMessage(SequenceNumber id); + void flushPendingMessages(); + /** schedule the processing of a completed ingress message.transfer command */ + void scheduleMsgCompletion(SequenceNumber cmd, + bool requiresAccept, + bool requiresSync); + void cancel(); // called by SessionState destructor. + void attached(); // called by SessionState on attach() + void detached(); // called by SessionState on detach() + }; + boost::intrusive_ptr<AsyncCommandCompleter> asyncCommandCompleter; + + /** Abstract class that represents a single asynchronous command that is + * pending completion. + */ + class AsyncCommandContext : public AsyncCompletion::Callback + { + public: + AsyncCommandContext( SessionState *ss, SequenceNumber _id ) + : id(_id), completerContext(ss->asyncCommandCompleter) {} + virtual ~AsyncCommandContext() {} + + protected: + SequenceNumber id; + boost::intrusive_ptr<AsyncCommandCompleter> completerContext; + }; + + /** incomplete Message.transfer commands - inbound to broker from client + */ + class IncompleteIngressMsgXfer : public SessionState::AsyncCommandContext + { + public: + IncompleteIngressMsgXfer( SessionState *ss, + boost::intrusive_ptr<Message> m ) + : AsyncCommandContext(ss, m->getCommandId()), + session(ss), + msg(m), + requiresAccept(m->requiresAccept()), + requiresSync(m->getFrames().getMethod()->isSync()), + pending(false) {} + IncompleteIngressMsgXfer( const IncompleteIngressMsgXfer& x ) + : AsyncCommandContext(x.session, x.msg->getCommandId()), + session(x.session), + msg(x.msg), + requiresAccept(x.requiresAccept), + requiresSync(x.requiresSync), + pending(x.pending) {} + + virtual ~IncompleteIngressMsgXfer() {}; + + virtual void completed(bool); + virtual boost::intrusive_ptr<AsyncCompletion::Callback> clone(); + + private: + SessionState *session; // only valid if sync flag in callback is true + boost::intrusive_ptr<Message> msg; + bool requiresAccept; + bool requiresSync; + bool pending; // true if msg saved on pending list... + }; + friend class SessionManager; }; diff --git a/cpp/src/qpid/broker/StatefulQueueObserver.h b/cpp/src/qpid/broker/StatefulQueueObserver.h new file mode 100644 index 0000000000..c682d460b7 --- /dev/null +++ b/cpp/src/qpid/broker/StatefulQueueObserver.h @@ -0,0 +1,63 @@ +#ifndef QPID_BROKER_STATEFULQUEUEOBSERVER_H +#define QPID_BROKER_STATEFULQUEUEOBSERVER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/QueueObserver.h" +#include "qpid/framing/FieldTable.h" + +namespace qpid { +namespace broker { + +/** + * Specialized type of QueueObserver that maintains internal state that has to + * be replicated across clustered brokers. + */ +class StatefulQueueObserver : public QueueObserver +{ + public: + StatefulQueueObserver(std::string _id) : id(_id) {} + virtual ~StatefulQueueObserver() {} + + /** This identifier must uniquely identify this particular observer amoung + * all observers on a queue. For cluster replication, this id will be used + * to identify the peer queue observer for synchronization across + * brokers. + */ + const std::string& getId() const { return id; } + + /** This method should return the observer's internal state as an opaque + * map. + */ + virtual void getState(qpid::framing::FieldTable& state ) const = 0; + + /** The input map represents the internal state of the peer observer that + * this observer should synchonize to. + */ + virtual void setState(const qpid::framing::FieldTable&) = 0; + + + private: + std::string id; +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_STATEFULQUEUEOBSERVER_H*/ diff --git a/cpp/src/qpid/broker/ThresholdAlerts.cpp b/cpp/src/qpid/broker/ThresholdAlerts.cpp index 4f35884af8..3c9e210d4d 100644 --- a/cpp/src/qpid/broker/ThresholdAlerts.cpp +++ b/cpp/src/qpid/broker/ThresholdAlerts.cpp @@ -28,6 +28,52 @@ namespace qpid { namespace broker { +namespace { +const qmf::org::apache::qpid::broker::EventQueueThresholdExceeded EVENT("dummy", 0, 0); +bool isQMFv2(const boost::intrusive_ptr<Message> message) +{ + const qpid::framing::MessageProperties* props = message->getProperties<qpid::framing::MessageProperties>(); + return props && props->getAppId() == "qmf2"; +} + +bool isThresholdEvent(const boost::intrusive_ptr<Message> message) +{ + if (message->getIsManagementMessage()) { + //is this a qmf event? if so is it a threshold event? + if (isQMFv2(message)) { + const qpid::framing::FieldTable* headers = message->getApplicationHeaders(); + if (headers && headers->getAsString("qmf.content") == "_event") { + //decode as list + std::string content = message->getFrames().getContent(); + qpid::types::Variant::List list; + qpid::amqp_0_10::ListCodec::decode(content, list); + if (list.empty() || list.front().getType() != qpid::types::VAR_MAP) return false; + qpid::types::Variant::Map map = list.front().asMap(); + try { + std::string eventName = map["_schema_id"].asMap()["_class_name"].asString(); + return eventName == EVENT.getEventName(); + } catch (const std::exception& e) { + QPID_LOG(error, "Error checking for recursive threshold alert: " << e.what()); + } + } + } else { + std::string content = message->getFrames().getContent(); + qpid::framing::Buffer buffer(const_cast<char*>(content.data()), content.size()); + if (buffer.getOctet() == 'A' && buffer.getOctet() == 'M' && buffer.getOctet() == '2' && buffer.getOctet() == 'e') { + buffer.getLong();//sequence + std::string packageName; + buffer.getShortString(packageName); + if (packageName != EVENT.getPackageName()) return false; + std::string eventName; + buffer.getShortString(eventName); + return eventName == EVENT.getEventName(); + } + } + } + return false; +} +} + ThresholdAlerts::ThresholdAlerts(const std::string& n, qpid::management::ManagementAgent& a, const uint32_t ct, @@ -44,8 +90,14 @@ void ThresholdAlerts::enqueued(const QueuedMessage& m) if ((countThreshold && count >= countThreshold) || (sizeThreshold && size >= sizeThreshold)) { if ((repeatInterval == 0 && lastAlert == qpid::sys::EPOCH) || qpid::sys::Duration(lastAlert, qpid::sys::now()) > repeatInterval) { - agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdExceeded(name, count, size)); + //Note: Raising an event may result in messages being + //enqueued on queues; it may even be that this event + //causes a message to be enqueued on the queue we are + //tracking, and so we need to avoid recursing + if (isThresholdEvent(m.payload)) return; lastAlert = qpid::sys::now(); + agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdExceeded(name, count, size)); + QPID_LOG(info, "Threshold event triggered for " << name << ", count=" << count << ", size=" << size); } } } @@ -75,12 +127,12 @@ void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& a } void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& agent, - const qpid::framing::FieldTable& settings) + const qpid::framing::FieldTable& settings, uint16_t limitRatio) { qpid::types::Variant::Map map; qpid::amqp_0_10::translate(settings, map); - observe(queue, agent, map); + observe(queue, agent, map, limitRatio); } template <class T> @@ -118,19 +170,19 @@ class Option }; void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& agent, - const qpid::types::Variant::Map& settings) + const qpid::types::Variant::Map& settings, uint16_t limitRatio) { //Note: aliases are keys defined by java broker Option<int64_t> repeatInterval("qpid.alert_repeat_gap", 60); repeatInterval.addAlias("x-qpid-minimum-alert-repeat-gap"); - //If no explicit threshold settings were given use 80% of any - //limit from the policy. + //If no explicit threshold settings were given use specified + //percentage of any limit from the policy. const QueuePolicy* policy = queue.getPolicy(); - Option<uint32_t> countThreshold("qpid.alert_count", (uint32_t) (policy ? policy->getMaxCount()*0.8 : 0)); + Option<uint32_t> countThreshold("qpid.alert_count", (uint32_t) (policy && limitRatio ? (policy->getMaxCount()*limitRatio/100) : 0)); countThreshold.addAlias("x-qpid-maximum-message-count"); - Option<uint64_t> sizeThreshold("qpid.alert_size", (uint64_t) (policy ? policy->getMaxSize()*0.8 : 0)); + Option<uint64_t> sizeThreshold("qpid.alert_size", (uint64_t) (policy && limitRatio ? (policy->getMaxSize()*limitRatio/100) : 0)); sizeThreshold.addAlias("x-qpid-maximum-message-size"); observe(queue, agent, countThreshold.get(settings), sizeThreshold.get(settings), repeatInterval.get(settings)); diff --git a/cpp/src/qpid/broker/ThresholdAlerts.h b/cpp/src/qpid/broker/ThresholdAlerts.h index e1f59252c4..2b4a46b736 100644 --- a/cpp/src/qpid/broker/ThresholdAlerts.h +++ b/cpp/src/qpid/broker/ThresholdAlerts.h @@ -50,14 +50,17 @@ class ThresholdAlerts : public QueueObserver const long repeatInterval); void enqueued(const QueuedMessage&); void dequeued(const QueuedMessage&); + void acquired(const QueuedMessage&) {}; + void requeued(const QueuedMessage&) {}; + static void observe(Queue& queue, qpid::management::ManagementAgent& agent, const uint64_t countThreshold, const uint64_t sizeThreshold, const long repeatInterval); static void observe(Queue& queue, qpid::management::ManagementAgent& agent, - const qpid::framing::FieldTable& settings); + const qpid::framing::FieldTable& settings, uint16_t limitRatio); static void observe(Queue& queue, qpid::management::ManagementAgent& agent, - const qpid::types::Variant::Map& settings); + const qpid::types::Variant::Map& settings, uint16_t limitRatio); private: const std::string name; qpid::management::ManagementAgent& agent; diff --git a/cpp/src/qpid/broker/TopicExchange.cpp b/cpp/src/qpid/broker/TopicExchange.cpp index 1b0fe71bcf..644a3d628e 100644 --- a/cpp/src/qpid/broker/TopicExchange.cpp +++ b/cpp/src/qpid/broker/TopicExchange.cpp @@ -221,6 +221,7 @@ TopicExchange::TopicExchange(const std::string& _name, bool _durable, bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args) { + ClearCache cc(&cacheLock,&bindingCache); // clear the cache on function exit. string fedOp(args ? args->getAsString(qpidFedOp) : fedOpBind); string fedTags(args ? args->getAsString(qpidFedTags) : ""); string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); @@ -249,21 +250,21 @@ bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, cons if (mgmtExchange != 0) { mgmtExchange->inc_bindingCount(); } - QPID_LOG(debug, "Bound key [" << routingPattern << "] to queue " << queue->getName() - << " (origin=" << fedOrigin << ")"); + QPID_LOG(debug, "Binding key [" << routingPattern << "] to queue " << queue->getName() + << " on exchange " << getName() << " (origin=" << fedOrigin << ")"); } } else if (fedOp == fedOpUnbind) { - bool reallyUnbind = false; - { - RWlock::ScopedWlock l(lock); - BindingKey* bk = bindingTree.getBindingKey(routingPattern); - if (bk) { - propagate = bk->fedBinding.delOrigin(queue->getName(), fedOrigin); - reallyUnbind = bk->fedBinding.countFedBindings(queue->getName()) == 0; + RWlock::ScopedWlock l(lock); + BindingKey* bk = getQueueBinding(queue, routingPattern); + if (bk) { + QPID_LOG(debug, "FedOpUnbind [" << routingPattern << "] from exchange " << getName() + << " on queue=" << queue->getName() << " origin=" << fedOrigin); + propagate = bk->fedBinding.delOrigin(queue->getName(), fedOrigin); + // if this was the last binding for the queue, delete the binding + if (bk->fedBinding.countFedBindings(queue->getName()) == 0) { + deleteBinding(queue, routingPattern, bk); } } - 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 @@ -281,20 +282,38 @@ bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, cons } } + cc.clearCache(); // clear the cache before we IVE route. routeIVE(); if (propagate) propagateFedOp(routingKey, fedTags, fedOp, fedOrigin); return true; } -bool TopicExchange::unbind(Queue::shared_ptr queue, const string& constRoutingKey, const FieldTable* /*args*/){ +bool TopicExchange::unbind(Queue::shared_ptr queue, const string& constRoutingKey, const FieldTable* args) +{ + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); + QPID_LOG(debug, "Unbinding key [" << constRoutingKey << "] from queue " << queue->getName() + << " on exchange " << getName() << " origin=" << fedOrigin << ")" ); + + ClearCache cc(&cacheLock,&bindingCache); // clear the cache on function exit. RWlock::ScopedWlock l(lock); string routingKey = normalize(constRoutingKey); - BindingKey* bk = bindingTree.getBindingKey(routingKey); + BindingKey* bk = getQueueBinding(queue, routingKey); if (!bk) return false; - Binding::vector& qv(bk->bindingVector); - bool propagate = false; + bool propagate = bk->fedBinding.delOrigin(queue->getName(), fedOrigin); + deleteBinding(queue, routingKey, bk); + if (propagate) + propagateFedOp(routingKey, string(), fedOpUnbind, string()); + return true; +} + +bool TopicExchange::deleteBinding(Queue::shared_ptr queue, + const std::string& routingKey, + BindingKey *bk) +{ + // Note well: write lock held by caller + Binding::vector& qv(bk->bindingVector); Binding::vector::iterator q; for (q = qv.begin(); q != qv.end(); q++) if ((*q)->queue == queue) @@ -303,42 +322,55 @@ bool TopicExchange::unbind(Queue::shared_ptr queue, const string& constRoutingKe qv.erase(q); assert(nBindings > 0); nBindings--; - propagate = bk->fedBinding.delOrigin(); + if(qv.empty()) { bindingTree.removeBindingKey(routingKey); } if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); } - QPID_LOG(debug, "Unbound [" << routingKey << "] from queue " << queue->getName()); - - if (propagate) - propagateFedOp(routingKey, string(), fedOpUnbind, string()); + QPID_LOG(debug, "Unbound key [" << routingKey << "] from queue " << queue->getName() + << " on exchange " << getName()); return true; } -bool TopicExchange::isBound(Queue::shared_ptr queue, const string& pattern) +/** returns a pointer to the BindingKey if the given queue is bound to this + * exchange using the routing pattern. 0 if queue binding does not exist. + */ +TopicExchange::BindingKey *TopicExchange::getQueueBinding(Queue::shared_ptr queue, const string& pattern) { // Note well: lock held by caller.... BindingKey *bk = bindingTree.getBindingKey(pattern); // Exact match against binding pattern - if (!bk) return false; + if (!bk) return 0; Binding::vector& qv(bk->bindingVector); Binding::vector::iterator q; for (q = qv.begin(); q != qv.end(); q++) if ((*q)->queue == queue) break; - return q != qv.end(); + return (q != qv.end()) ? bk : 0; } void TopicExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* /*args*/) { // Note: PERFORMANCE CRITICAL!!! - BindingList b(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >); + BindingList b; + std::map<std::string, BindingList>::iterator it; + { // only lock the cache for read + RWlock::ScopedRlock cl(cacheLock); + it = bindingCache.find(routingKey); + if (it != bindingCache.end()) { + b = it->second; + } + } PreRoute pr(msg, this); - BindingsFinderIter bindingsFinder(b); + if (!b.get()) // no cache hit { RWlock::ScopedRlock l(lock); + b = BindingList(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >); + BindingsFinderIter bindingsFinder(b); bindingTree.iterateMatch(routingKey, bindingsFinder); + RWlock::ScopedWlock cwl(cacheLock); + bindingCache[routingKey] = b; // update cache } doRoute(msg, b); } @@ -348,7 +380,7 @@ bool TopicExchange::isBound(Queue::shared_ptr queue, const string* const routing RWlock::ScopedRlock l(lock); if (routingKey && queue) { string key(normalize(*routingKey)); - return isBound(queue, key); + return getQueueBinding(queue, key) != 0; } else if (!routingKey && !queue) { return nBindings > 0; } else if (routingKey) { diff --git a/cpp/src/qpid/broker/TopicExchange.h b/cpp/src/qpid/broker/TopicExchange.h index a6c457dcb3..636918f8a1 100644 --- a/cpp/src/qpid/broker/TopicExchange.h +++ b/cpp/src/qpid/broker/TopicExchange.h @@ -56,7 +56,7 @@ class TopicExchange : public virtual Exchange { // | +-->d-->... // +-->x-->y-->... // - class BindingNode { + class QPID_BROKER_CLASS_EXTERN BindingNode { public: typedef boost::shared_ptr<BindingNode> shared_ptr; @@ -135,8 +135,31 @@ class TopicExchange : public virtual Exchange { BindingNode bindingTree; unsigned long nBindings; qpid::sys::RWlock lock; // protects bindingTree and nBindings - - bool isBound(Queue::shared_ptr queue, const std::string& pattern); + qpid::sys::RWlock cacheLock; // protects cache + std::map<std::string, BindingList> bindingCache; // cache of matched routes. + class ClearCache { + private: + qpid::sys::RWlock* cacheLock; + std::map<std::string, BindingList>* bindingCache; + bool cleared; + public: + ClearCache(qpid::sys::RWlock* l, std::map<std::string, BindingList>* bc): cacheLock(l), + bindingCache(bc),cleared(false) {}; + void clearCache() { + qpid::sys::RWlock::ScopedWlock l(*cacheLock); + if (!cleared) { + bindingCache->clear(); + cleared =true; + } + }; + ~ClearCache(){ + clearCache(); + }; + }; + BindingKey *getQueueBinding(Queue::shared_ptr queue, const std::string& pattern); + bool deleteBinding(Queue::shared_ptr queue, + const std::string& routingKey, + BindingKey *bk); class ReOriginIter; class BindingsFinderIter; diff --git a/cpp/src/qpid/broker/TxBuffer.cpp b/cpp/src/qpid/broker/TxBuffer.cpp index b509778e89..d92e6ace48 100644 --- a/cpp/src/qpid/broker/TxBuffer.cpp +++ b/cpp/src/qpid/broker/TxBuffer.cpp @@ -76,5 +76,5 @@ bool TxBuffer::commitLocal(TransactionalStore* const store) } void TxBuffer::accept(TxOpConstVisitor& v) const { - std::for_each(ops.begin(), ops.end(), boost::bind(&TxOp::accept, _1, boost::ref(v))); + std::for_each(ops.begin(), ops.end(), boost::bind(&TxOp::accept, _1, boost::ref(v))); } diff --git a/cpp/src/qpid/broker/TxPublish.cpp b/cpp/src/qpid/broker/TxPublish.cpp index 36a451e62c..9c2cf4a467 100644 --- a/cpp/src/qpid/broker/TxPublish.cpp +++ b/cpp/src/qpid/broker/TxPublish.cpp @@ -90,14 +90,7 @@ void TxPublish::deliverTo(const boost::shared_ptr<Queue>& 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 - */ - msg->enqueueComplete(); - } + queue->enqueue(ctxt, msg); } TxPublish::Commit::Commit(intrusive_ptr<Message>& _msg) : msg(_msg){} diff --git a/cpp/src/qpid/broker/TxPublish.h b/cpp/src/qpid/broker/TxPublish.h index effa585676..dba7878af2 100644 --- a/cpp/src/qpid/broker/TxPublish.h +++ b/cpp/src/qpid/broker/TxPublish.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 @@ -34,57 +34,58 @@ #include <boost/intrusive_ptr.hpp> namespace qpid { - namespace broker { - /** - * Defines the behaviour for publish operations on a - * transactional channel. Messages are routed through - * exchanges when received but are not at that stage delivered - * to the matching queues, rather the queues are held in an - * instance of this class. On prepare() the message is marked - * enqueued to the relevant queues in the MessagesStore. On - * commit() the messages will be passed to the queue for - * dispatch or to be added to the in-memory queue. - */ - class TxPublish : public TxOp, public Deliverable{ +namespace broker { +/** + * Defines the behaviour for publish operations on a + * transactional channel. Messages are routed through + * exchanges when received but are not at that stage delivered + * to the matching queues, rather the queues are held in an + * instance of this class. On prepare() the message is marked + * enqueued to the relevant queues in the MessagesStore. On + * commit() the messages will be passed to the queue for + * dispatch or to be added to the in-memory queue. + */ +class QPID_BROKER_CLASS_EXTERN TxPublish : public TxOp, public Deliverable{ - class Commit{ - boost::intrusive_ptr<Message>& msg; - public: - Commit(boost::intrusive_ptr<Message>& msg); - 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); - }; + class Commit{ + boost::intrusive_ptr<Message>& msg; + public: + Commit(boost::intrusive_ptr<Message>& msg); + 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<boost::shared_ptr<Queue> > queues; - std::list<boost::shared_ptr<Queue> > prepared; + boost::intrusive_ptr<Message> msg; + std::list<boost::shared_ptr<Queue> > queues; + std::list<boost::shared_ptr<Queue> > prepared; - void prepare(TransactionContext* ctxt, boost::shared_ptr<Queue>); + void prepare(TransactionContext* ctxt, boost::shared_ptr<Queue>); - public: - 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(); + public: + 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; }; - - QPID_BROKER_EXTERN virtual void deliverTo(const boost::shared_ptr<Queue>& queue); + virtual Message& getMessage() { return *msg; }; - virtual ~TxPublish(){} - virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + QPID_BROKER_EXTERN virtual void deliverTo(const boost::shared_ptr<Queue>& queue); - QPID_BROKER_EXTERN uint64_t contentSize(); + virtual ~TxPublish(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } - boost::intrusive_ptr<Message> getMessage() const { return msg; } - const std::list<boost::shared_ptr<Queue> > getQueues() const { return queues; } - }; - } + QPID_BROKER_EXTERN uint64_t contentSize(); + + boost::intrusive_ptr<Message> getMessage() const { return msg; } + const std::list<boost::shared_ptr<Queue> >& getQueues() const { return queues; } + const std::list<boost::shared_ptr<Queue> >& getPrepared() const { return prepared; } +}; +} } diff --git a/cpp/src/qpid/broker/windows/BrokerDefaults.cpp b/cpp/src/qpid/broker/windows/BrokerDefaults.cpp index b6862f0418..b65440b5ad 100644 --- a/cpp/src/qpid/broker/windows/BrokerDefaults.cpp +++ b/cpp/src/qpid/broker/windows/BrokerDefaults.cpp @@ -31,10 +31,16 @@ const std::string Broker::Options::DEFAULT_DATA_DIR_NAME("\\QPIDD.DATA"); std::string Broker::Options::getHome() { std::string home; +#ifdef _MSC_VER char home_c[MAX_PATH+1]; size_t unused; if (0 == getenv_s (&unused, home_c, sizeof(home_c), "HOME")) home += home_c; +#else + char *home_c = getenv("HOME"); + if (home_c) + home += home_c; +#endif return home; } diff --git a/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp index 608a8f7dae..2acc09cded 100644 --- a/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp +++ b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp @@ -42,7 +42,7 @@ public: NullAuthenticator(Connection& connection); ~NullAuthenticator(); void getMechanisms(framing::Array& mechanisms); - void start(const std::string& mechanism, const std::string& response); + void start(const std::string& mechanism, const std::string* response); void step(const std::string&) {} std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); }; @@ -57,7 +57,7 @@ public: SspiAuthenticator(Connection& connection); ~SspiAuthenticator(); void getMechanisms(framing::Array& mechanisms); - void start(const std::string& mechanism, const std::string& response); + void start(const std::string& mechanism, const std::string* response); void step(const std::string& response); std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); }; @@ -93,14 +93,15 @@ 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"))); } -void NullAuthenticator::start(const string& mechanism, const string& response) +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); + if (response && response->size() > 0 && (*response).c_str()[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); @@ -138,7 +139,7 @@ void SspiAuthenticator::getMechanisms(Array& mechanisms) QPID_LOG(info, "SASL: Mechanism list: ANONYMOUS PLAIN"); } -void SspiAuthenticator::start(const string& mechanism, const string& response) +void SspiAuthenticator::start(const string& mechanism, const string* response) { QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); if (mechanism == "ANONYMOUS") { @@ -151,16 +152,19 @@ void SspiAuthenticator::start(const string& mechanism, const string& response) // PLAIN's response is composed of 3 strings separated by 0 bytes: // authorization id, authentication id (user), clear-text password. - if (response.size() == 0) + if (!response || 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); + 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); + string dot("."); int error = 0; - if (!LogonUser(uid.c_str(), ".", pwd.c_str(), + if (!LogonUser(const_cast<char*>(uid.c_str()), + const_cast<char*>(dot.c_str()), + const_cast<char*>(pwd.c_str()), LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, &userToken)) @@ -176,7 +180,7 @@ void SspiAuthenticator::start(const string& mechanism, const string& response) client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); } -void SspiAuthenticator::step(const string& response) +void SspiAuthenticator::step(const string& /*response*/) { QPID_LOG(info, "SASL: Need another step!!!"); } diff --git a/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp b/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp index fd0e537192..1dff1ddc8f 100644 --- a/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp +++ b/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp @@ -27,10 +27,14 @@ #include "qpid/sys/AsynchIOHandler.h" #include "qpid/sys/ConnectionCodec.h" #include "qpid/sys/Socket.h" +#include "qpid/sys/SocketAddress.h" #include "qpid/sys/SystemInfo.h" #include "qpid/sys/windows/SslAsynchIO.h" + #include <boost/bind.hpp> +#include <boost/ptr_container/ptr_vector.hpp> #include <memory> + // security.h needs to see this to distinguish from kernel use. #define SECURITY_WIN32 #include <security.h> @@ -68,9 +72,10 @@ struct SslServerOptions : qpid::Options }; class SslProtocolFactory : public qpid::sys::ProtocolFactory { - qpid::sys::Socket listener; const bool tcpNoDelay; - const uint16_t listeningPort; + boost::ptr_vector<Socket> listeners; + boost::ptr_vector<AsynchAcceptor> acceptors; + uint16_t listeningPort; std::string brokerHost; const bool clientAuthSelected; std::auto_ptr<qpid::sys::AsynchAcceptor> acceptor; @@ -78,15 +83,14 @@ class SslProtocolFactory : public qpid::sys::ProtocolFactory { CredHandle credHandle; public: - SslProtocolFactory(const SslServerOptions&, int backlog, bool nodelay); + SslProtocolFactory(const SslServerOptions&, const std::string& host, const std::string& port, int backlog, bool nodelay); ~SslProtocolFactory(); void accept(sys::Poller::shared_ptr, sys::ConnectionCodec::Factory*); - void connect(sys::Poller::shared_ptr, const std::string& host, int16_t port, + void connect(sys::Poller::shared_ptr, const std::string& host, const std::string& port, sys::ConnectionCodec::Factory*, ConnectFailedCallback failed); uint16_t getPort() const; - std::string getHost() const; bool supports(const std::string& capability); private: @@ -115,6 +119,7 @@ static struct SslPlugin : public Plugin { try { const broker::Broker::Options& opts = broker->getOptions(); ProtocolFactory::shared_ptr protocol(new SslProtocolFactory(options, + "", boost::lexical_cast<std::string>(options.port), opts.connectionBacklog, opts.tcpNoDelay)); QPID_LOG(notice, "Listening for SSL connections on TCP port " << protocol->getPort()); @@ -127,12 +132,13 @@ static struct SslPlugin : public Plugin { } sslPlugin; SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, - int backlog, + const std::string& host, const std::string& port, int backlog, bool nodelay) : tcpNoDelay(nodelay), - listeningPort(listener.listen(options.port, backlog)), clientAuthSelected(options.clientAuth) { + // Make sure that certificate store is good before listening to sockets + // to avoid having open and listening sockets when there is no cert store SecInvalidateHandle(&credHandle); // Get the certificate for this server. @@ -177,6 +183,23 @@ SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, throw QPID_WINDOWS_ERROR(status); ::CertFreeCertificateContext(certContext); ::CertCloseStore(certStoreHandle, 0); + + // Listen to socket(s) + SocketAddress sa(host, port); + + // We must have at least one resolved address + QPID_LOG(info, "SSL Listening to: " << sa.asString()) + Socket* s = new Socket; + listeningPort = s->listen(sa, backlog); + listeners.push_back(s); + + // Try any other resolved addresses + while (sa.nextAddress()) { + QPID_LOG(info, "SSL Listening to: " << sa.asString()) + Socket* s = new Socket; + s->listen(sa, backlog); + listeners.push_back(s); + } } SslProtocolFactory::~SslProtocolFactory() { @@ -237,21 +260,19 @@ uint16_t SslProtocolFactory::getPort() const { return listeningPort; // Immutable no need for lock. } -std::string SslProtocolFactory::getHost() const { - return listener.getSockname(); -} - void SslProtocolFactory::accept(sys::Poller::shared_ptr poller, sys::ConnectionCodec::Factory* fact) { - acceptor.reset( - AsynchAcceptor::create(listener, - boost::bind(&SslProtocolFactory::established, this, poller, _1, fact, false))); - acceptor->start(poller); + for (unsigned i = 0; i<listeners.size(); ++i) { + acceptors.push_back( + AsynchAcceptor::create(listeners[i], + boost::bind(&SslProtocolFactory::established, this, poller, _1, fact, false))); + acceptors[i].start(poller); + } } void SslProtocolFactory::connect(sys::Poller::shared_ptr poller, const std::string& host, - int16_t port, + const std::string& port, sys::ConnectionCodec::Factory* fact, ConnectFailedCallback failed) { |