Skip to content

Commit

Permalink
6.3.0 (#142)
Browse files Browse the repository at this point in the history
* fix: add timeout to split stream close (#133)
* fix: cluster replica discovery (#134)
* fix: compile error when enable full-tracing (#139)
* feat: vec tuple response type conversions (#139)
---------

Co-authored-by: liyixin <[email protected]>
Co-authored-by: usrtax <[email protected]>
  • Loading branch information
3 people authored May 15, 2023
1 parent 516f2fd commit b3f89b4
Show file tree
Hide file tree
Showing 20 changed files with 478 additions and 242 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
## 6.3.0

* Fix cluster replica discovery with Elasticache
* Fix cluster replica `READONLY` usage
* Fix compilation error with `full-tracing`
* Support `Vec<(T1, T2, ...)>` with `FromRedis`

## 6.2.1

* Fix cluster failover with paused nodes.
* Fix cluster failover with paused nodes

## 6.2.0

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fred"
version = "6.2.1"
version = "6.3.0"
authors = ["Alec Embke <[email protected]>"]
edition = "2021"
description = "An async Redis client built on Tokio."
Expand Down
40 changes: 40 additions & 0 deletions src/clients/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,46 @@ use crate::{
/// A struct for interacting with individual nodes in a cluster.
///
/// See [with_cluster_node](crate::clients::RedisClient::with_cluster_node) for more information.
///
/// ```
/// # use fred::prelude::*;
/// async fn example(client: &RedisClient) -> Result<(), RedisError> {
/// // discover servers via the `RedisConfig` or active connections
/// let connections = client.active_connections().await?;
///
/// // ping each node in the cluster individually
/// for server in connections.into_iter() {
/// let _: () = client.with_cluster_node(server).ping().await?;
/// }
///
/// // or use the cached cluster routing table to discover servers
/// let servers = client
/// .cached_cluster_state()
/// .expect("Failed to read cached cluster state")
/// .unique_primary_nodes();
/// for server in servers {
/// // verify the server address with `CLIENT INFO`
/// let server_addr = client
/// .with_cluster_node(&server)
/// .client_info::<String>()
/// .await?
/// .split(" ")
/// .find_map(|s| {
/// let parts: Vec<&str> = s.split("=").collect();
/// if parts[0] == "laddr" {
/// Some(parts[1].to_owned())
/// } else {
/// None
/// }
/// })
/// .expect("Failed to read or parse client info.");
///
/// assert_eq!(server_addr, server.to_string());
/// }
///
/// Ok(())
/// }
/// ```
#[derive(Clone)]
pub struct Node {
inner: Arc<RedisClientInner>,
Expand Down
43 changes: 1 addition & 42 deletions src/clients/redis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,53 +242,12 @@ impl RedisClient {
Pipeline::from(self.clone())
}

/// Send subsequent commands to the provided cluster node.
/// Send commands to the provided cluster node.
///
/// The caller will receive a `RedisErrorKind::Cluster` error if the provided server does not exist.
///
/// The client will still automatically follow `MOVED` errors via this interface. Callers may not notice this, but
/// incorrect server arguments here could result in unnecessary calls to refresh the cached cluster routing table.
///
/// ```
/// # use fred::prelude::*;
///
/// async fn example(client: &RedisClient) -> Result<(), RedisError> {
/// // discover servers via the `RedisConfig` or active connections
/// let connections = client.active_connections().await?;
///
/// // ping each node in the cluster individually
/// for server in connections.into_iter() {
/// let _: () = client.with_cluster_node(server).ping().await?;
/// }
///
/// // or use the cached cluster routing table to discover servers
/// let servers = client
/// .cached_cluster_state()
/// .expect("Failed to read cached cluster state")
/// .unique_primary_nodes();
/// for server in servers {
/// // verify the server address with `CLIENT INFO`
/// let server_addr = client
/// .with_cluster_node(&server)
/// .client_info::<String>()
/// .await?
/// .split(" ")
/// .find_map(|s| {
/// let parts: Vec<&str> = s.split("=").collect();
/// if parts[0] == "laddr" {
/// Some(parts[1].to_owned())
/// } else {
/// None
/// }
/// })
/// .expect("Failed to read or parse client info.");
///
/// assert_eq!(server_addr, server.to_string());
/// }
///
/// Ok(())
/// }
/// ```
pub fn with_cluster_node<S>(&self, server: S) -> Node
where
S: Into<Server>,
Expand Down
40 changes: 0 additions & 40 deletions src/clients/replica.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,46 +35,6 @@ use tokio::sync::oneshot::channel as oneshot_channel;
/// or when any connection closes.
///
/// [Redis replication is asynchronous](https://redis.io/docs/management/replication/).
// ### Cluster Replication
//
// In a clustered deployment replicas may redirect callers back to primary nodes, even with read-only commands,
// depending on the server configuration. The client will automatically follow these redirections, but callers should
// be aware of this behavior for monitoring or tracing purposes.
//
// #### Example
//
// ```bash
// // connect to a primary node, print cluster and replica info, and `GET bar`
// foo@d85c70fd4fc0:/project$ redis-cli -h 172.21.0.5 -p 30001
// 172.21.0.5:30001> cluster nodes
// 60ca8d301ef624956e847e6e6ecc865a36513bbe 172.21.0.3:30001@40001 slave f837e4056f564ab7fd69c24264279a1bd81d6420 0 1674165394000 3 connected
// ddc30573f0c7ee1f79d7f263e2f83d7b83ad0ba0 172.21.0.8:30001@40001 slave 101b2a992c6c909d807d4c5fbd149bcc28e63ef8 0 1674165396000 2 connected
// 101b2a992c6c909d807d4c5fbd149bcc28e63ef8 172.21.0.2:30001@40001 master - 0 1674165395807 2 connected 5461-10922
// 38a7f9d3e440a37adf42f2ceddd9ad52bfb4186e 172.21.0.7:30001@40001 slave bd48cbd28cd927a284bab4424bd41b077a25acb6 0 1674165396810 1 connected
// f837e4056f564ab7fd69c24264279a1bd81d6420 172.21.0.4:30001@40001 master - 0 1674165395000 3 connected 10923-16383
// bd48cbd28cd927a284bab4424bd41b077a25acb6 172.21.0.5:30001@40001 myself,master - 0 1674165393000 1 connected 0-5460
// 172.21.0.5:30001> info replication
// # Replication
// role:master
// connected_slaves:1
// slave0:ip=172.21.0.7,port=30001,state=online,offset=183696,lag=0
// [truncated]
// 172.21.0.5:30001> get bar
// "2"
//
// // connect to the associated replica and `GET bar`
// foo@d85c70fd4fc0:/project$ redis-cli -h 172.21.0.7 -p 30001
// 172.21.0.7:30001> role
// 1) "slave"
// 2) "172.21.0.5"
// 3) (integer) 30001
// 4) "connected"
// 5) (integer) 185390
// 172.21.0.7:30001> get bar
// (error) MOVED 5061 172.21.0.5:30001
// ```
//
// **This can result in unexpected latency or errors depending on the client configuration.**
#[derive(Clone)]
#[cfg_attr(docsrs, doc(cfg(feature = "replicas")))]
pub struct Replicas {
Expand Down
2 changes: 1 addition & 1 deletion src/modules/mocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ impl Mocks for SimpleMap {
///
/// ```rust
/// #[tokio::test]
/// async fn should_use_echo_mock() {
/// async fn should_use_buffer_mock() {
/// let buffer = Arc::new(Buffer::new());
/// let config = RedisConfig {
/// mocks: buffer.clone(),
Expand Down
12 changes: 11 additions & 1 deletion src/modules/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,17 @@ where
Ok(vec![T::from_value(RedisValue::String(string))?])
}
},
RedisValue::Array(values) => T::from_values(values),
RedisValue::Array(values) => {
if values.len() > 0 {
if let RedisValue::Array(_) = &values[0] {
values.into_iter().map(|x| T::from_value(x)).collect()
} else {
T::from_values(values)
}
} else {
Ok(vec![])
}
},
RedisValue::Map(map) => {
// not being able to use collect() here is unfortunate
let out = Vec::with_capacity(map.len() * 2);
Expand Down
Loading

0 comments on commit b3f89b4

Please sign in to comment.