2020-05-20 09:40:49 +00:00
|
|
|
#include <utility>
|
2020-05-26 17:34:57 +00:00
|
|
|
#include <chrono>
|
|
|
|
#include <thread>
|
2020-05-31 08:39:22 +00:00
|
|
|
#include <mutex>
|
|
|
|
#include <atomic>
|
|
|
|
#include <memory>
|
2020-05-20 09:40:49 +00:00
|
|
|
#include <Storages/RabbitMQ/ReadBufferFromRabbitMQConsumer.h>
|
|
|
|
#include <Storages/RabbitMQ/RabbitMQHandler.h>
|
2020-06-13 21:37:37 +00:00
|
|
|
#include <boost/algorithm/string/split.hpp>
|
2020-05-20 09:40:49 +00:00
|
|
|
#include <common/logger_useful.h>
|
2020-06-07 11:14:05 +00:00
|
|
|
#include "Poco/Timer.h"
|
2020-05-20 09:40:49 +00:00
|
|
|
#include <amqpcpp.h>
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
2020-06-11 20:05:35 +00:00
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int BAD_ARGUMENTS;
|
|
|
|
}
|
2020-06-08 01:11:48 +00:00
|
|
|
|
2020-06-11 10:56:40 +00:00
|
|
|
namespace Exchange
|
|
|
|
{
|
|
|
|
/// Note that default here means default by implementation and not by rabbitmq settings
|
|
|
|
static const String DEFAULT = "default";
|
|
|
|
static const String FANOUT = "fanout";
|
|
|
|
static const String DIRECT = "direct";
|
|
|
|
static const String TOPIC = "topic";
|
|
|
|
static const String HASH = "consistent_hash";
|
2020-06-11 20:05:35 +00:00
|
|
|
static const String HEADERS = "headers";
|
2020-06-11 10:56:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-20 09:40:49 +00:00
|
|
|
ReadBufferFromRabbitMQConsumer::ReadBufferFromRabbitMQConsumer(
|
2020-05-29 16:04:44 +00:00
|
|
|
ChannelPtr consumer_channel_,
|
|
|
|
RabbitMQHandler & eventHandler_,
|
2020-05-20 09:40:49 +00:00
|
|
|
const String & exchange_name_,
|
2020-06-11 09:23:23 +00:00
|
|
|
const Names & routing_keys_,
|
2020-05-20 09:40:49 +00:00
|
|
|
const size_t channel_id_,
|
|
|
|
Poco::Logger * log_,
|
|
|
|
char row_delimiter_,
|
|
|
|
const bool bind_by_id_,
|
|
|
|
const size_t num_queues_,
|
2020-06-10 23:01:47 +00:00
|
|
|
const String & exchange_type_,
|
2020-06-13 18:15:59 +00:00
|
|
|
const String & local_exchange_name_,
|
2020-05-20 09:40:49 +00:00
|
|
|
const std::atomic<bool> & stopped_)
|
|
|
|
: ReadBuffer(nullptr, 0)
|
2020-05-29 16:04:44 +00:00
|
|
|
, consumer_channel(std::move(consumer_channel_))
|
|
|
|
, eventHandler(eventHandler_)
|
2020-05-20 09:40:49 +00:00
|
|
|
, exchange_name(exchange_name_)
|
2020-06-11 09:23:23 +00:00
|
|
|
, routing_keys(routing_keys_)
|
2020-05-20 09:40:49 +00:00
|
|
|
, channel_id(channel_id_)
|
|
|
|
, log(log_)
|
|
|
|
, row_delimiter(row_delimiter_)
|
|
|
|
, bind_by_id(bind_by_id_)
|
|
|
|
, num_queues(num_queues_)
|
2020-06-10 23:01:47 +00:00
|
|
|
, exchange_type(exchange_type_)
|
2020-06-13 18:15:59 +00:00
|
|
|
, local_exchange_name(local_exchange_name_)
|
2020-05-20 09:40:49 +00:00
|
|
|
, stopped(stopped_)
|
|
|
|
{
|
|
|
|
messages.clear();
|
|
|
|
current = messages.begin();
|
|
|
|
|
2020-06-11 20:05:35 +00:00
|
|
|
exchange_type_set = exchange_type != Exchange::DEFAULT;
|
2020-06-10 23:01:47 +00:00
|
|
|
|
2020-05-20 09:40:49 +00:00
|
|
|
/* One queue per consumer can handle up to 50000 messages. More queues per consumer can be added.
|
|
|
|
* By default there is one queue per consumer.
|
|
|
|
*/
|
|
|
|
for (size_t queue_id = 0; queue_id < num_queues; ++queue_id)
|
|
|
|
{
|
2020-06-10 23:01:47 +00:00
|
|
|
/// Queue bingings must be declared before any publishing => it must be done here and not in readPrefix()
|
2020-05-20 09:40:49 +00:00
|
|
|
initQueueBindings(queue_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ReadBufferFromRabbitMQConsumer::~ReadBufferFromRabbitMQConsumer()
|
|
|
|
{
|
2020-05-29 16:04:44 +00:00
|
|
|
consumer_channel->close();
|
2020-05-20 09:40:49 +00:00
|
|
|
|
|
|
|
messages.clear();
|
|
|
|
current = messages.begin();
|
|
|
|
BufferBase::set(nullptr, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ReadBufferFromRabbitMQConsumer::initExchange()
|
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
/* This direct-exchange is used for default implemenation and for INSERT query (so it is always declared). If exchange_type
|
|
|
|
* is not set, then there are only two exchanges - external, defined by the client, and local, unique for each table.
|
|
|
|
* This strict division to external and local exchanges is needed to avoid too much complexity with defining exchange_name
|
|
|
|
* for INSERT query producer and, in general, it is much better to distinguish them into separate ones.
|
2020-05-20 09:40:49 +00:00
|
|
|
*/
|
2020-06-13 18:15:59 +00:00
|
|
|
String default_exchange = exchange_type_set ? exchange_name + "_" + Exchange::DEFAULT : exchange_name;
|
|
|
|
consumer_channel->declareExchange(default_exchange, AMQP::fanout).onError([&](const char * message)
|
2020-05-20 09:40:49 +00:00
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
local_exchange_declared = false;
|
|
|
|
LOG_ERROR(log, "Failed to declare exchange {}. Reason: {}", default_exchange, message);
|
2020-05-20 09:40:49 +00:00
|
|
|
});
|
|
|
|
|
2020-06-13 18:15:59 +00:00
|
|
|
default_local_exchange = local_exchange_name;
|
|
|
|
default_local_exchange += exchange_type_set ? "_default_" + Exchange::DIRECT : "_" + Exchange::DIRECT;
|
|
|
|
consumer_channel->declareExchange(default_local_exchange, AMQP::direct).onError([&](const char * message)
|
2020-05-20 09:40:49 +00:00
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
local_exchange_declared = false;
|
|
|
|
LOG_ERROR(log, "Failed to declare exchange {}. Reason: {}", default_local_exchange, message);
|
2020-06-10 23:01:47 +00:00
|
|
|
});
|
2020-05-20 09:40:49 +00:00
|
|
|
|
2020-06-13 18:15:59 +00:00
|
|
|
/// With fanout exchange the binding key is ignored - a parameter might be arbitrary. All distribution lies on local_exchange.
|
|
|
|
consumer_channel->bindExchange(default_exchange, default_local_exchange, routing_keys[0]).onError([&](const char * message)
|
2020-05-20 09:40:49 +00:00
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
local_exchange_declared = false;
|
|
|
|
LOG_ERROR(log, "Failed to bind {} exchange to {} exchange. Reason: {}", default_exchange, default_local_exchange, message);
|
2020-06-10 23:01:47 +00:00
|
|
|
});
|
2020-05-20 09:40:49 +00:00
|
|
|
|
2020-06-10 23:01:47 +00:00
|
|
|
if (!exchange_type_set)
|
|
|
|
return;
|
|
|
|
|
2020-06-11 09:23:23 +00:00
|
|
|
/// For special purposes to use the flexibility of routing provided by rabbitmq - choosing exchange types is supported.
|
2020-06-10 23:01:47 +00:00
|
|
|
|
|
|
|
AMQP::ExchangeType type;
|
2020-06-11 10:56:40 +00:00
|
|
|
if (exchange_type == Exchange::FANOUT) type = AMQP::ExchangeType::fanout;
|
|
|
|
else if (exchange_type == Exchange::DIRECT) type = AMQP::ExchangeType::direct;
|
|
|
|
else if (exchange_type == Exchange::TOPIC) type = AMQP::ExchangeType::topic;
|
|
|
|
else if (exchange_type == Exchange::HASH) type = AMQP::ExchangeType::consistent_hash;
|
2020-06-13 21:37:37 +00:00
|
|
|
else if (exchange_type == Exchange::HEADERS) type = AMQP::ExchangeType::headers;
|
2020-06-11 20:05:35 +00:00
|
|
|
else throw Exception("Invalid exchange type", ErrorCodes::BAD_ARGUMENTS);
|
2020-06-10 23:01:47 +00:00
|
|
|
|
|
|
|
/* Declare exchange of the specified type and bind it to hash-exchange, which will evenly distribute messages
|
2020-06-13 18:15:59 +00:00
|
|
|
* between all consumers. (This enables better scaling as without hash-exchange - the only option to avoid getting
|
|
|
|
* the same messages more than once - is having only one consumer with one queue, which is not good.)
|
2020-06-10 23:01:47 +00:00
|
|
|
*/
|
|
|
|
consumer_channel->declareExchange(exchange_name, type).onError([&](const char * message)
|
|
|
|
{
|
|
|
|
local_exchange_declared = false;
|
2020-06-13 18:15:59 +00:00
|
|
|
LOG_ERROR(log, "Failed to declare client's {} exchange: {}", exchange_type, message);
|
2020-06-10 23:01:47 +00:00
|
|
|
});
|
|
|
|
|
2020-06-13 18:15:59 +00:00
|
|
|
/// No need for declaring hash-exchange if there is only one consumer with one queue or exchange type is already hash
|
|
|
|
if (!bind_by_id)
|
2020-06-10 23:01:47 +00:00
|
|
|
return;
|
|
|
|
|
2020-06-11 10:56:40 +00:00
|
|
|
hash_exchange = true;
|
|
|
|
|
2020-06-13 18:15:59 +00:00
|
|
|
if (exchange_type == Exchange::HASH)
|
|
|
|
return;
|
|
|
|
|
2020-06-10 23:01:47 +00:00
|
|
|
AMQP::Table exchange_arguments;
|
|
|
|
exchange_arguments["hash-property"] = "message_id";
|
|
|
|
|
2020-06-13 18:15:59 +00:00
|
|
|
String local_hash_exchange_name = local_exchange_name + "_hash";
|
|
|
|
consumer_channel->declareExchange(local_hash_exchange_name, AMQP::consistent_hash, exchange_arguments)
|
2020-06-10 23:01:47 +00:00
|
|
|
.onError([&](const char * message)
|
|
|
|
{
|
|
|
|
local_exchange_declared = false;
|
|
|
|
LOG_ERROR(log, "Failed to declare {} exchange: {}", exchange_type, message);
|
|
|
|
});
|
|
|
|
|
2020-06-13 21:37:37 +00:00
|
|
|
if (exchange_type == Exchange::HEADERS)
|
2020-06-10 23:01:47 +00:00
|
|
|
{
|
2020-06-13 21:37:37 +00:00
|
|
|
AMQP::Table binding_arguments;
|
|
|
|
std::vector<String> matching;
|
|
|
|
|
|
|
|
for (auto & header : routing_keys)
|
|
|
|
{
|
|
|
|
boost::split(matching, header, [](char c){ return c == '='; });
|
|
|
|
binding_arguments[matching[0]] = matching[1];
|
|
|
|
matching.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Routing key can be arbitrary here.
|
|
|
|
consumer_channel->bindExchange(exchange_name, local_hash_exchange_name, routing_keys[0], binding_arguments)
|
|
|
|
.onError([&](const char * message)
|
2020-06-11 09:23:23 +00:00
|
|
|
{
|
|
|
|
local_exchange_declared = false;
|
|
|
|
LOG_ERROR(log, "Failed to bind {} exchange to {} exchange: {}", local_exchange_name, exchange_name, message);
|
|
|
|
});
|
|
|
|
}
|
2020-06-13 21:37:37 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
for (auto & routing_key : routing_keys)
|
|
|
|
{
|
|
|
|
consumer_channel->bindExchange(exchange_name, local_hash_exchange_name, routing_key).onError([&](const char * message)
|
|
|
|
{
|
|
|
|
local_exchange_declared = false;
|
|
|
|
LOG_ERROR(log, "Failed to bind {} exchange to {} exchange: {}", local_exchange_name, exchange_name, message);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-05-20 09:40:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ReadBufferFromRabbitMQConsumer::initQueueBindings(const size_t queue_id)
|
|
|
|
{
|
2020-06-11 20:05:35 +00:00
|
|
|
/// These variables might be updated later from a separate thread in onError callbacks.
|
2020-06-13 18:15:59 +00:00
|
|
|
if (!local_exchange_declared || (exchange_type_set && !local_hash_exchange_declared))
|
2020-05-20 09:40:49 +00:00
|
|
|
{
|
|
|
|
initExchange();
|
2020-06-10 23:01:47 +00:00
|
|
|
local_exchange_declared = true;
|
2020-06-13 18:15:59 +00:00
|
|
|
local_hash_exchange_declared = true;
|
2020-05-20 09:40:49 +00:00
|
|
|
}
|
|
|
|
|
2020-06-13 18:15:59 +00:00
|
|
|
bool default_bindings_created = false, default_bindings_error = false;
|
|
|
|
bool bindings_created = false, bindings_error = false;
|
2020-05-20 09:40:49 +00:00
|
|
|
|
2020-06-07 11:14:05 +00:00
|
|
|
consumer_channel->declareQueue(AMQP::exclusive)
|
2020-05-20 09:40:49 +00:00
|
|
|
.onSuccess([&](const std::string & queue_name_, int /* msgcount */, int /* consumercount */)
|
|
|
|
{
|
|
|
|
queues.emplace_back(queue_name_);
|
2020-06-08 01:11:48 +00:00
|
|
|
subscribed_queue[queue_name_] = false;
|
|
|
|
|
2020-06-11 09:23:23 +00:00
|
|
|
String binding_key = routing_keys[0];
|
2020-05-20 09:40:49 +00:00
|
|
|
|
2020-05-26 17:34:57 +00:00
|
|
|
/* Every consumer has at least one unique queue. Bind the queues to exchange based on the consumer_channel_id
|
|
|
|
* in case there is one queue per consumer and bind by queue_id in case there is more than 1 queue per consumer.
|
|
|
|
* (queue_id is based on channel_id)
|
|
|
|
*/
|
|
|
|
if (bind_by_id || hash_exchange)
|
2020-05-20 09:40:49 +00:00
|
|
|
{
|
|
|
|
if (queues.size() == 1)
|
|
|
|
{
|
2020-05-26 17:34:57 +00:00
|
|
|
binding_key = std::to_string(channel_id);
|
2020-05-20 09:40:49 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-05-26 17:34:57 +00:00
|
|
|
binding_key = std::to_string(channel_id + queue_id);
|
2020-05-20 09:40:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-13 18:15:59 +00:00
|
|
|
consumer_channel->bindQueue(default_local_exchange, queue_name_, binding_key)
|
2020-05-20 09:40:49 +00:00
|
|
|
.onSuccess([&]
|
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
default_bindings_created = true;
|
2020-05-20 09:40:49 +00:00
|
|
|
})
|
|
|
|
.onError([&](const char * message)
|
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
default_bindings_error = true;
|
2020-06-11 10:56:40 +00:00
|
|
|
LOG_ERROR(log, "Failed to bind to key {}. Reason: {}", binding_key, message);
|
2020-05-20 09:40:49 +00:00
|
|
|
});
|
2020-06-10 23:01:47 +00:00
|
|
|
|
2020-06-11 20:05:35 +00:00
|
|
|
/* Subscription can probably be moved back to readPrefix(), but not sure whether it is better in regard to speed. Also note
|
|
|
|
* that if moved there, it must(!) be wrapped inside a channel->onReady callback or any other, otherwise consumer might fail
|
|
|
|
* to subscribe and no resubscription will help.
|
|
|
|
*/
|
2020-06-11 09:23:23 +00:00
|
|
|
subscribe(queues.back());
|
|
|
|
|
|
|
|
LOG_TRACE(log, "Queue " + queue_name_ + " is bound by key " + binding_key);
|
|
|
|
|
2020-06-10 23:01:47 +00:00
|
|
|
if (exchange_type_set)
|
|
|
|
{
|
2020-06-11 09:23:23 +00:00
|
|
|
if (hash_exchange)
|
2020-06-10 23:01:47 +00:00
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
/* If exchange_type == hash, then bind directly to this client's exchange (because there is no need for a distributor
|
|
|
|
* exchange as it is already hash-exchange), otherwise hash-exchange is a local distributor exchange.
|
|
|
|
*/
|
|
|
|
String hash_exchange_name = exchange_type == Exchange::HASH ? exchange_name : local_exchange_name + "_hash";
|
|
|
|
|
2020-06-13 21:37:37 +00:00
|
|
|
/// If hash-exchange is used for messages distribution, then the binding key is ignored - can be arbitrary.
|
2020-06-13 18:15:59 +00:00
|
|
|
consumer_channel->bindQueue(hash_exchange_name, queue_name_, binding_key)
|
2020-06-11 09:23:23 +00:00
|
|
|
.onSuccess([&]
|
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
bindings_created = true;
|
2020-06-11 09:23:23 +00:00
|
|
|
})
|
|
|
|
.onError([&](const char * message)
|
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
bindings_error = true;
|
2020-06-11 10:56:40 +00:00
|
|
|
LOG_ERROR(log, "Failed to create queue binding to key {}. Reason: {}", binding_key, message);
|
2020-06-11 09:23:23 +00:00
|
|
|
});
|
|
|
|
}
|
2020-06-13 21:37:37 +00:00
|
|
|
else if (exchange_type == Exchange::HEADERS)
|
|
|
|
{
|
|
|
|
AMQP::Table binding_arguments;
|
|
|
|
std::vector<String> matching;
|
|
|
|
|
|
|
|
/// It is not parsed for the second time - if it was parsed above, then it would go to the first if statement, not here.
|
|
|
|
for (auto & header : routing_keys)
|
|
|
|
{
|
|
|
|
boost::split(matching, header, [](char c){ return c == '='; });
|
|
|
|
binding_arguments[matching[0]] = matching[1];
|
|
|
|
matching.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
consumer_channel->bindQueue(exchange_name, queue_name_, routing_keys[0], binding_arguments)
|
|
|
|
.onSuccess([&]
|
|
|
|
{
|
|
|
|
bindings_created = true;
|
|
|
|
})
|
|
|
|
.onError([&](const char * message)
|
|
|
|
{
|
|
|
|
bindings_error = true;
|
|
|
|
LOG_ERROR(log, "Failed to create queue binding to key {}. Reason: {}", routing_keys[0], message);
|
|
|
|
});
|
|
|
|
}
|
2020-06-11 09:23:23 +00:00
|
|
|
else
|
2020-06-10 23:01:47 +00:00
|
|
|
{
|
2020-06-11 20:05:35 +00:00
|
|
|
/// Means there is only one queue with one consumer - no even distribution needed - no hash-exchange.
|
2020-06-11 09:23:23 +00:00
|
|
|
for (auto & routing_key : routing_keys)
|
|
|
|
{
|
2020-06-11 10:56:40 +00:00
|
|
|
/// Binding directly to exchange, specified by the client
|
|
|
|
consumer_channel->bindQueue(exchange_name, queue_name_, routing_key)
|
2020-06-11 09:23:23 +00:00
|
|
|
.onSuccess([&]
|
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
bindings_created = true;
|
2020-06-11 09:23:23 +00:00
|
|
|
})
|
|
|
|
.onError([&](const char * message)
|
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
bindings_error = true;
|
2020-06-11 10:56:40 +00:00
|
|
|
LOG_ERROR(log, "Failed to create queue binding to key {}. Reason: {}", routing_key, message);
|
2020-06-11 09:23:23 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-06-10 23:01:47 +00:00
|
|
|
}
|
2020-05-20 09:40:49 +00:00
|
|
|
})
|
|
|
|
.onError([&](const char * message)
|
|
|
|
{
|
2020-06-13 18:15:59 +00:00
|
|
|
default_bindings_error = true;
|
2020-05-26 20:43:20 +00:00
|
|
|
LOG_ERROR(log, "Failed to declare queue on the channel: {}", message);
|
2020-05-20 09:40:49 +00:00
|
|
|
});
|
|
|
|
|
2020-05-26 17:34:57 +00:00
|
|
|
/* Run event loop (which updates local variables in a separate thread) until bindings are created or failed to be created.
|
|
|
|
* It is important at this moment to make sure that queue bindings are created before any publishing can happen because
|
|
|
|
* otherwise messages will be routed nowhere.
|
|
|
|
*/
|
2020-06-13 18:15:59 +00:00
|
|
|
while (!default_bindings_created && !default_bindings_error || (exchange_type_set && !bindings_created && !bindings_error))
|
2020-05-20 09:40:49 +00:00
|
|
|
{
|
2020-06-09 21:52:06 +00:00
|
|
|
startEventLoop(loop_started);
|
2020-06-08 01:11:48 +00:00
|
|
|
}
|
2020-05-20 09:40:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ReadBufferFromRabbitMQConsumer::subscribe(const String & queue_name)
|
|
|
|
{
|
2020-06-08 01:11:48 +00:00
|
|
|
if (subscribed_queue[queue_name])
|
|
|
|
return;
|
|
|
|
|
2020-05-20 09:40:49 +00:00
|
|
|
consumer_channel->consume(queue_name, AMQP::noack)
|
2020-05-26 17:34:57 +00:00
|
|
|
.onSuccess([&](const std::string & /* consumer */)
|
2020-05-20 09:40:49 +00:00
|
|
|
{
|
2020-06-09 21:52:06 +00:00
|
|
|
subscribed_queue[queue_name] = true;
|
2020-06-11 20:05:35 +00:00
|
|
|
consumer_error = false;
|
2020-06-08 01:11:48 +00:00
|
|
|
++count_subscribed;
|
|
|
|
|
2020-06-07 11:14:05 +00:00
|
|
|
LOG_TRACE(log, "Consumer {} is subscribed to queue {}", channel_id, queue_name);
|
2020-05-20 09:40:49 +00:00
|
|
|
})
|
|
|
|
.onReceived([&](const AMQP::Message & message, uint64_t /* deliveryTag */, bool /* redelivered */)
|
|
|
|
{
|
|
|
|
size_t message_size = message.bodySize();
|
|
|
|
if (message_size && message.body() != nullptr)
|
|
|
|
{
|
|
|
|
String message_received = std::string(message.body(), message.body() + message_size);
|
|
|
|
|
|
|
|
if (row_delimiter != '\0')
|
2020-06-07 11:14:05 +00:00
|
|
|
{
|
2020-05-20 09:40:49 +00:00
|
|
|
message_received += row_delimiter;
|
2020-06-07 11:14:05 +00:00
|
|
|
}
|
2020-06-05 13:42:11 +00:00
|
|
|
|
2020-06-09 21:52:06 +00:00
|
|
|
/// Needed to avoid data race because this vector can be used at the same time by another thread in nextImpl().
|
2020-06-04 06:22:53 +00:00
|
|
|
{
|
|
|
|
std::lock_guard lock(mutex);
|
|
|
|
received.push_back(message_received);
|
|
|
|
}
|
|
|
|
|
2020-06-11 20:05:35 +00:00
|
|
|
/* As event loop is blocking to the thread that started it and a single thread should not be blocked while
|
|
|
|
* executing all callbacks on the connection (not only its own), then there should be some point to unblock.
|
|
|
|
* loop_started == 1 if current consumer is started the loop and not another.
|
|
|
|
*/
|
|
|
|
if (!loop_started.load() && !eventHandler.checkStopIsScheduled().load())
|
2020-06-04 06:22:53 +00:00
|
|
|
{
|
2020-06-07 11:14:05 +00:00
|
|
|
stopEventLoopWithTimeout();
|
2020-06-04 06:22:53 +00:00
|
|
|
}
|
2020-05-20 09:40:49 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.onError([&](const char * message)
|
|
|
|
{
|
2020-06-09 21:52:06 +00:00
|
|
|
consumer_error = true;
|
2020-06-04 06:22:53 +00:00
|
|
|
LOG_ERROR(log, "Consumer {} failed: {}", channel_id, message);
|
2020-05-20 09:40:49 +00:00
|
|
|
});
|
2020-06-09 21:52:06 +00:00
|
|
|
}
|
2020-05-20 09:40:49 +00:00
|
|
|
|
2020-06-08 01:11:48 +00:00
|
|
|
|
2020-06-09 21:52:06 +00:00
|
|
|
void ReadBufferFromRabbitMQConsumer::checkSubscription()
|
|
|
|
{
|
|
|
|
if (count_subscribed == num_queues)
|
|
|
|
return;
|
2020-06-08 01:11:48 +00:00
|
|
|
|
2020-06-09 21:52:06 +00:00
|
|
|
wait_subscribed = num_queues;
|
2020-06-08 01:11:48 +00:00
|
|
|
|
2020-06-09 21:52:06 +00:00
|
|
|
/// These variables are updated in a separate thread
|
|
|
|
while (count_subscribed != wait_subscribed && !consumer_error)
|
|
|
|
{
|
|
|
|
startEventLoop(loop_started);
|
2020-06-08 01:11:48 +00:00
|
|
|
}
|
|
|
|
|
2020-06-09 21:52:06 +00:00
|
|
|
LOG_TRACE(log, "Consumer {} is subscribed to {} queues", channel_id, count_subscribed);
|
|
|
|
|
2020-06-11 20:05:35 +00:00
|
|
|
/// Updated in callbacks which are run by the loop
|
|
|
|
if (count_subscribed == num_queues)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/// A case that should never normally happen
|
2020-06-09 21:52:06 +00:00
|
|
|
for (auto & queue : queues)
|
2020-06-08 01:11:48 +00:00
|
|
|
{
|
2020-06-09 21:52:06 +00:00
|
|
|
subscribe(queue);
|
2020-05-20 09:40:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-04 06:22:53 +00:00
|
|
|
void ReadBufferFromRabbitMQConsumer::stopEventLoop()
|
|
|
|
{
|
|
|
|
eventHandler.stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-07 11:14:05 +00:00
|
|
|
void ReadBufferFromRabbitMQConsumer::stopEventLoopWithTimeout()
|
|
|
|
{
|
|
|
|
eventHandler.stopWithTimeout();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-09 21:52:06 +00:00
|
|
|
void ReadBufferFromRabbitMQConsumer::startEventLoop(std::atomic<bool> & loop_started)
|
2020-05-20 09:40:49 +00:00
|
|
|
{
|
2020-06-09 21:52:06 +00:00
|
|
|
eventHandler.startConsumerLoop(loop_started);
|
2020-05-20 09:40:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ReadBufferFromRabbitMQConsumer::nextImpl()
|
|
|
|
{
|
|
|
|
if (stopped || !allowed)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (current == messages.end())
|
|
|
|
{
|
|
|
|
if (received.empty())
|
|
|
|
{
|
2020-06-11 20:05:35 +00:00
|
|
|
/// Run the onReceived callbacks to save the messages that have been received by now, blocks current thread.
|
2020-06-09 21:52:06 +00:00
|
|
|
startEventLoop(loop_started);
|
2020-06-11 20:05:35 +00:00
|
|
|
loop_started.store(false);
|
2020-05-20 09:40:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (received.empty())
|
|
|
|
{
|
2020-05-26 17:34:57 +00:00
|
|
|
LOG_TRACE(log, "No more messages to be fetched");
|
2020-05-20 09:40:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
messages.clear();
|
2020-05-29 16:04:44 +00:00
|
|
|
|
2020-06-09 21:52:06 +00:00
|
|
|
/// Needed to avoid data race because this vector can be used at the same time by another thread in onReceived callback.
|
2020-05-29 16:04:44 +00:00
|
|
|
std::lock_guard lock(mutex);
|
|
|
|
|
2020-05-20 09:40:49 +00:00
|
|
|
messages.swap(received);
|
|
|
|
current = messages.begin();
|
|
|
|
}
|
|
|
|
|
2020-05-26 17:34:57 +00:00
|
|
|
auto * new_position = const_cast<char *>(current->data());
|
2020-05-20 09:40:49 +00:00
|
|
|
BufferBase::set(new_position, current->size(), 0);
|
|
|
|
|
|
|
|
++current;
|
|
|
|
allowed = false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|