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