2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-20 17:12:09 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
Ruben Perez
3324e7ee71 Merge branch 'develop' 2025-11-14 10:53:00 +01:00
Anarthal (Rubén Pérez)
c9375a44eb Adds release notes for Boost 1.89 and Boost 1.90 (#347) 2025-11-03 14:53:32 +01:00
Ruben Perez
5af2f9cb72 Merge branch 'develop' 2025-11-03 10:34:28 +01:00
Anarthal (Rubén Pérez)
c88f9f4666 Fixes a potential use-after-move bug in async_connect (#343) 2025-10-30 14:49:57 +01:00
Anarthal (Rubén Pérez)
682bec618d Adds request::append() (#342)
close #341
2025-10-27 15:18:46 +01:00
Anarthal (Rubén Pérez)
cd46e39f36 Boost 1.90.0 beta1: merge develop to master 2025-10-27 11:03:05 +01:00
5 changed files with 311 additions and 18 deletions

View File

@@ -7,6 +7,146 @@
= Changelog
== Boost 1.90
* (Pull request https://github.com/boostorg/redis/pull/310[310])
Improves the per-operation support in `async_exec()`. Requests can now
be cancelled at any point, and cancellations don't interfere with other
requests anyhow. In previous versions, a cancellation could cause
`async_run()` to be cancelled, making cancellations unpredictable.
* (Issue https://github.com/boostorg/redis/issues/226[226])
Added support for the `asio::cancel_after` and `asio::cancel_at`
completion tokens in `async_exec()` and `async_run()`.
* (Issue https://github.com/boostorg/redis/issues/319[319])
Added support for per-operation cancellation in `async_run()`.
* (Pull request https://github.com/boostorg/redis/pull/329[329]
and https://github.com/boostorg/redis/pull/334[334])
The `cancel_on_connection_lost` and `cancel_if_not_connected`
flags in `request::config` have been deprecated, and will be removed
in subsequent releases. To limit the time span that `async_exec`
might take, use `asio::cancel_after`, instead.
`cancel_on_connection_lost` default has been changed to `false`.
This shouldn't cause much impact, since `cancel_on_connection_lost`
is not a reliable way to limit the time span that `async_exec` might take.
* (Pull request https://github.com/boostorg/redis/pull/321[321])
Calling `cancel` with `operation::resolve`, `operation::connect`,
`operation::ssl_handshake`, `operation::reconnection` and
`operation::health_check` is now deprecated.
These enumerators will be removed in subsequent releases.
Users should employ `cancel(operation::run)`, instead.
* (Issue github.com/boostorg/redis/issues/302[302] and
pull request https://github.com/boostorg/redis/pull/303[303])
Added support for custom setup requests using `config::setup`
and `config::use_setup`. When setting these fields, users can
replace the library-generated `HELLO` request by any other arbitrary request.
This request is executed every time a new physical connection with the server
is established. This feature can be used to interact with systems that don't
support `HELLO`, to handle authentication and to connect to replicas.
* (Pull request https://github.com/boostorg/redis/pull/305[305])
`request::config::hello_with_priority` and `request::has_hello_priority()` have
been deprecated and will be removed in subsequent releases.
This flag is not well specified and should only be used by the library.
If you need to execute a request before any other, use `config::setup`, instead.
* (Issue https://github.com/boostorg/redis/issues/296[296])
Valkey long-term support: we guarantee Valkey compatibility
starting with this release. Previous releases may also work,
but have not been tested with this database system.
* (Issue https://github.com/boostorg/redis/issues/341[341])
Adds a `request::append()` function, to concatenate request objects.
* (Issue https://github.com/boostorg/redis/issues/104[104])
The health checker algorithm has been redesigned to avoid
false positives under heavy loads. `PING` commands are now
only issued when the connection is idle, instead of periodically.
* (Pull request https://github.com/boostorg/redis/pull/283[283])
Added `config::read_buffer_append_size`, which allows to control
the expansion of the connection's read buffer.
* (Pull request https://github.com/boostorg/redis/pull/311[311])
Added `usage::bytes_rotated`, which measures data copying when
reading and parsing data from the server.
* (Issue https://github.com/boostorg/redis/issues/298[298])
Added support for authenticating users with an empty password
but a non-default username.
* (Issue https://github.com/boostorg/redis/issues/318[318])
Fixed a number of race conditions in the `cancel()` function
of `connection` and `basic_connection` that could cause
cancellations to be ignored.
* (Issue https://github.com/boostorg/redis/issues/290[290])
Fixed a problem that could cause an error during `HELLO`
to make subsequent `HELLO` attempts during reconnection to fail.
* (Issue https://github.com/boostorg/redis/issues/297[297])
Errors during `HELLO` are now correctly logged.
* (Issue https://github.com/boostorg/redis/issues/287[287])
Fixed a bug causing an exception to be thrown when parsing
a response that contains an intermediate error into a `generic_response`.
== Boost 1.89
* (Pull request https://github.com/boostorg/redis/pull/256[256],
https://github.com/boostorg/redis/pull/266[266] and
https://github.com/boostorg/redis/pull/273[273])
The following members in `connection` and `basic_connection` are now deprecated
and will be removed in subsequent releases:
* `next_layer()` and `next_layer_type`: there is no reason to access the underlying stream object directly.
Connection member functions should be used, instead.
* `get_ssl_context()`: SSL contexts should never be modified after an `asio::ssl::stream`
object has been created from them. Properties should be set before passing the context
to the constructor. There is no reason to access the SSL context after that.
* `reset_stream()`: connection internals have been refactored to reset the SSL stream
automatically when required. This function is now a no-op.
* The `async_run()` overload taking no parameters: use the `async_run`
overload taking a `config` object explicitly, instead.
* (Issue https://github.com/boostorg/redis/issues/213[213])
The logging interface has been re-written:
* Logging can now be customized by passing a function object
to the `logger` constructor. This allows integration with
third-party logging libraries, like spdlog.
This new logging interface is public and will be maintained long-term.
* The old, unstable interface consisting of `logger::on_xxx` functions has been removed.
* `connection` and `basic_connection` constructors now accept a `logger` object.
This is now the preferred way to configure logging.
The `async_run()` overload taking a `logger` object is now deprecated, and will
be removed in subsequent releases.
* `config::log_prefix` is now deprecated, and will
be removed in subsequent releases. Users can achieve the same effect
by passing a custom logging function to the `logger` constructor.
* The default logging function, which prints to `stderr`,
is now based on `printf` and is thus thread-safe.
The old function used `std::cerr` and could result to interleaved
output in multi-threaded programs.
* The default log level is now `logger::level::info`,
down from `logger::level::debug`. This results in less verbose output by default.
* (Issue https://github.com/boostorg/redis/issues/272[272])
Added support for connecting to Redis using UNIX domain sockets.
This feature can be accessed using `config::unix_socket`.
* (Issue https://github.com/boostorg/redis/issues/255[255])
Fixed an issue that caused `async_run` to complete when a connection
establishment error is encountered, even if `config::reconnect_wait_interval`
specified reconnection.
* (Issue https://github.com/boostorg/redis/issues/265[265])
`connection::async_exec` now uses `ignore` as the default response,
rather than having no default response. This matches
`basic_connection::async_exec` behavior.
* (Issue https://github.com/boostorg/redis/issues/260[260])
Fixed a memory corruption affecting the logger's prefix
configured in `config::log_prefix`.
* (Pull request https://github.com/boostorg/redis/pull/254[254])
Fixed some warnings regarding unused variables, name shadowing
and narrowing conversions.
* (Issue https://github.com/boostorg/redis/issues/252[252])
Fixed a bug that causes reconnection to ignore the reconnection
wait time configured in `config::reconnect_wait_interval`.
* (Issue https://github.com/boostorg/redis/issues/238[238])
Fixed a bug introduced in Boost 1.88 that caused `response<T>` to
not compile for some integral types.
* (Issue https://github.com/boostorg/redis/issues/247[247])
The documentation has been modernized, and a more complete
reference section has been added.
* Part of the internals have been refactored to allow for better
testing and reduce compile times.
== Boost 1.88
* (Issue https://github.com/boostorg/redis/issues/233[233])

View File

@@ -46,12 +46,13 @@ class redis_stream {
void reset_stream() { stream_ = {resolv_.get_executor(), ssl_ctx_}; }
struct connect_op {
redis_stream& obj;
redis_stream& obj_;
connect_fsm fsm_;
template <class Self>
void execute_action(Self& self, connect_action act)
{
auto& obj = this->obj_; // prevent use-after-move errors
const auto& cfg = fsm_.get_config();
switch (act.type) {
@@ -109,7 +110,7 @@ class redis_stream {
auto act = fsm_.resume(
ec,
selected_endpoint,
obj.st_,
obj_.st_,
self.get_cancellation_state().cancelled());
execute_action(self, act);
}
@@ -121,8 +122,9 @@ class redis_stream {
system::error_code ec,
asio::ip::tcp::resolver::results_type endpoints)
{
auto act = fsm_.resume(ec, endpoints, obj.st_, self.get_cancellation_state().cancelled());
auto act = fsm_.resume(ec, endpoints, obj_.st_, self.get_cancellation_state().cancelled());
if (act.type == connect_action_type::tcp_connect) {
auto& obj = this->obj_; // prevent use-after-free errors
asio::async_connect(
obj.stream_.next_layer(),
std::move(endpoints),
@@ -135,7 +137,7 @@ class redis_stream {
template <class Self>
void operator()(Self& self, system::error_code ec = {})
{
auto act = fsm_.resume(ec, obj.st_, self.get_cancellation_state().cancelled());
auto act = fsm_.resume(ec, obj_.st_, self.get_cancellation_state().cancelled());
execute_action(self, act);
}
};

View File

@@ -31,3 +31,10 @@ request make_hello_request()
}
} // namespace boost::redis::detail
void boost::redis::request::append(const request& other)
{
payload_ += other.payload_;
commands_ += other.commands_;
expected_responses_ += other.expected_responses_;
}

View File

@@ -338,6 +338,17 @@ public:
push_range(cmd, cbegin(range), cend(range));
}
/** @brief Appends the commands in another request to the end of the request.
*
* Appends all the commands contained in `other` to the end of
* this request. Configuration flags in `*this`,
* like @ref config::cancel_if_unresponded, are *not* modified,
* even if `other` has a different config than `*this`.
*
* @param other The request containing the commands to append.
*/
void append(const request& other);
private:
void check_cmd(std::string_view cmd)
{

View File

@@ -4,55 +4,188 @@
* accompanying file LICENSE.txt)
*/
#include <iostream>
#define BOOST_TEST_MODULE request
#include <boost/redis/request.hpp>
#include <boost/test/included/unit_test.hpp>
#include <boost/core/lightweight_test.hpp>
#include <map>
#include <string>
#include <string_view>
using boost::redis::request;
// TODO: Serialization.
BOOST_AUTO_TEST_CASE(single_arg_allocator)
namespace {
void test_push_no_args()
{
request req1;
req1.push("PING");
BOOST_CHECK_EQUAL(req1.payload(), std::string{"*1\r\n$4\r\nPING\r\n"});
BOOST_TEST_EQ(req1.payload(), "*1\r\n$4\r\nPING\r\n");
}
BOOST_AUTO_TEST_CASE(arg_int)
void test_push_int()
{
request req;
req.push("PING", 42);
BOOST_CHECK_EQUAL(req.payload(), std::string{"*2\r\n$4\r\nPING\r\n$2\r\n42\r\n"});
BOOST_TEST_EQ(req.payload(), "*2\r\n$4\r\nPING\r\n$2\r\n42\r\n");
}
BOOST_AUTO_TEST_CASE(multiple_args)
void test_push_multiple_args()
{
char const* res = "*5\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n$2\r\nEX\r\n$1\r\n2\r\n";
request req;
req.push("SET", "key", "value", "EX", "2");
BOOST_CHECK_EQUAL(req.payload(), std::string{res});
BOOST_TEST_EQ(req.payload(), res);
}
BOOST_AUTO_TEST_CASE(container_and_range)
void test_push_range()
{
std::map<std::string, std::string> in{
{"key1", "value1"},
{"key2", "value2"}
};
char const* res =
constexpr std::string_view expected =
"*6\r\n$4\r\nHSET\r\n$3\r\nkey\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$"
"6\r\nvalue2\r\n";
request req1;
req1.push_range("HSET", "key", in);
BOOST_CHECK_EQUAL(req1.payload(), std::string{res});
BOOST_TEST_EQ(req1.payload(), expected);
request req2;
req2.push_range("HSET", "key", std::cbegin(in), std::cend(in));
BOOST_CHECK_EQUAL(req2.payload(), std::string{res});
BOOST_TEST_EQ(req2.payload(), expected);
}
// Append
void test_append()
{
request req1;
req1.push("PING", "req1");
request req2;
req2.push("GET", "mykey");
req2.push("GET", "other");
req1.append(req2);
constexpr std::string_view expected =
"*2\r\n$4\r\nPING\r\n$4\r\nreq1\r\n"
"*2\r\n$3\r\nGET\r\n$5\r\nmykey\r\n"
"*2\r\n$3\r\nGET\r\n$5\r\nother\r\n";
BOOST_TEST_EQ(req1.payload(), expected);
BOOST_TEST_EQ(req1.get_commands(), 3u);
BOOST_TEST_EQ(req1.get_expected_responses(), 3u);
}
// Commands without responses are handled correctly
void test_append_no_response()
{
request req1;
req1.push("PING", "req1");
request req2;
req2.push("SUBSCRIBE", "mychannel");
req2.push("GET", "other");
req1.append(req2);
constexpr std::string_view expected =
"*2\r\n$4\r\nPING\r\n$4\r\nreq1\r\n"
"*2\r\n$9\r\nSUBSCRIBE\r\n$9\r\nmychannel\r\n"
"*2\r\n$3\r\nGET\r\n$5\r\nother\r\n";
BOOST_TEST_EQ(req1.payload(), expected);
BOOST_TEST_EQ(req1.get_commands(), 3u);
BOOST_TEST_EQ(req1.get_expected_responses(), 2u);
}
// Flags are not modified by append
void test_append_flags()
{
request req1;
req1.get_config().cancel_if_not_connected = false;
req1.get_config().cancel_if_unresponded = false;
req1.get_config().cancel_on_connection_lost = false;
req1.push("PING", "req1");
request req2;
req2.get_config().cancel_if_not_connected = true;
req2.get_config().cancel_if_unresponded = true;
req2.get_config().cancel_on_connection_lost = true;
req2.push("GET", "other");
req1.append(req2);
constexpr std::string_view expected =
"*2\r\n$4\r\nPING\r\n$4\r\nreq1\r\n"
"*2\r\n$3\r\nGET\r\n$5\r\nother\r\n";
BOOST_TEST_EQ(req1.payload(), expected);
BOOST_TEST_NOT(req1.get_config().cancel_if_not_connected);
BOOST_TEST_NOT(req1.get_config().cancel_if_unresponded);
BOOST_TEST_NOT(req1.get_config().cancel_on_connection_lost);
}
// Empty requests don't cause problems with append
void test_append_target_empty()
{
request req1;
request req2;
req2.push("GET", "other");
req1.append(req2);
constexpr std::string_view expected = "*2\r\n$3\r\nGET\r\n$5\r\nother\r\n";
BOOST_TEST_EQ(req1.payload(), expected);
BOOST_TEST_EQ(req1.get_commands(), 1u);
BOOST_TEST_EQ(req1.get_expected_responses(), 1u);
}
void test_append_source_empty()
{
request req1;
req1.push("GET", "other");
request req2;
req1.append(req2);
constexpr std::string_view expected = "*2\r\n$3\r\nGET\r\n$5\r\nother\r\n";
BOOST_TEST_EQ(req1.payload(), expected);
BOOST_TEST_EQ(req1.get_commands(), 1u);
BOOST_TEST_EQ(req1.get_expected_responses(), 1u);
}
void test_append_both_empty()
{
request req1;
request req2;
req1.append(req2);
BOOST_TEST_EQ(req1.payload(), "");
BOOST_TEST_EQ(req1.get_commands(), 0u);
BOOST_TEST_EQ(req1.get_expected_responses(), 0u);
}
} // namespace
int main()
{
test_push_no_args();
test_push_int();
test_push_multiple_args();
test_push_range();
test_append();
test_append_no_response();
test_append_flags();
test_append_target_empty();
test_append_source_empty();
test_append_both_empty();
return boost::report_errors();
}