Skip to content

Commit ec8a1c7

Browse files
authored
Merge pull request #105 from boostorg/95-improve-the-performance-of-connectionasync_receive
95 improve the performance of connectionasync receive
2 parents 7abfc5f + 3c02a76 commit ec8a1c7

10 files changed

+125
-61
lines changed

CMakeLists.txt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,16 @@ add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp)
7979
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
8080

8181
set(tests_cpp17
82-
test_conn_quit
83-
test_conn_tls
84-
test_low_level
85-
test_conn_exec_retry
86-
test_conn_exec_error
87-
test_request
88-
test_run
89-
test_low_level_sync
90-
test_conn_check_health)
82+
test_conn_quit
83+
test_conn_tls
84+
test_low_level
85+
test_conn_exec_retry
86+
test_conn_exec_error
87+
test_request
88+
test_run
89+
test_low_level_sync
90+
test_conn_check_health
91+
)
9192

9293
set(tests_cpp20
9394
test_conn_exec

CMakePresets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"CMAKE_BUILD_TYPE": "Debug",
6767
"CMAKE_CXX_EXTENSIONS": "OFF",
6868
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
69-
"CMAKE_CXX_COMPILER": "g++-11",
69+
"CMAKE_CXX_COMPILER": "clang++-13",
7070
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
7171
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
7272
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-13",

include/boost/redis/connection_base.hpp

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
#include <boost/system.hpp>
2121
#include <boost/asio/basic_stream_socket.hpp>
2222
#include <boost/asio/bind_executor.hpp>
23-
#include <boost/asio/experimental/channel.hpp>
2423
#include <boost/asio/experimental/parallel_group.hpp>
2524
#include <boost/asio/ip/tcp.hpp>
2625
#include <boost/asio/steady_timer.hpp>
@@ -43,7 +42,7 @@ namespace detail {
4342

4443
template <class Conn>
4544
struct wait_receive_op {
46-
Conn* conn;
45+
Conn* conn_;
4746
asio::coroutine coro{};
4847

4948
template <class Self>
@@ -52,14 +51,14 @@ struct wait_receive_op {
5251
{
5352
BOOST_ASIO_CORO_REENTER (coro)
5453
{
55-
BOOST_ASIO_CORO_YIELD
56-
conn->channel_.async_send(system::error_code{}, 0, std::move(self));
57-
BOOST_REDIS_CHECK_OP0(;);
54+
conn_->read_op_timer_.cancel();
5855

5956
BOOST_ASIO_CORO_YIELD
60-
conn->channel_.async_send(system::error_code{}, 0, std::move(self));
61-
BOOST_REDIS_CHECK_OP0(;);
62-
57+
conn_->read_op_timer_.async_wait(std::move(self));
58+
if (!conn_->is_open() || is_cancelled(self)) {
59+
self.complete(!!ec ? ec : asio::error::operation_aborted);
60+
return;
61+
}
6362
self.complete({});
6463
}
6564
}
@@ -143,7 +142,11 @@ class read_next_op {
143142

144143
++index_;
145144

146-
BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run););
145+
if (ec || redis::detail::is_cancelled(self)) {
146+
conn_->cancel(operation::run);
147+
self.complete(!!ec ? ec : asio::error::operation_aborted, {});
148+
return;
149+
}
147150

148151
read_size_ += n;
149152

@@ -158,7 +161,7 @@ class read_next_op {
158161

159162
template <class Conn, class Adapter>
160163
struct receive_op {
161-
Conn* conn;
164+
Conn* conn_;
162165
Adapter adapter;
163166
std::size_t read_size = 0;
164167
asio::coroutine coro{};
@@ -171,27 +174,32 @@ struct receive_op {
171174
{
172175
BOOST_ASIO_CORO_REENTER (coro)
173176
{
174-
BOOST_ASIO_CORO_YIELD
175-
conn->channel_.async_receive(std::move(self));
176-
BOOST_REDIS_CHECK_OP1(;);
177+
if (!conn_->is_next_push()) {
178+
BOOST_ASIO_CORO_YIELD
179+
conn_->read_op_timer_.async_wait(std::move(self));
180+
if (!conn_->is_open() || is_cancelled(self)) {
181+
self.complete(!!ec ? ec : asio::error::operation_aborted, 0);
182+
return;
183+
}
184+
}
177185

178-
if (conn->use_ssl())
179-
BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self));
186+
if (conn_->use_ssl())
187+
BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer(), conn_->make_dynamic_buffer(), adapter, std::move(self));
180188
else
181-
BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn->next_layer().next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self));
189+
BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer().next_layer(), conn_->make_dynamic_buffer(), adapter, std::move(self));
182190

183191
if (ec || is_cancelled(self)) {
184-
conn->cancel(operation::run);
185-
conn->cancel(operation::receive);
192+
conn_->cancel(operation::run);
193+
conn_->cancel(operation::receive);
186194
self.complete(!!ec ? ec : asio::error::operation_aborted, {});
187195
return;
188196
}
189197

190198
read_size = n;
191199

192-
BOOST_ASIO_CORO_YIELD
193-
conn->channel_.async_receive(std::move(self));
194-
BOOST_REDIS_CHECK_OP1(;);
200+
if (!conn_->is_next_push()) {
201+
conn_->read_op_timer_.cancel();
202+
}
195203

196204
self.complete({}, read_size);
197205
return;
@@ -490,11 +498,12 @@ class connection_base {
490498
, stream_{std::make_unique<next_layer_type>(ex, ctx_)}
491499
, writer_timer_{ex}
492500
, read_timer_{ex}
493-
, channel_{ex}
501+
, read_op_timer_{ex}
494502
, runner_{ex, {}}
495503
{
496504
writer_timer_.expires_at(std::chrono::steady_clock::time_point::max());
497505
read_timer_.expires_at(std::chrono::steady_clock::time_point::max());
506+
read_op_timer_.expires_at(std::chrono::steady_clock::time_point::max());
498507
}
499508

500509
/// Contructs from an execution context.
@@ -622,7 +631,7 @@ class connection_base {
622631
return asio::async_compose
623632
< CompletionToken
624633
, void(system::error_code, std::size_t)
625-
>(redis::detail::receive_op<this_type, decltype(f)>{this, f}, token, channel_);
634+
>(redis::detail::receive_op<this_type, decltype(f)>{this, f}, token, read_op_timer_);
626635
}
627636

628637
/** @brief Starts underlying connection operations.
@@ -689,7 +698,6 @@ class connection_base {
689698
using clock_type = std::chrono::steady_clock;
690699
using clock_traits_type = asio::wait_traits<clock_type>;
691700
using timer_type = asio::basic_waitable_timer<clock_type, clock_traits_type, executor_type>;
692-
using channel_type = asio::experimental::channel<executor_type, void(system::error_code, std::size_t)>;
693701
using runner_type = redis::detail::runner<executor_type>;
694702

695703
auto use_ssl() const noexcept
@@ -761,7 +769,7 @@ class connection_base {
761769
} break;
762770
case operation::receive:
763771
{
764-
channel_.cancel();
772+
read_op_timer_.cancel();
765773
} break;
766774
default: /* ignore */;
767775
}
@@ -885,7 +893,7 @@ class connection_base {
885893
return asio::async_compose
886894
< CompletionToken
887895
, void(system::error_code)
888-
>(redis::detail::wait_receive_op<this_type>{this}, token, channel_);
896+
>(redis::detail::wait_receive_op<this_type>{this}, token, read_op_timer_);
889897
}
890898

891899
void cancel_push_requests()
@@ -991,6 +999,11 @@ class connection_base {
991999
stream_->next_layer().close();
9921000
}
9931001

1002+
bool is_next_push() const noexcept
1003+
{
1004+
return !read_buffer_.empty() && (resp3::to_type(read_buffer_.front()) == resp3::type::push);
1005+
}
1006+
9941007
auto is_open() const noexcept { return stream_->next_layer().is_open(); }
9951008
auto& lowest_layer() noexcept { return stream_->lowest_layer(); }
9961009

@@ -1002,7 +1015,7 @@ class connection_base {
10021015
// not suspend.
10031016
timer_type writer_timer_;
10041017
timer_type read_timer_;
1005-
channel_type channel_;
1018+
timer_type read_op_timer_;
10061019
runner_type runner_;
10071020

10081021
std::string read_buffer_;

tests/common.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ run(
2525
std::shared_ptr<boost::redis::connection> conn,
2626
boost::redis::config cfg,
2727
boost::system::error_code ec,
28-
boost::redis::operation op)
28+
boost::redis::operation op,
29+
boost::redis::logger::level l)
2930
{
30-
conn->async_run(cfg, {}, run_callback{conn, op, ec});
31+
conn->async_run(cfg, {l}, run_callback{conn, op, ec});
3132
}
3233

3334
#ifdef BOOST_ASIO_HAS_CO_AWAIT

tests/common.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ run(
2020
std::shared_ptr<boost::redis::connection> conn,
2121
boost::redis::config cfg = {},
2222
boost::system::error_code ec = boost::asio::error::operation_aborted,
23-
boost::redis::operation op = boost::redis::operation::receive);
23+
boost::redis::operation op = boost::redis::operation::receive,
24+
boost::redis::logger::level l = boost::redis::logger::level::info);
2425

tests/test_conn_check_health.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#define BOOST_TEST_MODULE check-health
1010
#include <boost/test/included/unit_test.hpp>
1111
#include <iostream>
12+
#include <thread>
1213
#include "common.hpp"
1314

1415
namespace net = boost::asio;
@@ -119,5 +120,9 @@ BOOST_AUTO_TEST_CASE(check_health)
119120

120121
BOOST_TEST(!!res1);
121122
BOOST_TEST(!!res2);
123+
124+
// Waits before exiting otherwise it might cause subsequent tests
125+
// to fail.
126+
std::this_thread::sleep_for(std::chrono::seconds{10});
122127
}
123128

tests/test_conn_echo_stress.cpp

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <boost/redis/connection.hpp>
88
#include <boost/asio/co_spawn.hpp>
99
#include <boost/asio/detached.hpp>
10+
#include <boost/asio/deferred.hpp>
1011
#include <boost/system/errc.hpp>
1112
#define BOOST_TEST_MODULE echo-stress
1213
#include <boost/test/included/unit_test.hpp>
@@ -38,44 +39,75 @@ auto push_consumer(std::shared_ptr<connection> conn, int expected) -> net::await
3839
conn->cancel();
3940
}
4041

41-
auto echo_session(std::shared_ptr<connection> conn, std::string id, int n) -> net::awaitable<void>
42+
auto
43+
echo_session(
44+
std::shared_ptr<connection> conn,
45+
std::shared_ptr<request> pubs,
46+
std::string id,
47+
int n) -> net::awaitable<void>
4248
{
4349
auto ex = co_await net::this_coro::executor;
4450

4551
request req;
46-
response<ignore_t, std::string> resp;
52+
response<ignore_t, std::string, ignore_t> resp;
4753

4854
for (auto i = 0; i < n; ++i) {
4955
auto const msg = id + "/" + std::to_string(i);
5056
//std::cout << msg << std::endl;
51-
req.push("HELLO", 3);
57+
req.push("HELLO", 3); // Just to mess around.
5258
req.push("PING", msg);
53-
req.push("SUBSCRIBE", "channel");
59+
req.push("PING", "lsls"); // TODO: Change to HELLO after fixing issue 105.
5460
boost::system::error_code ec;
5561
co_await conn->async_exec(req, resp, redir(ec));
56-
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
57-
BOOST_CHECK_EQUAL(msg, std::get<1>(resp).value());
62+
63+
BOOST_REQUIRE_EQUAL(ec, boost::system::error_code{});
64+
BOOST_REQUIRE_EQUAL(msg, std::get<1>(resp).value());
5865
req.clear();
5966
std::get<1>(resp).value().clear();
67+
68+
co_await conn->async_exec(*pubs, ignore, net::deferred);
6069
}
6170
}
6271

6372
auto async_echo_stress() -> net::awaitable<void>
6473
{
6574
auto ex = co_await net::this_coro::executor;
6675
auto conn = std::make_shared<connection>(ex);
76+
config cfg;
77+
cfg.health_check_interval = std::chrono::seconds::zero();
78+
run(conn, cfg,
79+
boost::asio::error::operation_aborted,
80+
boost::redis::operation::receive,
81+
boost::redis::logger::level::crit);
82+
83+
request req;
84+
req.push("SUBSCRIBE", "channel");
85+
co_await conn->async_exec(req, ignore, net::deferred);
6786

87+
// Number of coroutines that will send pings sharing the same
88+
// connection to redis.
6889
int const sessions = 500;
90+
91+
// The number of pings that will be sent by each session.
6992
int const msgs = 1000;
70-
int total = sessions * msgs;
7193

72-
net::co_spawn(ex, push_consumer(conn, total), net::detached);
94+
// The number of publishes that will be sent by each session with
95+
// each message.
96+
int const n_pubs = 10;
7397

74-
for (int i = 0; i < sessions; ++i)
75-
net::co_spawn(ex, echo_session(conn, std::to_string(i), msgs), net::detached);
98+
// This is the total number of pushes we will receive.
99+
int total_pushes = sessions * msgs * n_pubs + 1;
76100

101+
auto pubs = std::make_shared<request>();
102+
for (int i = 0; i < n_pubs; ++i)
103+
pubs->push("PUBLISH", "channel", "payload");
77104

78-
run(conn);
105+
// Op that will consume the pushes counting down until all expected
106+
// pushes have been received.
107+
net::co_spawn(ex, push_consumer(conn, total_pushes), net::detached);
108+
109+
for (int i = 0; i < sessions; ++i)
110+
net::co_spawn(ex, echo_session(conn, pubs, std::to_string(i), msgs), net::detached);
79111
}
80112

81113
BOOST_AUTO_TEST_CASE(echo_stress)

tests/test_conn_exec.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
5151
conn->async_exec(req1, ignore, [&](auto ec, auto){
5252
// Second callback to the called.
5353
std::cout << "req1" << std::endl;
54-
BOOST_TEST(!ec);
54+
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
5555
BOOST_TEST(!seen2);
5656
BOOST_TEST(seen3);
5757
seen1 = true;
@@ -60,7 +60,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
6060
conn->async_exec(req2, ignore, [&](auto ec, auto){
6161
// Last callback to the called.
6262
std::cout << "req2" << std::endl;
63-
BOOST_TEST(!ec);
63+
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
6464
BOOST_TEST(seen1);
6565
BOOST_TEST(seen3);
6666
seen2 = true;
@@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
7171
conn->async_exec(req3, ignore, [&](auto ec, auto){
7272
// Callback that will be called first.
7373
std::cout << "req3" << std::endl;
74-
BOOST_TEST(!ec);
74+
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
7575
BOOST_TEST(!seen1);
7676
BOOST_TEST(!seen2);
7777
seen3 = true;

0 commit comments

Comments
 (0)