2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-19 04:42:09 +00:00

Fixes a race condition when cancelling requests on connection lost (#309)

Changes how cancel_on_conn_lost is used to ensure it is called only once and after the reader and writer tasks have exited.
This fixes a problem in test_conn_reconnect
Adds a test for multiplexer::reset()
Adds stronger invariants to the multiplexer functions to be called by the reader and writer
Removes test_issue_181, since the same functionality is being covered by unit tests already
Removes basic_connection::run_is_canceled
This commit is contained in:
Anarthal (Rubén Pérez)
2025-09-22 13:04:28 +02:00
committed by GitHub
parent 8da18379ba
commit 203e9298ed
7 changed files with 90 additions and 112 deletions

View File

@@ -59,7 +59,6 @@ make_test(test_conn_echo_stress)
make_test(test_conn_move)
make_test(test_conn_setup)
make_test(test_issue_50)
make_test(test_issue_181)
make_test(test_conversions)
make_test(test_conn_tls)
make_test(test_unix_sockets)

View File

@@ -42,7 +42,7 @@ net::awaitable<void> test_reconnect_impl()
// cancel_on_connection_lost is required because async_run might detect the failure
// after the 2nd async_exec is issued
request regular_req;
regular_req.push("GET", "mykey");
regular_req.push("PING", "SomeValue");
regular_req.get_config().cancel_on_connection_lost = false;
auto conn = std::make_shared<connection>(ex);
@@ -54,16 +54,14 @@ net::awaitable<void> test_reconnect_impl()
BOOST_TEST_CONTEXT("i=" << i)
{
// Issue a quit request, which will cause the server to close the connection.
// This request will fail
// This request will succeed, since this happens before the connection is lost.
error_code ec;
co_await conn->async_exec(quit_req, ignore, net::redirect_error(ec));
BOOST_TEST(ec == error_code());
// This should trigger reconnection, which will now succeed.
// We should be able to execute requests successfully now.
// TODO: this is currently unreliable - find our why and fix
// Reconnection will happen, and this request will succeed, too.
co_await conn->async_exec(regular_req, ignore, net::redirect_error(ec));
// BOOST_TEST(ec == error_code());
BOOST_TEST(ec == error_code());
}
}

View File

@@ -1,76 +0,0 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/error.hpp>
#include <boost/system/error_code.hpp>
#define BOOST_TEST_MODULE issue_181
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
#include <chrono>
#include <iostream>
namespace net = boost::asio;
using boost::redis::request;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::logger;
using boost::redis::config;
using boost::redis::operation;
using boost::redis::connection;
using boost::system::error_code;
using namespace std::chrono_literals;
namespace {
BOOST_AUTO_TEST_CASE(issue_181)
{
using basic_connection = boost::redis::basic_connection<net::any_io_executor>;
auto const level = boost::redis::logger::level::debug;
net::io_context ioc;
auto ctx = net::ssl::context{net::ssl::context::tlsv12_client};
basic_connection conn{ioc.get_executor(), std::move(ctx)};
net::steady_timer timer{ioc};
timer.expires_after(std::chrono::seconds{1});
bool run_finished = false;
auto run_cont = [&](error_code ec) {
std::cout << "async_run1: " << ec.message() << std::endl;
BOOST_TEST(ec == net::error::operation_aborted);
run_finished = true;
};
auto cfg = make_test_config();
cfg.health_check_interval = std::chrono::seconds{0};
cfg.reconnect_wait_interval = std::chrono::seconds{0};
conn.async_run(cfg, boost::redis::logger{level}, run_cont);
BOOST_TEST(!conn.run_is_canceled());
// Uses a timer to wait some time until run has been called.
auto timer_cont = [&](error_code ec) {
std::cout << "timer_cont: " << ec.message() << std::endl;
BOOST_TEST(ec == error_code());
BOOST_TEST(!conn.run_is_canceled());
conn.cancel(operation::run);
BOOST_TEST(conn.run_is_canceled());
};
timer.async_wait(timer_cont);
ioc.run_for(test_timeout);
BOOST_TEST(run_finished);
}
} // namespace

View File

@@ -497,11 +497,6 @@ void test_cancel_on_connection_lost()
BOOST_TEST(!item_waiting1.done);
BOOST_TEST(item_waiting1.elem_ptr->is_waiting());
BOOST_TEST(item_waiting2.done);
// Triggering it again does nothing
mpx.cancel_on_conn_lost();
BOOST_TEST(!item_written1.done);
BOOST_TEST(item_written1.elem_ptr->is_waiting());
}
// The test below fails. Uncomment when this is fixed:
@@ -551,6 +546,52 @@ void test_cancel_on_connection_lost()
// std::end(expected));
// }
// Resetting works
void test_reset()
{
// Setup
multiplexer mpx;
generic_response push_resp;
mpx.set_receive_adapter(any_adapter{push_resp});
test_item item1, item2;
// Add a request
mpx.add(item1.elem_ptr);
// Start parsing a push
error_code ec;
auto ret = mpx.consume_next(">2\r", ec);
BOOST_TEST_EQ(ret.first, consume_result::needs_more);
// Connection lost. The first request gets cancelled
mpx.cancel_on_conn_lost();
BOOST_TEST(item1.done);
// Reconnection happens
mpx.reset();
ec.clear();
// We're able to add write requests and read responses - all state was reset
mpx.add(item2.elem_ptr);
BOOST_TEST_EQ(mpx.prepare_write(), 1u);
BOOST_TEST_EQ(mpx.commit_write(), 0u);
std::string_view response_buffer = "$11\r\nHello world\r\n";
ret = mpx.consume_next(response_buffer, ec);
BOOST_TEST_EQ(ret.first, consume_result::got_response);
BOOST_TEST_EQ(ret.second, response_buffer.size());
BOOST_TEST(item2.resp.has_value());
const node expected[] = {
{type::blob_string, 1u, 0u, "Hello world"},
};
BOOST_TEST_ALL_EQ(
item2.resp->begin(),
item2.resp->end(),
std::begin(expected),
std::end(expected));
BOOST_TEST(item2.done);
}
} // namespace
int main()
@@ -566,6 +607,7 @@ int main()
test_mix_responses_pushes();
test_cancel_on_connection_lost();
// test_cancel_on_connection_lost_half_parsed_response();
test_reset();
return boost::report_errors();
}