Skip to main content

redis/aio/
multiplexed_connection.rs

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
41// Senders which the result of a single request are sent through
42type PipelineOutput = oneshot::Sender<RedisResult<Value>>;
43
44enum ErrorOrErrors {
45    Errors(Vec<(usize, ServerError)>),
46    // only set if we receive a transmission error
47    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
59// TODO - this is a really bad name.
60struct PipelineResponseExpectation {
61    // The number of responses to skip before starting to save responses in the buffer.
62    skipped_response_count: usize,
63    // The number of responses to keep in the buffer
64    expected_response_count: usize,
65    // whether the pipelined request is a transaction
66    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
88// A single message sent through the pipeline
89struct PipelineMessage {
90    input: Vec<u8>,
91    // If `output` is None, then the caller doesn't expect to receive an answer.
92    output: Option<PipelineOutput>,
93    // If `None`, this is a single request, not a pipeline of multiple requests.
94    // If `Some`, the first value is the number of responses to skip,
95    // the second is the number of responses to keep, and the third is whether the pipeline is a transaction.
96    expectation: Option<PipelineResponseExpectation>,
97}
98
99/// Wrapper around a `Stream + Sink` where each item sent through the `Sink` results in one or more
100/// items being output by the `Stream` (the number is specified at time of sending). With the
101/// interface provided by `Pipeline` an easy interface of request to response, hiding the `Stream`
102/// and `Sink`.
103#[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    // Read messages from the stream and send them back to the caller
170    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                // The redis response stream is not going to produce any more items so we simulate a disconnection error to break out of the loop.
176                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            // If this push message isn't a reply, we'll pass it as-is to the push manager and stop iterating
193            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            // If this push message is a reply to a query, we'll clone it to the push manager and continue with sending the reply
203            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                    // server errors in skipped values are still counted for errors in transactions, since they're errors that will cause the transaction to fail,
241                    // and we only skip values in transaction.
242                    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)); // - 1 to offset the early increment, and -1 to offset the added MULTI call.
247                                }
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                    // Need to gather more response values
272                    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                // `Err` means that the receiver was dropped in which case it does not
289                // care about the output and we can continue by just dropping the value
290                // and sender
291                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    // Retrieve incoming messages and write them to the sink
306    fn poll_ready(
307        mut self: Pin<&mut Self>,
308        cx: &mut task::Context,
309    ) -> Poll<Result<(), Self::Error>> {
310        // It is crucial that we always try to advance both the read and the write side together.
311        // If we do not, we are susceptible to TCP deadlock.
312        // Here, we do this by advancing reads and then moving on to advance writes regardless of
313        // whether the read was pending or not. This ensures that we are registered to be woken
314        // if either reads or writes become available.
315        // See https://github.com/redis-rs/redis-rs/issues/1955.
316        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 initially a receiver was created, but then dropped, there is nothing to receive our output we do not need to send the message as it is
337        // ambiguous whether the message will be sent anyway. Helps shed some load on the
338        // connection.
339        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        // It is crucial that we always try to advance both the read and the write side together.
377        // If we do not, we are susceptible to TCP deadlock.
378        // Here, we do this by advancing reads and then moving on to advance writes regardless of
379        // whether the read was pending or not. This ensures that we are registered to be woken
380        // if either reads or writes become available.
381        // See https://github.com/redis-rs/redis-rs/issues/1955.
382        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        // No new requests will come in after the first call to `close` but we need to complete any
399        // in progress requests before closing
400        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        // If `None`, this is a single request, not a pipeline of multiple requests.
447        // If `Some`, the value inside defines how the response should look like
448        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            // The `sender` was dropped which likely means that the stream part
483            // failed for one reason or another
484            .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/// A connection object which can be cloned, allowing requests to be be sent concurrently
500/// on the same underlying connection (tcp/unix socket).
501///
502/// This connection object is cancellation-safe, and the user can drop request future without polling them to completion,
503/// but this doesn't mean that the actual request sent to the server is cancelled.
504/// A side-effect of this is that the underlying connection won't be closed until all sent requests have been answered,
505/// which means that in case of blocking commands, the underlying connection resource might not be released,
506/// even when all clones of the multiplexed connection have been dropped (see <https://github.com/redis-rs/redis-rs/issues/1236>).
507/// This isn't an issue in a connection that was created in a canonical way, which ensures that `_task_handle` is set, so that
508/// once all of the connection's clones are dropped, the task will also be dropped. If the user creates the connection in
509/// another way and `_task_handle` isn't set, they should manually spawn the returned driver function, keep the spawned task's
510/// handle and abort the task whenever they want, at the risk of effectively closing the clones of the multiplexed connection.
511#[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    // This handle ensures that once all the clones of the connection will be dropped, the underlying task will stop.
519    // This handle is only set for connection whose task was spawned by the crate, not for users who spawned their own
520    // task.
521    _task_handle: Option<SharedHandleContainer>,
522    #[cfg(feature = "cache-aio")]
523    pub(crate) cache_manager: Option<CacheManager>,
524    #[cfg(feature = "token-based-authentication")]
525    // This handle ensures that once all the clones of the connection will be dropped, the underlying task will stop.
526    // It is only set for connections that use a credentials provider for token-based authentication.
527    _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    /// Constructs a new `MultiplexedConnection` out of a `AsyncRead + AsyncWrite` object
556    /// and a `RedisConnectionInfo`
557    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    /// Constructs a new `MultiplexedConnection` out of a `AsyncRead + AsyncWrite` object
568    /// , a `RedisConnectionInfo` and a `AsyncConnectionConfig`.
569    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            // Retrieve the initial credentials from the provider and apply them to the connection info
617            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        // Set up streaming credentials subscription if provider is available
677        #[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    /// This should be called strictly before the multiplexed connection is cloned - that is, before it is returned to the user.
724    /// Otherwise some clones will be able to kill the backing task, while other clones are still alive.
725    pub(crate) fn set_task_handle(&mut self, handle: TaskHandle) {
726        self._task_handle = Some(SharedHandleContainer::new(handle));
727    }
728
729    /// Sets the time that the multiplexer will wait for responses on operations before failing.
730    pub fn set_response_timeout(&mut self, timeout: std::time::Duration) {
731        self.response_timeout = Some(timeout);
732    }
733
734    /// Sends an already encoded (packed) command into the TCP socket and
735    /// reads the single response from it.
736    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    /// Sends multiple already encoded (packed) command into the TCP socket
783    /// and reads `count` responses from it.  This is used to implement
784    /// pipelining.
785    pub async fn send_packed_commands(
786        &mut self,
787        cmd: &crate::Pipeline,
788        offset: usize,
789        count: usize,
790    ) -> RedisResult<Vec<Value>> {
791        // Try to acquire 1 permit per command in the pipeline: block on the first to guarantee
792        // progress, then grab as many more as are immediately available without blocking.
793        // This roughly reflects the pipeline's load on the server while avoiding deadlock --
794        // a large pipeline can always proceed even if it can't acquire all permits.
795        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    /// Gets [`CacheStatistics`] for current connection if caching is enabled.
853    #[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    /// Subscribes to a new channel(s).    
881    ///
882    /// Updates from the sender will be sent on the push sender that was passed to the connection.
883    /// If the connection was configured without a push sender, the connection won't be able to pass messages back to the user.
884    ///
885    /// This method is only available when the connection is using RESP3 protocol, and will return an error otherwise.
886    ///
887    /// ```rust,no_run
888    /// # async fn func() -> redis::RedisResult<()> {
889    /// let client = redis::Client::open("redis://127.0.0.1/?protocol=resp3").unwrap();
890    /// let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
891    /// let config = redis::AsyncConnectionConfig::new().set_push_sender(tx);
892    /// let mut con = client.get_multiplexed_async_connection_with_config(&config).await?;
893    /// con.subscribe(&["channel_1", "channel_2"]).await?;
894    /// # Ok(()) }
895    /// ```
896    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    /// Unsubscribes from channel(s).
905    ///
906    /// This method is only available when the connection is using RESP3 protocol, and will return an error otherwise.
907    ///
908    /// ```rust,no_run
909    /// # async fn func() -> redis::RedisResult<()> {
910    /// let client = redis::Client::open("redis://127.0.0.1/?protocol=resp3").unwrap();
911    /// let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
912    /// let config = redis::AsyncConnectionConfig::new().set_push_sender(tx);
913    /// let mut con = client.get_multiplexed_async_connection_with_config(&config).await?;
914    /// con.subscribe(&["channel_1", "channel_2"]).await?;
915    /// con.unsubscribe(&["channel_1", "channel_2"]).await?;
916    /// # Ok(()) }
917    /// ```
918    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    /// Subscribes to new channel(s) with pattern(s).
927    ///
928    /// Updates from the sender will be sent on the push sender that was passed to the connection.
929    /// If the connection was configured without a push sender, the connection won't be able to pass messages back to the user.
930    ///
931    /// This method is only available when the connection is using RESP3 protocol, and will return an error otherwise.
932    ///
933    /// ```rust,no_run
934    /// # async fn func() -> redis::RedisResult<()> {
935    /// let client = redis::Client::open("redis://127.0.0.1/?protocol=resp3").unwrap();
936    /// let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
937    /// let config = redis::AsyncConnectionConfig::new().set_push_sender(tx);
938    /// let mut con = client.get_multiplexed_async_connection_with_config(&config).await?;
939    /// con.psubscribe("channel*_1").await?;
940    /// con.psubscribe(&["channel*_2", "channel*_3"]).await?;
941    /// # Ok(())
942    /// # }
943    /// ```
944    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    /// Unsubscribes from channel pattern(s).
953    ///
954    /// This method is only available when the connection is using RESP3 protocol, and will return an error otherwise.
955    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    /// Re-authenticate the connection with new credentials
967    ///
968    /// This method allows existing async connections to update their authentication
969    /// when tokens are refreshed, enabling streaming credential updates.
970    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        // Start a second request that will block on the semaphore, then cancel it
1215        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        // Complete the first request
1224        send_response_tx.send(()).await.unwrap();
1225        h1.await.unwrap().unwrap();
1226
1227        // The permit from the cancelled request should have been released,
1228        // so a new request should proceed
1229        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    /// Regression test for the TCP buffer deadlock reported in
1246    /// <https://github.com/redis-rs/redis-rs/issues/1955>.
1247    ///
1248    /// # Why this can deadlock
1249    ///
1250    /// A TCP buffer deadlock is a property of *both* peers — neither can
1251    /// produce it alone.
1252    ///
1253    /// **Client side (this bug):** `PipelineSink::poll_flush` polled the read
1254    /// half only *after* the underlying writer's flush returned Ready. When
1255    /// our TCP send buffer fills, the codec's flush is Pending, and we
1256    /// returned Pending without ever registering a read waker — so response
1257    /// bytes already sitting in our recv buffer were never polled. The driver
1258    /// task parked indefinitely.
1259    ///
1260    /// **Server side:** any server that stops reading from a client's
1261    /// socket while its own pending write to that client can't make
1262    /// forward progress is enough to trigger the deadlock. Combined with
1263    /// the client bug, both directions wedge: the client's send buffer
1264    /// can't drain (server isn't reading), the server's send buffer can't
1265    /// drain (client isn't reading because of the bug), and the state is
1266    /// permanent.
1267    ///
1268    /// # What this test simulates
1269    ///
1270    /// `tokio::io::duplex` stands in for a TCP socket pair with tiny
1271    /// kernel buffers. The mini-server is a loop that reads a request,
1272    /// writes a response, repeats — awaiting the write makes the loop
1273    /// stop reading the instant a write becomes Pending, which is the
1274    /// general server-side behavior described above.
1275    ///
1276    /// The test issues a few large concurrent SETs whose request payloads
1277    /// (and pretend responses) each exceed the duplex buffer in both
1278    /// directions. With the bug, the client's codec backs up, the
1279    /// server's writes back up, and both sides park without enough wakers
1280    /// to escape — the test times out and panics. With the fix, the
1281    /// driver drains responses even while the writer is back-pressured,
1282    /// which keeps the duplex moving and lets the SETs complete.
1283    #[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        // Small duplex buffer + ~4 KiB request/response sizes. The polling
1290        // pathology doesn't depend on scale; a real socket would see the
1291        // same shape with tens of KiB of buffer and MB-scale payloads.
1292        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        // Pretend response: a bulk string the same size as the request
1300        // payload, so server-bound writes also exceed the duplex buffer
1301        // (the server's write pends and its loop stops reading).
1302        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        // Mini-server: read a request, write a response, loop. Awaiting
1308        // the write makes the loop stop reading the instant a write
1309        // becomes Pending — the general "server stops reading once its
1310        // own write is back-pressured" behavior the deadlock requires.
1311        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        // A handful of concurrent large SETs. Each request exceeds the
1334        // duplex buffer (codec's flush stays backed up); each reply also
1335        // exceeds the duplex buffer (server's write stays backed up). Both
1336        // directions wedge.
1337        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        // Clean up before asserting so a panic doesn't leak tasks.
1358        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        // First request times out since the mock never responds
1409        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        // Second request should acquire the permit released by the first,
1415        // reach the server, and then also time out
1416        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}