1use super::{ConnID, ConnectionResult, Pool, PoolSlot};
21use crate::conn::{ConnectionHealth, ConnectionHealthWatcher, MaxConcurrency};
22use parking_lot::Mutex;
23use rama_core::Service;
24use rama_core::error::BoxErrorExt as _;
25use rama_core::error::{BoxError, ErrorExt};
26use rama_core::extensions::{Extension, Extensions, ExtensionsRef, NetExtension};
27use rama_core::futures::StreamExt as _;
28use rama_core::futures::stream::FuturesUnordered;
29use rama_core::telemetry::tracing::trace;
30use rama_utils::macros::generate_set_and_with;
31use rama_utils::time::AtomicInstant;
32use std::fmt::Debug;
33use std::num::NonZeroUsize;
34use std::sync::Arc;
35use std::sync::atomic::{AtomicUsize, Ordering};
36use std::time::Duration;
37use tokio::sync::{Notify, Semaphore};
38
39#[cfg(feature = "opentelemetry")]
40use super::metrics;
41#[cfg(feature = "opentelemetry")]
42use std::time::Instant;
43
44#[derive(Debug, Clone, Copy, Default)]
47#[non_exhaustive]
48pub enum MuxSelection {
49 #[default]
51 LeastLoaded,
52 FirstAvailable,
54 RoundRobin,
56}
57
58struct StoredConnection<C, ID> {
63 conn: C,
64 id: ID,
65 max_concurrency: Option<Arc<MaxConcurrency>>,
66 active: AtomicUsize,
67 notify: Arc<Notify>,
68 last_idle: AtomicInstant,
69 _pool_slot: PoolSlot,
70}
71
72impl<C, ID> StoredConnection<C, ID> {
73 fn is_idle(&self) -> bool {
75 self.active.load(Ordering::Relaxed) == 0
76 }
77
78 fn effective_capacity(&self, cap: usize) -> usize {
85 self.max_concurrency
86 .as_ref()
87 .map_or(usize::MAX, |m| m.get())
88 .min(cap)
89 }
90
91 fn try_create_multiplexed(
98 self: &Arc<Self>,
99 cap: usize,
100 ) -> Option<MultiplexedConnection<C, ID>> {
101 let limit = self.effective_capacity(cap);
102 let mut active = self.active.load(Ordering::Relaxed);
103 loop {
104 if active >= limit {
105 return None;
106 }
107 match self.active.compare_exchange_weak(
108 active,
109 active + 1,
110 Ordering::Relaxed,
111 Ordering::Relaxed,
112 ) {
113 Ok(_) => {
114 return Some(MultiplexedConnection {
115 inner: self.clone(),
116 });
117 }
118 Err(found) => active = found,
119 }
120 }
121 }
122}
123
124pub struct MultiplexedConnection<C, ID> {
130 inner: Arc<StoredConnection<C, ID>>,
131}
132
133impl<C, ID> Drop for MultiplexedConnection<C, ID> {
134 fn drop(&mut self) {
135 let prev = self.inner.active.fetch_sub(1, Ordering::Release);
136 if prev == 1 {
137 self.inner.last_idle.set_now();
139 }
140 self.inner.notify.notify_waiters();
142 }
143}
144
145impl<C: ExtensionsRef, ID> ExtensionsRef for MultiplexedConnection<C, ID> {
146 fn extensions(&self) -> &Extensions {
147 self.inner.conn.extensions()
148 }
149}
150
151impl<Input, C, ID> Service<Input> for MultiplexedConnection<C, ID>
152where
153 C: Service<Input> + ExtensionsRef,
154 ID: Send + Sync + 'static,
155 Input: Send + 'static,
156{
157 type Output = C::Output;
158 type Error = C::Error;
159
160 async fn serve(&self, input: Input) -> Result<Self::Output, Self::Error> {
161 self.inner.conn.serve(input).await
162 }
163}
164
165impl<C, ID> Debug for MultiplexedConnection<C, ID>
166where
167 ID: Debug,
168{
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 f.debug_struct("MultiplexedConnection")
171 .field("id", &self.inner.id)
172 .field("active_streams", &self.inner.active.load(Ordering::Relaxed))
173 .finish()
174 }
175}
176
177pub struct MultiplexPool<C, ID> {
180 storage: Arc<Mutex<Vec<Arc<StoredConnection<C, ID>>>>>,
181 total_slots: Arc<Semaphore>,
182 idle_timeout: Option<Duration>,
183 max_concurrent_streams: usize,
184 selection: MuxSelection,
185 rr_cursor: Arc<AtomicUsize>,
186 notify: Arc<Notify>,
187 #[cfg(feature = "opentelemetry")]
188 metrics: Option<Arc<metrics::PoolMetrics>>,
189}
190
191impl<C: Send, ID> Extension for MultiplexPool<C, ID>
194where
195 C: Send + Sync + 'static,
196 ID: Send + Sync + Debug + 'static,
197{
198}
199impl<C, ID> NetExtension for MultiplexPool<C, ID>
200where
201 C: Send + Sync + 'static,
202 ID: Send + Sync + Debug + 'static,
203{
204}
205
206impl<C, ID> Debug for MultiplexPool<C, ID> {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 f.debug_struct("MultiplexPool")
209 .field("idle_timeout", &self.idle_timeout)
210 .field("max_concurrent_streams", &self.max_concurrent_streams)
211 .field("selection", &self.selection)
212 .finish()
213 }
214}
215
216impl<C, ID> Clone for MultiplexPool<C, ID> {
217 fn clone(&self) -> Self {
218 Self {
219 storage: self.storage.clone(),
220 total_slots: self.total_slots.clone(),
221 idle_timeout: self.idle_timeout,
222 max_concurrent_streams: self.max_concurrent_streams,
223 selection: self.selection,
224 rr_cursor: self.rr_cursor.clone(),
225 notify: self.notify.clone(),
226 #[cfg(feature = "opentelemetry")]
227 metrics: self.metrics.clone(),
228 }
229 }
230}
231
232impl<C, ID> MultiplexPool<C, ID> {
233 #[must_use]
235 pub fn new(max_concurrent_streams: NonZeroUsize, max_total: NonZeroUsize) -> Self {
236 Self {
237 storage: Arc::new(Mutex::new(Vec::with_capacity(max_total.get()))),
238 total_slots: Arc::new(Semaphore::new(max_total.get())),
239 idle_timeout: None,
240 max_concurrent_streams: max_concurrent_streams.get(),
241 selection: MuxSelection::default(),
242 rr_cursor: Arc::new(AtomicUsize::new(0)),
243 notify: Arc::new(Notify::new()),
244 #[cfg(feature = "opentelemetry")]
245 metrics: None,
246 }
247 }
248
249 pub fn try_new(max_concurrent_streams: usize, max_total: usize) -> Result<Self, BoxError> {
257 let (Some(max_concurrent_streams), Some(max_total)) = (
258 NonZeroUsize::new(max_concurrent_streams),
259 NonZeroUsize::new(max_total),
260 ) else {
261 return Err(BoxError::from_static_str(
262 "max_concurrent_streams and max_total must be greater than 0",
263 )
264 .context_field("max_concurrent_streams", max_concurrent_streams)
265 .context_field("max_total", max_total));
266 };
267 Ok(Self::new(max_concurrent_streams, max_total))
268 }
269
270 generate_set_and_with! {
271 pub fn idle_timeout(mut self, timeout: Option<Duration>) -> Self {
274 self.idle_timeout = timeout;
275 self
276 }
277 }
278
279 generate_set_and_with! {
280 pub fn selection(mut self, selection: MuxSelection) -> Self {
282 self.selection = selection;
283 self
284 }
285 }
286
287 #[cfg(feature = "opentelemetry")]
288 generate_set_and_with! {
289 #[cfg_attr(docsrs, doc(cfg(feature = "opentelemetry")))]
290 pub fn metrics(mut self, metrics: Option<Arc<metrics::PoolMetrics>>) -> Self {
291 self.metrics = metrics;
292 self
293 }
294 }
295}
296
297impl<C, ID> Pool<C, ID> for MultiplexPool<C, ID>
298where
299 C: Send + Sync + ExtensionsRef + 'static,
300 ID: ConnID,
301{
302 type Connection = MultiplexedConnection<C, ID>;
303 type CreatePermit = PoolSlot;
304
305 async fn get_conn(
306 &self,
307 id: &ID,
308 ) -> Result<ConnectionResult<Self::Connection, Self::CreatePermit>, BoxError> {
309 #[cfg(feature = "opentelemetry")]
310 let metrics = self
311 .metrics
312 .as_ref()
313 .map(|metrics| (metrics, metrics.attributes(id)));
314 #[cfg(feature = "opentelemetry")]
315 let start = Instant::now();
316
317 let attempt =
322 |want_cap_changes: bool| -> Result<ConnectionResult<_, _>, FuturesUnordered<_>> {
323 let mut storage = self.storage.lock();
324
325 if let Some(idle_timeout) = self.idle_timeout {
327 storage.retain(|conn| {
328 let drop = conn.is_idle() && conn.last_idle.elapsed() >= idle_timeout;
329 if drop {
330 trace!(id = ?conn.id, "multiplex pool: dropping idle connection");
331 }
332 !drop
333 });
334 }
335
336 storage.retain(|conn| {
339 let broken = conn
340 .conn
341 .extensions()
342 .get_ref::<ConnectionHealthWatcher>()
343 .is_some_and(|watcher| watcher.health() == ConnectionHealth::Broken);
344 if broken {
345 trace!(id = ?conn.id, "multiplex pool: dropping broken connection");
346 }
347 !broken
348 });
349
350 if let Some(conn) = select_and_admit(
351 &storage,
352 id,
353 self.selection,
354 &self.rr_cursor,
355 self.max_concurrent_streams,
356 ) {
357 trace!(?id, "multiplex pool: reusing connection");
358 #[cfg(feature = "opentelemetry")]
359 if let Some((metrics, attrs)) = &metrics {
360 metrics.reused_connections.add(1, attrs);
361 metrics.streams.add(1, attrs);
362 metrics
363 .concurrent_streams
364 .record(conn.inner.active.load(Ordering::Relaxed) as f64, attrs);
365 metrics
366 .active_connection_delay_nanoseconds
367 .record(start.elapsed().as_nanos() as f64, attrs);
368 }
369 return Ok(ConnectionResult::Connection(conn));
370 }
371
372 let saturation = storage.iter().any(|conn| &conn.id == id);
373
374 let pool_slot = if let Ok(permit) = self.total_slots.clone().try_acquire_owned() {
377 Some(PoolSlot(permit))
378 } else {
379 let lru_idle = storage
380 .iter()
381 .enumerate()
382 .filter(|(_, conn)| conn.is_idle())
383 .min_by_key(|(_, conn)| conn.last_idle.as_nanos())
384 .map(|(pos, _)| pos);
385 if let Some(pos) = lru_idle {
386 storage.remove(pos);
387 #[cfg(feature = "opentelemetry")]
388 if let Some((metrics, attrs)) = &metrics {
389 metrics.evicted_connections.add(1, attrs);
390 }
391 self.total_slots
392 .clone()
393 .try_acquire_owned()
394 .ok()
395 .map(PoolSlot)
396 } else {
397 None
398 }
399 };
400
401 if let Some(pool_slot) = pool_slot {
402 trace!(
403 ?id,
404 "multiplex pool: no connection with capacity, returning create permit"
405 );
406 #[cfg(feature = "opentelemetry")]
407 if let Some((metrics, attrs)) = &metrics {
408 if saturation {
409 metrics.saturation_created_connections.add(1, attrs);
410 }
411 metrics
412 .active_connection_delay_nanoseconds
413 .record(start.elapsed().as_nanos() as f64, attrs);
414 }
415 #[cfg(not(feature = "opentelemetry"))]
416 let _ = saturation;
417 return Ok(ConnectionResult::CreatePermit(pool_slot));
418 }
419
420 let cap_changes = if want_cap_changes {
421 storage
422 .iter()
423 .filter(|conn| &conn.id == id)
424 .filter_map(|conn| conn.max_concurrency.clone())
425 .map(|mc| async move { mc.watch().changed().await })
426 .collect()
427 } else {
428 FuturesUnordered::new()
429 };
430 Err(cap_changes)
431 };
432
433 loop {
434 if let Ok(result) = attempt(false) {
436 return Ok(result);
437 }
438
439 let notified = self.notify.notified();
442 tokio::pin!(notified);
443 notified.as_mut().enable();
444 let mut cap_changes = match attempt(true) {
445 Ok(result) => return Ok(result),
446 Err(cap_changes) => cap_changes,
447 };
448
449 trace!(?id, "multiplex pool: saturated, waiting for capacity");
450 tokio::select! {
453 _ = notified => {}
454 _ = cap_changes.next(), if !cap_changes.is_empty() => {}
455 }
456 }
457 }
458
459 async fn create(&self, id: ID, conn: C, pool_slot: PoolSlot) -> Self::Connection {
460 let conn = Arc::new(StoredConnection {
461 max_concurrency: conn.extensions().get_arc::<MaxConcurrency>(),
462 conn,
463 id,
464 active: AtomicUsize::new(1),
465 notify: self.notify.clone(),
466 last_idle: AtomicInstant::now(),
467 _pool_slot: pool_slot,
468 });
469
470 trace!(id = ?conn.id, "multiplex pool: adding new connection");
471 self.storage.lock().push(conn.clone());
472
473 self.notify.notify_waiters();
476
477 #[cfg(feature = "opentelemetry")]
478 if let Some(metrics) = self.metrics.as_ref() {
479 let attrs = metrics.attributes(&conn.id);
480 metrics.total_connections.add(1, &attrs);
481 metrics.created_connections.add(1, &attrs);
482 metrics.streams.add(1, &attrs);
483 metrics.concurrent_streams.record(1.0, &attrs);
484 }
485
486 MultiplexedConnection { inner: conn }
487 }
488}
489
490fn select_and_admit<C, ID: PartialEq>(
493 storage: &[Arc<StoredConnection<C, ID>>],
494 id: &ID,
495 selection: MuxSelection,
496 rr_cursor: &AtomicUsize,
497 cap: usize,
498) -> Option<MultiplexedConnection<C, ID>> {
499 let same_id = |conn: &&Arc<StoredConnection<C, ID>>| &conn.id == id;
500 let has_capacity = |conn: &&Arc<StoredConnection<C, ID>>| {
501 same_id(conn) && conn.active.load(Ordering::Relaxed) < conn.effective_capacity(cap)
502 };
503 let create_conn = |conn: &Arc<StoredConnection<C, ID>>| conn.try_create_multiplexed(cap);
504
505 match selection {
506 MuxSelection::FirstAvailable => storage.iter().filter(same_id).find_map(create_conn),
508 MuxSelection::LeastLoaded => storage
510 .iter()
511 .filter(has_capacity)
512 .min_by_key(|conn| conn.active.load(Ordering::Relaxed))
513 .and_then(create_conn),
514 MuxSelection::RoundRobin => {
515 let count = storage.iter().filter(has_capacity).count();
516 if count == 0 {
517 None
518 } else {
519 let idx = rr_cursor.fetch_add(1, Ordering::Relaxed) % count;
520 storage
521 .iter()
522 .filter(has_capacity)
523 .nth(idx)
524 .and_then(create_conn)
525 }
526 }
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::super::PooledConnector;
533 use super::*;
534 use crate::client::{
535 ConnectionErrorDomain, ConnectionErrorKind, ConnectorService, EstablishedClientConnection,
536 };
537 use rama_core::ServiceInput;
538 use std::convert::Infallible;
539
540 #[derive(Clone, Debug, PartialEq, Eq)]
541 struct TestId(u32);
542 impl ConnID for TestId {}
543
544 #[derive(Debug)]
545 struct Conn {
546 serial: usize,
547 extensions: Extensions,
548 }
549
550 impl ExtensionsRef for Conn {
551 fn extensions(&self) -> &Extensions {
552 &self.extensions
553 }
554 }
555
556 impl Service<()> for Conn {
557 type Output = usize;
558 type Error = Infallible;
559
560 async fn serve(&self, (): ()) -> Result<Self::Output, Self::Error> {
561 Ok(self.serial)
562 }
563 }
564
565 #[derive(Default)]
566 struct TestConnector {
567 created: AtomicUsize,
568 max_concurrency: Option<usize>,
569 }
570
571 impl<Input> Service<Input> for TestConnector
572 where
573 Input: Send + 'static,
574 {
575 type Output = EstablishedClientConnection<Conn, Input>;
576 type Error = Infallible;
577
578 async fn serve(&self, input: Input) -> Result<Self::Output, Self::Error> {
579 let serial = self.created.fetch_add(1, Ordering::Relaxed);
580 let conn = Conn {
581 serial,
582 extensions: Extensions::new(),
583 };
584 conn.extensions.insert(ConnectionHealthWatcher::default());
585 if let Some(mc) = self.max_concurrency {
586 conn.extensions.insert(MaxConcurrency::new(mc));
587 }
588 Ok(EstablishedClientConnection { input, conn })
589 }
590 }
591
592 struct SlowConnector {
595 created: AtomicUsize,
596 delay: Duration,
597 }
598
599 impl<Input> Service<Input> for SlowConnector
600 where
601 Input: Send + 'static,
602 {
603 type Output = EstablishedClientConnection<Conn, Input>;
604 type Error = Infallible;
605
606 async fn serve(&self, input: Input) -> Result<Self::Output, Self::Error> {
607 tokio::time::sleep(self.delay).await;
608 let serial = self.created.fetch_add(1, Ordering::Relaxed);
609 let conn = Conn {
610 serial,
611 extensions: Extensions::new(),
612 };
613 conn.extensions.insert(ConnectionHealthWatcher::default());
614 Ok(EstablishedClientConnection { input, conn })
615 }
616 }
617
618 fn id_fn(input: &ServiceInput<u32>) -> Result<TestId, BoxError> {
619 Ok(TestId(input.input))
620 }
621
622 type MuxConnector = PooledConnector<
623 TestConnector,
624 MultiplexPool<Conn, TestId>,
625 fn(&ServiceInput<u32>) -> Result<TestId, BoxError>,
626 >;
627
628 fn connector_with(
629 pool: MultiplexPool<Conn, TestId>,
630 max_concurrency: Option<usize>,
631 ) -> MuxConnector {
632 let connector = TestConnector {
633 created: AtomicUsize::new(0),
634 max_concurrency,
635 };
636 PooledConnector::new(
637 connector,
638 pool,
639 id_fn as fn(&ServiceInput<u32>) -> Result<TestId, BoxError>,
640 )
641 }
642
643 fn connector(pool: MultiplexPool<Conn, TestId>) -> MuxConnector {
644 connector_with(pool, None)
646 }
647
648 async fn connect(
649 svc: &MuxConnector,
650 id: u32,
651 ) -> EstablishedClientConnection<MultiplexedConnection<Conn, TestId>, ServiceInput<u32>> {
652 svc.connect(ServiceInput::new(id)).await.unwrap()
653 }
654
655 fn created(svc: &MuxConnector) -> usize {
656 svc.inner.created.load(Ordering::Relaxed)
657 }
658
659 #[tokio::test]
660 async fn shares_one_connection() {
661 let pool = MultiplexPool::new(NonZeroUsize::new(4).unwrap(), NonZeroUsize::new(4).unwrap());
662 let svc = connector(pool);
663
664 let mut handles = Vec::new();
665 for _ in 0..4 {
666 handles.push(connect(&svc, 0).await);
667 }
668 assert_eq!(
669 created(&svc),
670 1,
671 "all 4 handouts should share one connection"
672 );
673 for h in &handles {
674 assert_eq!(h.conn.serve(()).await.unwrap(), 0);
675 }
676 }
677
678 #[tokio::test(start_paused = true)]
679 async fn maxconcurrency_increase_wakes_waiters() {
680 let pool = MultiplexPool::try_new(10, 1).unwrap();
681 let svc = Arc::new(connector_with(pool, Some(1)));
682
683 let c1 = svc.connect(ServiceInput::new(0)).await.unwrap();
684
685 let woke = Arc::new(std::sync::atomic::AtomicBool::new(false));
686 let waiter = {
687 let svc = svc.clone();
688 let woke = woke.clone();
689 tokio::spawn(async move {
690 let _h = svc.connect(ServiceInput::new(0)).await.unwrap();
691 woke.store(true, Ordering::Relaxed);
692 })
693 };
694
695 tokio::time::sleep(Duration::from_millis(50)).await;
697 assert!(!woke.load(Ordering::Relaxed), "waiter should be parked");
698
699 c1.conn
702 .extensions()
703 .get_ref::<MaxConcurrency>()
704 .unwrap()
705 .set(2);
706
707 tokio::time::timeout(Duration::from_secs(1), waiter)
708 .await
709 .expect("a MaxConcurrency increase should wake the parked waiter")
710 .unwrap();
711 assert!(woke.load(Ordering::Relaxed));
712 assert_eq!(svc.inner.created.load(Ordering::Relaxed), 1);
714 }
715
716 #[tokio::test]
717 async fn maxconcurrency_zero_admits_no_streams() {
718 let pool = MultiplexPool::try_new(4, 4).unwrap();
719 let svc = connector_with(pool, Some(0));
720
721 let c1 = connect(&svc, 0).await;
722 assert_eq!(created(&svc), 1);
723 drop(c1);
724
725 let _c2 = connect(&svc, 0).await;
729 assert_eq!(
730 created(&svc),
731 2,
732 "a connection advertising max_concurrency=0 must not admit new streams"
733 );
734 }
735
736 #[tokio::test(start_paused = true)]
737 async fn new_multiplexed_connection_wakes_waiters() {
738 let pool = MultiplexPool::try_new(2, 1).unwrap();
739 let svc = PooledConnector::new(
740 SlowConnector {
741 created: AtomicUsize::new(0),
742 delay: Duration::from_millis(100),
743 },
744 pool,
745 id_fn as fn(&ServiceInput<u32>) -> Result<TestId, BoxError>,
746 )
747 .with_wait_for_pool_timeout(Duration::from_millis(500));
748
749 let c1 = svc.connect(ServiceInput::new(1u32)).await.unwrap();
750
751 let waiter1 = svc.connect(ServiceInput::new(2u32));
752 let waiter2 = svc.connect(ServiceInput::new(2u32));
753
754 tokio::time::sleep(Duration::from_millis(20)).await;
755 drop(c1);
756
757 let (r1, r2) = tokio::join!(waiter1, waiter2);
758 assert!(r1.is_ok(), "first waiter should create a new connection");
759 assert!(
760 r2.is_ok(),
761 "second waiter should reuse the spare stream slot"
762 );
763 }
764
765 #[tokio::test]
766 async fn new_connection_when_saturated() {
767 let pool = MultiplexPool::try_new(2, 2).unwrap();
768 let svc = connector(pool);
769
770 let _c1 = connect(&svc, 0).await;
771 let _c2 = connect(&svc, 0).await;
772 assert_eq!(
773 created(&svc),
774 1,
775 "connection 0 should be reused while it has room"
776 );
777
778 let c3 = connect(&svc, 0).await;
779 assert_eq!(
780 created(&svc),
781 2,
782 "a 3rd concurrent handout needs a new connection"
783 );
784 assert_eq!(c3.conn.serve(()).await.unwrap(), 1);
785 }
786
787 #[tokio::test]
788 async fn extensions_propagate_at_establish() {
789 let pool = MultiplexPool::try_new(2, 2).unwrap();
790 let svc = connector(pool);
791
792 let c = connect(&svc, 0).await;
793
794 assert!(
795 c.conn
796 .extensions()
797 .get_ref::<ConnectionHealthWatcher>()
798 .is_some()
799 );
800 }
801
802 #[tokio::test]
803 async fn broken_removed_while_handles_survive() {
804 let pool = MultiplexPool::try_new(2, 2).unwrap();
805 let svc = connector(pool);
806
807 let c1 = connect(&svc, 0).await;
808 let c2 = connect(&svc, 0).await;
809 assert_eq!(created(&svc), 1);
810
811 c1.conn
813 .extensions()
814 .get_ref::<ConnectionHealthWatcher>()
815 .unwrap()
816 .mark_broken();
817
818 let c3 = connect(&svc, 0).await;
820 assert_eq!(created(&svc), 2);
821 assert_eq!(c3.conn.serve(()).await.unwrap(), 1);
822
823 assert_eq!(c1.conn.serve(()).await.unwrap(), 0);
825 assert_eq!(c2.conn.serve(()).await.unwrap(), 0);
826
827 drop(c1);
829 drop(c2);
830 drop(c3);
831 let _c4 = connect(&svc, 0).await;
832 assert_eq!(created(&svc), 2);
834 }
835
836 #[tokio::test(start_paused = true)]
837 async fn idle_eviction() {
838 let pool = MultiplexPool::try_new(2, 5)
839 .unwrap()
840 .with_idle_timeout(Duration::from_micros(1));
841 let svc = connector(pool);
842
843 let c = connect(&svc, 0).await;
844 assert_eq!(created(&svc), 1);
845 drop(c);
846
847 tokio::time::sleep(Duration::from_millis(50)).await;
848
849 let _c = connect(&svc, 0).await;
850 assert_eq!(created(&svc), 2, "idle connection should have been evicted");
851 }
852
853 #[tokio::test]
854 async fn least_loaded_selection() {
855 let pool = MultiplexPool::try_new(3, 2)
856 .unwrap()
857 .with_selection(MuxSelection::LeastLoaded);
858 let svc = connector(pool);
859
860 let c1 = connect(&svc, 0).await;
861 let _c2 = connect(&svc, 0).await;
862 let _c3 = connect(&svc, 0).await;
863 let _c4 = connect(&svc, 0).await;
864 assert_eq!(created(&svc), 2);
865
866 drop(c1);
867
868 let c5 = connect(&svc, 0).await;
869 assert_eq!(
870 c5.conn.serve(()).await.unwrap(),
871 1,
872 "least-loaded should pick connection 1 (more free streams)"
873 );
874 }
875
876 #[tokio::test]
877 async fn first_available_selection() {
878 let pool = MultiplexPool::try_new(3, 2)
879 .unwrap()
880 .with_selection(MuxSelection::FirstAvailable);
881 let svc = connector(pool);
882
883 let c1 = connect(&svc, 0).await;
884 let _c2 = connect(&svc, 0).await;
885 let _c3 = connect(&svc, 0).await;
886 let _c4 = connect(&svc, 0).await;
887 assert_eq!(created(&svc), 2);
888
889 drop(c1);
890
891 let c5 = connect(&svc, 0).await;
892 assert_eq!(
893 c5.conn.serve(()).await.unwrap(),
894 0,
895 "first-available should pick connection 0 (first with a free slot)"
896 );
897 }
898
899 #[tokio::test]
900 async fn capacity_one_is_exclusive() {
901 let pool = MultiplexPool::try_new(1, 3).unwrap();
902 let svc = connector(pool);
903
904 let c1 = connect(&svc, 0).await;
905 let c2 = connect(&svc, 0).await;
906 let c3 = connect(&svc, 0).await;
907 assert_eq!(created(&svc), 3, "capacity 1 never shares a connection");
908 assert_eq!(c1.conn.serve(()).await.unwrap(), 0);
910 assert_eq!(c2.conn.serve(()).await.unwrap(), 1);
911 assert_eq!(c3.conn.serve(()).await.unwrap(), 2);
912 }
913
914 #[tokio::test(start_paused = true)]
915 async fn saturation_waits_and_times_out() {
916 let pool = MultiplexPool::try_new(1, 1).unwrap();
917 let svc = connector(pool).with_wait_for_pool_timeout(Duration::from_millis(50));
918
919 let c1 = connect(&svc, 0).await;
920 let error = svc
922 .connect(ServiceInput::new(0u32))
923 .await
924 .expect_err("saturated pool should time out");
925 assert_eq!(error.domain(), ConnectionErrorDomain::Local);
926 assert_eq!(error.kind(), ConnectionErrorKind::Timeout);
927
928 drop(c1);
929 let _c2 = connect(&svc, 0).await;
931 }
932
933 #[tokio::test]
934 async fn capacity_from_extension() {
935 let pool = MultiplexPool::try_new(5, 5).unwrap();
937 let svc = connector_with(pool, Some(2));
938
939 let _c1 = connect(&svc, 0).await;
940 let _c2 = connect(&svc, 0).await;
941 assert_eq!(
942 created(&svc),
943 1,
944 "two streams share the connection (its advertised capacity)"
945 );
946
947 let _c3 = connect(&svc, 0).await;
948 assert_eq!(
949 created(&svc),
950 2,
951 "a 3rd stream exceeds the advertised capacity -> new connection"
952 );
953 }
954
955 #[tokio::test]
956 async fn capacity_is_read_live() {
957 let pool = MultiplexPool::try_new(10, 5).unwrap();
959 let svc = connector_with(pool, Some(1));
960
961 let c1 = connect(&svc, 0).await; let _c2 = connect(&svc, 0).await; assert_eq!(created(&svc), 2);
964
965 c1.conn
967 .extensions()
968 .get_ref::<MaxConcurrency>()
969 .unwrap()
970 .set(3);
971
972 let _c3 = connect(&svc, 0).await;
975 assert_eq!(
976 created(&svc),
977 2,
978 "raising MaxConcurrency lets A take another stream (dynamic capacity)"
979 );
980 }
981
982 #[tokio::test]
983 async fn no_extension_uses_pool_cap() {
984 let pool = MultiplexPool::try_new(2, 8).unwrap();
987 let svc = connector_with(pool, None);
988
989 let _c1 = connect(&svc, 0).await;
990 let _c2 = connect(&svc, 0).await;
991 assert_eq!(
992 created(&svc),
993 1,
994 "two streams share one connection (pool cap 2)"
995 );
996
997 let _c3 = connect(&svc, 0).await;
998 assert_eq!(
999 created(&svc),
1000 2,
1001 "a 3rd stream exceeds the pool cap -> new connection"
1002 );
1003 }
1004
1005 #[tokio::test]
1006 async fn lru_eviction_when_full() {
1007 let pool = MultiplexPool::try_new(1, 2).unwrap();
1008 let svc = connector(pool);
1009
1010 drop(connect(&svc, 0).await);
1013 drop(connect(&svc, 1).await);
1014 tokio::time::sleep(Duration::from_millis(10)).await;
1015 drop(connect(&svc, 0).await); assert_eq!(created(&svc), 2);
1017
1018 drop(connect(&svc, 2).await);
1020 assert_eq!(created(&svc), 3);
1021
1022 drop(connect(&svc, 0).await);
1025 assert_eq!(
1026 created(&svc),
1027 3,
1028 "A survived: LRU evicted B, not first-in-storage A"
1029 );
1030
1031 drop(connect(&svc, 1).await);
1033 assert_eq!(created(&svc), 4, "B (LRU) was evicted");
1034 }
1035
1036 #[test]
1037 fn virtual_conn_is_send_sync() {
1038 fn assert_send_sync<T: Send + Sync + 'static>() {}
1039 assert_send_sync::<MultiplexedConnection<Conn, TestId>>();
1040 fn assert_pool<P: Pool<Conn, TestId>>() {}
1041 assert_pool::<MultiplexPool<Conn, TestId>>();
1042 }
1043}