Cancellation and timeouts
By default, executing a request will wait until a connection to the Redis server is established by
connection::async_run / co_connection::run.
This may take a very long time if the server is down.
For this reason, it is usually a good idea to set a timeout to request execution. The specifics depend on whether you are using Asio or Corosio, but the general idea is similar:
-
Asio
-
Corosio
Use the
asio::cancel_after
completion token:
using namespace std::chrono_literals;
// Compose a request with a SET command
request req;
req.push("SET", "my_key", 42);
// If the request hasn't completed after 10 seconds, it will be cancelled
// and an exception will be thrown.
co_await conn->async_exec(req, ignore, asio::cancel_after(10s));
See our example on timeouts for a full code listing.
cancel_after also works with other completion styles, like
callbacks and futures. It works because async_exec supports the per-operation
cancellation mechanism, which Boost.Asio uses to implement features
like cancel_after and parallel groups. All asynchronous operations
in the library support this mechanism. Please consult the documentation
for individual operations for more info.
Wrap the awaitable with capy::timeout:
using namespace std::chrono_literals;
// Compose a request with a SET command
request req;
req.push("SET", "my_key", 42);
// If the request hasn't completed after 10 seconds, it will be cancelled
// and exec() will complete with an error matching capy::cond::timeout.
auto [ec] = co_await capy::timeout(conn.exec(req, ignore), 10s);
See our example on timeouts for a full code listing.
capy::timeout can be used with any I/O operation exposed by the library.
Combinators like capy::when_any and capy::when_all also work.
Retrying idempotent requests
We mentioned that executing a request waits until the server is up before sending the request. But what happens if there is a communication error after sending the request, but before receiving a response?
In this situation there is no way to know if the request was processed by the server or not.
By default, the library will consider the request as failed,
and execution will fail with an error matching
std::errc::operation_canceled.
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 object contains only idempotent commands,
you can instruct Boost.Redis to retry the request on failure, even
if the library is unsure about the server having processed the request or not.
You can do so by setting cancel_if_unresponded
in request::config
to false:
-
Asio
-
Corosio
// Compose a request
request req;
req.push("SET", "my_key", 42); // idempotent
req.get_config().cancel_if_unresponded = false; // Retry the request even if it was written but not responded
// Makes sure that the key is set, even in the presence of network errors.
// This may suspend for an unspecified period of time if the server is down.
co_await conn->async_exec(req, ignore);
// Compose a request
request req;
req.push("SET", "my_key", 42); // idempotent
req.get_config().cancel_if_unresponded = false; // Retry the request even if it was written but not responded
// Makes sure that the key is set, even in the presence of network errors.
// This may suspend for an unspecified period of time if the server is down.
co_await conn.exec(req, ignore);