Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timeouts in operations with sockets #417

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/agent/communicator/include/ihttp_socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
#include <boost/beast.hpp>
#include <boost/system/error_code.hpp>

#include <chrono>
#include <string>

namespace http_client
{
constexpr int TIMEOUT_DEFAULT = 2;

/// @brief Interface for HTTP sockets
class IHttpSocket
{
Expand All @@ -18,14 +21,20 @@ namespace http_client
/// @brief Connects the socket to the given endpoints
/// @param endpoints The endpoints to connect to
/// @param ec The error code, if any occurred
virtual void Connect(const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec) = 0;
/// @param timeOut The timeout for the connection
virtual void Connect(boost::asio::io_context& io_context,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add missing parameters to function description.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec,
const std::chrono::seconds timeOut = std::chrono::seconds(TIMEOUT_DEFAULT)) = 0;

/// @brief Asynchronous version of Connect
/// @param endpoints The endpoints to connect to
/// @param ec The error code, if any occurred
virtual boost::asio::awaitable<void> AsyncConnect(const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec) = 0;
/// @param timeOut The timeout for the connection
virtual boost::asio::awaitable<void>
AsyncConnect(const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec,
const std::chrono::seconds timeOut = std::chrono::seconds(TIMEOUT_DEFAULT)) = 0;

/// @brief Writes the given request to the socket
/// @param req The request to write
Expand Down
2 changes: 1 addition & 1 deletion src/agent/communicator/src/http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ namespace http_client

boost::system::error_code ec;

socket->Connect(results, ec);
socket->Connect(io_context, results, ec, std::chrono::seconds(TIMEOUT_DEFAULT));

if (ec)
{
Expand Down
106 changes: 99 additions & 7 deletions src/agent/communicator/src/http_socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <logger.hpp>

#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/beast.hpp>
#include <boost/system/error_code.hpp>

Expand All @@ -23,31 +24,122 @@ namespace http_client
}

/// @brief Connects the socket to the given endpoints
/// @param io_context The io_context associated to the socket
/// @param endpoints The endpoints to connect to
/// @param ec The error code, if any occurred
void Connect(const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec) override
/// @param timeOut The timeout for the connection
void Connect(boost::asio::io_context& io_context,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec,
const std::chrono::seconds timeOut = std::chrono::seconds(TIMEOUT_DEFAULT)) override
{
try
{
boost::asio::connect(m_socket, endpoints, ec);
bool connectionSuccess = false;

boost::asio::async_connect(m_socket,
endpoints,
[&](const boost::system::error_code& errorCode, const auto&)
{
if (!errorCode)
{
connectionSuccess = true;
ec = errorCode;
LogDebug("Connected successfully");
}
});

io_context.run_for(timeOut); // Run for 2 seconds();
if (!connectionSuccess)
{
ec = boost::asio::error::timed_out;
LogDebug("Connection timed out");
}
}
catch (const std::exception& e)
{
LogDebug("Exception thrown: {}", e.what());
}
}

/// @brief Starts a timer and updates the error code and task completion status accordingly
/// @param timer The timer to start
/// @param result The error code to update
/// @param taskCompleted In/Out Indicates whether the socket task is completed and, if not, serves as a flag
/// that indicates TimeOut

boost::asio::awaitable<void> TimerTask(std::shared_ptr<boost::asio::steady_timer> timer,
std::shared_ptr<boost::system::error_code> result,
std::shared_ptr<bool> taskCompleted)
{
try
{
co_await timer->async_wait(boost::asio::use_awaitable);

if (!(*taskCompleted))
{
LogDebug("Connection timed out");
*result = boost::asio::error::timed_out;
*taskCompleted = true;
}
}
catch (const boost::system::system_error& e)
{
if (!(*taskCompleted) && e.code() != boost::asio::error::operation_aborted)
{
*result = boost::asio::error::fault;
}
}
}

/// @brief Performs the socket connection
/// @param endpoints The endpoints to connect to
/// @param result The error code, if any occurred
/// @param taskCompleted In/Out Indicates whether the timer task is completed and, if not, serves as a flag that
/// indicates the socket is connected
boost::asio::awaitable<void> SocketTask(const boost::asio::ip::tcp::resolver::results_type& endpoints,
std::shared_ptr<boost::system::error_code> result,
std::shared_ptr<bool> taskCompleted)
{
boost::system::error_code socketErrorCode;
co_await boost::asio::async_connect(
m_socket, endpoints, boost::asio::redirect_error(boost::asio::use_awaitable, socketErrorCode));
if (!(*taskCompleted) && !socketErrorCode)
{
LogDebug("Connected successfully");
result->clear();
*taskCompleted = true;
}
else
{
*result = socketErrorCode;
}
}

/// @brief Asynchronous version of Connect
/// @param endpoints The endpoints to connect to
/// @param ec The error code, if any occurred
boost::asio::awaitable<void> AsyncConnect(const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec) override
/// @param timeOut The timeout for the connection
boost::asio::awaitable<void>
AsyncConnect(const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec,
const std::chrono::seconds timeOut = std::chrono::seconds(TIMEOUT_DEFAULT)) override
{
using namespace boost::asio::experimental::awaitable_operators;

auto timer = std::make_shared<boost::asio::steady_timer>(co_await boost::asio::this_coro::executor);
timer->expires_after(timeOut);

auto result = std::make_shared<boost::system::error_code>();
auto taskCompleted = std::make_shared<bool>(false);

try
{
co_await boost::asio::async_connect(
m_socket, endpoints, boost::asio::redirect_error(boost::asio::use_awaitable, ec));
co_await (TimerTask(timer, result, taskCompleted) || SocketTask(endpoints, result, taskCompleted));
if (!result)
{
LogDebug("Connection error: {}", result->value());
}
}
catch (const std::exception& e)
{
Expand Down
41 changes: 25 additions & 16 deletions src/agent/communicator/src/https_socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,30 @@ namespace http_client
/// @brief Connects the socket to the given endpoints
/// @param endpoints The endpoints to connect to
/// @param ec The error code, if any occurred
void Connect(const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec) override
/// @param timeOut The timeout for the connection
void Connect(boost::asio::io_context& io_context,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec,
const std::chrono::seconds timeOut = std::chrono::seconds(TIMEOUT_DEFAULT)) override
{
try
{
boost::asio::connect(m_ssl_socket.next_layer(), endpoints.begin(), endpoints.end(), ec);
if (ec)
{
LogDebug("Connect failed: {}", ec.message());
return;
}
bool connectionSuccess = false;

m_ssl_socket.handshake(boost::asio::ssl::stream_base::client, ec);
if (ec)
{
LogDebug("Handshake failed: {}", ec.message());
return;
}
// Start the asynchronous connect operation
boost::asio::async_connect(m_ssl_socket.lowest_layer(),
endpoints,
[&](const boost::system::error_code& errorCode, const auto&)
{
if (!errorCode)
{
connectionSuccess = true;
ec = errorCode;
LogDebug("Connected successfully");
}
});

io_context.run_for(timeOut); // Run for 2 seconds();
}
catch (const std::exception& e)
{
Expand All @@ -56,8 +62,11 @@ namespace http_client
/// @brief Asynchronous version of Connect
/// @param endpoints The endpoints to connect to
/// @param ec The error code, if any occurred
boost::asio::awaitable<void> AsyncConnect(const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec) override
/// @param timeOut The timeout for the connection
boost::asio::awaitable<void> AsyncConnect(
const boost::asio::ip::tcp::resolver::results_type& endpoints,
boost::system::error_code& ec,
[[maybe_unused]] const std::chrono::seconds timeOut = std::chrono::seconds(TIMEOUT_DEFAULT)) override
{
try
{
Expand Down
Loading