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