-
Notifications
You must be signed in to change notification settings - Fork 43
Adds support for asio::cancel_after #324
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
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
3c1a677
Failing test
anarthal 55570ac
Further failing tests
anarthal d526b88
Run initiation
anarthal d6139ef
Refactor test
anarthal 3c5dce4
async_exec initiation
anarthal c4f1605
Exec test
anarthal 5e3e367
Receive tst
anarthal e96e65f
Includes
anarthal c3e1d61
Receive test fixes
anarthal 9f05a25
First cancellation page
anarthal c927c2a
Add example
anarthal fe83e77
Empty commit
anarthal a6f4c92
Merge branch 'develop' into feature/cancel-after-2
anarthal f8362b2
Fix cancellation slot binding in async_run
anarthal 5ea3fa1
Extend conn_run_cancel tests
anarthal 790d3b9
Rephrase
anarthal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// | ||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com), | ||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com) | ||
// | ||
// Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
// | ||
|
||
= Cancellation | ||
|
||
Requests may take a very long time. If the server is down, they may suspend forever, | ||
waiting for the server to be up. Fortunately, requests can be cancelled after | ||
a certain time using asio::cancel_after: | ||
|
||
``` | ||
request req; | ||
// ... | ||
|
||
co_await conn.async_exec(req, resp, asio::cancel_after(10s)); | ||
``` | ||
|
||
If the request hasn't been responded after 10 seconds, it will | ||
fail with `asio::error::operation_aborted`. With the coroutine | ||
usage above, this means a `boost::system::system_error` exception | ||
with the error code mentioned above. | ||
|
||
== Retrying idempotent requests | ||
|
||
By default, the library waits until the server is up, | ||
and then sends the request. But what happens if there is a communication | ||
error after sending the request, but before receiving a response? | ||
|
||
In this situation, we don't know if the request was processed by the server or not. | ||
And we have no way to know it. By default, the library mark these requests as | ||
failed with `asio::error::operation_aborted`. (TODO: do we want another error code here?). | ||
|
||
Some requests can be executed several times and result in the same outcome | ||
as executing them only once. We say that these requests are idempotent. | ||
The `SET` command is idempotent, while `INCR` is not. | ||
|
||
If you know that a request contains only idempotent commands, | ||
you can instruct Redis to retry the request on failure, even | ||
if the library is unsure about whether the server received the request or not. | ||
You can do so by setting request::config::cancel_if_unresponded to false: | ||
|
||
``` | ||
request req; | ||
req.push("SET", "my_key", 42); // idempotent | ||
req.get_config().cancel_on_connection_lost = false; // TODO: we shouldn't need this | ||
req.get_config().cancel_if_unresponded = false; // retry | ||
|
||
// Makes sure that the key is set, even in the presence of network errors. | ||
// Note that if the server is down, the current coroutine will remain suspended | ||
// until the server is capable of serving requests again (e.g. until a process manager restarts the server). | ||
// Use cancel_after as seen above if you need to limit this time. | ||
co_await conn.async_exec(req, ignore); | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* Copyright (c) 2018-2022 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/asio/cancel_after.hpp> | ||
#include <boost/asio/co_spawn.hpp> | ||
#include <boost/asio/consign.hpp> | ||
#include <boost/asio/detached.hpp> | ||
|
||
#include <iostream> | ||
|
||
#if defined(BOOST_ASIO_HAS_CO_AWAIT) | ||
|
||
namespace asio = boost::asio; | ||
using boost::redis::request; | ||
using boost::redis::response; | ||
using boost::redis::config; | ||
using boost::redis::connection; | ||
using namespace std::chrono_literals; | ||
|
||
// Called from the main function (see main.cpp) | ||
auto co_main(config cfg) -> asio::awaitable<void> | ||
{ | ||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor); | ||
conn->async_run(cfg, asio::consign(asio::detached, conn)); | ||
|
||
// A request containing only a ping command. | ||
request req; | ||
req.push("PING", "Hello world"); | ||
|
||
// Response where the PONG response will be stored. | ||
response<std::string> resp; | ||
|
||
// Executes the request with a timeout. If the server is down, | ||
// async_exec will wait until it's back again, so it, | ||
// may suspend for a long time. | ||
// For this reason, it's good practice to set a timeout to requests with cancel_after. | ||
// If the request hasn't completed after 10 seconds, an exception will be thrown. | ||
co_await conn->async_exec(req, resp, asio::cancel_after(10s)); | ||
conn->cancel(); | ||
|
||
std::cout << "PING: " << std::get<0>(resp).value() << std::endl; | ||
} | ||
|
||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// | ||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com), | ||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com) | ||
// | ||
// Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
// | ||
|
||
#include <boost/redis/connection.hpp> | ||
#include <boost/redis/ignore.hpp> | ||
#include <boost/redis/request.hpp> | ||
#include <boost/redis/response.hpp> | ||
|
||
#include <boost/asio/cancel_after.hpp> | ||
#include <boost/asio/error.hpp> | ||
#include <boost/asio/experimental/channel_error.hpp> | ||
#include <boost/asio/io_context.hpp> | ||
#include <boost/core/lightweight_test.hpp> | ||
|
||
#include "common.hpp" | ||
|
||
using namespace std::chrono_literals; | ||
namespace asio = boost::asio; | ||
using boost::system::error_code; | ||
using boost::redis::request; | ||
using boost::redis::basic_connection; | ||
using boost::redis::connection; | ||
using boost::redis::ignore; | ||
using boost::redis::generic_response; | ||
|
||
namespace { | ||
|
||
template <class Connection> | ||
void test_run() | ||
{ | ||
// Setup | ||
asio::io_context ioc; | ||
Connection conn{ioc}; | ||
bool run_finished = false; | ||
|
||
// Call the function with a very short timeout | ||
conn.async_run(make_test_config(), asio::cancel_after(1ms, [&](error_code ec) { | ||
BOOST_TEST_EQ(ec, asio::error::operation_aborted); | ||
run_finished = true; | ||
})); | ||
|
||
ioc.run_for(test_timeout); | ||
|
||
BOOST_TEST(run_finished); | ||
} | ||
|
||
template <class Connection> | ||
void test_exec() | ||
{ | ||
// Setup | ||
asio::io_context ioc; | ||
Connection conn{ioc}; | ||
bool exec_finished = false; | ||
|
||
request req; | ||
req.push("PING", "cancel_after"); | ||
|
||
// Call the function with a very short timeout. | ||
// The connection is not being run, so these can't succeed | ||
conn.async_exec(req, ignore, asio::cancel_after(1ms, [&](error_code ec, std::size_t) { | ||
BOOST_TEST_EQ(ec, asio::error::operation_aborted); | ||
exec_finished = true; | ||
})); | ||
|
||
ioc.run_for(test_timeout); | ||
|
||
BOOST_TEST(exec_finished); | ||
} | ||
|
||
template <class Connection> | ||
void test_receive() | ||
{ | ||
// Setup | ||
asio::io_context ioc; | ||
Connection conn{ioc}; | ||
bool receive_finished = false; | ||
generic_response resp; | ||
conn.set_receive_response(resp); | ||
|
||
// Call the function with a very short timeout. | ||
conn.async_receive(asio::cancel_after(1ms, [&](error_code ec, std::size_t) { | ||
BOOST_TEST_EQ(ec, asio::experimental::channel_errc::channel_cancelled); | ||
receive_finished = true; | ||
})); | ||
|
||
ioc.run_for(test_timeout); | ||
|
||
BOOST_TEST(receive_finished); | ||
} | ||
|
||
} // namespace | ||
|
||
int main() | ||
{ | ||
test_run<basic_connection<asio::io_context::executor_type>>(); | ||
test_run<connection>(); | ||
|
||
test_exec<basic_connection<asio::io_context::executor_type>>(); | ||
test_exec<connection>(); | ||
|
||
test_receive<basic_connection<asio::io_context::executor_type>>(); | ||
test_receive<connection>(); | ||
|
||
return boost::report_errors(); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.