ClickHouse/src/Client/PacketReceiver.h

162 lines
4.7 KiB
C++
Raw Normal View History

2021-02-17 17:34:52 +00:00
#pragma once
#if defined(OS_LINUX)
2021-02-21 14:03:24 +00:00
#include <variant>
2021-02-17 17:34:52 +00:00
#include <Client/IConnections.h>
#include <Common/FiberStack.h>
#include <Common/Fiber.h>
2021-02-21 14:03:24 +00:00
#include <Common/Epoll.h>
#include <Common/TimerDescriptor.h>
2021-02-17 17:34:52 +00:00
namespace DB
{
/// Class for nonblocking packet receiving. It runs connection->receivePacket
/// in fiber and sets special read callback which is called when
/// reading from socket blocks. When read callback is called,
/// socket and receive timeout are added in epoll and execution returns to the main program.
/// So, you can poll this epoll file descriptor to determine when to resume
2021-02-21 14:03:24 +00:00
/// packet receiving.
2021-02-17 17:34:52 +00:00
class PacketReceiver
{
public:
2021-02-21 14:03:24 +00:00
explicit PacketReceiver(Connection * connection_) : connection(connection_)
2021-02-17 17:34:52 +00:00
{
epoll.add(receive_timeout.getDescriptor());
epoll.add(connection->getSocket()->impl()->sockfd());
2021-02-21 14:03:24 +00:00
2021-02-17 17:34:52 +00:00
fiber = boost::context::fiber(std::allocator_arg_t(), fiber_stack, Routine{*this});
}
/// Resume packet receiving.
std::variant<int, Packet, Poco::Timespan, std::exception_ptr> resume()
2021-02-17 17:34:52 +00:00
{
/// If there is no pending data, check receive timeout.
if (!connection->hasReadPendingData() && !checkReceiveTimeout())
2021-02-21 14:03:24 +00:00
{
/// Receive timeout expired.
return Poco::Timespan();
}
2021-02-17 17:34:52 +00:00
2021-02-21 14:03:24 +00:00
/// Resume fiber.
2021-02-17 17:34:52 +00:00
fiber = std::move(fiber).resume();
if (exception)
return std::move(exception);
2021-02-21 14:03:24 +00:00
if (is_read_in_process)
return epoll.getFileDescriptor();
/// Receiving packet was finished.
return std::move(packet);
2021-02-17 17:34:52 +00:00
}
void cancel()
{
Fiber to_destroy = std::move(fiber);
connection = nullptr;
}
int getFileDescriptor() const { return epoll.getFileDescriptor(); }
private:
2021-02-21 14:03:24 +00:00
/// When epoll file descriptor is ready, check if it's an expired timeout.
/// Return false if receive timeout expired and socket is not ready, return true otherwise.
2021-02-17 17:34:52 +00:00
bool checkReceiveTimeout()
{
bool is_socket_ready = false;
2021-02-21 14:03:24 +00:00
bool is_receive_timeout_expired = false;
2021-02-17 17:34:52 +00:00
epoll_event events[2];
events[0].data.fd = events[1].data.fd = -1;
size_t ready_count = epoll.getManyReady(2, events, true);
for (size_t i = 0; i != ready_count; ++i)
{
if (events[i].data.fd == connection->getSocket()->impl()->sockfd())
is_socket_ready = true;
if (events[i].data.fd == receive_timeout.getDescriptor())
is_receive_timeout_expired = true;
}
if (is_receive_timeout_expired && !is_socket_ready)
{
receive_timeout.reset();
return false;
}
return true;
}
struct Routine
{
PacketReceiver & receiver;
struct ReadCallback
{
PacketReceiver & receiver;
Fiber & sink;
2021-04-29 16:11:20 +00:00
void operator()(int, Poco::Timespan timeout, const std::string &)
2021-02-17 17:34:52 +00:00
{
receiver.receive_timeout.setRelative(timeout);
receiver.is_read_in_process = true;
sink = std::move(sink).resume();
receiver.is_read_in_process = false;
receiver.receive_timeout.reset();
}
};
Fiber operator()(Fiber && sink)
{
try
{
while (true)
{
2021-02-19 21:45:58 +00:00
{
AsyncCallbackSetter async_setter(receiver.connection, ReadCallback{receiver, sink});
receiver.packet = receiver.connection->receivePacket();
}
2021-02-17 17:34:52 +00:00
sink = std::move(sink).resume();
}
}
catch (const boost::context::detail::forced_unwind &)
{
/// This exception is thrown by fiber implementation in case if fiber is being deleted but hasn't exited
/// It should not be caught or it will segfault.
/// Other exceptions must be caught
throw;
}
catch (...)
{
receiver.exception = std::current_exception();
}
return std::move(sink);
}
};
Connection * connection;
2021-02-21 14:03:24 +00:00
Packet packet;
2021-02-17 17:34:52 +00:00
Fiber fiber;
FiberStack fiber_stack;
2021-02-21 14:03:24 +00:00
/// We use timer descriptor for checking socket receive timeout.
TimerDescriptor receive_timeout;
/// In read callback we add socket file descriptor and timer descriptor with receive timeout
/// in epoll, so we can return epoll file descriptor outside for polling.
Epoll epoll;
/// If and exception occurred in fiber resume, we save it and rethrow.
2021-02-17 17:34:52 +00:00
std::exception_ptr exception;
2021-02-21 14:03:24 +00:00
bool is_read_in_process = false;
2021-02-17 17:34:52 +00:00
};
}
#endif