Skip to main content

orbit_client_pool/
pool.rs

1use std::collections::{HashMap, VecDeque};
2use std::future::Future;
3use std::ops::{Deref, DerefMut};
4use std::sync::{Arc, Mutex, MutexGuard};
5use std::time::Instant;
6
7use async_trait::async_trait;
8use tokio::sync::Notify;
9use tokio::time::Instant as TokioInstant;
10use tokio_util::sync::CancellationToken;
11
12use crate::{
13    AcquireError, ClientPolicy, OptionsError, PoolDirective, PoolOptions,
14};
15
16/// Creates and validates one protocol-specific outbound client.
17#[async_trait]
18pub trait ClientManager: Send + Sync + 'static {
19    type Client: Send + 'static;
20    type Error: std::error::Error + Send + Sync + 'static;
21
22    /// Establish a fresh client. Called only after capacity is reserved.
23    async fn create(&self) -> Result<Self::Client, Self::Error>;
24
25    /// Prove that an idle client may be handed out again.
26    async fn recycle(
27        &self,
28        client: &mut Self::Client,
29    ) -> Result<(), Self::Error>;
30}
31
32/// Current local ownership and queue counts.
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct PoolStats {
35    pub live: usize,
36    pub idle: usize,
37    pub leased: usize,
38    pub creating: usize,
39    pub waiting: usize,
40    /// Requests that currently hold a client, including a client being created.
41    pub in_flight: usize,
42    pub state: PoolState,
43}
44
45/// Admission and lifecycle state of one pool.
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub enum PoolState {
48    Open,
49    Paused { reason: Arc<str> },
50    Draining,
51    Closed,
52}
53
54/// Shutdown behavior declared by the owner of one active request.
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub enum ActiveRequestPolicy {
57    /// Let the request finish naturally while pool drain waits for its lease.
58    Preserve,
59    /// Signal the request on drain, immediately or after the declared grace.
60    CancelOnDrain(Option<std::time::Duration>),
61}
62
63struct ActiveCancellation {
64    policy: ActiveRequestPolicy,
65    token: CancellationToken,
66}
67
68struct Entry<C> {
69    client: C,
70    created_at: Instant,
71    idle_since: Instant,
72}
73
74struct State<C> {
75    idle: VecDeque<Entry<C>>,
76    live: usize,
77    creating: usize,
78    waiting: usize,
79    in_flight: usize,
80    status: PoolState,
81    next_active_id: u64,
82    drain_started: Option<Instant>,
83    drain_cancellations: HashMap<u64, ActiveCancellation>,
84}
85
86struct Inner<M: ClientManager> {
87    manager: M,
88    options: PoolOptions,
89    state: Mutex<State<M::Client>>,
90    changed: Notify,
91    admission_closed: CancellationToken,
92    active_cancellation: CancellationToken,
93}
94
95/// A bounded process-local pool of protocol-specific clients.
96pub struct ClientPool<M: ClientManager> {
97    inner: Arc<Inner<M>>,
98}
99
100impl<M: ClientManager> Clone for ClientPool<M> {
101    fn clone(&self) -> Self {
102        Self { inner: Arc::clone(&self.inner) }
103    }
104}
105
106impl<M: ClientManager> ClientPool<M> {
107    /// Construct an empty pool. This performs no network IO.
108    pub fn new(
109        manager: M,
110        options: PoolOptions,
111    ) -> Result<Self, OptionsError> {
112        options.validate()?;
113        Ok(Self {
114            inner: Arc::new(Inner {
115                manager,
116                options,
117                state: Mutex::new(State {
118                    idle: VecDeque::new(),
119                    live: 0,
120                    creating: 0,
121                    waiting: 0,
122                    in_flight: 0,
123                    status: PoolState::Open,
124                    next_active_id: 0,
125                    drain_started: None,
126                    drain_cancellations: HashMap::new(),
127                }),
128                changed: Notify::new(),
129                admission_closed: CancellationToken::new(),
130                active_cancellation: CancellationToken::new(),
131            }),
132        })
133    }
134
135    /// Acquire one client, creating it lazily or waiting for returned capacity.
136    pub async fn acquire(
137        &self,
138        active_policy: ActiveRequestPolicy,
139    ) -> Result<ClientLease<M>, AcquireError<M::Error>> {
140        self.acquire_inner(active_policy, None).await
141    }
142
143    /// Acquire one client while also observing caller-owned cancellation.
144    pub async fn acquire_cancellable(
145        &self,
146        active_policy: ActiveRequestPolicy,
147        cancellation: &CancellationToken,
148    ) -> Result<ClientLease<M>, AcquireError<M::Error>> {
149        self.acquire_inner(active_policy, Some(cancellation)).await
150    }
151
152    async fn acquire_inner(
153        &self,
154        active_policy: ActiveRequestPolicy,
155        cancellation: Option<&CancellationToken>,
156    ) -> Result<ClientLease<M>, AcquireError<M::Error>> {
157        let deadline = self
158            .inner
159            .options
160            .acquire_timeout
161            .map(|duration| TokioInstant::now() + duration);
162        let mut waiter = Waiter::new(self);
163
164        loop {
165            if cancellation.is_some_and(CancellationToken::is_cancelled) {
166                return Err(AcquireError::Cancelled);
167            }
168            let notified = self.inner.changed.notified();
169            let action = {
170                let mut state = self.state();
171                self.prune_expired(&mut state);
172                match &state.status {
173                    PoolState::Draining | PoolState::Closed => {
174                        return Err(AcquireError::Closed);
175                    }
176                    PoolState::Paused { .. } => {
177                        waiter.enter(&mut state)?;
178                        Action::Wait
179                    }
180                    PoolState::Open => {
181                        if state.in_flight >= self.inner.options.max_concurrency
182                        {
183                            waiter.enter(&mut state)?;
184                            Action::Wait
185                        } else if let Some(entry) = state.idle.pop_front() {
186                            state.in_flight += 1;
187                            waiter.leave(&mut state);
188                            Action::Reuse(entry)
189                        } else if state.live < self.inner.options.max_live {
190                            state.live += 1;
191                            state.creating += 1;
192                            state.in_flight += 1;
193                            waiter.leave(&mut state);
194                            Action::Create
195                        } else {
196                            waiter.enter(&mut state)?;
197                            Action::Wait
198                        }
199                    }
200                }
201            };
202
203            match action {
204                Action::Reuse(mut entry) => {
205                    match self
206                        .operation(
207                            self.inner.manager.recycle(&mut entry.client),
208                            cancellation,
209                        )
210                        .await
211                    {
212                        Ok(Ok(())) => {}
213                        Ok(Err(error)) => {
214                            self.retire_one();
215                            return Err(AcquireError::Manager(error));
216                        }
217                        Err(interrupted) => {
218                            self.retire_one();
219                            return Err(interrupted.acquire_error());
220                        }
221                    }
222                    return Ok(ClientLease::new(
223                        self.clone(),
224                        entry,
225                        active_policy,
226                    ));
227                }
228                Action::Create => {
229                    match self
230                        .operation(self.inner.manager.create(), cancellation)
231                        .await
232                    {
233                        Ok(Ok(client)) => {
234                            self.finish_create(true, true);
235                            return Ok(ClientLease::new(
236                                self.clone(),
237                                Entry {
238                                    client,
239                                    created_at: Instant::now(),
240                                    idle_since: Instant::now(),
241                                },
242                                active_policy,
243                            ));
244                        }
245                        Ok(Err(error)) => {
246                            self.finish_create(false, true);
247                            return Err(AcquireError::Manager(error));
248                        }
249                        Err(interrupted) => {
250                            self.finish_create(false, true);
251                            return Err(interrupted.acquire_error());
252                        }
253                    }
254                }
255                Action::Wait => {
256                    self.wait_for_change(notified, deadline, cancellation)
257                        .await?
258                }
259            }
260        }
261    }
262
263    #[cfg(feature = "fleet")]
264    pub(crate) async fn create(
265        &self,
266        active_policy: ActiveRequestPolicy,
267    ) -> Result<ClientLease<M>, AcquireError<M::Error>> {
268        self.create_inner(active_policy, None).await
269    }
270
271    #[cfg(feature = "fleet")]
272    pub(crate) async fn create_cancellable(
273        &self,
274        active_policy: ActiveRequestPolicy,
275        cancellation: &CancellationToken,
276    ) -> Result<ClientLease<M>, AcquireError<M::Error>> {
277        self.create_inner(active_policy, Some(cancellation)).await
278    }
279
280    #[cfg(feature = "fleet")]
281    async fn create_inner(
282        &self,
283        active_policy: ActiveRequestPolicy,
284        cancellation: Option<&CancellationToken>,
285    ) -> Result<ClientLease<M>, AcquireError<M::Error>> {
286        {
287            let mut state = self.state();
288            self.prune_expired(&mut state);
289            if !matches!(&state.status, PoolState::Open) {
290                return Err(AcquireError::Closed);
291            }
292            if state.live >= self.inner.options.max_live {
293                return Err(AcquireError::AtCapacity);
294            }
295            if state.in_flight >= self.inner.options.max_concurrency {
296                return Err(AcquireError::AtCapacity);
297            }
298            state.live += 1;
299            state.creating += 1;
300            state.in_flight += 1;
301        }
302        match self.operation(self.inner.manager.create(), cancellation).await {
303            Ok(Ok(client)) => {
304                self.finish_create(true, true);
305                Ok(ClientLease::new(
306                    self.clone(),
307                    Entry {
308                        client,
309                        created_at: Instant::now(),
310                        idle_since: Instant::now(),
311                    },
312                    active_policy,
313                ))
314            }
315            Ok(Err(error)) => {
316                self.finish_create(false, true);
317                Err(AcquireError::Manager(error))
318            }
319            Err(interrupted) => {
320                self.finish_create(false, true);
321                Err(interrupted.acquire_error())
322            }
323        }
324    }
325
326    #[cfg(feature = "fleet")]
327    pub(crate) async fn acquire_idle_where(
328        &self,
329        predicate: impl Fn(&M::Client) -> bool,
330        active_policy: ActiveRequestPolicy,
331    ) -> Result<Option<ClientLease<M>>, AcquireError<M::Error>> {
332        self.acquire_idle_where_inner(predicate, active_policy, None).await
333    }
334
335    #[cfg(feature = "fleet")]
336    pub(crate) async fn acquire_idle_where_cancellable(
337        &self,
338        predicate: impl Fn(&M::Client) -> bool,
339        active_policy: ActiveRequestPolicy,
340        cancellation: &CancellationToken,
341    ) -> Result<Option<ClientLease<M>>, AcquireError<M::Error>> {
342        self.acquire_idle_where_inner(
343            predicate,
344            active_policy,
345            Some(cancellation),
346        )
347        .await
348    }
349
350    #[cfg(feature = "fleet")]
351    async fn acquire_idle_where_inner(
352        &self,
353        predicate: impl Fn(&M::Client) -> bool,
354        active_policy: ActiveRequestPolicy,
355        cancellation: Option<&CancellationToken>,
356    ) -> Result<Option<ClientLease<M>>, AcquireError<M::Error>> {
357        let mut entry = {
358            let mut state = self.state();
359            self.prune_expired(&mut state);
360            if !matches!(&state.status, PoolState::Open) {
361                return Err(AcquireError::Closed);
362            }
363            if state.in_flight >= self.inner.options.max_concurrency {
364                return Err(AcquireError::AtCapacity);
365            }
366            let Some(index) =
367                state.idle.iter().position(|entry| predicate(&entry.client))
368            else {
369                return Ok(None);
370            };
371            state.in_flight += 1;
372            state.idle.remove(index).expect("idle index came from this queue")
373        };
374        match self
375            .operation(
376                self.inner.manager.recycle(&mut entry.client),
377                cancellation,
378            )
379            .await
380        {
381            Ok(Ok(())) => {}
382            Ok(Err(error)) => {
383                self.retire_one();
384                return Err(AcquireError::Manager(error));
385            }
386            Err(interrupted) => {
387                self.retire_one();
388                return Err(interrupted.acquire_error());
389            }
390        }
391        Ok(Some(ClientLease::new(self.clone(), entry, active_policy)))
392    }
393
394    #[cfg(feature = "fleet")]
395    pub(crate) fn discard_idle_where(
396        &self,
397        predicate: impl Fn(&M::Client) -> bool,
398    ) -> bool {
399        let mut state = self.state();
400        let Some(index) =
401            state.idle.iter().position(|entry| predicate(&entry.client))
402        else {
403            return false;
404        };
405        let retired =
406            state.idle.remove(index).expect("idle index came from this queue");
407        state.live = state.live.saturating_sub(1);
408        drop(state);
409        drop(retired);
410        self.inner.changed.notify_waiters();
411        true
412    }
413
414    /// Maintain the declared warm floor until cancellation, then close and drain.
415    pub async fn run(
416        &self,
417        cancellation: CancellationToken,
418    ) -> Result<(), M::Error> {
419        loop {
420            if cancellation.is_cancelled() {
421                self.drain().await;
422                return Ok(());
423            }
424
425            self.prune_now();
426            if matches!(
427                self.stats().state,
428                PoolState::Draining | PoolState::Closed
429            ) {
430                self.drain().await;
431                return Ok(());
432            }
433            if self.reserve_warm_create() {
434                match self
435                    .operation(self.inner.manager.create(), Some(&cancellation))
436                    .await
437                {
438                    Ok(Ok(client)) => self.finish_warm_create(client),
439                    Ok(Err(error)) => {
440                        self.finish_create(false, false);
441                        return Err(error);
442                    }
443                    Err(_) => {
444                        self.finish_create(false, false);
445                        self.drain().await;
446                        return Ok(());
447                    }
448                }
449                continue;
450            }
451
452            let notified = self.inner.changed.notified();
453            let deadline = self.next_retirement_deadline();
454            match deadline {
455                Some(deadline) => {
456                    tokio::select! {
457                        _ = cancellation.cancelled() => {},
458                        _ = notified => {},
459                        _ = tokio::time::sleep_until(TokioInstant::from_std(deadline)) => {}
460                    }
461                }
462                None => {
463                    tokio::select! {
464                        _ = cancellation.cancelled() => {},
465                        _ = notified => {}
466                    }
467                }
468            }
469        }
470    }
471
472    /// Reject new acquisitions and drop retained idle clients.
473    pub fn close(&self) {
474        let mut state = self.state();
475        state.status = PoolState::Closed;
476        let idle = state.idle.len();
477        let retired: Vec<_> = state.idle.drain(..).collect();
478        state.live = state.live.saturating_sub(idle);
479        drop(state);
480        drop(retired);
481        self.inner.admission_closed.cancel();
482        self.inner.changed.notify_waiters();
483    }
484
485    /// Stop admission and wait indefinitely for every leased or creating client to end.
486    pub async fn drain(&self) {
487        let immediate = {
488            let mut state = self.state();
489            if !matches!(&state.status, PoolState::Closed) {
490                state.status = PoolState::Draining;
491            }
492            let drain_started =
493                *state.drain_started.get_or_insert_with(Instant::now);
494            let idle = state.idle.len();
495            let retired: Vec<_> = state.idle.drain(..).collect();
496            state.live = state.live.saturating_sub(idle);
497            let immediate = take_due_cancellations(&mut state, drain_started);
498            drop(state);
499            drop(retired);
500            immediate
501        };
502        for cancellation in immediate {
503            cancellation.cancel();
504        }
505        self.inner.admission_closed.cancel();
506        self.inner.changed.notify_waiters();
507        loop {
508            let notified = self.inner.changed.notified();
509            let (due, deadline) = {
510                let mut state = self.state();
511                if state.live == 0 {
512                    state.status = PoolState::Closed;
513                    drop(state);
514                    self.inner.changed.notify_waiters();
515                    return;
516                }
517                let now = Instant::now();
518                let due = take_due_cancellations(&mut state, now);
519                let deadline = next_active_cancellation_deadline(&state);
520                (due, deadline)
521            };
522            for cancellation in due {
523                cancellation.cancel();
524            }
525            match deadline {
526                Some(deadline) => {
527                    tokio::select! {
528                        _ = notified => {},
529                        _ = tokio::time::sleep_until(TokioInstant::from_std(deadline)) => {}
530                    }
531                }
532                None => notified.await,
533            }
534        }
535    }
536
537    /// Pause new acquisitions without retiring retained or leased clients.
538    pub fn pause(
539        &self,
540        reason: impl Into<Arc<str>>,
541    ) {
542        let mut state = self.state();
543        if matches!(&state.status, PoolState::Open | PoolState::Paused { .. }) {
544            state.status = PoolState::Paused { reason: reason.into() };
545        }
546        drop(state);
547        self.inner.changed.notify_waiters();
548    }
549
550    /// Resume a policy-paused pool. Closing and draining are irreversible.
551    pub fn resume(&self) -> bool {
552        let mut state = self.state();
553        if !matches!(&state.status, PoolState::Paused { .. }) {
554            return false;
555        }
556        state.status = PoolState::Open;
557        drop(state);
558        self.inner.changed.notify_waiters();
559        true
560    }
561
562    /// Apply a protocol-neutral admission consequence.
563    pub fn apply(
564        &self,
565        directive: PoolDirective,
566    ) {
567        match directive {
568            PoolDirective::Continue => {}
569            PoolDirective::Pause { reason } => self.pause(reason),
570        }
571    }
572
573    /// Explicitly cancel work holding a lease after admission has stopped.
574    ///
575    /// Graceful [`Self::drain`] never calls this. The runtime may call it only
576    /// when its own policy has decided that active requests should no longer
577    /// be preserved. Calling it while admission is open has no effect.
578    pub fn cancel_active(&self) -> bool {
579        if !matches!(
580            &self.state().status,
581            PoolState::Draining | PoolState::Closed
582        ) {
583            return false;
584        }
585        self.inner.active_cancellation.cancel();
586        true
587    }
588
589    fn register_active(
590        &self,
591        policy: ActiveRequestPolicy,
592    ) -> (CancellationToken, Option<u64>) {
593        let token = self.inner.active_cancellation.child_token();
594        if policy == ActiveRequestPolicy::Preserve {
595            return (token, None);
596        }
597
598        let mut state = self.state();
599        let id = state.next_active_id;
600        state.next_active_id = state.next_active_id.wrapping_add(1);
601        let cancel_now = match (policy, state.drain_started) {
602            (ActiveRequestPolicy::CancelOnDrain(None), Some(_)) => true,
603            (
604                ActiveRequestPolicy::CancelOnDrain(Some(duration)),
605                Some(started),
606            ) => started
607                .checked_add(duration)
608                .is_some_and(|deadline| deadline <= Instant::now()),
609            _ => false,
610        };
611        if cancel_now {
612            token.cancel();
613            (token, None)
614        } else {
615            state.drain_cancellations.insert(
616                id,
617                ActiveCancellation { policy, token: token.clone() },
618            );
619            drop(state);
620            self.inner.changed.notify_waiters();
621            (token, Some(id))
622        }
623    }
624
625    fn unregister_active(
626        &self,
627        registration: Option<u64>,
628    ) {
629        let Some(id) = registration else {
630            return;
631        };
632        self.state().drain_cancellations.remove(&id);
633        self.inner.changed.notify_waiters();
634    }
635
636    #[cfg(feature = "fleet")]
637    pub(crate) async fn wait_until_open(
638        &self,
639        deadline: Option<TokioInstant>,
640        cancellation: Option<&CancellationToken>,
641    ) -> Result<(), AcquireError<M::Error>> {
642        let mut waiter = Waiter::new(self);
643        loop {
644            if cancellation.is_some_and(CancellationToken::is_cancelled) {
645                return Err(AcquireError::Cancelled);
646            }
647            let notified = self.inner.changed.notified();
648            {
649                let mut state = self.state();
650                match &state.status {
651                    PoolState::Open => {
652                        waiter.leave(&mut state);
653                        return Ok(());
654                    }
655                    PoolState::Paused { .. } => waiter.enter(&mut state)?,
656                    PoolState::Draining | PoolState::Closed => {
657                        return Err(AcquireError::Closed);
658                    }
659                }
660            }
661            self.wait_for_change(notified, deadline, cancellation).await?;
662        }
663    }
664
665    #[cfg(feature = "fleet")]
666    pub(crate) async fn wait_until_open_for_maintenance(
667        &self,
668        cancellation: &CancellationToken,
669    ) -> bool {
670        loop {
671            let notified = self.inner.changed.notified();
672            match &self.state().status {
673                PoolState::Open => return true,
674                PoolState::Draining | PoolState::Closed => return false,
675                PoolState::Paused { .. } => {}
676            }
677            tokio::select! {
678                _ = notified => {},
679                _ = cancellation.cancelled() => return false
680            }
681        }
682    }
683
684    pub fn stats(&self) -> PoolStats {
685        let state = self.state();
686        PoolStats {
687            live: state.live,
688            idle: state.idle.len(),
689            leased: state
690                .live
691                .saturating_sub(state.idle.len() + state.creating),
692            creating: state.creating,
693            waiting: state.waiting,
694            in_flight: state.in_flight,
695            state: state.status.clone(),
696        }
697    }
698
699    fn state(&self) -> MutexGuard<'_, State<M::Client>> {
700        self.inner.state.lock().unwrap_or_else(|poison| poison.into_inner())
701    }
702
703    fn finish_create(
704        &self,
705        succeeded: bool,
706        admitted: bool,
707    ) {
708        let mut state = self.state();
709        state.creating = state.creating.saturating_sub(1);
710        if !succeeded {
711            state.live = state.live.saturating_sub(1);
712            if admitted {
713                state.in_flight = state.in_flight.saturating_sub(1);
714            }
715        }
716        drop(state);
717        self.inner.changed.notify_waiters();
718    }
719
720    fn finish_warm_create(
721        &self,
722        client: M::Client,
723    ) {
724        let now = Instant::now();
725        let mut state = self.state();
726        state.creating = state.creating.saturating_sub(1);
727        if matches!(&state.status, PoolState::Draining | PoolState::Closed)
728            || state.idle.len() >= self.inner.options.max_idle
729        {
730            state.live = state.live.saturating_sub(1);
731        } else {
732            state.idle.push_back(Entry {
733                client,
734                created_at: now,
735                idle_since: now,
736            });
737        }
738        drop(state);
739        self.inner.changed.notify_waiters();
740    }
741
742    fn reserve_warm_create(&self) -> bool {
743        let mut state = self.state();
744        if !matches!(&state.status, PoolState::Open)
745            || state.idle.len() + state.creating >= self.inner.options.min_idle
746            || state.live >= self.inner.options.max_live
747        {
748            return false;
749        }
750        state.live += 1;
751        state.creating += 1;
752        true
753    }
754
755    fn retire_one(&self) {
756        let mut state = self.state();
757        state.live = state.live.saturating_sub(1);
758        state.in_flight = state.in_flight.saturating_sub(1);
759        drop(state);
760        self.inner.changed.notify_waiters();
761    }
762
763    fn return_entry(
764        &self,
765        mut entry: Entry<M::Client>,
766    ) {
767        let now = Instant::now();
768        let mut state = self.state();
769        state.in_flight = state.in_flight.saturating_sub(1);
770        let reusable =
771            matches!(&state.status, PoolState::Open | PoolState::Paused { .. })
772                && state.idle.len() < self.inner.options.max_idle
773                && !expired(&entry, now, &self.inner.options);
774        if reusable {
775            entry.idle_since = now;
776            state.idle.push_back(entry);
777        } else {
778            state.live = state.live.saturating_sub(1);
779        }
780        drop(state);
781        self.inner.changed.notify_waiters();
782    }
783
784    fn prune_now(&self) {
785        let mut state = self.state();
786        self.prune_expired(&mut state);
787    }
788
789    fn prune_expired(
790        &self,
791        state: &mut State<M::Client>,
792    ) {
793        let now = Instant::now();
794        let before = state.idle.len();
795        state.idle.retain(|entry| !expired(entry, now, &self.inner.options));
796        state.live = state.live.saturating_sub(before - state.idle.len());
797    }
798
799    fn next_retirement_deadline(&self) -> Option<Instant> {
800        let state = self.state();
801        state
802            .idle
803            .iter()
804            .filter_map(|entry| retirement_deadline(entry, &self.inner.options))
805            .min()
806    }
807
808    async fn wait_for_change<F>(
809        &self,
810        notified: F,
811        deadline: Option<TokioInstant>,
812        cancellation: Option<&CancellationToken>,
813    ) -> Result<(), AcquireError<M::Error>>
814    where
815        F: Future<Output = ()>,
816    {
817        match (deadline, cancellation) {
818            (Some(deadline), Some(cancellation)) => {
819                tokio::select! {
820                    _ = notified => Ok(()),
821                    _ = cancellation.cancelled() => Err(AcquireError::Cancelled),
822                    _ = tokio::time::sleep_until(deadline) => Err(AcquireError::Timeout(
823                        self.inner.options.acquire_timeout.expect("deadline has duration")
824                    ))
825                }
826            }
827            (Some(deadline), None) => {
828                tokio::select! {
829                    _ = notified => Ok(()),
830                    _ = tokio::time::sleep_until(deadline) => Err(AcquireError::Timeout(
831                        self.inner.options.acquire_timeout.expect("deadline has duration")
832                    ))
833                }
834            }
835            (None, Some(cancellation)) => {
836                tokio::select! {
837                    _ = notified => Ok(()),
838                    _ = cancellation.cancelled() => Err(AcquireError::Cancelled)
839                }
840            }
841            (None, None) => {
842                notified.await;
843                Ok(())
844            }
845        }
846    }
847
848    async fn operation<F, T>(
849        &self,
850        operation: F,
851        cancellation: Option<&CancellationToken>,
852    ) -> Result<T, OperationInterrupted>
853    where
854        F: Future<Output = T>,
855    {
856        match cancellation {
857            Some(cancellation) => tokio::select! {
858                result = operation => Ok(result),
859                _ = cancellation.cancelled() => Err(OperationInterrupted::Caller),
860                _ = self.inner.admission_closed.cancelled() => {
861                    Err(OperationInterrupted::AdmissionClosed)
862                }
863            },
864            None => tokio::select! {
865                result = operation => Ok(result),
866                _ = self.inner.admission_closed.cancelled() => {
867                    Err(OperationInterrupted::AdmissionClosed)
868                }
869            },
870        }
871    }
872}
873
874enum OperationInterrupted {
875    Caller,
876    AdmissionClosed,
877}
878
879impl OperationInterrupted {
880    fn acquire_error<E>(self) -> AcquireError<E> {
881        match self {
882            Self::Caller => AcquireError::Cancelled,
883            Self::AdmissionClosed => AcquireError::Closed,
884        }
885    }
886}
887
888enum Action<C> {
889    Reuse(Entry<C>),
890    Create,
891    Wait,
892}
893
894struct Waiter<'a, M: ClientManager> {
895    pool: &'a ClientPool<M>,
896    entered: bool,
897}
898
899impl<'a, M: ClientManager> Waiter<'a, M> {
900    fn new(pool: &'a ClientPool<M>) -> Self {
901        Self { pool, entered: false }
902    }
903
904    fn enter(
905        &mut self,
906        state: &mut State<M::Client>,
907    ) -> Result<(), AcquireError<M::Error>> {
908        if self.entered {
909            return Ok(());
910        }
911        if state.waiting >= self.pool.inner.options.max_waiting {
912            return Err(AcquireError::QueueFull {
913                max_waiting: self.pool.inner.options.max_waiting,
914            });
915        }
916        state.waiting += 1;
917        self.entered = true;
918        Ok(())
919    }
920
921    fn leave(
922        &mut self,
923        state: &mut State<M::Client>,
924    ) {
925        if self.entered {
926            state.waiting = state.waiting.saturating_sub(1);
927            self.entered = false;
928        }
929    }
930}
931
932impl<M: ClientManager> Drop for Waiter<'_, M> {
933    fn drop(&mut self) {
934        if !self.entered {
935            return;
936        }
937        let mut state = self.pool.state();
938        state.waiting = state.waiting.saturating_sub(1);
939        drop(state);
940        self.pool.inner.changed.notify_waiters();
941    }
942}
943
944/// Exclusive checkout of one local client.
945pub struct ClientLease<M: ClientManager> {
946    pool: ClientPool<M>,
947    entry: Option<Entry<M::Client>>,
948    active_cancellation: CancellationToken,
949    active_registration: Option<u64>,
950}
951
952impl<M: ClientManager> ClientLease<M> {
953    fn new(
954        pool: ClientPool<M>,
955        entry: Entry<M::Client>,
956        active_policy: ActiveRequestPolicy,
957    ) -> Self {
958        let (active_cancellation, active_registration) =
959            pool.register_active(active_policy);
960        Self {
961            pool,
962            entry: Some(entry),
963            active_cancellation,
964            active_registration,
965        }
966    }
967
968    /// Retire this client instead of returning it to the idle pool.
969    pub fn discard(mut self) {
970        self.entry.take();
971        self.pool.unregister_active(self.active_registration.take());
972        self.pool.retire_one();
973    }
974
975    /// Apply the application policy for one protocol-specific outcome.
976    pub fn observe<O, P: ClientPolicy<O>>(
977        &self,
978        policy: &P,
979        outcome: &O,
980    ) {
981        self.pool.apply(policy.classify(outcome));
982    }
983
984    /// Optional cancellation for active work holding this lease.
985    ///
986    /// Its request policy controls whether drain signals it immediately, after
987    /// an explicit grace period, or only through [`ClientPool::cancel_active`].
988    pub fn cancellation_token(&self) -> CancellationToken {
989        self.active_cancellation.clone()
990    }
991}
992
993impl<M: ClientManager> Deref for ClientLease<M> {
994    type Target = M::Client;
995
996    fn deref(&self) -> &Self::Target {
997        &self.entry.as_ref().expect("client lease is live").client
998    }
999}
1000
1001impl<M: ClientManager> DerefMut for ClientLease<M> {
1002    fn deref_mut(&mut self) -> &mut Self::Target {
1003        &mut self.entry.as_mut().expect("client lease is live").client
1004    }
1005}
1006
1007impl<M: ClientManager> Drop for ClientLease<M> {
1008    fn drop(&mut self) {
1009        self.pool.unregister_active(self.active_registration.take());
1010        if let Some(entry) = self.entry.take() {
1011            self.pool.return_entry(entry);
1012        }
1013    }
1014}
1015
1016fn take_due_cancellations<C>(
1017    state: &mut State<C>,
1018    now: Instant,
1019) -> Vec<CancellationToken> {
1020    let Some(started) = state.drain_started else {
1021        return Vec::new();
1022    };
1023    let due: Vec<_> = state
1024        .drain_cancellations
1025        .iter()
1026        .filter_map(|(id, active)| {
1027            let due = match active.policy {
1028                ActiveRequestPolicy::Preserve => false,
1029                ActiveRequestPolicy::CancelOnDrain(None) => true,
1030                ActiveRequestPolicy::CancelOnDrain(Some(grace)) => started
1031                    .checked_add(grace)
1032                    .is_some_and(|deadline| deadline <= now),
1033            };
1034            due.then_some(*id)
1035        })
1036        .collect();
1037    due.into_iter()
1038        .filter_map(|id| {
1039            state.drain_cancellations.remove(&id).map(|active| active.token)
1040        })
1041        .collect()
1042}
1043
1044fn next_active_cancellation_deadline<C>(state: &State<C>) -> Option<Instant> {
1045    let started = state.drain_started?;
1046    state
1047        .drain_cancellations
1048        .values()
1049        .filter_map(|active| match active.policy {
1050            ActiveRequestPolicy::CancelOnDrain(Some(grace)) => {
1051                started.checked_add(grace)
1052            }
1053            ActiveRequestPolicy::Preserve
1054            | ActiveRequestPolicy::CancelOnDrain(None) => None,
1055        })
1056        .min()
1057}
1058
1059fn expired<C>(
1060    entry: &Entry<C>,
1061    now: Instant,
1062    options: &PoolOptions,
1063) -> bool {
1064    retirement_deadline(entry, options).is_some_and(|deadline| deadline <= now)
1065}
1066
1067fn retirement_deadline<C>(
1068    entry: &Entry<C>,
1069    options: &PoolOptions,
1070) -> Option<Instant> {
1071    let idle = options
1072        .idle_timeout
1073        .and_then(|duration| entry.idle_since.checked_add(duration));
1074    let lifetime = options
1075        .max_lifetime
1076        .and_then(|duration| entry.created_at.checked_add(duration));
1077    match (idle, lifetime) {
1078        (Some(idle), Some(lifetime)) => Some(idle.min(lifetime)),
1079        (Some(deadline), None) | (None, Some(deadline)) => Some(deadline),
1080        (None, None) => None,
1081    }
1082}
1083
1084#[cfg(test)]
1085#[path = "../tests/pool.rs"]
1086mod tests;