Skip to content

Commit 426bb99

Browse files
authored
Merge pull request #184 from amazon-contributing/fast_reconnect_cand
Introduce a fast reconnect process for async cluster connections.
2 parents 2d7200f + 24c19dd commit 426bb99

23 files changed

+1083
-712
lines changed

Diff for: redis-test/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,10 @@ impl AioConnectionLike for MockRedisConnection {
288288
fn get_db(&self) -> i64 {
289289
0
290290
}
291+
292+
fn is_closed(&self) -> bool {
293+
false
294+
}
291295
}
292296

293297
#[cfg(test)]

Diff for: redis/examples/async-await.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#![allow(unknown_lints, dependency_on_unit_never_type_fallback)]
2-
use redis::AsyncCommands;
2+
use redis::{AsyncCommands, GlideConnectionOptions};
33

44
#[tokio::main]
55
async fn main() -> redis::RedisResult<()> {
66
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
7-
let mut con = client.get_multiplexed_async_connection(None).await?;
7+
let mut con = client
8+
.get_multiplexed_async_connection(GlideConnectionOptions::default())
9+
.await?;
810

911
con.set("key1", b"foo").await?;
1012

Diff for: redis/examples/async-connection-loss.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::time::Duration;
1313

1414
use futures::future;
1515
use redis::aio::ConnectionLike;
16+
use redis::GlideConnectionOptions;
1617
use redis::RedisResult;
1718
use tokio::time::interval;
1819

@@ -80,7 +81,14 @@ async fn main() -> RedisResult<()> {
8081

8182
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
8283
match mode {
83-
Mode::Default => run_multi(client.get_multiplexed_tokio_connection(None).await?).await?,
84+
Mode::Default => {
85+
run_multi(
86+
client
87+
.get_multiplexed_tokio_connection(GlideConnectionOptions::default())
88+
.await?,
89+
)
90+
.await?
91+
}
8492
Mode::Reconnect => run_multi(client.get_connection_manager().await?).await?,
8593
#[allow(deprecated)]
8694
Mode::Deprecated => run_single(client.get_async_connection(None).await?).await?,

Diff for: redis/examples/async-multiplexed.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![allow(unknown_lints, dependency_on_unit_never_type_fallback)]
22
use futures::prelude::*;
3-
use redis::{aio::MultiplexedConnection, RedisResult};
3+
use redis::{aio::MultiplexedConnection, GlideConnectionOptions, RedisResult};
44

55
async fn test_cmd(con: &MultiplexedConnection, i: i32) -> RedisResult<()> {
66
let mut con = con.clone();
@@ -34,7 +34,10 @@ async fn test_cmd(con: &MultiplexedConnection, i: i32) -> RedisResult<()> {
3434
async fn main() {
3535
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
3636

37-
let con = client.get_multiplexed_tokio_connection(None).await.unwrap();
37+
let con = client
38+
.get_multiplexed_tokio_connection(GlideConnectionOptions::default())
39+
.await
40+
.unwrap();
3841

3942
let cmds = (0..100).map(|i| test_cmd(&con, i));
4043
let result = future::try_join_all(cmds).await.unwrap();

Diff for: redis/examples/async-pub-sub.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#![allow(unknown_lints, dependency_on_unit_never_type_fallback)]
22
use futures_util::StreamExt as _;
3-
use redis::AsyncCommands;
3+
use redis::{AsyncCommands, GlideConnectionOptions};
44

55
#[tokio::main]
66
async fn main() -> redis::RedisResult<()> {
77
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
8-
let mut publish_conn = client.get_multiplexed_async_connection(None).await?;
8+
let mut publish_conn = client
9+
.get_multiplexed_async_connection(GlideConnectionOptions::default())
10+
.await?;
911
let mut pubsub_conn = client.get_async_pubsub().await?;
1012

1113
pubsub_conn.subscribe("wavephone").await?;

Diff for: redis/examples/async-scan.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#![allow(unknown_lints, dependency_on_unit_never_type_fallback)]
22
use futures::stream::StreamExt;
3-
use redis::{AsyncCommands, AsyncIter};
3+
use redis::{AsyncCommands, AsyncIter, GlideConnectionOptions};
44

55
#[tokio::main]
66
async fn main() -> redis::RedisResult<()> {
77
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
8-
let mut con = client.get_multiplexed_async_connection(None).await?;
8+
let mut con = client
9+
.get_multiplexed_async_connection(GlideConnectionOptions::default())
10+
.await?;
911

1012
con.set("async-key1", b"foo").await?;
1113
con.set("async-key2", b"foo").await?;

Diff for: redis/src/aio/connection.rs

+5
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,11 @@ where
305305
fn get_db(&self) -> i64 {
306306
self.db
307307
}
308+
309+
fn is_closed(&self) -> bool {
310+
// always false for AsyncRead + AsyncWrite (cant do better)
311+
false
312+
}
308313
}
309314

310315
/// Represents a `PubSub` connection.

Diff for: redis/src/aio/connection_manager.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::RedisFuture;
2+
use crate::client::GlideConnectionOptions;
23
use crate::cmd::Cmd;
34
use crate::push_manager::PushManager;
45
use crate::types::{RedisError, RedisResult, Value};
@@ -195,7 +196,7 @@ impl ConnectionManager {
195196
client.get_multiplexed_async_connection_with_timeouts(
196197
response_timeout,
197198
connection_timeout,
198-
None,
199+
GlideConnectionOptions::default(),
199200
)
200201
})
201202
.await
@@ -301,4 +302,9 @@ impl ConnectionLike for ConnectionManager {
301302
fn get_db(&self) -> i64 {
302303
self.client.connection_info().redis.db
303304
}
305+
306+
fn is_closed(&self) -> bool {
307+
// always return false due to automatic reconnect
308+
false
309+
}
304310
}

Diff for: redis/src/aio/mod.rs

+23
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::net::SocketAddr;
1212
#[cfg(unix)]
1313
use std::path::Path;
1414
use std::pin::Pin;
15+
use std::time::Duration;
1516

1617
/// Enables the async_std compatibility
1718
#[cfg(feature = "async-std-comp")]
@@ -85,6 +86,28 @@ pub trait ConnectionLike {
8586
/// also might be incorrect if the connection like object is not
8687
/// actually connected.
8788
fn get_db(&self) -> i64;
89+
90+
/// Returns the state of the connection
91+
fn is_closed(&self) -> bool;
92+
}
93+
94+
/// Implements ability to notify about disconnection events
95+
#[async_trait]
96+
pub trait DisconnectNotifier: Send + Sync {
97+
/// Notify about disconnect event
98+
fn notify_disconnect(&mut self);
99+
100+
/// Wait for disconnect event with timeout
101+
async fn wait_for_disconnect_with_timeout(&self, max_wait: &Duration);
102+
103+
/// Intended to be used with Box
104+
fn clone_box(&self) -> Box<dyn DisconnectNotifier>;
105+
}
106+
107+
impl Clone for Box<dyn DisconnectNotifier> {
108+
fn clone(&self) -> Box<dyn DisconnectNotifier> {
109+
self.clone_box()
110+
}
88111
}
89112

90113
// Initial setup for every connection.

Diff for: redis/src/aio/multiplexed_connection.rs

+51-20
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use super::{ConnectionLike, Runtime};
22
use crate::aio::setup_connection;
3+
use crate::aio::DisconnectNotifier;
4+
use crate::client::GlideConnectionOptions;
35
use crate::cmd::Cmd;
46
#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))]
57
use crate::parser::ValueCodec;
68
use crate::push_manager::PushManager;
79
use crate::types::{RedisError, RedisFuture, RedisResult, Value};
8-
use crate::{cmd, ConnectionInfo, ProtocolVersion, PushInfo, PushKind};
10+
use crate::{cmd, ConnectionInfo, ProtocolVersion, PushKind};
911
use ::tokio::{
1012
io::{AsyncRead, AsyncWrite},
1113
sync::{mpsc, oneshot},
@@ -23,6 +25,7 @@ use std::fmt;
2325
use std::fmt::Debug;
2426
use std::io;
2527
use std::pin::Pin;
28+
use std::sync::atomic::{AtomicBool, Ordering};
2629
use std::sync::Arc;
2730
use std::task::{self, Poll};
2831
use std::time::Duration;
@@ -73,19 +76,11 @@ struct PipelineMessage<S> {
7376
/// items being output by the `Stream` (the number is specified at time of sending). With the
7477
/// interface provided by `Pipeline` an easy interface of request to response, hiding the `Stream`
7578
/// and `Sink`.
79+
#[derive(Clone)]
7680
struct Pipeline<SinkItem> {
7781
sender: mpsc::Sender<PipelineMessage<SinkItem>>,
78-
7982
push_manager: Arc<ArcSwap<PushManager>>,
80-
}
81-
82-
impl<SinkItem> Clone for Pipeline<SinkItem> {
83-
fn clone(&self) -> Self {
84-
Pipeline {
85-
sender: self.sender.clone(),
86-
push_manager: self.push_manager.clone(),
87-
}
88-
}
83+
is_stream_closed: Arc<AtomicBool>,
8984
}
9085

9186
impl<SinkItem> Debug for Pipeline<SinkItem>
@@ -104,14 +99,21 @@ pin_project! {
10499
in_flight: VecDeque<InFlight>,
105100
error: Option<RedisError>,
106101
push_manager: Arc<ArcSwap<PushManager>>,
102+
disconnect_notifier: Option<Box<dyn DisconnectNotifier>>,
103+
is_stream_closed: Arc<AtomicBool>,
107104
}
108105
}
109106

110107
impl<T> PipelineSink<T>
111108
where
112109
T: Stream<Item = RedisResult<Value>> + 'static,
113110
{
114-
fn new<SinkItem>(sink_stream: T, push_manager: Arc<ArcSwap<PushManager>>) -> Self
111+
fn new<SinkItem>(
112+
sink_stream: T,
113+
push_manager: Arc<ArcSwap<PushManager>>,
114+
disconnect_notifier: Option<Box<dyn DisconnectNotifier>>,
115+
is_stream_closed: Arc<AtomicBool>,
116+
) -> Self
115117
where
116118
T: Sink<SinkItem, Error = RedisError> + Stream<Item = RedisResult<Value>> + 'static,
117119
{
@@ -120,6 +122,8 @@ where
120122
in_flight: VecDeque::new(),
121123
error: None,
122124
push_manager,
125+
disconnect_notifier,
126+
is_stream_closed,
123127
}
124128
}
125129

@@ -130,7 +134,15 @@ where
130134
Some(result) => result,
131135
// The redis response stream is not going to produce any more items so we `Err`
132136
// to break out of the `forward` combinator and stop handling requests
133-
None => return Poll::Ready(Err(())),
137+
None => {
138+
// this is the right place to notify about the passive TCP disconnect
139+
// In other places we cannot distinguish between the active destruction of MultiplexedConnection and passive disconnect
140+
if let Some(disconnect_notifier) = self.as_mut().project().disconnect_notifier {
141+
disconnect_notifier.notify_disconnect();
142+
}
143+
self.is_stream_closed.store(true, Ordering::Relaxed);
144+
return Poll::Ready(Err(()));
145+
}
134146
};
135147
self.as_mut().send_result(item);
136148
}
@@ -296,7 +308,10 @@ impl<SinkItem> Pipeline<SinkItem>
296308
where
297309
SinkItem: Send + 'static,
298310
{
299-
fn new<T>(sink_stream: T) -> (Self, impl Future<Output = ()>)
311+
fn new<T>(
312+
sink_stream: T,
313+
disconnect_notifier: Option<Box<dyn DisconnectNotifier>>,
314+
) -> (Self, impl Future<Output = ()>)
300315
where
301316
T: Sink<SinkItem, Error = RedisError> + Stream<Item = RedisResult<Value>> + 'static,
302317
T: Send + 'static,
@@ -308,7 +323,13 @@ where
308323
let (sender, mut receiver) = mpsc::channel(BUFFER_SIZE);
309324
let push_manager: Arc<ArcSwap<PushManager>> =
310325
Arc::new(ArcSwap::new(Arc::new(PushManager::default())));
311-
let sink = PipelineSink::new::<SinkItem>(sink_stream, push_manager.clone());
326+
let is_stream_closed = Arc::new(AtomicBool::new(false));
327+
let sink = PipelineSink::new::<SinkItem>(
328+
sink_stream,
329+
push_manager.clone(),
330+
disconnect_notifier,
331+
is_stream_closed.clone(),
332+
);
312333
let f = stream::poll_fn(move |cx| receiver.poll_recv(cx))
313334
.map(Ok)
314335
.forward(sink)
@@ -317,6 +338,7 @@ where
317338
Pipeline {
318339
sender,
319340
push_manager,
341+
is_stream_closed,
320342
},
321343
f,
322344
)
@@ -363,6 +385,10 @@ where
363385
async fn set_push_manager(&mut self, push_manager: PushManager) {
364386
self.push_manager.store(Arc::new(push_manager));
365387
}
388+
389+
pub fn is_closed(&self) -> bool {
390+
self.is_stream_closed.load(Ordering::Relaxed)
391+
}
366392
}
367393

368394
/// A connection object which can be cloned, allowing requests to be be sent concurrently
@@ -391,7 +417,7 @@ impl MultiplexedConnection {
391417
pub async fn new<C>(
392418
connection_info: &ConnectionInfo,
393419
stream: C,
394-
push_sender: Option<mpsc::UnboundedSender<PushInfo>>,
420+
glide_connection_options: GlideConnectionOptions,
395421
) -> RedisResult<(Self, impl Future<Output = ()>)>
396422
where
397423
C: Unpin + AsyncRead + AsyncWrite + Send + 'static,
@@ -400,7 +426,7 @@ impl MultiplexedConnection {
400426
connection_info,
401427
stream,
402428
std::time::Duration::MAX,
403-
push_sender,
429+
glide_connection_options,
404430
)
405431
.await
406432
}
@@ -411,7 +437,7 @@ impl MultiplexedConnection {
411437
connection_info: &ConnectionInfo,
412438
stream: C,
413439
response_timeout: std::time::Duration,
414-
push_sender: Option<mpsc::UnboundedSender<PushInfo>>,
440+
glide_connection_options: GlideConnectionOptions,
415441
) -> RedisResult<(Self, impl Future<Output = ()>)>
416442
where
417443
C: Unpin + AsyncRead + AsyncWrite + Send + 'static,
@@ -429,10 +455,11 @@ impl MultiplexedConnection {
429455
let codec = ValueCodec::default()
430456
.framed(stream)
431457
.and_then(|msg| async move { msg });
432-
let (mut pipeline, driver) = Pipeline::new(codec);
458+
let (mut pipeline, driver) =
459+
Pipeline::new(codec, glide_connection_options.disconnect_notifier);
433460
let driver = boxed(driver);
434461
let pm = PushManager::default();
435-
if let Some(sender) = push_sender {
462+
if let Some(sender) = glide_connection_options.push_sender {
436463
pm.replace_sender(sender);
437464
}
438465

@@ -560,6 +587,10 @@ impl ConnectionLike for MultiplexedConnection {
560587
fn get_db(&self) -> i64 {
561588
self.db
562589
}
590+
591+
fn is_closed(&self) -> bool {
592+
self.pipeline.is_closed()
593+
}
563594
}
564595
impl MultiplexedConnection {
565596
/// Subscribes to a new channel.

0 commit comments

Comments
 (0)