mirror of
https://github.com/boostorg/redis.git
synced 2026-01-20 17:12:09 +00:00
Compare commits
6 Commits
better_sub
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3324e7ee71 | ||
|
|
c9375a44eb | ||
|
|
5af2f9cb72 | ||
|
|
c88f9f4666 | ||
|
|
682bec618d | ||
|
|
cd46e39f36 |
@@ -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])
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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_;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user