Skip to content

Commit dfc2bd1

Browse files
committed
Fixes issue 181.
1 parent 0445e74 commit dfc2bd1

File tree

4 files changed

+84
-1
lines changed

4 files changed

+84
-1
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,11 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
693693
apps need only one connection for their entire application, which
694694
makes the overhead of one ssl-context per connection negligible.
695695

696+
* ([Issue 181](https://github.com/boostorg/redis/issues/181)).
697+
See a detailed description of this bug in
698+
[this](https://github.com/boostorg/redis/issues/181#issuecomment-1913346983)
699+
comment.
700+
696701
### Boost 1.84 (First release in Boost)
697702

698703
* Deprecates the `async_receive` overload that takes a response. Users

include/boost/redis/detail/connection_base.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,9 @@ class connection_base {
506506
usage get_usage() const noexcept
507507
{ return usage_; }
508508

509+
auto run_is_canceled() const noexcept
510+
{ return cancel_run_called_; }
511+
509512
private:
510513
using receive_channel_type = asio::experimental::channel<executor_type, void(system::error_code, std::size_t)>;
511514
using runner_type = runner<executor_type>;
@@ -539,6 +542,7 @@ class connection_base {
539542
});
540543

541544
reqs_.erase(point, std::end(reqs_));
545+
542546
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
543547
return ptr->mark_waiting();
544548
});
@@ -575,6 +579,12 @@ class connection_base {
575579
} break;
576580
case operation::run:
577581
{
582+
// Protects the code below from being called more than
583+
// once, see https://github.com/boostorg/redis/issues/181
584+
if (std::exchange(cancel_run_called_, true)) {
585+
return;
586+
}
587+
578588
close();
579589
writer_timer_.cancel();
580590
receive_channel_.cancel();
@@ -601,8 +611,9 @@ class connection_base {
601611
// partition of unwritten requests instead of them all.
602612
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
603613
BOOST_ASSERT_MSG(ptr != nullptr, "Expects non-null pointer.");
604-
if (ptr->is_staged())
614+
if (ptr->is_staged()) {
605615
ptr->mark_written();
616+
}
606617
});
607618
}
608619

@@ -921,6 +932,7 @@ class connection_base {
921932
read_buffer_.clear();
922933
parser_.reset();
923934
on_push_ = false;
935+
cancel_run_called_ = false;
924936
}
925937

926938
asio::ssl::context ctx_;
@@ -942,6 +954,7 @@ class connection_base {
942954
reqs_type reqs_;
943955
resp3::parser parser_{};
944956
bool on_push_ = false;
957+
bool cancel_run_called_ = false;
945958

946959
usage usage_;
947960
};

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ make_test(test_conn_exec_cancel2 20)
4747
make_test(test_conn_echo_stress 20)
4848
make_test(test_conn_run_cancel 20)
4949
make_test(test_issue_50 20)
50+
make_test(test_issue_181 17)
5051

5152
# Coverage
5253
set(

test/test_issue_181.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva ([email protected])
2+
*
3+
* Distributed under the Boost Software License, Version 1.0. (See
4+
* accompanying file LICENSE.txt)
5+
*/
6+
7+
#include <boost/redis/connection.hpp>
8+
#include <boost/redis/logger.hpp>
9+
#include <boost/asio/awaitable.hpp>
10+
#include <boost/asio/use_awaitable.hpp>
11+
#define BOOST_TEST_MODULE conn-quit
12+
#include <boost/test/included/unit_test.hpp>
13+
#include <chrono>
14+
#include <iostream>
15+
#include <memory>
16+
#include <optional>
17+
#include <string>
18+
#include "common.hpp"
19+
20+
namespace net = boost::asio;
21+
using boost::redis::request;
22+
using boost::redis::request;
23+
using boost::redis::response;
24+
using boost::redis::ignore;
25+
using boost::redis::logger;
26+
using boost::redis::config;
27+
using boost::redis::operation;
28+
using boost::redis::connection;
29+
using boost::system::error_code;
30+
using namespace std::chrono_literals;
31+
32+
BOOST_AUTO_TEST_CASE(issue_181)
33+
{
34+
using connection_base = boost::redis::detail::connection_base<net::any_io_executor>;
35+
36+
auto const level = boost::redis::logger::level::debug;
37+
net::io_context ioc;
38+
auto ctx = net::ssl::context{net::ssl::context::tlsv12_client};
39+
connection_base conn{ioc.get_executor(), std::move(ctx), 1000000};
40+
net::steady_timer timer{ioc};
41+
timer.expires_after(std::chrono::seconds{1});
42+
43+
auto run_cont = [&](auto ec){
44+
std::cout << "async_run1: " << ec.message() << std::endl;
45+
};
46+
47+
auto cfg = make_test_config();
48+
cfg.health_check_interval = std::chrono::seconds{0};
49+
cfg.reconnect_wait_interval = std::chrono::seconds{0};
50+
conn.async_run(cfg, boost::redis::logger{level}, run_cont);
51+
BOOST_TEST(!conn.run_is_canceled());
52+
53+
// Uses a timer to wait some time until run has been called.
54+
auto timer_cont = [&](auto ec){
55+
std::cout << "timer_cont: " << ec.message() << std::endl;
56+
BOOST_TEST(!conn.run_is_canceled());
57+
conn.cancel(operation::run);
58+
BOOST_TEST(conn.run_is_canceled());
59+
};
60+
61+
timer.async_wait(timer_cont);
62+
63+
ioc.run();
64+
}

0 commit comments

Comments
 (0)