From 69f4669ccca584a64a69e2d9c53a05d847e8b403 Mon Sep 17 00:00:00 2001 From: Niels Werensteijn Date: Mon, 3 Mar 2014 17:29:50 +0100 Subject: [PATCH 01/41] Three fixes to get this lib compiled on linux g++ 4.7.3 Added include to amqpcpp.h Removed virtual destructors for exception.h and protocolexception.h. They were causing compile issues because of (no) noexcept specification. They were empty implementations anyway, and the ~std::exception was already virtual, so they were not needed anyway. --- amqpcpp.h | 1 + src/exception.h | 6 ------ src/protocolexception.h | 5 ----- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/amqpcpp.h b/amqpcpp.h index 0708362..3ae5c67 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -16,6 +16,7 @@ #include #include #include +#include // base C include files #include diff --git a/src/exception.h b/src/exception.h index 17d81f7..e4bb35a 100644 --- a/src/exception.h +++ b/src/exception.h @@ -22,12 +22,6 @@ protected: * @param what */ explicit Exception(const std::string &what) : runtime_error(what) {} - -public: - /** - * Destructor - */ - virtual ~Exception() {} }; /** diff --git a/src/protocolexception.h b/src/protocolexception.h index 9973f0b..7f829bc 100644 --- a/src/protocolexception.h +++ b/src/protocolexception.h @@ -23,11 +23,6 @@ public: * @param what */ explicit ProtocolException(const std::string &what) : Exception(what) {} - - /** - * Destructor - */ - virtual ~ProtocolException() {} }; /** From 72d0b18fe6be9120cd40e6a79104e14701a7da37 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 20 Mar 2014 11:17:47 -0400 Subject: [PATCH 02/41] fix namespace in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6777e3f..79da95c 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ class MyConnectionHandler : public AMQP::ConnectionHandler * to use. * @param connection The connection that can now be used */ - virtual void onConnected(Connection *connection) + virtual void onConnected(AMQP::Connection *connection) { // @todo // add your own implementation, for example by creating a channel @@ -77,7 +77,7 @@ class MyConnectionHandler : public AMQP::ConnectionHandler * @param connection The connection on which the error occured * @param message A human readable error message */ - virtual void onError(Connection *connection, const std::string &message) + virtual void onError(AMQP::Connection *connection, const std::string &message) { // @todo // add your own implementation, for example by reporting the error From 47f5ec2710873031cbc2b95c6cf5983df295b5ad Mon Sep 17 00:00:00 2001 From: Luca Marturana Date: Wed, 2 Apr 2014 11:53:46 +0200 Subject: [PATCH 03/41] On connection::parse char* can be const --- include/connection.h | 2 +- include/connectionimpl.h | 2 +- src/connectionimpl.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/connection.h b/include/connection.h index 64298a8..64e0ea1 100644 --- a/include/connection.h +++ b/include/connection.h @@ -74,7 +74,7 @@ public: * @param size size of the buffer to decode * @return number of bytes that were processed */ - size_t parse(char *buffer, size_t size) + size_t parse(const char *buffer, size_t size) { return _implementation.parse(buffer, size); } diff --git a/include/connectionimpl.h b/include/connectionimpl.h index 1755281..780c0ff 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -234,7 +234,7 @@ public: * @param size size of the buffer to decode * @return number of bytes that were processed */ - size_t parse(char *buffer, size_t size); + size_t parse(const char *buffer, size_t size); /** * Close the connection diff --git a/src/connectionimpl.cpp b/src/connectionimpl.cpp index 1a37f4a..8f40fde 100644 --- a/src/connectionimpl.cpp +++ b/src/connectionimpl.cpp @@ -104,7 +104,7 @@ void ConnectionImpl::remove(ChannelImpl *channel) * @param size size of the buffer to decode * @return number of bytes that were processed */ -size_t ConnectionImpl::parse(char *buffer, size_t size) +size_t ConnectionImpl::parse(const char *buffer, size_t size) { // do not parse if already in an error state if (_state == state_closed) return 0; From 41c1402d15efafd0addc9aa2d4f80058e5bf535d Mon Sep 17 00:00:00 2001 From: Luca Marturana Date: Wed, 2 Apr 2014 12:59:24 +0200 Subject: [PATCH 04/41] Const modifier to table get and some array operations --- include/array.h | 27 +++++++++++++++++++++++---- include/table.h | 2 +- src/array.cpp | 25 ++++++++++++++++++++----- src/table.cpp | 2 +- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/include/array.h b/include/array.h index befd1d6..98f78c5 100644 --- a/include/array.h +++ b/include/array.h @@ -1,6 +1,6 @@ /** * AMQP field array - * + * * @copyright 2014 Copernica BV */ @@ -20,7 +20,7 @@ private: * @typedef */ typedef std::vector> FieldArray; - + /** * The actual fields * @var FieldArray @@ -30,7 +30,7 @@ private: public: /** * Constructor to construct an array from a received frame - * + * * @param frame received frame */ Array(ReceivedFrame &frame); @@ -99,6 +99,25 @@ public: */ const Field &get(uint8_t index); + /** + * Get number of elements on this array + * + * @return array size + */ + uint32_t count() const; + + /** + * Remove last element from array + */ + void pop_back(); + + /** + * Add field to end of array + * + * @param value + */ + void push_back(const Field &value); + /** * Get a field * @@ -111,7 +130,7 @@ public: } /** - * Write encoded payload to the given buffer. + * Write encoded payload to the given buffer. * @param buffer */ virtual void fill(OutBuffer& buffer) const override; diff --git a/include/table.h b/include/table.h index c26fb6d..5343769 100644 --- a/include/table.h +++ b/include/table.h @@ -109,7 +109,7 @@ public: * @param name field name * @return the field value */ - const Field &get(const std::string &name); + const Field &get(const std::string &name) const; /** * Get a field diff --git a/src/array.cpp b/src/array.cpp index 3b5831b..706572d 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -1,8 +1,8 @@ /** * Array.cpp - * + * * Implementation of an array - * + * */ #include "includes.h" @@ -23,7 +23,7 @@ Array::Array(ReceivedFrame &frame) { // one byte less for the field type charsToRead -= 1; - + // read the field type and construct the field Field *field = Field::decode(frame); if (!field) continue; @@ -62,7 +62,7 @@ const Field &Array::get(uint8_t index) { // used if index does not exist static ShortString empty; - + // check whether we have that many elements if (index >= _fields.size()) return empty; @@ -70,6 +70,21 @@ const Field &Array::get(uint8_t index) return *_fields[index]; } +uint32_t Array::count() const +{ + return _fields.size(); +} + +void Array::pop_back() +{ + _fields.pop_back(); +} + +void Array::push_back(const Field& value) +{ + _fields.push_back(std::shared_ptr(value.clone())); +} + /** * Get the size this field will take when * encoded in the AMQP wire-frame format @@ -92,7 +107,7 @@ size_t Array::size() const } /** - * Write encoded payload to the given buffer. + * Write encoded payload to the given buffer. */ void Array::fill(OutBuffer& buffer) const { diff --git a/src/table.cpp b/src/table.cpp index ba41983..4be9b11 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -97,7 +97,7 @@ Table &Table::operator=(Table &&table) * @param name field name * @return the field value */ -const Field &Table::get(const std::string &name) +const Field &Table::get(const std::string &name) const { // we need an empty string static ShortString empty; From 17be969d49f2c1d8327b3dc3898f46820f22e2a1 Mon Sep 17 00:00:00 2001 From: Luca Marturana Date: Wed, 2 Apr 2014 15:40:35 +0200 Subject: [PATCH 05/41] Add pragma once --- include/array.h | 1 + include/booleanset.h | 1 + include/channel.h | 1 + include/channelhandler.h | 1 + include/channelimpl.h | 1 + include/classes.h | 1 + include/connection.h | 1 + include/connectionhandler.h | 1 + include/connectionimpl.h | 1 + include/decimalfield.h | 1 + include/entityimpl.h | 1 + include/envelope.h | 1 + include/exchangetype.h | 1 + include/field.h | 1 + include/fieldproxy.h | 1 + include/flags.h | 1 + include/login.h | 1 + include/message.h | 1 + include/metadata.h | 1 + include/numericfield.h | 1 + include/outbuffer.h | 1 + include/receivedframe.h | 1 + include/stringfield.h | 1 + include/table.h | 1 + include/watchable.h | 1 + 25 files changed, 25 insertions(+) diff --git a/include/array.h b/include/array.h index 98f78c5..7e59406 100644 --- a/include/array.h +++ b/include/array.h @@ -1,3 +1,4 @@ +#pragma once /** * AMQP field array * diff --git a/include/booleanset.h b/include/booleanset.h index 6383ab2..577fe47 100644 --- a/include/booleanset.h +++ b/include/booleanset.h @@ -1,3 +1,4 @@ +#pragma once /** * BooleanSet.h * diff --git a/include/channel.h b/include/channel.h index 0b36dba..d9dae3e 100644 --- a/include/channel.h +++ b/include/channel.h @@ -1,3 +1,4 @@ +#pragma once /** * Class describing a (mid-level) AMQP channel implementation * diff --git a/include/channelhandler.h b/include/channelhandler.h index ad0c001..c0d3438 100644 --- a/include/channelhandler.h +++ b/include/channelhandler.h @@ -1,3 +1,4 @@ +#pragma once /** * ChannelHandler.h * diff --git a/include/channelimpl.h b/include/channelimpl.h index ad02d27..77e4f16 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -1,3 +1,4 @@ +#pragma once /** * ChannelImpl.h * diff --git a/include/classes.h b/include/classes.h index af49200..b4a5500 100644 --- a/include/classes.h +++ b/include/classes.h @@ -1,3 +1,4 @@ +#pragma once /** * Classes.h * diff --git a/include/connection.h b/include/connection.h index 64e0ea1..a67e6ad 100644 --- a/include/connection.h +++ b/include/connection.h @@ -1,3 +1,4 @@ +#pragma once /** * Class describing a mid-level Amqp connection * diff --git a/include/connectionhandler.h b/include/connectionhandler.h index 5233948..7a90b50 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -1,3 +1,4 @@ +#pragma once /** * ConnectionHandler.h * diff --git a/include/connectionimpl.h b/include/connectionimpl.h index 780c0ff..77adada 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -1,3 +1,4 @@ +#pragma once /** * Connection implementation * diff --git a/include/decimalfield.h b/include/decimalfield.h index 5df085b..c9d3cfc 100644 --- a/include/decimalfield.h +++ b/include/decimalfield.h @@ -1,3 +1,4 @@ +#pragma once /** * Decimal field type for AMQP * diff --git a/include/entityimpl.h b/include/entityimpl.h index 51ac4ac..be5207d 100644 --- a/include/entityimpl.h +++ b/include/entityimpl.h @@ -1,3 +1,4 @@ +#pragma once /** * EntityImpl.h * diff --git a/include/envelope.h b/include/envelope.h index f66331f..fbe646e 100644 --- a/include/envelope.h +++ b/include/envelope.h @@ -1,3 +1,4 @@ +#pragma once /** * Envelope.h * diff --git a/include/exchangetype.h b/include/exchangetype.h index a5e7929..ea6ade8 100644 --- a/include/exchangetype.h +++ b/include/exchangetype.h @@ -1,3 +1,4 @@ +#pragma once /** * ExchangeType.h * diff --git a/include/field.h b/include/field.h index 41fbdca..079c741 100644 --- a/include/field.h +++ b/include/field.h @@ -1,3 +1,4 @@ +#pragma once /** * Available field types for AMQP * diff --git a/include/fieldproxy.h b/include/fieldproxy.h index 947d6cc..ad8c445 100644 --- a/include/fieldproxy.h +++ b/include/fieldproxy.h @@ -1,3 +1,4 @@ +#pragma once /** * Field proxy. Returned by the table. Can be casted to the * relevant native type (std::string or numeric) diff --git a/include/flags.h b/include/flags.h index 0a9c2ee..5f46b52 100644 --- a/include/flags.h +++ b/include/flags.h @@ -1,3 +1,4 @@ +#pragma once /** * AmqpFlags.h * diff --git a/include/login.h b/include/login.h index 7605696..51f79de 100644 --- a/include/login.h +++ b/include/login.h @@ -1,3 +1,4 @@ +#pragma once /** * The login information to access a server * diff --git a/include/message.h b/include/message.h index a036420..c92cc5e 100644 --- a/include/message.h +++ b/include/message.h @@ -1,3 +1,4 @@ +#pragma once /** * Message.h * diff --git a/include/metadata.h b/include/metadata.h index 7c1714a..42bf051 100644 --- a/include/metadata.h +++ b/include/metadata.h @@ -1,3 +1,4 @@ +#pragma once /** * MetaData.h * diff --git a/include/numericfield.h b/include/numericfield.h index bcddf58..82edf99 100644 --- a/include/numericfield.h +++ b/include/numericfield.h @@ -1,3 +1,4 @@ +#pragma once /** * Numeric field types for AMQP * diff --git a/include/outbuffer.h b/include/outbuffer.h index 70d4b56..24833fa 100644 --- a/include/outbuffer.h +++ b/include/outbuffer.h @@ -1,3 +1,4 @@ +#pragma once /** * OutBuffer.h * diff --git a/include/receivedframe.h b/include/receivedframe.h index 0f9a0e4..03bd0ae 100644 --- a/include/receivedframe.h +++ b/include/receivedframe.h @@ -1,3 +1,4 @@ +#pragma once /** * ReceivedFrame.h * diff --git a/include/stringfield.h b/include/stringfield.h index 1a2ffd3..724b495 100644 --- a/include/stringfield.h +++ b/include/stringfield.h @@ -1,3 +1,4 @@ +#pragma once /** * String field types for amqp * diff --git a/include/table.h b/include/table.h index 5343769..3435f13 100644 --- a/include/table.h +++ b/include/table.h @@ -1,3 +1,4 @@ +#pragma once /** * AMQP field table * diff --git a/include/watchable.h b/include/watchable.h index dab54aa..a81e71d 100644 --- a/include/watchable.h +++ b/include/watchable.h @@ -1,3 +1,4 @@ +#pragma once /** * Watchable.h * From 2939272bc8e189f221d36c3f485f512affde5002 Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Tue, 8 Apr 2014 14:42:07 +0200 Subject: [PATCH 06/41] Work in progress to convert channel handler to callback system --- amqpcpp.h | 2 + include/callbacks.h | 157 ++++++++++++++ include/channel.h | 360 ++++++++++++++++++------------ include/channelhandler.h | 126 +---------- include/channelimpl.h | 362 +++++++++++++++++-------------- include/connection.h | 18 ++ include/connectionimpl.h | 59 +++-- include/deferred.h | 179 +++++++++++++++ src/basicqosokframe.h | 16 +- src/basicrecoverokframe.h | 16 +- src/channelflowokframe.h | 13 +- src/channelimpl.cpp | 298 ++++++++++++++++--------- src/exchangebindokframe.h | 20 +- src/exchangedeclareokframe.h | 12 +- src/exchangedeleteokframe.h | 14 +- src/exchangeunbindokframe.h | 20 +- src/methodframe.h | 5 +- src/queuebindokframe.h | 12 +- src/queueunbindokframe.h | 14 +- src/transactioncommitokframe.h | 20 ++ src/transactionrollbackokframe.h | 22 +- src/transactionselectokframe.h | 20 ++ 22 files changed, 1137 insertions(+), 628 deletions(-) create mode 100644 include/callbacks.h create mode 100644 include/deferred.h diff --git a/amqpcpp.h b/amqpcpp.h index 3ae5c67..af79811 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -49,6 +49,8 @@ // mid level includes #include #include +#include +#include #include #include #include diff --git a/include/callbacks.h b/include/callbacks.h new file mode 100644 index 0000000..b29bb38 --- /dev/null +++ b/include/callbacks.h @@ -0,0 +1,157 @@ +/** + * Callbacks.h + * + * Class storing deferred callbacks of different type. + * + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * Class for managing deferred callbacks + */ +class Callbacks +{ +private: + /** + * Different callback types supported + */ + std::tuple< + std::deque>, + std::deque>, + std::deque> + > _callbacks; + + /** + * If all else fails, we have gotten the wrong + * type, which is not present in the arguments. + * + * This should result in a compile error. + */ + template + struct getIndex + { + // if this structure is used, we went past the last argument + // and this static_assert should trigger a compile failure. + static_assert(N < sizeof...(Arguments), "Type T not found in Arguments"); + + // we still have to provide this member though + static constexpr std::size_t value = N; + }; + + /** + * This structure has one static member that represents + * the index of T in Arguments. This variant is used where U + * does equal T, so a match is found, meaning the current + * index given is the right one. + */ + template + struct getIndex + { + // element is same type as we are looking for + static constexpr std::size_t value = N; + }; + + /** + * This structure has one static member that represents + * the index of T in Arguments. This variant is used where U + * does not equal T, so we need to look at the next member. + */ + template + struct getIndex + { + // current N is not correct, unroll to next element + static constexpr std::size_t value = getIndex::value; + }; + + /** + * Retrieve the list of callbacks matching the type + * + * @param tuple tuple with callbacks + */ + template + T& get(std::tuple& tuple) + { + // retrieve the index at which the requested callbacks can be found + constexpr std::size_t index = getIndex::value; + + // retrieve the callbacks + return std::get(tuple); + } +public: + /** + * Add a deferred to the available callbacks + * + * @param deferred the deferred to add + * @return reference to the inserted deferred + */ + template + Deferred& push_back(const Deferred& item) + { + // retrieve the container + auto &container = get>>(_callbacks); + + // add the element + container.push_back(item); + + // return reference to the new item + return container.back(); + } + + /** + * Report success to the relevant callback + * + * @param mixed... additional parameters + */ + template + void reportSuccess(Arguments ...parameters) + { + // retrieve the container and element + auto &container = get>>(_callbacks); + auto &callback = container.front(); + + // execute the callback + callback.success(parameters...); + + // remove the executed callback + container.pop_front(); + } + + /** + * Report a failure + * + * @param error a description of the error + */ + template + typename std::enable_if::value>::type + reportFailure(const std::string& message) + {} + + /** + * Report a failure + * + * @param error a description of the error + */ + template + typename std::enable_if::value>::type + reportFailure(const std::string& message) + { + // retrieve the callbacks at current index + auto &callbacks = std::get(_callbacks); + + // report errors to all callbacks of the current type + for (auto &callback : callbacks) callback.error(message); + + // execute the next type + reportFailure(message); + } +}; + +/** + * End namespace + */ +} diff --git a/include/channel.h b/include/channel.h index d9dae3e..8a2550a 100644 --- a/include/channel.h +++ b/include/channel.h @@ -1,7 +1,7 @@ #pragma once /** * Class describing a (mid-level) AMQP channel implementation - * + * * @copyright 2014 Copernica BV */ @@ -36,27 +36,55 @@ public: virtual ~Channel() {} /** - * Pause deliveries on a channel - * - * This will stop all incoming messages - * - * This method returns true if the request to pause has been sent to the - * broker. This does not necessarily mean that the channel is already - * paused. - * - * @return bool + * Callback that is called when the channel was succesfully created. + * + * Only one callback can be registered. Calling this function multiple + * times will remove the old callback. + * + * @param callback the callback to execute */ - bool pause() + void onReady(const std::function& callback) + { + // store callback in implementation + _implementation._readyCallback = callback; + } + + /** + * Callback that is called when an error occurs. + * + * Only one error callback can be registered. Calling this function + * multiple times will remove the old callback. + * + * @param callback the callback to execute + */ + void onError(const std::function& callback) + { + // store callback in implementation + _implementation._errorCallback = callback; + } + + /** + * Pause deliveries on a channel + * + * This will stop all incoming messages + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + */ + Deferred<>& pause() { return _implementation.pause(); } - + /** * Resume a paused channel - * - * @return bool + * + * This will resume incoming messages + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool resume() + Deferred<>& resume() { return _implementation.resume(); } @@ -69,199 +97,254 @@ public: { return _implementation.connected(); } - + /** * Start a transaction - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool startTransaction() + Deferred<>& startTransaction() { return _implementation.startTransaction(); } - + /** * Commit the current transaction - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool commitTransaction() + Deferred<>& commitTransaction() { return _implementation.commitTransaction(); } - + /** * Rollback the current transaction - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool rollbackTransaction() + Deferred<>& rollbackTransaction() { return _implementation.rollbackTransaction(); } - + /** * Declare an exchange - * + * * If an empty name is supplied, a name will be assigned by the server. - * + * * The following flags can be used for the exchange: - * + * * - durable exchange survives a broker restart * - autodelete exchange is automatically removed when all connected queues are removed * - passive only check if the exchange exist - * + * * @param name name of the exchange * @param type exchange type * @param flags exchange flags * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(name, type, flags, arguments); } - bool declareExchange(const std::string &name, ExchangeType type, const Table &arguments) { return _implementation.declareExchange(name, type, 0, arguments); } - bool declareExchange(const std::string &name, ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(name, type, flags, Table()); } - bool declareExchange(ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(std::string(), type, flags, arguments); } - bool declareExchange(ExchangeType type, const Table &arguments) { return _implementation.declareExchange(std::string(), type, 0, arguments); } - bool declareExchange(ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(std::string(), type, flags, Table()); } - + Deferred<>& declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(name, type, flags, arguments); } + Deferred<>& declareExchange(const std::string &name, ExchangeType type, const Table &arguments) { return _implementation.declareExchange(name, type, 0, arguments); } + Deferred<>& declareExchange(const std::string &name, ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(name, type, flags, Table()); } + Deferred<>& declareExchange(ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(std::string(), type, flags, arguments); } + Deferred<>& declareExchange(ExchangeType type, const Table &arguments) { return _implementation.declareExchange(std::string(), type, 0, arguments); } + Deferred<>& declareExchange(ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(std::string(), type, flags, Table()); } + /** * Remove an exchange - * + * * The following flags can be used for the exchange: - * + * * - ifunused only delete if no queues are connected * @param name name of the exchange to remove * @param flags optional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool removeExchange(const std::string &name, int flags = 0) { return _implementation.removeExchange(name, flags); } - + Deferred<>& removeExchange(const std::string &name, int flags = 0) { return _implementation.removeExchange(name, flags); } + /** * Bind two exchanges to each other - * + * * The following flags can be used for the exchange - * + * * - nowait do not wait on response - * + * * @param source the source exchange * @param target the target exchange * @param routingkey the routing key * @param flags optional flags * @param arguments additional bind arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, flags, arguments); } - bool bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, 0, arguments); } - bool bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags = 0) { return _implementation.bindExchange(source, target, routingkey, flags, Table()); } - + Deferred<>& bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, flags, arguments); } + Deferred<>& bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, 0, arguments); } + Deferred<>& bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags = 0) { return _implementation.bindExchange(source, target, routingkey, flags, Table()); } + /** * Unbind two exchanges from one another - * + * * The following flags can be used for the exchange - * + * * - nowait do not wait on response - * + * * @param target the target exchange * @param source the source exchange * @param routingkey the routing key * @param flags optional flags * @param arguments additional unbind arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, flags, arguments); } - bool unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, 0, arguments); } - bool unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags = 0) { return _implementation.unbindExchange(target, source, routingkey, flags, Table()); } - + Deferred<>& unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, flags, arguments); } + Deferred<>& unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, 0, arguments); } + Deferred<>& unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags = 0) { return _implementation.unbindExchange(target, source, routingkey, flags, Table()); } + /** * Declare a queue - * + * * If you do not supply a name, a name will be assigned by the server. - * + * * The flags can be a combination of the following values: - * + * * - durable queue survives a broker restart * - autodelete queue is automatically removed when all connected consumers are gone * - passive only check if the queue exist * - exclusive the queue only exists for this connection, and is automatically removed when connection is gone - * + * * @param name name of the queue * @param flags combination of flags * @param arguments optional arguments + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount) { + * + * std::cout << "Queue '" << name << "' has been declared with " << messageCount << " messages and " << consumerCount << " consumers" << std::endl; + * + * }); */ - bool declareQueue(const std::string &name, int flags, const Table &arguments) { return _implementation.declareQueue(name, flags, arguments); } - bool declareQueue(const std::string &name, const Table &arguments) { return _implementation.declareQueue(name, 0, arguments); } - bool declareQueue(const std::string &name, int flags = 0) { return _implementation.declareQueue(name, flags, Table()); } - bool declareQueue(int flags, const Table &arguments) { return _implementation.declareQueue(std::string(), flags, arguments); } - bool declareQueue(const Table &arguments) { return _implementation.declareQueue(std::string(), 0, arguments); } - bool declareQueue(int flags = 0) { return _implementation.declareQueue(std::string(), flags, Table()); } + Deferred& declareQueue(const std::string &name, int flags, const Table &arguments) { return _implementation.declareQueue(name, flags, arguments); } + Deferred& declareQueue(const std::string &name, const Table &arguments) { return _implementation.declareQueue(name, 0, arguments); } + Deferred& declareQueue(const std::string &name, int flags = 0) { return _implementation.declareQueue(name, flags, Table()); } + Deferred& declareQueue(int flags, const Table &arguments) { return _implementation.declareQueue(std::string(), flags, arguments); } + Deferred& declareQueue(const Table &arguments) { return _implementation.declareQueue(std::string(), 0, arguments); } + Deferred& declareQueue(int flags = 0) { return _implementation.declareQueue(std::string(), flags, Table()); } /** * Bind a queue to an exchange - * + * * The following flags can be used for the exchange - * + * * - nowait do not wait on response - * + * * @param exchange the source exchange * @param queue the target queue * @param routingkey the routing key * @param flags additional flags * @param arguments additional bind arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, flags, arguments); } - bool bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, 0, arguments); } - bool bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags = 0) { return _implementation.bindQueue(exchange, queue, routingkey, flags, Table()); } - + Deferred<>& bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, flags, arguments); } + Deferred<>& bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, 0, arguments); } + Deferred<>& bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags = 0) { return _implementation.bindQueue(exchange, queue, routingkey, flags, Table()); } + /** * Unbind a queue from an exchange * @param exchange the source exchange * @param queue the target queue * @param routingkey the routing key * @param arguments additional bind arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.unbindQueue(exchange, queue, routingkey, arguments); } - bool unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey) { return _implementation.unbindQueue(exchange, queue, routingkey, Table()); } - + Deferred<>& unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.unbindQueue(exchange, queue, routingkey, arguments); } + Deferred<>& unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey) { return _implementation.unbindQueue(exchange, queue, routingkey, Table()); } + /** * Purge a queue - * + * * The following flags can be used for the exchange - * + * * - nowait do not wait on response - * + * * @param name name of the queue * @param flags additional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, uint32_t messageCount); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { + * + * std::cout << "Queue purged, all " << messageCount << " messages removed" << std::endl; + * + * }); */ - bool purgeQueue(const std::string &name, int flags = 0){ return _implementation.purgeQueue(name, flags); } - + Deferred& purgeQueue(const std::string &name, int flags = 0){ return _implementation.purgeQueue(name, flags); } + /** * Remove a queue - * + * * The following flags can be used for the exchange: - * + * * - ifunused only delete if no consumers are connected * - ifempty only delete if the queue is empty * * @param name name of the queue to remove * @param flags optional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, uint32_t messageCount); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { + * + * std::cout << "Queue deleted, along with " << messageCount << " messages" << std::endl; + * + * }); */ - bool removeQueue(const std::string &name, int flags = 0) { return _implementation.removeQueue(name, flags); } - + Deferred& removeQueue(const std::string &name, int flags = 0) { return _implementation.removeQueue(name, flags); } + /** * Publish a message to an exchange - * + * * The following flags can be used - * + * * - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method * - immediate if set, a message that could not immediately be consumed is returned to the onReturned method - * + * * If either of the two flags is set, and the message could not immediately * be published, the message is returned by the server to the client. If you - * want to catch such returned messages, you need to implement the + * want to catch such returned messages, you need to implement the * ChannelHandler::onReturned() method. - * + * * @param exchange the exchange to publish to * @param routingkey the routing key * @param flags optional flags (see above) @@ -275,44 +358,44 @@ public: bool publish(const std::string &exchange, const std::string &routingKey, const std::string &message) { return _implementation.publish(exchange, routingKey, 0, Envelope(message)); } bool publish(const std::string &exchange, const std::string &routingKey, int flags, const char *message, size_t size) { return _implementation.publish(exchange, routingKey, flags, Envelope(message, size)); } bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size) { return _implementation.publish(exchange, routingKey, 0, Envelope(message, size)); } - + /** * Set the Quality of Service (QOS) for this channel - * - * When you consume messages, every single messages needs to be ack'ed to inform + * + * When you consume messages, every single message needs to be ack'ed to inform * the RabbitMQ server that is has been received. The Qos setting specifies the * number of unacked messages that may exist in the client application. The server * stops delivering more messages if the number of unack'ed messages has reached * the prefetchCount - * + * * @param prefetchCount maximum number of messages to prefetch * @return bool whether the Qos frame is sent. */ - bool setQos(uint16_t prefetchCount) + Deferred<>& setQos(uint16_t prefetchCount) { return _implementation.setQos(prefetchCount); } - + /** * Tell the RabbitMQ server that we're ready to consume messages - * + * * After this method is called, RabbitMQ starts delivering messages to the client * application. The consume tag is a string identifier that will be passed to - * each received message, so that you can associate incoming messages with a + * each received message, so that you can associate incoming messages with a * consumer. If you do not specify a consumer tag, the server will assign one * for you. - * + * * The following flags are supported: - * + * * - nolocal if set, messages published on this channel are not also consumed * - noack if set, consumed messages do not have to be acked, this happens automatically * - exclusive request exclusive access, only this consumer can access the queue * - nowait the server does not have to send a response back that consuming is active - * - * The method ChannelHandler::onConsumerStarted() will be called when the + * + * The method ChannelHandler::onConsumerStarted() will be called when the * consumer has started (unless the nowait option was set, in which case * no confirmation method is called) - * + * * @param queue the queue from which you want to consume * @param tag a consumer tag that will be associated with this consume operation * @param flags additional flags @@ -325,56 +408,56 @@ public: bool consume(const std::string &queue, int flags, const Table &arguments) { return _implementation.consume(queue, std::string(), flags, arguments); } bool consume(const std::string &queue, int flags = 0) { return _implementation.consume(queue, std::string(), flags, Table()); } bool consume(const std::string &queue, const Table &arguments) { return _implementation.consume(queue, std::string(), 0, arguments); } - + /** * Cancel a running consume call - * + * * If you want to stop a running consumer, you can use this method with the consumer tag - * + * * The following flags are supported: - * + * * - nowait the server does not have to send a response back that the consumer has been cancelled - * + * * The method ChannelHandler::onConsumerStopped() will be called when the consumer * was succesfully stopped (unless the nowait option was used, in which case no * confirmation method is called) - * + * * @param tag the consumer tag * @param flags optional additional flags * @return bool */ bool cancel(const std::string &tag, int flags = 0) { return _implementation.cancel(tag, flags); } - + /** * Acknoldge a received message - * + * * When a message is received in the ChannelHandler::onReceived() method, * you must acknoledge it so that RabbitMQ removes it from the queue (unless * you are consuming with the noack option). This method can be used for * this acknoledging. - * + * * The following flags are supported: - * + * * - multiple acknoledge multiple messages: all un-acked messages that were earlier delivered are acknowledged too - * + * * @param deliveryTag the unique delivery tag of the message * @param flags optional flags * @return bool */ bool ack(uint64_t deliveryTag, int flags=0) { return _implementation.ack(deliveryTag, flags); } - + /** * Reject or nack a message - * + * * When a message was received in the ChannelHandler::onReceived() method, * and you don't want to acknoledge it, you can also choose to reject it by - * calling this reject method. - * + * calling this reject method. + * * The following flags are supported: - * + * * - multiple reject multiple messages: all un-acked messages that were earlier delivered are unacked too * - requeue if set, the message is put back in the queue, otherwise it is dead-lettered/removed - * + * * @param deliveryTag the unique delivery tag of the message * @param flags optional flags * @return bool @@ -383,27 +466,28 @@ public: /** * Recover all messages that were not yet acked - * - * This method asks the server to redeliver all unacknowledged messages on a specified + * + * This method asks the server to redeliver all unacknowledged messages on a specified * channel. Zero or more messages may be redelivered. - * + * * The following flags are supported: - * + * * - requeue if set, the server will requeue the messages, so the could also end up with at different consumer - * + * * @param flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool recover(int flags = 0) { return _implementation.recover(flags); } + Deferred<>& recover(int flags = 0) { return _implementation.recover(flags); } /** * Close the current channel - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool close() - { - return _implementation.close(); - } + Deferred<>& close() { return _implementation.close(); } /** * Get the channel we're working on diff --git a/include/channelhandler.h b/include/channelhandler.h index c0d3438..2ad468f 100644 --- a/include/channelhandler.h +++ b/include/channelhandler.h @@ -22,12 +22,6 @@ namespace AMQP { class ChannelHandler { public: - /** - * Method that is called when the channel was succesfully created. - * @param channel the channel that is ready - */ - virtual void onReady(Channel *channel) {} - /** * An error has occured on the channel * The channel is no longer usable after an error has occured on it. @@ -36,108 +30,6 @@ public: */ virtual void onError(Channel *channel, const std::string &message) {} - /** - * Method that is called when the channel was paused - * This is the result of a call to Channel::pause() - * @param channel the channel that is now paused - */ - virtual void onPaused(Channel *channel) {} - - /** - * Method that is called when the channel was resumed - * This is the result of a call to Channel::resume() - * @param channel the channel that is no longer paused - */ - virtual void onResumed(Channel *channel) {} - - /** - * Method that is called when a channel is closed - * This is the result of a call to Channel::close() - * @param channel the channel that is closed - */ - virtual void onClosed(Channel *channel) {} - - /** - * Method that is called when a transaction was started - * This is the result of a call to Channel::startTransaction() - * @param channel the channel on which the transaction was started - */ - virtual void onTransactionStarted(Channel *channel) {} - - /** - * Method that is called when a transaction was committed - * This is the result of a call to Channel::commitTransaction() - * @param channel the channel on which the transaction was committed - */ - virtual void onTransactionCommitted(Channel *channel) {} - - /** - * Method that is called when a transaction was rolled back - * This is the result of a call to Channel::rollbackTransaction() - * @param channel the channel on which the transaction was rolled back - */ - virtual void onTransactionRolledBack(Channel *channel) {} - - /** - * Method that is called when an exchange is bound - * This is the result of a call to Channel::bindExchange() - * @param channel the channel on which the exchange was bound - */ - virtual void onExchangeBound(Channel *channel) {} - - /** - * Method that is called when an exchange is unbound - * This is the result of a call to Channel::unbindExchange() - * @param channel the channel on which the exchange was unbound - */ - virtual void onExchangeUnbound(Channel *channel) {} - - /** - * Method that is called when an exchange is deleted - * This is the result of a call to Channel::deleteExchange() - * @param channel the channel on which the exchange was deleted - */ - virtual void onExchangeDeleted(Channel *channel) {} - - /** - * Mehod that is called when an exchange is declared - * This is the result of a call to Channel::declareExchange() - * @param channel the channel on which the exchange was declared - */ - virtual void onExchangeDeclared(Channel *channel) {} - - /** - * Method that is called when a queue is declared - * This is the result of a call to Channel::declareQueue() - * @param channel the channel on which the queue was declared - * @param name name of the queue - * @param messageCount number of messages in queue - * @param consumerCount number of active consumers - */ - virtual void onQueueDeclared(Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount) {} - - /** - * Method that is called when a queue is bound - * This is the result of a call to Channel::bindQueue() - * @param channel the channel on which the queue was bound - */ - virtual void onQueueBound(Channel *channel) {} - - /** - * Method that is called when a queue is deleted - * This is the result of a call to Channel::deleteQueue() - * @param channel the channel on which the queue was deleted - * @param messageCount number of messages deleted along with the queue - */ - virtual void onQueueDeleted(Channel *channel, uint32_t messageCount) {} - - /** - * Method that is called when a queue is unbound - * This is the result of a call to Channel::unbindQueue() - * @param channel the channel on which the queue was unbound - */ - virtual void onQueueUnbound(Channel *channel) {} - /** * Method that is called when a queue is purged * This is the result of a call to Channel::purgeQueue() @@ -146,13 +38,6 @@ public: */ virtual void onQueuePurged(Channel *channel, uint32_t messageCount) {} - /** - * Method that is called when the quality-of-service was changed - * This is the result of a call to Channel::setQos() - * @param channel the channel on which the qos was set - */ - virtual void onQosSet(Channel *channel) {} - /** * Method that is called when a consumer was started * This is the result of a call to Channel::consume() @@ -168,7 +53,7 @@ public: * @param tag the consumer tag */ virtual void onConsumerStopped(Channel *channel, const std::string &tag) {} - + /** * Method that is called when a message has been received on a channel * This message will be called for every message that is received after @@ -182,7 +67,7 @@ public: * @param redelivered is this a redelivered message? */ virtual void onReceived(Channel *channel, const Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) {} - + /** * Method that is called when a message you tried to publish was returned * by the server. This only happens when the 'mandatory' or 'immediate' flag @@ -193,13 +78,6 @@ public: * @param text human readable reply reason */ virtual void onReturned(Channel *channel, const Message &message, int16_t code, const std::string &text) {} - - /** - * Method that is called when the server starts recovering messages - * This is the result of a call to Channel::recover() - * @param channel the channel on which the recover method was called - */ - virtual void onRecovering(Channel *channel) {} }; diff --git a/include/channelimpl.h b/include/channelimpl.h index 77e4f16..6651e9b 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -37,7 +37,28 @@ private: * @var MyChannelHandler */ ChannelHandler *_handler; - + + /** + * Callback when the channel is ready + */ + std::function _readyCallback; + + /** + * Callback when the channel errors out + */ + std::function _errorCallback; + + /** + * The callbacks waiting to be called + */ + std::deque> _callbacks; + + /** + * Callbacks with additional parameters + */ + std::deque> _queueDeclaredCallbacks; + std::deque> _queueRemovedCallbacks; + /** * The channel number * @var uint16_t @@ -53,13 +74,13 @@ private: state_closing, state_closed } _state = state_connected; - + /** * Is a transaction now active? * @var bool */ bool _transaction = false; - + /** * The message that is now being received * @var MessageImpl @@ -68,11 +89,11 @@ private: /** * Construct a channel object - * + * * Note that the constructor is private, and that the Channel class is * a friend. By doing this we ensure that nobody can instantiate this * object, and that it can thus only be used inside the library. - * + * * @param parent the public channel object * @param connection pointer to the connection * @param handler handler that is notified on events @@ -96,23 +117,23 @@ public: /** * Pause deliveries on a channel - * + * * This will stop all incoming messages - * - * This method returns true if the request to pause has been sent to the - * broker. This does not necessarily mean that the channel is already - * paused. - * - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool pause(); - + Deferred<>& pause(); + /** * Resume a paused channel - * - * @return bool + * + * This will resume incoming messages + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool resume(); + Deferred<>& resume(); /** * Is the channel connected? @@ -122,123 +143,170 @@ public: { return _state == state_connected; } - + /** * Start a transaction - * @return bool */ - bool startTransaction(); - + Deferred<>& startTransaction(); + /** * Commit the current transaction - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool commitTransaction(); - + Deferred<>& commitTransaction(); + /** * Rollback the current transaction - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool rollbackTransaction(); - + Deferred<>& rollbackTransaction(); + /** * declare an exchange + * * @param name name of the exchange to declare * @param type type of exchange * @param flags additional settings for the exchange * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments); - + Deferred<>& declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments); + /** * bind two exchanges + * @param source exchange which binds to target * @param target exchange to bind to * @param routingKey routing key * @param glags additional flags * @param arguments additional arguments for binding - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); - + Deferred<>& bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); + /** * unbind two exchanges + * @param source the source exchange * @param target the target exchange * @param routingkey the routing key * @param flags optional flags * @param arguments additional unbind arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); - + Deferred<>& unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); + /** * remove an exchange + * * @param name name of the exchange to remove * @param flags additional settings for deleting the exchange - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool removeExchange(const std::string &name, int flags); - + Deferred<>& removeExchange(const std::string &name, int flags); + /** * declare a queue * @param name queue name * @param flags additional settings for the queue * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool declareQueue(const std::string &name, int flags, const Table &arguments); - + Deferred& declareQueue(const std::string &name, int flags, const Table &arguments); + /** * Bind a queue to an exchange + * * @param exchangeName name of the exchange to bind to * @param queueName name of the queue * @param routingkey routingkey * @param flags additional flags * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments); + Deferred<>& bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments); /** * Unbind a queue from an exchange + * * @param exchange the source exchange * @param queue the target queue * @param routingkey the routing key * @param arguments additional bind arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool unbindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments); - + Deferred<>& unbindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments); + /** * Purge a queue * @param queue queue to purge * @param flags additional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, uint32_t messageCount); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { + * + * std::cout << "Queue purged, all " << messageCount << " messages removed" << std::endl; + * + * }); */ - bool purgeQueue(const std::string &name, int flags); - + Deferred& purgeQueue(const std::string &name, int flags); + /** * Remove a queue * @param queue queue to remove * @param flags additional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, uint32_t messageCount); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { + * + * std::cout << "Queue deleted, along with " << messageCount << " messages" << std::endl; + * + * }); */ - bool removeQueue(const std::string &name, int flags); + Deferred& removeQueue(const std::string &name, int flags); /** * Publish a message to an exchange - * + * * The following flags can be used - * + * * - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method * - immediate if set, a message that could not immediately be consumed is returned to the onReturned method - * + * * If the mandatory or immediate flag is set, and the message could not immediately * be published, the message will be returned to the client, and will eventually * end up in your ChannelHandler::onReturned() method. - * + * * @param exchange the exchange to publish to * @param routingkey the routing key * @param flags optional flags (see above) @@ -247,13 +315,15 @@ public: * @param size size of the message */ bool publish(const std::string &exchange, const std::string &routingKey, int flags, const Envelope &envelope); - + /** * Set the Quality of Service (QOS) of the entire connection * @param prefetchCount maximum number of messages to prefetch - * @return bool whether the Qos frame is sent. + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool setQos(uint16_t prefetchCount); + Deferred<>& setQos(uint16_t prefetchCount); /** * Tell the RabbitMQ server that we're ready to consume messages @@ -264,7 +334,7 @@ public: * @return bool */ bool consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments); - + /** * Cancel a running consumer * @param tag the consumer tag @@ -288,19 +358,23 @@ public: * @return bool */ bool reject(uint64_t deliveryTag, int flags); - + /** * Recover messages that were not yet ack'ed * @param flags optional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool recover(int flags); - + Deferred<>& recover(int flags); + /** * Close the current channel - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ - bool close(); + Deferred<>& close(); /** * Get the channel we're working on @@ -310,14 +384,34 @@ public: { return _id; } - + /** * Send a frame over the channel * @param frame frame to send * @return bool was frame succesfully sent? */ bool send(const Frame &frame); - + + /** + * Send a frame over the channel and + * get a deferred handler for it. + * + * @param frame frame to send + * @param message the message to trigger if the frame cannot be send at all + */ + Deferred<>& send(const Frame &frame, const char *message); + + /** + * Send a frame over the channel and + * get a deferred handler for it. + * + * @param frame frame to send + * @param message the message to trigger if the frame cannot be send at all + * @param queue the queue to store the callbacks in + */ + template + Deferred& send(const Frame &frame, const char *message, std::deque>& queue); + /** * Report to the handler that the channel is closed */ @@ -325,38 +419,33 @@ public: { // change state _state = state_closed; - + // inform handler - if (_handler) _handler->onClosed(_parent); + reportSuccess(); } - + /** - * Report to the handler that the channel is paused + * Report success + * + * This function is called to report success for all + * cases where the callback does not receive any parameters */ - void reportPaused() + void reportSuccess() { - // inform handler - if (_handler) _handler->onPaused(_parent); + // report success for the oldest request + _callbacks.front().success(); + _callbacks.pop_front(); } - - /** - * Report to the handler that the channel is resumed - */ - void reportResumed() - { - // inform handler - if (_handler) _handler->onResumed(_parent); - } - + /** * Report to the handler that the channel is opened */ void reportReady() { // inform handler - if (_handler) _handler->onReady(_parent); + if (_readyCallback) _readyCallback(_parent); } - + /** * Report an error message on a channel * @param message @@ -365,43 +454,16 @@ public: { // change state _state = state_closed; - + // inform handler - if (_handler) _handler->onError(_parent, message); + if (_errorCallback) _errorCallback(_parent, message); + + // and all waiting deferred callbacks + for (auto &deferred : _callbacks) deferred.error(message); + for (auto &deferred : _queueDeclaredCallbacks) deferred.error(message); + for (auto &deferred : _queueRemovedCallbacks) deferred.error(message); } - /** - * Report that the exchange is succesfully declared - */ - void reportExchangeDeclared() - { - if (_handler) _handler->onExchangeDeclared(_parent); - } - - /** - * Report that the exchange is succesfully deleted - */ - void reportExchangeDeleted() - { - if (_handler) _handler->onExchangeDeleted(_parent); - } - - /** - * Report that the exchange is bound - */ - void reportExchangeBound() - { - if (_handler) _handler->onExchangeBound(_parent); - } - - /** - * Report that the exchange is unbound - */ - void reportExchangeUnbound() - { - if (_handler) _handler->onExchangeUnbound(_parent); - } - /** * Report that the queue was succesfully declared * @param queueName name of the queue which was declared @@ -410,51 +472,33 @@ public: */ void reportQueueDeclared(const std::string &queueName, uint32_t messageCount, uint32_t consumerCount) { - if (_handler) _handler->onQueueDeclared(_parent, queueName, messageCount, consumerCount); + // report success for the oldest queue declare callbacks + _queueDeclaredCallbacks.front().success(queueName, messageCount, consumerCount); + _queueDeclaredCallbacks.pop_front(); } - - /** - * Report that a queue was succesfully bound - */ - void reportQueueBound() - { - if (_handler) _handler->onQueueBound(_parent); - } - - /** - * Report that a queue was succesfully unbound - */ - void reportQueueUnbound() - { - if (_handler) _handler->onQueueUnbound(_parent); - } - + /** * Report that a queue was succesfully deleted * @param messageCount number of messages left in queue, now deleted */ void reportQueueDeleted(uint32_t messageCount) { - if (_handler) _handler->onQueueDeleted(_parent, messageCount); + // report success for the oldest queue remove callbacks + _queueRemovedCallbacks.front().success(messageCount); + _queueRemovedCallbacks.pop_front(); } - + /** * Report that a queue was succesfully purged * @param messageCount number of messages purged */ void reportQueuePurged(uint32_t messageCount) { - if (_handler) _handler->onQueuePurged(_parent, messageCount); + // report success for the oldest queue remove callbacks + _queueRemovedCallbacks.front().success(messageCount); + _queueRemovedCallbacks.pop_front(); } - - /** - * Report that the qos has been set - */ - void reportQosSet() - { - if (_handler) _handler->onQosSet(_parent); - } - + /** * Report that a consumer has started * @param tag the consumer tag @@ -472,20 +516,12 @@ public: { if (_handler) _handler->onConsumerStopped(_parent, tag); } - + /** * Report that a message was received */ void reportMessage(); - /** - * Report that the recover operation has started - */ - void reportRecovering() - { - if (_handler) _handler->onRecovering(_parent); - } - /** * Create an incoming message * @param frame @@ -493,7 +529,7 @@ public: */ MessageImpl *message(const BasicDeliverFrame &frame); MessageImpl *message(const BasicReturnFrame &frame); - + /** * Retrieve the current incoming message * @return MessageImpl @@ -502,12 +538,12 @@ public: { return _message; } - + /** * The channel class is its friend, thus can it instantiate this object */ friend class Channel; - + }; /** diff --git a/include/connection.h b/include/connection.h index a67e6ad..a5fed2b 100644 --- a/include/connection.h +++ b/include/connection.h @@ -22,6 +22,24 @@ private: */ ConnectionImpl _implementation; + /** + * Function to execute code after a certain timeout. + * + * If the timeout is 0, the code is supposed to be run + * in the next iteration of the event loop. + * + * This is a simple placeholder function that will just + * execute the code immediately, it should be overridden + * by the timeout function the used event loop has. + * + * @param timeout the amount of time to wait + * @param callback the callback to execute after the timeout + */ + std::function)> _timeoutHandler = [](double timeout, const std::function& callback) { + // execute callback immediately + callback(); + }; + public: /** * Construct an AMQP object based on full login data diff --git a/include/connectionimpl.h b/include/connectionimpl.h index 77adada..26f7f63 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -5,7 +5,7 @@ * This is the implementation of the connection - a class that can only be * constructed by the connection class itselves and that has all sorts of * methods that are only useful inside the library - * + * * @copyright 2014 Copernica BV */ @@ -44,13 +44,13 @@ protected: state_closing, // connection is busy closing (we have sent the close frame) state_closed // connection is closed } _state = state_protocol; - + /** * Has the close() method been called? * @var bool */ bool _closed = false; - + /** * All channels that are active * @var map @@ -62,13 +62,13 @@ protected: * @var uint16_t */ uint16_t _nextFreeChannel = 1; - + /** * Max number of channels (0 for unlimited) * @var uint16_t */ uint16_t _maxChannels = 0; - + /** * Max frame size * @var uint32_t @@ -80,19 +80,19 @@ protected: * @var Login */ Login _login; - + /** * Vhost to connect to * @var string */ std::string _vhost; - + /** * Queued messages that should be sent after the connection has been established * @var queue */ std::queue _queue; - + /** * Helper method to send the close frame * Return value tells if the connection is still valid @@ -100,17 +100,16 @@ protected: */ bool sendClose(); - private: /** * Construct an AMQP object based on full login data - * + * * The first parameter is a handler object. This handler class is * an interface that should be implemented by the caller. - * + * * Note that the constructor is private to ensure that nobody can construct * this class, only the real Connection class via a friend construct - * + * * @param parent Parent connection object * @param handler Connection handler * @param login Login data @@ -132,7 +131,7 @@ public: // must be busy doing the connection handshake, or already connected return _state == state_handshake || _state == state_connected; } - + /** * Mark the protocol as being ok */ @@ -141,7 +140,7 @@ public: // move on to handshake state if (_state == state_protocol) _state = state_handshake; } - + /** * Are we fully connected? * @return bool @@ -151,12 +150,12 @@ public: // state must be connected return _state == state_connected; } - + /** * Mark the connection as connected */ void setConnected(); - + /** * Retrieve the login data * @return Login @@ -165,7 +164,7 @@ public: { return _login; } - + /** * Retrieve the vhost * @return string @@ -185,7 +184,7 @@ public: _maxChannels = channels; _maxFrame = size; } - + /** * The max frame size * @return uint32_t @@ -194,7 +193,7 @@ public: { return _maxFrame; } - + /** * The max payload size for body frames * @return uint32_t @@ -204,7 +203,7 @@ public: // 8 bytes for header and end-of-frame byte return _maxFrame - 8; } - + /** * Add a channel to the connection, and return the channel ID that it * is allowed to use, or 0 when no more ID's are available @@ -212,16 +211,16 @@ public: * @return uint16_t */ uint16_t add(ChannelImpl *channel); - + /** * Remove a channel * @param channel */ void remove(ChannelImpl *channel); - + /** * Parse the buffer into a recognized frame - * + * * Every time that data comes in on the connection, you should call this method to parse * the incoming data, and let it handle by the AMQP library. This method returns the number * of bytes that were processed. @@ -246,9 +245,9 @@ public: /** * Send a frame over the connection - * + * * This is an internal method that you normally do not have to call yourself - * + * * @param frame the frame to send * @return bool */ @@ -256,11 +255,11 @@ public: /** * Get a channel by its identifier - * + * * This method only works if you had already created the channel before. * This is an internal method that you will not need if you cache the channel * object. - * + * * @param number channel identifier * @return channel the channel object, or nullptr if not yet created */ @@ -278,11 +277,11 @@ public: { // set connection state to closed _state = state_closed; - + // inform handler _handler->onError(_parent, message); } - + /** * Report that the connection is closed */ @@ -290,7 +289,7 @@ public: { // change state _state = state_closed; - + // inform the handler _handler->onClosed(_parent); } diff --git a/include/deferred.h b/include/deferred.h new file mode 100644 index 0000000..8e0e9a0 --- /dev/null +++ b/include/deferred.h @@ -0,0 +1,179 @@ +/** + * Deferred.h + * + * Class describing a set of actions that could + * possibly happen in the future that can be + * caught. + * + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace AMQP { + +// forward declaration +class ChannelImpl; + +/** + * Class definition + */ +template +class Deferred +{ +private: + /** + * The channel we operate under + */ + Channel *_channel; + + /** + * Do we already know we failed? + */ + bool _failed; + + /** + * Callback to execute on success + */ + std::function _successCallback; + + /** + * Callback to execute on failure + */ + std::function _errorCallback; + + /** + * Callback to execute either way + */ + std::function _finalizeCallback; + + /** + * The channel implementation may call our + * private members and construct us + */ + friend class ChannelImpl; + + /** + * Indicate success + * + * @param parameters... the extra parameters relevant for this deferred handler + */ + void success(Arguments ...parameters) + { + // execute callbacks if registered + if (_successCallback) _successCallback(_channel, parameters...); + if (_finalizeCallback) _finalizeCallback(_channel, ""); + } + + /** + * Indicate failure + * + * @param error description of the error that occured + */ + void error(const std::string& error) + { + // we are now in a failed state + _failed = true; + + // execute callbacks if registered + if (_errorCallback) _errorCallback(_channel, error); + if (_finalizeCallback) _finalizeCallback(_channel, error); + } + + /** + * Private constructor that can only be called + * from within the channel implementation + * + * @param channel the channel we operate under + * @param boolea are we already failed? + */ + Deferred(Channel *channel, bool failed = false) : + _channel(channel), + _failed(failed) + {} +public: + /** + * Deleted copy constructor + */ + Deferred(const Deferred& that) = delete; + + /** + * Move constructor + */ + Deferred(Deferred&& that) : + _successCallback(std::move(that._successCallback)), + _errorCallback(std::move(that._errorCallback)), + _finalizeCallback(std::move(that._finalizeCallback)) + {} + + /** + * Cast to a boolean + */ + operator bool () + { + return !_failed; + } + + /** + * Register a function to be called + * if and when the operation succesfully + * completes. + * + * Only one callback can be registered at a time. + * Successive calls to this function will clear + * callbacks registered before. + * + * @param callback the callback to execute + */ + Deferred& onSuccess(const std::function& callback) + { + // store callback + _successCallback = callback; + return *this; + } + + /** + * Register a function to be called + * if and when the operation fails. + * + * Only one callback can be registered at a time. + * Successive calls to this function will clear + * callbacks registered before. + * + * @param callback the callback to execute + */ + Deferred& onError(const std::function& callback) + { + // store callback + _errorCallback = callback; + return *this; + } + + /** + * Register a function to be called + * if and when the operation completes + * or fails. This function will be called + * either way. + * + * In the case of success, the provided + * error parameter will be an empty string. + * + * Only one callback can be registered at at time. + * Successive calls to this function will clear + * callbacks registered before. + * + * @param callback the callback to execute + */ + Deferred& onFinalize(const std::function& callback) + { + // store callback + _finalizeCallback = callback; + return *this; + } +}; + +/** + * End namespace + */ +} diff --git a/src/basicqosokframe.h b/src/basicqosokframe.h index 5d8bca1..dc6e612 100644 --- a/src/basicqosokframe.h +++ b/src/basicqosokframe.h @@ -1,6 +1,6 @@ /** * Class describing a basic QOS frame - * + * * @copyright 2014 Copernica BV */ @@ -32,7 +32,7 @@ public: * @param channel channel we're working on */ BasicQosOKFrame(uint16_t channel) : BasicFrame(channel, 0) {} - + /** * Constructor based on incoming data * @param frame @@ -52,7 +52,7 @@ public: { return 11; } - + /** * Process the frame * @param connection The connection over which it was received @@ -62,13 +62,13 @@ public: { // we need the appropriate channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist - if (!channel) return false; - + if (!channel) return false; + // report - channel->reportQosSet(); - + channel->reportSuccess(); + // done return true; } diff --git a/src/basicrecoverokframe.h b/src/basicrecoverokframe.h index 66004b1..1bcd771 100644 --- a/src/basicrecoverokframe.h +++ b/src/basicrecoverokframe.h @@ -1,6 +1,6 @@ /** * Class describing a basic recover-async frame - * + * * @copyright 2014 Copernica BV */ @@ -57,7 +57,7 @@ public: { return 111; } - + /** * Process the frame * @param connection The connection over which it was received @@ -67,17 +67,17 @@ public: { // we need the appropriate channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist - if (!channel) return false; - + if (!channel) return false; + // report - channel->reportRecovering(); - + channel->reportSuccess(); + // done return true; } - + }; diff --git a/src/channelflowokframe.h b/src/channelflowokframe.h index efc3ae6..fcd325f 100644 --- a/src/channelflowokframe.h +++ b/src/channelflowokframe.h @@ -88,14 +88,13 @@ public: { // we need the appropriate channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist - if (!channel) return false; - - // is the flow active? - if (active()) channel->reportResumed(); - else channel->reportPaused(); - + if (!channel) return false; + + // report success for the call + channel->reportSuccess(); + // done return true; } diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 28a3f09..c951f5e 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -55,13 +55,13 @@ ChannelImpl::ChannelImpl(Channel *parent, Connection *connection, ChannelHandler { // add the channel to the connection _id = _connection->add(this); - + // check if the id is valid if (_id == 0) { // this is invalid _state = state_closed; - + // invalid id, this channel can not exist handler->onError(_parent, "Max number of channels reached"); } @@ -69,7 +69,7 @@ ChannelImpl::ChannelImpl(Channel *parent, Connection *connection, ChannelHandler { // busy connecting _state = state_connected; - + // valid id, send a channel open frame send(ChannelOpenFrame(_id)); } @@ -80,106 +80,114 @@ ChannelImpl::ChannelImpl(Channel *parent, Connection *connection, ChannelHandler */ ChannelImpl::~ChannelImpl() { - // remove incoming message + // remove incoming message if (_message) delete _message; _message = nullptr; - + // remove this channel from the connection (but not if the connection is already destructed) if (_connection) _connection->remove(this); - + // close the channel now close(); } /** * Pause deliveries on a channel - * + * * This will stop all incoming messages - * - * This method returns true if the request to pause has been sent to the - * broker. This does not necessarily mean that the channel is already - * paused. - * - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::pause() +Deferred<>& ChannelImpl::pause() { - // send a flow frame - return send(ChannelFlowFrame(_id, false)); + // send a channel flow frame + return send(ChannelFlowFrame(_id, false), "Cannot send channel flow frame"); } /** * Resume a paused channel - * - * @return bool + * + * This will resume incoming messages + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::resume() +Deferred<>& ChannelImpl::resume() { - // send a flow frame - return send(ChannelFlowFrame(_id, true)); + // send a channel flow frame + return send(ChannelFlowFrame(_id, true), "Cannot send channel flow frame"); } /** * Start a transaction - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::startTransaction() +Deferred<>& ChannelImpl::startTransaction() { - // send a flow frame - return send(TransactionSelectFrame(_id)); -} + // send a transaction frame + return send(TransactionSelectFrame(_id), "Cannot send transaction start frame"); +} /** * Commit the current transaction - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::commitTransaction() +Deferred<>& ChannelImpl::commitTransaction() { - // send a flow frame - return send(TransactionCommitFrame(_id)); + // send a transaction frame + return send(TransactionCommitFrame(_id), "Cannot send transaction commit frame"); } /** * Rollback the current transaction - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::rollbackTransaction() +Deferred<>& ChannelImpl::rollbackTransaction() { - // send a flow frame - return send(TransactionRollbackFrame(_id)); + // send a transaction frame + return send(TransactionRollbackFrame(_id), "Cannot send transaction commit frame"); } /** * Close the current channel - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::close() +Deferred<>& ChannelImpl::close() { // channel could be dead after send operation, we need to monitor that Monitor monitor(this); - // send a flow frame - if (!send(ChannelCloseFrame(_id))) return false; + // send a channel close frame + auto &handler = send(ChannelCloseFrame(_id), "Cannot send channel close frame"); - // leap out if channel was destructed - if (!monitor.valid()) return true; - - // now it is closing - _state = state_closing; + // was the frame sent and are we still alive? + if (handler && monitor.valid()) _state = state_closing; // done - return true; + return handler; } /** * declare an exchange + * @param name name of the exchange to declare * @param type type of exchange * @param flags additional settings for the exchange * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) +Deferred<>& ChannelImpl::declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) { // convert exchange type std::string exchangeType; @@ -189,49 +197,58 @@ bool ChannelImpl::declareExchange(const std::string &name, ExchangeType type, in if (type == ExchangeType::headers)exchangeType = "headers"; // send declare exchange frame - return send(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments)); + return send(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments), "Cannot send exchange declare frame"); } /** * bind an exchange + * * @param source exchange which binds to target * @param target exchange to bind to * @param routingKey routing key * @param flags additional flags * @param arguments additional arguments for binding - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) +Deferred<>& ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { // send exchange bind frame - return send(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments)); + return send(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange bind frame"); } /** * unbind two exchanges + * * @param source the source exchange * @param target the target exchange * @param routingkey the routing key * @param flags optional flags * @param arguments additional unbind arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) +Deferred<>& ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { // send exchange unbind frame - return send(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments)); + return send(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange unbind frame"); } /** * remove an exchange + * * @param name name of the exchange to remove * @param flags additional settings for deleting the exchange - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::removeExchange(const std::string &name, int flags) +Deferred<>& ChannelImpl::removeExchange(const std::string &name, int flags) { // send delete exchange frame - return send(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait)); + return send(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait), "Cannot send exchange delete frame"); } /** @@ -239,75 +256,107 @@ bool ChannelImpl::removeExchange(const std::string &name, int flags) * @param name queue name * @param flags additional settings for the queue * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::declareQueue(const std::string &name, int flags, const Table &arguments) +Deferred& ChannelImpl::declareQueue(const std::string &name, int flags, const Table &arguments) { // send the queuedeclareframe - return send(QueueDeclareFrame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, flags & nowait, arguments)); + return send(QueueDeclareFrame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, flags & nowait, arguments), "Cannot send queue declare frame", _queueDeclaredCallbacks); } /** * Bind a queue to an exchange + * * @param exchangeName name of the exchange to bind to * @param queueName name of the queue * @param routingkey routingkey * @param flags additional flags * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments) +Deferred<>& ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments) { // send the bind queue frame - return send(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments)); + return send(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments), "Cannot send queue bind frame"); } /** * Unbind a queue from an exchange + * * @param exchange the source exchange * @param queue the target queue * @param routingkey the routing key * @param arguments additional bind arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) +Deferred<>& ChannelImpl::unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { // send the unbind queue frame - return send(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments)); + return send(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments), "Cannot send queue unbind frame"); } /** * Purge a queue * @param queue queue to purge * @param flags additional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, uint32_t messageCount); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { + * + * std::cout << "Queue purged, all " << messageCount << " messages removed" << std::endl; + * + * }); */ -bool ChannelImpl::purgeQueue(const std::string &name, int flags) +Deferred& ChannelImpl::purgeQueue(const std::string &name, int flags) { // send the queue purge frame - return send(QueuePurgeFrame(_id, name, flags & nowait)); + return send(QueuePurgeFrame(_id, name, flags & nowait), "Cannot send queue purge frame", _queueRemovedCallbacks); } /** * Remove a queue * @param queue queue to remove * @param flags additional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, uint32_t messageCount); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { + * + * std::cout << "Queue deleted, along with " << messageCount << " messages" << std::endl; + * + * }); */ -bool ChannelImpl::removeQueue(const std::string &name, int flags) +Deferred& ChannelImpl::removeQueue(const std::string &name, int flags) { // send the remove queue frame - return send(QueueDeleteFrame(_id, name, flags & ifunused, flags & ifempty, flags & nowait)); + return send(QueueDeleteFrame(_id, name, flags & ifunused, flags & ifempty, flags & nowait), "Cannot send remove queue frame", _queueRemovedCallbacks); } /** * Publish a message to an exchange - * + * * The following flags can be used - * + * * - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method * - immediate if set, a message that could not immediately be consumed is returned to the onReturned method - * + * * @param exchange the exchange to publish to * @param routingkey the routing key * @param flags optional flags (see above) @@ -320,46 +369,46 @@ bool ChannelImpl::publish(const std::string &exchange, const std::string &routin // we are going to send out multiple frames, each one will trigger a call to the handler, // which in turn could destruct the channel object, we need to monitor that Monitor monitor(this); - + // @todo do not copy the entire buffer to individual frames - + // send the publish frame if (!send(BasicPublishFrame(_id, exchange, routingKey, flags & mandatory, flags & immediate))) return false; - + // channel still valid? if (!monitor.valid()) return false; // send header if (!send(BasicHeaderFrame(_id, envelope))) return false; - + // channel and connection still valid? if (!monitor.valid() || !_connection) return false; - + // the max payload size is the max frame size minus the bytes for headers and trailer uint32_t maxpayload = _connection->maxPayload(); uint32_t bytessent = 0; - + // the buffer const char *data = envelope.body(); uint32_t bytesleft = envelope.bodySize(); - + // split up the body in multiple frames depending on the max frame size while (bytesleft > 0) { // size of this chunk uint32_t chunksize = std::min(maxpayload, bytesleft); - + // send out a body frame if (!send(BodyFrame(_id, data + bytessent, chunksize))) return false; - + // channel still valid? if (!monitor.valid()) return false; - + // update counters bytessent += chunksize; bytesleft -= chunksize; } - + // done return true; } @@ -367,12 +416,14 @@ bool ChannelImpl::publish(const std::string &exchange, const std::string &routin /** * Set the Quality of Service (QOS) for this channel * @param prefetchCount maximum number of messages to prefetch - * @return bool whether the Qos frame is sent. + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::setQos(uint16_t prefetchCount) +Deferred<>& ChannelImpl::setQos(uint16_t prefetchCount) { // send a qos frame - return send(BasicQosFrame(_id, prefetchCount, false)); + return send(BasicQosFrame(_id, prefetchCount, false), "Cannot send basic QOS frame"); } /** @@ -427,12 +478,14 @@ bool ChannelImpl::reject(uint64_t deliveryTag, int flags) /** * Recover un-acked messages * @param flags optional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. */ -bool ChannelImpl::recover(int flags) +Deferred<>& ChannelImpl::recover(int flags) { // send a nack frame - return send(BasicRecoverFrame(_id, flags & requeue)); + return send(BasicRecoverFrame(_id, flags & requeue), "Cannot send basic recover frame"); } /** @@ -444,11 +497,58 @@ bool ChannelImpl::send(const Frame &frame) { // skip if channel is not connected if (_state != state_connected || !_connection) return false; - + // send to tcp connection return _connection->send(frame); } +/** + * Send a frame over the channel and + * get a deferred handler for it. + * + * @param frame frame to send + * @param message the message to trigger if the frame cannot be send at all + */ +Deferred<>& ChannelImpl::send(const Frame &frame, const char *message) +{ + // use the generic implementation + return send<>(frame, message, _callbacks); +} + +/** + * Send a frame over the channel and + * get a deferred handler for it. + * + * @param frame frame to send + * @param message the message to trigger if the frame cannot be send at all + * @param queue the queue to store the callbacks in + */ +template +Deferred& ChannelImpl::send(const Frame &frame, const char *message, std::deque>& queue) +{ + // create a new deferred handler and get a pointer to it + queue.push_back(Deferred(_parent)); + auto *handler = &queue.back(); + + // send the frame over the channel + if (!send(frame)) + { + // we can immediately put the handler in failed state + handler->_failed = true; + + // the frame could not be send + // we should register an error + // on the handler, but only after + // a timeout, so a handler can + // be attached first + + // TODO + } + + // return the new handler + return *handler; +} + /** * Report the received message */ @@ -456,18 +556,18 @@ void ChannelImpl::reportMessage() { // skip if there is no message if (!_message) return; - + // after the report the channel may be destructed, monitor that Monitor monitor(this); - + // do we have a handler? if (_handler) _message->report(_parent, _handler); - + // skip if channel was destructed if (!monitor.valid()) return; - + // no longer need the message - delete _message; + delete _message; _message = nullptr; } @@ -480,7 +580,7 @@ MessageImpl *ChannelImpl::message(const BasicDeliverFrame &frame) { // it should not be possible that a message already exists, but lets check it anyhow if (_message) delete _message; - + // construct a message return _message = new ConsumedMessage(frame); } @@ -494,7 +594,7 @@ MessageImpl *ChannelImpl::message(const BasicReturnFrame &frame) { // it should not be possible that a message already exists, but lets check it anyhow if (_message) delete _message; - + // construct a message return _message = new ReturnedMessage(frame); } diff --git a/src/exchangebindokframe.h b/src/exchangebindokframe.h index ad3c54c..5b1f29c 100644 --- a/src/exchangebindokframe.h +++ b/src/exchangebindokframe.h @@ -1,6 +1,6 @@ /** * Exchangebindokframe.h - * + * * @copyright 2014 Copernica BV */ @@ -8,7 +8,7 @@ * Set up namespace */ namespace AMQP { - + /** * Class definition */ @@ -29,10 +29,10 @@ protected: public: /** * Constructor based on incoming data - * + * * @param frame received frame to decode */ - ExchangeBindOKFrame(ReceivedFrame &frame) : + ExchangeBindOKFrame(ReceivedFrame &frame) : ExchangeFrame(frame) {} @@ -47,12 +47,12 @@ public: ExchangeBindOKFrame(uint16_t channel) : ExchangeFrame(channel, 0) {} - + virtual uint16_t methodID() const override { return 31; } - + /** * Process the frame * @param connection The connection over which it was received @@ -62,17 +62,17 @@ public: { // check if we have a channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist if(!channel) return false; // report to handler - channel->reportExchangeBound(); - + channel->reportSuccess(); + // done return true; } }; - + // end namespace } diff --git a/src/exchangedeclareokframe.h b/src/exchangedeclareokframe.h index b271601..f86bf3e 100644 --- a/src/exchangedeclareokframe.h +++ b/src/exchangedeclareokframe.h @@ -1,6 +1,6 @@ /** * Class describing an AMQP exchange declare ok frame - * + * * @copyright 2014 Copernica BV */ @@ -55,7 +55,7 @@ public: { return 11; } - + /** * Process the frame * @param connection The connection over which it was received @@ -65,13 +65,13 @@ public: { // we need the appropriate channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist if(!channel) return false; - + // report exchange declare ok - channel->reportExchangeDeclared(); - + channel->reportSuccess(); + // done return true; } diff --git a/src/exchangedeleteokframe.h b/src/exchangedeleteokframe.h index c386358..ce44336 100644 --- a/src/exchangedeleteokframe.h +++ b/src/exchangedeleteokframe.h @@ -1,6 +1,6 @@ /** * Class describing an AMQP exchange delete ok frame - * + * * @copyright 2014 Copernica BV */ @@ -32,7 +32,7 @@ public: * * @param frame received frame */ - ExchangeDeleteOKFrame(ReceivedFrame &frame) : + ExchangeDeleteOKFrame(ReceivedFrame &frame) : ExchangeFrame(frame) {} @@ -56,7 +56,7 @@ public: { return 21; } - + /** * Process the frame * @param connection The connection over which it was received @@ -66,13 +66,13 @@ public: { // check if we have a channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist if(!channel) return false; - + // report to handler - channel->reportExchangeDeleted(); - + channel->reportSuccess(); + // done return true; } diff --git a/src/exchangeunbindokframe.h b/src/exchangeunbindokframe.h index f3a2a9b..ea256ab 100644 --- a/src/exchangeunbindokframe.h +++ b/src/exchangeunbindokframe.h @@ -1,5 +1,5 @@ /** - * Exchangeunbindokframe.h + * Exchangeunbindokframe.h * * @copyright 2014 Copernica BV */ @@ -9,7 +9,7 @@ * Set up namespace */ namespace AMQP { - + /** * Class definition */ @@ -30,10 +30,10 @@ protected: public: /** * Constructor based on incoming data - * + * * @param frame received frame to decode */ - ExchangeUnbindOKFrame(ReceivedFrame &frame) : + ExchangeUnbindOKFrame(ReceivedFrame &frame) : ExchangeFrame(frame) {} @@ -48,12 +48,12 @@ public: ExchangeUnbindOKFrame(uint16_t channel) : ExchangeFrame(channel, 0) {} - + virtual uint16_t methodID() const override { return 51; } - + /** * Process the frame * @param connection The connection over which it was received @@ -63,17 +63,17 @@ public: { // check if we have a channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist if(!channel) return false; // report to handler - channel->reportExchangeUnbound(); - + channel->reportSuccess(); + // done return true; } }; - + // end namespace } diff --git a/src/methodframe.h b/src/methodframe.h index 749e088..8b847fd 100644 --- a/src/methodframe.h +++ b/src/methodframe.h @@ -75,13 +75,10 @@ public: * @param connection The connection over which it was received * @return bool Was it succesfully processed? */ - virtual bool process(ConnectionImpl *connection) override + virtual bool process[[ noreturn ]](ConnectionImpl *connection) override { // this is an exception throw ProtocolException("unimplemented frame type " + std::to_string(type()) + " class " + std::to_string(classID()) + " method " + std::to_string(methodID())); - - // unreachable - return false; } }; diff --git a/src/queuebindokframe.h b/src/queuebindokframe.h index 6c66cb2..1d55a33 100644 --- a/src/queuebindokframe.h +++ b/src/queuebindokframe.h @@ -1,6 +1,6 @@ /** * Class describing an AMQP queue bind ok frame - * + * * @copyright 2014 Copernica BV */ @@ -54,7 +54,7 @@ public: { return 21; } - + /** * Process the frame * @param connection The connection over which it was received @@ -64,13 +64,13 @@ public: { // check if we have a channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist if(!channel) return false; - + // report to handler - channel->reportQueueBound(); - + channel->reportSuccess(); + // done return true; } diff --git a/src/queueunbindokframe.h b/src/queueunbindokframe.h index cd779f0..14a2761 100644 --- a/src/queueunbindokframe.h +++ b/src/queueunbindokframe.h @@ -1,6 +1,6 @@ /** * Class describing an AMQP queue unbind ok frame - * + * * @copyright 2014 Copernica BV */ @@ -24,7 +24,7 @@ protected: { // call base QueueFrame::fill(buffer); - } + } public: /** * Decode a queueunbindokframe from a received frame @@ -58,7 +58,7 @@ public: { return 51; } - + /** * Process the frame * @param connection The connection over which it was received @@ -68,13 +68,13 @@ public: { // check if we have a channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist if(!channel) return false; - + // report queue unbind success - channel->reportQueueUnbound(); - + channel->reportSuccess(); + // done return true; } diff --git a/src/transactioncommitokframe.h b/src/transactioncommitokframe.h index a10e1fe..27f7004 100644 --- a/src/transactioncommitokframe.h +++ b/src/transactioncommitokframe.h @@ -59,6 +59,26 @@ public: { return 21; } + + /** + * Process the frame + * @param connection The connection over which it was received + * @return bool Was it succesfully processed? + */ + virtual bool process(ConnectionImpl *connection) override + { + // we need the appropriate channel + ChannelImpl *channel = connection->channel(this->channel()); + + // channel does not exist + if(!channel) return false; + + // report that the channel is open + channel->reportSuccess(); + + // done + return true; + } }; /** diff --git a/src/transactionrollbackokframe.h b/src/transactionrollbackokframe.h index a203e4c..e87d893 100644 --- a/src/transactionrollbackokframe.h +++ b/src/transactionrollbackokframe.h @@ -58,7 +58,27 @@ public: virtual uint16_t methodID() const override { return 31; - } + } + + /** + * Process the frame + * @param connection The connection over which it was received + * @return bool Was it succesfully processed? + */ + virtual bool process(ConnectionImpl *connection) override + { + // we need the appropriate channel + ChannelImpl *channel = connection->channel(this->channel()); + + // channel does not exist + if(!channel) return false; + + // report that the channel is open + channel->reportSuccess(); + + // done + return true; + } }; /** diff --git a/src/transactionselectokframe.h b/src/transactionselectokframe.h index 9529144..8a842f7 100644 --- a/src/transactionselectokframe.h +++ b/src/transactionselectokframe.h @@ -59,6 +59,26 @@ public: { return 11; } + + /** + * Process the frame + * @param connection The connection over which it was received + * @return bool Was it succesfully processed? + */ + virtual bool process(ConnectionImpl *connection) override + { + // we need the appropriate channel + ChannelImpl *channel = connection->channel(this->channel()); + + // channel does not exist + if(!channel) return false; + + // report that the channel is open + channel->reportSuccess(); + + // done + return true; + } }; /** From e1b0e3dea1909142ed817c7c2fd68a6451672e9a Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Tue, 8 Apr 2014 16:12:04 +0200 Subject: [PATCH 07/41] Added a generic callback class that acts as a container for the different types of callbacks --- amqpcpp.h | 1 + include/callbacks.h | 10 ++--- include/channelimpl.h | 83 +++++++-------------------------------- include/deferred.h | 2 + src/channelimpl.cpp | 56 ++++++++++---------------- src/queuedeclareokframe.h | 22 +++++------ src/queuedeleteokframe.h | 14 +++---- src/queuepurgeokframe.h | 12 +++--- 8 files changed, 67 insertions(+), 133 deletions(-) diff --git a/amqpcpp.h b/amqpcpp.h index af79811..430844c 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -17,6 +17,7 @@ #include #include #include +#include // base C include files #include diff --git a/include/callbacks.h b/include/callbacks.h index b29bb38..3ed7601 100644 --- a/include/callbacks.h +++ b/include/callbacks.h @@ -90,13 +90,13 @@ public: * @return reference to the inserted deferred */ template - Deferred& push_back(const Deferred& item) + Deferred& push_back(Deferred&& item) { // retrieve the container auto &container = get>>(_callbacks); // add the element - container.push_back(item); + container.push_back(std::move(item)); // return reference to the new item return container.back(); @@ -128,7 +128,7 @@ public: */ template typename std::enable_if::value>::type - reportFailure(const std::string& message) + reportError(const std::string& message) {} /** @@ -138,7 +138,7 @@ public: */ template typename std::enable_if::value>::type - reportFailure(const std::string& message) + reportError(const std::string& message) { // retrieve the callbacks at current index auto &callbacks = std::get(_callbacks); @@ -147,7 +147,7 @@ public: for (auto &callback : callbacks) callback.error(message); // execute the next type - reportFailure(message); + reportError(message); } }; diff --git a/include/channelimpl.h b/include/channelimpl.h index 6651e9b..cf75dfb 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -51,13 +51,7 @@ private: /** * The callbacks waiting to be called */ - std::deque> _callbacks; - - /** - * Callbacks with additional parameters - */ - std::deque> _queueDeclaredCallbacks; - std::deque> _queueRemovedCallbacks; + Callbacks _callbacks; /** * The channel number @@ -399,18 +393,17 @@ public: * @param frame frame to send * @param message the message to trigger if the frame cannot be send at all */ - Deferred<>& send(const Frame &frame, const char *message); + template + Deferred& send(const Frame &frame, const char *message); /** - * Send a frame over the channel and - * get a deferred handler for it. - * - * @param frame frame to send - * @param message the message to trigger if the frame cannot be send at all - * @param queue the queue to store the callbacks in + * Report to the handler that the channel is opened */ - template - Deferred& send(const Frame &frame, const char *message, std::deque>& queue); + void reportReady() + { + // inform handler + if (_readyCallback) _readyCallback(_parent); + } /** * Report to the handler that the channel is closed @@ -430,20 +423,11 @@ public: * This function is called to report success for all * cases where the callback does not receive any parameters */ - void reportSuccess() + template + void reportSuccess(Arguments ...parameters) { - // report success for the oldest request - _callbacks.front().success(); - _callbacks.pop_front(); - } - - /** - * Report to the handler that the channel is opened - */ - void reportReady() - { - // inform handler - if (_readyCallback) _readyCallback(_parent); + // report success to the relevant callback + _callbacks.reportSuccess(std::forward(parameters)...); } /** @@ -458,45 +442,8 @@ public: // inform handler if (_errorCallback) _errorCallback(_parent, message); - // and all waiting deferred callbacks - for (auto &deferred : _callbacks) deferred.error(message); - for (auto &deferred : _queueDeclaredCallbacks) deferred.error(message); - for (auto &deferred : _queueRemovedCallbacks) deferred.error(message); - } - - /** - * Report that the queue was succesfully declared - * @param queueName name of the queue which was declared - * @param messagecount number of messages currently in the queue - * @param consumerCount number of active consumers in the queue - */ - void reportQueueDeclared(const std::string &queueName, uint32_t messageCount, uint32_t consumerCount) - { - // report success for the oldest queue declare callbacks - _queueDeclaredCallbacks.front().success(queueName, messageCount, consumerCount); - _queueDeclaredCallbacks.pop_front(); - } - - /** - * Report that a queue was succesfully deleted - * @param messageCount number of messages left in queue, now deleted - */ - void reportQueueDeleted(uint32_t messageCount) - { - // report success for the oldest queue remove callbacks - _queueRemovedCallbacks.front().success(messageCount); - _queueRemovedCallbacks.pop_front(); - } - - /** - * Report that a queue was succesfully purged - * @param messageCount number of messages purged - */ - void reportQueuePurged(uint32_t messageCount) - { - // report success for the oldest queue remove callbacks - _queueRemovedCallbacks.front().success(messageCount); - _queueRemovedCallbacks.pop_front(); + // report to all waiting callbacks too + _callbacks.reportError(message); } /** diff --git a/include/deferred.h b/include/deferred.h index 8e0e9a0..2d99187 100644 --- a/include/deferred.h +++ b/include/deferred.h @@ -15,6 +15,7 @@ namespace AMQP { // forward declaration class ChannelImpl; +class Callbacks; /** * Class definition @@ -53,6 +54,7 @@ private: * private members and construct us */ friend class ChannelImpl; + friend class Callbacks; /** * Indicate success diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index c951f5e..10ea529 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -102,7 +102,7 @@ ChannelImpl::~ChannelImpl() Deferred<>& ChannelImpl::pause() { // send a channel flow frame - return send(ChannelFlowFrame(_id, false), "Cannot send channel flow frame"); + return send<>(ChannelFlowFrame(_id, false), "Cannot send channel flow frame"); } /** @@ -116,7 +116,7 @@ Deferred<>& ChannelImpl::pause() Deferred<>& ChannelImpl::resume() { // send a channel flow frame - return send(ChannelFlowFrame(_id, true), "Cannot send channel flow frame"); + return send<>(ChannelFlowFrame(_id, true), "Cannot send channel flow frame"); } /** @@ -128,7 +128,7 @@ Deferred<>& ChannelImpl::resume() Deferred<>& ChannelImpl::startTransaction() { // send a transaction frame - return send(TransactionSelectFrame(_id), "Cannot send transaction start frame"); + return send<>(TransactionSelectFrame(_id), "Cannot send transaction start frame"); } /** @@ -140,7 +140,7 @@ Deferred<>& ChannelImpl::startTransaction() Deferred<>& ChannelImpl::commitTransaction() { // send a transaction frame - return send(TransactionCommitFrame(_id), "Cannot send transaction commit frame"); + return send<>(TransactionCommitFrame(_id), "Cannot send transaction commit frame"); } /** @@ -152,7 +152,7 @@ Deferred<>& ChannelImpl::commitTransaction() Deferred<>& ChannelImpl::rollbackTransaction() { // send a transaction frame - return send(TransactionRollbackFrame(_id), "Cannot send transaction commit frame"); + return send<>(TransactionRollbackFrame(_id), "Cannot send transaction commit frame"); } /** @@ -167,7 +167,7 @@ Deferred<>& ChannelImpl::close() Monitor monitor(this); // send a channel close frame - auto &handler = send(ChannelCloseFrame(_id), "Cannot send channel close frame"); + auto &handler = send<>(ChannelCloseFrame(_id), "Cannot send channel close frame"); // was the frame sent and are we still alive? if (handler && monitor.valid()) _state = state_closing; @@ -197,7 +197,7 @@ Deferred<>& ChannelImpl::declareExchange(const std::string &name, ExchangeType t if (type == ExchangeType::headers)exchangeType = "headers"; // send declare exchange frame - return send(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments), "Cannot send exchange declare frame"); + return send<>(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments), "Cannot send exchange declare frame"); } /** @@ -215,7 +215,7 @@ Deferred<>& ChannelImpl::declareExchange(const std::string &name, ExchangeType t Deferred<>& ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { // send exchange bind frame - return send(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange bind frame"); + return send<>(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange bind frame"); } /** @@ -233,7 +233,7 @@ Deferred<>& ChannelImpl::bindExchange(const std::string &source, const std::stri Deferred<>& ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { // send exchange unbind frame - return send(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange unbind frame"); + return send<>(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange unbind frame"); } /** @@ -248,7 +248,7 @@ Deferred<>& ChannelImpl::unbindExchange(const std::string &source, const std::st Deferred<>& ChannelImpl::removeExchange(const std::string &name, int flags) { // send delete exchange frame - return send(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait), "Cannot send exchange delete frame"); + return send<>(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait), "Cannot send exchange delete frame"); } /** @@ -263,7 +263,7 @@ Deferred<>& ChannelImpl::removeExchange(const std::string &name, int flags) Deferred& ChannelImpl::declareQueue(const std::string &name, int flags, const Table &arguments) { // send the queuedeclareframe - return send(QueueDeclareFrame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, flags & nowait, arguments), "Cannot send queue declare frame", _queueDeclaredCallbacks); + return send(QueueDeclareFrame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, flags & nowait, arguments), "Cannot send queue declare frame"); } /** @@ -281,7 +281,7 @@ Deferred& ChannelImpl::declareQueue(cons Deferred<>& ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments) { // send the bind queue frame - return send(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments), "Cannot send queue bind frame"); + return send<>(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments), "Cannot send queue bind frame"); } /** @@ -298,7 +298,7 @@ Deferred<>& ChannelImpl::bindQueue(const std::string &exchangeName, const std::s Deferred<>& ChannelImpl::unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { // send the unbind queue frame - return send(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments), "Cannot send queue unbind frame"); + return send<>(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments), "Cannot send queue unbind frame"); } /** @@ -322,7 +322,7 @@ Deferred<>& ChannelImpl::unbindQueue(const std::string &exchange, const std::str Deferred& ChannelImpl::purgeQueue(const std::string &name, int flags) { // send the queue purge frame - return send(QueuePurgeFrame(_id, name, flags & nowait), "Cannot send queue purge frame", _queueRemovedCallbacks); + return send(QueuePurgeFrame(_id, name, flags & nowait), "Cannot send queue purge frame"); } /** @@ -346,7 +346,7 @@ Deferred& ChannelImpl::purgeQueue(const std::string &name, int flags) Deferred& ChannelImpl::removeQueue(const std::string &name, int flags) { // send the remove queue frame - return send(QueueDeleteFrame(_id, name, flags & ifunused, flags & ifempty, flags & nowait), "Cannot send remove queue frame", _queueRemovedCallbacks); + return send(QueueDeleteFrame(_id, name, flags & ifunused, flags & ifempty, flags & nowait), "Cannot send remove queue frame"); } /** @@ -503,38 +503,22 @@ bool ChannelImpl::send(const Frame &frame) } /** - * Send a frame over the channel and - * get a deferred handler for it. + * Send a frame over the channel and get a deferred handler for it. * * @param frame frame to send * @param message the message to trigger if the frame cannot be send at all */ -Deferred<>& ChannelImpl::send(const Frame &frame, const char *message) -{ - // use the generic implementation - return send<>(frame, message, _callbacks); -} - -/** - * Send a frame over the channel and - * get a deferred handler for it. - * - * @param frame frame to send - * @param message the message to trigger if the frame cannot be send at all - * @param queue the queue to store the callbacks in - */ template -Deferred& ChannelImpl::send(const Frame &frame, const char *message, std::deque>& queue) +Deferred& ChannelImpl::send(const Frame &frame, const char *message) { // create a new deferred handler and get a pointer to it - queue.push_back(Deferred(_parent)); - auto *handler = &queue.back(); + auto &handler = _callbacks.push_back(Deferred(_parent)); // send the frame over the channel if (!send(frame)) { // we can immediately put the handler in failed state - handler->_failed = true; + handler._failed = true; // the frame could not be send // we should register an error @@ -546,7 +530,7 @@ Deferred& ChannelImpl::send(const Frame &frame, const char *messag } // return the new handler - return *handler; + return handler; } /** diff --git a/src/queuedeclareokframe.h b/src/queuedeclareokframe.h index 3cbc113..875ff2b 100644 --- a/src/queuedeclareokframe.h +++ b/src/queuedeclareokframe.h @@ -1,6 +1,6 @@ /** * Class describing an AMQP queue declare ok frame - * + * * @copyright 2014 Copernica BV */ @@ -70,7 +70,7 @@ public: /** * Constructor based on incoming data - * @param frame received frame + * @param frame received frame */ QueueDeclareOKFrame(ReceivedFrame &frame) : QueueFrame(frame), @@ -91,7 +91,7 @@ public: { return 11; } - + /** * Queue name * @return string @@ -105,7 +105,7 @@ public: * Number of messages * @return int32_t */ - int32_t messageCount() const + uint32_t messageCount() const { return _messageCount; } @@ -114,11 +114,11 @@ public: * Number of consumers * @return int32_t */ - int32_t consumerCount() const + uint32_t consumerCount() const { return _consumerCount; } - + /** * Process the frame * @param connection The connection over which it was received @@ -128,13 +128,13 @@ public: { // check if we have a channel ChannelImpl *channel = connection->channel(this->channel()); - + // what if channel doesn't exist? if (!channel) return false; - - // report to the handler - channel->reportQueueDeclared(this->name(), this->messageCount(), this->consumerCount()); - + + // report to the handler, we need to specify template arguments otherwise a string will lose const and reference + channel->reportSuccess(this->name(), this->messageCount(), this->consumerCount()); + // done return true; } diff --git a/src/queuedeleteokframe.h b/src/queuedeleteokframe.h index 881ed2b..39cd9b5 100644 --- a/src/queuedeleteokframe.h +++ b/src/queuedeleteokframe.h @@ -1,6 +1,6 @@ /** * Class describing an AMQP queue delete frame - * + * * @copyright 2014 Copernica BV */ @@ -79,7 +79,7 @@ public: { return _messageCount; } - + /** * Process the frame * @param connection The connection over which it was received @@ -89,13 +89,13 @@ public: { // check if we have a channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist if(!channel) return false; - + // report queue deletion success - channel->reportQueueDeleted(this->messageCount()); - + channel->reportSuccess(this->messageCount()); + // done return true; } @@ -105,4 +105,4 @@ public: * end namespace */ } - + diff --git a/src/queuepurgeokframe.h b/src/queuepurgeokframe.h index 715ef00..166cb60 100644 --- a/src/queuepurgeokframe.h +++ b/src/queuepurgeokframe.h @@ -1,6 +1,6 @@ /** * Class describing an AMQP queue purge frame - * + * * @copyright 2014 Copernica BV */ @@ -35,7 +35,7 @@ protected: // add fields buffer.add(_messageCount); } - + public: /** * Construct a queuepurgeokframe @@ -79,7 +79,7 @@ public: { return _messageCount; } - + /** * Process the frame * @param connection The connection over which it was received @@ -89,13 +89,13 @@ public: { // check if we have a channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist if(!channel) return false; // report queue purge success - channel->reportQueuePurged(this->messageCount()); - + channel->reportSuccess(this->messageCount()); + // done return true; } From d2c17869e0b644ac9e9955aeff72c39bca3de19d Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Thu, 10 Apr 2014 12:51:04 +0200 Subject: [PATCH 08/41] Moved the remaining methods over to deferred handlers --- amqpcpp.h | 2 +- include/callbacks.h | 3 +- include/channel.h | 82 ++++--- include/channelhandler.h | 87 -------- include/channelimpl.h | 97 +++++---- include/deferred.h | 38 ++-- src/basiccancelokframe.h | 14 +- src/basicconsumeokframe.h | 12 +- src/basicreturnframe.h | 13 +- src/channelimpl.cpp | 105 +++++---- src/consumedmessage.h | 19 +- src/messageimpl.h | 29 ++- src/returnedmessage.h | 14 +- tests/main.cpp | 4 +- tests/myconnection.cpp | 448 ++++++++++++-------------------------- tests/myconnection.h | 195 ++--------------- 16 files changed, 388 insertions(+), 774 deletions(-) delete mode 100644 include/channelhandler.h diff --git a/amqpcpp.h b/amqpcpp.h index 430844c..908da12 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -51,8 +51,8 @@ #include #include #include +#include #include -#include #include #include #include diff --git a/include/callbacks.h b/include/callbacks.h index 3ed7601..9d378df 100644 --- a/include/callbacks.h +++ b/include/callbacks.h @@ -23,7 +23,8 @@ private: std::tuple< std::deque>, std::deque>, - std::deque> + std::deque>, + std::deque> > _callbacks; /** diff --git a/include/channel.h b/include/channel.h index 8a2550a..e5df3d3 100644 --- a/include/channel.h +++ b/include/channel.h @@ -26,9 +26,8 @@ public: /** * Construct a channel object * @param connection - * @param handler */ - Channel(Connection *connection, ChannelHandler *handler) : _implementation(this, connection, handler) {} + Channel(Connection *connection) : _implementation(this, connection) {} /** * Destructor @@ -324,7 +323,7 @@ public: * * void myCallback(AMQP::Channel *channel, uint32_t messageCount); * - * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { + * For example: channel.removeQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { * * std::cout << "Queue deleted, along with " << messageCount << " messages" << std::endl; * @@ -335,29 +334,20 @@ public: /** * Publish a message to an exchange * - * The following flags can be used - * - * - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method - * - immediate if set, a message that could not immediately be consumed is returned to the onReturned method - * * If either of the two flags is set, and the message could not immediately * be published, the message is returned by the server to the client. If you - * want to catch such returned messages, you need to implement the - * ChannelHandler::onReturned() method. + * want to catch such returned messages, you need to install a handler using + * the onReturned() method. * * @param exchange the exchange to publish to * @param routingkey the routing key - * @param flags optional flags (see above) * @param envelope the full envelope to send * @param message the message to send * @param size size of the message */ - bool publish(const std::string &exchange, const std::string &routingKey, int flags, const Envelope &envelope) { return _implementation.publish(exchange, routingKey, flags, envelope); } - bool publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) { return _implementation.publish(exchange, routingKey, 0, envelope); } - bool publish(const std::string &exchange, const std::string &routingKey, int flags, const std::string &message) { return _implementation.publish(exchange, routingKey, flags, Envelope(message)); } - bool publish(const std::string &exchange, const std::string &routingKey, const std::string &message) { return _implementation.publish(exchange, routingKey, 0, Envelope(message)); } - bool publish(const std::string &exchange, const std::string &routingKey, int flags, const char *message, size_t size) { return _implementation.publish(exchange, routingKey, flags, Envelope(message, size)); } - bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size) { return _implementation.publish(exchange, routingKey, 0, Envelope(message, size)); } + bool publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) { return _implementation.publish(exchange, routingKey, envelope); } + bool publish(const std::string &exchange, const std::string &routingKey, const std::string &message) { return _implementation.publish(exchange, routingKey, Envelope(message)); } + bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size) { return _implementation.publish(exchange, routingKey, Envelope(message, size)); } /** * Set the Quality of Service (QOS) for this channel @@ -392,7 +382,7 @@ public: * - exclusive request exclusive access, only this consumer can access the queue * - nowait the server does not have to send a response back that consuming is active * - * The method ChannelHandler::onConsumerStarted() will be called when the + * The method Deferred::onSuccess() will be called when the * consumer has started (unless the nowait option was set, in which case * no confirmation method is called) * @@ -400,14 +390,26 @@ public: * @param tag a consumer tag that will be associated with this consume operation * @param flags additional flags * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, const std::string& tag); + * + * For example: channel.consume("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) { + * + * std::cout << "Started consuming under tag " << tag << std::endl; + * + * }); */ - bool consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { return _implementation.consume(queue, tag, flags, arguments); } - bool consume(const std::string &queue, const std::string &tag, int flags = 0) { return _implementation.consume(queue, tag, flags, Table()); } - bool consume(const std::string &queue, const std::string &tag, const Table &arguments) { return _implementation.consume(queue, tag, 0, arguments); } - bool consume(const std::string &queue, int flags, const Table &arguments) { return _implementation.consume(queue, std::string(), flags, arguments); } - bool consume(const std::string &queue, int flags = 0) { return _implementation.consume(queue, std::string(), flags, Table()); } - bool consume(const std::string &queue, const Table &arguments) { return _implementation.consume(queue, std::string(), 0, arguments); } + DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { return _implementation.consume(queue, tag, flags, arguments); } + DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags = 0) { return _implementation.consume(queue, tag, flags, Table()); } + DeferredConsumer& consume(const std::string &queue, const std::string &tag, const Table &arguments) { return _implementation.consume(queue, tag, 0, arguments); } + DeferredConsumer& consume(const std::string &queue, int flags, const Table &arguments) { return _implementation.consume(queue, std::string(), flags, arguments); } + DeferredConsumer& consume(const std::string &queue, int flags = 0) { return _implementation.consume(queue, std::string(), flags, Table()); } + DeferredConsumer& consume(const std::string &queue, const Table &arguments) { return _implementation.consume(queue, std::string(), 0, arguments); } /** * Cancel a running consume call @@ -418,27 +420,39 @@ public: * * - nowait the server does not have to send a response back that the consumer has been cancelled * - * The method ChannelHandler::onConsumerStopped() will be called when the consumer + * The method Deferred::onSuccess() will be called when the consumer * was succesfully stopped (unless the nowait option was used, in which case no * confirmation method is called) * * @param tag the consumer tag * @param flags optional additional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, const std::string& tag); + * + * For example: channel.cancel("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) { + * + * std::cout << "Stopped consuming under tag " << tag << std::endl; + * + * }); */ - bool cancel(const std::string &tag, int flags = 0) { return _implementation.cancel(tag, flags); } + Deferred& cancel(const std::string &tag, int flags = 0) { return _implementation.cancel(tag, flags); } /** * Acknoldge a received message * - * When a message is received in the ChannelHandler::onReceived() method, - * you must acknoledge it so that RabbitMQ removes it from the queue (unless + * When a message is received in the DeferredConsumer::onReceived() method, + * you must acknowledge it so that RabbitMQ removes it from the queue (unless * you are consuming with the noack option). This method can be used for - * this acknoledging. + * this acknowledging. * * The following flags are supported: * - * - multiple acknoledge multiple messages: all un-acked messages that were earlier delivered are acknowledged too + * - multiple acknowledge multiple messages: all un-acked messages that were earlier delivered are acknowledged too * * @param deliveryTag the unique delivery tag of the message * @param flags optional flags @@ -449,8 +463,8 @@ public: /** * Reject or nack a message * - * When a message was received in the ChannelHandler::onReceived() method, - * and you don't want to acknoledge it, you can also choose to reject it by + * When a message was received in the DeferredConsumer::onReceived() method, + * and you don't want to acknowledge it, you can also choose to reject it by * calling this reject method. * * The following flags are supported: diff --git a/include/channelhandler.h b/include/channelhandler.h deleted file mode 100644 index 2ad468f..0000000 --- a/include/channelhandler.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once -/** - * ChannelHandler.h - * - * Interface that should be implemented by a user of the AMQP library, - * and that is passed to the Connection::createChannel() method. - * - * This interface contains a number of methods that are called when - * the channel changes state. - * - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace AMQP { - -/** - * Class definition - */ -class ChannelHandler -{ -public: - /** - * An error has occured on the channel - * The channel is no longer usable after an error has occured on it. - * @param channel the channel on which the error occured - * @param message human readable error message - */ - virtual void onError(Channel *channel, const std::string &message) {} - - /** - * Method that is called when a queue is purged - * This is the result of a call to Channel::purgeQueue() - * @param channel the channel on which the queue was emptied - * @param messageCount number of message purged - */ - virtual void onQueuePurged(Channel *channel, uint32_t messageCount) {} - - /** - * Method that is called when a consumer was started - * This is the result of a call to Channel::consume() - * @param channel the channel on which the consumer was started - * @param tag the consumer tag - */ - virtual void onConsumerStarted(Channel *channel, const std::string &tag) {} - - /** - * Method that is called when a consumer was stopped - * This is the result of a call to Channel::cancel() - * @param channel the channel on which the consumer was stopped - * @param tag the consumer tag - */ - virtual void onConsumerStopped(Channel *channel, const std::string &tag) {} - - /** - * Method that is called when a message has been received on a channel - * This message will be called for every message that is received after - * you started consuming. Make sure you acknowledge the messages when its - * safe to remove them from RabbitMQ (unless you set no-ack option when you - * started the consumer) - * @param channel the channel on which the consumer was started - * @param message the consumed message - * @param deliveryTag the delivery tag, you need this to acknowledge the message - * @param consumerTag the consumer identifier that was used to retrieve this message - * @param redelivered is this a redelivered message? - */ - virtual void onReceived(Channel *channel, const Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) {} - - /** - * Method that is called when a message you tried to publish was returned - * by the server. This only happens when the 'mandatory' or 'immediate' flag - * was set with the Channel::publish() call. - * @param channel the channel on which the message was returned - * @param message the returned message - * @param code the reply code - * @param text human readable reply reason - */ - virtual void onReturned(Channel *channel, const Message &message, int16_t code, const std::string &text) {} - -}; - -/** - * End of namespace - */ -} diff --git a/include/channelimpl.h b/include/channelimpl.h index cf75dfb..37e8e07 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -32,12 +32,6 @@ private: */ ConnectionImpl *_connection; - /** - * The handler that is notified about events - * @var MyChannelHandler - */ - ChannelHandler *_handler; - /** * Callback when the channel is ready */ @@ -48,6 +42,11 @@ private: */ std::function _errorCallback; + /** + * Callback to execute when a message arrives + */ + std::unique_ptr _consumer; + /** * The callbacks waiting to be called */ @@ -69,12 +68,6 @@ private: state_closed } _state = state_connected; - /** - * Is a transaction now active? - * @var bool - */ - bool _transaction = false; - /** * The message that is now being received * @var MessageImpl @@ -90,9 +83,8 @@ private: * * @param parent the public channel object * @param connection pointer to the connection - * @param handler handler that is notified on events */ - ChannelImpl(Channel *parent, Connection *connection, ChannelHandler *handler = nullptr); + ChannelImpl(Channel *parent, Connection *connection); public: /** @@ -292,23 +284,17 @@ public: /** * Publish a message to an exchange * - * The following flags can be used - * - * - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method - * - immediate if set, a message that could not immediately be consumed is returned to the onReturned method - * * If the mandatory or immediate flag is set, and the message could not immediately * be published, the message will be returned to the client, and will eventually - * end up in your ChannelHandler::onReturned() method. + * end up in your onReturned() handler method. * * @param exchange the exchange to publish to * @param routingkey the routing key - * @param flags optional flags (see above) * @param envelope the full envelope to send * @param message the message to send * @param size size of the message */ - bool publish(const std::string &exchange, const std::string &routingKey, int flags, const Envelope &envelope); + bool publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope); /** * Set the Quality of Service (QOS) of the entire connection @@ -325,20 +311,44 @@ public: * @param tag a consumer tag that will be associated with this consume operation * @param flags additional flags * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, const std::string& tag); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) { + * + * std::cout << "Started consuming under tag " << tag << std::endl; + * + * }); */ - bool consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments); + DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments); /** * Cancel a running consumer * @param tag the consumer tag * @param flags optional flags - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, const std::string& tag); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) { + * + * std::cout << "Started consuming under tag " << tag << std::endl; + * + * }); */ - bool cancel(const std::string &tag, int flags); + Deferred& cancel(const std::string &tag, int flags); /** - * Acknoledge a message + * Acknowledge a message * @param deliveryTag the delivery tag * @param flags optional flags * @return bool @@ -446,24 +456,6 @@ public: _callbacks.reportError(message); } - /** - * Report that a consumer has started - * @param tag the consumer tag - */ - void reportConsumerStarted(const std::string &tag) - { - if (_handler) _handler->onConsumerStarted(_parent, tag); - } - - /** - * Report that a consumer has stopped - * @param tag the consumer tag - */ - void reportConsumerStopped(const std::string &tag) - { - if (_handler) _handler->onConsumerStopped(_parent, tag); - } - /** * Report that a message was received */ @@ -475,7 +467,6 @@ public: * @return MessageImpl */ MessageImpl *message(const BasicDeliverFrame &frame); - MessageImpl *message(const BasicReturnFrame &frame); /** * Retrieve the current incoming message @@ -486,6 +477,20 @@ public: return _message; } + /** + * Report that the consumer has started + * + * @param consumerTag the tag under which we are now consuming + */ + void reportConsumerStarted(const std::string& consumerTag) + { + // if we do not have a consumer, something is very wrong + if (!_consumer) reportError("Received basic consume ok frame, but no consumer was found"); + + // otherwise, we now report the consumer as started + else _consumer->success(consumerTag); + } + /** * The channel class is its friend, thus can it instantiate this object */ diff --git a/include/deferred.h b/include/deferred.h index 2d99187..489c537 100644 --- a/include/deferred.h +++ b/include/deferred.h @@ -24,16 +24,6 @@ template class Deferred { private: - /** - * The channel we operate under - */ - Channel *_channel; - - /** - * Do we already know we failed? - */ - bool _failed; - /** * Callback to execute on success */ @@ -49,19 +39,12 @@ private: */ std::function _finalizeCallback; - /** - * The channel implementation may call our - * private members and construct us - */ - friend class ChannelImpl; - friend class Callbacks; - /** * Indicate success * * @param parameters... the extra parameters relevant for this deferred handler */ - void success(Arguments ...parameters) + void success(Arguments ...parameters) const { // execute callbacks if registered if (_successCallback) _successCallback(_channel, parameters...); @@ -84,7 +67,24 @@ private: } /** - * Private constructor that can only be called + * The channel implementation may call our + * private members and construct us + */ + friend class ChannelImpl; + friend class Callbacks; +protected: + /** + * The channel we operate under + */ + Channel *_channel; + + /** + * Do we already know we failed? + */ + bool _failed; + + /** + * Protected constructor that can only be called * from within the channel implementation * * @param channel the channel we operate under diff --git a/src/basiccancelokframe.h b/src/basiccancelokframe.h index 0841379..6d9d42f 100644 --- a/src/basiccancelokframe.h +++ b/src/basiccancelokframe.h @@ -1,6 +1,6 @@ /** * Class describing a basic cancel ok frame - * + * * @copyright 2014 Copernica BV */ @@ -42,7 +42,7 @@ public: * * @param frame received frame */ - BasicCancelOKFrame(ReceivedFrame &frame) : + BasicCancelOKFrame(ReceivedFrame &frame) : BasicFrame(frame), _consumerTag(frame) {} @@ -89,13 +89,13 @@ public: { // we need the appropriate channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist - if (!channel) return false; - + if (!channel) return false; + // report - channel->reportConsumerStopped(consumerTag()); - + channel->reportSuccess(consumerTag()); + // done return true; } diff --git a/src/basicconsumeokframe.h b/src/basicconsumeokframe.h index cf7298a..1ed8ef4 100644 --- a/src/basicconsumeokframe.h +++ b/src/basicconsumeokframe.h @@ -1,6 +1,6 @@ /** * Class describing a basic consume ok frame - * + * * @copyright 2014 Copernica BV */ @@ -52,7 +52,7 @@ public: * * @param frame received frame */ - BasicConsumeOKFrame(ReceivedFrame &frame) : + BasicConsumeOKFrame(ReceivedFrame &frame) : BasicFrame(frame), _consumerTag(frame) {} @@ -89,13 +89,13 @@ public: { // we need the appropriate channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist - if (!channel) return false; - + if (!channel) return false; + // report channel->reportConsumerStarted(consumerTag()); - + // done return true; } diff --git a/src/basicreturnframe.h b/src/basicreturnframe.h index 1759240..6bb8aef 100644 --- a/src/basicreturnframe.h +++ b/src/basicreturnframe.h @@ -144,17 +144,8 @@ public: */ virtual bool process(ConnectionImpl *connection) override { - // we need the appropriate channel - ChannelImpl *channel = connection->channel(this->channel()); - - // channel does not exist - if (!channel) return false; - - // construct the message - channel->message(*this); - - // done - return true; + // we no longer support returned messages + return false; } }; diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 10ea529..f65237f 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -48,10 +48,9 @@ namespace AMQP { * @param connection * @param handler */ -ChannelImpl::ChannelImpl(Channel *parent, Connection *connection, ChannelHandler *handler) : +ChannelImpl::ChannelImpl(Channel *parent, Connection *connection) : _parent(parent), - _connection(&connection->_implementation), - _handler(handler) + _connection(&connection->_implementation) { // add the channel to the connection _id = _connection->add(this); @@ -61,9 +60,6 @@ ChannelImpl::ChannelImpl(Channel *parent, Connection *connection, ChannelHandler { // this is invalid _state = state_closed; - - // invalid id, this channel can not exist - handler->onError(_parent, "Max number of channels reached"); } else { @@ -352,19 +348,13 @@ Deferred& ChannelImpl::removeQueue(const std::string &name, int flags) /** * Publish a message to an exchange * - * The following flags can be used - * - * - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method - * - immediate if set, a message that could not immediately be consumed is returned to the onReturned method - * * @param exchange the exchange to publish to * @param routingkey the routing key - * @param flags optional flags (see above) * @param envelope the full envelope to send * @param message the message to send * @param size size of the message */ -bool ChannelImpl::publish(const std::string &exchange, const std::string &routingKey, int flags, const Envelope &envelope) +bool ChannelImpl::publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) { // we are going to send out multiple frames, each one will trigger a call to the handler, // which in turn could destruct the channel object, we need to monitor that @@ -373,7 +363,7 @@ bool ChannelImpl::publish(const std::string &exchange, const std::string &routin // @todo do not copy the entire buffer to individual frames // send the publish frame - if (!send(BasicPublishFrame(_id, exchange, routingKey, flags & mandatory, flags & immediate))) return false; + if (!send(BasicPublishFrame(_id, exchange, routingKey))) return false; // channel still valid? if (!monitor.valid()) return false; @@ -432,27 +422,65 @@ Deferred<>& ChannelImpl::setQos(uint16_t prefetchCount) * @param tag a consumer tag that will be associated with this consume operation * @param flags additional flags * @param arguments additional arguments - * @return bool + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, const std::string& tag); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) { + * + * std::cout << "Started consuming under tag " << tag << std::endl; + * + * }); */ -bool ChannelImpl::consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) +DeferredConsumer& ChannelImpl::consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { - // send a consume frame - return send(BasicConsumeFrame(_id, queue, tag, flags & nolocal, flags & noack, flags & exclusive, flags & nowait, arguments)); + // create the deferred consumer + _consumer = std::unique_ptr(new DeferredConsumer(_parent, false)); + + // can we send the basic consume frame? + if (!send(BasicConsumeFrame(_id, queue, tag, flags & nolocal, flags & noack, flags & exclusive, flags & nowait, arguments))) + { + // we set the consumer to be failed immediately + _consumer->_failed = true; + + // we should call the error function later + // TODO + } + + // return the consumer + return *_consumer; } /** * Cancel a running consumer * @param tag the consumer tag * @param flags optional flags + * + * This function returns a deferred handler. Callbacks can be installed + * using onSuccess(), onError() and onFinalize() methods. + * + * The onSuccess() callback that you can install should have the following signature: + * + * void myCallback(AMQP::Channel *channel, const std::string& tag); + * + * For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) { + * + * std::cout << "Started consuming under tag " << tag << std::endl; + * + * }); */ -bool ChannelImpl::cancel(const std::string &tag, int flags) +Deferred& ChannelImpl::cancel(const std::string &tag, int flags) { // send a cancel frame - return send(BasicCancelFrame(_id, tag, flags & nowait)); + return send(BasicCancelFrame(_id, tag, flags & nowait), "Cannot send basic cancel frame"); } /** - * Acknoledge a message + * Acknowledge a message * @param deliveryTag the delivery tag * @param flags optional flags * @return bool @@ -541,14 +569,23 @@ void ChannelImpl::reportMessage() // skip if there is no message if (!_message) return; - // after the report the channel may be destructed, monitor that - Monitor monitor(this); + // do we even have a consumer? + if (!_consumer) + { + // this should not be possible: receiving a message without doing a consume() call + reportError("Received message without having a consumer"); + } + else + { + // after the report the channel may be destructed, monitor that + Monitor monitor(this); - // do we have a handler? - if (_handler) _message->report(_parent, _handler); + // send message to the consumer + _message->report(*_consumer); - // skip if channel was destructed - if (!monitor.valid()) return; + // skip if channel was destructed + if (!monitor.valid()) return; + } // no longer need the message delete _message; @@ -569,20 +606,6 @@ MessageImpl *ChannelImpl::message(const BasicDeliverFrame &frame) return _message = new ConsumedMessage(frame); } -/** - * Create an incoming message - * @param frame - * @return MessageImpl - */ -MessageImpl *ChannelImpl::message(const BasicReturnFrame &frame) -{ - // it should not be possible that a message already exists, but lets check it anyhow - if (_message) delete _message; - - // construct a message - return _message = new ReturnedMessage(frame); -} - /** * End of namespace */ diff --git a/src/consumedmessage.h b/src/consumedmessage.h index 30ed208..2227763 100644 --- a/src/consumedmessage.h +++ b/src/consumedmessage.h @@ -26,7 +26,7 @@ private: * @var uint64_t */ uint64_t _deliveryTag; - + /** * Is this a redelivered message? * @var bool @@ -39,25 +39,24 @@ public: * Constructor * @param frame */ - ConsumedMessage(const BasicDeliverFrame &frame) : - MessageImpl(frame.exchange(), frame.routingKey()), + ConsumedMessage(const BasicDeliverFrame &frame) : + MessageImpl(frame.exchange(), frame.routingKey()), _consumerTag(frame.consumerTag()), _deliveryTag(frame.deliveryTag()), _redelivered(frame.redelivered()) {} - + /** * Destructor */ virtual ~ConsumedMessage() {} - + /** * Report to the handler - * @param channel - * @param handler + * @param consumer */ - virtual void report(Channel *channel, ChannelHandler *handler) override + virtual void report(const DeferredConsumer& consumer) override { - // report to the handler - handler->onReceived(channel, *this, _deliveryTag, _consumerTag, _redelivered); + // send ourselves to the consumer + consumer.message(*this, _deliveryTag, _consumerTag, _redelivered); } }; diff --git a/src/messageimpl.h b/src/messageimpl.h index 27d5b85..1b087dd 100644 --- a/src/messageimpl.h +++ b/src/messageimpl.h @@ -20,10 +20,10 @@ class MessageImpl : public Message private: /** * How many bytes have been received? - * @var uint64_t + * @var uint64_t */ uint64_t _received; - + /** * Was the buffer allocated by us? * @var bool @@ -36,8 +36,8 @@ protected: * @param exchange * @param routingKey */ - MessageImpl(const std::string &exchange, const std::string &routingKey) : - Message(exchange, routingKey), + MessageImpl(const std::string &exchange, const std::string &routingKey) : + Message(exchange, routingKey), _received(0), _selfAllocated(false) {} @@ -45,12 +45,12 @@ public: /** * Destructor */ - virtual ~MessageImpl() + virtual ~MessageImpl() { // clear up memory if it was self allocated if (_selfAllocated) delete[] _body; } - + /** * Set the body size * This field is set when the header is received @@ -60,7 +60,7 @@ public: { _bodySize = size; } - + /** * Append data * @param buffer incoming data @@ -84,27 +84,26 @@ public: // it does not yet fit, do we have to allocate? if (!_body) _body = new char[_bodySize]; _selfAllocated = true; - + // prevent that size is too big if (size > _bodySize - _received) size = _bodySize - _received; - + // append data memcpy((char *)(_body + _received), buffer, size); - + // we have more data now _received += size; - + // done return _received >= _bodySize; } } - + /** * Report to the handler - * @param channel - * @param handler + * @param consumer */ - virtual void report(Channel *channel, ChannelHandler *handler) = 0; + virtual void report(const DeferredConsumer& consumer) = 0; }; /** diff --git a/src/returnedmessage.h b/src/returnedmessage.h index 61115d7..322bb88 100644 --- a/src/returnedmessage.h +++ b/src/returnedmessage.h @@ -24,7 +24,7 @@ private: * @var int16_t */ int16_t _replyCode; - + /** * The reply message * @var string @@ -40,21 +40,19 @@ public: ReturnedMessage(const BasicReturnFrame &frame) : MessageImpl(frame.exchange(), frame.routingKey()), _replyCode(frame.replyCode()), _replyText(frame.replyText()) {} - + /** * Destructor */ virtual ~ReturnedMessage() {} - + /** * Report to the handler - * @param channel - * @param handler + * @param consumer */ - virtual void report(Channel *channel, ChannelHandler *handler) override + virtual void report(const DeferredConsumer& consumer) override { - // report to the handler - handler->onReturned(channel, *this, _replyCode, _replyText); + // we no longer support returned messages } }; diff --git a/tests/main.cpp b/tests/main.cpp index 49076b7..f0c1b04 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -30,13 +30,13 @@ using namespace Copernica; * @return int */ int main(int argc, const char *argv[]) -{ +{ // need an ip if (argc != 2) { // report error std::cerr << "usage: " << argv[0] << " " << std::endl; - + // done return -1; } diff --git a/tests/myconnection.cpp b/tests/myconnection.cpp index 64c82b4..7234ad7 100644 --- a/tests/myconnection.cpp +++ b/tests/myconnection.cpp @@ -3,13 +3,13 @@ * * @copyright 2014 Copernica BV */ - + /** * Required external libraries */ #include #include - + #include /** @@ -17,7 +17,7 @@ */ using namespace std; using namespace Copernica; - + /** * Required local class definitions */ @@ -33,23 +33,11 @@ MyConnection::MyConnection(const std::string &ip) : { // start connecting if (_socket.connect(Network::Ipv4Address(ip), 5672)) return; - + // failure onFailure(&_socket); } -/** - * Destructor - */ -MyConnection::~MyConnection() -{ - // do we still have a channel? - if (_channel) delete _channel; - - // do we still have a connection? - if (_connection) delete _connection; -} - /** * Method that is called when the connection failed * @param socket Pointer to the socket @@ -78,19 +66,118 @@ void MyConnection::onConnected(Network::TcpSocket *socket) { // report connection std::cout << "connected" << std::endl; - + // we are connected, leap out if there already is a amqp connection if (_connection) return; - + // create amqp connection, and a new channel - _connection = new AMQP::Connection(this, AMQP::Login("guest", "guest"), "/"); - _channel = new AMQP::Channel(_connection, this); - - // we declare a queue, an exchange and we publish a message - _channel->declareQueue("my_queue"); -// _channel->declareQueue("my_queue", AMQP::autodelete); - _channel->declareExchange("my_exchange", AMQP::direct); - _channel->bindQueue("my_exchange", "my_queue", "key"); + _connection = std::unique_ptr(new AMQP::Connection(this, AMQP::Login("guest", "guest"), "/")); + _channel = std::unique_ptr(new AMQP::Channel(_connection.get())); + + // watch for the channel becoming ready + _channel->onReady([](AMQP::Channel *channel) { + // show that we are ready + std::cout << "AMQP channel ready, id: " << (int) channel->id() << std::endl; + }); + + // and of course for channel errors + _channel->onError([this](AMQP::Channel *channel, const std::string& message) { + // inform the user of the error + std::cerr << "AMQP channel error on channel " << channel->id() << ": " << message << std::endl; + + // delete the channel + _channel = nullptr; + + // close the connection + _connection->close(); + }); + + // declare a queue and let us know when it succeeds + _channel->declareQueue("my_queue").onSuccess([](AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount){ + // queue was successfully declared + std::cout << "AMQP Queue declared with name '" << name << "', " << messageCount << " messages and " << consumerCount << " consumer" << std::endl; + }); + + // also declare an exchange + _channel->declareExchange("my_exchange", AMQP::direct).onSuccess([](AMQP::Channel *channel) { + // exchange successfully declared + std::cout << "AMQP exchange declared" << std::endl; + }); + + // bind the queue to the exchange + _channel->bindQueue("my_exchange", "my_queue", "key").onSuccess([](AMQP::Channel *channel) { + // queue successfully bound to exchange + std::cout << "AMQP Queue bound" << std::endl; + }); + + // set quality of service + _channel->setQos(1).onSuccess([](AMQP::Channel *channel) { + // quality of service successfully set + std::cout << "AMQP Quality of Service set" << std::endl; + }); + + // publish a message to the exchange + if (!_channel->publish("my_exchange", "key", "my_message")) + { + // we could not publish the message, something is wrong somewhere + std::cerr << "Unable to publish message" << std::endl; + + // close the channel + _channel->close().onSuccess([this](AMQP::Channel *channel) { + // also close the connection + _connection->close(); + }); + } + + // consume the message we just published + _channel->consume("my_queue", "my_consumer", AMQP::exclusive) + .onReceived([this](AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) { + // show the message data + std::cout << "AMQP consumed: " << message.message() << std::endl; + + // ack the message + _channel->ack(deliveryTag); + + // and stop consuming (there is only one message anyways) + _channel->cancel("my_consumer").onSuccess([](AMQP::Channel *channel, const std::string& tag) { + // we successfully stopped consuming + std::cout << "Stopped consuming under tag " << tag << std::endl; + }); + + // unbind the queue again + _channel->unbindQueue("my_exchange", "my_queue", "key").onSuccess([](AMQP::Channel *channel) { + // queueu successfully unbound + std::cout << "Queue unbound" << std::endl; + }); + + // the queue should now be empty, so we can delete it + _channel->removeQueue("my_queue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { + // queue was removed, it should have been empty, so messageCount should be 0 + if (messageCount) std::cerr << "Removed queue which should have been empty but contained " << messageCount << " messages" << std::endl; + + // no messages is the expected behavior + else std::cout << "Queue removed" << std::endl; + }); + + // also remove the exchange + _channel->removeExchange("my_exchange").onSuccess([](AMQP::Channel *channel) { + // exchange was successfully removed + std::cout << "Removed exchange" << std::endl; + }); + + // everything done, close the channel + _channel->close().onSuccess([this](AMQP::Channel *channel) { + // channel was closed + std::cout << "Channel closed" << std::endl; + + // close the connection too + _connection->close(); + }); + }) + .onSuccess([](AMQP::Channel *channel, const std::string& tag) { + // consumer was started + std::cout << "Started consuming under tag " << tag << std::endl; + }); } /** @@ -103,12 +190,11 @@ void MyConnection::onClosed(Network::TcpSocket *socket) std::cout << "myconnection closed" << std::endl; // close the channel and connection - if (_channel) delete _channel; - if (_connection) delete _connection; - - // set to null _channel = nullptr; _connection = nullptr; + + // stop the loop + Event::MainLoop::instance()->stop(); } /** @@ -119,14 +205,13 @@ void MyConnection::onLost(Network::TcpSocket *socket) { // report error std::cout << "connection lost" << std::endl; - + // close the channel and connection - if (_channel) delete _channel; - if (_connection) delete _connection; - - // set to null _channel = nullptr; _connection = nullptr; + + // stop the loop + Event::MainLoop::instance()->stop(); } /** @@ -136,15 +221,12 @@ void MyConnection::onLost(Network::TcpSocket *socket) */ void MyConnection::onData(Network::TcpSocket *socket, Network::Buffer *buffer) { - // send what came in - std::cout << "received: " << buffer->size() << " bytes" << std::endl; - // leap out if there is no connection if (!_connection) return; - + // let the data be handled by the connection size_t bytes = _connection->parse(buffer->data(), buffer->size()); - + // shrink the buffer buffer->shrink(bytes); } @@ -152,7 +234,7 @@ void MyConnection::onData(Network::TcpSocket *socket, Network::Buffer *buffer) /** * Method that is called when data needs to be sent over the network * - * Note that the AMQP library does no buffering by itself. This means + * Note that the AMQP library does no buffering by itself. This means * that this method should always send out all data or do the buffering * itself. * @@ -162,21 +244,27 @@ void MyConnection::onData(Network::TcpSocket *socket, Network::Buffer *buffer) */ void MyConnection::onData(AMQP::Connection *connection, const char *buffer, size_t size) { -// // report what is going on -// std::cout << "send: " << size << std::endl; -// -// for (unsigned i=0; i(new AMQP::Channel(connection)); } - -/** - * Method that is called when the channel was succesfully created. - * Only after the channel was created, you can use it for subsequent messages over it - * @param channel - */ -void MyConnection::onReady(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP channel ready, id: " << (int) channel->id() << std::endl; -} - -/** - * An error has occured on the channel - * @param channel - * @param message - */ - -void MyConnection::onError(AMQP::Channel *channel, const std::string &message) -{ - // show - std::cout << "AMQP channel error, id: " << (int) channel->id() << " - message: " << message << std::endl; - - // main channel cause an error, get rid of if - delete _channel; - - // reset pointer - _channel = nullptr; -} - -/** - * Method that is called when the channel was paused - * @param channel - */ -void MyConnection::onPaused(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP channel paused" << std::endl; -} - -/** - * Method that is called when the channel was resumed - * @param channel - */ -void MyConnection::onResumed(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP channel resumed" << std::endl; -} - -/** - * Method that is called when a channel is closed - * @param channel - */ -void MyConnection::onClosed(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP channel closed" << std::endl; -} - -/** - * Method that is called when a transaction was started - * @param channel - */ -void MyConnection::onTransactionStarted(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP transaction started" << std::endl; -} - -/** - * Method that is called when a transaction was committed - * @param channel - */ -void MyConnection::onTransactionCommitted(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP transaction committed" << std::endl; -} - -/** - * Method that is called when a transaction was rolled back - * @param channel - */ -void MyConnection::onTransactionRolledBack(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP transaction rolled back" << std::endl; -} - -/** - * Mehod that is called when an exchange is declared - * @param channel - */ -void MyConnection::onExchangeDeclared(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP exchange declared" << std::endl; -} - -/** - * Method that is called when an exchange is bound - * @param channel - */ -void MyConnection::onExchangeBound(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP Exchange bound" << std::endl; -} - -/** - * Method that is called when an exchange is unbound - * @param channel - */ -void MyConnection::onExchangeUnbound(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP Exchange unbound" << std::endl; -} - -/** - * Method that is called when an exchange is deleted - * @param channel - */ -void MyConnection::onExchangeDeleted(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP Exchange deleted" << std::endl; -} - -/** - * Method that is called when a queue is declared - * @param channel - * @param name name of the queue - * @param messageCount number of messages in queue - * @param consumerCount number of active consumers - */ -void MyConnection::onQueueDeclared(AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount) -{ - // show - std::cout << "AMQP Queue declared" << std::endl; -} - -/** - * Method that is called when a queue is bound - * @param channel - * @param - */ -void MyConnection::onQueueBound(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP Queue bound" << std::endl; - -// _connection->setQos(10); -// _channel->setQos(1); - - - _channel->publish("my_exchange", "invalid-key", AMQP::mandatory, "this is the message"); -// _channel->consume("my_queue"); -} - -/** - * Method that is called when a queue is deleted - * @param channel - * @param messageCount number of messages deleted along with the queue - */ -void MyConnection::onQueueDeleted(AMQP::Channel *channel, uint32_t messageCount) -{ - // show - std::cout << "AMQP Queue deleted" << std::endl; -} - -/** - * Method that is called when a queue is unbound - * @param channel - */ -void MyConnection::onQueueUnbound(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP Queue unbound" << std::endl; -} - -/** - * Method that is called when a queue is purged - * @param messageCount number of message purged - */ -void MyConnection::onQueuePurged(AMQP::Channel *channel, uint32_t messageCount) -{ - // show - std::cout << "AMQP Queue purged" << std::endl; -} - -/** - * Method that is called when the quality-of-service was changed - * This is the result of a call to Channel::setQos() - */ -void MyConnection::onQosSet(AMQP::Channel *channel) -{ - // show - std::cout << "AMQP Qos set" << std::endl; -} - -/** - * Method that is called when a consumer was started - * This is the result of a call to Channel::consume() - * @param channel the channel on which the consumer was started - * @param tag the consumer tag - */ -void MyConnection::onConsumerStarted(AMQP::Channel *channel, const std::string &tag) -{ - // show - std::cout << "AMQP consumer started" << std::endl; -} - -/** - * Method that is called when a message has been received on a channel - * @param channel the channel on which the consumer was started - * @param message the consumed message - * @param deliveryTag the delivery tag, you need this to acknowledge the message - * @param consumerTag the consumer identifier that was used to retrieve this message - * @param redelivered is this a redelivered message? - */ -void MyConnection::onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) -{ - // show - std::cout << "AMQP consumed: " << message.message() << std::endl; - - // ack the message - channel->ack(deliveryTag); -} - -/** - * Method that is called when a message you tried to publish was returned - * by the server. This only happens when the 'mandatory' or 'immediate' flag - * was set with the Channel::publish() call. - * @param channel the channel on which the message was returned - * @param message the returned message - * @param code the reply code - * @param text human readable reply reason - */ -void MyConnection::onReturned(AMQP::Channel *channel, const AMQP::Message &message, int16_t code, const std::string &text) -{ - // show - std::cout << "AMQP message returned: " << text << std::endl; -} - -/** - * Method that is called when a consumer was stopped - * This is the result of a call to Channel::cancel() - * @param channel the channel on which the consumer was stopped - * @param tag the consumer tag - */ -void MyConnection::onConsumerStopped(AMQP::Channel *channel, const std::string &tag) -{ - // show - std::cout << "AMQP consumer stopped" << std::endl; -} - diff --git a/tests/myconnection.h b/tests/myconnection.h index 61b0e1e..698428b 100644 --- a/tests/myconnection.h +++ b/tests/myconnection.h @@ -9,9 +9,8 @@ /** * Class definition */ -class MyConnection : +class MyConnection : public AMQP::ConnectionHandler, - public AMQP::ChannelHandler, public Network::TcpHandler { private: @@ -20,18 +19,18 @@ private: * @var TcpSocket */ Network::TcpSocket _socket; - + /** * The AMQP connection * @var Connection */ - AMQP::Connection *_connection; - + std::unique_ptr _connection; + /** * The AMQP channel * @var Channel */ - AMQP::Channel *_channel; + std::unique_ptr _channel; /** * Method that is called when the connection failed @@ -44,13 +43,13 @@ private: * @param socket Pointer to the socket */ virtual void onTimeout(Network::TcpSocket *socket) override; - + /** * Method that is called when the connection succeeded * @param socket Pointer to the socket */ virtual void onConnected(Network::TcpSocket *socket) override; - + /** * Method that is called when the socket is closed (as a result of a TcpSocket::close() call) * @param socket Pointer to the socket @@ -62,18 +61,18 @@ private: * @param socket Pointer to the socket */ virtual void onLost(Network::TcpSocket *socket) override; - + /** * Method that is called when data is received on the socket * @param socket Pointer to the socket * @param buffer Pointer to the fill input buffer */ virtual void onData(Network::TcpSocket *socket, Network::Buffer *buffer) override; - + /** * Method that is called when data needs to be sent over the network * - * Note that the AMQP library does no buffering by itself. This means + * Note that the AMQP library does no buffering by itself. This means * that this method should always send out all data or do the buffering * itself. * @@ -82,11 +81,17 @@ private: * @param size Size of the buffer */ virtual void onData(AMQP::Connection *connection, const char *buffer, size_t size) override; - + + /** + * Method that is called when the connection to AMQP was closed + * @param connection pointer to connection object + */ + virtual void onClosed(AMQP::Connection *connection) override; + /** * When the connection ends up in an error state this method is called. * This happens when data comes in that does not match the AMQP protocol - * + * * After this method is called, the connection no longer is in a valid * state and can be used. In normal circumstances this method is not called. * @@ -103,175 +108,11 @@ private: */ virtual void onConnected(AMQP::Connection *connection) override; - /** - * Method that is called when the channel was succesfully created. - * Only after the channel was created, you can use it for subsequent messages over it - * @param channel - */ - virtual void onReady(AMQP::Channel *channel) override; - - /** - * An error has occured on the channel - * @param channel - * @param message - */ - virtual void onError(AMQP::Channel *channel, const std::string &message) override; - - /** - * Method that is called when the channel was paused - * @param channel - */ - virtual void onPaused(AMQP::Channel *channel) override; - - /** - * Method that is called when the channel was resumed - * @param channel - */ - virtual void onResumed(AMQP::Channel *channel) override; - - /** - * Method that is called when a channel is closed - * @param channel - */ - virtual void onClosed(AMQP::Channel *channel) override; - - /** - * Method that is called when a transaction was started - * @param channel - */ - virtual void onTransactionStarted(AMQP::Channel *channel) override; - - /** - * Method that is called when a transaction was committed - * @param channel - */ - virtual void onTransactionCommitted(AMQP::Channel *channel) override; - - /** - * Method that is called when a transaction was rolled back - * @param channel - */ - virtual void onTransactionRolledBack(AMQP::Channel *channel) override; - - /** - * Method that is called when an exchange is bound - * @param channel - */ - virtual void onExchangeBound(AMQP::Channel *channel) override; - - /** - * Method that is called when an exchange is unbound - * @param channel - */ - virtual void onExchangeUnbound(AMQP::Channel *channel) override; - - /** - * Method that is called when an exchange is deleted - * @param channel - */ - virtual void onExchangeDeleted(AMQP::Channel *channel) override; - - /** - * Mehod that is called when an exchange is declared - * @param channel - */ - virtual void onExchangeDeclared(AMQP::Channel *channel) override; - - /** - * Method that is called when a queue is declared - * @param channel - * @param name name of the queue - * @param messageCount number of messages in queue - * @param consumerCount number of active consumers - */ - virtual void onQueueDeclared(AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount) override; - - /** - * Method that is called when a queue is bound - * @param channel - * @param - */ - virtual void onQueueBound(AMQP::Channel *channel) override; - - /** - * Method that is called when a queue is deleted - * @param channel - * @param messageCount number of messages deleted along with the queue - */ - virtual void onQueueDeleted(AMQP::Channel *channel, uint32_t messageCount) override; - - /** - * Method that is called when a queue is unbound - * @param channel - */ - virtual void onQueueUnbound(AMQP::Channel *channel) override; - - /** - * Method that is called when a queue is purged - * @param messageCount number of message purged - */ - virtual void onQueuePurged(AMQP::Channel *channel, uint32_t messageCount) override; - - /** - * Method that is called when the quality-of-service was changed - * This is the result of a call to Channel::setQos() - */ - virtual void onQosSet(AMQP::Channel *channel) override; - - /** - * Method that is called when a consumer was started - * This is the result of a call to Channel::consume() - * @param channel the channel on which the consumer was started - * @param tag the consumer tag - */ - virtual void onConsumerStarted(AMQP::Channel *channel, const std::string &tag) override; - - /** - * Method that is called when a consumer was stopped - * This is the result of a call to Channel::cancel() - * @param channel the channel on which the consumer was stopped - * @param tag the consumer tag - */ - virtual void onConsumerStopped(AMQP::Channel *channel, const std::string &tag) override; - - /** - * Method that is called when a message has been received on a channel - * This message will be called for every message that is received after - * you started consuming. Make sure you acknowledge the messages when its - * safe to remove them from RabbitMQ (unless you set no-ack option when you - * started the consumer) - * @param channel the channel on which the consumer was started - * @param message the consumed message - * @param deliveryTag the delivery tag, you need this to acknowledge the message - * @param consumerTag the consumer identifier that was used to retrieve this message - * @param redelivered is this a redelivered message? - */ - virtual void onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) override; - - /** - * Method that is called when a message you tried to publish was returned - * by the server. This only happens when the 'mandatory' or 'immediate' flag - * was set with the Channel::publish() call. - * @param channel the channel on which the message was returned - * @param message the returned message - * @param code the reply code - * @param text human readable reply reason - */ - virtual void onReturned(AMQP::Channel *channel, const AMQP::Message &message, int16_t code, const std::string &text) override; - - public: /** * Constructor * @param ip */ MyConnection(const std::string &ip); - - /** - * Destructor - */ - virtual ~MyConnection(); - - }; From 3348e2881c17f12dc4360e62052d48abb9bf7928 Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Thu, 10 Apr 2014 13:50:24 +0200 Subject: [PATCH 09/41] Updated README --- README.md | 334 ++++++++++++++++++++++++------------------------------ 1 file changed, 145 insertions(+), 189 deletions(-) diff --git a/README.md b/README.md index 79da95c..ce5468d 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,27 @@ AMQP-CPP ======== -AMQP-CPP is a C++ library for communicating with a RabbitMQ message broker. The -library can be used to parse incoming data from a RabbitMQ server, and to +AMQP-CPP is a C++ library for communicating with a RabbitMQ message broker. The +library can be used to parse incoming data from a RabbitMQ server, and to generate frames that can be sent to a RabbitMQ server. Unlike all other AMQP libraries, this AMQP-CPP library does not make a connection to -RabbitMQ by itself, nor does it create sockets and/or performs IO operations. As -a user of this library, you first need to set up a socket connection -to RabbitMQ by yourself, and implement a certain interface that you pass to the +RabbitMQ by itself, nor does it create sockets and/or performs IO operations. As +a user of this library, you first need to set up a socket connection +to RabbitMQ by yourself, and implement a certain interface that you pass to the AMQP-CPP library and that the library will use for IO operations. This architecture makes the library extremely flexible: it does not rely on operating system specific IO calls, and it can be easily integrated into any -event loop. It is fully asynchronous and does not do any blocking (system) calls, +event loop. It is fully asynchronous and does not do any blocking (system) calls, so it can be used in high performance applications without the need for threads. ABOUT ===== -This library is created and maintained by Copernica (www.copernica.com), and is -used inside the MailerQ (www.mailerq.com) application, MailerQ is a tool for +This library is created and maintained by Copernica (www.copernica.com), and is +used inside the MailerQ (www.mailerq.com) application, MailerQ is a tool for sending large volumes of email, using AMQP message queues. @@ -42,34 +42,34 @@ class MyConnectionHandler : public AMQP::ConnectionHandler { /** * Method that is called by the AMQP library every time it has data - * available that should be sent to RabbitMQ. - * @param connection pointer to the main connection object + * available that should be sent to RabbitMQ. + * @param connection pointer to the main connection object * @param data memory buffer with the data that should be sent to RabbitMQ * @param size size of the buffer */ virtual void onData(AMQP::Connection *connection, const char *data, size_t size) { - // @todo + // @todo // Add your own implementation, for example by doing a call to the // send() system call. But be aware that the send() call may not // send all data at once, so you also need to take care of buffering - // the bytes that could not immediately be sent, and try to send + // the bytes that could not immediately be sent, and try to send // them again when the socket becomes writable again } - + /** - * Method that is called by the AMQP library when the login attempt - * succeeded. After this method has been called, the connection is ready + * Method that is called by the AMQP library when the login attempt + * succeeded. After this method has been called, the connection is ready * to use. * @param connection The connection that can now be used */ virtual void onConnected(AMQP::Connection *connection) { // @todo - // add your own implementation, for example by creating a channel + // add your own implementation, for example by creating a channel // instance, and start publishing or consuming } - + /** * Method that is called by the AMQP library when a fatal error occurs * on the connection, for example because data received from RabbitMQ @@ -81,7 +81,7 @@ class MyConnectionHandler : public AMQP::ConnectionHandler { // @todo // add your own implementation, for example by reporting the error - // to the user of your program, log the error, and destruct the + // to the user of your program, log the error, and destruct the // connection object because it is no longer in a usable state } }; @@ -108,18 +108,18 @@ channel.bindQueue("my-exchange", "my-queue"); A number of remarks about the example above. First you may have noticed that we've created all objects on the stack. You are of course also free to create them on the heap with the C++ operator 'new'. That works just as well, and is in real -life code probably more useful as you normally want to keep your handlers, connection +life code probably more useful as you normally want to keep your handlers, connection and channel objects around for a much longer time. -But more importantly, you can see in the example above that we have created the -channel object directly after we made the connection object, and we also +But more importantly, you can see in the example above that we have created the +channel object directly after we made the connection object, and we also started declaring exchanges and queues right away. However, under the hood, a handshake protocol is executed between the server and the client when the Connection object is first created. During this handshake procedure it is not permitted to send -other instructions (like opening a channel or declaring a queue). It would therefore have been better -if we had first waited for the connection to be ready (implement the MyConnectionHandler::onConnected() method), -and create the channel object only then. But this is not strictly necessary. -The methods that are called before the handshake is completed are cached by the +other instructions (like opening a channel or declaring a queue). It would therefore have been better +if we had first waited for the connection to be ready (implement the MyConnectionHandler::onConnected() method), +and create the channel object only then. But this is not strictly necessary. +The methods that are called before the handshake is completed are cached by the AMQP library and will be executed the moment the handshake is completed and the connection becomes ready for use. @@ -128,15 +128,15 @@ PARSING INCOMING DATA ===================== The ConnectionHandler class has a method onData() that is called by the library -every time that it wants to send out data. We've explained that it is up to you to -implement that method. But what about data in the other direction? How does the +every time that it wants to send out data. We've explained that it is up to you to +implement that method. But what about data in the other direction? How does the library receive data back from RabbitMQ? -As we've explained above, the AMQP-CPP library does not do any IO by itself -and it is therefore of course also not possible for the library to receive data from -a socket. It is again up to you to do this. If, for example, you notice in your -event loop that the socket that is connected with the RabbitMQ server becomes -readable, you should read out that data (for example by using the recv() system +As we've explained above, the AMQP-CPP library does not do any IO by itself +and it is therefore of course also not possible for the library to receive data from +a socket. It is again up to you to do this. If, for example, you notice in your +event loop that the socket that is connected with the RabbitMQ server becomes +readable, you should read out that data (for example by using the recv() system call), and pass the received bytes to the AMQP-CPP library. This is done by calling the parse() method in the Connection object. @@ -147,7 +147,7 @@ The code snippet below comes from the Connection.h C++ header file. ````c++ /** * Parse data that was recevied from RabbitMQ - * + * * Every time that data comes in from RabbitMQ, you should call this method to parse * the incoming data, and let it handle by the AMQP-CPP library. This method returns the number * of bytes that were processed. @@ -170,36 +170,42 @@ size_t parse(char *buffer, size_t size) CHANNELS ======== -In the example you saw that we created a channel object. A channel is a virtual -connection over a single TCP connection, and it is possible to create many channels +In the example you saw that we created a channel object. A channel is a virtual +connection over a single TCP connection, and it is possible to create many channels that all use the same TCP connection. -AMQP instructions are always sent over a channel, so before you can send the first -command to the RabbitMQ server, you first need a channel object. The channel +AMQP instructions are always sent over a channel, so before you can send the first +command to the RabbitMQ server, you first need a channel object. The channel object has many methods to send instructions to the RabbitMQ server. It for -example has methods to declare queues and exchanges, to bind and unbind them, -and to publish and consume messages. You can best take a look at the channel.h -C++ header file for a list of all available methods. Every method in it is well +example has methods to declare queues and exchanges, to bind and unbind them, +and to publish and consume messages. You can best take a look at the channel.h +C++ header file for a list of all available methods. Every method in it is well documented. -The constructor of the Channel object accepts two parameters: the connection object, -and a pointer to a ChannelHandler object. In the example we did -not use this ChannelHandler object. However, in normal circumstances, you should -always pass a pointer to a ChannelHandler object every time you construct a channel. - -Just like the ConnectionHandler class, the ChannelHandler class is a base class that -you can extend to override the virtual methods you need. The AMQP library -will call these methods to inform you that an operation on the channel has succeeded -or has failed. +The constructor of the Channel object accepts one parameter: the connection object. +Unlike the connection it does not accept a handler. Instead of this (almost) every +function in the channel returns a Deferred object. This deferred object can be used +to install handlers to be called in case of success, failure or in either case. For example, if you call the channel.declareQueue() method, the AMQP-CPP library will send a message to the RabbitMQ message broker to ask it to declare the queue. However, because all operations in the library are asynchronous, the declareQueue() method immediately returns 'true', although it is at that time not yet known whether the queue was correctly declared. Only after a while, -after the instruction has reached the server, and the confirmation from the server -has been sent back to the client, your ChannelHandler::onQueueDeclared() -method will be called to inform you that the operation was succesful. +after the instruction has reached the server, and the confirmation from the server +has been sent back to the client, your handler method will be called to inform +you that the operation was succesful. + +````c++ +Channel myChannel(&connection); +myChannel.declareQueue("my-queue") +.onSuccess([](AMQP::Channel *channel, const std::string& name, uint32_t messageCount, uint32_t consumerCount) { + // by now the queue is created +}) +.onError([](AMQP::Channel *channel, const std::string message) { + // something went wrong creating the channel +}); +```` It is important to realize that any error that occurs on a channel, will invalidate the entire channel,. including all subsequent instructions that @@ -212,9 +218,9 @@ myChannel.declareQueue("my-queue"); myChannel.declareExchange("my-exchange"); ```` -If the first declareQueue() call fails in the example above, your ChannelHandler::onError() -method will be called after a while to report this failure. And although the -second instruction to declare an exchange has already been sent to the server, it will be +If the first declareQueue() call fails in the example above, your Deferred::onError() +method will be called after a while to report this failure. And although the +second instruction to declare an exchange has already been sent to the server, it will be ignored because the channel was already in an invalid state after the first failure. You can overcome this by using multiple channels: @@ -223,7 +229,7 @@ You can overcome this by using multiple channels: Channel channel1(connection, &myHandler); Channel channel2(connection, &myHandler); channel1.declareQueue("my-queue"); -channel2.declareQueue("my-exchange"); +channel2.declareExchange("my-exchange"); ```` Now, if an error occurs with declaring the queue, it will not have @@ -233,51 +239,6 @@ RabbitMQ server, so some extra bytes are sent over the network, and some additional resources in both the client application and the RabbitMQ server are used (although this is all very limited). -Let's get back to the ChannelHandler class. It has many methods that you can -implement - all of which are optional. All methods in it have a default empty implementation, -so you can choose to only override the ones that you are interested in. When you're -writing a consumer application for example, you probably are only interested in -errors that occur, and in incoming messages: - -````c++ -#include - -class MyChannelHandler : public AMQP::ChannelHandler -{ -public: - /** - * Method that is called when an error occurs on the channel, and - * the channel ends up in an error state - * @param channel the channel on which the error occured - * @param message human readable error message - */ - virtual void onError(AMQP::Channel *channel, const std::string &message) - { - // @todo - // do something with the error message (like reporting it to the end-user) - // and destruct the channel object because it now no longer is usable - } - - /** - * Method that is called when a message has been received on a channel - * This message will be called for every message that is received after - * you started consuming. Make sure you acknowledge the messages when its - * safe to remove them from RabbitMQ (unless you set no-ack option when you - * started the consumer) - * @param channel the channel on which the consumer was started - * @param message the consumed message - * @param deliveryTag the delivery tag, you need this to acknowledge the message - * @param consumerTag the consumer identifier that was used to retrieve this message - * @param redelivered is this a redelivered message? - */ - virtual void onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) - { - // @todo - // do something with the incoming message - } -}; -```` - FLAGS AND TABLES ================ @@ -290,30 +251,30 @@ tables are used by many methods. ````c++ /** * Declare a queue - * + * * If you do not supply a name, a name will be assigned by the server. - * + * * The flags can be a combination of the following values: - * + * * - durable queue survives a broker restart * - autodelete queue is automatically removed when all connected consumers are gone * - passive only check if the queue exist * - exclusive the queue only exists for this connection, and is automatically removed when connection is gone - * + * * @param name name of the queue * @param flags combination of flags * @param arguments optional arguments */ -bool declareQueue(const std::string &name, int flags, const AMQP::Table &arguments); -bool declareQueue(const std::string &name, const AMQP::Table &arguments); -bool declareQueue(const std::string &name, int flags = 0); -bool declareQueue(int flags, const AMQP::Table &arguments); -bool declareQueue(const AMQP::Table &arguments); -bool declareQueue(int flags = 0); +Deferred& declareQueue(const std::string &name, int flags, const Table &arguments); +Deferred& declareQueue(const std::string &name, const Table &arguments); +Deferred& declareQueue(const std::string &name, int flags = 0); +Deferred& declareQueue(int flags, const Table &arguments); +Deferred& declareQueue(const Table &arguments); +Deferred& declareQueue(int flags = 0); ```` As you can see, the method comes in many forms, and it is up to you to choose -the one that is most appropriate. We now take a look at the most complete +the one that is most appropriate. We now take a look at the most complete one, the method with three parameters. Many methods in the Channel class accept an integer parameter named 'flags'. @@ -324,13 +285,13 @@ AMQP::durable + AMQP::autodelete. The declareQueue() method also accepts a parameter named 'arguments', which is of type Table. This Table object can be used as an associative array to send additional -options to RabbitMQ, that are often custom RabbitMQ extensions to the AMQP +options to RabbitMQ, that are often custom RabbitMQ extensions to the AMQP standard. For a list of all supported arguments, take a look at the documentation on the RabbitMQ website. With every new RabbitMQ release more features, and supported arguments are added. -The Table class is a very powerful class that enables you to build -complicated, deeply nested structures full of strings, arrays and even other +The Table class is a very powerful class that enables you to build +complicated, deeply nested structures full of strings, arrays and even other tables. In reality, you only need strings and integers. ````c++ @@ -354,13 +315,13 @@ you're publishing - all these parameters are standard C++ strings. More extended versions of the publish() method exist that accept additional arguments, and that enable you to publish entire Envelope objects, which are -objects that contain the message plus a list of optional meta information like -the content-type, content-encoding, priority, expire time and more. None of these -meta fields are interpreted by this library, and also the RabbitMQ ignores most -of them, but the AMQP protocol defines them, and they are free for you to use. -For an extensive list of the fields that are supported, take a look at the MetaData.h -header file (MetaData is the base class for Envelope). You should also check the -RabbitMQ documentation to find out if an envelope header is interpreted by the +objects that contain the message plus a list of optional meta information like +the content-type, content-encoding, priority, expire time and more. None of these +meta fields are interpreted by this library, and also the RabbitMQ ignores most +of them, but the AMQP protocol defines them, and they are free for you to use. +For an extensive list of the fields that are supported, take a look at the MetaData.h +header file (MetaData is the base class for Envelope). You should also check the +RabbitMQ documentation to find out if an envelope header is interpreted by the RabbitMQ server (at the time of this writing, only the expire time is being used). The following snippet is copied from the Channel.h header file and lists all @@ -370,17 +331,17 @@ in almost any form: ````c++ /** * Publish a message to an exchange - * + * * The following flags can be used - * + * * - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method * - immediate if set, a message that could not immediately be consumed is returned to the onReturned method - * + * * If either of the two flags is set, and the message could not immediately * be published, the message is returned by the server to the client. If you - * want to catch such returned messages, you need to implement the + * want to catch such returned messages, you need to implement the * ChannelHandler::onReturned() method. - * + * * @param exchange the exchange to publish to * @param routingkey the routing key * @param flags optional flags (see above) @@ -396,18 +357,29 @@ bool publish(const std::string &exchange, const std::string &routingKey, int fla bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size); ```` -Published messages are normally not confirmed by the server, hence there is no -ChannelHandler::onPublished() method that you can implement to find out if -a message was correctly received by the server. That's by design in the -AMQP protocol, to not unnecessarily slow down message publishing. As long -as no error is reported via the ChannelHandler::onError() method, you can safely +Published messages are normally not confirmed by the server, therefore the publish method does not +return a deferred. That's by design in the AMQP protocol, to not unnecessarily slow down message +publishing. As long as no error is reported via the ChannelHandler::onError() method, you can safely assume that your messages were delivered. -If you use the flags parameter to set either the option 'mandatory' or -'immediate', a message that could not be routed or directly delivered to a consumer -is sent back to the client, and ends up in the ChannelHandler::onReturned() -method. At the time of this writing however, the 'immediate' option does not -seem to be supported by RabbitMQ. +This can of course be a problem when you are publishing many messages. If you get an error halfway through +there is no way to know for sure how many messages made it to the broker and how many should be republished. +If this is important, you can wrap the publish commands inside a transaction. In this case, if an error occurs, +the transaction is automatically rolled back by RabbitMQ and none of the messages are actually published. + +````c++ +channel.startTransaction(); +channel.publish("my-exchange", "my-key", "my first message"); +channel.publish("my-exchange", "my-key", "another message"); +channel.commitTransaction() +.onSuccess([](AMQP::Channel *channel) { + // all messages were successfully published +}) +.onError([](AMQP::Channel *channel) { + // none of the messages were published + // now we have to do it all over again +}); +```` CONSUMING MESSAGES @@ -430,80 +402,63 @@ The full documentation from the C++ Channel.h headerfile looks like this: ````c++ /** * Tell the RabbitMQ server that we're ready to consume messages - * + * * After this method is called, RabbitMQ starts delivering messages to the client * application. The consume tag is a string identifier that will be passed to - * each received message, so that you can associate incoming messages with a + * each received message, so that you can associate incoming messages with a * consumer. If you do not specify a consumer tag, the server will assign one * for you. - * + * * The following flags are supported: - * + * * - nolocal if set, messages published on this channel are not also consumed * - noack if set, consumed messages do not have to be acked, this happens automatically * - exclusive request exclusive access, only this consumer can access the queue * - nowait the server does not have to send a response back that consuming is active - * - * The method ChannelHandler::onConsumerStarted() will be called when the + * + * The method ChannelHandler::onConsumerStarted() will be called when the * consumer has started (unless the nowait option was set, in which case * no confirmation method is called) - * + * * @param queue the queue from which you want to consume * @param tag a consumer tag that will be associated with this consume operation * @param flags additional flags * @param arguments additional arguments * @return bool */ -bool consume(const std::string &queue, const std::string &tag, int flags, const AMQP::Table &arguments); -bool consume(const std::string &queue, const std::string &tag, int flags = 0); -bool consume(const std::string &queue, const std::string &tag, const AMQP::Table &arguments); -bool consume(const std::string &queue, int flags, const AMQP::Table &arguments); -bool consume(const std::string &queue, int flags = 0); -bool consume(const std::string &queue, const AMQP::Table &arguments); +DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags, const AMQP::Table &arguments); +DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags = 0); +DeferredConsumer& consume(const std::string &queue, const std::string &tag, const AMQP::Table &arguments); +DeferredConsumer& consume(const std::string &queue, int flags, const AMQP::Table &arguments); +DeferredConsumer& consume(const std::string &queue, int flags = 0); +DeferredConsumer& consume(const std::string &queue, const AMQP::Table &arguments); ```` -In your ChannelHandler you can override the onConsumerStarted() method, that will be -first called before any messages are sent to you. Most users choose not to override this -method, because there is not much useful to do in it. After the consumer has started, however, -messages are starting to be sent from RabbitMQ to your client application, and they are all -passed to the ChannelHandler::onReceived() method. This method is thus very important to implement. +As you can see, the consume method returns a DeferredConsumer. This object is a regular Deferred, with the +addition of the onReceived method. This method can be used to retrieve incoming messages after consumption +has begun. + ````c++ -class MyChannelHandler : public AMQP::ChannelHandler -{ - /** - * Method that is called when a message has been received on a channel - * This message will be called for every message that is received after - * you started consuming. Make sure you acknowledge the messages when its - * safe to remove them from RabbitMQ (unless you set no-ack option when you - * started the consumer) - * @param channel the channel on which the consumer was started - * @param message the consumed message - * @param deliveryTag the delivery tag, you need this to acknowledge the message - * @param consumerTag the consumer identifier that was used to retrieve this message - * @param redelivered is this a redelivered message? - */ - virtual void onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) - { - // @todo - // add your own processing - - - // after the message was processed, acknowledge it - channel->ack(deliveryTag); - } -} +channel.consume("my-queue").onReceived([](AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) { + // @todo + // add your own processing + + + // after the message was processed, acknowledge it + channel->ack(deliveryTag); +}); ```` -The Message object holds all information of the delivered message: the actual content, +The Message object holds all information of the delivered message: the actual content, all meta information from the envelope (in fact, the Message class is derived from the Envelope class), and even the name of the exchange and the routing key that were used when the message was originally published. For a full list of all information in the Message class, you best have a look at the message.h, envelope.h and metadata.h header files. -Another important parameter to the onReceived() method is the deliveryTag parameter. This is a +Another important parameter to the onReceived() method is the deliveryTag parameter. This is a unique identifier that you need to acknowledge an incoming message. RabbitMQ only removes the -message after it has been acknowledged, so that if your application crashes while it was busy +message after it has been acknowledged, so that if your application crashes while it was busy processing the message, the message does not get lost but remains in the queue. But this means that after you've processed the message, you must inform RabbitMQ about it by calling the Channel:ack() method. This method is very simple and takes in its simplest form only one parameter: the @@ -518,10 +473,10 @@ or the entire TCP connection, consuming also stops. RabbitMQ throttles the number of messages that are delivered to you, to prevent that your application is flooded with messages from the queue, and to spread out the messages over multiple consumers. -This is done with a setting called quality-of-service (QOS). The QOS setting is a numeric value which -holds the number of unacknowledged messages that you are allowed to have. RabbitMQ stops sending -additional messages when the number of unacknowledges messages has reached this limit, and only -sends additional messages when an earlier message gets acknowledged. To change the QOS, you can +This is done with a setting called quality-of-service (QOS). The QOS setting is a numeric value which +holds the number of unacknowledged messages that you are allowed to have. RabbitMQ stops sending +additional messages when the number of unacknowledges messages has reached this limit, and only +sends additional messages when an earlier message gets acknowledged. To change the QOS, you can simple call Channel::setQos(). @@ -534,11 +489,12 @@ need additional attention: - ability to set up secure connections (or is this fully done on the IO level) - login with other protocols than login/password - publish confirms + - returned messages -We also need to add more safety checks so that strange or invalid data from -RabbitMQ does not break the library (although in reality RabbitMQ only sends +We also need to add more safety checks so that strange or invalid data from +RabbitMQ does not break the library (although in reality RabbitMQ only sends valid data). Also, when we now receive an answer from RabbitMQ that does not -match the request that we sent before, we do not report an error (this is also +match the request that we sent before, we do not report an error (this is also an issue that only occurs in theory). It would be nice to have sample implementations for the ConnectionHandler From 3680d621baf57dbee2109789a2ef65c32af6fd5b Mon Sep 17 00:00:00 2001 From: Luca Marturana Date: Fri, 11 Apr 2014 11:18:59 +0200 Subject: [PATCH 10/41] Bug fix on array serialization --- src/array.cpp | 14 ++++++++------ src/table.cpp | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/array.cpp b/src/array.cpp index 706572d..ca1d4cb 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -95,11 +95,11 @@ size_t Array::size() const size_t size = 4; // iterate over all elements - for (auto iter(_fields.begin()); iter != _fields.end(); ++iter) + for (auto item : _fields) { // add the size of the field type and size of element - size += sizeof((*iter)->typeID()); - size += (*iter)->size(); + size += sizeof(item->typeID()); + size += item->size(); } // return the result @@ -111,12 +111,14 @@ size_t Array::size() const */ void Array::fill(OutBuffer& buffer) const { + buffer.add(static_cast(size()-4)); + // iterate over all elements - for (auto iter(_fields.begin()); iter != _fields.end(); ++iter) + for (auto item : _fields) { // encode the element type and element - buffer.add((*iter)->typeID()); - (*iter)->fill(buffer); + buffer.add((uint8_t)item->typeID()); + item->fill(buffer); } } diff --git a/src/table.cpp b/src/table.cpp index 4be9b11..d0fb853 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -145,7 +145,7 @@ size_t Table::size() const void Table::fill(OutBuffer& buffer) const { // add size - buffer.add((uint32_t) size()-4); + buffer.add(static_cast(size()-4)); // loop through the fields for (auto iter(_fields.begin()); iter != _fields.end(); ++iter) From cf5def0e89fa41460614479f8ee0d47ff9db07b2 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 14 Apr 2014 13:34:46 +0200 Subject: [PATCH 11/41] fixed operator[] for arrays and strings, implemented << operator for field and fieldproxy objects to simplify debugging (reported from issue #7) --- amqpcpp.h | 1 + include/array.h | 51 +++++++++++++-- include/booleanset.h | 16 +++++ include/decimalfield.h | 10 +++ include/field.h | 18 +++++- include/fieldproxy.h | 141 ++++++++++++----------------------------- include/numericfield.h | 10 +++ include/stringfield.h | 10 +++ include/table.h | 29 +++++++++ src/Makefile | 2 +- src/array.cpp | 14 ++++ src/includes.h | 3 + 12 files changed, 197 insertions(+), 108 deletions(-) diff --git a/amqpcpp.h b/amqpcpp.h index 3ae5c67..8f905c1 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -17,6 +17,7 @@ #include #include #include +#include // base C include files #include diff --git a/include/array.h b/include/array.h index 7e59406..ca165c2 100644 --- a/include/array.h +++ b/include/array.h @@ -83,8 +83,20 @@ public: */ Array set(uint8_t index, const Field &value) { - // copy to a new pointer and store it - _fields[index] = std::shared_ptr(value.clone()); + // construct a shared pointer + auto ptr = std::shared_ptr(value.clone()); + + // should we overwrite an existing record? + if (index <= _fields.size()) + { + // append index + _fields.push_back(ptr); + } + else + { + // overwrite pointer + _fields[index] = ptr; + } // allow chaining return *this; @@ -108,14 +120,14 @@ public: uint32_t count() const; /** - * Remove last element from array + * Remove last element from array */ void pop_back(); /** - * Add field to end of array + * Add field to end of array * - * @param value + * @param value */ void push_back(const Field &value); @@ -145,6 +157,35 @@ public: { return 'A'; } + + /** + * Output the object to a stream + * @param std::ostream + */ + virtual void output(std::ostream &stream) const + { + // prefix + stream << "array("; + + // is this the first iteration + bool first = true; + + // loop through all members + for (auto iter : _fields) + { + // split with comma + if (!first) stream << ","; + + // show output + stream << *iter; + + // no longer first iter + first = false; + } + + // postfix + stream << ")"; + } }; /** diff --git a/include/booleanset.h b/include/booleanset.h index 577fe47..f165643 100644 --- a/include/booleanset.h +++ b/include/booleanset.h @@ -83,6 +83,22 @@ public: return new BooleanSet(*this); } + /** + * Output the object to a stream + * @param std::ostream + */ + virtual void output(std::ostream &stream) const override + { + // prefix + stream << "booleanset("; + + // the members + for (int i=0; i<8; i++) stream << (i == 0 ? "" : ",") << (get(i) ? 1 : 0); + + // postfix + stream << ")"; + } + /** * Get one of the booleans * @param index from 0 to 7, where 0 is rightmost bit diff --git a/include/decimalfield.h b/include/decimalfield.h index c9d3cfc..3164286 100644 --- a/include/decimalfield.h +++ b/include/decimalfield.h @@ -88,6 +88,16 @@ public: return new DecimalField(_places, _number); } + /** + * Output the object to a stream + * @param std::ostream + */ + virtual void output(std::ostream &stream) const override + { + // output floating point value + stream << "decimal(" << _number / pow(10, _places) << ")"; + } + /** * Assign a new value * diff --git a/include/field.h b/include/field.h index 079c741..76f89ba 100644 --- a/include/field.h +++ b/include/field.h @@ -59,9 +59,25 @@ public: */ virtual char typeID() const = 0; - + /** + * Output the object to a stream + * @param std::ostream + */ + virtual void output(std::ostream &stream) const = 0; }; +/** + * Custom output stream operator + * @param stream + * @param field + * @return ostream + */ +inline std::ostream &operator<<(std::ostream &stream, const Field &field) +{ + field.output(stream); + return stream; +} + /** * end namespace */ diff --git a/include/fieldproxy.h b/include/fieldproxy.h index ad8c445..5cafcdc 100644 --- a/include/fieldproxy.h +++ b/include/fieldproxy.h @@ -196,21 +196,14 @@ public: // cast to a string return operator=(std::string(value)); } - + /** - * Get boolean value - * @return BooleanSet + * Get the underlying field + * @return Field */ - operator BooleanSet () + const Field &get() const { - // the value - BooleanSet value; - - // retrieve the value - _source->get(_index, value); - - // return the result - return value; + return _source->get(_index); } /** @@ -219,14 +212,8 @@ public: */ operator bool () { - // the value - BooleanSet value; - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); + return _source->get(_index); } /** @@ -235,14 +222,8 @@ public: */ operator int8_t () { - // the value - Octet value; - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); + return _source->get(_index); } /** @@ -251,14 +232,8 @@ public: */ operator uint8_t () { - // the value - UOctet value; - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); + return _source->get(_index); } /** @@ -267,14 +242,8 @@ public: */ operator int16_t () { - // the value - Short value; - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); + return _source->get(_index); } /** @@ -283,14 +252,8 @@ public: */ operator uint16_t () { - // the value - UShort value; - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); + return _source->get(_index); } /** @@ -299,14 +262,8 @@ public: */ operator int32_t () { - // the value - Long value; - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); + return _source->get(_index); } /** @@ -315,14 +272,8 @@ public: */ operator uint32_t () { - // the value - ULong value; - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); + return _source->get(_index); } /** @@ -331,14 +282,8 @@ public: */ operator int64_t () { - // the value - Long value; - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); + return _source->get(_index); } /** @@ -347,30 +292,8 @@ public: */ operator uint64_t () { - // the value - ULong value; - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); - } - - /** - * Get decimal value - * @return DecimalField - */ - operator DecimalField () - { - // the value - DecimalField value; - - // retrieve the value - _source->get(_index, value); - - // return the result - return value.value(); + return _source->get(_index); } /** @@ -379,16 +302,8 @@ public: */ operator std::string () { - // it has to be either a short or a long string - ShortString shortValue; - LongString longValue; - - // try to retrieve the value - if (_source->get(_index, shortValue)) return shortValue.value(); - if (_source->get(_index, longValue)) return longValue.value(); - - // no valid string found - return std::string(""); + // retrieve the value + return _source->get(_index); } }; @@ -396,6 +311,30 @@ public: typedef FieldProxy AssociativeFieldProxy; typedef FieldProxy ArrayFieldProxy; +/** + * Custom output stream operator + * @param stream + * @param field + * @return ostream + */ +inline std::ostream &operator<<(std::ostream &stream, const AssociativeFieldProxy &field) +{ + // get underlying field, and output that + return stream << field.get(); +} + +/** + * Custom output stream operator + * @param stream + * @param field + * @return ostream + */ +inline std::ostream &operator<<(std::ostream &stream, const ArrayFieldProxy &field) +{ + // get underlying field, and output that + return stream << field.get(); +} + /** * end namespace */ diff --git a/include/numericfield.h b/include/numericfield.h index 82edf99..9a60e4d 100644 --- a/include/numericfield.h +++ b/include/numericfield.h @@ -157,6 +157,16 @@ public: { return F; } + + /** + * Output the object to a stream + * @param std::ostream + */ + virtual void output(std::ostream &stream) const override + { + // show + stream << "numeric(" << value() << ")"; + } }; /** diff --git a/include/stringfield.h b/include/stringfield.h index 724b495..3def910 100644 --- a/include/stringfield.h +++ b/include/stringfield.h @@ -145,6 +145,16 @@ public: { return F; } + + /** + * Output the object to a stream + * @param std::ostream + */ + virtual void output(std::ostream &stream) const override + { + // show + stream << "string(" << value() << ")"; + } }; /** diff --git a/include/table.h b/include/table.h index 3435f13..f7682e5 100644 --- a/include/table.h +++ b/include/table.h @@ -136,6 +136,35 @@ public: { return 'F'; } + + /** + * Output the object to a stream + * @param std::ostream + */ + virtual void output(std::ostream &stream) const + { + // prefix + stream << "table("; + + // is this the first iteration + bool first = true; + + // loop through all members + for (auto iter : _fields) + { + // split with comma + if (!first) stream << ","; + + // show output + stream << iter.first << ":" << *iter.second; + + // no longer first iter + first = false; + } + + // postfix + stream << ")"; + } }; /** diff --git a/src/Makefile b/src/Makefile index 3a9b4e9..4f0baa2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,6 @@ CPP = g++ RM = rm -f -CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++11 -g +CPPFLAGS = -Wall -c -I. -g -flto -std=c++11 -g LD = g++ LD_FLAGS = -Wall -shared -O2 SHARED_LIB = libamqpcpp.so diff --git a/src/array.cpp b/src/array.cpp index ca1d4cb..175c168 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -70,16 +70,27 @@ const Field &Array::get(uint8_t index) return *_fields[index]; } +/** + * Number of entries in the array + * @return uint32_t + */ uint32_t Array::count() const { return _fields.size(); } +/** + * Remove a field from the array + */ void Array::pop_back() { _fields.pop_back(); } +/** + * Add a field to the array + * @param value + */ void Array::push_back(const Field& value) { _fields.push_back(std::shared_ptr(value.clone())); @@ -88,6 +99,7 @@ void Array::push_back(const Field& value) /** * Get the size this field will take when * encoded in the AMQP wire-frame format + * @return size_t */ size_t Array::size() const { @@ -108,9 +120,11 @@ size_t Array::size() const /** * Write encoded payload to the given buffer. + * @param buffer */ void Array::fill(OutBuffer& buffer) const { + // store total size for all elements buffer.add(static_cast(size()-4)); // iterate over all elements diff --git a/src/includes.h b/src/includes.h index 52b31d0..d6c8e0f 100644 --- a/src/includes.h +++ b/src/includes.h @@ -7,6 +7,9 @@ * @documentation private */ +// for debug +#include + // include the generic amqp functions #include "../amqpcpp.h" From 1c0495378ae2bc7058305ffe24cdac4232aa14c5 Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Mon, 14 Apr 2014 14:10:57 +0200 Subject: [PATCH 12/41] Implemented deferred consumers and a setTimeout method on the connection handler for indicating immediate failures on deferred objects --- amqpcpp.h | 2 + include/connection.h | 18 --------- include/connectionhandler.h | 36 ++++++++++++------ include/connectionimpl.h | 2 +- include/deferred.h | 5 +-- include/deferredconsumer.h | 76 +++++++++++++++++++++++++++++++++++++ src/channelimpl.cpp | 22 ++++++----- src/connectionimpl.cpp | 70 +++++++++++++++++----------------- 8 files changed, 151 insertions(+), 80 deletions(-) create mode 100644 include/deferredconsumer.h diff --git a/amqpcpp.h b/amqpcpp.h index 908da12..e2173e8 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -6,6 +6,8 @@ * @documentation public */ +#pragma once + // base C++ include files #include #include diff --git a/include/connection.h b/include/connection.h index a5fed2b..a67e6ad 100644 --- a/include/connection.h +++ b/include/connection.h @@ -22,24 +22,6 @@ private: */ ConnectionImpl _implementation; - /** - * Function to execute code after a certain timeout. - * - * If the timeout is 0, the code is supposed to be run - * in the next iteration of the event loop. - * - * This is a simple placeholder function that will just - * execute the code immediately, it should be overridden - * by the timeout function the used event loop has. - * - * @param timeout the amount of time to wait - * @param callback the callback to execute after the timeout - */ - std::function)> _timeoutHandler = [](double timeout, const std::function& callback) { - // execute callback immediately - callback(); - }; - public: /** * Construct an AMQP object based on full login data diff --git a/include/connectionhandler.h b/include/connectionhandler.h index 7a90b50..f5c1d0c 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -4,7 +4,7 @@ * * Interface that should be implemented by the caller of the library and * that is passed to the AMQP connection. This interface contains all sorts - * of methods that are called when data needs to be sent, or when the + * of methods that are called when data needs to be sent, or when the * AMQP connection ends up in a broken state. * * @copyright 2014 Copernica BV @@ -21,10 +21,22 @@ namespace AMQP { class ConnectionHandler { public: + /** + * Set a function to be executed after a given timeout. + * + * This function is not strictly necessary to implement. If you + * do not implement it, certain channel methods that fail + * immediately will not be reported. + * + * @param timeout number of seconds to wait + * @param callback function to execute once time runs out + */ + virtual void setTimeout(double seconds, const std::function& callback) {} + /** * Method that is called when data needs to be sent over the network * - * Note that the AMQP library does no buffering by itself. This means + * Note that the AMQP library does no buffering by itself. This means * that this method should always send out all data or do the buffering * itself. * @@ -33,28 +45,28 @@ public: * @param size Size of the buffer */ virtual void onData(Connection *connection, const char *buffer, size_t size) = 0; - + /** * When the connection ends up in an error state this method is called. * This happens when data comes in that does not match the AMQP protocol - * + * * After this method is called, the connection no longer is in a valid * state and can no longer be used. - * + * * This method has an empty default implementation, although you are very * much advised to implement it. Because when an error occurs, the connection * is no longer usable, so you probably want to know. - * + * * @param connection The connection that entered the error state * @param message Error message */ virtual void onError(Connection *connection, const std::string &message) {} - + /** * Method that is called when the login attempt succeeded. After this method * was called, the connection is ready to use. This is the first method * that is normally called after you've constructed the connection object. - * + * * According to the AMQP protocol, you must wait for the connection to become * ready (and this onConnected method to be called) before you can start * using the Connection object. However, this AMQP library will cache all @@ -64,17 +76,17 @@ public: * @param connection The connection that can now be used */ virtual void onConnected(Connection *connection) {} - + /** * Method that is called when the connection was closed. - * + * * This is the counter part of a call to Connection::close() and it confirms * that the connection was correctly closed. - * + * * @param connection The connection that was closed and that is now unusable */ virtual void onClosed(Connection *connection) {} - + }; /** diff --git a/include/connectionimpl.h b/include/connectionimpl.h index 26f7f63..bcbeed0 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -298,7 +298,7 @@ public: * The actual connection is a friend and can construct this class */ friend class Connection; - + friend class ChannelImpl; }; /** diff --git a/include/deferred.h b/include/deferred.h index 489c537..e2fb2d0 100644 --- a/include/deferred.h +++ b/include/deferred.h @@ -56,11 +56,8 @@ private: * * @param error description of the error that occured */ - void error(const std::string& error) + void error(const std::string& error) const { - // we are now in a failed state - _failed = true; - // execute callbacks if registered if (_errorCallback) _errorCallback(_channel, error); if (_finalizeCallback) _finalizeCallback(_channel, error); diff --git a/include/deferredconsumer.h b/include/deferredconsumer.h new file mode 100644 index 0000000..3610bd7 --- /dev/null +++ b/include/deferredconsumer.h @@ -0,0 +1,76 @@ +/** + * DeferredConsumer.h + * + * Deferred callback for consumers + * + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * We extend from the default deferred and add extra functionality + */ +class DeferredConsumer : public Deferred +{ +private: + /** + * Callback to execute when a message arrives + */ + std::function _messageCallback; + + /** + * Process a message + * + * @param message the message to process + * @param deliveryTag the message delivery tag + * @param consumerTag the tag we are consuming under + * @param is this a redelivered message? + */ + void message(const Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) const + { + // do we have a valid callback + if (_messageCallback) _messageCallback(_channel, message, deliveryTag, consumerTag, redelivered); + } + + /** + * The channel implementation may call our + * private members and construct us + */ + friend class ChannelImpl; + friend class ConsumedMessage; +protected: + /** + * Protected constructor that can only be called + * from within the channel implementation + * + * @param channel the channel we operate under + * @param boolea are we already failed? + */ + DeferredConsumer(Channel *channel, bool failed = false) : + Deferred(channel, failed) + {} +public: + /** + * Register a function to be called when a message arrives + * + * Only one callback can be registered. Successive calls + * to this function will clear callbacks registered before. + * + * @param callback the callback to execute + */ + DeferredConsumer& onReceived(const std::function& callback) + { + // store callback + _messageCallback = callback; + return *this; + } +}; + +/** + * End namespace + */ +} diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index f65237f..8607026 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -540,25 +540,27 @@ template Deferred& ChannelImpl::send(const Frame &frame, const char *message) { // create a new deferred handler and get a pointer to it - auto &handler = _callbacks.push_back(Deferred(_parent)); + // note: cannot use auto here or the lambda below chokes + // when compiling under gcc 4.8 + Deferred *handler = &_callbacks.push_back(Deferred(_parent)); // send the frame over the channel if (!send(frame)) { // we can immediately put the handler in failed state - handler._failed = true; + handler->_failed = true; - // the frame could not be send - // we should register an error - // on the handler, but only after - // a timeout, so a handler can - // be attached first - - // TODO + // register an error on the deferred handler + // after a timeout, so it gets called only + // after a possible handler was installed. + _connection->_handler->setTimeout(0, [handler, message]() { + // emit an error on the handler + handler->error(message); + }); } // return the new handler - return handler; + return *handler; } /** diff --git a/src/connectionimpl.cpp b/src/connectionimpl.cpp index 8f40fde..668fbb3 100644 --- a/src/connectionimpl.cpp +++ b/src/connectionimpl.cpp @@ -17,13 +17,13 @@ namespace AMQP { /** * Construct an AMQP object based on full login data - * + * * The first parameter is a handler object. This handler class is * an interface that should be implemented by the caller. - * + * * Note that the constructor is private to ensure that nobody can construct * this class, only the real Connection class via a friend construct - * + * * @param parent Parent connection object * @param handler Connection handler * @param login Login data @@ -42,7 +42,7 @@ ConnectionImpl::~ConnectionImpl() { // close the connection in a nice fashion close(); - + // invalidate all channels, so they will no longer call methods on this channel object for (auto iter = _channels.begin(); iter != _channels.end(); iter++) iter->second->invalidate(); } @@ -57,20 +57,20 @@ uint16_t ConnectionImpl::add(ChannelImpl *channel) { // check if we have exceeded the limit already if (_maxChannels > 0 && _channels.size() >= _maxChannels) return 0; - + // keep looping to find an id that is not in use while (true) { // is this id in use? if (_nextFreeChannel > 0 && _channels.find(_nextFreeChannel) == _channels.end()) break; - + // id is in use, move on _nextFreeChannel++; } - + // we have a new channel _channels[_nextFreeChannel] = channel; - + // done return _nextFreeChannel++; } @@ -90,7 +90,7 @@ void ConnectionImpl::remove(ChannelImpl *channel) /** * Parse the buffer into a recognized frame - * + * * Every time that data comes in on the connection, you should call this method to parse * the incoming data, and let it handle by the AMQP library. This method returns the number * of bytes that were processed. @@ -108,13 +108,13 @@ size_t ConnectionImpl::parse(const char *buffer, size_t size) { // do not parse if already in an error state if (_state == state_closed) return 0; - + // number of bytes processed size_t processed = 0; - + // create a monitor object that checks if the connection still exists Monitor monitor(this); - + // keep looping until we have processed all bytes, and the monitor still // indicates that the connection is in a valid state while (size > 0 && monitor.valid()) @@ -131,7 +131,7 @@ size_t ConnectionImpl::parse(const char *buffer, size_t size) // number of bytes processed size_t bytes = receivedFrame.totalSize(); - + // add bytes processed += bytes; size -= bytes; buffer += bytes; } @@ -139,12 +139,12 @@ size_t ConnectionImpl::parse(const char *buffer, size_t size) { // something terrible happened on the protocol (like data out of range) reportError(exception.what()); - + // done return processed; } } - + // done return processed; } @@ -161,13 +161,13 @@ bool ConnectionImpl::close() // mark that the object is closed _closed = true; - + // if still busy with handshake, we delay closing for a while if (_state == state_handshake || _state == state_protocol) return true; // perform the close operation sendClose(); - + // done return true; } @@ -181,26 +181,26 @@ bool ConnectionImpl::sendClose() { // after the send operation the object could be dead Monitor monitor(this); - + // loop over all channels for (auto iter = _channels.begin(); iter != _channels.end(); iter++) { // close the channel iter->second->close(); - + // we could be dead now if (!monitor.valid()) return false; } - + // send the close frame send(ConnectionCloseFrame(0, "shutdown")); - + // leap out if object no longer is alive if (!monitor.valid()) return false; - + // we're in a new state _state = state_closing; - + // done return true; } @@ -214,20 +214,20 @@ void ConnectionImpl::setConnected() _state = state_connected; // if the close operation was already called, we do that again now again - // so that the actual messages to close down the connection and the channel + // so that the actual messages to close down the connection and the channel // are appended to the queue if (_closed && !sendClose()) return; - + // we're going to call the handler, which can destruct the connection, // so we must monitor if the queue object is still valid after calling Monitor monitor(this); - + // inform handler _handler->onConnected(_parent); - + // leap out if the connection no longer exists if (!monitor.valid()) return; - + // empty the queue of messages while (!_queue.empty()) { @@ -236,10 +236,10 @@ void ConnectionImpl::setConnected() // remove it from the queue _queue.pop(); - + // send it _handler->onData(_parent, buffer.data(), buffer.size()); - + // leap out if the connection was destructed if (!monitor.valid()) return; } @@ -254,16 +254,16 @@ bool ConnectionImpl::send(const Frame &frame) { // its not possible to send anything if closed or closing down if (_state == state_closing || _state == state_closed) return false; - + // we need an output buffer OutBuffer buffer(frame.totalSize()); - + // fill the buffer frame.fill(buffer); - + // append an end of frame byte (but not when still negotiating the protocol) if (frame.needsSeparator()) buffer.add((uint8_t)206); - + // are we still setting up the connection? if ((_state == state_connected && _queue.size() == 0) || frame.partOfHandshake()) { @@ -275,7 +275,7 @@ bool ConnectionImpl::send(const Frame &frame) // the connection is still being set up, so we need to delay the message sending _queue.push(std::move(buffer)); } - + // done return true; } From f10e33c7c7f14d5d2a5b618b839284cbf1fc6271 Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Mon, 14 Apr 2014 14:18:51 +0200 Subject: [PATCH 13/41] Fix logic error in the array type --- include/array.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/array.h b/include/array.h index ca165c2..4eb5c30 100644 --- a/include/array.h +++ b/include/array.h @@ -85,9 +85,9 @@ public: { // construct a shared pointer auto ptr = std::shared_ptr(value.clone()); - + // should we overwrite an existing record? - if (index <= _fields.size()) + if (index >= _fields.size()) { // append index _fields.push_back(ptr); From 8b0cc3dcc8880d956bff6beb1c38357d0ea88b50 Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Mon, 14 Apr 2014 14:33:00 +0200 Subject: [PATCH 14/41] We need the iostream to be included so fields can be shown --- amqpcpp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/amqpcpp.h b/amqpcpp.h index e2173e8..9f84823 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -20,6 +20,7 @@ #include #include #include +#include // base C include files #include From f7838492fb7a763ee606b4930045e4c804d3e94c Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Mon, 14 Apr 2014 15:23:53 +0200 Subject: [PATCH 15/41] Removed tests: they are moved to the REACT-CPP-AMQP library --- tests/Makefile | 19 --- tests/README.md | 2 - tests/main.cpp | 55 -------- tests/myconnection.cpp | 293 ----------------------------------------- tests/myconnection.h | 118 ----------------- 5 files changed, 487 deletions(-) delete mode 100644 tests/Makefile delete mode 100644 tests/README.md delete mode 100644 tests/main.cpp delete mode 100644 tests/myconnection.cpp delete mode 100644 tests/myconnection.h diff --git a/tests/Makefile b/tests/Makefile deleted file mode 100644 index f1602b5..0000000 --- a/tests/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -CPP = g++ -CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++11 -g -LD = g++ -LDFLAGS = -lamqpcpp -lcopernica_event -lcopernica_network -lev -RESULT = a.out -SOURCES = $(wildcard *.cpp) -OBJECTS = $(SOURCES:%.cpp=%.o) - -all: ${OBJECTS} ${RESULT} - -${RESULT}: ${OBJECTS} - ${LD} -o $@ ${OBJECTS} ${LDFLAGS} - -clean: - ${RM} *.obj *~* ${OBJECTS} ${RESULT} - -${OBJECTS}: - ${CPP} ${CPPFLAGS} -o $@ ${@:%.o=%.cpp} - diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 99ebf42..0000000 --- a/tests/README.md +++ /dev/null @@ -1,2 +0,0 @@ -I'm sorry, the test case makes use of the closed source Copernica libraries. Maybe someone -is willing to provide a test case based on plain system calls? diff --git a/tests/main.cpp b/tests/main.cpp deleted file mode 100644 index f0c1b04..0000000 --- a/tests/main.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Main.cpp - * - * Test program - * - * @copyright 2014 Copernica BV - */ - -/** - * Global libraries that we need - */ -#include -#include - -/** - * Namespaces to use - */ -using namespace std; -using namespace Copernica; - -/** - * Local libraries - */ -#include "myconnection.h" - -/** - * Main procedure - * @param argc - * @param argv - * @return int - */ -int main(int argc, const char *argv[]) -{ - // need an ip - if (argc != 2) - { - // report error - std::cerr << "usage: " << argv[0] << " " << std::endl; - - // done - return -1; - } - else - { - // create connection - MyConnection connection(argv[1]); - - // start the main event loop - Event::MainLoop::instance()->run(); - - // done - return 0; - } -} - diff --git a/tests/myconnection.cpp b/tests/myconnection.cpp deleted file mode 100644 index 7234ad7..0000000 --- a/tests/myconnection.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/** - * MyConnection.cpp - * - * @copyright 2014 Copernica BV - */ - -/** - * Required external libraries - */ -#include -#include - -#include - -/** - * Namespaces to use - */ -using namespace std; -using namespace Copernica; - -/** - * Required local class definitions - */ -#include "myconnection.h" - -/** - * Constructor - */ -MyConnection::MyConnection(const std::string &ip) : - _socket(Event::MainLoop::instance(), this), - _connection(nullptr), - _channel(nullptr) -{ - // start connecting - if (_socket.connect(Network::Ipv4Address(ip), 5672)) return; - - // failure - onFailure(&_socket); -} - -/** - * Method that is called when the connection failed - * @param socket Pointer to the socket - */ -void MyConnection::onFailure(Network::TcpSocket *socket) -{ - // report error - std::cout << "connect failure" << std::endl; -} - -/** - * Method that is called when the connection timed out (which also is a failure - * @param socket Pointer to the socket - */ -void MyConnection::onTimeout(Network::TcpSocket *socket) -{ - // report error - std::cout << "connect timeout" << std::endl; -} - -/** - * Method that is called when the connection succeeded - * @param socket Pointer to the socket - */ -void MyConnection::onConnected(Network::TcpSocket *socket) -{ - // report connection - std::cout << "connected" << std::endl; - - // we are connected, leap out if there already is a amqp connection - if (_connection) return; - - // create amqp connection, and a new channel - _connection = std::unique_ptr(new AMQP::Connection(this, AMQP::Login("guest", "guest"), "/")); - _channel = std::unique_ptr(new AMQP::Channel(_connection.get())); - - // watch for the channel becoming ready - _channel->onReady([](AMQP::Channel *channel) { - // show that we are ready - std::cout << "AMQP channel ready, id: " << (int) channel->id() << std::endl; - }); - - // and of course for channel errors - _channel->onError([this](AMQP::Channel *channel, const std::string& message) { - // inform the user of the error - std::cerr << "AMQP channel error on channel " << channel->id() << ": " << message << std::endl; - - // delete the channel - _channel = nullptr; - - // close the connection - _connection->close(); - }); - - // declare a queue and let us know when it succeeds - _channel->declareQueue("my_queue").onSuccess([](AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount){ - // queue was successfully declared - std::cout << "AMQP Queue declared with name '" << name << "', " << messageCount << " messages and " << consumerCount << " consumer" << std::endl; - }); - - // also declare an exchange - _channel->declareExchange("my_exchange", AMQP::direct).onSuccess([](AMQP::Channel *channel) { - // exchange successfully declared - std::cout << "AMQP exchange declared" << std::endl; - }); - - // bind the queue to the exchange - _channel->bindQueue("my_exchange", "my_queue", "key").onSuccess([](AMQP::Channel *channel) { - // queue successfully bound to exchange - std::cout << "AMQP Queue bound" << std::endl; - }); - - // set quality of service - _channel->setQos(1).onSuccess([](AMQP::Channel *channel) { - // quality of service successfully set - std::cout << "AMQP Quality of Service set" << std::endl; - }); - - // publish a message to the exchange - if (!_channel->publish("my_exchange", "key", "my_message")) - { - // we could not publish the message, something is wrong somewhere - std::cerr << "Unable to publish message" << std::endl; - - // close the channel - _channel->close().onSuccess([this](AMQP::Channel *channel) { - // also close the connection - _connection->close(); - }); - } - - // consume the message we just published - _channel->consume("my_queue", "my_consumer", AMQP::exclusive) - .onReceived([this](AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) { - // show the message data - std::cout << "AMQP consumed: " << message.message() << std::endl; - - // ack the message - _channel->ack(deliveryTag); - - // and stop consuming (there is only one message anyways) - _channel->cancel("my_consumer").onSuccess([](AMQP::Channel *channel, const std::string& tag) { - // we successfully stopped consuming - std::cout << "Stopped consuming under tag " << tag << std::endl; - }); - - // unbind the queue again - _channel->unbindQueue("my_exchange", "my_queue", "key").onSuccess([](AMQP::Channel *channel) { - // queueu successfully unbound - std::cout << "Queue unbound" << std::endl; - }); - - // the queue should now be empty, so we can delete it - _channel->removeQueue("my_queue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) { - // queue was removed, it should have been empty, so messageCount should be 0 - if (messageCount) std::cerr << "Removed queue which should have been empty but contained " << messageCount << " messages" << std::endl; - - // no messages is the expected behavior - else std::cout << "Queue removed" << std::endl; - }); - - // also remove the exchange - _channel->removeExchange("my_exchange").onSuccess([](AMQP::Channel *channel) { - // exchange was successfully removed - std::cout << "Removed exchange" << std::endl; - }); - - // everything done, close the channel - _channel->close().onSuccess([this](AMQP::Channel *channel) { - // channel was closed - std::cout << "Channel closed" << std::endl; - - // close the connection too - _connection->close(); - }); - }) - .onSuccess([](AMQP::Channel *channel, const std::string& tag) { - // consumer was started - std::cout << "Started consuming under tag " << tag << std::endl; - }); -} - -/** - * Method that is called when the socket is closed (as a result of a TcpSocket::close() call) - * @param socket Pointer to the socket - */ -void MyConnection::onClosed(Network::TcpSocket *socket) -{ - // show - std::cout << "myconnection closed" << std::endl; - - // close the channel and connection - _channel = nullptr; - _connection = nullptr; - - // stop the loop - Event::MainLoop::instance()->stop(); -} - -/** - * Method that is called when the peer closed the connection - * @param socket Pointer to the socket - */ -void MyConnection::onLost(Network::TcpSocket *socket) -{ - // report error - std::cout << "connection lost" << std::endl; - - // close the channel and connection - _channel = nullptr; - _connection = nullptr; - - // stop the loop - Event::MainLoop::instance()->stop(); -} - -/** - * Method that is called when data is received on the socket - * @param socket Pointer to the socket - * @param buffer Pointer to the fill input buffer - */ -void MyConnection::onData(Network::TcpSocket *socket, Network::Buffer *buffer) -{ - // leap out if there is no connection - if (!_connection) return; - - // let the data be handled by the connection - size_t bytes = _connection->parse(buffer->data(), buffer->size()); - - // shrink the buffer - buffer->shrink(bytes); -} - -/** - * Method that is called when data needs to be sent over the network - * - * Note that the AMQP library does no buffering by itself. This means - * that this method should always send out all data or do the buffering - * itself. - * - * @param connection The connection that created this output - * @param buffer Data to send - * @param size Size of the buffer - */ -void MyConnection::onData(AMQP::Connection *connection, const char *buffer, size_t size) -{ - // send to the socket - _socket.write(buffer, size); -} - -/** - * Method that is called when the connection to AMQP was closed - * @param connection pointer to connection object - */ -void MyConnection::onClosed(AMQP::Connection *connection) -{ - // report that AMQP connection is closed - std::cout << "AMQP connection closed" << std::endl; - - // close the underlying socket - _socket.close(); -} - -/** - * When the connection ends up in an error state this method is called. - * This happens when data comes in that does not match the AMQP protocol - * - * After this method is called, the connection no longer is in a valid - * state and can be used. In normal circumstances this method is not called. - * - * @param connection The connection that entered the error state - * @param message Error message - */ -void MyConnection::onError(AMQP::Connection *connection, const std::string &message) -{ - // report error - std::cout << "AMQP Connection error: " << message << std::endl; -} - -/** - * Method that is called when the login attempt succeeded. After this method - * was called, the connection is ready to use - * - * @param connection The connection that can now be used - */ -void MyConnection::onConnected(AMQP::Connection *connection) -{ - // show - std::cout << "AMQP login success" << std::endl; - - // create channel if it does not yet exist - if (!_channel) _channel = std::unique_ptr(new AMQP::Channel(connection)); -} diff --git a/tests/myconnection.h b/tests/myconnection.h deleted file mode 100644 index 698428b..0000000 --- a/tests/myconnection.h +++ /dev/null @@ -1,118 +0,0 @@ -/** - * MyConnection.h - * - * Our own test implementation for a connection handler - * - * @copyright 2014 Copernica BV - */ - -/** - * Class definition - */ -class MyConnection : - public AMQP::ConnectionHandler, - public Network::TcpHandler -{ -private: - /** - * The actual TCP socket that is connected with RabbitMQ - * @var TcpSocket - */ - Network::TcpSocket _socket; - - /** - * The AMQP connection - * @var Connection - */ - std::unique_ptr _connection; - - /** - * The AMQP channel - * @var Channel - */ - std::unique_ptr _channel; - - /** - * Method that is called when the connection failed - * @param socket Pointer to the socket - */ - virtual void onFailure(Network::TcpSocket *socket) override; - - /** - * Method that is called when the connection timed out (which also is a failure - * @param socket Pointer to the socket - */ - virtual void onTimeout(Network::TcpSocket *socket) override; - - /** - * Method that is called when the connection succeeded - * @param socket Pointer to the socket - */ - virtual void onConnected(Network::TcpSocket *socket) override; - - /** - * Method that is called when the socket is closed (as a result of a TcpSocket::close() call) - * @param socket Pointer to the socket - */ - virtual void onClosed(Network::TcpSocket *socket) override; - - /** - * Method that is called when the peer closed the connection - * @param socket Pointer to the socket - */ - virtual void onLost(Network::TcpSocket *socket) override; - - /** - * Method that is called when data is received on the socket - * @param socket Pointer to the socket - * @param buffer Pointer to the fill input buffer - */ - virtual void onData(Network::TcpSocket *socket, Network::Buffer *buffer) override; - - /** - * Method that is called when data needs to be sent over the network - * - * Note that the AMQP library does no buffering by itself. This means - * that this method should always send out all data or do the buffering - * itself. - * - * @param connection The connection that created this output - * @param buffer Data to send - * @param size Size of the buffer - */ - virtual void onData(AMQP::Connection *connection, const char *buffer, size_t size) override; - - /** - * Method that is called when the connection to AMQP was closed - * @param connection pointer to connection object - */ - virtual void onClosed(AMQP::Connection *connection) override; - - /** - * When the connection ends up in an error state this method is called. - * This happens when data comes in that does not match the AMQP protocol - * - * After this method is called, the connection no longer is in a valid - * state and can be used. In normal circumstances this method is not called. - * - * @param connection The connection that entered the error state - * @param message Error message - */ - virtual void onError(AMQP::Connection *connection, const std::string &message) override; - - /** - * Method that is called when the login attempt succeeded. After this method - * was called, the connection is ready to use - * - * @param connection The connection that can now be used - */ - virtual void onConnected(AMQP::Connection *connection) override; - -public: - /** - * Constructor - * @param ip - */ - MyConnection(const std::string &ip); -}; - From 83621790f4aab8af5787005385f8f7324c57f1a3 Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Mon, 14 Apr 2014 15:31:15 +0200 Subject: [PATCH 16/41] Give the connection object to the timeout as well --- include/connectionhandler.h | 3 ++- src/channelimpl.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/connectionhandler.h b/include/connectionhandler.h index 84210f2..2ffe23b 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -28,10 +28,11 @@ public: * do not implement it, certain methods that fail immediately * will not be reported. * + * @param connection the connection that needs the timeout * @param timeout number of seconds to wait * @param callback function to execute once time runs out */ - virtual void setTimeout(double seconds, const std::function& callback) {} + virtual void setTimeout(Connection *connection, double seconds, const std::function& callback) {} /** * Method that is called when data needs to be sent over the network diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 8607026..4609d0d 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -553,7 +553,7 @@ Deferred& ChannelImpl::send(const Frame &frame, const char *messag // register an error on the deferred handler // after a timeout, so it gets called only // after a possible handler was installed. - _connection->_handler->setTimeout(0, [handler, message]() { + _connection->_handler->setTimeout(_connection->_parent, 0, [handler, message]() { // emit an error on the handler handler->error(message); }); From bcc6eaff82162d21e6b7e3b3de11e44cd5bc7680 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 14 Apr 2014 16:04:49 +0200 Subject: [PATCH 17/41] added const access for operator[] (issue #7) --- include/array.h | 12 +++++++++++- include/table.h | 10 ++++++++++ src/array.cpp | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/include/array.h b/include/array.h index 4eb5c30..ddc07b7 100644 --- a/include/array.h +++ b/include/array.h @@ -110,7 +110,7 @@ public: * @param index field index * @return Field */ - const Field &get(uint8_t index); + const Field &get(uint8_t index) const; /** * Get number of elements on this array @@ -141,6 +141,16 @@ public: { return ArrayFieldProxy(this, index); } + + /** + * Get a const field + * @param index field index + * @return Field + */ + const Field &operator[](uint8_t index) const + { + return get(index); + } /** * Write encoded payload to the given buffer. diff --git a/include/table.h b/include/table.h index f7682e5..997c210 100644 --- a/include/table.h +++ b/include/table.h @@ -122,6 +122,16 @@ public: return AssociativeFieldProxy(this, name); } + /** + * Get a const field + * + * @param name field name + */ + const Field &operator[](const std::string& name) const + { + return get(name); + } + /** * Write encoded payload to the given buffer. * @param buffer diff --git a/src/array.cpp b/src/array.cpp index 175c168..d04c865 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -58,7 +58,7 @@ Array::Array(const Array &array) * @param index field index * @return Field */ -const Field &Array::get(uint8_t index) +const Field &Array::get(uint8_t index) const { // used if index does not exist static ShortString empty; From b8d458156986bf616e23c7ebd760a54a6265e81c Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 14 Apr 2014 17:14:36 +0200 Subject: [PATCH 18/41] cast to array and cast to object implemented, refactored shared-ptr and clone() methods to utilize std::make_shared (see issue #7) --- include/array.h | 18 +++++-- include/booleanset.h | 4 +- include/decimalfield.h | 4 +- include/field.h | 21 +++++++- include/fieldproxy.h | 117 +++++++++-------------------------------- include/numericfield.h | 6 +-- include/stringfield.h | 6 +-- include/table.h | 39 ++++++++++++-- src/field.cpp | 39 ++++++++++++++ 9 files changed, 144 insertions(+), 110 deletions(-) diff --git a/include/array.h b/include/array.h index ddc07b7..054fac6 100644 --- a/include/array.h +++ b/include/array.h @@ -62,9 +62,9 @@ public: * Create a new instance of this object * @return Field* */ - virtual Field *clone() const override + virtual std::shared_ptr clone() const override { - return new Array(*this); + return std::make_shared(*this); } /** @@ -84,7 +84,7 @@ public: Array set(uint8_t index, const Field &value) { // construct a shared pointer - auto ptr = std::shared_ptr(value.clone()); + auto ptr = value.clone(); // should we overwrite an existing record? if (index >= _fields.size()) @@ -181,7 +181,7 @@ public: bool first = true; // loop through all members - for (auto iter : _fields) + for (auto &iter : _fields) { // split with comma if (!first) stream << ","; @@ -196,6 +196,16 @@ public: // postfix stream << ")"; } + + /** + * Cast to array + * @return Array + */ + virtual operator const Array& () const override + { + // this already is an array, so no cast is necessary + return *this; + } }; /** diff --git a/include/booleanset.h b/include/booleanset.h index f165643..b527048 100644 --- a/include/booleanset.h +++ b/include/booleanset.h @@ -78,9 +78,9 @@ public: * Extending from field forces us to implement a clone function. * @return shared_ptr */ - virtual Field *clone() const override + virtual std::shared_ptr clone() const override { - return new BooleanSet(*this); + return std::make_shared(*this); } /** diff --git a/include/decimalfield.h b/include/decimalfield.h index 3164286..75a8ab0 100644 --- a/include/decimalfield.h +++ b/include/decimalfield.h @@ -83,9 +83,9 @@ public: * Create a new identical instance of this object * @return Field* */ - virtual Field *clone() const override + virtual std::shared_ptr clone() const override { - return new DecimalField(_places, _number); + return std::make_shared(_places, _number); } /** diff --git a/include/field.h b/include/field.h index 76f89ba..d6a7a88 100644 --- a/include/field.h +++ b/include/field.h @@ -37,7 +37,7 @@ public: * Create a new instance on the heap of this object, identical to the object passed * @return Field* */ - virtual Field *clone() const = 0; + virtual std::shared_ptr clone() const = 0; /** * Get the size this field will take when @@ -64,6 +64,25 @@ public: * @param std::ostream */ virtual void output(std::ostream &stream) const = 0; + + /** + * Casting operators + * @return mixed + */ + virtual operator const std::string& () const; + virtual operator const char * () const { return nullptr; } + virtual operator uint8_t () const { return 0; } + virtual operator uint16_t () const { return 0; } + virtual operator uint32_t () const { return 0; } + virtual operator uint64_t () const { return 0; } + virtual operator int8_t () const { return 0; } + virtual operator int16_t () const { return 0; } + virtual operator int32_t () const { return 0; } + virtual operator int64_t () const { return 0; } + virtual operator float () const { return 0; } + virtual operator double () const { return 0; } + virtual operator const Array& () const; + virtual operator const Table& () const; }; /** diff --git a/include/fieldproxy.h b/include/fieldproxy.h index 5cafcdc..2666850 100644 --- a/include/fieldproxy.h +++ b/include/fieldproxy.h @@ -197,6 +197,30 @@ public: return operator=(std::string(value)); } + /** + * Assign an array value + * @param value + * @return FieldProxy + */ + FieldProxy &operator=(const Array &value) + { + // assign value and allow chaining + _source->set(_index, value); + return *this; + } + + /** + * Assign a table value + * @param value + * @return FieldProxy + */ + FieldProxy &operator=(const Table &value) + { + // assign value and allow chaining + _source->set(_index, value); + return *this; + } + /** * Get the underlying field * @return Field @@ -210,97 +234,8 @@ public: * Get a boolean * @return bool */ - operator bool () - { - // retrieve the value - return _source->get(_index); - } - - /** - * Get numeric value - * @return int8_t - */ - operator int8_t () - { - // retrieve the value - return _source->get(_index); - } - - /** - * Get numeric value - * @return uint8_t - */ - operator uint8_t () - { - // retrieve the value - return _source->get(_index); - } - - /** - * Get numeric value - * @return int16_t - */ - operator int16_t () - { - // retrieve the value - return _source->get(_index); - } - - /** - * Get numeric value - * @return uint16_t - */ - operator uint16_t () - { - // retrieve the value - return _source->get(_index); - } - - /** - * Get numeric value - * @return int32_t - */ - operator int32_t () - { - // retrieve the value - return _source->get(_index); - } - - /** - * Get numeric value - * @return uint32_t - */ - operator uint32_t () - { - // retrieve the value - return _source->get(_index); - } - - /** - * Get numeric value - * @return int64_t - */ - operator int64_t () - { - // retrieve the value - return _source->get(_index); - } - - /** - * Get numeric value - * @return uint64_t - */ - operator uint64_t () - { - // retrieve the value - return _source->get(_index); - } - - /** - * Get string value - * @return string - */ - operator std::string () + template + operator TARGET () const { // retrieve the value return _source->get(_index); diff --git a/include/numericfield.h b/include/numericfield.h index 9a60e4d..3cd4c93 100644 --- a/include/numericfield.h +++ b/include/numericfield.h @@ -78,10 +78,10 @@ public: * Create a new instance of this object * @return Field* */ - virtual Field *clone() const override + virtual std::shared_ptr clone() const override { // create a new copy of ourselves and return it - return new NumericField(_value); + return std::make_shared(_value); } /** @@ -100,7 +100,7 @@ public: * Get the value * @return mixed */ - operator T () const + virtual operator T () const override { return _value; } diff --git a/include/stringfield.h b/include/stringfield.h index 3def910..0408de7 100644 --- a/include/stringfield.h +++ b/include/stringfield.h @@ -58,10 +58,10 @@ public: * Create a new instance of this object * @return Field* */ - virtual Field *clone() const override + virtual std::shared_ptr clone() const override { // create a new copy of ourselves and return it - return new StringField(_data); + return std::make_shared(_data); } /** @@ -96,7 +96,7 @@ public: * Get the value * @return string */ - operator const std::string& () const + virtual operator const std::string& () const override { return _data; } diff --git a/include/table.h b/include/table.h index 997c210..2faec2b 100644 --- a/include/table.h +++ b/include/table.h @@ -76,9 +76,9 @@ public: * Create a new instance on the heap of this object, identical to the object passed * @return Field* */ - virtual Field *clone() const override + virtual std::shared_ptr clone() const override { - return new Table(*this); + return std::make_shared(*this); } /** @@ -96,7 +96,7 @@ public: Table set(const std::string& name, const Field &value) { // copy to a new pointer and store it - _fields[name] = std::shared_ptr(value.clone()); + _fields[name] = value.clone(); // allow chaining return *this; @@ -122,6 +122,16 @@ public: return AssociativeFieldProxy(this, name); } + /** + * Get a field + * + * @param name field name + */ + AssociativeFieldProxy operator[](const char *name) + { + return AssociativeFieldProxy(this, name); + } + /** * Get a const field * @@ -132,6 +142,16 @@ public: return get(name); } + /** + * Get a const field + * + * @param name field name + */ + const Field &operator[](const char *name) const + { + return get(name); + } + /** * Write encoded payload to the given buffer. * @param buffer @@ -160,7 +180,7 @@ public: bool first = true; // loop through all members - for (auto iter : _fields) + for (auto &iter : _fields) { // split with comma if (!first) stream << ","; @@ -175,6 +195,17 @@ public: // postfix stream << ")"; } + + /** + * Cast to table + * @return Table + */ + virtual operator const Table& () const override + { + // this already is an array, so no cast is necessary + return *this; + } + }; /** diff --git a/src/field.cpp b/src/field.cpp index e49dc17..2655bb2 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -45,6 +45,45 @@ Field *Field::decode(ReceivedFrame &frame) } } +/** + * Cast to string + * @return std::string + */ +Field::operator const std::string& () const +{ + // static empty string + static std::string empty; + + // return it + return empty; +} + +/** + * Cast to array + * @return Array + */ +Field::operator const Array& () const +{ + // static empty array + static Array empty; + + // return it + return empty; +} + +/** + * Cast to object + * @return Array + */ +Field::operator const Table& () const +{ + // static empty table + static Table empty; + + // return it + return empty; +} + /** * End of namespace */ From d47d6bda5d7dd5a12c1fc4f8d37afd7e5dba4f81 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 14 Apr 2014 17:17:17 +0200 Subject: [PATCH 19/41] removed noreturn attribute because not supported on gcc 4.7 (issue #10) --- src/methodframe.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/methodframe.h b/src/methodframe.h index 8b847fd..c435df7 100644 --- a/src/methodframe.h +++ b/src/methodframe.h @@ -75,7 +75,7 @@ public: * @param connection The connection over which it was received * @return bool Was it succesfully processed? */ - virtual bool process[[ noreturn ]](ConnectionImpl *connection) override + virtual bool process(ConnectionImpl *connection) override { // this is an exception throw ProtocolException("unimplemented frame type " + std::to_string(type()) + " class " + std::to_string(classID()) + " method " + std::to_string(methodID())); From 7b20f465192c23a30058bc1ef7d3180a90de8616 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 08:52:49 +0200 Subject: [PATCH 20/41] removed channel parameter from the callbacks, because this can easily be captured --- include/deferred.h | 36 +++++++++++++++--------------------- include/deferredconsumer.h | 12 ++++++------ src/Makefile | 2 +- src/channelimpl.cpp | 5 +++-- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/include/deferred.h b/include/deferred.h index e2fb2d0..df4f9ed 100644 --- a/include/deferred.h +++ b/include/deferred.h @@ -27,17 +27,17 @@ private: /** * Callback to execute on success */ - std::function _successCallback; + std::function _successCallback; /** * Callback to execute on failure */ - std::function _errorCallback; + std::function _errorCallback; /** * Callback to execute either way */ - std::function _finalizeCallback; + std::function _finalizeCallback; /** * Indicate success @@ -47,8 +47,8 @@ private: void success(Arguments ...parameters) const { // execute callbacks if registered - if (_successCallback) _successCallback(_channel, parameters...); - if (_finalizeCallback) _finalizeCallback(_channel, ""); + if (_successCallback) _successCallback(parameters...); + if (_finalizeCallback) _finalizeCallback(""); } /** @@ -59,8 +59,8 @@ private: void error(const std::string& error) const { // execute callbacks if registered - if (_errorCallback) _errorCallback(_channel, error); - if (_finalizeCallback) _finalizeCallback(_channel, error); + if (_errorCallback) _errorCallback(error); + if (_finalizeCallback) _finalizeCallback(error); } /** @@ -69,14 +69,11 @@ private: */ friend class ChannelImpl; friend class Callbacks; + protected: - /** - * The channel we operate under - */ - Channel *_channel; - /** * Do we already know we failed? + * @var bool */ bool _failed; @@ -84,13 +81,10 @@ protected: * Protected constructor that can only be called * from within the channel implementation * - * @param channel the channel we operate under - * @param boolea are we already failed? + * @param failed are we already failed? */ - Deferred(Channel *channel, bool failed = false) : - _channel(channel), - _failed(failed) - {} + Deferred(bool failed = false) : _failed(failed) {} + public: /** * Deleted copy constructor @@ -125,7 +119,7 @@ public: * * @param callback the callback to execute */ - Deferred& onSuccess(const std::function& callback) + Deferred& onSuccess(const std::function& callback) { // store callback _successCallback = callback; @@ -142,7 +136,7 @@ public: * * @param callback the callback to execute */ - Deferred& onError(const std::function& callback) + Deferred& onError(const std::function& callback) { // store callback _errorCallback = callback; @@ -164,7 +158,7 @@ public: * * @param callback the callback to execute */ - Deferred& onFinalize(const std::function& callback) + Deferred& onFinalize(const std::function& callback) { // store callback _finalizeCallback = callback; diff --git a/include/deferredconsumer.h b/include/deferredconsumer.h index 3610bd7..8de39bd 100644 --- a/include/deferredconsumer.h +++ b/include/deferredconsumer.h @@ -20,7 +20,7 @@ private: /** * Callback to execute when a message arrives */ - std::function _messageCallback; + std::function _messageCallback; /** * Process a message @@ -33,7 +33,7 @@ private: void message(const Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) const { // do we have a valid callback - if (_messageCallback) _messageCallback(_channel, message, deliveryTag, consumerTag, redelivered); + if (_messageCallback) _messageCallback(message, deliveryTag, consumerTag, redelivered); } /** @@ -42,6 +42,7 @@ private: */ friend class ChannelImpl; friend class ConsumedMessage; + protected: /** * Protected constructor that can only be called @@ -50,9 +51,8 @@ protected: * @param channel the channel we operate under * @param boolea are we already failed? */ - DeferredConsumer(Channel *channel, bool failed = false) : - Deferred(channel, failed) - {} + DeferredConsumer(bool failed = false) : Deferred(failed) {} + public: /** * Register a function to be called when a message arrives @@ -62,7 +62,7 @@ public: * * @param callback the callback to execute */ - DeferredConsumer& onReceived(const std::function& callback) + DeferredConsumer& onReceived(const std::function& callback) { // store callback _messageCallback = callback; diff --git a/src/Makefile b/src/Makefile index 3a9b4e9..4f0baa2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,6 @@ CPP = g++ RM = rm -f -CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++11 -g +CPPFLAGS = -Wall -c -I. -g -flto -std=c++11 -g LD = g++ LD_FLAGS = -Wall -shared -O2 SHARED_LIB = libamqpcpp.so diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 4609d0d..a478dff 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -439,7 +439,7 @@ Deferred<>& ChannelImpl::setQos(uint16_t prefetchCount) DeferredConsumer& ChannelImpl::consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { // create the deferred consumer - _consumer = std::unique_ptr(new DeferredConsumer(_parent, false)); + _consumer = std::unique_ptr(new DeferredConsumer(false)); // can we send the basic consume frame? if (!send(BasicConsumeFrame(_id, queue, tag, flags & nolocal, flags & noack, flags & exclusive, flags & nowait, arguments))) @@ -542,7 +542,7 @@ Deferred& ChannelImpl::send(const Frame &frame, const char *messag // create a new deferred handler and get a pointer to it // note: cannot use auto here or the lambda below chokes // when compiling under gcc 4.8 - Deferred *handler = &_callbacks.push_back(Deferred(_parent)); + Deferred *handler = &_callbacks.push_back(Deferred()); // send the frame over the channel if (!send(frame)) @@ -554,6 +554,7 @@ Deferred& ChannelImpl::send(const Frame &frame, const char *messag // after a timeout, so it gets called only // after a possible handler was installed. _connection->_handler->setTimeout(_connection->_parent, 0, [handler, message]() { + // emit an error on the handler handler->error(message); }); From 921f24ae06b61bfbfd2a5045cd28759ed465dd8e Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 10:43:33 +0200 Subject: [PATCH 21/41] de-templified the deferred objects, to make them easier to understand for other programmers --- amqpcpp.h | 5 +- include/callbacks.h | 149 +++----------------------------- include/channel.h | 74 ++++++++-------- include/channelimpl.h | 102 ++++++++++++++-------- include/deferred.h | 143 +++++++++++++++++++++++-------- include/deferredcancel.h | 91 ++++++++++++++++++++ include/deferredconsumer.h | 17 ++-- include/deferreddelete.h | 91 ++++++++++++++++++++ include/deferredqueue.h | 91 ++++++++++++++++++++ src/channelimpl.cpp | 168 +++++++++++++++++++++++-------------- src/queuedeclareokframe.h | 4 +- 11 files changed, 623 insertions(+), 312 deletions(-) create mode 100644 include/deferredcancel.h create mode 100644 include/deferreddelete.h create mode 100644 include/deferredqueue.h diff --git a/amqpcpp.h b/amqpcpp.h index 9f84823..06f19f8 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -53,9 +53,12 @@ // mid level includes #include #include +#include #include #include -#include +#include +#include +#include #include #include #include diff --git a/include/callbacks.h b/include/callbacks.h index 9d378df..50239a0 100644 --- a/include/callbacks.h +++ b/include/callbacks.h @@ -12,145 +12,18 @@ namespace AMQP { /** - * Class for managing deferred callbacks + * All the callbacks that are supported + * + * When someone registers a callback function for certain events, it should + * match one of the following signatures. */ -class Callbacks -{ -private: - /** - * Different callback types supported - */ - std::tuple< - std::deque>, - std::deque>, - std::deque>, - std::deque> - > _callbacks; - - /** - * If all else fails, we have gotten the wrong - * type, which is not present in the arguments. - * - * This should result in a compile error. - */ - template - struct getIndex - { - // if this structure is used, we went past the last argument - // and this static_assert should trigger a compile failure. - static_assert(N < sizeof...(Arguments), "Type T not found in Arguments"); - - // we still have to provide this member though - static constexpr std::size_t value = N; - }; - - /** - * This structure has one static member that represents - * the index of T in Arguments. This variant is used where U - * does equal T, so a match is found, meaning the current - * index given is the right one. - */ - template - struct getIndex - { - // element is same type as we are looking for - static constexpr std::size_t value = N; - }; - - /** - * This structure has one static member that represents - * the index of T in Arguments. This variant is used where U - * does not equal T, so we need to look at the next member. - */ - template - struct getIndex - { - // current N is not correct, unroll to next element - static constexpr std::size_t value = getIndex::value; - }; - - /** - * Retrieve the list of callbacks matching the type - * - * @param tuple tuple with callbacks - */ - template - T& get(std::tuple& tuple) - { - // retrieve the index at which the requested callbacks can be found - constexpr std::size_t index = getIndex::value; - - // retrieve the callbacks - return std::get(tuple); - } -public: - /** - * Add a deferred to the available callbacks - * - * @param deferred the deferred to add - * @return reference to the inserted deferred - */ - template - Deferred& push_back(Deferred&& item) - { - // retrieve the container - auto &container = get>>(_callbacks); - - // add the element - container.push_back(std::move(item)); - - // return reference to the new item - return container.back(); - } - - /** - * Report success to the relevant callback - * - * @param mixed... additional parameters - */ - template - void reportSuccess(Arguments ...parameters) - { - // retrieve the container and element - auto &container = get>>(_callbacks); - auto &callback = container.front(); - - // execute the callback - callback.success(parameters...); - - // remove the executed callback - container.pop_front(); - } - - /** - * Report a failure - * - * @param error a description of the error - */ - template - typename std::enable_if::value>::type - reportError(const std::string& message) - {} - - /** - * Report a failure - * - * @param error a description of the error - */ - template - typename std::enable_if::value>::type - reportError(const std::string& message) - { - // retrieve the callbacks at current index - auto &callbacks = std::get(_callbacks); - - // report errors to all callbacks of the current type - for (auto &callback : callbacks) callback.error(message); - - // execute the next type - reportError(message); - } -}; +using SuccessCallback = std::function; +using ErrorCallback = std::function; +using FinalizeCallback = std::function; +using ConsumeCallback = std::function; +using QueueCallback = std::function; +using DeleteCallback = std::function; +using CancelCallback = std::function; /** * End namespace diff --git a/include/channel.h b/include/channel.h index e5df3d3..0f315c4 100644 --- a/include/channel.h +++ b/include/channel.h @@ -42,7 +42,7 @@ public: * * @param callback the callback to execute */ - void onReady(const std::function& callback) + void onReady(const SuccessCallback &callback) { // store callback in implementation _implementation._readyCallback = callback; @@ -56,7 +56,7 @@ public: * * @param callback the callback to execute */ - void onError(const std::function& callback) + void onError(const ErrorCallback &callback) { // store callback in implementation _implementation._errorCallback = callback; @@ -70,7 +70,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& pause() + Deferred &pause() { return _implementation.pause(); } @@ -83,7 +83,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& resume() + Deferred &resume() { return _implementation.resume(); } @@ -103,7 +103,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& startTransaction() + Deferred &startTransaction() { return _implementation.startTransaction(); } @@ -114,7 +114,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& commitTransaction() + Deferred &commitTransaction() { return _implementation.commitTransaction(); } @@ -125,7 +125,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& rollbackTransaction() + Deferred &rollbackTransaction() { return _implementation.rollbackTransaction(); } @@ -149,12 +149,12 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(name, type, flags, arguments); } - Deferred<>& declareExchange(const std::string &name, ExchangeType type, const Table &arguments) { return _implementation.declareExchange(name, type, 0, arguments); } - Deferred<>& declareExchange(const std::string &name, ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(name, type, flags, Table()); } - Deferred<>& declareExchange(ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(std::string(), type, flags, arguments); } - Deferred<>& declareExchange(ExchangeType type, const Table &arguments) { return _implementation.declareExchange(std::string(), type, 0, arguments); } - Deferred<>& declareExchange(ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(std::string(), type, flags, Table()); } + Deferred &declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(name, type, flags, arguments); } + Deferred &declareExchange(const std::string &name, ExchangeType type, const Table &arguments) { return _implementation.declareExchange(name, type, 0, arguments); } + Deferred &declareExchange(const std::string &name, ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(name, type, flags, Table()); } + Deferred &declareExchange(ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(std::string(), type, flags, arguments); } + Deferred &declareExchange(ExchangeType type, const Table &arguments) { return _implementation.declareExchange(std::string(), type, 0, arguments); } + Deferred &declareExchange(ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(std::string(), type, flags, Table()); } /** * Remove an exchange @@ -169,7 +169,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& removeExchange(const std::string &name, int flags = 0) { return _implementation.removeExchange(name, flags); } + Deferred &removeExchange(const std::string &name, int flags = 0) { return _implementation.removeExchange(name, flags); } /** * Bind two exchanges to each other @@ -187,9 +187,9 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, flags, arguments); } - Deferred<>& bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, 0, arguments); } - Deferred<>& bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags = 0) { return _implementation.bindExchange(source, target, routingkey, flags, Table()); } + Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, flags, arguments); } + Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, 0, arguments); } + Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags = 0) { return _implementation.bindExchange(source, target, routingkey, flags, Table()); } /** * Unbind two exchanges from one another @@ -207,9 +207,9 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, flags, arguments); } - Deferred<>& unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, 0, arguments); } - Deferred<>& unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags = 0) { return _implementation.unbindExchange(target, source, routingkey, flags, Table()); } + Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, flags, arguments); } + Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, 0, arguments); } + Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags = 0) { return _implementation.unbindExchange(target, source, routingkey, flags, Table()); } /** * Declare a queue @@ -240,12 +240,12 @@ public: * * }); */ - Deferred& declareQueue(const std::string &name, int flags, const Table &arguments) { return _implementation.declareQueue(name, flags, arguments); } - Deferred& declareQueue(const std::string &name, const Table &arguments) { return _implementation.declareQueue(name, 0, arguments); } - Deferred& declareQueue(const std::string &name, int flags = 0) { return _implementation.declareQueue(name, flags, Table()); } - Deferred& declareQueue(int flags, const Table &arguments) { return _implementation.declareQueue(std::string(), flags, arguments); } - Deferred& declareQueue(const Table &arguments) { return _implementation.declareQueue(std::string(), 0, arguments); } - Deferred& declareQueue(int flags = 0) { return _implementation.declareQueue(std::string(), flags, Table()); } + DeferredQueue &declareQueue(const std::string &name, int flags, const Table &arguments) { return _implementation.declareQueue(name, flags, arguments); } + DeferredQueue &declareQueue(const std::string &name, const Table &arguments) { return _implementation.declareQueue(name, 0, arguments); } + DeferredQueue &declareQueue(const std::string &name, int flags = 0) { return _implementation.declareQueue(name, flags, Table()); } + DeferredQueue &declareQueue(int flags, const Table &arguments) { return _implementation.declareQueue(std::string(), flags, arguments); } + DeferredQueue &declareQueue(const Table &arguments) { return _implementation.declareQueue(std::string(), 0, arguments); } + DeferredQueue &declareQueue(int flags = 0) { return _implementation.declareQueue(std::string(), flags, Table()); } /** * Bind a queue to an exchange @@ -263,9 +263,9 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, flags, arguments); } - Deferred<>& bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, 0, arguments); } - Deferred<>& bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags = 0) { return _implementation.bindQueue(exchange, queue, routingkey, flags, Table()); } + Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, flags, arguments); } + Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, 0, arguments); } + Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags = 0) { return _implementation.bindQueue(exchange, queue, routingkey, flags, Table()); } /** * Unbind a queue from an exchange @@ -277,8 +277,8 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.unbindQueue(exchange, queue, routingkey, arguments); } - Deferred<>& unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey) { return _implementation.unbindQueue(exchange, queue, routingkey, Table()); } + Deferred &unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.unbindQueue(exchange, queue, routingkey, arguments); } + Deferred &unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey) { return _implementation.unbindQueue(exchange, queue, routingkey, Table()); } /** * Purge a queue @@ -303,7 +303,7 @@ public: * * }); */ - Deferred& purgeQueue(const std::string &name, int flags = 0){ return _implementation.purgeQueue(name, flags); } + DeferredDelete &purgeQueue(const std::string &name, int flags = 0){ return _implementation.purgeQueue(name, flags); } /** * Remove a queue @@ -329,7 +329,7 @@ public: * * }); */ - Deferred& removeQueue(const std::string &name, int flags = 0) { return _implementation.removeQueue(name, flags); } + DeferredDelete &removeQueue(const std::string &name, int flags = 0) { return _implementation.removeQueue(name, flags); } /** * Publish a message to an exchange @@ -361,7 +361,7 @@ public: * @param prefetchCount maximum number of messages to prefetch * @return bool whether the Qos frame is sent. */ - Deferred<>& setQos(uint16_t prefetchCount) + Deferred &setQos(uint16_t prefetchCount) { return _implementation.setQos(prefetchCount); } @@ -440,7 +440,7 @@ public: * * }); */ - Deferred& cancel(const std::string &tag, int flags = 0) { return _implementation.cancel(tag, flags); } + DeferredCancel &cancel(const std::string &tag, int flags = 0) { return _implementation.cancel(tag, flags); } /** * Acknoldge a received message @@ -493,7 +493,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& recover(int flags = 0) { return _implementation.recover(flags); } + Deferred &recover(int flags = 0) { return _implementation.recover(flags); } /** * Close the current channel @@ -501,7 +501,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& close() { return _implementation.close(); } + Deferred &close() { return _implementation.close(); } /** * Get the channel we're working on diff --git a/include/channelimpl.h b/include/channelimpl.h index 37e8e07..43a87ba 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -34,24 +34,38 @@ private: /** * Callback when the channel is ready + * @var SuccessCallback */ - std::function _readyCallback; + SuccessCallback _readyCallback; /** * Callback when the channel errors out + * @var ErrorCallback */ - std::function _errorCallback; + ErrorCallback _errorCallback; /** * Callback to execute when a message arrives + * + * @todo do this different?? */ std::unique_ptr _consumer; /** - * The callbacks waiting to be called + * Pointer to the oldest deferred result (the first one that is going + * to be executed) + * + * @var Deferred */ - Callbacks _callbacks; - + Deferred *_oldestCallback = nullptr; + + /** + * Pointer to the newest deferred result (the last one to be added). + * + * @var Deferred + */ + Deferred *_newestCallback = nullptr; + /** * The channel number * @var uint16_t @@ -86,6 +100,14 @@ private: */ ChannelImpl(Channel *parent, Connection *connection); + /** + * Push a deferred result + * @param result The deferred result + * @param error Error message in case the result is not ok + */ + void push(Deferred *deferred, const char *error); + + public: /** * Destructor @@ -109,7 +131,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& pause(); + Deferred &pause(); /** * Resume a paused channel @@ -119,7 +141,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& resume(); + Deferred &resume(); /** * Is the channel connected? @@ -133,7 +155,7 @@ public: /** * Start a transaction */ - Deferred<>& startTransaction(); + Deferred &startTransaction(); /** * Commit the current transaction @@ -141,7 +163,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& commitTransaction(); + Deferred &commitTransaction(); /** * Rollback the current transaction @@ -149,7 +171,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& rollbackTransaction(); + Deferred &rollbackTransaction(); /** * declare an exchange @@ -162,7 +184,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments); + Deferred &declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments); /** * bind two exchanges @@ -176,7 +198,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); + Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); /** * unbind two exchanges @@ -190,7 +212,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); + Deferred &unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); /** * remove an exchange @@ -201,7 +223,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& removeExchange(const std::string &name, int flags); + Deferred &removeExchange(const std::string &name, int flags); /** * declare a queue @@ -212,7 +234,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred& declareQueue(const std::string &name, int flags, const Table &arguments); + DeferredQueue &declareQueue(const std::string &name, int flags, const Table &arguments); /** * Bind a queue to an exchange @@ -226,7 +248,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments); + Deferred &bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments); /** * Unbind a queue from an exchange @@ -239,7 +261,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& unbindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments); + Deferred &unbindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments); /** * Purge a queue @@ -259,7 +281,7 @@ public: * * }); */ - Deferred& purgeQueue(const std::string &name, int flags); + DeferredDelete &purgeQueue(const std::string &name, int flags); /** * Remove a queue @@ -279,7 +301,7 @@ public: * * }); */ - Deferred& removeQueue(const std::string &name, int flags); + DeferredDelete &removeQueue(const std::string &name, int flags); /** * Publish a message to an exchange @@ -303,7 +325,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& setQos(uint16_t prefetchCount); + Deferred &setQos(uint16_t prefetchCount); /** * Tell the RabbitMQ server that we're ready to consume messages @@ -345,7 +367,7 @@ public: * * }); */ - Deferred& cancel(const std::string &tag, int flags); + DeferredCancel &cancel(const std::string &tag, int flags); /** * Acknowledge a message @@ -370,7 +392,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& recover(int flags); + Deferred &recover(int flags); /** * Close the current channel @@ -378,7 +400,7 @@ public: * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred<>& close(); + Deferred &close(); /** * Get the channel we're working on @@ -402,9 +424,9 @@ public: * * @param frame frame to send * @param message the message to trigger if the frame cannot be send at all + * @return Deferred the deferred object */ - template - Deferred& send(const Frame &frame, const char *message); + Deferred &send(const Frame &frame, const char *message); /** * Report to the handler that the channel is opened @@ -412,7 +434,7 @@ public: void reportReady() { // inform handler - if (_readyCallback) _readyCallback(_parent); + if (_readyCallback) _readyCallback(); } /** @@ -436,8 +458,13 @@ public: template void reportSuccess(Arguments ...parameters) { - // report success to the relevant callback - _callbacks.reportSuccess(std::forward(parameters)...); + // skip if there is no oldest callback + if (!_oldestCallback) return; + + // report to the oldest callback, and install a new oldest callback + _oldestCallback = _oldestCallback->reportSuccess(std::forward(parameters)...); + + // @todo destruct oldest callback } /** @@ -449,11 +476,19 @@ public: // change state _state = state_closed; - // inform handler - if (_errorCallback) _errorCallback(_parent, message); + // @todo multiple callbacks are called, this could break + // @todo should this be a std::string parameter? - // report to all waiting callbacks too - _callbacks.reportError(message); + // inform handler + if (_errorCallback) _errorCallback(message.c_str()); + + // skip if there is no oldest callback + if (!_oldestCallback) return; + + // report to the oldest callback, and install a new oldest callback + _oldestCallback = _oldestCallback->reportError(message); + + // @todo destruct oldest callback } /** @@ -488,7 +523,8 @@ public: if (!_consumer) reportError("Received basic consume ok frame, but no consumer was found"); // otherwise, we now report the consumer as started - else _consumer->success(consumerTag); + // @todo look at this implementation + //else _consumer->success(consumerTag); } /** diff --git a/include/deferred.h b/include/deferred.h index df4f9ed..777a370 100644 --- a/include/deferred.h +++ b/include/deferred.h @@ -20,47 +20,132 @@ class Callbacks; /** * Class definition */ -template class Deferred { -private: +protected: /** * Callback to execute on success + * @var SuccessCallback */ - std::function _successCallback; + SuccessCallback _successCallback; /** * Callback to execute on failure + * @var ErrorCallback */ - std::function _errorCallback; + ErrorCallback _errorCallback; /** * Callback to execute either way + * @var FinalizeCallback */ - std::function _finalizeCallback; + FinalizeCallback _finalizeCallback; + + /** + * Pointer to the next deferred object + * @var Deferred + */ + Deferred *_next = nullptr; + + /** + * Do we already know we failed? + * @var bool + */ + bool _failed; + /** * Indicate success - * - * @param parameters... the extra parameters relevant for this deferred handler + * @return Deferred Next deferred result */ - void success(Arguments ...parameters) const + Deferred *reportSuccess() const { // execute callbacks if registered - if (_successCallback) _successCallback(parameters...); - if (_finalizeCallback) _finalizeCallback(""); + if (_successCallback) _successCallback(); + if (_finalizeCallback) _finalizeCallback(); + + // return the next deferred result + return _next; + } + + /** + * Report success for queue declared messages + * @param name Name of the new queue + * @param messagecount Number of messages in the queue + * @param consumercount Number of consumers linked to the queue + * @return Deferred Next deferred result + */ + virtual Deferred *reportSuccess(const std::string &name, uint32_t messagecount, uint32_t consumercount) const + { + // this is the same as a regular success message + return reportSuccess(); + } + + /** + * Report success for frames that report delete operations + * @param messagecount Number of messages that were deleted + * @return Deferred + */ + virtual Deferred *reportSuccess(uint32_t messagecount) const + { + // this is the same as a regular success message + return reportSuccess(); + } + + /** + * Report success for frames that report cancel operations + * @param name Consumer tag that is cancelled + * @return Deferred + */ + virtual Deferred *reportSuccess(const std::string &name) const + { + // this is the same as a regular success message + return reportSuccess(); + } + + /** + * Indicate failure + * @param error Description of the error that occured + * @return Deferred Next deferred result + */ + Deferred *reportError(const std::string& error) + { + // from this moment on the object should be listed as failed + _failed = true; + + // execute callbacks if registered + if (_errorCallback) _errorCallback(error.c_str()); + if (_finalizeCallback) _finalizeCallback(); + + // return the next deferred result + return _next; } /** * Indicate failure - * * @param error description of the error that occured */ - void error(const std::string& error) const + Deferred *reportError(const char *error) { + // from this moment on the object should be listed as failed + _failed = true; + // execute callbacks if registered if (_errorCallback) _errorCallback(error); - if (_finalizeCallback) _finalizeCallback(error); + if (_finalizeCallback) _finalizeCallback(); + + // return the next deferred result + return _next; + } + + /** + * Add a pointer to the next deferred result + * @param deferred + */ + void add(Deferred *deferred) + { + // store pointer + _next = deferred; } /** @@ -71,12 +156,6 @@ private: friend class Callbacks; protected: - /** - * Do we already know we failed? - * @var bool - */ - bool _failed; - /** * Protected constructor that can only be called * from within the channel implementation @@ -87,18 +166,10 @@ protected: public: /** - * Deleted copy constructor + * Deleted copy and move constructors */ - Deferred(const Deferred& that) = delete; - - /** - * Move constructor - */ - Deferred(Deferred&& that) : - _successCallback(std::move(that._successCallback)), - _errorCallback(std::move(that._errorCallback)), - _finalizeCallback(std::move(that._finalizeCallback)) - {} + Deferred(const Deferred &that) = delete; + Deferred(Deferred &&that) = delete; /** * Cast to a boolean @@ -119,10 +190,12 @@ public: * * @param callback the callback to execute */ - Deferred& onSuccess(const std::function& callback) + Deferred &onSuccess(const SuccessCallback &callback) { // store callback _successCallback = callback; + + // allow chaining return *this; } @@ -136,10 +209,12 @@ public: * * @param callback the callback to execute */ - Deferred& onError(const std::function& callback) + Deferred &onError(const ErrorCallback &callback) { // store callback _errorCallback = callback; + + // allow chaining return *this; } @@ -158,10 +233,12 @@ public: * * @param callback the callback to execute */ - Deferred& onFinalize(const std::function& callback) + Deferred &onFinalize(const FinalizeCallback &callback) { // store callback _finalizeCallback = callback; + + // allow chaining return *this; } }; diff --git a/include/deferredcancel.h b/include/deferredcancel.h new file mode 100644 index 0000000..401716d --- /dev/null +++ b/include/deferredcancel.h @@ -0,0 +1,91 @@ +/** + * DeferredCancel.h + * + * Deferred callback for instructions that cancel a running consumer. This + * deferred object allows one to register a callback that also gets the + * consumer tag as one of its parameters. + * + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * We extend from the default deferred and add extra functionality + */ +class DeferredCancel : public Deferred +{ +private: + /** + * Callback to execute when the instruction is completed + * @var CancelCallback + */ + CancelCallback _cancelCallback; + + /** + * Report success for frames that report cancel operations + * @param name Consumer tag that is cancelled + * @return Deferred + */ + virtual Deferred *reportSuccess(const std::string &name) const override + { + // skip if no special callback was installed + if (!_cancelCallback) return Deferred::reportSuccess(); + + // call the callback + _cancelCallback(name); + + // call finalize callback + if (_finalizeCallback) _finalizeCallback(); + + // return next object + return _next; + } + + /** + * The channel implementation may call our + * private members and construct us + */ + friend class ChannelImpl; + friend class ConsumedMessage; + +protected: + /** + * Protected constructor that can only be called + * from within the channel implementation + * + * @param boolean are we already failed? + */ + DeferredCancel(bool failed = false) : Deferred(failed) {} + +public: + /** + * Register a function to be called when the cancel operation succeeded + * + * Only one callback can be registered. Successive calls + * to this function will clear callbacks registered before. + * + * @param callback the callback to execute + */ + DeferredCancel &onSuccess(const CancelCallback &callback) + { + // store callback + _cancelCallback = callback; + + // allow chaining + return *this; + } + + /** + * All the onSuccess() functions defined in the base class are accessible too + */ + using Deferred::onSuccess; +}; + +/** + * End namespace + */ +} diff --git a/include/deferredconsumer.h b/include/deferredconsumer.h index 8de39bd..c1555ad 100644 --- a/include/deferredconsumer.h +++ b/include/deferredconsumer.h @@ -14,13 +14,14 @@ namespace AMQP { /** * We extend from the default deferred and add extra functionality */ -class DeferredConsumer : public Deferred +class DeferredConsumer : public Deferred { private: /** * Callback to execute when a message arrives + * @var ConsumeCallbacl */ - std::function _messageCallback; + ConsumeCallback _consumeCallback; /** * Process a message @@ -33,7 +34,7 @@ private: void message(const Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) const { // do we have a valid callback - if (_messageCallback) _messageCallback(message, deliveryTag, consumerTag, redelivered); + if (_consumeCallback) _consumeCallback(message, deliveryTag, consumerTag, redelivered); } /** @@ -48,7 +49,6 @@ protected: * Protected constructor that can only be called * from within the channel implementation * - * @param channel the channel we operate under * @param boolea are we already failed? */ DeferredConsumer(bool failed = false) : Deferred(failed) {} @@ -62,12 +62,17 @@ public: * * @param callback the callback to execute */ - DeferredConsumer& onReceived(const std::function& callback) + DeferredConsumer& onReceived(const ConsumeCallback &callback) { // store callback - _messageCallback = callback; + _consumeCallback = callback; return *this; } + + /** + * All the onSuccess() functions defined in the base class are accessible too + */ + using Deferred::onSuccess; }; /** diff --git a/include/deferreddelete.h b/include/deferreddelete.h new file mode 100644 index 0000000..43ada61 --- /dev/null +++ b/include/deferreddelete.h @@ -0,0 +1,91 @@ +/** + * DeferredDelete.h + * + * Deferred callback for instructions that delete or purge queues, and that + * want to report the number of deleted messages. + * + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * We extend from the default deferred and add extra functionality + */ +class DeferredDelete : public Deferred +{ +private: + /** + * Callback to execute when the instruction is completed + * @var DeleteCallback + */ + DeleteCallback _deleteCallback; + + /** + * Report success for queue delete and queue purge messages + * @param messagecount Number of messages that were deleted + * @return Deferred Next deferred result + */ + virtual Deferred *reportSuccess(uint32_t messagecount) const override + { + // skip if no special callback was installed + if (!_deleteCallback) return Deferred::reportSuccess(); + + // call the callback + _deleteCallback(messagecount); + + // call finalize callback + if (_finalizeCallback) _finalizeCallback(); + + // return next object + return _next; + } + + + /** + * The channel implementation may call our + * private members and construct us + */ + friend class ChannelImpl; + friend class ConsumedMessage; + +protected: + /** + * Protected constructor that can only be called + * from within the channel implementation + * + * @param boolean are we already failed? + */ + DeferredDelete(bool failed = false) : Deferred(failed) {} + +public: + /** + * Register a function to be called when the queue is deleted or purged + * + * Only one callback can be registered. Successive calls + * to this function will clear callbacks registered before. + * + * @param callback the callback to execute + */ + DeferredDelete &onSuccess(const DeleteCallback &callback) + { + // store callback + _deleteCallback = callback; + + // allow chaining + return *this; + } + + /** + * All the onSuccess() functions defined in the base class are accessible too + */ + using Deferred::onSuccess; +}; + +/** + * End namespace + */ +} diff --git a/include/deferredqueue.h b/include/deferredqueue.h new file mode 100644 index 0000000..085f095 --- /dev/null +++ b/include/deferredqueue.h @@ -0,0 +1,91 @@ +/** + * DeferredQueue.h + * + * Deferred callback for "declare-queue" instructions. + * + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * We extend from the default deferred and add extra functionality + */ +class DeferredQueue : public Deferred +{ +private: + /** + * Callback to execute when the queue is declared + * @var QueueCallback + */ + QueueCallback _queueCallback; + + /** + * Report success for queue declared messages + * @param name Name of the new queue + * @param messagecount Number of messages in the queue + * @param consumercount Number of consumers linked to the queue + * @return Deferred Next deferred result + */ + virtual Deferred *reportSuccess(const std::string &name, uint32_t messagecount, uint32_t consumercount) const override + { + // skip if no special callback was installed + if (!_queueCallback) return Deferred::reportSuccess(); + + // call the queue callback + _queueCallback(name, messagecount, consumercount); + + // call finalize callback + if (_finalizeCallback) _finalizeCallback(); + + // return next object + return _next; + } + + /** + * The channel implementation may call our + * private members and construct us + */ + friend class ChannelImpl; + friend class ConsumedMessage; + +protected: + /** + * Protected constructor that can only be called + * from within the channel implementation + * + * @param boolea are we already failed? + */ + DeferredQueue(bool failed = false) : Deferred(failed) {} + +public: + /** + * Register a function to be called when the queue is declared + * + * Only one callback can be registered. Successive calls + * to this function will clear callbacks registered before. + * + * @param callback the callback to execute + */ + DeferredQueue &onSuccess(const QueueCallback &callback) + { + // store callback + _queueCallback = callback; + + // allow chaining + return *this; + } + + /** + * All the onSuccess() functions defined in the base class are accessible too + */ + using Deferred::onSuccess; +}; + +/** + * End namespace + */ +} diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index a478dff..53e7438 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -85,6 +85,28 @@ ChannelImpl::~ChannelImpl() // close the channel now close(); + + + // @todo destruct deferred resutls +} + +/** + * Push a deferred result + * @param result The deferred object to push + * @param error Error message in case of error + */ +void ChannelImpl::push(Deferred *deferred, const char *error) +{ + // do we already have an oldest? + if (!_oldestCallback) _oldestCallback = deferred; + + // do we already have a newest? + if (_newestCallback) _newestCallback->add(deferred); + + // store newest callback + _newestCallback = deferred; + + // @todo in case of error we have to report the error with a timeout } /** @@ -95,10 +117,10 @@ ChannelImpl::~ChannelImpl() * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::pause() +Deferred &ChannelImpl::pause() { // send a channel flow frame - return send<>(ChannelFlowFrame(_id, false), "Cannot send channel flow frame"); + return send(ChannelFlowFrame(_id, false), "Cannot send channel flow frame"); } /** @@ -109,10 +131,10 @@ Deferred<>& ChannelImpl::pause() * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::resume() +Deferred &ChannelImpl::resume() { // send a channel flow frame - return send<>(ChannelFlowFrame(_id, true), "Cannot send channel flow frame"); + return send(ChannelFlowFrame(_id, true), "Cannot send channel flow frame"); } /** @@ -121,10 +143,10 @@ Deferred<>& ChannelImpl::resume() * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::startTransaction() +Deferred &ChannelImpl::startTransaction() { // send a transaction frame - return send<>(TransactionSelectFrame(_id), "Cannot send transaction start frame"); + return send(TransactionSelectFrame(_id), "Cannot send transaction start frame"); } /** @@ -133,10 +155,10 @@ Deferred<>& ChannelImpl::startTransaction() * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::commitTransaction() +Deferred &ChannelImpl::commitTransaction() { // send a transaction frame - return send<>(TransactionCommitFrame(_id), "Cannot send transaction commit frame"); + return send(TransactionCommitFrame(_id), "Cannot send transaction commit frame"); } /** @@ -145,10 +167,10 @@ Deferred<>& ChannelImpl::commitTransaction() * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::rollbackTransaction() +Deferred &ChannelImpl::rollbackTransaction() { // send a transaction frame - return send<>(TransactionRollbackFrame(_id), "Cannot send transaction commit frame"); + return send(TransactionRollbackFrame(_id), "Cannot send transaction commit frame"); } /** @@ -157,13 +179,13 @@ Deferred<>& ChannelImpl::rollbackTransaction() * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::close() +Deferred &ChannelImpl::close() { // channel could be dead after send operation, we need to monitor that Monitor monitor(this); // send a channel close frame - auto &handler = send<>(ChannelCloseFrame(_id), "Cannot send channel close frame"); + auto &handler = send(ChannelCloseFrame(_id), "Cannot send channel close frame"); // was the frame sent and are we still alive? if (handler && monitor.valid()) _state = state_closing; @@ -183,7 +205,7 @@ Deferred<>& ChannelImpl::close() * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) +Deferred &ChannelImpl::declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) { // convert exchange type std::string exchangeType; @@ -193,7 +215,7 @@ Deferred<>& ChannelImpl::declareExchange(const std::string &name, ExchangeType t if (type == ExchangeType::headers)exchangeType = "headers"; // send declare exchange frame - return send<>(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments), "Cannot send exchange declare frame"); + return send(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments), "Cannot send exchange declare frame"); } /** @@ -208,10 +230,10 @@ Deferred<>& ChannelImpl::declareExchange(const std::string &name, ExchangeType t * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) +Deferred &ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { // send exchange bind frame - return send<>(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange bind frame"); + return send(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange bind frame"); } /** @@ -226,10 +248,10 @@ Deferred<>& ChannelImpl::bindExchange(const std::string &source, const std::stri * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) +Deferred &ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { // send exchange unbind frame - return send<>(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange unbind frame"); + return send(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange unbind frame"); } /** @@ -241,10 +263,10 @@ Deferred<>& ChannelImpl::unbindExchange(const std::string &source, const std::st * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::removeExchange(const std::string &name, int flags) +Deferred &ChannelImpl::removeExchange(const std::string &name, int flags) { // send delete exchange frame - return send<>(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait), "Cannot send exchange delete frame"); + return send(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait), "Cannot send exchange delete frame"); } /** @@ -256,10 +278,19 @@ Deferred<>& ChannelImpl::removeExchange(const std::string &name, int flags) * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred& ChannelImpl::declareQueue(const std::string &name, int flags, const Table &arguments) +DeferredQueue &ChannelImpl::declareQueue(const std::string &name, int flags, const Table &arguments) { + // the frame to send + QueueDeclareFrame frame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, flags & nowait, arguments); + // send the queuedeclareframe - return send(QueueDeclareFrame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, flags & nowait, arguments), "Cannot send queue declare frame"); + auto *result = new DeferredQueue(send(frame)); + + // add the deferred result + push(result, "Cannot send queue declare frame"); + + // done + return *result; } /** @@ -274,10 +305,10 @@ Deferred& ChannelImpl::declareQueue(cons * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments) +Deferred &ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments) { // send the bind queue frame - return send<>(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments), "Cannot send queue bind frame"); + return send(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments), "Cannot send queue bind frame"); } /** @@ -291,10 +322,10 @@ Deferred<>& ChannelImpl::bindQueue(const std::string &exchangeName, const std::s * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) +Deferred &ChannelImpl::unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { // send the unbind queue frame - return send<>(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments), "Cannot send queue unbind frame"); + return send(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments), "Cannot send queue unbind frame"); } /** @@ -315,10 +346,19 @@ Deferred<>& ChannelImpl::unbindQueue(const std::string &exchange, const std::str * * }); */ -Deferred& ChannelImpl::purgeQueue(const std::string &name, int flags) +DeferredDelete &ChannelImpl::purgeQueue(const std::string &name, int flags) { - // send the queue purge frame - return send(QueuePurgeFrame(_id, name, flags & nowait), "Cannot send queue purge frame"); + // the frame to send + QueuePurgeFrame frame(_id, name, flags & nowait); + + // send the frame, and create deferred object + auto *deferred = new DeferredDelete(send(frame)); + + // push to list + push(deferred, "Cannot send queue purge frame"); + + // done + return *deferred; } /** @@ -339,10 +379,19 @@ Deferred& ChannelImpl::purgeQueue(const std::string &name, int flags) * * }); */ -Deferred& ChannelImpl::removeQueue(const std::string &name, int flags) +DeferredDelete &ChannelImpl::removeQueue(const std::string &name, int flags) { - // send the remove queue frame - return send(QueueDeleteFrame(_id, name, flags & ifunused, flags & ifempty, flags & nowait), "Cannot send remove queue frame"); + // the frame to send + QueueDeleteFrame frame(_id, name, flags & ifunused, flags & ifempty, flags & nowait); + + // send the frame, and create deferred object + auto *deferred = new DeferredDelete(send(frame)); + + // push to list + push(deferred, "Cannot send remove queue frame"); + + // done + return *deferred; } /** @@ -410,7 +459,7 @@ bool ChannelImpl::publish(const std::string &exchange, const std::string &routin * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::setQos(uint16_t prefetchCount) +Deferred &ChannelImpl::setQos(uint16_t prefetchCount) { // send a qos frame return send(BasicQosFrame(_id, prefetchCount, false), "Cannot send basic QOS frame"); @@ -473,10 +522,19 @@ DeferredConsumer& ChannelImpl::consume(const std::string &queue, const std::stri * * }); */ -Deferred& ChannelImpl::cancel(const std::string &tag, int flags) +DeferredCancel &ChannelImpl::cancel(const std::string &tag, int flags) { - // send a cancel frame - return send(BasicCancelFrame(_id, tag, flags & nowait), "Cannot send basic cancel frame"); + // the cancel frame to send + BasicCancelFrame frame(_id, tag, flags & nowait); + + // send the frame, and create deferred object + auto *deferred = new DeferredCancel(send(frame)); + + // push to list + push(deferred, "Cannot send basic cancel frame"); + + // done + return *deferred; } /** @@ -510,7 +568,7 @@ bool ChannelImpl::reject(uint64_t deliveryTag, int flags) * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred<>& ChannelImpl::recover(int flags) +Deferred &ChannelImpl::recover(int flags) { // send a nack frame return send(BasicRecoverFrame(_id, flags & requeue), "Cannot send basic recover frame"); @@ -536,32 +594,16 @@ bool ChannelImpl::send(const Frame &frame) * @param frame frame to send * @param message the message to trigger if the frame cannot be send at all */ -template -Deferred& ChannelImpl::send(const Frame &frame, const char *message) +Deferred &ChannelImpl::send(const Frame &frame, const char *message) { - // create a new deferred handler and get a pointer to it - // note: cannot use auto here or the lambda below chokes - // when compiling under gcc 4.8 - Deferred *handler = &_callbacks.push_back(Deferred()); - - // send the frame over the channel - if (!send(frame)) - { - // we can immediately put the handler in failed state - handler->_failed = true; - - // register an error on the deferred handler - // after a timeout, so it gets called only - // after a possible handler was installed. - _connection->_handler->setTimeout(_connection->_parent, 0, [handler, message]() { - - // emit an error on the handler - handler->error(message); - }); - } - - // return the new handler - return *handler; + // send the frame, and create deferred object + auto *deferred = new Deferred(send(frame)); + + // push to list + push(deferred, message); + + // done + return *deferred; } /** @@ -569,6 +611,8 @@ Deferred& ChannelImpl::send(const Frame &frame, const char *messag */ void ChannelImpl::reportMessage() { + // @todo what does this method do? + // skip if there is no message if (!_message) return; diff --git a/src/queuedeclareokframe.h b/src/queuedeclareokframe.h index 875ff2b..eb660d8 100644 --- a/src/queuedeclareokframe.h +++ b/src/queuedeclareokframe.h @@ -132,8 +132,8 @@ public: // what if channel doesn't exist? if (!channel) return false; - // report to the handler, we need to specify template arguments otherwise a string will lose const and reference - channel->reportSuccess(this->name(), this->messageCount(), this->consumerCount()); + // report success + channel->reportSuccess(name(), messageCount(), consumerCount()); // done return true; From 745ab512a5d4116d08fbd936d47a17e853fc9ee6 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 11:39:52 +0200 Subject: [PATCH 22/41] the consumer message callback can now also be installed via the Deferred objects, and it is no longer passed a consumer tag, because it already is obvious what the consumer tag is supposed to be --- include/callbacks.h | 3 +- include/channelimpl.h | 53 +++++++++++++++-------------- include/deferredconsumer.h | 59 +++++++++++++++++++++----------- src/basicconsumeokframe.h | 2 +- src/channelimpl.cpp | 69 +++++++++++++++++--------------------- src/consumedmessage.h | 17 +++++++--- src/deferredconsumer.cpp | 43 ++++++++++++++++++++++++ src/messageimpl.h | 6 ---- src/receivedframe.cpp | 1 + src/returnedmessage.h | 9 ----- 10 files changed, 157 insertions(+), 105 deletions(-) create mode 100644 src/deferredconsumer.cpp diff --git a/include/callbacks.h b/include/callbacks.h index 50239a0..a974ef0 100644 --- a/include/callbacks.h +++ b/include/callbacks.h @@ -20,9 +20,10 @@ namespace AMQP { using SuccessCallback = std::function; using ErrorCallback = std::function; using FinalizeCallback = std::function; -using ConsumeCallback = std::function; +using MessageCallback = std::function; using QueueCallback = std::function; using DeleteCallback = std::function; +using ConsumeCallback = std::function; using CancelCallback = std::function; /** diff --git a/include/channelimpl.h b/include/channelimpl.h index 43a87ba..8666240 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -14,6 +14,11 @@ */ namespace AMQP { +/** + * Forward declarations + */ +class ConsumedMessage; + /** * Class definition */ @@ -45,11 +50,10 @@ private: ErrorCallback _errorCallback; /** - * Callback to execute when a message arrives - * - * @todo do this different?? + * Callbacks for all consumers that are active + * @var std::map */ - std::unique_ptr _consumer; + std::map _consumers; /** * Pointer to the oldest deferred result (the first one that is going @@ -84,9 +88,9 @@ private: /** * The message that is now being received - * @var MessageImpl + * @var ConsumedMessage */ - MessageImpl *_message = nullptr; + ConsumedMessage *_message = nullptr; /** * Construct a channel object @@ -491,6 +495,20 @@ public: // @todo destruct oldest callback } + /** + * Install a consumer callback + * @param consumertag The consumer tag + * @param callback The callback to be called + */ + void install(const std::string &consumertag, const MessageCallback &callback) + { + // install the callback if it is assigned + if (callback) _consumers[consumertag] = callback; + + // otherwise we erase the previously set callback + else _consumers.erase(consumertag); + } + /** * Report that a message was received */ @@ -499,34 +517,19 @@ public: /** * Create an incoming message * @param frame - * @return MessageImpl + * @return ConsumedMessage */ - MessageImpl *message(const BasicDeliverFrame &frame); + ConsumedMessage *message(const BasicDeliverFrame &frame); /** * Retrieve the current incoming message - * @return MessageImpl + * @return ConsumedMessage */ - MessageImpl *message() + ConsumedMessage *message() { return _message; } - /** - * Report that the consumer has started - * - * @param consumerTag the tag under which we are now consuming - */ - void reportConsumerStarted(const std::string& consumerTag) - { - // if we do not have a consumer, something is very wrong - if (!_consumer) reportError("Received basic consume ok frame, but no consumer was found"); - - // otherwise, we now report the consumer as started - // @todo look at this implementation - //else _consumer->success(consumerTag); - } - /** * The channel class is its friend, thus can it instantiate this object */ diff --git a/include/deferredconsumer.h b/include/deferredconsumer.h index c1555ad..60104d1 100644 --- a/include/deferredconsumer.h +++ b/include/deferredconsumer.h @@ -17,25 +17,31 @@ namespace AMQP { class DeferredConsumer : public Deferred { private: + /** + * The channel to which the consumer is linked + * @var ChannelImpl + */ + ChannelImpl *_channel; + /** * Callback to execute when a message arrives - * @var ConsumeCallbacl + * @var ConsumeCallback */ ConsumeCallback _consumeCallback; /** - * Process a message - * - * @param message the message to process - * @param deliveryTag the message delivery tag - * @param consumerTag the tag we are consuming under - * @param is this a redelivered message? + * Callback for incoming messages + * @var MessageCallback */ - void message(const Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) const - { - // do we have a valid callback - if (_consumeCallback) _consumeCallback(message, deliveryTag, consumerTag, redelivered); - } + MessageCallback _messageCallback; + + + /** + * Report success for frames that report start consumer operations + * @param name Consumer tag that is started + * @return Deferred + */ + virtual Deferred *reportSuccess(const std::string &name) const override; /** * The channel implementation may call our @@ -49,23 +55,36 @@ protected: * Protected constructor that can only be called * from within the channel implementation * - * @param boolea are we already failed? + * @param channel the channel implementation + * @param failed are we already failed? */ - DeferredConsumer(bool failed = false) : Deferred(failed) {} + DeferredConsumer(ChannelImpl *channel, bool failed = false) : + Deferred(failed), _channel(channel) {} public: + /** + * Register the function that is called when the consumer starts + * @param callback + */ + DeferredConsumer &onSuccess(const ConsumeCallback &callback) + { + // store the callback + _consumeCallback = callback; + + // allow chaining + return *this; + } + /** * Register a function to be called when a message arrives - * - * Only one callback can be registered. Successive calls - * to this function will clear callbacks registered before. - * * @param callback the callback to execute */ - DeferredConsumer& onReceived(const ConsumeCallback &callback) + DeferredConsumer& onReceived(const MessageCallback &callback) { // store callback - _consumeCallback = callback; + _messageCallback = callback; + + // allow chaining return *this; } diff --git a/src/basicconsumeokframe.h b/src/basicconsumeokframe.h index 1ed8ef4..31c44ef 100644 --- a/src/basicconsumeokframe.h +++ b/src/basicconsumeokframe.h @@ -94,7 +94,7 @@ public: if (!channel) return false; // report - channel->reportConsumerStarted(consumerTag()); + channel->reportSuccess(consumerTag()); // done return true; diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 53e7438..a28bf77 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -487,21 +487,17 @@ Deferred &ChannelImpl::setQos(uint16_t prefetchCount) */ DeferredConsumer& ChannelImpl::consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { - // create the deferred consumer - _consumer = std::unique_ptr(new DeferredConsumer(false)); - - // can we send the basic consume frame? - if (!send(BasicConsumeFrame(_id, queue, tag, flags & nolocal, flags & noack, flags & exclusive, flags & nowait, arguments))) - { - // we set the consumer to be failed immediately - _consumer->_failed = true; - - // we should call the error function later - // TODO - } - - // return the consumer - return *_consumer; + // the frame to send + BasicConsumeFrame frame(_id, queue, tag, flags & nolocal, flags & noack, flags & exclusive, flags & nowait, arguments); + + // send the frame, and create deferred object + auto *deferred = new DeferredConsumer(this, send(frame)); + + // push to list + push(deferred, "Cannot send basic consume frame"); + + // done + return *deferred; } /** @@ -611,44 +607,39 @@ Deferred &ChannelImpl::send(const Frame &frame, const char *message) */ void ChannelImpl::reportMessage() { - // @todo what does this method do? - // skip if there is no message if (!_message) return; - // do we even have a consumer? - if (!_consumer) - { - // this should not be possible: receiving a message without doing a consume() call - reportError("Received message without having a consumer"); - } - else - { - // after the report the channel may be destructed, monitor that - Monitor monitor(this); + // look for the consumer + auto iter = _consumers.find(_message->consumer()); + if (iter == _consumers.end()) return; + + // is this a valid callback method + if (!iter->second) return; - // send message to the consumer - _message->report(*_consumer); - - // skip if channel was destructed - if (!monitor.valid()) return; - } + // after the report the channel may be destructed, monitor that + Monitor monitor(this); + + // call the callback + _message->report(iter->second); + + // skip if channel was destructed + if (!monitor.valid()) return; // no longer need the message - delete _message; - _message = nullptr; + delete _message; _message = nullptr; } /** * Create an incoming message * @param frame - * @return MessageImpl + * @return ConsumedMessage */ -MessageImpl *ChannelImpl::message(const BasicDeliverFrame &frame) +ConsumedMessage *ChannelImpl::message(const BasicDeliverFrame &frame) { - // it should not be possible that a message already exists, but lets check it anyhow + // destruct if message is already set if (_message) delete _message; - + // construct a message return _message = new ConsumedMessage(frame); } diff --git a/src/consumedmessage.h b/src/consumedmessage.h index 2227763..447bdc6 100644 --- a/src/consumedmessage.h +++ b/src/consumedmessage.h @@ -50,13 +50,22 @@ public: virtual ~ConsumedMessage() {} /** - * Report to the handler - * @param consumer + * Retrieve the consumer tag + * @return std::string */ - virtual void report(const DeferredConsumer& consumer) override + const std::string &consumer() const + { + return _consumerTag; + } + + /** + * Report to the handler + * @param callback + */ + void report(const MessageCallback &callback) const { // send ourselves to the consumer - consumer.message(*this, _deliveryTag, _consumerTag, _redelivered); + if (callback) callback(*this, _deliveryTag, _redelivered); } }; diff --git a/src/deferredconsumer.cpp b/src/deferredconsumer.cpp new file mode 100644 index 0000000..eb8c6e1 --- /dev/null +++ b/src/deferredconsumer.cpp @@ -0,0 +1,43 @@ +/** + * DeferredConsumer.cpp + * + * Implementation file for the DeferredConsumer class + * + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Namespace + */ +namespace AMQP { + +/** + * Report success for frames that report start consumer operations + * @param name Consumer tag that is started + * @return Deferred + */ +Deferred *DeferredConsumer::reportSuccess(const std::string &name) const +{ + // we now know the name, so we can install the message callback on the channel + _channel->install(name, _messageCallback); + + // @todo when a consumer stops, we should uninstall the message callback + + // skip if no special callback was installed + if (!_consumeCallback) return Deferred::reportSuccess(); + + // call the callback + _consumeCallback(name); + + // call finalize callback + if (_finalizeCallback) _finalizeCallback(); + + // return next object + return _next; +} + +/** + * End namespace + */ +} diff --git a/src/messageimpl.h b/src/messageimpl.h index 1b087dd..f9f839a 100644 --- a/src/messageimpl.h +++ b/src/messageimpl.h @@ -98,12 +98,6 @@ public: return _received >= _bodySize; } } - - /** - * Report to the handler - * @param consumer - */ - virtual void report(const DeferredConsumer& consumer) = 0; }; /** diff --git a/src/receivedframe.cpp b/src/receivedframe.cpp index cfe1c77..87aaba0 100644 --- a/src/receivedframe.cpp +++ b/src/receivedframe.cpp @@ -66,6 +66,7 @@ #include "transactionrollbackframe.h" #include "transactionrollbackokframe.h" #include "messageimpl.h" +#include "consumedmessage.h" #include "bodyframe.h" #include "basicheaderframe.h" #include "framecheck.h" diff --git a/src/returnedmessage.h b/src/returnedmessage.h index 322bb88..776d72d 100644 --- a/src/returnedmessage.h +++ b/src/returnedmessage.h @@ -45,15 +45,6 @@ public: * Destructor */ virtual ~ReturnedMessage() {} - - /** - * Report to the handler - * @param consumer - */ - virtual void report(const DeferredConsumer& consumer) override - { - // we no longer support returned messages - } }; /** From 301b8153e30e1a2d76d6df19e0969bca01cf875f Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 12:25:56 +0200 Subject: [PATCH 23/41] deferred objects are now correctly destructed + added extra checks so that no crashes occur when someone destructs a channel inside a callback function --- amqpcpp.h | 1 + include/channelimpl.h | 43 +++++++++++++++++++++++++++----------- include/deferred.h | 9 ++++++++ {src => include}/monitor.h | 0 src/channelimpl.cpp | 6 +++--- src/includes.h | 1 - 6 files changed, 44 insertions(+), 16 deletions(-) rename {src => include}/monitor.h (100%) diff --git a/amqpcpp.h b/amqpcpp.h index 06f19f8..0065da6 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -34,6 +34,7 @@ #include #include #include +#include // amqp types #include diff --git a/include/channelimpl.h b/include/channelimpl.h index 8666240..91d666c 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -61,7 +61,7 @@ private: * * @var Deferred */ - Deferred *_oldestCallback = nullptr; + std::unique_ptr _oldestCallback = nullptr; /** * Pointer to the newest deferred result (the last one to be added). @@ -464,11 +464,21 @@ public: { // skip if there is no oldest callback if (!_oldestCallback) return; + + // we are going to call callbacks that could destruct the channel + Monitor monitor(this); + + // call the callback + auto *next = _oldestCallback->reportSuccess(std::forward(parameters)...); - // report to the oldest callback, and install a new oldest callback - _oldestCallback = _oldestCallback->reportSuccess(std::forward(parameters)...); + // leap out if channel no longer exists + if (!monitor.valid()) return; - // @todo destruct oldest callback + // set the oldest callback + _oldestCallback.reset(next); + + // if there was no next callback, the newest callback was just used + if (!next) _newestCallback = nullptr; } /** @@ -480,19 +490,28 @@ public: // change state _state = state_closed; - // @todo multiple callbacks are called, this could break + // we are going to call callbacks that could destruct the channel + Monitor monitor(this); + // @todo should this be a std::string parameter? // inform handler if (_errorCallback) _errorCallback(message.c_str()); + + // leap out if channel is already destructed, or when there are no further callbacks + if (!monitor.valid() || !_oldestCallback) return; - // skip if there is no oldest callback - if (!_oldestCallback) return; - - // report to the oldest callback, and install a new oldest callback - _oldestCallback = _oldestCallback->reportError(message); - - // @todo destruct oldest callback + // call the callback + auto *next = _oldestCallback->reportError(message); + + // leap out if channel no longer exists + if (!monitor.valid()) return; + + // set the oldest callback + _oldestCallback.reset(next); + + // if there was no next callback, the newest callback was just used + if (!next) _newestCallback = nullptr; } /** diff --git a/include/deferred.h b/include/deferred.h index 777a370..f2e849c 100644 --- a/include/deferred.h +++ b/include/deferred.h @@ -54,6 +54,15 @@ protected: bool _failed; + /** + * The next deferred object + * @return Deferred + */ + Deferred *next() const + { + return _next; + } + /** * Indicate success * @return Deferred Next deferred result diff --git a/src/monitor.h b/include/monitor.h similarity index 100% rename from src/monitor.h rename to include/monitor.h diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index a28bf77..8d93563 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -86,8 +86,8 @@ ChannelImpl::~ChannelImpl() // close the channel now close(); - - // @todo destruct deferred resutls + // destruct deferred results + while (_oldestCallback) _oldestCallback.reset(_oldestCallback->next()); } /** @@ -98,7 +98,7 @@ ChannelImpl::~ChannelImpl() void ChannelImpl::push(Deferred *deferred, const char *error) { // do we already have an oldest? - if (!_oldestCallback) _oldestCallback = deferred; + if (!_oldestCallback) _oldestCallback.reset(deferred); // do we already have a newest? if (_newestCallback) _newestCallback->add(deferred); diff --git a/src/includes.h b/src/includes.h index 52b31d0..2a75192 100644 --- a/src/includes.h +++ b/src/includes.h @@ -11,7 +11,6 @@ #include "../amqpcpp.h" // classes that are very commonly used -#include "monitor.h" #include "exception.h" #include "protocolexception.h" #include "frame.h" From 3b782473633145a8ab48da42bd8d81fab3f68093 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 12:29:22 +0200 Subject: [PATCH 24/41] error callbacks get a const char *, no longer a std::string --- include/channelimpl.h | 4 ++-- include/deferred.h | 19 +------------------ src/channelcloseframe.h | 2 +- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/include/channelimpl.h b/include/channelimpl.h index 91d666c..ba0d9e4 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -485,7 +485,7 @@ public: * Report an error message on a channel * @param message */ - void reportError(const std::string &message) + void reportError(const char *message) { // change state _state = state_closed; @@ -496,7 +496,7 @@ public: // @todo should this be a std::string parameter? // inform handler - if (_errorCallback) _errorCallback(message.c_str()); + if (_errorCallback) _errorCallback(message); // leap out if channel is already destructed, or when there are no further callbacks if (!monitor.valid() || !_oldestCallback) return; diff --git a/include/deferred.h b/include/deferred.h index f2e849c..897c36a 100644 --- a/include/deferred.h +++ b/include/deferred.h @@ -117,28 +117,11 @@ protected: * @param error Description of the error that occured * @return Deferred Next deferred result */ - Deferred *reportError(const std::string& error) - { - // from this moment on the object should be listed as failed - _failed = true; - - // execute callbacks if registered - if (_errorCallback) _errorCallback(error.c_str()); - if (_finalizeCallback) _finalizeCallback(); - - // return the next deferred result - return _next; - } - - /** - * Indicate failure - * @param error description of the error that occured - */ Deferred *reportError(const char *error) { // from this moment on the object should be listed as failed _failed = true; - + // execute callbacks if registered if (_errorCallback) _errorCallback(error); if (_finalizeCallback) _finalizeCallback(); diff --git a/src/channelcloseframe.h b/src/channelcloseframe.h index 6db745f..0d9c1d6 100644 --- a/src/channelcloseframe.h +++ b/src/channelcloseframe.h @@ -156,7 +156,7 @@ public: if (!channel) return false; // report to the handler - channel->reportError(text()); + channel->reportError(_text.value().c_str()); // done return true; From ae7a32a8bf7c602171c510fdd0871d6dd10dbd26 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 12:36:11 +0200 Subject: [PATCH 25/41] when a consumer is cancelled, it is also removed from the map of active consumers in the the ChannelImpl object --- include/channelimpl.h | 12 ++++++++++-- include/deferredcancel.h | 27 +++++++++++--------------- src/channelimpl.cpp | 2 +- src/deferredcancel.cpp | 42 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 src/deferredcancel.cpp diff --git a/include/channelimpl.h b/include/channelimpl.h index ba0d9e4..0f607bf 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -493,8 +493,6 @@ public: // we are going to call callbacks that could destruct the channel Monitor monitor(this); - // @todo should this be a std::string parameter? - // inform handler if (_errorCallback) _errorCallback(message); @@ -528,6 +526,16 @@ public: else _consumers.erase(consumertag); } + /** + * Uninstall a consumer callback + * @param consumertag The consumer tag + */ + void uninstall(const std::string &consumertag) + { + // erase the callback + _consumers.erase(consumertag); + } + /** * Report that a message was received */ diff --git a/include/deferredcancel.h b/include/deferredcancel.h index 401716d..244e145 100644 --- a/include/deferredcancel.h +++ b/include/deferredcancel.h @@ -19,6 +19,12 @@ namespace AMQP { class DeferredCancel : public Deferred { private: + /** + * Pointer to the channel + * @var ChannelImpl + */ + ChannelImpl *_channel; + /** * Callback to execute when the instruction is completed * @var CancelCallback @@ -30,20 +36,7 @@ private: * @param name Consumer tag that is cancelled * @return Deferred */ - virtual Deferred *reportSuccess(const std::string &name) const override - { - // skip if no special callback was installed - if (!_cancelCallback) return Deferred::reportSuccess(); - - // call the callback - _cancelCallback(name); - - // call finalize callback - if (_finalizeCallback) _finalizeCallback(); - - // return next object - return _next; - } + virtual Deferred *reportSuccess(const std::string &name) const override; /** * The channel implementation may call our @@ -57,9 +50,11 @@ protected: * Protected constructor that can only be called * from within the channel implementation * - * @param boolean are we already failed? + * @param channel Pointer to the channel + * @param failed Are we already failed? */ - DeferredCancel(bool failed = false) : Deferred(failed) {} + DeferredCancel(ChannelImpl *channel, bool failed = false) : + Deferred(failed), _channel(channel) {} public: /** diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 8d93563..aac9ce6 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -524,7 +524,7 @@ DeferredCancel &ChannelImpl::cancel(const std::string &tag, int flags) BasicCancelFrame frame(_id, tag, flags & nowait); // send the frame, and create deferred object - auto *deferred = new DeferredCancel(send(frame)); + auto *deferred = new DeferredCancel(this, send(frame)); // push to list push(deferred, "Cannot send basic cancel frame"); diff --git a/src/deferredcancel.cpp b/src/deferredcancel.cpp new file mode 100644 index 0000000..aaecebe --- /dev/null +++ b/src/deferredcancel.cpp @@ -0,0 +1,42 @@ +/** + * DeferredCancel.cpp + * + * Implementation file for the DeferredCancel class + * + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Namespace + */ +namespace AMQP { + +/** + * Report success for frames that report cancel operations + * @param name Consumer tag that is cancelled + * @return Deferred + */ +Deferred *DeferredCancel::reportSuccess(const std::string &name) const +{ + // in the channel, we should uninstall the consumer + _channel->uninstall(name); + + // skip if no special callback was installed + if (!_cancelCallback) return Deferred::reportSuccess(); + + // call the callback + _cancelCallback(name); + + // call finalize callback + if (_finalizeCallback) _finalizeCallback(); + + // return next object + return _next; +} + +/** + * End namespace + */ +} + From d08270701e4aabc09f0b3637246e2eeda3e7b318 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 13:01:27 +0200 Subject: [PATCH 26/41] refactored dealing with error messages --- include/channelimpl.h | 28 ++++++++------ include/connectionhandler.h | 7 ++-- src/channelimpl.cpp | 77 ++++++++++++++++--------------------- src/deferredconsumer.cpp | 2 - 4 files changed, 54 insertions(+), 60 deletions(-) diff --git a/include/channelimpl.h b/include/channelimpl.h index 0f607bf..62cc70d 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -107,9 +107,16 @@ private: /** * Push a deferred result * @param result The deferred result - * @param error Error message in case the result is not ok + * @return Deferred The object just pushed */ - void push(Deferred *deferred, const char *error); + Deferred &push(Deferred *deferred); + + /** + * Send a framen and push a deferred result + * @param frame The frame to send + * @return Deferred The object just pushed + */ + Deferred &push(const Frame &frame); public: @@ -422,18 +429,10 @@ public: */ bool send(const Frame &frame); - /** - * Send a frame over the channel and - * get a deferred handler for it. - * - * @param frame frame to send - * @param message the message to trigger if the frame cannot be send at all - * @return Deferred the deferred object - */ - Deferred &send(const Frame &frame, const char *message); - /** * Report to the handler that the channel is opened + * + * @todo when is this sent? */ void reportReady() { @@ -443,6 +442,8 @@ public: /** * Report to the handler that the channel is closed + * + * @todo do we need this? */ void reportClosed() { @@ -450,6 +451,9 @@ public: _state = state_closed; // inform handler + + // @todo do we report success here? + reportSuccess(); } diff --git a/include/connectionhandler.h b/include/connectionhandler.h index 2ffe23b..220e2dc 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -55,7 +55,7 @@ public: * state and can no longer be used. * * This method has an empty default implementation, although you are very - * much advised to implement it. Because when an error occurs, the connection + * much advised to implement it. When an error occurs, the connection * is no longer usable, so you probably want to know. * * @param connection The connection that entered the error state @@ -65,14 +65,15 @@ public: /** * Method that is called when the login attempt succeeded. After this method - * was called, the connection is ready to use. This is the first method + * is called, the connection is ready to use. This is the first method * that is normally called after you've constructed the connection object. * * According to the AMQP protocol, you must wait for the connection to become * ready (and this onConnected method to be called) before you can start * using the Connection object. However, this AMQP library will cache all * methods that you call before the connection is ready, so in reality there - * is no real reason to wait. + * is no real reason to wait for this method to be called before you send + * the first instructions. * * @param connection The connection that can now be used */ diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index aac9ce6..a3cc37e 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -93,9 +93,8 @@ ChannelImpl::~ChannelImpl() /** * Push a deferred result * @param result The deferred object to push - * @param error Error message in case of error */ -void ChannelImpl::push(Deferred *deferred, const char *error) +Deferred &ChannelImpl::push(Deferred *deferred) { // do we already have an oldest? if (!_oldestCallback) _oldestCallback.reset(deferred); @@ -107,6 +106,19 @@ void ChannelImpl::push(Deferred *deferred, const char *error) _newestCallback = deferred; // @todo in case of error we have to report the error with a timeout + + // done + return *deferred; +} + +/** + * Send a frame and push a deferred result + * @param frame The frame to send + */ +Deferred &ChannelImpl::push(const Frame &frame) +{ + // send the frame, and push the result + return push(new Deferred(send(frame))); } /** @@ -120,7 +132,7 @@ void ChannelImpl::push(Deferred *deferred, const char *error) Deferred &ChannelImpl::pause() { // send a channel flow frame - return send(ChannelFlowFrame(_id, false), "Cannot send channel flow frame"); + return push(ChannelFlowFrame(_id, false)); } /** @@ -134,7 +146,7 @@ Deferred &ChannelImpl::pause() Deferred &ChannelImpl::resume() { // send a channel flow frame - return send(ChannelFlowFrame(_id, true), "Cannot send channel flow frame"); + return push(ChannelFlowFrame(_id, true)); } /** @@ -146,7 +158,7 @@ Deferred &ChannelImpl::resume() Deferred &ChannelImpl::startTransaction() { // send a transaction frame - return send(TransactionSelectFrame(_id), "Cannot send transaction start frame"); + return push(TransactionSelectFrame(_id)); } /** @@ -158,7 +170,7 @@ Deferred &ChannelImpl::startTransaction() Deferred &ChannelImpl::commitTransaction() { // send a transaction frame - return send(TransactionCommitFrame(_id), "Cannot send transaction commit frame"); + return push(TransactionCommitFrame(_id)); } /** @@ -170,7 +182,7 @@ Deferred &ChannelImpl::commitTransaction() Deferred &ChannelImpl::rollbackTransaction() { // send a transaction frame - return send(TransactionRollbackFrame(_id), "Cannot send transaction commit frame"); + return push(TransactionRollbackFrame(_id)); } /** @@ -181,14 +193,11 @@ Deferred &ChannelImpl::rollbackTransaction() */ Deferred &ChannelImpl::close() { - // channel could be dead after send operation, we need to monitor that - Monitor monitor(this); - // send a channel close frame - auto &handler = send(ChannelCloseFrame(_id), "Cannot send channel close frame"); + auto &handler = push(ChannelCloseFrame(_id)); // was the frame sent and are we still alive? - if (handler && monitor.valid()) _state = state_closing; + if (handler) _state = state_closing; // done return handler; @@ -215,7 +224,7 @@ Deferred &ChannelImpl::declareExchange(const std::string &name, ExchangeType typ if (type == ExchangeType::headers)exchangeType = "headers"; // send declare exchange frame - return send(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments), "Cannot send exchange declare frame"); + return push(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments)); } /** @@ -233,7 +242,7 @@ Deferred &ChannelImpl::declareExchange(const std::string &name, ExchangeType typ Deferred &ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { // send exchange bind frame - return send(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange bind frame"); + return push(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments)); } /** @@ -251,7 +260,7 @@ Deferred &ChannelImpl::bindExchange(const std::string &source, const std::string Deferred &ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { // send exchange unbind frame - return send(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments), "Cannot send exchange unbind frame"); + return push(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments)); } /** @@ -266,7 +275,7 @@ Deferred &ChannelImpl::unbindExchange(const std::string &source, const std::stri Deferred &ChannelImpl::removeExchange(const std::string &name, int flags) { // send delete exchange frame - return send(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait), "Cannot send exchange delete frame"); + return push(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait)); } /** @@ -287,7 +296,7 @@ DeferredQueue &ChannelImpl::declareQueue(const std::string &name, int flags, con auto *result = new DeferredQueue(send(frame)); // add the deferred result - push(result, "Cannot send queue declare frame"); + push(result); // done return *result; @@ -308,7 +317,7 @@ DeferredQueue &ChannelImpl::declareQueue(const std::string &name, int flags, con Deferred &ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments) { // send the bind queue frame - return send(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments), "Cannot send queue bind frame"); + return push(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments)); } /** @@ -325,7 +334,7 @@ Deferred &ChannelImpl::bindQueue(const std::string &exchangeName, const std::str Deferred &ChannelImpl::unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { // send the unbind queue frame - return send(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments), "Cannot send queue unbind frame"); + return push(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments)); } /** @@ -355,7 +364,7 @@ DeferredDelete &ChannelImpl::purgeQueue(const std::string &name, int flags) auto *deferred = new DeferredDelete(send(frame)); // push to list - push(deferred, "Cannot send queue purge frame"); + push(deferred); // done return *deferred; @@ -388,7 +397,7 @@ DeferredDelete &ChannelImpl::removeQueue(const std::string &name, int flags) auto *deferred = new DeferredDelete(send(frame)); // push to list - push(deferred, "Cannot send remove queue frame"); + push(deferred); // done return *deferred; @@ -462,7 +471,7 @@ bool ChannelImpl::publish(const std::string &exchange, const std::string &routin Deferred &ChannelImpl::setQos(uint16_t prefetchCount) { // send a qos frame - return send(BasicQosFrame(_id, prefetchCount, false), "Cannot send basic QOS frame"); + return push(BasicQosFrame(_id, prefetchCount, false)); } /** @@ -494,7 +503,7 @@ DeferredConsumer& ChannelImpl::consume(const std::string &queue, const std::stri auto *deferred = new DeferredConsumer(this, send(frame)); // push to list - push(deferred, "Cannot send basic consume frame"); + push(deferred); // done return *deferred; @@ -527,7 +536,7 @@ DeferredCancel &ChannelImpl::cancel(const std::string &tag, int flags) auto *deferred = new DeferredCancel(this, send(frame)); // push to list - push(deferred, "Cannot send basic cancel frame"); + push(deferred); // done return *deferred; @@ -567,7 +576,7 @@ bool ChannelImpl::reject(uint64_t deliveryTag, int flags) Deferred &ChannelImpl::recover(int flags) { // send a nack frame - return send(BasicRecoverFrame(_id, flags & requeue), "Cannot send basic recover frame"); + return push(BasicRecoverFrame(_id, flags & requeue)); } /** @@ -584,24 +593,6 @@ bool ChannelImpl::send(const Frame &frame) return _connection->send(frame); } -/** - * Send a frame over the channel and get a deferred handler for it. - * - * @param frame frame to send - * @param message the message to trigger if the frame cannot be send at all - */ -Deferred &ChannelImpl::send(const Frame &frame, const char *message) -{ - // send the frame, and create deferred object - auto *deferred = new Deferred(send(frame)); - - // push to list - push(deferred, message); - - // done - return *deferred; -} - /** * Report the received message */ diff --git a/src/deferredconsumer.cpp b/src/deferredconsumer.cpp index eb8c6e1..528cbcb 100644 --- a/src/deferredconsumer.cpp +++ b/src/deferredconsumer.cpp @@ -22,8 +22,6 @@ Deferred *DeferredConsumer::reportSuccess(const std::string &name) const // we now know the name, so we can install the message callback on the channel _channel->install(name, _messageCallback); - // @todo when a consumer stops, we should uninstall the message callback - // skip if no special callback was installed if (!_consumeCallback) return Deferred::reportSuccess(); From 60b59524e7a8b44dab94d69b239d67362f3dbb3d Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 13:14:16 +0200 Subject: [PATCH 27/41] when an error is detected on a channel, all subsequent and cached deferred objects are notified about the error too --- include/channelimpl.h | 9 +++++++++ include/connectionhandler.h | 3 +++ src/channelimpl.cpp | 26 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/include/channelimpl.h b/include/channelimpl.h index 62cc70d..3d77fc2 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -485,6 +485,12 @@ public: if (!next) _newestCallback = nullptr; } + /** + * Report errors to all deferred objects already in an error state + * @param force Report errors even for objects not already in error state + */ + void reportErrors(bool force = false); + /** * Report an error message on a channel * @param message @@ -514,6 +520,9 @@ public: // if there was no next callback, the newest callback was just used if (!next) _newestCallback = nullptr; + + // when one error occured, all subsequent messages are in an error state too + reportErrors(true); } /** diff --git a/include/connectionhandler.h b/include/connectionhandler.h index 220e2dc..0fa28ee 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -31,6 +31,9 @@ public: * @param connection the connection that needs the timeout * @param timeout number of seconds to wait * @param callback function to execute once time runs out + * + * + * @todo this one should be removed */ virtual void setTimeout(Connection *connection, double seconds, const std::function& callback) {} diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index a3cc37e..27c1bea 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -121,6 +121,32 @@ Deferred &ChannelImpl::push(const Frame &frame) return push(new Deferred(send(frame))); } +/** + * Report errors to all deferred objects already in an error state + * @param force Report errors even for objects not already in error state + */ +void ChannelImpl::reportErrors(bool force) +{ + // keep looping for as long as the oldest callback is in an error state + while (_oldestCallback && (force || !*_oldestCallback)) + { + // construct monitor, because channel could be destructed + Monitor monitor(this); + + // report the error + auto *next = _oldestCallback->reportError("Frame could not be sent"); + + // leap out if object is no longer valid after the callback was called + if (!monitor.valid()) return; + + // install the next deferred object + _oldestCallback.reset(next); + + // was this also the newest callback + if (!next) _newestCallback = nullptr; + } +} + /** * Pause deliveries on a channel * From b13398b09dcd64b32cdde09ed61a1ecc7a10e5a9 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 13:18:32 +0200 Subject: [PATCH 28/41] setTimeout function removed from connection handler, the finalize and error callbacks are called right away if installed on an object that already is in an error state --- include/connectionhandler.h | 16 ---------------- include/deferred.h | 6 ++++++ src/channelimpl.cpp | 2 +- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/include/connectionhandler.h b/include/connectionhandler.h index 0fa28ee..59c551e 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -21,22 +21,6 @@ namespace AMQP { class ConnectionHandler { public: - /** - * Set a function to be executed after a given timeout. - * - * This function is not strictly necessary to implement. If you - * do not implement it, certain methods that fail immediately - * will not be reported. - * - * @param connection the connection that needs the timeout - * @param timeout number of seconds to wait - * @param callback function to execute once time runs out - * - * - * @todo this one should be removed - */ - virtual void setTimeout(Connection *connection, double seconds, const std::function& callback) {} - /** * Method that is called when data needs to be sent over the network * diff --git a/include/deferred.h b/include/deferred.h index 897c36a..d8fd257 100644 --- a/include/deferred.h +++ b/include/deferred.h @@ -205,6 +205,9 @@ public: { // store callback _errorCallback = callback; + + // if the object is already in a failed state, we call the callback right away + if (_failed) callback("Frame could not be sent"); // allow chaining return *this; @@ -230,6 +233,9 @@ public: // store callback _finalizeCallback = callback; + // if the object is already in a failed state, we call the callback right away + if (_failed) callback(); + // allow chaining return *this; } diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 27c1bea..31cbec1 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -134,7 +134,7 @@ void ChannelImpl::reportErrors(bool force) Monitor monitor(this); // report the error - auto *next = _oldestCallback->reportError("Frame could not be sent"); + auto *next = _oldestCallback->reportError("Frame could not be delivered"); // leap out if object is no longer valid after the callback was called if (!monitor.valid()) return; From 3d4a1b865e7799d7c0ba0c43649a10cb20831fa8 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 13:22:06 +0200 Subject: [PATCH 29/41] removed some @todo tags --- include/channelimpl.h | 10 ++-------- src/channelimpl.cpp | 2 -- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/include/channelimpl.h b/include/channelimpl.h index 3d77fc2..21ee2af 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -431,8 +431,6 @@ public: /** * Report to the handler that the channel is opened - * - * @todo when is this sent? */ void reportReady() { @@ -442,18 +440,14 @@ public: /** * Report to the handler that the channel is closed - * - * @todo do we need this? */ void reportClosed() { // change state _state = state_closed; - // inform handler - - // @todo do we report success here? - + // and pass on to the reportSuccess() method which will call the + // appropriate deferred object to report the successful operation reportSuccess(); } diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 31cbec1..f699692 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -105,8 +105,6 @@ Deferred &ChannelImpl::push(Deferred *deferred) // store newest callback _newestCallback = deferred; - // @todo in case of error we have to report the error with a timeout - // done return *deferred; } From 82249ee368adad8a2093aead5067cfb09d31d129 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 14:22:30 +0200 Subject: [PATCH 30/41] update documentation, error callbacks now get a const char * instead of a std::string --- README.md | 385 +++++++++++++++++++++++++----------- include/connectionhandler.h | 2 +- include/connectionimpl.h | 2 +- src/connectioncloseframe.h | 2 +- 4 files changed, 271 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index ce5468d..81de738 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,24 @@ class MyConnectionHandler : public AMQP::ConnectionHandler * @param connection The connection on which the error occured * @param message A human readable error message */ - virtual void onError(AMQP::Connection *connection, const std::string &message) + virtual void onError(AMQP::Connection *connection, const char *message) { // @todo // add your own implementation, for example by reporting the error // to the user of your program, log the error, and destruct the // connection object because it is no longer in a usable state } + + /** + * Method that is called when the connection was closed. This is the + * counter part of a call to Connection::close() and it confirms that the + * connection was correctly closed. + * + * @param connection The connection that was closed and that is now unusable + */ + virtual void onClosed(AMQP::Connection *connection) {} + + }; ```` After you've implemented the ConnectionHandler class, you can start using @@ -132,13 +143,13 @@ every time that it wants to send out data. We've explained that it is up to you implement that method. But what about data in the other direction? How does the library receive data back from RabbitMQ? -As we've explained above, the AMQP-CPP library does not do any IO by itself -and it is therefore of course also not possible for the library to receive data from -a socket. It is again up to you to do this. If, for example, you notice in your -event loop that the socket that is connected with the RabbitMQ server becomes -readable, you should read out that data (for example by using the recv() system -call), and pass the received bytes to the AMQP-CPP library. This is done by -calling the parse() method in the Connection object. +The AMQP-CPP library does not do any IO by itself and it is therefore of course +also not possible for the library to receive data from a socket. It is again up +to you to do this. If, for example, you notice in your event loop that the socket +that is connected with the RabbitMQ server becomes readable, you should read out +that socket (for example by using the recv() system call), and pass the received +bytes to the AMQP-CPP library. This is done by calling the parse() method in the +Connection object. The Connection::parse() method gets two parameters, a pointer to a buffer of data received from RabbitMQ, and a parameter that holds the size of this buffer. @@ -149,13 +160,13 @@ The code snippet below comes from the Connection.h C++ header file. * Parse data that was recevied from RabbitMQ * * Every time that data comes in from RabbitMQ, you should call this method to parse - * the incoming data, and let it handle by the AMQP-CPP library. This method returns the number - * of bytes that were processed. + * the incoming data, and let it handle by the AMQP-CPP library. This method returns + * the number of bytes that were processed. * - * If not all bytes could be processed because it only contained a partial frame, you should - * call this same method later on when more data is available. The AMQP-CPP library does not do - * any buffering, so it is up to the caller to ensure that the old data is also passed in that - * later call. + * If not all bytes could be processed because it only contained a partial frame, + * you should call this same method later on when more data is available. The + * AMQP-CPP library does not do any buffering, so it is up to the caller to ensure + * that the old data is also passed in that later call. * * @param buffer buffer to decode * @param size size of the buffer to decode @@ -167,12 +178,20 @@ size_t parse(char *buffer, size_t size) } ```` +You should do all the book keeping for the buffer yourselves. If you for example +call the Connection::parse() method with a buffer of 100 bytes, and the method +returns that only 60 bytes were processed, you should later call the method again, +with a buffer filled with the remaining 40 bytes. If the method returns 0, you should +make a new call to parse() when more data is available, with a buffer with more +data. + + CHANNELS ======== -In the example you saw that we created a channel object. A channel is a virtual -connection over a single TCP connection, and it is possible to create many channels -that all use the same TCP connection. +In the example we created a channel object. A channel is a virtual connection over +a single TCP connection, and it is possible to create many channels that all use +the same TCP connection. AMQP instructions are always sent over a channel, so before you can send the first command to the RabbitMQ server, you first need a channel object. The channel @@ -183,30 +202,100 @@ C++ header file for a list of all available methods. Every method in it is well documented. The constructor of the Channel object accepts one parameter: the connection object. -Unlike the connection it does not accept a handler. Instead of this (almost) every -function in the channel returns a Deferred object. This deferred object can be used -to install handlers to be called in case of success, failure or in either case. +Unlike the connection it does not accept a handler. Instead of a handler object, +(almost) every method of the Channel class returns an instance of the 'Deferred' +class. This object can be used to install handlers that will be called in case +of success or failure. -For example, if you call the channel.declareQueue() method, the AMQP-CPP library will -send a message to the RabbitMQ message broker to ask it to declare the +For example, if you call the channel.declareExchange() method, the AMQP-CPP library +will send a message to the RabbitMQ message broker to ask it to declare the queue. However, because all operations in the library are asynchronous, the -declareQueue() method immediately returns 'true', although it is at that time -not yet known whether the queue was correctly declared. Only after a while, -after the instruction has reached the server, and the confirmation from the server -has been sent back to the client, your handler method will be called to inform -you that the operation was succesful. +declareExchange() method can not return 'true' or 'false' to inform you whether +the operation was succesful or not. Only after a while, after the instruction +has reached the RabbitMQ server, and the confirmation from the server has been +sent back to the client, the library can report the result of the declareExchange() +call. + +To prevent any blocking calls, the channel.declareExchange() method returns a +'Deferred' result object, on which you can set callback functions that will be +called when the operation succeeds or fails. ````c++ +// create a channel Channel myChannel(&connection); -myChannel.declareQueue("my-queue") -.onSuccess([](AMQP::Channel *channel, const std::string& name, uint32_t messageCount, uint32_t consumerCount) { - // by now the queue is created -}) -.onError([](AMQP::Channel *channel, const std::string message) { - // something went wrong creating the channel + +// declare an exchange, and install callbacks for success and failure +myChannel.declareExchange("my-exchange") + + .onSuccess([]() { + // by now the exchange is created + }) + + .onError([](const char *message) { + // something went wrong creating the exchange + }); +```` + +As you can see in the above example, we call the declareExchange() method, and +treat its return value as an object, on which we immediately install a lambda +callback function to handle success, and to handle failure. + +Installing the callback methods is optional. If you're not interested in the +result of an operation, you do not have to install a callback for it. Next +to the onSuccess() and onError() callbacks that can be installed, you can also +install a onFinalize() method that gets called directly after the onSuccess() +and onError() methods, and that can be used to set a callback that should +run in either case: when the operation succeeds or when it fails. + +The signature for the onError() method is always the same: it gets one parameter +with a human readable error message. The onSuccess() function has a different +signature depending on the method that you call. Most onSuccess() functions +(like the one we showed for the declareExchange() method) do not get any +parameters at all. Some specific onSuccess callbacks receive extra parameters +with additional information. + + +CHANNEL CALLBACKS +================= + +As explained, most channel methods return a 'Deferred' object on which you can +install callbacks using the Deferred::onError() and Deferred::onSuccess() methods. + +The callbacks that you install on a Deferred object, only apply to one specific +operation. If you want to install a generic error callback for the entire channel, +you can so so by using the Channel::onError() method. Next to the Channel::onError() +method, you can also install a callback to be notified when the channel is ready +for sending the first instruction to RabbitMQ. + +````c++ +// create a channel +Channel myChannel(connection, &myHandler); + +// install a generic channel-error handler that will be called for every +// error that occurs on the channel +myChannel.onError([](const char *message) { + + // report error + std::cout << "channel error: " << message << std::endl; +}); + +// install a generic callback that will be called when the channel is ready +// for sending the first instruction +myChannel.onReady([]() { + + // send the first instructions (like publishing messages) }); ```` +In theory, you should always use the onReady() function before you send any +other instructions over the channel. In practive however, the AMQP library +caches all instructions that were sent too early, so that you can use the +channel object right after it was constructed. + + +CHANNEL ERRORS +============== + It is important to realize that any error that occurs on a channel, will invalidate the entire channel,. including all subsequent instructions that were already sent over it. This means that if you call multiple methods in a row, @@ -218,10 +307,11 @@ myChannel.declareQueue("my-queue"); myChannel.declareExchange("my-exchange"); ```` -If the first declareQueue() call fails in the example above, your Deferred::onError() -method will be called after a while to report this failure. And although the -second instruction to declare an exchange has already been sent to the server, it will be -ignored because the channel was already in an invalid state after the first failure. +If the first declareQueue() call fails in the example above, the second +myChannel.declareExchange() method will not be executed, even when this +second instruction was already sent to the server. The second instruction will be +ignored by the RabbitMQ server because the channel was already in an invalid +state after the first failure. You can overcome this by using multiple channels: @@ -232,12 +322,12 @@ channel1.declareQueue("my-queue"); channel2.declareExchange("my-exchange"); ```` -Now, if an error occurs with declaring the queue, it will not have -consequences for the other call. But this comes at a small price: -setting up the extra channel requires and extra instruction to be sent to the -RabbitMQ server, so some extra bytes are sent over the network, -and some additional resources in both the client application and the -RabbitMQ server are used (although this is all very limited). +Now, if an error occurs with declaring the queue, it will not have consequences +for the other call. But this comes at a small price: setting up the extra channel +requires and extra instruction to be sent to the RabbitMQ server, so some extra +bytes are sent over the network, and some additional resources in both the client +application and the RabbitMQ server are used (although this is all very limited). + FLAGS AND TABLES ================ @@ -265,23 +355,40 @@ tables are used by many methods. * @param flags combination of flags * @param arguments optional arguments */ -Deferred& declareQueue(const std::string &name, int flags, const Table &arguments); -Deferred& declareQueue(const std::string &name, const Table &arguments); -Deferred& declareQueue(const std::string &name, int flags = 0); -Deferred& declareQueue(int flags, const Table &arguments); -Deferred& declareQueue(const Table &arguments); -Deferred& declareQueue(int flags = 0); +DeferredQueue &declareQueue(const std::string &name, int flags, const Table &arguments); +DeferredQueue &declareQueue(const std::string &name, const Table &arguments); +DeferredQueue &declareQueue(const std::string &name, int flags = 0); +DeferredQueue &declareQueue(int flags, const Table &arguments); +DeferredQueue &declareQueue(const Table &arguments); +DeferredQueue &declareQueue(int flags = 0); ```` As you can see, the method comes in many forms, and it is up to you to choose the one that is most appropriate. We now take a look at the most complete one, the method with three parameters. -Many methods in the Channel class accept an integer parameter named 'flags'. -This is a variable in which you can set a number of options, by summing up -all the options that are described in the documentation. If you for example -want to create a durable, auto-deleted queue, you can pass in the value -AMQP::durable + AMQP::autodelete. +All above methods returns a 'DeferredQueue' object. The DeferredQueue class +extends from the AMQP::Deferred class and allows you to install a more powerful +onSuccess() callback function. The 'onSuccess' method for the declareQueue() +function gets three arguments: + +````c++ +// create a custom callback +auto callback = [](const std::string &name, int msgcount, int consumercount) { + + // @todo add your own implementation + +} + +// declare the queue, and install the callback that is called on success +channel.declareQueue("myQueue").onSuccess(callback); +```` + +Just like many others methods in the Channel class, the declareQueue() method +accept an integer parameter named 'flags'. This is a variable in which you can +set method-specific options, by summing up all the options that are described in +the documentation above the method. If you for example want to create a durable, +auto-deleted queue, you can pass in the value AMQP::durable + AMQP::autodelete. The declareQueue() method also accepts a parameter named 'arguments', which is of type Table. This Table object can be used as an associative array to send additional @@ -314,8 +421,8 @@ exchange to publish to, the routing key to use, and the actual message that you're publishing - all these parameters are standard C++ strings. More extended versions of the publish() method exist that accept additional -arguments, and that enable you to publish entire Envelope objects, which are -objects that contain the message plus a list of optional meta information like +arguments, and that enable you to publish entire Envelope objects. An envelope +is an object that contains the message plus a list of optional meta information like the content-type, content-encoding, priority, expire time and more. None of these meta fields are interpreted by this library, and also the RabbitMQ ignores most of them, but the AMQP protocol defines them, and they are free for you to use. @@ -334,8 +441,11 @@ in almost any form: * * The following flags can be used * - * - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method - * - immediate if set, a message that could not immediately be consumed is returned to the onReturned method + * - mandatory if set, an unroutable message will be reported to the + * channel handler with the onReturned method + * + * - immediate if set, a message that could not immediately be consumed + * is returned to the onReturned method * * If either of the two flags is set, and the message could not immediately * be published, the message is returned by the server to the client. If you @@ -357,28 +467,39 @@ bool publish(const std::string &exchange, const std::string &routingKey, int fla bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size); ```` -Published messages are normally not confirmed by the server, therefore the publish method does not -return a deferred. That's by design in the AMQP protocol, to not unnecessarily slow down message -publishing. As long as no error is reported via the ChannelHandler::onError() method, you can safely +Published messages are normally not confirmed by the server, and the RabbitMQ +will not send a report back to inform us whether the message was succesfully +published or not. Therefore the publish method does also not return a Deferred +object. + +As long as no error is reported via the Channel::onError() method, you can safely assume that your messages were delivered. -This can of course be a problem when you are publishing many messages. If you get an error halfway through -there is no way to know for sure how many messages made it to the broker and how many should be republished. -If this is important, you can wrap the publish commands inside a transaction. In this case, if an error occurs, -the transaction is automatically rolled back by RabbitMQ and none of the messages are actually published. +This can of course be a problem when you are publishing many messages. If you get +an error halfway through there is no way to know for sure how many messages made +it to the broker and how many should be republished. If this is important, you can +wrap the publish commands inside a transaction. In this case, if an error occurs, +the transaction is automatically rolled back by RabbitMQ and none of the messages +are actually published. ````c++ +// start a transaction channel.startTransaction(); + +// publish a number of messages channel.publish("my-exchange", "my-key", "my first message"); channel.publish("my-exchange", "my-key", "another message"); + +// commit the transactions, and set up callbacks that are called when +// the transaction was successful or not channel.commitTransaction() -.onSuccess([](AMQP::Channel *channel) { - // all messages were successfully published -}) -.onError([](AMQP::Channel *channel) { - // none of the messages were published - // now we have to do it all over again -}); + .onSuccess([]() { + // all messages were successfully published + }) + .onError([]() { + // none of the messages were published + // now we have to do it all over again + }); ```` @@ -393,9 +514,8 @@ Just like the publish() method that we just described, the consume() method also comes in many forms. The first parameter is always the name of the queue you like to consume from. The subsequent parameters are an optional consumer tag, flags and a table with custom arguments. The first additional parameter, the consumer tag, -is nothing more than a string identifier that will be passed with every consumed message. -This can be useful if you call the consume() methods a number of times to consume -from multiple queues, and you would like to know from which consume call the received messages came. +is nothing more than a string identifier that you can use when you want to stop +consuming. The full documentation from the C++ Channel.h headerfile looks like this: @@ -411,10 +531,17 @@ The full documentation from the C++ Channel.h headerfile looks like this: * * The following flags are supported: * - * - nolocal if set, messages published on this channel are not also consumed - * - noack if set, consumed messages do not have to be acked, this happens automatically - * - exclusive request exclusive access, only this consumer can access the queue - * - nowait the server does not have to send a response back that consuming is active + * - nolocal if set, messages published on this channel are + * not also consumed + * + * - noack if set, consumed messages do not have to be acked, + * this happens automatically + * + * - exclusive request exclusive access, only this consumer can + * access the queue + * + * - nowait the server does not have to send a response back + * that consuming is active * * The method ChannelHandler::onConsumerStarted() will be called when the * consumer has started (unless the nowait option was set, in which case @@ -426,58 +553,82 @@ The full documentation from the C++ Channel.h headerfile looks like this: * @param arguments additional arguments * @return bool */ -DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags, const AMQP::Table &arguments); -DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags = 0); -DeferredConsumer& consume(const std::string &queue, const std::string &tag, const AMQP::Table &arguments); -DeferredConsumer& consume(const std::string &queue, int flags, const AMQP::Table &arguments); -DeferredConsumer& consume(const std::string &queue, int flags = 0); -DeferredConsumer& consume(const std::string &queue, const AMQP::Table &arguments); +DeferredConsumer &consume(const std::string &queue, const std::string &tag, int flags, const AMQP::Table &arguments); +DeferredConsumer &consume(const std::string &queue, const std::string &tag, int flags = 0); +DeferredConsumer &consume(const std::string &queue, const std::string &tag, const AMQP::Table &arguments); +DeferredConsumer &consume(const std::string &queue, int flags, const AMQP::Table &arguments); +DeferredConsumer &consume(const std::string &queue, int flags = 0); +DeferredConsumer &consume(const std::string &queue, const AMQP::Table &arguments); ```` -As you can see, the consume method returns a DeferredConsumer. This object is a regular Deferred, with the -addition of the onReceived method. This method can be used to retrieve incoming messages after consumption -has begun. +As you can see, the consume method returns a DeferredConsumer. This object is a +regular Deferred, with the some additions. The onSuccess() method of a +DeferredConsumer is slightly different than the onSuccess() method of a regular +Deferred object: one extra parameter will be supplied to your callback function +with the consumer tag. +The onSuccess() callback will be called when the consume operation _has started_, +but not when messages are actually consumed. For this you will have to install +a different callback, using the onReceived() method. ````c++ -channel.consume("my-queue").onReceived([](AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) { - // @todo - // add your own processing +// callback function that is called when the consume operation starts +auto startCb = [](const std::string &consumertag) { + std::cout << "consume operation started" << std::endl; +}; + +// callback function that is called when the consume operation failed +auto errorCb = [](const char *message) { + + std::cout << "consume operation failed" << std::endl; +} + +// callback operation when a message was received +auto messageCb = [&channel](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) { + + std::cout << "message received" << std::endl; + + // acknowledge the message + channel.ack(deliveryTag); +} + +// start consuming from the queue, and install the callbacks +channel.consume("my-queue") + .onSuccess(startCb) + .onError(errorCb) + .onReceived(messageCb); - // after the message was processed, acknowledge it - channel->ack(deliveryTag); -}); ```` -The Message object holds all information of the delivered message: the actual content, -all meta information from the envelope (in fact, the Message class is derived from the Envelope class), -and even the name of the exchange and the routing key that were used when the message was originally -published. For a full list of all information in the Message class, you best have a look at the +The Message object holds all information of the delivered message: the actual +content, all meta information from the envelope (in fact, the Message class is +derived from the Envelope class), and even the name of the exchange and the +routing key that were used when the message was originally published. For a full +list of all information in the Message class, you best have a look at the message.h, envelope.h and metadata.h header files. -Another important parameter to the onReceived() method is the deliveryTag parameter. This is a -unique identifier that you need to acknowledge an incoming message. RabbitMQ only removes the -message after it has been acknowledged, so that if your application crashes while it was busy -processing the message, the message does not get lost but remains in the queue. But this means that -after you've processed the message, you must inform RabbitMQ about it by calling the Channel:ack() -method. This method is very simple and takes in its simplest form only one parameter: the +Another important parameter to the onReceived() method is the deliveryTag parameter. +This is a unique identifier that you need to acknowledge an incoming message. +RabbitMQ only removes the message after it has been acknowledged, so that if your +application crashes while it was busy processing the message, the message does +not get lost but remains in the queue. But this means that after you've processed +the message, you must inform RabbitMQ about it by calling the Channel:ack() method. +This method is very simple and takes in its simplest form only one parameter: the deliveryTag of the message. -The consumerTag that you see in the onReceived method() is the same string identifier that was -passed to the Channel::consume() method. +Consuming messages is a continuous process. RabbitMQ keeps sending messages, until +you stop the consumer, which can be done by calling the Channel::cancel() method. +If you close the channel, or the entire TCP connection, consuming also stops. -Consuming messages is a continuous process. RabbitMQ keeps sending messages, until you stop -the consumer, which can be done by calling the Channel::cancel() method. If you close the channel, -or the entire TCP connection, consuming also stops. - -RabbitMQ throttles the number of messages that are delivered to you, to prevent that your application -is flooded with messages from the queue, and to spread out the messages over multiple consumers. -This is done with a setting called quality-of-service (QOS). The QOS setting is a numeric value which -holds the number of unacknowledged messages that you are allowed to have. RabbitMQ stops sending -additional messages when the number of unacknowledges messages has reached this limit, and only -sends additional messages when an earlier message gets acknowledged. To change the QOS, you can -simple call Channel::setQos(). +RabbitMQ throttles the number of messages that are delivered to you, to prevent +that your application is flooded with messages from the queue, and to spread out +the messages over multiple consumers. This is done with a setting called +quality-of-service (QOS). The QOS setting is a numeric value which holds the number +of unacknowledged messages that you are allowed to have. RabbitMQ stops sending +additional messages when the number of unacknowledges messages has reached this +limit, and only sends additional messages when an earlier message gets acknowledged. +To change the QOS, you can simple call Channel::setQos(). WORK IN PROGRESS diff --git a/include/connectionhandler.h b/include/connectionhandler.h index 59c551e..17b98b2 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -48,7 +48,7 @@ public: * @param connection The connection that entered the error state * @param message Error message */ - virtual void onError(Connection *connection, const std::string &message) {} + virtual void onError(Connection *connection, const char *message) {} /** * Method that is called when the login attempt succeeded. After this method diff --git a/include/connectionimpl.h b/include/connectionimpl.h index bcbeed0..f3fceb2 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -273,7 +273,7 @@ public: * Report an error message * @param message */ - void reportError(const std::string &message) + void reportError(const char *message) { // set connection state to closed _state = state_closed; diff --git a/src/connectioncloseframe.h b/src/connectioncloseframe.h index 6bff24a..1c5059f 100644 --- a/src/connectioncloseframe.h +++ b/src/connectioncloseframe.h @@ -157,7 +157,7 @@ public: // no need to check for a channel, the error is connection wide // report the error on the connection - connection->reportError(text()); + connection->reportError(text().c_str()); // done return true; From 44a9dff4136a424112e8baa55eb680145f19845e Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 14:27:16 +0200 Subject: [PATCH 31/41] removed channel handlers in readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 81de738..44c08d9 100644 --- a/README.md +++ b/README.md @@ -182,8 +182,8 @@ You should do all the book keeping for the buffer yourselves. If you for example call the Connection::parse() method with a buffer of 100 bytes, and the method returns that only 60 bytes were processed, you should later call the method again, with a buffer filled with the remaining 40 bytes. If the method returns 0, you should -make a new call to parse() when more data is available, with a buffer with more -data. +make a new call to parse() when more data is available, with a buffer that contains +both the old data, and the new data. CHANNELS @@ -269,7 +269,7 @@ for sending the first instruction to RabbitMQ. ````c++ // create a channel -Channel myChannel(connection, &myHandler); +Channel myChannel(&connection); // install a generic channel-error handler that will be called for every // error that occurs on the channel @@ -302,7 +302,7 @@ were already sent over it. This means that if you call multiple methods in a row and the first method fails, all subsequent methods will not be executed either: ````c++ -Channel myChannel(connection, &myHandler); +Channel myChannel(&connection); myChannel.declareQueue("my-queue"); myChannel.declareExchange("my-exchange"); ```` @@ -316,8 +316,8 @@ state after the first failure. You can overcome this by using multiple channels: ````c++ -Channel channel1(connection, &myHandler); -Channel channel2(connection, &myHandler); +Channel channel1(&connection); +Channel channel2(&connection); channel1.declareQueue("my-queue"); channel2.declareExchange("my-exchange"); ```` From b043c33cc6f170a0a9d4afaf4e5e048cb1236ce9 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 14:33:40 +0200 Subject: [PATCH 32/41] update documentation --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 44c08d9..ddc6605 100644 --- a/README.md +++ b/README.md @@ -287,8 +287,8 @@ myChannel.onReady([]() { }); ```` -In theory, you should always use the onReady() function before you send any -other instructions over the channel. In practive however, the AMQP library +In theory, you should wait for the onReady() callback to be called before you +send any other instructions over the channel. In practice however, the AMQP library caches all instructions that were sent too early, so that you can use the channel object right after it was constructed. @@ -562,7 +562,7 @@ DeferredConsumer &consume(const std::string &queue, const AMQP::Table &arguments ```` As you can see, the consume method returns a DeferredConsumer. This object is a -regular Deferred, with the some additions. The onSuccess() method of a +regular Deferred, with additions. The onSuccess() method of a DeferredConsumer is slightly different than the onSuccess() method of a regular Deferred object: one extra parameter will be supplied to your callback function with the consumer tag. From 59e0b61e6bf5c8a172a8e0906cd12ac573622c08 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 15 Apr 2014 14:49:03 +0200 Subject: [PATCH 33/41] installed test program, added onMessage() function in deferredconsumer --- include/channel.h | 12 +- include/deferredconsumer.h | 17 ++- tests/Makefile | 18 +++ tests/README.md | 2 + tests/a.out | Bin 0 -> 1016087 bytes tests/main.cpp | 54 +++++++++ tests/myconnection.cpp | 234 +++++++++++++++++++++++++++++++++++++ tests/myconnection.h | 118 +++++++++++++++++++ tests/table/table.cpp | 19 +++ 9 files changed, 467 insertions(+), 7 deletions(-) create mode 100644 tests/Makefile create mode 100644 tests/README.md create mode 100755 tests/a.out create mode 100644 tests/main.cpp create mode 100644 tests/myconnection.cpp create mode 100644 tests/myconnection.h create mode 100644 tests/table/table.cpp diff --git a/include/channel.h b/include/channel.h index 0f315c4..6f24a86 100644 --- a/include/channel.h +++ b/include/channel.h @@ -404,12 +404,12 @@ public: * * }); */ - DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { return _implementation.consume(queue, tag, flags, arguments); } - DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags = 0) { return _implementation.consume(queue, tag, flags, Table()); } - DeferredConsumer& consume(const std::string &queue, const std::string &tag, const Table &arguments) { return _implementation.consume(queue, tag, 0, arguments); } - DeferredConsumer& consume(const std::string &queue, int flags, const Table &arguments) { return _implementation.consume(queue, std::string(), flags, arguments); } - DeferredConsumer& consume(const std::string &queue, int flags = 0) { return _implementation.consume(queue, std::string(), flags, Table()); } - DeferredConsumer& consume(const std::string &queue, const Table &arguments) { return _implementation.consume(queue, std::string(), 0, arguments); } + DeferredConsumer &consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { return _implementation.consume(queue, tag, flags, arguments); } + DeferredConsumer &consume(const std::string &queue, const std::string &tag, int flags = 0) { return _implementation.consume(queue, tag, flags, Table()); } + DeferredConsumer &consume(const std::string &queue, const std::string &tag, const Table &arguments) { return _implementation.consume(queue, tag, 0, arguments); } + DeferredConsumer &consume(const std::string &queue, int flags, const Table &arguments) { return _implementation.consume(queue, std::string(), flags, arguments); } + DeferredConsumer &consume(const std::string &queue, int flags = 0) { return _implementation.consume(queue, std::string(), flags, Table()); } + DeferredConsumer &consume(const std::string &queue, const Table &arguments) { return _implementation.consume(queue, std::string(), 0, arguments); } /** * Cancel a running consume call diff --git a/include/deferredconsumer.h b/include/deferredconsumer.h index 60104d1..ee82068 100644 --- a/include/deferredconsumer.h +++ b/include/deferredconsumer.h @@ -77,9 +77,24 @@ public: /** * Register a function to be called when a message arrives + * This fuction is also available as onMessage() because I always forget which name I gave to it * @param callback the callback to execute */ - DeferredConsumer& onReceived(const MessageCallback &callback) + DeferredConsumer &onReceived(const MessageCallback &callback) + { + // store callback + _messageCallback = callback; + + // allow chaining + return *this; + } + + /** + * Register a function to be called when a message arrives + * This fuction is also available as onMessage() because I always forget which name I gave to it + * @param callback the callback to execute + */ + DeferredConsumer &onMessage(const MessageCallback &callback) { // store callback _messageCallback = callback; diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..b5bf2f3 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,18 @@ +CPP = g++ +CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++11 -g +LD = g++ +LDFLAGS = -lamqpcpp -lcopernica_event -lcopernica_network -lev +RESULT = a.out +SOURCES = $(wildcard *.cpp) +OBJECTS = $(SOURCES:%.cpp=%.o) + +all: ${OBJECTS} ${RESULT} + +${RESULT}: ${OBJECTS} + ${LD} -o $@ ${OBJECTS} ${LDFLAGS} + +clean: + ${RM} *.obj *~* ${OBJECTS} ${RESULT} + +${OBJECTS}: + ${CPP} ${CPPFLAGS} -o $@ ${@:%.o=%.cpp} diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..07af04a --- /dev/null +++ b/tests/README.md @@ -0,0 +1,2 @@ +I'm sorry, the test case makes use of the closed source Copernica libraries. Maybe someone +is willing to provide a test case based on plain system calls? \ No newline at end of file diff --git a/tests/a.out b/tests/a.out new file mode 100755 index 0000000000000000000000000000000000000000..fe76148079ddc296418d5f7454c4959cdb7cc7c3 GIT binary patch literal 1016087 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 z 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�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 +#include + +/** + * Namespaces to use + */ +using namespace std; +using namespace Copernica; + +/** + * Local libraries + */ +#include "myconnection.h" + +/** + * Main procedure + * @param argc + * @param argv + * @return int + */ +int main(int argc, const char *argv[]) +{ + // need an ip + if (argc != 2) + { + // report error + std::cerr << "usage: " << argv[0] << " " << std::endl; + + // done + return -1; + } + else + { + // create connection + MyConnection connection(argv[1]); + + // start the main event loop + Event::MainLoop::instance()->run(); + + // done + return 0; + } +} \ No newline at end of file diff --git a/tests/myconnection.cpp b/tests/myconnection.cpp new file mode 100644 index 0000000..dd6e265 --- /dev/null +++ b/tests/myconnection.cpp @@ -0,0 +1,234 @@ +/** + * MyConnection.cpp + * + * @copyright 2014 Copernica BV + */ + +/** + * Required external libraries + */ +#include +#include + +#include + +/** + * Namespaces to use + */ +using namespace std; +using namespace Copernica; + +/** + * Required local class definitions + */ +#include "myconnection.h" + +/** + * Constructor + */ +MyConnection::MyConnection(const std::string &ip) : + _socket(Event::MainLoop::instance(), this), + _connection(nullptr), + _channel(nullptr) +{ + // start connecting + if (_socket.connect(Network::Ipv4Address(ip), 5672)) return; + + // failure + onFailure(&_socket); +} + +/** + * Destructor + */ +MyConnection::~MyConnection() +{ + // do we still have a channel? + if (_channel) delete _channel; + + // do we still have a connection? + if (_connection) delete _connection; +} + +/** + * Method that is called when the connection failed + * @param socket Pointer to the socket + */ +void MyConnection::onFailure(Network::TcpSocket *socket) +{ + // report error + std::cout << "connect failure" << std::endl; +} + +/** + * Method that is called when the connection timed out (which also is a failure + * @param socket Pointer to the socket + */ +void MyConnection::onTimeout(Network::TcpSocket *socket) +{ + // report error + std::cout << "connect timeout" << std::endl; +} + +/** + * Method that is called when the connection succeeded + * @param socket Pointer to the socket + */ +void MyConnection::onConnected(Network::TcpSocket *socket) +{ + // report connection + std::cout << "connected" << std::endl; + + // we are connected, leap out if there already is a amqp connection + if (_connection) return; + + // create amqp connection, and a new channel + _connection = new AMQP::Connection(this, AMQP::Login("guest", "guest"), "/"); + _channel = new AMQP::Channel(_connection); + + // install a handler when channel is in error + _channel->onError([](const char *message) { + + std::cout << "channel error " << message << std::endl; + }); + + // install a handler when channel is ready + _channel->onReady([]() { + + std::cout << "channel ready" << std::endl; + }); + + // we declare a queue, an exchange and we publish a message + _channel->declareQueue("my_queue").onSuccess([this]() { + std::cout << "queue declared" << std::endl; + + // start consuming + _channel->consume("my_queue").onReceived([](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) { + std::cout << "received: " << message.message() << std::endl; + }); + }); + + // declare an exchange + _channel->declareExchange("my_exchange", AMQP::direct).onSuccess([]() { + std::cout << "exchange declared" << std::endl; + }); + + // bind queue and exchange + _channel->bindQueue("my_exchange", "my_queue", "key").onSuccess([this]() { + std::cout << "queue bound to exchange" << std::endl; + + _channel->publish("my_exchange", "key", "just a message"); + }); +} + +/** + * Method that is called when the socket is closed (as a result of a TcpSocket::close() call) + * @param socket Pointer to the socket + */ +void MyConnection::onClosed(Network::TcpSocket *socket) +{ + // show + std::cout << "myconnection closed" << std::endl; + + // close the channel and connection + if (_channel) delete _channel; + if (_connection) delete _connection; + + // set to null + _channel = nullptr; + _connection = nullptr; +} + +/** + * Method that is called when the peer closed the connection + * @param socket Pointer to the socket + */ +void MyConnection::onLost(Network::TcpSocket *socket) +{ + // report error + std::cout << "connection lost" << std::endl; + + // close the channel and connection + if (_channel) delete _channel; + if (_connection) delete _connection; + + // set to null + _channel = nullptr; + _connection = nullptr; +} + +/** + * Method that is called when data is received on the socket + * @param socket Pointer to the socket + * @param buffer Pointer to the fill input buffer + */ +void MyConnection::onData(Network::TcpSocket *socket, Network::Buffer *buffer) +{ + // send what came in + std::cout << "received: " << buffer->size() << " bytes" << std::endl; + + // leap out if there is no connection + if (!_connection) return; + + // let the data be handled by the connection + size_t bytes = _connection->parse(buffer->data(), buffer->size()); + + // shrink the buffer + buffer->shrink(bytes); +} + +/** + * Method that is called when data needs to be sent over the network + * + * Note that the AMQP library does no buffering by itself. This means + * that this method should always send out all data or do the buffering + * itself. + * + * @param connection The connection that created this output + * @param buffer Data to send + * @param size Size of the buffer + */ +void MyConnection::onData(AMQP::Connection *connection, const char *buffer, size_t size) +{ +// // report what is going on +// std::cout << "send: " << size << std::endl; +// +// for (unsigned i=0; i +#include + + +int main() +{ + AMQP::Array x; + + x[0] = "abc"; + x[1] = "xyz"; + + std::cout << x << std::endl; + std::cout << x[0] << std::endl; + std::cout << x[1] << std::endl; + + + return 0; +} + From 1fecc57d67bc5014b02b9c797592863658464237 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 16 Apr 2014 09:25:08 +0200 Subject: [PATCH 34/41] when a connection gets in error state, all deferred results will now also call their error callback, and the channel wide error handler is called _after_ all individual error handlers are called --- include/channelimpl.h | 53 ++++++++++++++++++++++------------------ include/connectionimpl.h | 13 ++++++++++ src/channelimpl.cpp | 26 -------------------- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/include/channelimpl.h b/include/channelimpl.h index 21ee2af..a4c4e39 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -479,12 +479,6 @@ public: if (!next) _newestCallback = nullptr; } - /** - * Report errors to all deferred objects already in an error state - * @param force Report errors even for objects not already in error state - */ - void reportErrors(bool force = false); - /** * Report an error message on a channel * @param message @@ -496,27 +490,38 @@ public: // we are going to call callbacks that could destruct the channel Monitor monitor(this); + + // call the oldest + if (_oldestCallback) + { + // call the callback + auto *next = _oldestCallback->reportError(message); + + // leap out if channel no longer exists + if (!monitor.valid()) return; + + // set the oldest callback + _oldestCallback.reset(next); + } + + // clean up all deferred other objects + while (_oldestCallback) + { + // call the callback + auto *next = _oldestCallback->reportError("Channel is in error state"); + + // leap out if channel no longer exists + if (!monitor.valid()) return; + + // set the oldest callback + _oldestCallback.reset(next); + } + + // all callbacks have been processed, so we also can reset the pointer to the newest + _newestCallback = nullptr; // inform handler if (_errorCallback) _errorCallback(message); - - // leap out if channel is already destructed, or when there are no further callbacks - if (!monitor.valid() || !_oldestCallback) return; - - // call the callback - auto *next = _oldestCallback->reportError(message); - - // leap out if channel no longer exists - if (!monitor.valid()) return; - - // set the oldest callback - _oldestCallback.reset(next); - - // if there was no next callback, the newest callback was just used - if (!next) _newestCallback = nullptr; - - // when one error occured, all subsequent messages are in an error state too - reportErrors(true); } /** diff --git a/include/connectionimpl.h b/include/connectionimpl.h index f3fceb2..bd654b2 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -277,6 +277,19 @@ public: { // set connection state to closed _state = state_closed; + + // monitor because every callback could invalidate the connection + Monitor monitor(this); + + // all deferred result objects in the channels should report this error too + for (auto &iter : _channels) + { + // report the errors + iter.second->reportError(message); + + // leap out if no longer valid + if (!monitor.valid()) return; + } // inform handler _handler->onError(_parent, message); diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index f699692..c612232 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -119,32 +119,6 @@ Deferred &ChannelImpl::push(const Frame &frame) return push(new Deferred(send(frame))); } -/** - * Report errors to all deferred objects already in an error state - * @param force Report errors even for objects not already in error state - */ -void ChannelImpl::reportErrors(bool force) -{ - // keep looping for as long as the oldest callback is in an error state - while (_oldestCallback && (force || !*_oldestCallback)) - { - // construct monitor, because channel could be destructed - Monitor monitor(this); - - // report the error - auto *next = _oldestCallback->reportError("Frame could not be delivered"); - - // leap out if object is no longer valid after the callback was called - if (!monitor.valid()) return; - - // install the next deferred object - _oldestCallback.reset(next); - - // was this also the newest callback - if (!next) _newestCallback = nullptr; - } -} - /** * Pause deliveries on a channel * From e903cdb4ee33f0a61d52d4aa9c5dd79fc3a13b67 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 16 Apr 2014 11:43:27 +0200 Subject: [PATCH 35/41] the derived deferred classes now also re-implement the onSuccess() method to ensure that the same object type is returned, to support propert chaining --- include/channel.h | 5 ----- include/deferredcancel.h | 12 ++++++++++-- include/deferredconsumer.h | 18 +++++++++++++----- include/deferreddelete.h | 12 ++++++++++-- include/deferredqueue.h | 12 ++++++++++-- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/include/channel.h b/include/channel.h index 6f24a86..618a314 100644 --- a/include/channel.h +++ b/include/channel.h @@ -334,11 +334,6 @@ public: /** * Publish a message to an exchange * - * If either of the two flags is set, and the message could not immediately - * be published, the message is returned by the server to the client. If you - * want to catch such returned messages, you need to install a handler using - * the onReturned() method. - * * @param exchange the exchange to publish to * @param routingkey the routing key * @param envelope the full envelope to send diff --git a/include/deferredcancel.h b/include/deferredcancel.h index 244e145..0c1f8e6 100644 --- a/include/deferredcancel.h +++ b/include/deferredcancel.h @@ -75,9 +75,17 @@ public: } /** - * All the onSuccess() functions defined in the base class are accessible too + * Register the function that is called when the cancel operation succeeded + * @param callback */ - using Deferred::onSuccess; + DeferredCancel &onSuccess(const SuccessCallback &callback) + { + // call base + Deferred::onSuccess(callback); + + // allow chaining + return *this; + } }; /** diff --git a/include/deferredconsumer.h b/include/deferredconsumer.h index ee82068..c656728 100644 --- a/include/deferredconsumer.h +++ b/include/deferredconsumer.h @@ -75,6 +75,19 @@ public: return *this; } + /** + * Register the function that is called when the consumer starts + * @param callback + */ + DeferredConsumer &onSuccess(const SuccessCallback &callback) + { + // call base + Deferred::onSuccess(callback); + + // allow chaining + return *this; + } + /** * Register a function to be called when a message arrives * This fuction is also available as onMessage() because I always forget which name I gave to it @@ -102,11 +115,6 @@ public: // allow chaining return *this; } - - /** - * All the onSuccess() functions defined in the base class are accessible too - */ - using Deferred::onSuccess; }; /** diff --git a/include/deferreddelete.h b/include/deferreddelete.h index 43ada61..aa0d83e 100644 --- a/include/deferreddelete.h +++ b/include/deferreddelete.h @@ -80,9 +80,17 @@ public: } /** - * All the onSuccess() functions defined in the base class are accessible too + * Register the function that is called when the queue is deleted or purged + * @param callback */ - using Deferred::onSuccess; + DeferredDelete &onSuccess(const SuccessCallback &callback) + { + // call base + Deferred::onSuccess(callback); + + // allow chaining + return *this; + } }; /** diff --git a/include/deferredqueue.h b/include/deferredqueue.h index 085f095..6b5d930 100644 --- a/include/deferredqueue.h +++ b/include/deferredqueue.h @@ -80,9 +80,17 @@ public: } /** - * All the onSuccess() functions defined in the base class are accessible too + * Register the function that is called when the queue is declared + * @param callback */ - using Deferred::onSuccess; + DeferredQueue &onSuccess(const SuccessCallback &callback) + { + // call base + Deferred::onSuccess(callback); + + // allow chaining + return *this; + } }; /** From e0b709fa63ce47440d872f7b0e97142338a31b49 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 16 Apr 2014 12:04:44 +0200 Subject: [PATCH 36/41] in case of a connection error, we no longer call the channel wide error handler --- include/channelimpl.h | 7 ++++--- include/connectionimpl.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/channelimpl.h b/include/channelimpl.h index a4c4e39..846c1b8 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -481,9 +481,10 @@ public: /** * Report an error message on a channel - * @param message + * @param message the error message + * @param notifyhandler should the channel-wide handler also be called? */ - void reportError(const char *message) + void reportError(const char *message, bool notifyhandler = true) { // change state _state = state_closed; @@ -521,7 +522,7 @@ public: _newestCallback = nullptr; // inform handler - if (_errorCallback) _errorCallback(message); + if (notifyhandler && _errorCallback) _errorCallback(message); } /** diff --git a/include/connectionimpl.h b/include/connectionimpl.h index bd654b2..836d3a0 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -285,7 +285,7 @@ public: for (auto &iter : _channels) { // report the errors - iter.second->reportError(message); + iter.second->reportError(message, false); // leap out if no longer valid if (!monitor.valid()) return; From a9570277b76830c060a5b51abf7cff63c2b1dbf2 Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Tue, 29 Apr 2014 15:51:33 +0200 Subject: [PATCH 37/41] Removed the nowait option from the public interface, because the deferred would never be called and implemented a queue to wait for synchronous methods to complete before sending the next frame --- include/channel.h | 53 ++----------- include/channelimpl.h | 99 ++++++++++++++++-------- include/connectionimpl.h | 7 ++ include/flags.h | 1 - include/outbuffer.h | 20 ++--- src/basicackframe.h | 25 +++++-- src/basiccancelframe.h | 16 +++- src/basiccancelokframe.h | 2 +- src/basicconsumeframe.h | 12 +++ src/basicconsumeokframe.h | 2 +- src/basicdeliverframe.h | 11 +++ src/basicpublishframe.h | 11 +++ src/basicqosokframe.h | 2 +- src/basicrecoverasyncframe.h | 11 +++ src/basicrecoverframe.h | 11 +++ src/basicrejectframe.h | 11 +++ src/basicreturnframe.h | 11 +++ src/channelcloseokframe.h | 12 +-- src/channelflowokframe.h | 2 +- src/channelimpl.cpp | 125 ++++++++++++++++++++----------- src/connectionimpl.cpp | 50 ++++++++----- src/exchangebindframe.h | 47 +++++++----- src/exchangebindokframe.h | 2 +- src/exchangedeclareframe.h | 12 +++ src/exchangedeclareokframe.h | 2 +- src/exchangedeleteframe.h | 12 +++ src/exchangedeleteokframe.h | 2 +- src/exchangeunbindframe.h | 49 +++++++----- src/exchangeunbindokframe.h | 2 +- src/frame.h | 51 ++++++++++--- src/methodframe.h | 8 ++ src/queuebindframe.h | 12 +++ src/queuebindokframe.h | 2 +- src/queuedeclareframe.h | 14 +++- src/queuedeclareokframe.h | 2 +- src/queuedeleteframe.h | 12 +++ src/queuedeleteokframe.h | 2 +- src/queuepurgeframe.h | 26 +++++-- src/queuepurgeokframe.h | 2 +- src/queueunbindokframe.h | 2 +- src/transactioncommitokframe.h | 2 +- src/transactionrollbackokframe.h | 2 +- src/transactionselectokframe.h | 2 +- 43 files changed, 524 insertions(+), 237 deletions(-) diff --git a/include/channel.h b/include/channel.h index 618a314..e722ebb 100644 --- a/include/channel.h +++ b/include/channel.h @@ -174,42 +174,30 @@ public: /** * Bind two exchanges to each other * - * The following flags can be used for the exchange - * - * - nowait do not wait on response - * * @param source the source exchange * @param target the target exchange * @param routingkey the routing key - * @param flags optional flags * @param arguments additional bind arguments * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, flags, arguments); } - Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, 0, arguments); } - Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags = 0) { return _implementation.bindExchange(source, target, routingkey, flags, Table()); } + Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, arguments); } + Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey) { return _implementation.bindExchange(source, target, routingkey, Table()); } /** * Unbind two exchanges from one another * - * The following flags can be used for the exchange - * - * - nowait do not wait on response - * * @param target the target exchange * @param source the source exchange * @param routingkey the routing key - * @param flags optional flags * @param arguments additional unbind arguments * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, flags, arguments); } - Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, 0, arguments); } - Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags = 0) { return _implementation.unbindExchange(target, source, routingkey, flags, Table()); } + Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, arguments); } + Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey) { return _implementation.unbindExchange(target, source, routingkey, Table()); } /** * Declare a queue @@ -250,22 +238,16 @@ public: /** * Bind a queue to an exchange * - * The following flags can be used for the exchange - * - * - nowait do not wait on response - * * @param exchange the source exchange * @param queue the target queue * @param routingkey the routing key - * @param flags additional flags * @param arguments additional bind arguments * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, flags, arguments); } - Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, 0, arguments); } - Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags = 0) { return _implementation.bindQueue(exchange, queue, routingkey, flags, Table()); } + Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, arguments); } + Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey) { return _implementation.bindQueue(exchange, queue, routingkey, Table()); } /** * Unbind a queue from an exchange @@ -283,12 +265,7 @@ public: /** * Purge a queue * - * The following flags can be used for the exchange - * - * - nowait do not wait on response - * * @param name name of the queue - * @param flags additional flags * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. @@ -303,7 +280,7 @@ public: * * }); */ - DeferredDelete &purgeQueue(const std::string &name, int flags = 0){ return _implementation.purgeQueue(name, flags); } + DeferredDelete &purgeQueue(const std::string &name){ return _implementation.purgeQueue(name); } /** * Remove a queue @@ -375,11 +352,6 @@ public: * - nolocal if set, messages published on this channel are not also consumed * - noack if set, consumed messages do not have to be acked, this happens automatically * - exclusive request exclusive access, only this consumer can access the queue - * - nowait the server does not have to send a response back that consuming is active - * - * The method Deferred::onSuccess() will be called when the - * consumer has started (unless the nowait option was set, in which case - * no confirmation method is called) * * @param queue the queue from which you want to consume * @param tag a consumer tag that will be associated with this consume operation @@ -411,16 +383,7 @@ public: * * If you want to stop a running consumer, you can use this method with the consumer tag * - * The following flags are supported: - * - * - nowait the server does not have to send a response back that the consumer has been cancelled - * - * The method Deferred::onSuccess() will be called when the consumer - * was succesfully stopped (unless the nowait option was used, in which case no - * confirmation method is called) - * * @param tag the consumer tag - * @param flags optional additional flags * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. @@ -435,7 +398,7 @@ public: * * }); */ - DeferredCancel &cancel(const std::string &tag, int flags = 0) { return _implementation.cancel(tag, flags); } + DeferredCancel &cancel(const std::string &tag) { return _implementation.cancel(tag); } /** * Acknoldge a received message diff --git a/include/channelimpl.h b/include/channelimpl.h index 846c1b8..062c160 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -58,18 +58,18 @@ private: /** * Pointer to the oldest deferred result (the first one that is going * to be executed) - * + * * @var Deferred */ std::unique_ptr _oldestCallback = nullptr; - + /** * Pointer to the newest deferred result (the last one to be added). - * + * * @var Deferred */ Deferred *_newestCallback = nullptr; - + /** * The channel number * @var uint16_t @@ -86,6 +86,19 @@ private: state_closed } _state = state_connected; + /** + * The frames that still need to be send out + * + * We store the data as well as whether they + * should be handled synchronously. + */ + std::queue> _queue; + + /** + * Are we currently operating in synchronous mode? + */ + bool _synchronous = false; + /** * The message that is now being received * @var ConsumedMessage @@ -203,13 +216,12 @@ public: * @param source exchange which binds to target * @param target exchange to bind to * @param routingKey routing key - * @param glags additional flags * @param arguments additional arguments for binding * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); + Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments); /** * unbind two exchanges @@ -217,13 +229,12 @@ public: * @param source the source exchange * @param target the target exchange * @param routingkey the routing key - * @param flags optional flags * @param arguments additional unbind arguments * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred &unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments); + Deferred &unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments); /** * remove an exchange @@ -253,13 +264,12 @@ public: * @param exchangeName name of the exchange to bind to * @param queueName name of the queue * @param routingkey routingkey - * @param flags additional flags * @param arguments additional arguments * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ - Deferred &bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments); + Deferred &bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments); /** * Unbind a queue from an exchange @@ -277,7 +287,6 @@ public: /** * Purge a queue * @param queue queue to purge - * @param flags additional flags * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. @@ -292,7 +301,7 @@ public: * * }); */ - DeferredDelete &purgeQueue(const std::string &name, int flags); + DeferredDelete &purgeQueue(const std::string &name); /** * Remove a queue @@ -363,7 +372,6 @@ public: /** * Cancel a running consumer * @param tag the consumer tag - * @param flags optional flags * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. @@ -378,7 +386,7 @@ public: * * }); */ - DeferredCancel &cancel(const std::string &tag, int flags); + DeferredCancel &cancel(const std::string &tag); /** * Acknowledge a message @@ -429,54 +437,79 @@ public: */ bool send(const Frame &frame); + /** + * Signal the channel that a synchronous operation + * was completed. After this operation, waiting + * frames can be sent out. + */ + void synchronized(); + /** * Report to the handler that the channel is opened */ void reportReady() { + // callbacks could destroy us, so monitor it + Monitor monitor(this); + // inform handler if (_readyCallback) _readyCallback(); + + // if the monitor is still valid, we exit synchronous mode now + if (monitor.valid()) synchronized(); } /** * Report to the handler that the channel is closed + * + * Returns whether the channel object is still valid */ - void reportClosed() + bool reportClosed() { // change state _state = state_closed; // and pass on to the reportSuccess() method which will call the // appropriate deferred object to report the successful operation - reportSuccess(); + return reportSuccess(); + + // technically, we should exit synchronous method now + // since the synchronous channel close frame has been + // acknowledged by the server. + // + // but since the channel was just closed, there is no + // real point in doing this, as we cannot send frames + // out anymore. } /** * Report success * - * This function is called to report success for all - * cases where the callback does not receive any parameters + * Returns whether the channel object is still valid */ template - void reportSuccess(Arguments ...parameters) + bool reportSuccess(Arguments ...parameters) { // skip if there is no oldest callback - if (!_oldestCallback) return; - + if (!_oldestCallback) return true; + // we are going to call callbacks that could destruct the channel Monitor monitor(this); - + // call the callback auto *next = _oldestCallback->reportSuccess(std::forward(parameters)...); - + // leap out if channel no longer exists - if (!monitor.valid()) return; - + if (!monitor.valid()) return false; + // set the oldest callback _oldestCallback.reset(next); - + // if there was no next callback, the newest callback was just used if (!next) _newestCallback = nullptr; + + // we are still valid + return true; } /** @@ -497,30 +530,30 @@ public: { // call the callback auto *next = _oldestCallback->reportError(message); - + // leap out if channel no longer exists if (!monitor.valid()) return; - + // set the oldest callback _oldestCallback.reset(next); } - + // clean up all deferred other objects while (_oldestCallback) { // call the callback auto *next = _oldestCallback->reportError("Channel is in error state"); - + // leap out if channel no longer exists if (!monitor.valid()) return; - + // set the oldest callback _oldestCallback.reset(next); } // all callbacks have been processed, so we also can reset the pointer to the newest _newestCallback = nullptr; - + // inform handler if (notifyhandler && _errorCallback) _errorCallback(message); } @@ -534,7 +567,7 @@ public: { // install the callback if it is assigned if (callback) _consumers[consumertag] = callback; - + // otherwise we erase the previously set callback else _consumers.erase(consumertag); } diff --git a/include/connectionimpl.h b/include/connectionimpl.h index 836d3a0..530888a 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -253,6 +253,13 @@ public: */ bool send(const Frame &frame); + /** + * Send buffered data over the connection + * + * @param buffer the buffer with data to send + */ + bool send(OutBuffer &&buffer); + /** * Get a channel by its identifier * diff --git a/include/flags.h b/include/flags.h index 5f46b52..c2de72f 100644 --- a/include/flags.h +++ b/include/flags.h @@ -26,7 +26,6 @@ extern const int global; extern const int nolocal; extern const int noack; extern const int exclusive; -extern const int nowait; extern const int mandatory; extern const int immediate; extern const int redelivered; diff --git a/include/outbuffer.h b/include/outbuffer.h index 24833fa..54d5988 100644 --- a/include/outbuffer.h +++ b/include/outbuffer.h @@ -24,7 +24,7 @@ private: * @var char* */ char *_buffer; - + /** * Pointer to the buffer to be filled * @var char* @@ -36,13 +36,13 @@ private: * @var size_t */ size_t _size; - + /** * The total capacity of the out buffer * @var size_t */ size_t _capacity; - + public: /** @@ -56,7 +56,7 @@ public: _capacity = capacity; _buffer = _current = new char[capacity]; } - + /** * Copy constructor * @param that @@ -68,11 +68,11 @@ public: _capacity = that._capacity; _buffer = new char[_capacity]; _current = _buffer + _size; - + // copy memory memcpy(_buffer, that._buffer, _size); } - + /** * Move constructor * @param that @@ -84,7 +84,7 @@ public: _capacity = that._capacity; _buffer = that._buffer; _current = that._current; - + // reset the other object that._size = 0; that._capacity = 0; @@ -95,7 +95,7 @@ public: /** * Destructor */ - virtual ~OutBuffer() + virtual ~OutBuffer() { if (_buffer) delete[] _buffer; } @@ -104,7 +104,7 @@ public: * Get access to the internal buffer * @return const char* */ - const char *data() + const char *data() const { return _buffer; } @@ -113,7 +113,7 @@ public: * Current size of the output buffer * @return size_t */ - size_t size() + size_t size() const { return _size; } diff --git a/src/basicackframe.h b/src/basicackframe.h index 8337b9f..7549155 100644 --- a/src/basicackframe.h +++ b/src/basicackframe.h @@ -1,6 +1,6 @@ /** * Class describing a basic acknowledgement frame - * + * * @copyright 2014 Copernica BV */ @@ -38,10 +38,10 @@ protected: { // call base BasicFrame::fill(buffer); - + // add the delivery tag buffer.add(_deliveryTag); - + // add the booleans _multiple.fill(buffer); } @@ -54,25 +54,36 @@ public: * @param deliveryTag server-assigned and channel specific delivery tag * @param multiple acknowledge mutiple messages */ - BasicAckFrame(uint16_t channel, uint64_t deliveryTag, bool multiple = false) : + BasicAckFrame(uint16_t channel, uint64_t deliveryTag, bool multiple = false) : BasicFrame(channel, 9), _deliveryTag(deliveryTag), _multiple(multiple) {} - + /** * Construct based on received frame * @param frame */ - BasicAckFrame(ReceivedFrame &frame) : + BasicAckFrame(ReceivedFrame &frame) : BasicFrame(frame), _deliveryTag(frame.nextUint64()), _multiple(frame) {} - + /** * Destructor */ virtual ~BasicAckFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + virtual bool synchronous() const override + { + return false; + } + /** * Return the method ID * @return uint16_t diff --git a/src/basiccancelframe.h b/src/basiccancelframe.h index ea65d3e..e665ab2 100644 --- a/src/basiccancelframe.h +++ b/src/basiccancelframe.h @@ -67,7 +67,19 @@ public: * Destructor */ virtual ~BasicCancelFrame() {} - + + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous when the nowait option is not used + return !noWait(); + } + /** * Return the consumertag, which is specified by the client or provided by the server * @return string @@ -90,7 +102,7 @@ public: * Return whether to wait for a response * @return boolean */ - const bool noWait() + const bool noWait() const { return _noWait.get(0); } diff --git a/src/basiccancelokframe.h b/src/basiccancelokframe.h index 6d9d42f..6592166 100644 --- a/src/basiccancelokframe.h +++ b/src/basiccancelokframe.h @@ -94,7 +94,7 @@ public: if (!channel) return false; // report - channel->reportSuccess(consumerTag()); + if (channel->reportSuccess(consumerTag())) channel->synchronized(); // done return true; diff --git a/src/basicconsumeframe.h b/src/basicconsumeframe.h index 0bc7888..7e57eee 100644 --- a/src/basicconsumeframe.h +++ b/src/basicconsumeframe.h @@ -111,6 +111,18 @@ public: */ virtual ~BasicConsumeFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous when the nowait option is not set + return !noWait(); + } + /** * Return the method ID * @return uint16_t diff --git a/src/basicconsumeokframe.h b/src/basicconsumeokframe.h index 31c44ef..e767f0f 100644 --- a/src/basicconsumeokframe.h +++ b/src/basicconsumeokframe.h @@ -94,7 +94,7 @@ public: if (!channel) return false; // report - channel->reportSuccess(consumerTag()); + if (channel->reportSuccess(consumerTag())) channel->synchronized(); // done return true; diff --git a/src/basicdeliverframe.h b/src/basicdeliverframe.h index aef677f..ac6128d 100644 --- a/src/basicdeliverframe.h +++ b/src/basicdeliverframe.h @@ -120,6 +120,17 @@ public: return _routingKey; } + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + virtual bool synchronous() const override + { + return false; + } + /** * Return the method ID * @return uint16_t diff --git a/src/basicpublishframe.h b/src/basicpublishframe.h index 9f9eb69..975c2ab 100644 --- a/src/basicpublishframe.h +++ b/src/basicpublishframe.h @@ -94,6 +94,17 @@ public: */ virtual ~BasicPublishFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + return false; + } + /** * Return the name of the exchange to publish to * @return string diff --git a/src/basicqosokframe.h b/src/basicqosokframe.h index dc6e612..9c235e2 100644 --- a/src/basicqosokframe.h +++ b/src/basicqosokframe.h @@ -67,7 +67,7 @@ public: if (!channel) return false; // report - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/basicrecoverasyncframe.h b/src/basicrecoverasyncframe.h index 7d7e036..68eb3ec 100644 --- a/src/basicrecoverasyncframe.h +++ b/src/basicrecoverasyncframe.h @@ -62,6 +62,17 @@ public: */ virtual ~BasicRecoverAsyncFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + virtual bool synchronous() const override + { + return false; + } + /** * Return the method ID * @return uint16_t diff --git a/src/basicrecoverframe.h b/src/basicrecoverframe.h index d378393..ca67f16 100644 --- a/src/basicrecoverframe.h +++ b/src/basicrecoverframe.h @@ -62,6 +62,17 @@ public: */ virtual ~BasicRecoverFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + return false; + } + /** * Return the method ID * @return uint16_t diff --git a/src/basicrejectframe.h b/src/basicrejectframe.h index c5c1f6b..a77d7da 100644 --- a/src/basicrejectframe.h +++ b/src/basicrejectframe.h @@ -73,6 +73,17 @@ public: */ virtual ~BasicRejectFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + return false; + } + /** * Return the method ID * @return uint16_t diff --git a/src/basicreturnframe.h b/src/basicreturnframe.h index 6bb8aef..2fedce6 100644 --- a/src/basicreturnframe.h +++ b/src/basicreturnframe.h @@ -92,6 +92,17 @@ public: */ virtual ~BasicReturnFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + virtual bool synchronous() const override + { + return false; + } + /** * Return the name of the exchange to publish to * @return string diff --git a/src/channelcloseokframe.h b/src/channelcloseokframe.h index 5eb5cda..d06e845 100644 --- a/src/channelcloseokframe.h +++ b/src/channelcloseokframe.h @@ -1,6 +1,6 @@ /** * Class describing a channel close acknowledgement frame - * + * * @copyright 2014 Copernica BV */ @@ -67,13 +67,13 @@ public: { // we need the appropriate channel ChannelImpl *channel = connection->channel(this->channel()); - + // channel does not exist - if (!channel) return false; - + if (!channel) return false; + // report that the channel is closed - channel->reportClosed(); - + if (channel->reportClosed()) channel->synchronized(); + // done return true; } diff --git a/src/channelflowokframe.h b/src/channelflowokframe.h index fcd325f..c335564 100644 --- a/src/channelflowokframe.h +++ b/src/channelflowokframe.h @@ -93,7 +93,7 @@ public: if (!channel) return false; // report success for the call - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index c612232..0b917f4 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -85,7 +85,7 @@ ChannelImpl::~ChannelImpl() // close the channel now close(); - + // destruct deferred results while (_oldestCallback) _oldestCallback.reset(_oldestCallback->next()); } @@ -98,13 +98,13 @@ Deferred &ChannelImpl::push(Deferred *deferred) { // do we already have an oldest? if (!_oldestCallback) _oldestCallback.reset(deferred); - + // do we already have a newest? if (_newestCallback) _newestCallback->add(deferred); - + // store newest callback _newestCallback = deferred; - + // done return *deferred; } @@ -222,7 +222,7 @@ Deferred &ChannelImpl::declareExchange(const std::string &name, ExchangeType typ if (type == ExchangeType::headers)exchangeType = "headers"; // send declare exchange frame - return push(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments)); + return push(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, false, arguments)); } /** @@ -231,16 +231,15 @@ Deferred &ChannelImpl::declareExchange(const std::string &name, ExchangeType typ * @param source exchange which binds to target * @param target exchange to bind to * @param routingKey routing key - * @param flags additional flags * @param arguments additional arguments for binding * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred &ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) +Deferred &ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { // send exchange bind frame - return push(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments)); + return push(ExchangeBindFrame(_id, target, source, routingkey, false, arguments)); } /** @@ -249,16 +248,15 @@ Deferred &ChannelImpl::bindExchange(const std::string &source, const std::string * @param source the source exchange * @param target the target exchange * @param routingkey the routing key - * @param flags optional flags * @param arguments additional unbind arguments * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred &ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) +Deferred &ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { // send exchange unbind frame - return push(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments)); + return push(ExchangeUnbindFrame(_id, target, source, routingkey, false, arguments)); } /** @@ -273,7 +271,7 @@ Deferred &ChannelImpl::unbindExchange(const std::string &source, const std::stri Deferred &ChannelImpl::removeExchange(const std::string &name, int flags) { // send delete exchange frame - return push(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait)); + return push(ExchangeDeleteFrame(_id, name, flags & ifunused, false)); } /** @@ -288,14 +286,14 @@ Deferred &ChannelImpl::removeExchange(const std::string &name, int flags) DeferredQueue &ChannelImpl::declareQueue(const std::string &name, int flags, const Table &arguments) { // the frame to send - QueueDeclareFrame frame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, flags & nowait, arguments); - + QueueDeclareFrame frame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, false, arguments); + // send the queuedeclareframe auto *result = new DeferredQueue(send(frame)); - + // add the deferred result push(result); - + // done return *result; } @@ -306,16 +304,15 @@ DeferredQueue &ChannelImpl::declareQueue(const std::string &name, int flags, con * @param exchangeName name of the exchange to bind to * @param queueName name of the queue * @param routingkey routingkey - * @param flags additional flags * @param arguments additional arguments * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. */ -Deferred &ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments) +Deferred &ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments) { // send the bind queue frame - return push(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments)); + return push(QueueBindFrame(_id, queueName, exchangeName, routingkey, false, arguments)); } /** @@ -338,7 +335,6 @@ Deferred &ChannelImpl::unbindQueue(const std::string &exchange, const std::strin /** * Purge a queue * @param queue queue to purge - * @param flags additional flags * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. @@ -353,17 +349,17 @@ Deferred &ChannelImpl::unbindQueue(const std::string &exchange, const std::strin * * }); */ -DeferredDelete &ChannelImpl::purgeQueue(const std::string &name, int flags) +DeferredDelete &ChannelImpl::purgeQueue(const std::string &name) { - // the frame to send - QueuePurgeFrame frame(_id, name, flags & nowait); - + // the frame to send + QueuePurgeFrame frame(_id, name, false); + // send the frame, and create deferred object auto *deferred = new DeferredDelete(send(frame)); - + // push to list push(deferred); - + // done return *deferred; } @@ -389,14 +385,14 @@ DeferredDelete &ChannelImpl::purgeQueue(const std::string &name, int flags) DeferredDelete &ChannelImpl::removeQueue(const std::string &name, int flags) { // the frame to send - QueueDeleteFrame frame(_id, name, flags & ifunused, flags & ifempty, flags & nowait); + QueueDeleteFrame frame(_id, name, flags & ifunused, flags & ifempty, false); // send the frame, and create deferred object auto *deferred = new DeferredDelete(send(frame)); - + // push to list push(deferred); - + // done return *deferred; } @@ -495,14 +491,14 @@ Deferred &ChannelImpl::setQos(uint16_t prefetchCount) DeferredConsumer& ChannelImpl::consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { // the frame to send - BasicConsumeFrame frame(_id, queue, tag, flags & nolocal, flags & noack, flags & exclusive, flags & nowait, arguments); - + BasicConsumeFrame frame(_id, queue, tag, flags & nolocal, flags & noack, flags & exclusive, false, arguments); + // send the frame, and create deferred object auto *deferred = new DeferredConsumer(this, send(frame)); - + // push to list push(deferred); - + // done return *deferred; } @@ -510,7 +506,6 @@ DeferredConsumer& ChannelImpl::consume(const std::string &queue, const std::stri /** * Cancel a running consumer * @param tag the consumer tag - * @param flags optional flags * * This function returns a deferred handler. Callbacks can be installed * using onSuccess(), onError() and onFinalize() methods. @@ -525,17 +520,17 @@ DeferredConsumer& ChannelImpl::consume(const std::string &queue, const std::stri * * }); */ -DeferredCancel &ChannelImpl::cancel(const std::string &tag, int flags) +DeferredCancel &ChannelImpl::cancel(const std::string &tag) { // the cancel frame to send - BasicCancelFrame frame(_id, tag, flags & nowait); + BasicCancelFrame frame(_id, tag, false); // send the frame, and create deferred object auto *deferred = new DeferredCancel(this, send(frame)); - + // push to list push(deferred); - + // done return *deferred; } @@ -587,10 +582,56 @@ bool ChannelImpl::send(const Frame &frame) // skip if channel is not connected if (_state != state_connected || !_connection) return false; + // are we currently in synchronous mode or are there + // other frames waiting for their turn to be sent? + if (_synchronous || !_queue.empty()) + { + // we need to wait until the synchronous frame has + // been processed, so queue the frame until it was + _queue.emplace(frame.synchronous(), frame.buffer()); + + // it was of course not actually sent but we pretend + // that it was, because no error occured + return true; + } + + // enter synchronous mode if necessary + _synchronous = frame.synchronous(); + // send to tcp connection return _connection->send(frame); } +/** + * Signal the channel that a synchronous operation + * was completed. After this operation, waiting + * frames can be sent out. + */ +void ChannelImpl::synchronized() +{ + // we are no longer waiting for synchronous operations + _synchronous = false; + + // we need to monitor the channel for validity + Monitor monitor(this); + + // send all frames while not in synchronous mode + while (monitor.valid() && !_synchronous && !_queue.empty()) + { + // retrieve the first buffer and synchronous + auto pair = std::move(_queue.front()); + + // remove from the list + _queue.pop(); + + // mark as synchronous if necessary + _synchronous = pair.first; + + // send it over the connection + _connection->send(std::move(pair.second)); + } +} + /** * Report the received message */ @@ -602,16 +643,16 @@ void ChannelImpl::reportMessage() // look for the consumer auto iter = _consumers.find(_message->consumer()); if (iter == _consumers.end()) return; - + // is this a valid callback method if (!iter->second) return; // after the report the channel may be destructed, monitor that Monitor monitor(this); - + // call the callback _message->report(iter->second); - + // skip if channel was destructed if (!monitor.valid()) return; @@ -628,7 +669,7 @@ ConsumedMessage *ChannelImpl::message(const BasicDeliverFrame &frame) { // destruct if message is already set if (_message) delete _message; - + // construct a message return _message = new ConsumedMessage(frame); } diff --git a/src/connectionimpl.cpp b/src/connectionimpl.cpp index 668fbb3..a26f903 100644 --- a/src/connectionimpl.cpp +++ b/src/connectionimpl.cpp @@ -213,9 +213,9 @@ void ConnectionImpl::setConnected() // store connected state _state = state_connected; - // if the close operation was already called, we do that again now again - // so that the actual messages to close down the connection and the channel - // are appended to the queue + // if the close method was called before, the frame was not + // sent. append it to the end of the queue to make sure we + // are correctly closed down. if (_closed && !sendClose()) return; // we're going to call the handler, which can destruct the connection, @@ -225,11 +225,8 @@ void ConnectionImpl::setConnected() // inform handler _handler->onConnected(_parent); - // leap out if the connection no longer exists - if (!monitor.valid()) return; - // empty the queue of messages - while (!_queue.empty()) + while (monitor.valid() && !_queue.empty()) { // get the next message OutBuffer buffer(std::move(_queue.front())); @@ -239,9 +236,6 @@ void ConnectionImpl::setConnected() // send it _handler->onData(_parent, buffer.data(), buffer.size()); - - // leap out if the connection was destructed - if (!monitor.valid()) return; } } @@ -256,16 +250,10 @@ bool ConnectionImpl::send(const Frame &frame) if (_state == state_closing || _state == state_closed) return false; // we need an output buffer - OutBuffer buffer(frame.totalSize()); - - // fill the buffer - frame.fill(buffer); - - // append an end of frame byte (but not when still negotiating the protocol) - if (frame.needsSeparator()) buffer.add((uint8_t)206); + OutBuffer buffer(frame.buffer()); // are we still setting up the connection? - if ((_state == state_connected && _queue.size() == 0) || frame.partOfHandshake()) + if ((_state == state_connected && _queue.empty()) || frame.partOfHandshake()) { // send the buffer _handler->onData(_parent, buffer.data(), buffer.size()); @@ -280,6 +268,32 @@ bool ConnectionImpl::send(const Frame &frame) return true; } +/** + * Send buffered data over the connection + * + * @param buffer the buffer with data to send + */ +bool ConnectionImpl::send(OutBuffer &&buffer) +{ + // this only works when we are already connected + if (_state != state_connected) return false; + + // are we waiting for other frames to be sent before us? + if (_queue.empty()) + { + // send it directly + _handler->onData(_parent, buffer.data(), buffer.size()); + } + else + { + // add to the list of waiting buffers + _queue.push(std::move(buffer)); + } + + // done + return true; +} + /** * End of namspace */ diff --git a/src/exchangebindframe.h b/src/exchangebindframe.h index e246fad..1d01161 100644 --- a/src/exchangebindframe.h +++ b/src/exchangebindframe.h @@ -1,6 +1,6 @@ /** * Exchangebindframe.h - * + * * @copyright 2014 Copernica BV */ @@ -26,25 +26,25 @@ private: * @var ShortString */ ShortString _destination; - + /** - * Exchange which is bound + * Exchange which is bound * @var ShortString */ ShortString _source; - + /** * Routing key * @var ShortString */ ShortString _routingKey; - + /** * contains: nowait do not wait on response * @var booleanset */ BooleanSet _bools; - + /** * Additional arguments * @var Table @@ -61,7 +61,7 @@ protected: { // call base ExchangeFrame::fill(buffer); - + buffer.add(_reserved); _destination.fill(buffer); _source.fill(buffer); @@ -70,13 +70,13 @@ protected: _arguments.fill(buffer); } -public: +public: /** * Constructor based on incoming data - * + * * @param frame received frame to decode */ - ExchangeBindFrame(ReceivedFrame &frame) : + ExchangeBindFrame(ReceivedFrame &frame) : ExchangeFrame(frame), _reserved(frame.nextUint16()), _destination(frame), @@ -102,8 +102,19 @@ public: _bools(noWait), _arguments(arguments) {} - - + + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous when the nowait option has not been set + return !noWait(); + } + /** * Get the destination exchange * @return string @@ -112,7 +123,7 @@ public: { return _destination; } - + /** * Get the source exchange * @return string @@ -121,7 +132,7 @@ public: { return _source; } - + /** * Get the routing key * @return string @@ -130,7 +141,7 @@ public: { return _routingKey; } - + /** * Get the method id * @return uint16_t @@ -139,7 +150,7 @@ public: { return 30; } - + /** * Get the additional arguments * @return Table @@ -148,12 +159,12 @@ public: { return _arguments; } - + /** * Get the nowait bool * @return bool */ - bool noWait() + bool noWait() const { return _bools.get(0); } diff --git a/src/exchangebindokframe.h b/src/exchangebindokframe.h index 5b1f29c..157d9e4 100644 --- a/src/exchangebindokframe.h +++ b/src/exchangebindokframe.h @@ -67,7 +67,7 @@ public: if(!channel) return false; // report to handler - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/exchangedeclareframe.h b/src/exchangedeclareframe.h index 9f36e64..aa940dd 100644 --- a/src/exchangedeclareframe.h +++ b/src/exchangedeclareframe.h @@ -106,6 +106,18 @@ public: */ virtual ~ExchangeDeclareFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous without the nowait option + return !noWait(); + } + /** * Method id * @return uint16_t diff --git a/src/exchangedeclareokframe.h b/src/exchangedeclareokframe.h index f86bf3e..1938f73 100644 --- a/src/exchangedeclareokframe.h +++ b/src/exchangedeclareokframe.h @@ -70,7 +70,7 @@ public: if(!channel) return false; // report exchange declare ok - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/exchangedeleteframe.h b/src/exchangedeleteframe.h index 960690c..cdd906f 100644 --- a/src/exchangedeleteframe.h +++ b/src/exchangedeleteframe.h @@ -83,6 +83,18 @@ public: */ virtual ~ExchangeDeleteFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous without the nowait option + return !noWait(); + } + /** * returns the method id * @return uint16_t diff --git a/src/exchangedeleteokframe.h b/src/exchangedeleteokframe.h index ce44336..b8ec41b 100644 --- a/src/exchangedeleteokframe.h +++ b/src/exchangedeleteokframe.h @@ -71,7 +71,7 @@ public: if(!channel) return false; // report to handler - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/exchangeunbindframe.h b/src/exchangeunbindframe.h index 64999d2..d92d941 100644 --- a/src/exchangeunbindframe.h +++ b/src/exchangeunbindframe.h @@ -8,7 +8,7 @@ * Set up namespace */ namespace AMQP { - + /** * Class definition */ @@ -26,31 +26,31 @@ private: * @var ShortString */ ShortString _destination; - + /** - * Exchange which is bound + * Exchange which is bound * @var ShortString */ ShortString _source; - + /** * Routing key * @var ShortString */ ShortString _routingKey; - + /** * contains: nowait do not wait on response * @var booleanset */ BooleanSet _bools; - + /** * Additional arguments * @var Table */ Table _arguments; - + protected: /** * Encode a frame on a string buffer @@ -61,7 +61,7 @@ protected: { // call base ExchangeFrame::fill(buffer); - + buffer.add(_reserved); _destination.fill(buffer); _source.fill(buffer); @@ -73,10 +73,10 @@ protected: public: /** * Constructor based on incoming data - * + * * @param frame received frame to decode */ - ExchangeUnbindFrame(ReceivedFrame &frame) : + ExchangeUnbindFrame(ReceivedFrame &frame) : ExchangeFrame(frame), _reserved(frame.nextUint16()), _destination(frame), @@ -102,8 +102,19 @@ public: _bools(noWait), _arguments(arguments) {} - - + + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous without the nowait option + return !noWait(); + } + /** * Get the destination exchange * @return string @@ -112,7 +123,7 @@ public: { return _destination; } - + /** * Get the source exchange * @return string @@ -121,7 +132,7 @@ public: { return _source; } - + /** * Get the routing key * @return string @@ -130,7 +141,7 @@ public: { return _routingKey; } - + /** * Get the method id * @return uint16_t @@ -139,7 +150,7 @@ public: { return 40; } - + /** * Get the additional arguments * @return Table @@ -148,16 +159,16 @@ public: { return _arguments; } - + /** * Get the nowait bool * @return bool */ - bool noWait() + bool noWait() const { return _bools.get(0); } - + }; // leave namespace } diff --git a/src/exchangeunbindokframe.h b/src/exchangeunbindokframe.h index ea256ab..d25e2a1 100644 --- a/src/exchangeunbindokframe.h +++ b/src/exchangeunbindokframe.h @@ -68,7 +68,7 @@ public: if(!channel) return false; // report to handler - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/frame.h b/src/frame.h index ed8ad0a..2522e7c 100644 --- a/src/frame.h +++ b/src/frame.h @@ -1,9 +1,9 @@ /** * Frame.h - * + * * Base class for frames. This base class can not be constructed from outside * the library, and is only used internally. - * + * * @copyright 2014 Copernica BV */ @@ -11,7 +11,7 @@ * Set up namespace */ namespace AMQP { - + /** * Class definition */ @@ -19,11 +19,11 @@ class Frame { protected: /** - * Protected constructor to ensure that no objects are created from + * Protected constructor to ensure that no objects are created from * outside the library */ Frame() {} - + public: /** * Destructor @@ -35,40 +35,67 @@ public: * @return uint32_t */ virtual uint32_t totalSize() const = 0; - + /** * Fill an output buffer * @param buffer */ virtual void fill(OutBuffer &buffer) const = 0; - + /** * Is this a frame that is part of the connection setup? * @return bool */ virtual bool partOfHandshake() const { return false; } - + /** * Does this frame need an end-of-frame seperator? * @return bool */ virtual bool needsSeparator() const { return true; } - + + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + virtual bool synchronous() const { return false; } + + /** + * Retrieve the buffer in AMQP wire-format for + * sending over the socket connection + */ + OutBuffer buffer() const + { + // we need an output buffer + OutBuffer buffer(totalSize()); + + // fill the buffer + fill(buffer); + + // append an end of frame byte (but not when still negotiating the protocol) + if (needsSeparator()) buffer.add((uint8_t)206); + + // return the created buffer + return buffer; + } + /** * Process the frame * @param connection The connection over which it was received * @return bool Was it succesfully processed? */ - virtual bool process(ConnectionImpl *connection) + virtual bool process(ConnectionImpl *connection) { // this is an exception throw ProtocolException("unimplemented frame"); - + // unreachable return false; } }; - + /** * End of namespace */ diff --git a/src/methodframe.h b/src/methodframe.h index c435df7..eac62b5 100644 --- a/src/methodframe.h +++ b/src/methodframe.h @@ -49,6 +49,14 @@ public: */ virtual ~MethodFrame() {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override { return true; } + /** * Get the message type * @return uint8_t diff --git a/src/queuebindframe.h b/src/queuebindframe.h index 93b30c6..bc4d2e9 100644 --- a/src/queuebindframe.h +++ b/src/queuebindframe.h @@ -110,6 +110,18 @@ public: _arguments(frame) {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous without the nowait option + return !noWait(); + } + /** * Returns the method id * @return uint16_t diff --git a/src/queuebindokframe.h b/src/queuebindokframe.h index 1d55a33..bf27b87 100644 --- a/src/queuebindokframe.h +++ b/src/queuebindokframe.h @@ -69,7 +69,7 @@ public: if(!channel) return false; // report to handler - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/queuedeclareframe.h b/src/queuedeclareframe.h index 214cd48..475c8b6 100644 --- a/src/queuedeclareframe.h +++ b/src/queuedeclareframe.h @@ -98,6 +98,18 @@ public: _arguments(frame) {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous without the nowait option + return !noWait(); + } + /** * returns the method id * @return string @@ -156,7 +168,7 @@ public: * returns whether to wait for a response * @return bool */ - bool noWait() + bool noWait() const { return _bools.get(4); } diff --git a/src/queuedeclareokframe.h b/src/queuedeclareokframe.h index eb660d8..852c4ec 100644 --- a/src/queuedeclareokframe.h +++ b/src/queuedeclareokframe.h @@ -133,7 +133,7 @@ public: if (!channel) return false; // report success - channel->reportSuccess(name(), messageCount(), consumerCount()); + if (channel->reportSuccess(name(), messageCount(), consumerCount())) channel->synchronized(); // done return true; diff --git a/src/queuedeleteframe.h b/src/queuedeleteframe.h index 8892c5e..dc9a00a 100644 --- a/src/queuedeleteframe.h +++ b/src/queuedeleteframe.h @@ -85,6 +85,18 @@ public: _bools(frame) {} + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous without the nowait option + return !noWait(); + } + /** * returns the method id * @returns uint16_t diff --git a/src/queuedeleteokframe.h b/src/queuedeleteokframe.h index 39cd9b5..c214aa3 100644 --- a/src/queuedeleteokframe.h +++ b/src/queuedeleteokframe.h @@ -94,7 +94,7 @@ public: if(!channel) return false; // report queue deletion success - channel->reportSuccess(this->messageCount()); + if (channel->reportSuccess(this->messageCount())) channel->synchronized(); // done return true; diff --git a/src/queuepurgeframe.h b/src/queuepurgeframe.h index e2a45b2..08cfad5 100644 --- a/src/queuepurgeframe.h +++ b/src/queuepurgeframe.h @@ -1,6 +1,6 @@ /** * Class describing an AMQP queue purge frame - * + * * @copyright 2014 Copernica BV */ @@ -27,7 +27,7 @@ private: ShortString _name; /** - * Do not wait on response + * Do not wait on response * @var BooleanSet */ BooleanSet _noWait; @@ -37,7 +37,7 @@ protected: * Encode the frame into a buffer * * @param buffer buffer to write frame to - */ + */ virtual void fill(OutBuffer& buffer) const override { // call base @@ -63,13 +63,13 @@ public: * @param noWait Do not wait on response * * @return newly created Queuepurgeframe - */ + */ QueuePurgeFrame(uint16_t channel, const std::string& name, bool noWait = false) : QueueFrame(channel, name.length() + 4), // 1 extra for string length, 1 for bool, 2 for deprecated field _name(name), _noWait(noWait) {} - + /** * Constructor based on received data * @param frame received frame @@ -80,11 +80,23 @@ public: _name(frame), _noWait(frame) {} - + + /** + * Is this a synchronous frame? + * + * After a synchronous frame no more frames may be + * sent until the accompanying -ok frame arrives + */ + bool synchronous() const override + { + // we are synchronous without the nowait option + return !noWait(); + } + /** * The method ID * @return method id - */ + */ virtual uint16_t methodID() const override { return 30; diff --git a/src/queuepurgeokframe.h b/src/queuepurgeokframe.h index 166cb60..aaa939d 100644 --- a/src/queuepurgeokframe.h +++ b/src/queuepurgeokframe.h @@ -94,7 +94,7 @@ public: if(!channel) return false; // report queue purge success - channel->reportSuccess(this->messageCount()); + if (channel->reportSuccess(this->messageCount())) channel->synchronized(); // done return true; diff --git a/src/queueunbindokframe.h b/src/queueunbindokframe.h index 14a2761..cdcb570 100644 --- a/src/queueunbindokframe.h +++ b/src/queueunbindokframe.h @@ -73,7 +73,7 @@ public: if(!channel) return false; // report queue unbind success - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/transactioncommitokframe.h b/src/transactioncommitokframe.h index 27f7004..3993a8f 100644 --- a/src/transactioncommitokframe.h +++ b/src/transactioncommitokframe.h @@ -74,7 +74,7 @@ public: if(!channel) return false; // report that the channel is open - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/transactionrollbackokframe.h b/src/transactionrollbackokframe.h index e87d893..868626b 100644 --- a/src/transactionrollbackokframe.h +++ b/src/transactionrollbackokframe.h @@ -74,7 +74,7 @@ public: if(!channel) return false; // report that the channel is open - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; diff --git a/src/transactionselectokframe.h b/src/transactionselectokframe.h index 8a842f7..b66aede 100644 --- a/src/transactionselectokframe.h +++ b/src/transactionselectokframe.h @@ -74,7 +74,7 @@ public: if(!channel) return false; // report that the channel is open - channel->reportSuccess(); + if (channel->reportSuccess()) channel->synchronized(); // done return true; From 7084d49b13e5a334a5daae8be4e1ecf084cbfd5c Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Tue, 29 Apr 2014 16:07:43 +0200 Subject: [PATCH 38/41] Updated README --- README.md | 152 ++++++++++++++++++++++++++---------------------------- 1 file changed, 74 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index ddc6605..cef887d 100644 --- a/README.md +++ b/README.md @@ -86,8 +86,8 @@ class MyConnectionHandler : public AMQP::ConnectionHandler } /** - * Method that is called when the connection was closed. This is the - * counter part of a call to Connection::close() and it confirms that the + * Method that is called when the connection was closed. This is the + * counter part of a call to Connection::close() and it confirms that the * connection was correctly closed. * * @param connection The connection that was closed and that is now unusable @@ -143,12 +143,12 @@ every time that it wants to send out data. We've explained that it is up to you implement that method. But what about data in the other direction? How does the library receive data back from RabbitMQ? -The AMQP-CPP library does not do any IO by itself and it is therefore of course -also not possible for the library to receive data from a socket. It is again up -to you to do this. If, for example, you notice in your event loop that the socket -that is connected with the RabbitMQ server becomes readable, you should read out -that socket (for example by using the recv() system call), and pass the received -bytes to the AMQP-CPP library. This is done by calling the parse() method in the +The AMQP-CPP library does not do any IO by itself and it is therefore of course +also not possible for the library to receive data from a socket. It is again up +to you to do this. If, for example, you notice in your event loop that the socket +that is connected with the RabbitMQ server becomes readable, you should read out +that socket (for example by using the recv() system call), and pass the received +bytes to the AMQP-CPP library. This is done by calling the parse() method in the Connection object. The Connection::parse() method gets two parameters, a pointer to a buffer of @@ -160,12 +160,12 @@ The code snippet below comes from the Connection.h C++ header file. * Parse data that was recevied from RabbitMQ * * Every time that data comes in from RabbitMQ, you should call this method to parse - * the incoming data, and let it handle by the AMQP-CPP library. This method returns + * the incoming data, and let it handle by the AMQP-CPP library. This method returns * the number of bytes that were processed. * - * If not all bytes could be processed because it only contained a partial frame, - * you should call this same method later on when more data is available. The - * AMQP-CPP library does not do any buffering, so it is up to the caller to ensure + * If not all bytes could be processed because it only contained a partial frame, + * you should call this same method later on when more data is available. The + * AMQP-CPP library does not do any buffering, so it is up to the caller to ensure * that the old data is also passed in that later call. * * @param buffer buffer to decode @@ -178,10 +178,10 @@ size_t parse(char *buffer, size_t size) } ```` -You should do all the book keeping for the buffer yourselves. If you for example -call the Connection::parse() method with a buffer of 100 bytes, and the method -returns that only 60 bytes were processed, you should later call the method again, -with a buffer filled with the remaining 40 bytes. If the method returns 0, you should +You should do all the book keeping for the buffer yourselves. If you for example +call the Connection::parse() method with a buffer of 100 bytes, and the method +returns that only 60 bytes were processed, you should later call the method again, +with a buffer filled with the remaining 40 bytes. If the method returns 0, you should make a new call to parse() when more data is available, with a buffer that contains both the old data, and the new data. @@ -189,8 +189,8 @@ both the old data, and the new data. CHANNELS ======== -In the example we created a channel object. A channel is a virtual connection over -a single TCP connection, and it is possible to create many channels that all use +In the example we created a channel object. A channel is a virtual connection over +a single TCP connection, and it is possible to create many channels that all use the same TCP connection. AMQP instructions are always sent over a channel, so before you can send the first @@ -203,21 +203,21 @@ documented. The constructor of the Channel object accepts one parameter: the connection object. Unlike the connection it does not accept a handler. Instead of a handler object, -(almost) every method of the Channel class returns an instance of the 'Deferred' -class. This object can be used to install handlers that will be called in case +(almost) every method of the Channel class returns an instance of the 'Deferred' +class. This object can be used to install handlers that will be called in case of success or failure. -For example, if you call the channel.declareExchange() method, the AMQP-CPP library +For example, if you call the channel.declareExchange() method, the AMQP-CPP library will send a message to the RabbitMQ message broker to ask it to declare the queue. However, because all operations in the library are asynchronous, the declareExchange() method can not return 'true' or 'false' to inform you whether -the operation was succesful or not. Only after a while, after the instruction -has reached the RabbitMQ server, and the confirmation from the server has been +the operation was succesful or not. Only after a while, after the instruction +has reached the RabbitMQ server, and the confirmation from the server has been sent back to the client, the library can report the result of the declareExchange() call. -To prevent any blocking calls, the channel.declareExchange() method returns a -'Deferred' result object, on which you can set callback functions that will be +To prevent any blocking calls, the channel.declareExchange() method returns a +'Deferred' result object, on which you can set callback functions that will be called when the operation succeeds or fails. ````c++ @@ -249,9 +249,9 @@ run in either case: when the operation succeeds or when it fails. The signature for the onError() method is always the same: it gets one parameter with a human readable error message. The onSuccess() function has a different -signature depending on the method that you call. Most onSuccess() functions -(like the one we showed for the declareExchange() method) do not get any -parameters at all. Some specific onSuccess callbacks receive extra parameters +signature depending on the method that you call. Most onSuccess() functions +(like the one we showed for the declareExchange() method) do not get any +parameters at all. Some specific onSuccess callbacks receive extra parameters with additional information. @@ -287,7 +287,7 @@ myChannel.onReady([]() { }); ```` -In theory, you should wait for the onReady() callback to be called before you +In theory, you should wait for the onReady() callback to be called before you send any other instructions over the channel. In practice however, the AMQP library caches all instructions that were sent too early, so that you can use the channel object right after it was constructed. @@ -307,10 +307,10 @@ myChannel.declareQueue("my-queue"); myChannel.declareExchange("my-exchange"); ```` -If the first declareQueue() call fails in the example above, the second +If the first declareQueue() call fails in the example above, the second myChannel.declareExchange() method will not be executed, even when this second instruction was already sent to the server. The second instruction will be -ignored by the RabbitMQ server because the channel was already in an invalid +ignored by the RabbitMQ server because the channel was already in an invalid state after the first failure. You can overcome this by using multiple channels: @@ -322,10 +322,10 @@ channel1.declareQueue("my-queue"); channel2.declareExchange("my-exchange"); ```` -Now, if an error occurs with declaring the queue, it will not have consequences -for the other call. But this comes at a small price: setting up the extra channel -requires and extra instruction to be sent to the RabbitMQ server, so some extra -bytes are sent over the network, and some additional resources in both the client +Now, if an error occurs with declaring the queue, it will not have consequences +for the other call. But this comes at a small price: setting up the extra channel +requires and extra instruction to be sent to the RabbitMQ server, so some extra +bytes are sent over the network, and some additional resources in both the client application and the RabbitMQ server are used (although this is all very limited). @@ -385,9 +385,9 @@ channel.declareQueue("myQueue").onSuccess(callback); ```` Just like many others methods in the Channel class, the declareQueue() method -accept an integer parameter named 'flags'. This is a variable in which you can -set method-specific options, by summing up all the options that are described in -the documentation above the method. If you for example want to create a durable, +accept an integer parameter named 'flags'. This is a variable in which you can +set method-specific options, by summing up all the options that are described in +the documentation above the method. If you for example want to create a durable, auto-deleted queue, you can pass in the value AMQP::durable + AMQP::autodelete. The declareQueue() method also accepts a parameter named 'arguments', which is of type @@ -421,7 +421,7 @@ exchange to publish to, the routing key to use, and the actual message that you're publishing - all these parameters are standard C++ strings. More extended versions of the publish() method exist that accept additional -arguments, and that enable you to publish entire Envelope objects. An envelope +arguments, and that enable you to publish entire Envelope objects. An envelope is an object that contains the message plus a list of optional meta information like the content-type, content-encoding, priority, expire time and more. None of these meta fields are interpreted by this library, and also the RabbitMQ ignores most @@ -441,10 +441,10 @@ in almost any form: * * The following flags can be used * - * - mandatory if set, an unroutable message will be reported to the + * - mandatory if set, an unroutable message will be reported to the * channel handler with the onReturned method - * - * - immediate if set, a message that could not immediately be consumed + * + * - immediate if set, a message that could not immediately be consumed * is returned to the onReturned method * * If either of the two flags is set, and the message could not immediately @@ -469,17 +469,17 @@ bool publish(const std::string &exchange, const std::string &routingKey, const c Published messages are normally not confirmed by the server, and the RabbitMQ will not send a report back to inform us whether the message was succesfully -published or not. Therefore the publish method does also not return a Deferred +published or not. Therefore the publish method does also not return a Deferred object. -As long as no error is reported via the Channel::onError() method, you can safely +As long as no error is reported via the Channel::onError() method, you can safely assume that your messages were delivered. -This can of course be a problem when you are publishing many messages. If you get -an error halfway through there is no way to know for sure how many messages made -it to the broker and how many should be republished. If this is important, you can +This can of course be a problem when you are publishing many messages. If you get +an error halfway through there is no way to know for sure how many messages made +it to the broker and how many should be republished. If this is important, you can wrap the publish commands inside a transaction. In this case, if an error occurs, -the transaction is automatically rolled back by RabbitMQ and none of the messages +the transaction is automatically rolled back by RabbitMQ and none of the messages are actually published. ````c++ @@ -531,21 +531,17 @@ The full documentation from the C++ Channel.h headerfile looks like this: * * The following flags are supported: * - * - nolocal if set, messages published on this channel are + * - nolocal if set, messages published on this channel are * not also consumed * - * - noack if set, consumed messages do not have to be acked, + * - noack if set, consumed messages do not have to be acked, * this happens automatically * - * - exclusive request exclusive access, only this consumer can + * - exclusive request exclusive access, only this consumer can * access the queue * - * - nowait the server does not have to send a response back - * that consuming is active - * * The method ChannelHandler::onConsumerStarted() will be called when the - * consumer has started (unless the nowait option was set, in which case - * no confirmation method is called) + * consumer has started. * * @param queue the queue from which you want to consume * @param tag a consumer tag that will be associated with this consume operation @@ -561,14 +557,14 @@ DeferredConsumer &consume(const std::string &queue, int flags = 0); DeferredConsumer &consume(const std::string &queue, const AMQP::Table &arguments); ```` -As you can see, the consume method returns a DeferredConsumer. This object is a -regular Deferred, with additions. The onSuccess() method of a +As you can see, the consume method returns a DeferredConsumer. This object is a +regular Deferred, with additions. The onSuccess() method of a DeferredConsumer is slightly different than the onSuccess() method of a regular Deferred object: one extra parameter will be supplied to your callback function with the consumer tag. The onSuccess() callback will be called when the consume operation _has started_, -but not when messages are actually consumed. For this you will have to install +but not when messages are actually consumed. For this you will have to install a different callback, using the onReceived() method. ````c++ @@ -588,7 +584,7 @@ auto errorCb = [](const char *message) { auto messageCb = [&channel](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) { std::cout << "message received" << std::endl; - + // acknowledge the message channel.ack(deliveryTag); } @@ -601,33 +597,33 @@ channel.consume("my-queue") ```` -The Message object holds all information of the delivered message: the actual -content, all meta information from the envelope (in fact, the Message class is -derived from the Envelope class), and even the name of the exchange and the -routing key that were used when the message was originally published. For a full +The Message object holds all information of the delivered message: the actual +content, all meta information from the envelope (in fact, the Message class is +derived from the Envelope class), and even the name of the exchange and the +routing key that were used when the message was originally published. For a full list of all information in the Message class, you best have a look at the message.h, envelope.h and metadata.h header files. -Another important parameter to the onReceived() method is the deliveryTag parameter. -This is a unique identifier that you need to acknowledge an incoming message. -RabbitMQ only removes the message after it has been acknowledged, so that if your -application crashes while it was busy processing the message, the message does -not get lost but remains in the queue. But this means that after you've processed -the message, you must inform RabbitMQ about it by calling the Channel:ack() method. +Another important parameter to the onReceived() method is the deliveryTag parameter. +This is a unique identifier that you need to acknowledge an incoming message. +RabbitMQ only removes the message after it has been acknowledged, so that if your +application crashes while it was busy processing the message, the message does +not get lost but remains in the queue. But this means that after you've processed +the message, you must inform RabbitMQ about it by calling the Channel:ack() method. This method is very simple and takes in its simplest form only one parameter: the deliveryTag of the message. -Consuming messages is a continuous process. RabbitMQ keeps sending messages, until -you stop the consumer, which can be done by calling the Channel::cancel() method. +Consuming messages is a continuous process. RabbitMQ keeps sending messages, until +you stop the consumer, which can be done by calling the Channel::cancel() method. If you close the channel, or the entire TCP connection, consuming also stops. -RabbitMQ throttles the number of messages that are delivered to you, to prevent -that your application is flooded with messages from the queue, and to spread out -the messages over multiple consumers. This is done with a setting called -quality-of-service (QOS). The QOS setting is a numeric value which holds the number +RabbitMQ throttles the number of messages that are delivered to you, to prevent +that your application is flooded with messages from the queue, and to spread out +the messages over multiple consumers. This is done with a setting called +quality-of-service (QOS). The QOS setting is a numeric value which holds the number of unacknowledged messages that you are allowed to have. RabbitMQ stops sending -additional messages when the number of unacknowledges messages has reached this -limit, and only sends additional messages when an earlier message gets acknowledged. +additional messages when the number of unacknowledges messages has reached this +limit, and only sends additional messages when an earlier message gets acknowledged. To change the QOS, you can simple call Channel::setQos(). From ca8a39ea4542b3885023e32a0a7f85f2db626300 Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Tue, 6 May 2014 16:49:43 +0200 Subject: [PATCH 39/41] Fix memory leak --- include/deferred.h | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/include/deferred.h b/include/deferred.h index d8fd257..01a3fe8 100644 --- a/include/deferred.h +++ b/include/deferred.h @@ -72,11 +72,11 @@ protected: // execute callbacks if registered if (_successCallback) _successCallback(); if (_finalizeCallback) _finalizeCallback(); - + // return the next deferred result return _next; } - + /** * Report success for queue declared messages * @param name Name of the new queue @@ -89,7 +89,7 @@ protected: // this is the same as a regular success message return reportSuccess(); } - + /** * Report success for frames that report delete operations * @param messagecount Number of messages that were deleted @@ -100,7 +100,7 @@ protected: // this is the same as a regular success message return reportSuccess(); } - + /** * Report success for frames that report cancel operations * @param name Consumer tag that is cancelled @@ -117,15 +117,15 @@ protected: * @param error Description of the error that occured * @return Deferred Next deferred result */ - Deferred *reportError(const char *error) + Deferred *reportError(const char *error) { // from this moment on the object should be listed as failed _failed = true; - + // execute callbacks if registered if (_errorCallback) _errorCallback(error); if (_finalizeCallback) _finalizeCallback(); - + // return the next deferred result return _next; } @@ -146,7 +146,7 @@ protected: */ friend class ChannelImpl; friend class Callbacks; - + protected: /** * Protected constructor that can only be called @@ -163,6 +163,11 @@ public: Deferred(const Deferred &that) = delete; Deferred(Deferred &&that) = delete; + /** + * Destructor + */ + virtual ~Deferred() {} + /** * Cast to a boolean */ @@ -186,7 +191,7 @@ public: { // store callback _successCallback = callback; - + // allow chaining return *this; } @@ -208,7 +213,7 @@ public: // if the object is already in a failed state, we call the callback right away if (_failed) callback("Frame could not be sent"); - + // allow chaining return *this; } @@ -232,10 +237,10 @@ public: { // store callback _finalizeCallback = callback; - + // if the object is already in a failed state, we call the callback right away if (_failed) callback(); - + // allow chaining return *this; } From 3a700226c8b18fff7c0f6dadc4b7885f9fe52c37 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 26 May 2014 18:17:49 +0200 Subject: [PATCH 40/41] BasicNack frame makes the consumer stop (dont know why) so we use the BackReject message instead when there is no need to reject multiple messages --- src/channelimpl.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 0b917f4..0f6171d 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -36,6 +36,7 @@ #include "basicackframe.h" #include "basicnackframe.h" #include "basicrecoverframe.h" +#include "basicrejectframe.h" /** * Set up namespace @@ -555,8 +556,17 @@ bool ChannelImpl::ack(uint64_t deliveryTag, int flags) */ bool ChannelImpl::reject(uint64_t deliveryTag, int flags) { - // send a nack frame - return send(BasicNackFrame(_id, deliveryTag, flags & multiple, flags & requeue)); + // should we reject multiple messages? + if (flags & multiple) + { + // send a nack frame + return send(BasicNackFrame(_id, deliveryTag, true, flags & requeue)); + } + else + { + // send a reject frame + return send(BasicRejectFrame(_id, deliveryTag, flags & requeue)); + } } /** From 850252e1ddce9ac05569f7a3fb7436c31d927ac2 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 25 Jul 2014 09:34:01 +0200 Subject: [PATCH 41/41] link time optimization only for static libs --- src/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 4f0baa2..c9330b9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,6 @@ CPP = g++ RM = rm -f -CPPFLAGS = -Wall -c -I. -g -flto -std=c++11 -g +CPPFLAGS = -Wall -c -I. -g -std=c++11 -g LD = g++ LD_FLAGS = -Wall -shared -O2 SHARED_LIB = libamqpcpp.so @@ -25,7 +25,7 @@ clean: ${RM} *.obj *~* ${SHARED_OBJECTS} ${STATIC_OBJECTS} ${SHARED_LIB} ${STATIC_LIB} ${SHARED_OBJECTS}: - ${CPP} ${CPPFLAGS} -fpic -o $@ ${@:%.o=%.cpp} + ${CPP} ${CPPFLAGS} -flto -fpic -o $@ ${@:%.o=%.cpp} ${STATIC_OBJECTS}: ${CPP} ${CPPFLAGS} -o $@ ${@:%.s.o=%.cpp}