uaf fix
This commit is contained in:
@@ -48,81 +48,94 @@ private:
|
|||||||
boost::asio::ip::tcp::socket &sock;
|
boost::asio::ip::tcp::socket &sock;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class op_t>
|
|
||||||
inline void
|
|
||||||
run_with_deadline(boost::asio::io_context &io_ctx,
|
|
||||||
boost::asio::ip::tcp::socket &sock, op_t operation,
|
|
||||||
std::chrono::milliseconds deadline,
|
|
||||||
std::string_view event_name, std::string_view function_name);
|
|
||||||
|
|
||||||
void apply_common_socket_properties(boost::asio::ip::tcp::socket &sock);
|
void apply_common_socket_properties(boost::asio::ip::tcp::socket &sock);
|
||||||
|
|
||||||
[[nodiscard]] auto is_socket_still_alive(boost::asio::ip::tcp::socket &sock)
|
[[nodiscard]] auto is_socket_still_alive(boost::asio::ip::tcp::socket &sock)
|
||||||
-> bool;
|
-> bool;
|
||||||
|
|
||||||
void connect_with_deadline(
|
void connect_with_deadline(
|
||||||
boost::asio::io_context &io_ctx, boost::asio::ip::tcp::socket &sock,
|
boost::asio::io_context &ctx, boost::asio::ip::tcp::socket &sock,
|
||||||
boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type
|
boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type
|
||||||
&endpoints,
|
&endpoints,
|
||||||
std::chrono::milliseconds deadline);
|
std::chrono::milliseconds deadline);
|
||||||
|
|
||||||
void read_exact_with_deadline(boost::asio::io_context &io_ctx,
|
void read_exact_with_deadline(boost::asio::io_context &ctx,
|
||||||
boost::asio::ip::tcp::socket &sock,
|
boost::asio::ip::tcp::socket &sock,
|
||||||
boost::asio::mutable_buffer buf,
|
boost::asio::mutable_buffer buf,
|
||||||
std::chrono::milliseconds deadline);
|
std::chrono::milliseconds deadline);
|
||||||
|
|
||||||
void write_all_with_deadline(boost::asio::io_context &io_ctx,
|
template <class op_t>
|
||||||
|
auto run_with_deadline(boost::asio::io_context &ctx,
|
||||||
|
boost::asio::ip::tcp::socket &sock, op_t &&operation,
|
||||||
|
std::chrono::milliseconds deadline,
|
||||||
|
std::string event_name, std::string function_name)
|
||||||
|
-> void;
|
||||||
|
|
||||||
|
void write_all_with_deadline(boost::asio::io_context &ctx,
|
||||||
boost::asio::ip::tcp::socket &sock,
|
boost::asio::ip::tcp::socket &sock,
|
||||||
boost::asio::mutable_buffer buf,
|
boost::asio::mutable_buffer buf,
|
||||||
std::chrono::milliseconds deadline);
|
std::chrono::milliseconds deadline);
|
||||||
|
|
||||||
template <class op_t>
|
template <class op_t>
|
||||||
inline void
|
auto run_with_deadline(boost::asio::io_context &ctx,
|
||||||
run_with_deadline(boost::asio::io_context &io_ctx,
|
boost::asio::ip::tcp::socket &sock, op_t &&operation,
|
||||||
boost::asio::ip::tcp::socket &sock, op_t operation,
|
std::chrono::milliseconds deadline,
|
||||||
std::chrono::milliseconds deadline,
|
std::string event_name, std::string function_name)
|
||||||
std::string_view event_name, std::string_view function_name) {
|
-> void {
|
||||||
deadline = std::max(deadline, std::chrono::milliseconds{250});
|
deadline = std::max(deadline, std::chrono::milliseconds{250});
|
||||||
|
|
||||||
struct request_state final {
|
struct request_state final {
|
||||||
|
request_state(boost::asio::io_context &ctx_,
|
||||||
|
boost::asio::ip::tcp::socket &sock_)
|
||||||
|
: sock(sock_),
|
||||||
|
timer(ctx_),
|
||||||
|
work_guard(boost::asio::make_work_guard(ctx_.get_executor())) {}
|
||||||
|
|
||||||
|
boost::asio::ip::tcp::socket &sock;
|
||||||
|
boost::asio::steady_timer timer;
|
||||||
|
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
|
||||||
|
work_guard;
|
||||||
|
|
||||||
std::atomic<bool> done{false};
|
std::atomic<bool> done{false};
|
||||||
std::atomic<bool> timed_out{false};
|
|
||||||
boost::system::error_code err;
|
boost::system::error_code err;
|
||||||
|
std::atomic<bool> timed_out{false};
|
||||||
};
|
};
|
||||||
auto state = std::make_shared<request_state>();
|
|
||||||
|
|
||||||
boost::asio::steady_timer timer{io_ctx};
|
auto state_ptr = std::make_shared<request_state>(ctx, sock);
|
||||||
timer.expires_after(deadline);
|
state_ptr->timer.expires_after(deadline);
|
||||||
|
state_ptr->timer.async_wait([state_ptr](auto &&err) {
|
||||||
timer.async_wait([state, &sock](auto &&err) {
|
if (not err and not state_ptr->done) {
|
||||||
if (not err && not state->done) {
|
state_ptr->timed_out = true;
|
||||||
state->timed_out = true;
|
boost::system::error_code err2;
|
||||||
boost::system::error_code ignored_ec;
|
[[maybe_unused]] auto res = state_ptr->sock.cancel(err2);
|
||||||
[[maybe_unused]] auto res = sock.cancel(ignored_ec);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
operation([state](auto &&err) {
|
operation([state_ptr](auto &&err) {
|
||||||
state->err = err;
|
state_ptr->err = err;
|
||||||
state->done = true;
|
state_ptr->done = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
io_ctx.restart();
|
ctx.restart();
|
||||||
while (not state->done && not state->timed_out) {
|
while (not state_ptr->done and not state_ptr->timed_out) {
|
||||||
io_ctx.run_one();
|
ctx.run_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.cancel();
|
state_ptr->timer.cancel();
|
||||||
|
while (ctx.poll_one() > 0) {
|
||||||
|
}
|
||||||
|
|
||||||
if (state->timed_out) {
|
state_ptr->work_guard.reset();
|
||||||
|
|
||||||
|
if (state_ptr->timed_out) {
|
||||||
repertory::event_system::instance().raise<repertory::packet_client_timeout>(
|
repertory::event_system::instance().raise<repertory::packet_client_timeout>(
|
||||||
event_name, function_name);
|
event_name, function_name);
|
||||||
throw std::runtime_error(std::string{event_name} + " timed-out");
|
throw std::runtime_error(fmt::format("{} timed-out", event_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state->err) {
|
if (state_ptr->err) {
|
||||||
throw std::runtime_error(std::string{event_name} + " failed|err|" +
|
throw std::runtime_error(
|
||||||
state->err.message());
|
fmt::format("{} failed|err|{}", event_name, state_ptr->err.message()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace repertory::comm
|
} // namespace repertory::comm
|
||||||
|
|||||||
@@ -73,14 +73,14 @@ void apply_common_socket_properties(boost::asio::ip::tcp::socket &sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void connect_with_deadline(
|
void connect_with_deadline(
|
||||||
boost::asio::io_context &io_ctx, boost::asio::ip::tcp::socket &sock,
|
boost::asio::io_context &ctx, boost::asio::ip::tcp::socket &sock,
|
||||||
boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type
|
boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type
|
||||||
&endpoints,
|
&endpoints,
|
||||||
std::chrono::milliseconds deadline) {
|
std::chrono::milliseconds deadline) {
|
||||||
REPERTORY_USES_FUNCTION_NAME();
|
REPERTORY_USES_FUNCTION_NAME();
|
||||||
|
|
||||||
run_with_deadline(
|
run_with_deadline(
|
||||||
io_ctx, sock,
|
ctx, sock,
|
||||||
[&sock, &endpoints](auto &&handler) {
|
[&sock, &endpoints](auto &&handler) {
|
||||||
boost::asio::async_connect(
|
boost::asio::async_connect(
|
||||||
sock, endpoints, [handler](auto &&err, auto &&) { handler(err); });
|
sock, endpoints, [handler](auto &&err, auto &&) { handler(err); });
|
||||||
@@ -88,7 +88,7 @@ void connect_with_deadline(
|
|||||||
deadline, "connect", std::string{function_name});
|
deadline, "connect", std::string{function_name});
|
||||||
}
|
}
|
||||||
|
|
||||||
void read_exact_with_deadline(boost::asio::io_context &io_ctx,
|
void read_exact_with_deadline(boost::asio::io_context &ctx,
|
||||||
boost::asio::ip::tcp::socket &sock,
|
boost::asio::ip::tcp::socket &sock,
|
||||||
boost::asio::mutable_buffer buf,
|
boost::asio::mutable_buffer buf,
|
||||||
std::chrono::milliseconds deadline) {
|
std::chrono::milliseconds deadline) {
|
||||||
@@ -103,7 +103,7 @@ void read_exact_with_deadline(boost::asio::io_context &io_ctx,
|
|||||||
std::size_t bytes_read = 0U;
|
std::size_t bytes_read = 0U;
|
||||||
|
|
||||||
run_with_deadline(
|
run_with_deadline(
|
||||||
io_ctx, sock,
|
ctx, sock,
|
||||||
[&](auto &&handler) {
|
[&](auto &&handler) {
|
||||||
sock.async_read_some(
|
sock.async_read_some(
|
||||||
boost::asio::buffer(base + offset, total - offset),
|
boost::asio::buffer(base + offset, total - offset),
|
||||||
@@ -122,7 +122,7 @@ void read_exact_with_deadline(boost::asio::io_context &io_ctx,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_all_with_deadline(boost::asio::io_context &io_ctx,
|
void write_all_with_deadline(boost::asio::io_context &ctx,
|
||||||
boost::asio::ip::tcp::socket &sock,
|
boost::asio::ip::tcp::socket &sock,
|
||||||
boost::asio::mutable_buffer buf,
|
boost::asio::mutable_buffer buf,
|
||||||
std::chrono::milliseconds deadline) {
|
std::chrono::milliseconds deadline) {
|
||||||
@@ -136,7 +136,7 @@ void write_all_with_deadline(boost::asio::io_context &io_ctx,
|
|||||||
std::size_t bytes_written = 0U;
|
std::size_t bytes_written = 0U;
|
||||||
|
|
||||||
run_with_deadline(
|
run_with_deadline(
|
||||||
io_ctx, sock,
|
ctx, sock,
|
||||||
[&](auto &&handler) {
|
[&](auto &&handler) {
|
||||||
sock.async_write_some(
|
sock.async_write_some(
|
||||||
boost::asio::buffer(base + offset, total - offset),
|
boost::asio::buffer(base + offset, total - offset),
|
||||||
|
|||||||
@@ -57,9 +57,9 @@ packet_client::~packet_client() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void packet_client::close(client &cli) noexcept {
|
void packet_client::close(client &cli) noexcept {
|
||||||
boost::system::error_code err1;
|
boost::system::error_code err;
|
||||||
[[maybe_unused]] auto res =
|
[[maybe_unused]] auto res =
|
||||||
cli.socket.shutdown(boost::asio::socket_base::shutdown_both, err1);
|
cli.socket.shutdown(boost::asio::socket_base::shutdown_both, err);
|
||||||
|
|
||||||
boost::system::error_code err2;
|
boost::system::error_code err2;
|
||||||
[[maybe_unused]] auto res2 = cli.socket.close(err2);
|
[[maybe_unused]] auto res2 = cli.socket.close(err2);
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to do so, subject to the
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
following conditions: The above copyright notice and this permission notice
|
furnished to do so, subject to the following conditions:
|
||||||
shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@@ -17,7 +21,6 @@
|
|||||||
*/
|
*/
|
||||||
#include "test_common.hpp"
|
#include "test_common.hpp"
|
||||||
|
|
||||||
#include "comm/packet/common.hpp"
|
|
||||||
#include "comm/packet/packet.hpp"
|
#include "comm/packet/packet.hpp"
|
||||||
#include "comm/packet/packet_client.hpp"
|
#include "comm/packet/packet_client.hpp"
|
||||||
#include "comm/packet/packet_server.hpp"
|
#include "comm/packet/packet_server.hpp"
|
||||||
|
|||||||
@@ -1,30 +1,284 @@
|
|||||||
/*
|
/*
|
||||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions: The above copyright
|
furnished to do so, subject to the following conditions:
|
||||||
notice and this permission notice shall be included in all copies or
|
|
||||||
substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS",
|
The above copyright notice and this permission notice shall be included in all
|
||||||
WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
copies or substantial portions of the Software.
|
||||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "test_common.hpp"
|
#include "test_common.hpp"
|
||||||
|
|
||||||
#include "comm/packet/common.hpp"
|
#include "comm/packet/common.hpp"
|
||||||
|
|
||||||
using boost::asio::ip::tcp;
|
namespace {
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
class packet_comm_common_test2 : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override { repertory::event_system::instance().start(); }
|
||||||
|
void TearDown() override { repertory::event_system::instance().stop(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class loopback_server final {
|
||||||
|
public:
|
||||||
|
loopback_server(std::size_t send_bytes,
|
||||||
|
std::chrono::milliseconds delay_before_send, bool never_send)
|
||||||
|
: acceptor_{io_ctx_, {boost::asio::ip::tcp::v4(), 0}},
|
||||||
|
send_bytes_{send_bytes},
|
||||||
|
delay_before_send_{delay_before_send},
|
||||||
|
never_send_{never_send} {
|
||||||
|
start_accept();
|
||||||
|
io_thr_ = std::thread([this]() { io_ctx_.run(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
~loopback_server() {
|
||||||
|
boost::system::error_code ign{};
|
||||||
|
[[maybe_unused]] auto res = acceptor_.close(ign);
|
||||||
|
io_ctx_.stop();
|
||||||
|
if (io_thr_.joinable()) {
|
||||||
|
io_thr_.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto port() const -> std::uint16_t {
|
||||||
|
return acceptor_.local_endpoint().port();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto start_accept() -> void {
|
||||||
|
auto sock_ptr = std::make_shared<boost::asio::ip::tcp::socket>(io_ctx_);
|
||||||
|
acceptor_.async_accept(*sock_ptr, [this, sock_ptr](auto &&err) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (never_send_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tmr_ptr = std::make_shared<boost::asio::steady_timer>(io_ctx_);
|
||||||
|
tmr_ptr->expires_after(delay_before_send_);
|
||||||
|
tmr_ptr->async_wait([this, sock_ptr, tmr_ptr](auto &&err2) {
|
||||||
|
if (err2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buf_ptr =
|
||||||
|
std::make_shared<std::vector<std::uint8_t>>(send_bytes_, 0xAB);
|
||||||
|
boost::asio::async_write(
|
||||||
|
*sock_ptr, boost::asio::buffer(*buf_ptr),
|
||||||
|
[buf_ptr, sock_ptr](auto &&, auto &&) {
|
||||||
|
boost::system::error_code ign{};
|
||||||
|
[[maybe_unused]] auto res = sock_ptr->shutdown(
|
||||||
|
boost::asio::ip::tcp::socket::shutdown_both, ign);
|
||||||
|
[[maybe_unused]] auto res2 = sock_ptr->close(ign);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::asio::io_context io_ctx_;
|
||||||
|
boost::asio::ip::tcp::acceptor acceptor_;
|
||||||
|
std::thread io_thr_;
|
||||||
|
|
||||||
|
std::size_t send_bytes_;
|
||||||
|
std::chrono::milliseconds delay_before_send_;
|
||||||
|
bool never_send_;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto connect_client(boost::asio::ip::tcp::socket &sock, std::uint16_t srv_port)
|
||||||
|
-> void {
|
||||||
|
boost::asio::ip::tcp::endpoint endpoint{
|
||||||
|
boost::asio::ip::address_v4::loopback(),
|
||||||
|
srv_port,
|
||||||
|
};
|
||||||
|
boost::system::error_code err;
|
||||||
|
[[maybe_unused]] auto res = sock.connect(endpoint, err);
|
||||||
|
ASSERT_FALSE(err) << "connect failed: " << err.message();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace repertory {
|
namespace repertory {
|
||||||
|
TEST_F(packet_comm_common_test2, run_with_deadline_op_finishes_first) {
|
||||||
|
constexpr std::size_t test_bytes{16U * 1024U};
|
||||||
|
const auto deadline = 750ms;
|
||||||
|
const auto server_delay = 50ms;
|
||||||
|
|
||||||
|
loopback_server server{test_bytes, server_delay, false};
|
||||||
|
|
||||||
|
boost::asio::io_context io_ctx;
|
||||||
|
boost::asio::ip::tcp::socket cli_sock{io_ctx};
|
||||||
|
connect_client(cli_sock, server.port());
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> recv_buf(test_bytes, 0x00);
|
||||||
|
|
||||||
|
EXPECT_NO_THROW({
|
||||||
|
repertory::comm::run_with_deadline(
|
||||||
|
io_ctx, cli_sock,
|
||||||
|
[&](auto complete) {
|
||||||
|
boost::asio::async_read(
|
||||||
|
cli_sock, boost::asio::buffer(recv_buf),
|
||||||
|
[complete](auto &&err, auto &&) { complete(err); });
|
||||||
|
},
|
||||||
|
deadline, "read_exact", "op_finishes_first");
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto val : recv_buf) {
|
||||||
|
ASSERT_EQ(val, 0xAB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(packet_comm_common_test2, run_with_deadline_timeout_fires_first) {
|
||||||
|
constexpr std::size_t test_bytes{8U * 1024U};
|
||||||
|
const auto deadline = 100ms;
|
||||||
|
const auto server_delay = 1500ms;
|
||||||
|
|
||||||
|
loopback_server server{test_bytes, server_delay, false};
|
||||||
|
|
||||||
|
boost::asio::io_context io_ctx;
|
||||||
|
boost::asio::ip::tcp::socket cli_sock{io_ctx};
|
||||||
|
connect_client(cli_sock, server.port());
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> recv_buf(test_bytes, 0x00);
|
||||||
|
|
||||||
|
EXPECT_THROW(
|
||||||
|
{
|
||||||
|
repertory::comm::run_with_deadline(
|
||||||
|
io_ctx, cli_sock,
|
||||||
|
[&](auto complete) {
|
||||||
|
boost::asio::async_read(
|
||||||
|
cli_sock, boost::asio::buffer(recv_buf),
|
||||||
|
[complete](auto &&err, auto &&) { complete(err); });
|
||||||
|
},
|
||||||
|
deadline, "read_exact", "timeout_fires_first");
|
||||||
|
},
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(packet_comm_common_test2, run_with_deadline_threaded_boundary_stress) {
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
constexpr int thread_count{6};
|
||||||
|
constexpr int iter_count{16};
|
||||||
|
constexpr std::size_t test_bytes{4U * 1024U};
|
||||||
|
|
||||||
|
const auto deadline = 250ms;
|
||||||
|
|
||||||
|
std::atomic<int> seen_timeouts{0};
|
||||||
|
std::atomic<int> seen_success{0};
|
||||||
|
|
||||||
|
std::vector<std::thread> thr_pool;
|
||||||
|
thr_pool.reserve(thread_count);
|
||||||
|
|
||||||
|
for (int tid = 0; tid < thread_count; ++tid) {
|
||||||
|
thr_pool.emplace_back([tid, deadline, &seen_timeouts, &seen_success]() {
|
||||||
|
std::mt19937 rng(static_cast<std::mt19937::result_type>(
|
||||||
|
std::chrono::high_resolution_clock::now().time_since_epoch().count() +
|
||||||
|
tid));
|
||||||
|
std::uniform_int_distribution<int> tiny_jitter{-10, 10};
|
||||||
|
|
||||||
|
for (int iter = 0; iter < iter_count; ++iter) {
|
||||||
|
const bool make_timeout = (iter % 2) == 1;
|
||||||
|
|
||||||
|
auto delay = make_timeout
|
||||||
|
? (deadline + 200ms +
|
||||||
|
std::chrono::milliseconds{tiny_jitter(rng)})
|
||||||
|
: (deadline - 100ms +
|
||||||
|
std::chrono::milliseconds{tiny_jitter(rng)});
|
||||||
|
if (delay < 0ms) {
|
||||||
|
delay = 0ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
loopback_server server{test_bytes, delay, false};
|
||||||
|
|
||||||
|
boost::asio::io_context io_ctx;
|
||||||
|
boost::asio::ip::tcp::socket cli_sock{io_ctx};
|
||||||
|
|
||||||
|
boost::asio::ip::tcp::endpoint endpoint{
|
||||||
|
boost::asio::ip::address_v4::loopback(),
|
||||||
|
server.port(),
|
||||||
|
};
|
||||||
|
boost::system::error_code err{};
|
||||||
|
[[maybe_unused]] auto res = cli_sock.connect(endpoint, err);
|
||||||
|
if (err) {
|
||||||
|
ADD_FAILURE() << "connect failed: " << err.message();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> recv_buf(test_bytes, 0x00);
|
||||||
|
|
||||||
|
try {
|
||||||
|
repertory::comm::run_with_deadline(
|
||||||
|
io_ctx, cli_sock,
|
||||||
|
[&](auto complete) {
|
||||||
|
boost::asio::async_read(
|
||||||
|
cli_sock, boost::asio::buffer(recv_buf),
|
||||||
|
[complete](auto &&err2, auto &&) { complete(err2); });
|
||||||
|
},
|
||||||
|
deadline, "read_exact", "threaded_boundary_stress");
|
||||||
|
seen_success.fetch_add(1);
|
||||||
|
} catch (const std::runtime_error &ex) {
|
||||||
|
std::string msg{ex.what()};
|
||||||
|
if (msg.find("timed-out") != std::string::npos) {
|
||||||
|
seen_timeouts.fetch_add(1);
|
||||||
|
} else {
|
||||||
|
ADD_FAILURE() << "unexpected runtime_error: " << msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &thr : thr_pool) {
|
||||||
|
thr.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_GT(seen_timeouts.load(), 0);
|
||||||
|
EXPECT_GT(seen_success.load(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(packet_comm_common_test2, run_with_deadline_server_never_sends) {
|
||||||
|
constexpr std::size_t test_bytes{2U * 1024U};
|
||||||
|
const auto deadline = 120ms;
|
||||||
|
|
||||||
|
loopback_server server{test_bytes, 0ms, true};
|
||||||
|
|
||||||
|
boost::asio::io_context io_ctx;
|
||||||
|
boost::asio::ip::tcp::socket cli_sock{io_ctx};
|
||||||
|
connect_client(cli_sock, server.port());
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> recv_buf(test_bytes, 0x00);
|
||||||
|
|
||||||
|
EXPECT_THROW(
|
||||||
|
{
|
||||||
|
repertory::comm::run_with_deadline(
|
||||||
|
io_ctx, cli_sock,
|
||||||
|
[&](auto complete) {
|
||||||
|
boost::asio::async_read(
|
||||||
|
cli_sock, boost::asio::buffer(recv_buf),
|
||||||
|
[complete](auto &&err, auto &&) { complete(err); });
|
||||||
|
},
|
||||||
|
deadline, "read_exact", "server_never_sends");
|
||||||
|
},
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(packet_comm_common_test, operation_completes_prior_to_timeout) {
|
TEST(packet_comm_common_test, operation_completes_prior_to_timeout) {
|
||||||
boost::asio::io_context io_ctx;
|
boost::asio::io_context io_ctx;
|
||||||
tcp::socket sock(io_ctx);
|
boost::asio::ip::tcp::socket sock(io_ctx);
|
||||||
|
|
||||||
std::atomic<bool> completed{false};
|
std::atomic<bool> completed{false};
|
||||||
|
|
||||||
@@ -46,7 +300,7 @@ TEST(packet_comm_common_test, operation_completes_prior_to_timeout) {
|
|||||||
|
|
||||||
TEST(packet_comm_common_test, timeout_completes_prior_to_operation) {
|
TEST(packet_comm_common_test, timeout_completes_prior_to_operation) {
|
||||||
boost::asio::io_context io_ctx;
|
boost::asio::io_context io_ctx;
|
||||||
tcp::socket sock(io_ctx);
|
boost::asio::ip::tcp::socket sock(io_ctx);
|
||||||
|
|
||||||
std::atomic<bool> completed{false};
|
std::atomic<bool> completed{false};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user