add preliminary support for publisher acknowledgements
RabbitMQ supports publish confirms (Publisher Acknowledgements) on a given channel. This enables the user to toggle this functionality and ensure that published messages are in fact published.
This commit is contained in:
parent
8be77044ef
commit
f2ac01de34
|
|
@ -12,9 +12,9 @@ class QAmqpChannelPrivate : public QAmqpMethodFrameHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum MethodId {
|
enum MethodId {
|
||||||
METHOD_ID_ENUM(miOpen, 10),
|
METHOD_ID_ENUM(miOpen, 10),
|
||||||
METHOD_ID_ENUM(miFlow, 20),
|
METHOD_ID_ENUM(miFlow, 20),
|
||||||
METHOD_ID_ENUM(miClose, 40)
|
METHOD_ID_ENUM(miClose, 40)
|
||||||
};
|
};
|
||||||
|
|
||||||
enum BasicMethod {
|
enum BasicMethod {
|
||||||
|
|
@ -29,7 +29,8 @@ public:
|
||||||
bmAck = 80,
|
bmAck = 80,
|
||||||
bmReject = 90,
|
bmReject = 90,
|
||||||
bmRecoverAsync = 100,
|
bmRecoverAsync = 100,
|
||||||
METHOD_ID_ENUM(bmRecover, 110)
|
METHOD_ID_ENUM(bmRecover, 110),
|
||||||
|
bmNack = 120
|
||||||
};
|
};
|
||||||
|
|
||||||
QAmqpChannelPrivate(QAmqpChannel *q);
|
QAmqpChannelPrivate(QAmqpChannel *q);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
#include <QEventLoop>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
|
#include <QTimer>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include "qamqpexchange.h"
|
#include "qamqpexchange.h"
|
||||||
|
|
@ -22,7 +24,8 @@ QString QAmqpExchangePrivate::typeToString(QAmqpExchange::ExchangeType type)
|
||||||
QAmqpExchangePrivate::QAmqpExchangePrivate(QAmqpExchange *q)
|
QAmqpExchangePrivate::QAmqpExchangePrivate(QAmqpExchange *q)
|
||||||
: QAmqpChannelPrivate(q),
|
: QAmqpChannelPrivate(q),
|
||||||
delayedDeclare(false),
|
delayedDeclare(false),
|
||||||
declared(false)
|
declared(false),
|
||||||
|
nextDeliveryTag(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,29 +61,36 @@ void QAmqpExchangePrivate::declare()
|
||||||
|
|
||||||
bool QAmqpExchangePrivate::_q_method(const QAmqpMethodFrame &frame)
|
bool QAmqpExchangePrivate::_q_method(const QAmqpMethodFrame &frame)
|
||||||
{
|
{
|
||||||
|
Q_Q(QAmqpExchange);
|
||||||
if (QAmqpChannelPrivate::_q_method(frame))
|
if (QAmqpChannelPrivate::_q_method(frame))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (frame.methodClass() == QAmqpFrame::Exchange) {
|
if (frame.methodClass() == QAmqpFrame::Basic) {
|
||||||
switch (frame.id()) {
|
switch (frame.id()) {
|
||||||
case miDeclareOk:
|
case bmAck:
|
||||||
declareOk(frame);
|
case bmNack:
|
||||||
break;
|
handleAckOrNack(frame);
|
||||||
|
|
||||||
case miDeleteOk:
|
|
||||||
deleteOk(frame);
|
|
||||||
break;
|
break;
|
||||||
|
case bmReturn: basicReturn(frame); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (frame.methodClass() == QAmqpFrame::Basic) {
|
}
|
||||||
|
|
||||||
|
if (frame.methodClass() == QAmqpFrame::Confirm) {
|
||||||
|
if (frame.id() == cmConfirmOk) {
|
||||||
|
Q_EMIT q->confirmsEnabled();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.methodClass() == QAmqpFrame::Exchange) {
|
||||||
switch (frame.id()) {
|
switch (frame.id()) {
|
||||||
case bmReturn:
|
case miDeclareOk: declareOk(frame); break;
|
||||||
basicReturn(frame);
|
case miDeleteOk: deleteOk(frame); break;
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
@ -145,6 +155,39 @@ void QAmqpExchangePrivate::basicReturn(const QAmqpMethodFrame &frame)
|
||||||
qAmqpDebug(">> routingKey: %s", qPrintable(routingKey));
|
qAmqpDebug(">> routingKey: %s", qPrintable(routingKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QAmqpExchangePrivate::handleAckOrNack(const QAmqpMethodFrame &frame)
|
||||||
|
{
|
||||||
|
Q_Q(QAmqpExchange);
|
||||||
|
QByteArray data = frame.arguments();
|
||||||
|
QDataStream stream(&data, QIODevice::ReadOnly);
|
||||||
|
|
||||||
|
qlonglong deliveryTag =
|
||||||
|
QAmqpFrame::readAmqpField(stream, QAmqpMetaType::LongLongUint).toLongLong();
|
||||||
|
bool multiple = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::Boolean).toBool();
|
||||||
|
if (frame.id() == QAmqpExchangePrivate::bmAck) {
|
||||||
|
if (deliveryTag == 0) {
|
||||||
|
unconfirmedDeliveryTags.clear();
|
||||||
|
} else {
|
||||||
|
int idx = unconfirmedDeliveryTags.indexOf(deliveryTag);
|
||||||
|
if (idx == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multiple) {
|
||||||
|
unconfirmedDeliveryTags.remove(0, idx + 1);
|
||||||
|
} else {
|
||||||
|
unconfirmedDeliveryTags.remove(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unconfirmedDeliveryTags.isEmpty())
|
||||||
|
Q_EMIT q->allMessagesDelivered();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qAmqpDebug() << "nacked(" << deliveryTag << "), multiple=" << multiple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
QAmqpExchange::QAmqpExchange(int channelNumber, QAmqpClient *parent)
|
QAmqpExchange::QAmqpExchange(int channelNumber, QAmqpClient *parent)
|
||||||
|
|
@ -231,6 +274,11 @@ void QAmqpExchange::publish(const QByteArray &message, const QString &routingKey
|
||||||
const QAmqpMessage::PropertyHash &properties, int publishOptions)
|
const QAmqpMessage::PropertyHash &properties, int publishOptions)
|
||||||
{
|
{
|
||||||
Q_D(QAmqpExchange);
|
Q_D(QAmqpExchange);
|
||||||
|
if (d->nextDeliveryTag > 0) {
|
||||||
|
d->unconfirmedDeliveryTags.append(d->nextDeliveryTag);
|
||||||
|
d->nextDeliveryTag++;
|
||||||
|
}
|
||||||
|
|
||||||
QAmqpMethodFrame frame(QAmqpFrame::Basic, QAmqpExchangePrivate::bmPublish);
|
QAmqpMethodFrame frame(QAmqpFrame::Basic, QAmqpExchangePrivate::bmPublish);
|
||||||
frame.setChannel(d->channelNumber);
|
frame.setChannel(d->channelNumber);
|
||||||
|
|
||||||
|
|
@ -268,3 +316,32 @@ void QAmqpExchange::publish(const QByteArray &message, const QString &routingKey
|
||||||
d->sendFrame(body);
|
d->sendFrame(body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QAmqpExchange::enableConfirms(bool noWait)
|
||||||
|
{
|
||||||
|
Q_D(QAmqpExchange);
|
||||||
|
QAmqpMethodFrame frame(QAmqpFrame::Confirm, QAmqpExchangePrivate::cmConfirm);
|
||||||
|
frame.setChannel(d->channelNumber);
|
||||||
|
|
||||||
|
QByteArray arguments;
|
||||||
|
QDataStream stream(&arguments, QIODevice::WriteOnly);
|
||||||
|
stream << qint8(noWait ? 1 : 0);
|
||||||
|
|
||||||
|
frame.setArguments(arguments);
|
||||||
|
d->sendFrame(frame);
|
||||||
|
|
||||||
|
// for tracking acks and nacks
|
||||||
|
if (d->nextDeliveryTag == 0) d->nextDeliveryTag = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QAmqpExchange::waitForConfirms(int msecs)
|
||||||
|
{
|
||||||
|
Q_D(QAmqpExchange);
|
||||||
|
|
||||||
|
QEventLoop loop;
|
||||||
|
connect(this, SIGNAL(allMessagesDelivered()), &loop, SLOT(quit()));
|
||||||
|
QTimer::singleShot(msecs, &loop, SLOT(quit()));
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
return (d->unconfirmedDeliveryTags.isEmpty());
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ class QAMQP_EXPORT QAmqpExchange : public QAmqpChannel
|
||||||
Q_ENUMS(ExchangeOptions)
|
Q_ENUMS(ExchangeOptions)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
virtual ~QAmqpExchange();
|
||||||
|
|
||||||
enum ExchangeType {
|
enum ExchangeType {
|
||||||
Direct,
|
Direct,
|
||||||
FanOut,
|
FanOut,
|
||||||
|
|
@ -50,13 +52,11 @@ public:
|
||||||
Q_DECLARE_FLAGS(ExchangeOptions, ExchangeOption)
|
Q_DECLARE_FLAGS(ExchangeOptions, ExchangeOption)
|
||||||
ExchangeOptions options() const;
|
ExchangeOptions options() const;
|
||||||
|
|
||||||
virtual ~QAmqpExchange();
|
|
||||||
|
|
||||||
// AMQP Exchange
|
// AMQP Exchange
|
||||||
void declare(ExchangeType type = Direct,
|
void declare(ExchangeType type = Direct,
|
||||||
ExchangeOptions options = NoOptions,
|
ExchangeOptions options = NoOptions,
|
||||||
const QAmqpTable &args = QAmqpTable());
|
const QAmqpTable &args = QAmqpTable());
|
||||||
void declare(const QString &type = QLatin1String("direct"),
|
void declare(const QString &type,
|
||||||
ExchangeOptions options = NoOptions,
|
ExchangeOptions options = NoOptions,
|
||||||
const QAmqpTable &args = QAmqpTable());
|
const QAmqpTable &args = QAmqpTable());
|
||||||
void remove(int options = roIfUnused|roNoWait);
|
void remove(int options = roIfUnused|roNoWait);
|
||||||
|
|
@ -73,10 +73,16 @@ public:
|
||||||
const QAmqpMessage::PropertyHash &properties = QAmqpMessage::PropertyHash(),
|
const QAmqpMessage::PropertyHash &properties = QAmqpMessage::PropertyHash(),
|
||||||
int publishOptions = poNoOptions);
|
int publishOptions = poNoOptions);
|
||||||
|
|
||||||
|
void enableConfirms(bool noWait = false);
|
||||||
|
bool waitForConfirms(int msecs = 30000);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void declared();
|
void declared();
|
||||||
void removed();
|
void removed();
|
||||||
|
|
||||||
|
void confirmsEnabled();
|
||||||
|
void allMessagesDelivered();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void channelOpened();
|
virtual void channelOpened();
|
||||||
virtual void channelClosed();
|
virtual void channelClosed();
|
||||||
|
|
@ -86,7 +92,6 @@ private:
|
||||||
|
|
||||||
Q_DISABLE_COPY(QAmqpExchange)
|
Q_DISABLE_COPY(QAmqpExchange)
|
||||||
Q_DECLARE_PRIVATE(QAmqpExchange)
|
Q_DECLARE_PRIVATE(QAmqpExchange)
|
||||||
|
|
||||||
friend class QAmqpClient;
|
friend class QAmqpClient;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ public:
|
||||||
METHOD_ID_ENUM(miDelete, 20)
|
METHOD_ID_ENUM(miDelete, 20)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ConfirmMethod {
|
||||||
|
METHOD_ID_ENUM(cmConfirm, 10)
|
||||||
|
};
|
||||||
|
|
||||||
QAmqpExchangePrivate(QAmqpExchange *q);
|
QAmqpExchangePrivate(QAmqpExchange *q);
|
||||||
static QString typeToString(QAmqpExchange::ExchangeType type);
|
static QString typeToString(QAmqpExchange::ExchangeType type);
|
||||||
|
|
||||||
|
|
@ -24,12 +28,15 @@ public:
|
||||||
void declareOk(const QAmqpMethodFrame &frame);
|
void declareOk(const QAmqpMethodFrame &frame);
|
||||||
void deleteOk(const QAmqpMethodFrame &frame);
|
void deleteOk(const QAmqpMethodFrame &frame);
|
||||||
void basicReturn(const QAmqpMethodFrame &frame);
|
void basicReturn(const QAmqpMethodFrame &frame);
|
||||||
|
void handleAckOrNack(const QAmqpMethodFrame &frame);
|
||||||
|
|
||||||
QString type;
|
QString type;
|
||||||
QAmqpExchange::ExchangeOptions options;
|
QAmqpExchange::ExchangeOptions options;
|
||||||
QAmqpTable arguments;
|
QAmqpTable arguments;
|
||||||
bool delayedDeclare;
|
bool delayedDeclare;
|
||||||
bool declared;
|
bool declared;
|
||||||
|
qlonglong nextDeliveryTag;
|
||||||
|
QVector<qlonglong> unconfirmedDeliveryTags;
|
||||||
|
|
||||||
Q_DECLARE_PUBLIC(QAmqpExchange)
|
Q_DECLARE_PUBLIC(QAmqpExchange)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ public:
|
||||||
Exchange = 40,
|
Exchange = 40,
|
||||||
Queue = 50,
|
Queue = 50,
|
||||||
Basic = 60,
|
Basic = 60,
|
||||||
Tx = 90,
|
Confirm = 85,
|
||||||
|
Tx = 90
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ~QAmqpFrame();
|
virtual ~QAmqpFrame();
|
||||||
|
|
|
||||||
|
|
@ -502,6 +502,11 @@ void QAmqpQueue::get(bool noAck)
|
||||||
}
|
}
|
||||||
|
|
||||||
void QAmqpQueue::ack(const QAmqpMessage &message)
|
void QAmqpQueue::ack(const QAmqpMessage &message)
|
||||||
|
{
|
||||||
|
ack(message.deliveryTag(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QAmqpQueue::ack(qlonglong deliveryTag, bool multiple)
|
||||||
{
|
{
|
||||||
Q_D(QAmqpQueue);
|
Q_D(QAmqpQueue);
|
||||||
if (!d->opened) {
|
if (!d->opened) {
|
||||||
|
|
@ -515,8 +520,8 @@ void QAmqpQueue::ack(const QAmqpMessage &message)
|
||||||
QByteArray arguments;
|
QByteArray arguments;
|
||||||
QDataStream out(&arguments, QIODevice::WriteOnly);
|
QDataStream out(&arguments, QIODevice::WriteOnly);
|
||||||
|
|
||||||
out << message.deliveryTag();
|
out << deliveryTag;
|
||||||
out << qint8(0); // multiple
|
out << qint8(multiple ? 1 : 0); // multiple
|
||||||
|
|
||||||
frame.setArguments(arguments);
|
frame.setArguments(arguments);
|
||||||
d->sendFrame(frame);
|
d->sendFrame(frame);
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ public:
|
||||||
~QAmqpQueue();
|
~QAmqpQueue();
|
||||||
|
|
||||||
bool isConsuming() const;
|
bool isConsuming() const;
|
||||||
|
|
||||||
void setConsumerTag(const QString &consumerTag);
|
void setConsumerTag(const QString &consumerTag);
|
||||||
QString consumerTag() const;
|
QString consumerTag() const;
|
||||||
|
|
||||||
|
|
@ -67,9 +68,11 @@ public:
|
||||||
// AMQP Basic
|
// AMQP Basic
|
||||||
bool consume(int options = NoOptions);
|
bool consume(int options = NoOptions);
|
||||||
void get(bool noAck = true);
|
void get(bool noAck = true);
|
||||||
void ack(const QAmqpMessage &message);
|
|
||||||
bool cancel(bool noWait = false);
|
bool cancel(bool noWait = false);
|
||||||
|
|
||||||
|
void ack(const QAmqpMessage &message);
|
||||||
|
void ack(qlonglong deliveryTag, bool multiple);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void declared();
|
void declared();
|
||||||
void bound();
|
void bound();
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ private Q_SLOTS:
|
||||||
void removeIfUnused();
|
void removeIfUnused();
|
||||||
void invalidMandatoryRouting();
|
void invalidMandatoryRouting();
|
||||||
void invalidImmediateRouting();
|
void invalidImmediateRouting();
|
||||||
|
void confirmsSupport();
|
||||||
|
void confirmDontLoseMessages();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QScopedPointer<QAmqpClient> client;
|
QScopedPointer<QAmqpClient> client;
|
||||||
|
|
@ -167,5 +169,26 @@ void tst_QAMQPExchange::invalidImmediateRouting()
|
||||||
QCOMPARE(client->error(), QAMQP::NotImplementedError);
|
QCOMPARE(client->error(), QAMQP::NotImplementedError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QAMQPExchange::confirmsSupport()
|
||||||
|
{
|
||||||
|
QAmqpExchange *exchange = client->createExchange("confirm-test");
|
||||||
|
exchange->enableConfirms();
|
||||||
|
QVERIFY(waitForSignal(exchange, SIGNAL(confirmsEnabled())));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QAMQPExchange::confirmDontLoseMessages()
|
||||||
|
{
|
||||||
|
QAmqpExchange *defaultExchange = client->createExchange();
|
||||||
|
defaultExchange->enableConfirms();
|
||||||
|
QVERIFY(waitForSignal(defaultExchange, SIGNAL(confirmsEnabled())));
|
||||||
|
|
||||||
|
QAmqpMessage::PropertyHash properties;
|
||||||
|
properties[QAmqpMessage::DeliveryMode] = "2"; // make message persistent
|
||||||
|
|
||||||
|
for (int i = 0; i < 10000; ++i)
|
||||||
|
defaultExchange->publish("noop", "confirms-test", properties);
|
||||||
|
QVERIFY(defaultExchange->waitForConfirms());
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QAMQPExchange)
|
QTEST_MAIN(tst_QAMQPExchange)
|
||||||
#include "tst_qamqpexchange.moc"
|
#include "tst_qamqpexchange.moc"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue