1use super::{AsyncPushSender, ConnectionLike, Runtime, SharedHandleContainer, TaskHandle};
2#[cfg(feature = "cache-aio")]
3use crate::caching::{CacheManager, CacheStatistics, PrepareCacheResult};
4use crate::{
5 AsyncConnectionConfig, ProtocolVersion, PushInfo, RedisConnectionInfo, ServerError,
6 ToRedisArgs,
7 aio::setup_connection,
8 check_resp3, cmd,
9 cmd::Cmd,
10 errors::{RedisError, closed_connection_error},
11 parser::ValueCodec,
12 types::{RedisFuture, RedisResult, Value},
13};
14use ::tokio::{
15 io::{AsyncRead, AsyncWrite},
16 sync::{mpsc, oneshot},
17};
18#[cfg(feature = "token-based-authentication")]
19use {
20 crate::errors::ErrorKind,
21 arcstr::ArcStr,
22 log::{debug, error, warn},
23};
24
25use futures_util::{
26 future::{Future, FutureExt},
27 ready,
28 sink::Sink,
29 stream::{self, Stream, StreamExt},
30};
31use pin_project_lite::pin_project;
32use std::collections::VecDeque;
33use std::fmt;
34use std::fmt::Debug;
35use std::pin::Pin;
36use std::sync::Arc;
37use std::task::{self, Poll};
38use std::time::Duration;
39use tokio_util::codec::Decoder;
40
41type PipelineOutput = oneshot::Sender<RedisResult<Value>>;
43
44enum ErrorOrErrors {
45 Errors(Vec<(usize, ServerError)>),
46 FirstError(RedisError),
48}
49
50enum ResponseAggregate {
51 SingleCommand,
52 Pipeline {
53 buffer: Vec<Value>,
54 error_or_errors: ErrorOrErrors,
55 expectation: PipelineResponseExpectation,
56 },
57}
58
59struct PipelineResponseExpectation {
61 skipped_response_count: usize,
63 expected_response_count: usize,
65 is_transaction: bool,
67 seen_responses: usize,
68}
69
70impl ResponseAggregate {
71 fn new(expectation: Option<PipelineResponseExpectation>) -> Self {
72 match expectation {
73 Some(expectation) => ResponseAggregate::Pipeline {
74 buffer: Vec::new(),
75 error_or_errors: ErrorOrErrors::Errors(Vec::new()),
76 expectation,
77 },
78 None => ResponseAggregate::SingleCommand,
79 }
80 }
81}
82
83struct InFlight {
84 output: Option<PipelineOutput>,
85 response_aggregate: ResponseAggregate,
86}
87
88struct PipelineMessage {
90 input: Vec<u8>,
91 output: Option<PipelineOutput>,
93 expectation: Option<PipelineResponseExpectation>,
97}
98
99#[derive(Clone)]
104struct Pipeline {
105 sender: mpsc::Sender<PipelineMessage>,
106}
107
108impl Debug for Pipeline {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 f.debug_tuple("Pipeline").field(&self.sender).finish()
111 }
112}
113
114#[cfg(feature = "cache-aio")]
115pin_project! {
116 struct PipelineSink<T> {
117 #[pin]
118 sink_stream: T,
119 in_flight: VecDeque<InFlight>,
120 error: Option<RedisError>,
121 push_sender: Option<Arc<dyn AsyncPushSender>>,
122 cache_manager: Option<CacheManager>,
123 }
124}
125
126#[cfg(not(feature = "cache-aio"))]
127pin_project! {
128 struct PipelineSink<T> {
129 #[pin]
130 sink_stream: T,
131 in_flight: VecDeque<InFlight>,
132 error: Option<RedisError>,
133 push_sender: Option<Arc<dyn AsyncPushSender>>,
134 }
135}
136
137fn send_push(push_sender: &Option<Arc<dyn AsyncPushSender>>, info: PushInfo) {
138 if let Some(sender) = push_sender {
139 let _ = sender.send(info);
140 };
141}
142
143pub(crate) fn send_disconnect(push_sender: &Option<Arc<dyn AsyncPushSender>>) {
144 send_push(push_sender, PushInfo::disconnect());
145}
146
147impl<T> PipelineSink<T>
148where
149 T: Stream<Item = RedisResult<Value>> + 'static,
150{
151 fn new(
152 sink_stream: T,
153 push_sender: Option<Arc<dyn AsyncPushSender>>,
154 #[cfg(feature = "cache-aio")] cache_manager: Option<CacheManager>,
155 ) -> Self
156 where
157 T: Sink<Vec<u8>, Error = RedisError> + Stream<Item = RedisResult<Value>> + 'static,
158 {
159 PipelineSink {
160 sink_stream,
161 in_flight: VecDeque::new(),
162 error: None,
163 push_sender,
164 #[cfg(feature = "cache-aio")]
165 cache_manager,
166 }
167 }
168
169 fn poll_read(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Result<(), ()>> {
171 loop {
172 let item = ready!(self.as_mut().project().sink_stream.poll_next(cx));
173 let item = match item {
174 Some(result) => result,
175 None => Err(closed_connection_error()),
177 };
178
179 let is_unrecoverable = item.as_ref().is_err_and(|err| err.is_unrecoverable_error());
180 self.as_mut().send_result(item);
181 if is_unrecoverable {
182 let self_ = self.project();
183 send_disconnect(self_.push_sender);
184 return Poll::Ready(Err(()));
185 }
186 }
187 }
188
189 fn send_result(self: Pin<&mut Self>, result: RedisResult<Value>) {
190 let self_ = self.project();
191 let result = match result {
192 Ok(Value::Push { kind, data }) if !kind.has_reply() => {
194 #[cfg(feature = "cache-aio")]
195 if let Some(cache_manager) = &self_.cache_manager {
196 cache_manager.handle_push_value(&kind, &data);
197 }
198 send_push(self_.push_sender, PushInfo { kind, data });
199
200 return;
201 }
202 Ok(Value::Push { kind, data }) if kind.has_reply() => {
204 send_push(
205 self_.push_sender,
206 PushInfo {
207 kind: kind.clone(),
208 data: data.clone(),
209 },
210 );
211 Ok(Value::Push { kind, data })
212 }
213 _ => result,
214 };
215
216 let mut entry = match self_.in_flight.pop_front() {
217 Some(entry) => entry,
218 None => return,
219 };
220
221 match &mut entry.response_aggregate {
222 ResponseAggregate::SingleCommand => {
223 if let Some(output) = entry.output.take() {
224 _ = output.send(result);
225 }
226 }
227 ResponseAggregate::Pipeline {
228 buffer,
229 error_or_errors,
230 expectation:
231 PipelineResponseExpectation {
232 expected_response_count,
233 skipped_response_count,
234 is_transaction,
235 seen_responses,
236 },
237 } => {
238 *seen_responses += 1;
239 if *skipped_response_count > 0 {
240 if *is_transaction {
243 if let ErrorOrErrors::Errors(errs) = error_or_errors {
244 match result {
245 Ok(Value::ServerError(err)) => {
246 errs.push((*seen_responses - 2, err)); }
248 Err(err) => *error_or_errors = ErrorOrErrors::FirstError(err),
249 _ => {}
250 }
251 }
252 }
253
254 *skipped_response_count -= 1;
255 self_.in_flight.push_front(entry);
256 return;
257 }
258
259 match result {
260 Ok(item) => {
261 buffer.push(item);
262 }
263 Err(err) => {
264 if matches!(error_or_errors, ErrorOrErrors::Errors(_)) {
265 *error_or_errors = ErrorOrErrors::FirstError(err)
266 }
267 }
268 }
269
270 if buffer.len() < *expected_response_count {
271 self_.in_flight.push_front(entry);
273 return;
274 }
275
276 let response =
277 match std::mem::replace(error_or_errors, ErrorOrErrors::Errors(Vec::new())) {
278 ErrorOrErrors::Errors(errors) => {
279 if errors.is_empty() {
280 Ok(Value::Array(std::mem::take(buffer)))
281 } else {
282 Err(RedisError::make_aborted_transaction(errors))
283 }
284 }
285 ErrorOrErrors::FirstError(redis_error) => Err(redis_error),
286 };
287
288 if let Some(output) = entry.output.take() {
292 _ = output.send(response);
293 }
294 }
295 }
296 }
297}
298
299impl<T> Sink<PipelineMessage> for PipelineSink<T>
300where
301 T: Sink<Vec<u8>, Error = RedisError> + Stream<Item = RedisResult<Value>> + 'static,
302{
303 type Error = ();
304
305 fn poll_ready(
307 mut self: Pin<&mut Self>,
308 cx: &mut task::Context,
309 ) -> Poll<Result<(), Self::Error>> {
310 if matches!(self.as_mut().poll_read(cx), Poll::Ready(Err(()))) {
317 return Poll::Ready(Err(()));
318 }
319 match ready!(self.as_mut().project().sink_stream.poll_ready(cx)) {
320 Ok(()) => Ok(()).into(),
321 Err(err) => {
322 *self.project().error = Some(err);
323 Ok(()).into()
324 }
325 }
326 }
327
328 fn start_send(
329 mut self: Pin<&mut Self>,
330 PipelineMessage {
331 input,
332 mut output,
333 expectation,
334 }: PipelineMessage,
335 ) -> Result<(), Self::Error> {
336 if output.as_ref().is_some_and(|output| output.is_closed()) {
340 return Ok(());
341 }
342
343 let self_ = self.as_mut().project();
344
345 if let Some(err) = self_.error.take() {
346 if let Some(output) = output.take() {
347 _ = output.send(Err(err));
348 }
349 return Err(());
350 }
351
352 match self_.sink_stream.start_send(input) {
353 Ok(()) => {
354 let response_aggregate = ResponseAggregate::new(expectation);
355 let entry = InFlight {
356 output,
357 response_aggregate,
358 };
359
360 self_.in_flight.push_back(entry);
361 Ok(())
362 }
363 Err(err) => {
364 if let Some(output) = output.take() {
365 _ = output.send(Err(err));
366 }
367 Err(())
368 }
369 }
370 }
371
372 fn poll_flush(
373 mut self: Pin<&mut Self>,
374 cx: &mut task::Context,
375 ) -> Poll<Result<(), Self::Error>> {
376 if matches!(self.as_mut().poll_read(cx), Poll::Ready(Err(()))) {
383 return Poll::Ready(Err(()));
384 }
385 self.as_mut()
386 .project()
387 .sink_stream
388 .poll_flush(cx)
389 .map_err(|err| {
390 self.as_mut().send_result(Err(err));
391 })
392 }
393
394 fn poll_close(
395 mut self: Pin<&mut Self>,
396 cx: &mut task::Context,
397 ) -> Poll<Result<(), Self::Error>> {
398 if !self.in_flight.is_empty() {
401 ready!(self.as_mut().poll_flush(cx))?;
402 }
403 let this = self.as_mut().project();
404 this.sink_stream.poll_close(cx).map_err(|err| {
405 self.send_result(Err(err));
406 })
407 }
408}
409
410impl Pipeline {
411 const DEFAULT_BUFFER_SIZE: usize = 50;
412
413 fn resolve_buffer_size(size: Option<usize>) -> usize {
414 size.unwrap_or(Self::DEFAULT_BUFFER_SIZE)
415 }
416
417 fn new<T>(
418 sink_stream: T,
419 push_sender: Option<Arc<dyn AsyncPushSender>>,
420 #[cfg(feature = "cache-aio")] cache_manager: Option<CacheManager>,
421 buffer_size: usize,
422 ) -> (Self, impl Future<Output = ()>)
423 where
424 T: Sink<Vec<u8>, Error = RedisError>,
425 T: Stream<Item = RedisResult<Value>>,
426 T: Unpin + Send + 'static,
427 {
428 let (sender, mut receiver) = mpsc::channel(buffer_size);
429
430 let sink = PipelineSink::new(
431 sink_stream,
432 push_sender,
433 #[cfg(feature = "cache-aio")]
434 cache_manager,
435 );
436 let f = stream::poll_fn(move |cx| receiver.poll_recv(cx))
437 .map(Ok)
438 .forward(sink)
439 .map(|_| ());
440 (Pipeline { sender }, f)
441 }
442
443 async fn send_recv(
444 &mut self,
445 input: Vec<u8>,
446 expectation: Option<PipelineResponseExpectation>,
449 timeout: Option<Duration>,
450 skip_response: bool,
451 ) -> Result<Value, RedisError> {
452 if input.is_empty() {
453 return Err(RedisError::make_empty_command());
454 }
455
456 let request = async {
457 if skip_response {
458 self.sender
459 .send(PipelineMessage {
460 input,
461 expectation,
462 output: None,
463 })
464 .await
465 .map_err(|_| None)?;
466
467 return Ok(Value::Nil);
468 }
469
470 let (sender, receiver) = oneshot::channel();
471
472 self.sender
473 .send(PipelineMessage {
474 input,
475 expectation,
476 output: Some(sender),
477 })
478 .await
479 .map_err(|_| None)?;
480
481 receiver.await
482 .map_err(|_| None)
485 .and_then(|res| res.map_err(Some))
486 };
487
488 match timeout {
489 Some(timeout) => match Runtime::locate().timeout(timeout, request).await {
490 Ok(res) => res,
491 Err(elapsed) => Err(Some(elapsed.into())),
492 },
493 None => request.await,
494 }
495 .map_err(|err| err.unwrap_or_else(closed_connection_error))
496 }
497}
498
499#[derive(Clone)]
512pub struct MultiplexedConnection {
513 pipeline: Pipeline,
514 db: i64,
515 response_timeout: Option<Duration>,
516 protocol: ProtocolVersion,
517 concurrency_limiter: Option<Arc<async_lock::Semaphore>>,
518 _task_handle: Option<SharedHandleContainer>,
522 #[cfg(feature = "cache-aio")]
523 pub(crate) cache_manager: Option<CacheManager>,
524 #[cfg(feature = "token-based-authentication")]
525 _credentials_subscription_task_handle: Option<SharedHandleContainer>,
528}
529
530impl Debug for MultiplexedConnection {
531 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532 let MultiplexedConnection {
533 pipeline,
534 db,
535 response_timeout,
536 protocol,
537 concurrency_limiter: _,
538 _task_handle,
539 #[cfg(feature = "cache-aio")]
540 cache_manager: _,
541 #[cfg(feature = "token-based-authentication")]
542 _credentials_subscription_task_handle: _,
543 } = self;
544
545 f.debug_struct("MultiplexedConnection")
546 .field("pipeline", &pipeline)
547 .field("db", &db)
548 .field("response_timeout", &response_timeout)
549 .field("protocol", &protocol)
550 .finish()
551 }
552}
553
554impl MultiplexedConnection {
555 pub async fn new<C>(
558 connection_info: &RedisConnectionInfo,
559 stream: C,
560 ) -> RedisResult<(Self, impl Future<Output = ()>)>
561 where
562 C: Unpin + AsyncRead + AsyncWrite + Send + 'static,
563 {
564 Self::new_with_config(connection_info, stream, AsyncConnectionConfig::default()).await
565 }
566
567 pub async fn new_with_config<C>(
570 connection_info: &RedisConnectionInfo,
571 stream: C,
572 config: AsyncConnectionConfig,
573 ) -> RedisResult<(Self, impl Future<Output = ()> + 'static)>
574 where
575 C: Unpin + AsyncRead + AsyncWrite + Send + 'static,
576 {
577 let mut codec = ValueCodec::default().framed(stream);
578 if config.push_sender.is_some() {
579 check_resp3!(
580 connection_info.protocol,
581 "Can only pass push sender to a connection using RESP3"
582 );
583 }
584
585 #[cfg(feature = "cache-aio")]
586 let cache_config = config.cache.as_ref().map(|cache| match cache {
587 crate::client::Cache::Config(cache_config) => *cache_config,
588 #[cfg(any(feature = "connection-manager", feature = "cluster-async"))]
589 crate::client::Cache::Manager(cache_manager) => cache_manager.cache_config,
590 });
591 #[cfg(feature = "cache-aio")]
592 let cache_manager_opt = config
593 .cache
594 .map(|cache| {
595 check_resp3!(
596 connection_info.protocol,
597 "Can only enable client side caching in a connection using RESP3"
598 );
599 match cache {
600 crate::client::Cache::Config(cache_config) => {
601 Ok(CacheManager::new(cache_config))
602 }
603 #[cfg(any(feature = "connection-manager", feature = "cluster-async"))]
604 crate::client::Cache::Manager(cache_manager) => Ok(cache_manager),
605 }
606 })
607 .transpose()?;
608
609 #[cfg(feature = "token-based-authentication")]
610 let mut connection_info = connection_info.clone();
611 #[cfg(not(feature = "token-based-authentication"))]
612 let connection_info = connection_info.clone();
613
614 #[cfg(feature = "token-based-authentication")]
615 if let Some(ref credentials_provider) = config.credentials_provider {
616 match credentials_provider.subscribe().next().await {
618 Some(Ok(credentials)) => {
619 connection_info.username = Some(ArcStr::from(credentials.username));
620 connection_info.password = Some(ArcStr::from(credentials.password));
621 }
622 Some(Err(err)) => {
623 error!("Error while receiving credentials from stream: {err}");
624 return Err(err);
625 }
626 None => {
627 let err = RedisError::from((
628 ErrorKind::AuthenticationFailed,
629 "Credentials stream closed unexpectedly before yielding credentials!",
630 ));
631 error!("{err}");
632 return Err(err);
633 }
634 }
635 }
636
637 setup_connection(
638 &mut codec,
639 &connection_info,
640 #[cfg(feature = "cache-aio")]
641 cache_config,
642 )
643 .await?;
644 if config.push_sender.is_some() {
645 check_resp3!(
646 connection_info.protocol,
647 "Can only pass push sender to a connection using RESP3"
648 );
649 }
650
651 let (pipeline, driver) = Pipeline::new(
652 codec,
653 config.push_sender,
654 #[cfg(feature = "cache-aio")]
655 cache_manager_opt.clone(),
656 Pipeline::resolve_buffer_size(config.pipeline_buffer_size),
657 );
658
659 let concurrency_limiter = config
660 .concurrency_limit
661 .map(|n| Arc::new(async_lock::Semaphore::new(n)));
662
663 let con = MultiplexedConnection {
664 pipeline,
665 db: connection_info.db,
666 response_timeout: config.response_timeout,
667 protocol: connection_info.protocol,
668 concurrency_limiter,
669 _task_handle: None,
670 #[cfg(feature = "cache-aio")]
671 cache_manager: cache_manager_opt,
672 #[cfg(feature = "token-based-authentication")]
673 _credentials_subscription_task_handle: None,
674 };
675
676 #[cfg(feature = "token-based-authentication")]
678 if let Some(streaming_provider) = config.credentials_provider {
679 let mut inner_connection = con.clone();
680 let mut stream = streaming_provider.subscribe();
681
682 let subscription_task_handle = Runtime::locate().spawn(async move {
683 while let Some(result) = stream.next().await {
684 match result {
685 Ok(credentials) => {
686 if let Err(err) = inner_connection
687 .re_authenticate_with_credentials(&credentials)
688 .await
689 {
690 if err.is_connection_dropped() {
691 warn!(
692 "Re-authentication task ended, connection is dead: {err}"
693 );
694 return;
695 }
696 error!("Failed to re-authenticate async connection: {err}.");
697 return;
698 } else {
699 debug!("Re-authenticated async connection");
700 }
701 }
702 Err(err) => {
703 error!("Credentials stream error for async connection: {err}.");
704 }
705 }
706 }
707 warn!("Credentials stream ended; no further re-authentication will occur.");
708 });
709 return Ok((
710 Self {
711 _credentials_subscription_task_handle: Some(SharedHandleContainer::new(
712 subscription_task_handle,
713 )),
714 ..con
715 },
716 driver,
717 ));
718 }
719
720 Ok((con, driver))
721 }
722
723 pub(crate) fn set_task_handle(&mut self, handle: TaskHandle) {
726 self._task_handle = Some(SharedHandleContainer::new(handle));
727 }
728
729 pub fn set_response_timeout(&mut self, timeout: std::time::Duration) {
731 self.response_timeout = Some(timeout);
732 }
733
734 pub async fn send_packed_command(&mut self, cmd: &Cmd) -> RedisResult<Value> {
737 let _permit = if cmd.skip_concurrency_limit {
738 None
739 } else if let Some(limiter) = &self.concurrency_limiter {
740 Some(limiter.acquire().await)
741 } else {
742 None
743 };
744 #[cfg(feature = "cache-aio")]
745 if let Some(cache_manager) = &self.cache_manager {
746 match cache_manager.get_cached_cmd(cmd) {
747 PrepareCacheResult::Cached(value) => return Ok(value),
748 PrepareCacheResult::NotCached(cacheable_command) => {
749 let mut pipeline = crate::Pipeline::new();
750 cacheable_command.pack_command(cache_manager, &mut pipeline);
751
752 let result = self
753 .pipeline
754 .send_recv(
755 pipeline.get_packed_pipeline(),
756 Some(PipelineResponseExpectation {
757 skipped_response_count: 0,
758 expected_response_count: pipeline.commands.len(),
759 is_transaction: false,
760 seen_responses: 0,
761 }),
762 self.response_timeout,
763 cmd.is_no_response(),
764 )
765 .await?;
766 let replies: Vec<Value> = crate::types::from_redis_value(result)?;
767 return cacheable_command.resolve(cache_manager, replies.into_iter());
768 }
769 _ => (),
770 }
771 }
772 self.pipeline
773 .send_recv(
774 cmd.get_packed_command(),
775 None,
776 self.response_timeout,
777 cmd.is_no_response(),
778 )
779 .await
780 }
781
782 pub async fn send_packed_commands(
786 &mut self,
787 cmd: &crate::Pipeline,
788 offset: usize,
789 count: usize,
790 ) -> RedisResult<Vec<Value>> {
791 let _permits = if let Some(limiter) = &self.concurrency_limiter {
796 let mut permits = Vec::with_capacity(count.max(1));
797 permits.push(limiter.acquire().await);
798 for _ in 1..count {
799 match limiter.try_acquire() {
800 Some(permit) => permits.push(permit),
801 None => break,
802 }
803 }
804 permits
805 } else {
806 Vec::new()
807 };
808 #[cfg(feature = "cache-aio")]
809 if let Some(cache_manager) = &self.cache_manager {
810 let (cacheable_pipeline, pipeline, (skipped_response_count, expected_response_count)) =
811 cache_manager.get_cached_pipeline(cmd);
812 if pipeline.is_empty() {
813 return cacheable_pipeline.resolve(cache_manager, Value::Array(Vec::new()));
814 }
815 let result = self
816 .pipeline
817 .send_recv(
818 pipeline.get_packed_pipeline(),
819 Some(PipelineResponseExpectation {
820 skipped_response_count,
821 expected_response_count,
822 is_transaction: cacheable_pipeline.transaction_mode,
823 seen_responses: 0,
824 }),
825 self.response_timeout,
826 false,
827 )
828 .await?;
829
830 return cacheable_pipeline.resolve(cache_manager, result);
831 }
832 let value = self
833 .pipeline
834 .send_recv(
835 cmd.get_packed_pipeline(),
836 Some(PipelineResponseExpectation {
837 skipped_response_count: offset,
838 expected_response_count: count,
839 is_transaction: cmd.is_transaction(),
840 seen_responses: 0,
841 }),
842 self.response_timeout,
843 false,
844 )
845 .await?;
846 match value {
847 Value::Array(values) => Ok(values),
848 _ => Ok(vec![value]),
849 }
850 }
851
852 #[cfg(feature = "cache-aio")]
854 #[cfg_attr(docsrs, doc(cfg(feature = "cache-aio")))]
855 pub fn get_cache_statistics(&self) -> Option<CacheStatistics> {
856 self.cache_manager.as_ref().map(|cm| cm.statistics())
857 }
858}
859
860impl ConnectionLike for MultiplexedConnection {
861 fn req_packed_command<'a>(&'a mut self, cmd: &'a Cmd) -> RedisFuture<'a, Value> {
862 (async move { self.send_packed_command(cmd).await }).boxed()
863 }
864
865 fn req_packed_commands<'a>(
866 &'a mut self,
867 cmd: &'a crate::Pipeline,
868 offset: usize,
869 count: usize,
870 ) -> RedisFuture<'a, Vec<Value>> {
871 (async move { self.send_packed_commands(cmd, offset, count).await }).boxed()
872 }
873
874 fn get_db(&self) -> i64 {
875 self.db
876 }
877}
878
879impl MultiplexedConnection {
880 pub async fn subscribe(&mut self, channel_name: impl ToRedisArgs) -> RedisResult<()> {
897 check_resp3!(self.protocol);
898 let mut cmd = cmd("SUBSCRIBE");
899 cmd.arg(channel_name);
900 cmd.exec_async(self).await?;
901 Ok(())
902 }
903
904 pub async fn unsubscribe(&mut self, channel_name: impl ToRedisArgs) -> RedisResult<()> {
919 check_resp3!(self.protocol);
920 let mut cmd = cmd("UNSUBSCRIBE");
921 cmd.arg(channel_name);
922 cmd.exec_async(self).await?;
923 Ok(())
924 }
925
926 pub async fn psubscribe(&mut self, channel_pattern: impl ToRedisArgs) -> RedisResult<()> {
945 check_resp3!(self.protocol);
946 let mut cmd = cmd("PSUBSCRIBE");
947 cmd.arg(channel_pattern);
948 cmd.exec_async(self).await?;
949 Ok(())
950 }
951
952 pub async fn punsubscribe(&mut self, channel_pattern: impl ToRedisArgs) -> RedisResult<()> {
956 check_resp3!(self.protocol);
957 let mut cmd = cmd("PUNSUBSCRIBE");
958 cmd.arg(channel_pattern);
959 cmd.exec_async(self).await?;
960 Ok(())
961 }
962}
963
964#[cfg(feature = "token-based-authentication")]
965impl MultiplexedConnection {
966 async fn re_authenticate_with_credentials(
971 &mut self,
972 credentials: &crate::auth::BasicAuth,
973 ) -> RedisResult<()> {
974 let mut auth_cmd =
975 crate::connection::authenticate_cmd(Some(&credentials.username), &credentials.password);
976 auth_cmd.skip_concurrency_limit = true;
977 self.send_packed_command(&auth_cmd)
978 .await?
979 .extract_error()
980 .map(|_| ())
981 }
982}
983
984#[cfg(test)]
985mod tests {
986 use super::*;
987
988 #[test]
989 fn test_pipeline_resolve_buffer_size_default() {
990 assert_eq!(Pipeline::resolve_buffer_size(None), 50);
991 }
992
993 #[test]
994 fn test_pipeline_resolve_buffer_size_custom() {
995 assert_eq!(Pipeline::resolve_buffer_size(Some(100)), 100);
996 }
997
998 fn mock_conn_info() -> RedisConnectionInfo {
999 RedisConnectionInfo {
1000 skip_set_lib_name: true,
1001 ..Default::default()
1002 }
1003 }
1004
1005 async fn create_mock_connection(
1006 concurrency_limit: usize,
1007 ) -> (
1008 MultiplexedConnection,
1009 tokio::sync::mpsc::Receiver<()>,
1010 tokio::sync::mpsc::Sender<()>,
1011 ) {
1012 use futures_util::StreamExt;
1013 use tokio::io::AsyncWriteExt;
1014 use tokio_util::codec::FramedRead;
1015
1016 let (client_half, server_half) = tokio::io::duplex(4096);
1017 let (cmd_received_tx, cmd_received_rx) = tokio::sync::mpsc::channel::<()>(10);
1018 let (send_response_tx, mut send_response_rx) = tokio::sync::mpsc::channel::<()>(10);
1019
1020 let (server_read, mut server_write) = tokio::io::split(server_half);
1021
1022 tokio::spawn(async move {
1023 let mut reader = FramedRead::new(server_read, ValueCodec::default());
1024 while let Some(Ok(_)) = reader.next().await {
1025 let _ = cmd_received_tx.send(()).await;
1026 }
1027 });
1028
1029 tokio::spawn(async move {
1030 while send_response_rx.recv().await.is_some() {
1031 let _ = server_write.write_all(b"+OK\r\n").await;
1032 let _ = server_write.flush().await;
1033 }
1034 });
1035
1036 let config = AsyncConnectionConfig::new()
1037 .set_concurrency_limit(concurrency_limit)
1038 .set_response_timeout(None)
1039 .set_connection_timeout(None);
1040
1041 let (conn, driver) =
1042 MultiplexedConnection::new_with_config(&mock_conn_info(), client_half, config)
1043 .await
1044 .unwrap();
1045 tokio::spawn(driver);
1046
1047 (conn, cmd_received_rx, send_response_tx)
1048 }
1049
1050 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1051 async fn test_concurrency_limit_enforced() {
1052 let (conn, mut cmd_received_rx, send_response_tx) = create_mock_connection(2).await;
1053
1054 let h1 = tokio::spawn({
1055 let mut c = conn.clone();
1056 async move { c.send_packed_command(&cmd("PING")).await }
1057 });
1058 let h2 = tokio::spawn({
1059 let mut c = conn.clone();
1060 async move { c.send_packed_command(&cmd("PING")).await }
1061 });
1062 let h3 = tokio::spawn({
1063 let mut c = conn.clone();
1064 async move { c.send_packed_command(&cmd("PING")).await }
1065 });
1066
1067 cmd_received_rx.recv().await.unwrap();
1068 cmd_received_rx.recv().await.unwrap();
1069
1070 let third = tokio::time::timeout(Duration::from_millis(100), cmd_received_rx.recv()).await;
1071 assert!(
1072 third.is_err(),
1073 "3rd request should be blocked by concurrency limit"
1074 );
1075
1076 send_response_tx.send(()).await.unwrap();
1077
1078 cmd_received_rx.recv().await.unwrap();
1079
1080 send_response_tx.send(()).await.unwrap();
1081 send_response_tx.send(()).await.unwrap();
1082
1083 h1.await.unwrap().unwrap();
1084 h2.await.unwrap().unwrap();
1085 h3.await.unwrap().unwrap();
1086 }
1087
1088 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1089 async fn test_no_limit_bypasses_concurrency_limit() {
1090 let (conn, mut cmd_received_rx, send_response_tx) = create_mock_connection(1).await;
1091
1092 let h1 = tokio::spawn({
1093 let mut c = conn.clone();
1094 async move { c.send_packed_command(&cmd("PING")).await }
1095 });
1096
1097 cmd_received_rx.recv().await.unwrap();
1098
1099 let h2 = tokio::spawn({
1100 let mut c = conn.clone();
1101 async move {
1102 let mut ping = cmd("PING");
1103 ping.skip_concurrency_limit = true;
1104 c.send_packed_command(&ping).await
1105 }
1106 });
1107
1108 let received =
1109 tokio::time::timeout(Duration::from_millis(100), cmd_received_rx.recv()).await;
1110 assert!(
1111 received.is_ok(),
1112 "no_limit request should bypass concurrency limit"
1113 );
1114
1115 send_response_tx.send(()).await.unwrap();
1116 send_response_tx.send(()).await.unwrap();
1117
1118 h1.await.unwrap().unwrap();
1119 h2.await.unwrap().unwrap();
1120 }
1121
1122 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1123 async fn test_pipeline_acquires_multiple_permits() {
1124 let (conn, mut cmd_received_rx, send_response_tx) = create_mock_connection(3).await;
1125
1126 let pipeline_handle = tokio::spawn({
1127 let mut c = conn.clone();
1128 async move {
1129 let mut pipe = crate::Pipeline::new();
1130 pipe.cmd("SET").arg("a").arg("1");
1131 pipe.cmd("SET").arg("b").arg("2");
1132 pipe.cmd("SET").arg("c").arg("3");
1133 c.send_packed_commands(&pipe, 0, 3).await
1134 }
1135 });
1136
1137 for _ in 0..3 {
1138 cmd_received_rx.recv().await.unwrap();
1139 }
1140
1141 let single_handle = tokio::spawn({
1142 let mut c = conn.clone();
1143 async move { c.send_packed_command(&cmd("PING")).await }
1144 });
1145
1146 let blocked =
1147 tokio::time::timeout(Duration::from_millis(100), cmd_received_rx.recv()).await;
1148 assert!(
1149 blocked.is_err(),
1150 "single command should be blocked while pipeline holds all permits"
1151 );
1152
1153 for _ in 0..3 {
1154 send_response_tx.send(()).await.unwrap();
1155 }
1156
1157 cmd_received_rx.recv().await.unwrap();
1158 send_response_tx.send(()).await.unwrap();
1159
1160 pipeline_handle.await.unwrap().unwrap();
1161 single_handle.await.unwrap().unwrap();
1162 }
1163
1164 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1165 async fn test_pipeline_proceeds_with_partial_permits() {
1166 let (conn, mut cmd_received_rx, send_response_tx) = create_mock_connection(2).await;
1167
1168 let single_handle = tokio::spawn({
1169 let mut c = conn.clone();
1170 async move { c.send_packed_command(&cmd("PING")).await }
1171 });
1172 cmd_received_rx.recv().await.unwrap();
1173
1174 let pipeline_handle = tokio::spawn({
1175 let mut c = conn.clone();
1176 async move {
1177 let mut pipe = crate::Pipeline::new();
1178 for i in 0..5 {
1179 pipe.cmd("SET").arg(format!("k{i}")).arg(i);
1180 }
1181 c.send_packed_commands(&pipe, 0, 5).await
1182 }
1183 });
1184
1185 let received =
1186 tokio::time::timeout(Duration::from_millis(100), cmd_received_rx.recv()).await;
1187 assert!(
1188 received.is_ok(),
1189 "pipeline should proceed even with only partial permits"
1190 );
1191
1192 for _ in 1..5 {
1193 cmd_received_rx.recv().await.unwrap();
1194 }
1195
1196 for _ in 0..6 {
1197 send_response_tx.send(()).await.unwrap();
1198 }
1199
1200 single_handle.await.unwrap().unwrap();
1201 pipeline_handle.await.unwrap().unwrap();
1202 }
1203
1204 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1205 async fn test_permit_released_on_cancellation() {
1206 let (conn, mut cmd_received_rx, send_response_tx) = create_mock_connection(1).await;
1207
1208 let h1 = tokio::spawn({
1209 let mut c = conn.clone();
1210 async move { c.send_packed_command(&cmd("PING")).await }
1211 });
1212 cmd_received_rx.recv().await.unwrap();
1213
1214 let h2 = tokio::spawn({
1216 let mut c = conn.clone();
1217 async move { c.send_packed_command(&cmd("PING")).await }
1218 });
1219 tokio::time::sleep(Duration::from_millis(50)).await;
1220 h2.abort();
1221 let _ = h2.await;
1222
1223 send_response_tx.send(()).await.unwrap();
1225 h1.await.unwrap().unwrap();
1226
1227 let h3 = tokio::spawn({
1230 let mut c = conn.clone();
1231 async move { c.send_packed_command(&cmd("PING")).await }
1232 });
1233
1234 let received =
1235 tokio::time::timeout(Duration::from_millis(100), cmd_received_rx.recv()).await;
1236 assert!(
1237 received.is_ok(),
1238 "request after cancellation should acquire the permit"
1239 );
1240
1241 send_response_tx.send(()).await.unwrap();
1242 h3.await.unwrap().unwrap();
1243 }
1244
1245 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1284 async fn test_deadlock_when_writes_blocked_with_pending_response() {
1285 use futures_util::StreamExt;
1286 use tokio::io::AsyncWriteExt;
1287 use tokio_util::codec::FramedRead;
1288
1289 const BUFFER_SIZE: usize = 256;
1293 const PAYLOAD_SIZE: usize = 4096;
1294 const REQUEST_COUNT: usize = 3;
1295
1296 let (client_half, server_half) = tokio::io::duplex(BUFFER_SIZE);
1297 let (server_read, mut server_write) = tokio::io::split(server_half);
1298
1299 let mut response = Vec::with_capacity(PAYLOAD_SIZE + 16);
1303 response.extend_from_slice(format!("${PAYLOAD_SIZE}\r\n").as_bytes());
1304 response.extend(std::iter::repeat_n(b'V', PAYLOAD_SIZE));
1305 response.extend_from_slice(b"\r\n");
1306
1307 let server_task = tokio::spawn(async move {
1312 let mut reader = FramedRead::new(server_read, ValueCodec::default());
1313 loop {
1314 match reader.next().await {
1315 Some(Ok(_)) => {}
1316 _ => return,
1317 }
1318 if server_write.write_all(&response).await.is_err() {
1319 return;
1320 }
1321 }
1322 });
1323
1324 let config = AsyncConnectionConfig::new()
1325 .set_response_timeout(None)
1326 .set_connection_timeout(None);
1327 let (conn, driver) =
1328 MultiplexedConnection::new_with_config(&mock_conn_info(), client_half, config)
1329 .await
1330 .unwrap();
1331 let driver_handle = tokio::spawn(driver);
1332
1333 let mut handles = Vec::with_capacity(REQUEST_COUNT);
1338 for i in 0..REQUEST_COUNT {
1339 let mut c = conn.clone();
1340 handles.push(tokio::spawn(async move {
1341 let mut set = cmd("SET");
1342 set.arg(format!("k{i}")).arg(vec![b'X'; PAYLOAD_SIZE]);
1343 c.send_packed_command(&set).await
1344 }));
1345 }
1346
1347 let join_all = async move {
1348 let mut results = Vec::with_capacity(handles.len());
1349 for h in handles {
1350 results.push(h.await);
1351 }
1352 results
1353 };
1354
1355 let outcome = tokio::time::timeout(Duration::from_secs(5), join_all).await;
1356
1357 driver_handle.abort();
1359 server_task.abort();
1360
1361 let results = outcome.expect(
1362 "DEADLOCK reproduced: client driver parked in poll_flush with no \
1363 read waker registered. Server has buffered responses in the duplex \
1364 and stopped reading once its own write became Pending; the client \
1365 cannot send the rest of its requests because the server is no \
1366 longer draining the link. Both sides wedged.",
1367 );
1368 for (i, res) in results.into_iter().enumerate() {
1369 let join = res.unwrap_or_else(|e| panic!("SET task {i} panicked: {e}"));
1370 join.unwrap_or_else(|e| panic!("SET task {i} returned an error: {e}"));
1371 }
1372 }
1373
1374 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1375 async fn test_permit_released_on_response_timeout() {
1376 use futures_util::StreamExt;
1377 use tokio::io::AsyncWriteExt;
1378 use tokio_util::codec::FramedRead;
1379
1380 let (client_half, server_half) = tokio::io::duplex(4096);
1381 let (cmd_received_tx, mut cmd_received_rx) = tokio::sync::mpsc::channel::<()>(10);
1382
1383 let (server_read, mut server_write) = tokio::io::split(server_half);
1384
1385 tokio::spawn(async move {
1386 let mut reader = FramedRead::new(server_read, ValueCodec::default());
1387 while let Some(Ok(_)) = reader.next().await {
1388 let _ = cmd_received_tx.send(()).await;
1389 }
1390 });
1391
1392 tokio::spawn(async move {
1393 futures_util::future::pending::<()>().await;
1394 let _ = server_write.write_all(b"").await;
1395 });
1396
1397 let config = AsyncConnectionConfig::new()
1398 .set_concurrency_limit(1)
1399 .set_response_timeout(Some(Duration::from_millis(100)))
1400 .set_connection_timeout(None);
1401
1402 let (conn, driver) =
1403 MultiplexedConnection::new_with_config(&mock_conn_info(), client_half, config)
1404 .await
1405 .unwrap();
1406 tokio::spawn(driver);
1407
1408 let mut c1 = conn.clone();
1410 let err = c1.send_packed_command(&cmd("PING")).await.unwrap_err();
1411 assert!(err.is_io_error(), "expected IO error from timeout");
1412 cmd_received_rx.recv().await.unwrap();
1413
1414 let mut c2 = conn.clone();
1417 let err = c2.send_packed_command(&cmd("PING")).await.unwrap_err();
1418 assert!(err.is_io_error(), "expected IO error from timeout");
1419 cmd_received_rx.recv().await.unwrap();
1420 }
1421}