Skip to main content

sfo_pool/
classified_worker_pool.rs

1use crate::{
2    pool_cleared_error, pool_clearing_error, pool_invalid_config_error, PoolError, PoolResult,
3};
4use notify_future::Notify;
5use std::collections::{HashMap, VecDeque};
6use std::hash::Hash;
7use std::ops::{Deref, DerefMut};
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant};
10use tokio::sync::Semaphore;
11
12const DEFAULT_MAX_CONCURRENT_CREATION_COUNT: u16 = 20;
13
14pub trait WorkerClassification: Send + 'static + Clone + Hash + Eq + PartialEq {}
15
16impl<T: Send + 'static + Clone + Hash + Eq + PartialEq> WorkerClassification for T {}
17
18#[derive(Debug, Clone)]
19/// Configuration for [`ClassifiedWorkerPool`].
20///
21/// Optional limits default to `None`, and the concurrent creation limit defaults
22/// to 20. Use the `with_*` methods to configure the pool.
23pub struct ClassifiedWorkerPoolConfig {
24    idle_timeout: Option<Duration>,
25    /// Maximum number of concurrent calls to [`ClassifiedWorkerFactory::create`].
26    ///
27    /// The default is 20. Zero is invalid.
28    max_concurrent_creation_count: u16,
29    /// Maximum number of workers whose primary classification is the same.
30    ///
31    /// `None` leaves classification counts unlimited.
32    max_count_per_classification: Option<u16>,
33    /// Maximum number of idle workers retained for each primary classification.
34    ///
35    /// This limit is independent for every classification. `None` adds no
36    /// per-classification idle limit; `Some(0)` disables idle caching.
37    max_idle_count_per_classification: Option<u16>,
38}
39
40impl Default for ClassifiedWorkerPoolConfig {
41    fn default() -> Self {
42        Self {
43            idle_timeout: None,
44            max_concurrent_creation_count: DEFAULT_MAX_CONCURRENT_CREATION_COUNT,
45            max_count_per_classification: None,
46            max_idle_count_per_classification: None,
47        }
48    }
49}
50
51impl ClassifiedWorkerPoolConfig {
52    /// Sets the maximum number of concurrent worker creation calls.
53    ///
54    /// Zero is invalid.
55    pub fn with_max_concurrent_creation_count(mut self, max_count: u16) -> Self {
56        self.max_concurrent_creation_count = max_count;
57        self
58    }
59
60    /// Sets the maximum duration for which an idle worker is retained.
61    ///
62    /// `None` disables timeout-based cleanup.
63    pub fn with_idle_timeout(mut self, idle_timeout: Option<Duration>) -> Self {
64        self.idle_timeout = idle_timeout;
65        self
66    }
67
68    /// Sets the worker-count limit for each primary classification.
69    ///
70    /// `None` leaves per-classification counts unlimited. `Some(0)` is invalid.
71    pub fn with_max_count_per_classification(mut self, max_count: Option<u16>) -> Self {
72        self.max_count_per_classification = max_count;
73        self
74    }
75
76    /// Sets the idle-worker cache limit for each primary classification.
77    ///
78    /// `None` adds no per-classification idle limit. `Some(0)` disables idle caching.
79    pub fn with_max_idle_count_per_classification(mut self, max_idle_count: Option<u16>) -> Self {
80        self.max_idle_count_per_classification = max_idle_count;
81        self
82    }
83}
84
85#[async_trait::async_trait]
86/// A classification-aware worker managed by [`ClassifiedWorkerPool`].
87///
88/// Methods on this trait may be called while the pool's internal state lock is held.
89/// Implementations must be non-blocking and must not re-enter APIs on the same pool.
90pub trait ClassifiedWorker<C: WorkerClassification>: Send + 'static {
91    fn is_work(&self) -> bool;
92    /// Returns whether this worker can currently serve its requested classification.
93    /// Explicit classified acquisition only reuses workers from that classification's bucket.
94    /// A worker that is no longer valid for its cached primary classification is discarded.
95    fn is_valid(&self, c: C) -> bool;
96    /// Returns the worker's primary classification used for accounting and replacement.
97    /// The pool validates and caches this value when the worker is created.
98    /// If it differs from the cached value when the worker is returned, the worker is discarded.
99    fn classification(&self) -> C;
100}
101
102pub struct ClassifiedWorkerGuard<
103    C: WorkerClassification,
104    W: ClassifiedWorker<C>,
105    F: ClassifiedWorkerFactory<C, W>,
106> {
107    pool_ref: ClassifiedWorkerPoolRef<C, W, F>,
108    worker: Option<W>,
109    primary_classification: C,
110}
111
112impl<C: WorkerClassification, W: ClassifiedWorker<C>, F: ClassifiedWorkerFactory<C, W>>
113    ClassifiedWorkerGuard<C, W, F>
114{
115    fn new(
116        worker: W,
117        pool_ref: ClassifiedWorkerPoolRef<C, W, F>,
118        primary_classification: C,
119    ) -> Self {
120        ClassifiedWorkerGuard {
121            pool_ref,
122            worker: Some(worker),
123            primary_classification,
124        }
125    }
126}
127
128impl<C: WorkerClassification, W: ClassifiedWorker<C>, F: ClassifiedWorkerFactory<C, W>> DerefMut
129    for ClassifiedWorkerGuard<C, W, F>
130{
131    fn deref_mut(&mut self) -> &mut Self::Target {
132        self.worker.as_mut().unwrap()
133    }
134}
135
136impl<C: WorkerClassification, W: ClassifiedWorker<C>, F: ClassifiedWorkerFactory<C, W>> Deref
137    for ClassifiedWorkerGuard<C, W, F>
138{
139    type Target = W;
140
141    fn deref(&self) -> &Self::Target {
142        self.worker.as_ref().unwrap()
143    }
144}
145
146impl<C: WorkerClassification, W: ClassifiedWorker<C>, F: ClassifiedWorkerFactory<C, W>> Drop
147    for ClassifiedWorkerGuard<C, W, F>
148{
149    fn drop(&mut self) {
150        if let Some(worker) = self.worker.take() {
151            self.pool_ref
152                .release(worker, self.primary_classification.clone());
153        }
154    }
155}
156
157struct ClassifiedWorkerReservation<
158    C: WorkerClassification,
159    W: ClassifiedWorker<C>,
160    F: ClassifiedWorkerFactory<C, W>,
161> {
162    pool_ref: ClassifiedWorkerPoolRef<C, W, F>,
163    requested_classification: Option<C>,
164    active: bool,
165}
166
167enum ReservationCompletion {
168    Complete,
169    Clearing,
170    ClassificationLimitReached,
171}
172
173impl<C: WorkerClassification, W: ClassifiedWorker<C>, F: ClassifiedWorkerFactory<C, W>>
174    ClassifiedWorkerReservation<C, W, F>
175{
176    fn new(pool_ref: ClassifiedWorkerPoolRef<C, W, F>, classification: Option<C>) -> Self {
177        Self {
178            pool_ref,
179            requested_classification: classification,
180            active: true,
181        }
182    }
183
184    fn complete(mut self, worker_classification: C) -> ReservationCompletion {
185        let (completion, retry_waiters, clear_waiters) = {
186            let mut state = self.pool_ref.state.lock().unwrap();
187            if let Some(classification) = self.requested_classification.as_ref() {
188                state.dec_pending_classified_count(classification.clone());
189            }
190            if state.clearing {
191                state.current_count -= 1;
192                (
193                    ReservationCompletion::Clearing,
194                    Vec::new(),
195                    state.take_clear_waiters_if_done(),
196                )
197            } else if self
198                .pool_ref
199                .classification_limit_reached(&state, &worker_classification)
200            {
201                state.current_count -= 1;
202                (
203                    ReservationCompletion::ClassificationLimitReached,
204                    state.drain_waiters(),
205                    Vec::new(),
206                )
207            } else {
208                state.inc_classified_count(worker_classification);
209                (ReservationCompletion::Complete, Vec::new(), Vec::new())
210            }
211        };
212        self.active = false;
213        ClassifiedWorkerPool::<C, W, F>::notify_retry_waiters(retry_waiters);
214        for waiter in clear_waiters {
215            waiter.notify(());
216        }
217        completion
218    }
219}
220
221impl<C: WorkerClassification, W: ClassifiedWorker<C>, F: ClassifiedWorkerFactory<C, W>> Drop
222    for ClassifiedWorkerReservation<C, W, F>
223{
224    fn drop(&mut self) {
225        if self.active {
226            self.pool_ref
227                .rollback_reservation(self.requested_classification.as_ref());
228        }
229    }
230}
231
232#[async_trait::async_trait]
233pub trait ClassifiedWorkerFactory<C: WorkerClassification, W: ClassifiedWorker<C>>:
234    Send + Sync + 'static
235{
236    async fn create(&self, c: Option<C>) -> PoolResult<W>;
237}
238
239struct WaitingItem<
240    C: WorkerClassification,
241    W: ClassifiedWorker<C>,
242    F: ClassifiedWorkerFactory<C, W>,
243> {
244    future: Notify<ClassifiedWorkerWaitResult<C, W, F>>,
245    condition: Option<C>,
246}
247
248struct IdleWorker<C, W> {
249    worker: W,
250    primary_classification: C,
251    idle_since: Instant,
252}
253
254enum ClassifiedWorkerWaitResult<
255    C: WorkerClassification,
256    W: ClassifiedWorker<C>,
257    F: ClassifiedWorkerFactory<C, W>,
258> {
259    Worker(ClassifiedWorkerGuard<C, W, F>),
260    Retry,
261    Error(PoolError),
262}
263
264struct WorkerPoolState<
265    C: WorkerClassification,
266    W: ClassifiedWorker<C>,
267    F: ClassifiedWorkerFactory<C, W>,
268> {
269    current_count: usize,
270    classified_count_map: HashMap<C, usize>,
271    pending_classified_count_map: HashMap<C, usize>,
272    // Idle workers are grouped by primary classification; buckets are LRU to MRU.
273    worker_list: HashMap<C, VecDeque<IdleWorker<C, W>>>,
274    waiting_list: Vec<WaitingItem<C, W, F>>,
275    clearing: bool,
276    clear_waiting_list: Vec<Notify<()>>,
277}
278
279impl<C: WorkerClassification, W: ClassifiedWorker<C>, F: ClassifiedWorkerFactory<C, W>>
280    WorkerPoolState<C, W, F>
281{
282    fn push_idle_worker(&mut self, worker: W, primary_classification: C) {
283        let idle_worker = IdleWorker {
284            worker,
285            primary_classification: primary_classification.clone(),
286            idle_since: Instant::now(),
287        };
288        self.worker_list
289            .entry(primary_classification)
290            .or_default()
291            .push_back(idle_worker);
292    }
293
294    fn remove_idle_worker(&mut self, primary_classification: &C, index: usize) -> IdleWorker<C, W> {
295        let (idle_worker, remove_bucket) = {
296            let workers = self.worker_list.get_mut(primary_classification).unwrap();
297            let idle_worker = workers.remove(index).unwrap();
298            (idle_worker, workers.is_empty())
299        };
300        if remove_bucket {
301            self.worker_list.remove(primary_classification);
302        }
303        idle_worker
304    }
305
306    fn next_idle_worker_classification(&self) -> Option<C> {
307        self.worker_list.keys().next().cloned()
308    }
309
310    fn take_idle_worker_for_classification(
311        &mut self,
312        classification: &C,
313    ) -> Option<IdleWorker<C, W>> {
314        let index = self
315            .worker_list
316            .get(classification)
317            .map(VecDeque::len)
318            .and_then(|len| len.checked_sub(1))?;
319        Some(self.remove_idle_worker(classification, index))
320    }
321
322    fn drain_idle_workers(&mut self) -> Vec<IdleWorker<C, W>> {
323        std::mem::take(&mut self.worker_list)
324            .into_values()
325            .flatten()
326            .collect()
327    }
328
329    fn inc_classified_count(&mut self, c: C) {
330        let count = self.classified_count_map.entry(c).or_insert(0);
331        *count += 1;
332    }
333
334    fn dec_classified_count(&mut self, c: C) {
335        let mut should_remove = false;
336        if let Some(count) = self.classified_count_map.get_mut(&c) {
337            debug_assert!(*count > 0);
338            *count -= 1;
339            should_remove = *count == 0;
340        }
341        if should_remove {
342            self.classified_count_map.remove(&c);
343        }
344    }
345
346    fn inc_pending_classified_count(&mut self, c: C) {
347        let count = self.pending_classified_count_map.entry(c).or_insert(0);
348        *count += 1;
349    }
350
351    fn dec_pending_classified_count(&mut self, c: C) {
352        let mut should_remove = false;
353        if let Some(count) = self.pending_classified_count_map.get_mut(&c) {
354            debug_assert!(*count > 0);
355            *count -= 1;
356            should_remove = *count == 0;
357        }
358        if should_remove {
359            self.pending_classified_count_map.remove(&c);
360        }
361    }
362
363    fn reserved_classified_count(&self, c: &C) -> usize {
364        self.classified_count_map.get(c).copied().unwrap_or(0)
365            + self
366                .pending_classified_count_map
367                .get(c)
368                .copied()
369                .unwrap_or(0)
370    }
371
372    fn take_clear_waiters_if_done(&mut self) -> Vec<Notify<()>> {
373        if self.clearing && self.current_count == 0 {
374            self.clearing = false;
375            self.clear_waiting_list.drain(..).collect()
376        } else {
377            Vec::new()
378        }
379    }
380
381    fn find_matching_waiter_index_for_worker(
382        &self,
383        worker: &W,
384        primary_classification: &C,
385    ) -> Option<usize> {
386        self.waiting_list.iter().position(|waiting| {
387            if waiting.future.is_canceled() {
388                return false;
389            }
390            waiting
391                .condition
392                .as_ref()
393                .map(|condition| {
394                    condition == primary_classification && worker.is_valid(condition.clone())
395                })
396                .unwrap_or(true)
397        })
398    }
399
400    fn remove_canceled_waiters(&mut self) {
401        self.waiting_list
402            .retain(|waiting| !waiting.future.is_canceled());
403    }
404
405    fn drain_waiters(&mut self) -> Vec<Notify<ClassifiedWorkerWaitResult<C, W, F>>> {
406        self.waiting_list
407            .drain(..)
408            .map(|waiting| waiting.future)
409            .collect()
410    }
411}
412
413pub struct ClassifiedWorkerPool<
414    C: WorkerClassification,
415    W: ClassifiedWorker<C>,
416    F: ClassifiedWorkerFactory<C, W>,
417> {
418    factory: Arc<F>,
419    config: ClassifiedWorkerPoolConfig,
420    creation_semaphore: Semaphore,
421    state: Mutex<WorkerPoolState<C, W, F>>,
422}
423pub type ClassifiedWorkerPoolRef<C, W, F> = Arc<ClassifiedWorkerPool<C, W, F>>;
424
425#[cfg(test)]
426#[test]
427fn test_classified_worker_pool_config_default_idle_limits() {
428    let config = ClassifiedWorkerPoolConfig::default();
429    assert_eq!(
430        config.max_concurrent_creation_count,
431        DEFAULT_MAX_CONCURRENT_CREATION_COUNT
432    );
433    assert_eq!(config.max_idle_count_per_classification, None);
434}
435
436#[cfg(test)]
437#[test]
438fn test_classified_worker_pool_config_builder() {
439    let timeout = Duration::from_secs(1);
440    let config = ClassifiedWorkerPoolConfig::default()
441        .with_max_concurrent_creation_count(3)
442        .with_idle_timeout(Some(timeout))
443        .with_max_count_per_classification(Some(2))
444        .with_max_idle_count_per_classification(Some(1));
445    assert_eq!(config.idle_timeout, Some(timeout));
446    assert_eq!(config.max_concurrent_creation_count, 3);
447    assert_eq!(config.max_count_per_classification, Some(2));
448    assert_eq!(config.max_idle_count_per_classification, Some(1));
449}
450
451#[cfg(test)]
452#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
453async fn test_classified_worker_pool_limits_concurrent_creation() {
454    use std::sync::atomic::{AtomicUsize, Ordering};
455
456    #[derive(Clone, Debug, Eq, Hash, PartialEq)]
457    struct Classification;
458
459    struct TestWorker;
460
461    impl ClassifiedWorker<Classification> for TestWorker {
462        fn is_work(&self) -> bool {
463            true
464        }
465
466        fn is_valid(&self, _classification: Classification) -> bool {
467            true
468        }
469
470        fn classification(&self) -> Classification {
471            Classification
472        }
473    }
474
475    struct BlockingFactory {
476        active: Arc<AtomicUsize>,
477        max_active: Arc<AtomicUsize>,
478        started: Arc<AtomicUsize>,
479        gate: Arc<Semaphore>,
480    }
481
482    #[async_trait::async_trait]
483    impl ClassifiedWorkerFactory<Classification, TestWorker> for BlockingFactory {
484        async fn create(&self, _classification: Option<Classification>) -> PoolResult<TestWorker> {
485            let active = self.active.fetch_add(1, Ordering::SeqCst) + 1;
486            self.max_active.fetch_max(active, Ordering::SeqCst);
487            self.started.fetch_add(1, Ordering::SeqCst);
488            let permit = self.gate.acquire().await.unwrap();
489            permit.forget();
490            self.active.fetch_sub(1, Ordering::SeqCst);
491            Ok(TestWorker)
492        }
493    }
494
495    let active = Arc::new(AtomicUsize::new(0));
496    let max_active = Arc::new(AtomicUsize::new(0));
497    let started = Arc::new(AtomicUsize::new(0));
498    let gate = Arc::new(Semaphore::new(0));
499    let pool = ClassifiedWorkerPool::new(
500        BlockingFactory {
501            active: active.clone(),
502            max_active: max_active.clone(),
503            started: started.clone(),
504            gate: gate.clone(),
505        },
506        ClassifiedWorkerPoolConfig::default().with_max_concurrent_creation_count(2),
507    );
508
509    let pool_ref = pool.clone();
510    let task1 = tokio::spawn(async move { pool_ref.get_worker().await });
511    let pool_ref = pool.clone();
512    let task2 = tokio::spawn(async move { pool_ref.get_worker().await });
513    let pool_ref = pool.clone();
514    let task3 = tokio::spawn(async move { pool_ref.get_worker().await });
515
516    tokio::time::timeout(Duration::from_secs(1), async {
517        while started.load(Ordering::SeqCst) < 2 {
518            tokio::task::yield_now().await;
519        }
520    })
521    .await
522    .unwrap();
523    tokio::time::sleep(Duration::from_millis(20)).await;
524    assert_eq!(started.load(Ordering::SeqCst), 2);
525
526    gate.add_permits(2);
527    tokio::time::timeout(Duration::from_secs(1), async {
528        while started.load(Ordering::SeqCst) < 3 {
529            tokio::task::yield_now().await;
530        }
531    })
532    .await
533    .unwrap();
534    gate.add_permits(1);
535
536    let (worker1, worker2, worker3) = tokio::time::timeout(Duration::from_secs(1), async {
537        (
538            task1.await.unwrap().unwrap(),
539            task2.await.unwrap().unwrap(),
540            task3.await.unwrap().unwrap(),
541        )
542    })
543    .await
544    .unwrap();
545    assert_eq!(max_active.load(Ordering::SeqCst), 2);
546    drop((worker1, worker2, worker3));
547
548    let zero_pool = ClassifiedWorkerPool::new(
549        BlockingFactory {
550            active,
551            max_active,
552            started,
553            gate,
554        },
555        ClassifiedWorkerPoolConfig::default().with_max_concurrent_creation_count(0),
556    );
557    let error = zero_pool.get_worker().await.err().unwrap();
558    assert_eq!(error.code(), crate::PoolErrorCode::InvalidConfig);
559}
560
561impl<C: WorkerClassification, W: ClassifiedWorker<C>, F: ClassifiedWorkerFactory<C, W>>
562    ClassifiedWorkerPool<C, W, F>
563{
564    fn classification_limit_reached(&self, state: &WorkerPoolState<C, W, F>, c: &C) -> bool {
565        self.config
566            .max_count_per_classification
567            .map(|max_count| state.reserved_classified_count(c) >= usize::from(max_count))
568            .unwrap_or(false)
569    }
570
571    fn validate_created_worker(requested_classification: Option<&C>, worker: &W) -> PoolResult<C> {
572        let worker_classification = worker.classification();
573        if !worker.is_valid(worker_classification.clone()) {
574            return Err(pool_invalid_config_error(
575                "worker primary classification is not valid for itself",
576            ));
577        }
578        if let Some(classification) = requested_classification {
579            if worker_classification != classification.clone() {
580                return Err(pool_invalid_config_error(
581                    "factory returned worker with mismatched classification",
582                ));
583            }
584        }
585        Ok(worker_classification)
586    }
587
588    /// Creates a classification-aware worker pool with per-classification configuration.
589    pub fn new(factory: F, config: ClassifiedWorkerPoolConfig) -> ClassifiedWorkerPoolRef<C, W, F> {
590        let creation_semaphore = Semaphore::new(usize::from(config.max_concurrent_creation_count));
591        Arc::new(ClassifiedWorkerPool {
592            factory: Arc::new(factory),
593            config,
594            creation_semaphore,
595            state: Mutex::new(WorkerPoolState {
596                current_count: 0,
597                classified_count_map: HashMap::new(),
598                pending_classified_count_map: HashMap::new(),
599                worker_list: HashMap::new(),
600                waiting_list: Vec::new(),
601                clearing: false,
602                clear_waiting_list: Vec::new(),
603            }),
604        })
605    }
606
607    async fn create_worker(&self, classification: Option<C>) -> PoolResult<W> {
608        let _permit = self
609            .creation_semaphore
610            .acquire()
611            .await
612            .expect("creation semaphore is never closed");
613        if self.state.lock().unwrap().clearing {
614            return Err(pool_cleared_error());
615        }
616        self.factory.create(classification).await
617    }
618
619    fn take_expired_idle_workers(
620        state: &mut WorkerPoolState<C, W, F>,
621        idle_timeout: Option<std::time::Duration>,
622    ) -> Vec<IdleWorker<C, W>> {
623        let Some(idle_timeout) = idle_timeout else {
624            return Vec::new();
625        };
626        let mut removed_workers = Vec::new();
627        let now = Instant::now();
628        for workers in state.worker_list.values_mut() {
629            while workers
630                .front()
631                .map(|idle_worker| now.duration_since(idle_worker.idle_since) >= idle_timeout)
632                .unwrap_or(false)
633            {
634                removed_workers.push(workers.pop_front().unwrap());
635            }
636        }
637        state.worker_list.retain(|_, workers| !workers.is_empty());
638        for idle_worker in &removed_workers {
639            state.current_count -= 1;
640            state.dec_classified_count(idle_worker.primary_classification.clone());
641        }
642        removed_workers
643    }
644
645    fn take_expired_idle_workers_for_classification(
646        state: &mut WorkerPoolState<C, W, F>,
647        classification: &C,
648        idle_timeout: Option<Duration>,
649    ) -> Vec<IdleWorker<C, W>> {
650        let Some(idle_timeout) = idle_timeout else {
651            return Vec::new();
652        };
653        let mut removed_workers = Vec::new();
654        let now = Instant::now();
655        let remove_bucket = if let Some(workers) = state.worker_list.get_mut(classification) {
656            while workers
657                .front()
658                .map(|idle_worker| now.duration_since(idle_worker.idle_since) >= idle_timeout)
659                .unwrap_or(false)
660            {
661                removed_workers.push(workers.pop_front().unwrap());
662            }
663            workers.is_empty()
664        } else {
665            false
666        };
667        if remove_bucket {
668            state.worker_list.remove(classification);
669        }
670        for idle_worker in &removed_workers {
671            state.current_count -= 1;
672            state.dec_classified_count(idle_worker.primary_classification.clone());
673        }
674        removed_workers
675    }
676
677    pub fn cleanup_idle_worker(&self) -> usize {
678        let (removed_workers, clear_waiters) = {
679            let mut state = self.state.lock().unwrap();
680            let removed_workers =
681                Self::take_expired_idle_workers(&mut state, self.config.idle_timeout);
682            let clear_waiters = state.take_clear_waiters_if_done();
683            (removed_workers, clear_waiters)
684        };
685        for waiter in clear_waiters {
686            waiter.notify(());
687        }
688        let removed_count = removed_workers.len();
689        drop(removed_workers);
690        removed_count
691    }
692
693    pub async fn get_worker(
694        self: &ClassifiedWorkerPoolRef<C, W, F>,
695    ) -> PoolResult<ClassifiedWorkerGuard<C, W, F>> {
696        let mut blocked_classification = None;
697        loop {
698            if self.config.max_concurrent_creation_count == 0 {
699                return Err(pool_invalid_config_error(
700                    "pool max_concurrent_creation_count is zero",
701                ));
702            }
703            if self.config.max_count_per_classification == Some(0) {
704                return Err(pool_invalid_config_error(
705                    "pool max_count_per_classification is zero",
706                ));
707            }
708
709            let (worker, wait, should_create, removed_workers) = {
710                let mut state = self.state.lock().unwrap();
711                if state.clearing {
712                    return Err(pool_clearing_error());
713                }
714                state.remove_canceled_waiters();
715
716                let mut removed_workers = Vec::new();
717
718                let worker = loop {
719                    let Some(primary_classification) = state.next_idle_worker_classification()
720                    else {
721                        break None;
722                    };
723                    removed_workers.extend(Self::take_expired_idle_workers_for_classification(
724                        &mut state,
725                        &primary_classification,
726                        self.config.idle_timeout,
727                    ));
728                    let Some(idle_worker) =
729                        state.take_idle_worker_for_classification(&primary_classification)
730                    else {
731                        continue;
732                    };
733                    let expired = self
734                        .config
735                        .idle_timeout
736                        .map(|idle_timeout| idle_worker.idle_since.elapsed() >= idle_timeout)
737                        .unwrap_or(false);
738                    if expired
739                        || !idle_worker.worker.is_work()
740                        || idle_worker.worker.classification() != idle_worker.primary_classification
741                        || !idle_worker
742                            .worker
743                            .is_valid(idle_worker.primary_classification.clone())
744                    {
745                        state.current_count -= 1;
746                        state.dec_classified_count(idle_worker.primary_classification.clone());
747                        removed_workers.push(idle_worker);
748                        continue;
749                    }
750                    break Some((idle_worker.worker, idle_worker.primary_classification));
751                };
752
753                if worker.is_some() {
754                    (worker, None, false, removed_workers)
755                } else if blocked_classification
756                    .as_ref()
757                    .map(|classification| self.classification_limit_reached(&state, classification))
758                    .unwrap_or(false)
759                {
760                    let (notify, waiter) = Notify::new();
761                    state.waiting_list.push(WaitingItem {
762                        future: notify,
763                        condition: None,
764                    });
765                    (None, Some(waiter), false, removed_workers)
766                } else {
767                    state.current_count += 1;
768                    (None, None, true, removed_workers)
769                }
770            };
771
772            let reservation =
773                should_create.then(|| ClassifiedWorkerReservation::new(self.clone(), None));
774            drop(removed_workers);
775
776            if let Some((worker, primary_classification)) = worker {
777                return Ok(ClassifiedWorkerGuard::new(
778                    worker,
779                    self.clone(),
780                    primary_classification,
781                ));
782            }
783
784            if let Some(wait) = wait {
785                match wait.await {
786                    ClassifiedWorkerWaitResult::Worker(worker) => return Ok(worker),
787                    ClassifiedWorkerWaitResult::Retry => continue,
788                    ClassifiedWorkerWaitResult::Error(err) => return Err(err),
789                }
790            }
791
792            let reservation = reservation.unwrap();
793            let (worker, primary_classification) = match self.create_worker(None).await {
794                Ok(worker) => {
795                    let primary_classification = Self::validate_created_worker(None, &worker)?;
796                    (worker, primary_classification)
797                }
798                Err(err) => return Err(err),
799            };
800            match reservation.complete(primary_classification.clone()) {
801                ReservationCompletion::Complete => {}
802                ReservationCompletion::Clearing => return Err(pool_cleared_error()),
803                ReservationCompletion::ClassificationLimitReached => {
804                    blocked_classification = Some(primary_classification);
805                    drop(worker);
806                    continue;
807                }
808            }
809            return Ok(ClassifiedWorkerGuard::new(
810                worker,
811                self.clone(),
812                primary_classification,
813            ));
814        }
815    }
816
817    pub async fn get_classified_worker(
818        self: &ClassifiedWorkerPoolRef<C, W, F>,
819        classification: C,
820    ) -> PoolResult<ClassifiedWorkerGuard<C, W, F>> {
821        loop {
822            if self.config.max_concurrent_creation_count == 0 {
823                return Err(pool_invalid_config_error(
824                    "pool max_concurrent_creation_count is zero",
825                ));
826            }
827            if self.config.max_count_per_classification == Some(0) {
828                return Err(pool_invalid_config_error(
829                    "pool max_count_per_classification is zero",
830                ));
831            }
832
833            let (worker, wait, should_create, removed_workers) = {
834                let mut state = self.state.lock().unwrap();
835                if state.clearing {
836                    return Err(pool_clearing_error());
837                }
838                state.remove_canceled_waiters();
839
840                let mut removed_workers = Self::take_expired_idle_workers_for_classification(
841                    &mut state,
842                    &classification,
843                    self.config.idle_timeout,
844                );
845
846                let worker = loop {
847                    let Some(idle_worker) =
848                        state.take_idle_worker_for_classification(&classification)
849                    else {
850                        break None;
851                    };
852                    let expired = self
853                        .config
854                        .idle_timeout
855                        .map(|idle_timeout| idle_worker.idle_since.elapsed() >= idle_timeout)
856                        .unwrap_or(false);
857                    if expired
858                        || !idle_worker.worker.is_work()
859                        || idle_worker.worker.classification() != idle_worker.primary_classification
860                        || !idle_worker
861                            .worker
862                            .is_valid(idle_worker.primary_classification.clone())
863                    {
864                        state.current_count -= 1;
865                        state.dec_classified_count(idle_worker.primary_classification.clone());
866                        removed_workers.push(idle_worker);
867                        continue;
868                    }
869                    break Some((idle_worker.worker, idle_worker.primary_classification));
870                };
871
872                if worker.is_some() {
873                    (worker, None, false, removed_workers)
874                } else if self.classification_limit_reached(&state, &classification) {
875                    let (notify, waiter) = Notify::new();
876                    state.waiting_list.push(WaitingItem {
877                        future: notify,
878                        condition: Some(classification.clone()),
879                    });
880                    (None, Some(waiter), false, removed_workers)
881                } else {
882                    state.current_count += 1;
883                    state.inc_pending_classified_count(classification.clone());
884                    (None, None, true, removed_workers)
885                }
886            };
887
888            let reservation = should_create.then(|| {
889                ClassifiedWorkerReservation::new(self.clone(), Some(classification.clone()))
890            });
891            drop(removed_workers);
892
893            if let Some((worker, primary_classification)) = worker {
894                return Ok(ClassifiedWorkerGuard::new(
895                    worker,
896                    self.clone(),
897                    primary_classification,
898                ));
899            }
900
901            if let Some(wait) = wait {
902                match wait.await {
903                    ClassifiedWorkerWaitResult::Worker(worker) => return Ok(worker),
904                    ClassifiedWorkerWaitResult::Retry => continue,
905                    ClassifiedWorkerWaitResult::Error(err) => return Err(err),
906                }
907            }
908
909            let reservation = reservation.unwrap();
910            let (worker, primary_classification) =
911                match self.create_worker(Some(classification.clone())).await {
912                    Ok(worker) => {
913                        let primary_classification =
914                            Self::validate_created_worker(Some(&classification), &worker)?;
915                        (worker, primary_classification)
916                    }
917                    Err(err) => return Err(err),
918                };
919            match reservation.complete(primary_classification.clone()) {
920                ReservationCompletion::Complete => {}
921                ReservationCompletion::Clearing => return Err(pool_cleared_error()),
922                ReservationCompletion::ClassificationLimitReached => {
923                    drop(worker);
924                    continue;
925                }
926            }
927            return Ok(ClassifiedWorkerGuard::new(
928                worker,
929                self.clone(),
930                primary_classification,
931            ));
932        }
933    }
934
935    pub async fn clear_all_worker(&self) {
936        let (waiter, waiting_list, clear_waiters, idle_workers) = {
937            let mut state = self.state.lock().unwrap();
938            let idle_workers = if !state.clearing {
939                state.clearing = true;
940                let idle_workers = state.drain_idle_workers();
941                let cur_worker_count = idle_workers.len();
942                state.current_count -= cur_worker_count;
943                for idle_worker in &idle_workers {
944                    state.dec_classified_count(idle_worker.primary_classification.clone());
945                }
946                idle_workers
947            } else {
948                Vec::new()
949            };
950
951            let waiting_list = state.waiting_list.drain(..).collect::<Vec<_>>();
952            if state.current_count == 0 {
953                let clear_waiters = state.take_clear_waiters_if_done();
954                (None, waiting_list, clear_waiters, idle_workers)
955            } else {
956                let (notify, waiter) = Notify::new();
957                state.clear_waiting_list.push(notify);
958                (Some(waiter), waiting_list, Vec::new(), idle_workers)
959            }
960        };
961        for waiting in waiting_list {
962            waiting
963                .future
964                .notify(ClassifiedWorkerWaitResult::Error(pool_cleared_error()));
965        }
966        for waiter in clear_waiters {
967            waiter.notify(());
968        }
969        drop(idle_workers);
970        if let Some(waiter) = waiter {
971            waiter.await;
972        }
973    }
974
975    fn notify_retry_waiters(waiters: Vec<Notify<ClassifiedWorkerWaitResult<C, W, F>>>) {
976        for waiter in waiters {
977            waiter.notify(ClassifiedWorkerWaitResult::Retry);
978        }
979    }
980
981    fn rollback_reservation(&self, classification: Option<&C>) {
982        let (retry_waiters, clear_waiters) = {
983            let mut state = self.state.lock().unwrap();
984            state.current_count -= 1;
985            if let Some(classification) = classification {
986                state.dec_pending_classified_count(classification.clone());
987            }
988            let retry_waiters = state.drain_waiters();
989            let clear_waiters = state.take_clear_waiters_if_done();
990            (retry_waiters, clear_waiters)
991        };
992        Self::notify_retry_waiters(retry_waiters);
993        for waiter in clear_waiters {
994            waiter.notify(());
995        }
996    }
997
998    fn release(self: &ClassifiedWorkerPoolRef<C, W, F>, work: W, primary_classification: C) {
999        enum ReleaseAction<
1000            C: WorkerClassification,
1001            W: ClassifiedWorker<C>,
1002            F: ClassifiedWorkerFactory<C, W>,
1003        > {
1004            None,
1005            Notify(
1006                Notify<ClassifiedWorkerWaitResult<C, W, F>>,
1007                ClassifiedWorkerGuard<C, W, F>,
1008            ),
1009            Retry(Vec<Notify<ClassifiedWorkerWaitResult<C, W, F>>>),
1010        }
1011
1012        let primary_classification_valid = work.classification() == primary_classification
1013            && work.is_valid(primary_classification.clone());
1014        let mut clear_waiters = Vec::new();
1015        let mut removed_workers = Vec::new();
1016        let action = {
1017            let mut state = self.state.lock().unwrap();
1018            state.remove_canceled_waiters();
1019            if state.clearing {
1020                state.current_count -= 1;
1021                state.dec_classified_count(primary_classification);
1022                clear_waiters = state.take_clear_waiters_if_done();
1023                ReleaseAction::None
1024            } else if !primary_classification_valid {
1025                state.current_count -= 1;
1026                state.dec_classified_count(primary_classification);
1027                let waiters = state.drain_waiters();
1028                if !waiters.is_empty() {
1029                    ReleaseAction::Retry(waiters)
1030                } else {
1031                    ReleaseAction::None
1032                }
1033            } else if work.is_work() {
1034                if let Some(index) =
1035                    state.find_matching_waiter_index_for_worker(&work, &primary_classification)
1036                {
1037                    let waiting_item = state.waiting_list.remove(index);
1038                    ReleaseAction::Notify(
1039                        waiting_item.future,
1040                        ClassifiedWorkerGuard::new(work, self.clone(), primary_classification),
1041                    )
1042                } else {
1043                    state.push_idle_worker(work, primary_classification.clone());
1044                    if let Some(max_idle_count_per_classification) =
1045                        self.config.max_idle_count_per_classification
1046                    {
1047                        while state
1048                            .worker_list
1049                            .get(&primary_classification)
1050                            .map(VecDeque::len)
1051                            .unwrap_or(0)
1052                            > usize::from(max_idle_count_per_classification)
1053                        {
1054                            let idle_worker = state.remove_idle_worker(&primary_classification, 0);
1055                            state.current_count -= 1;
1056                            state.dec_classified_count(idle_worker.primary_classification.clone());
1057                            removed_workers.push(idle_worker);
1058                        }
1059                    }
1060                    ReleaseAction::None
1061                }
1062            } else {
1063                state.dec_classified_count(primary_classification);
1064                state.current_count -= 1;
1065                let waiters = state.drain_waiters();
1066                if !waiters.is_empty() {
1067                    ReleaseAction::Retry(waiters)
1068                } else {
1069                    clear_waiters = state.take_clear_waiters_if_done();
1070                    ReleaseAction::None
1071                }
1072            }
1073        };
1074
1075        for waiter in clear_waiters {
1076            waiter.notify(());
1077        }
1078        drop(removed_workers);
1079
1080        match action {
1081            ReleaseAction::None => {}
1082            ReleaseAction::Notify(waiting, worker) => {
1083                waiting.notify(ClassifiedWorkerWaitResult::Worker(worker));
1084            }
1085            ReleaseAction::Retry(waiters) => {
1086                Self::notify_retry_waiters(waiters);
1087            }
1088        }
1089    }
1090}
1091
1092#[cfg(test)]
1093mod idle_limit_tests {
1094    use super::*;
1095    use std::sync::atomic::{AtomicUsize, Ordering};
1096
1097    #[derive(Clone, Debug, Eq, Hash, PartialEq)]
1098    enum Classification {
1099        A,
1100        B,
1101    }
1102
1103    struct TestWorker {
1104        id: usize,
1105        classification: Classification,
1106    }
1107
1108    impl ClassifiedWorker<Classification> for TestWorker {
1109        fn is_work(&self) -> bool {
1110            true
1111        }
1112
1113        fn is_valid(&self, classification: Classification) -> bool {
1114            self.classification == classification
1115                || (self.classification == Classification::A && classification == Classification::B)
1116        }
1117
1118        fn classification(&self) -> Classification {
1119            self.classification.clone()
1120        }
1121    }
1122
1123    struct TestFactory(AtomicUsize);
1124
1125    #[async_trait::async_trait]
1126    impl ClassifiedWorkerFactory<Classification, TestWorker> for TestFactory {
1127        async fn create(&self, classification: Option<Classification>) -> PoolResult<TestWorker> {
1128            Ok(TestWorker {
1129                id: self.0.fetch_add(1, Ordering::SeqCst),
1130                classification: classification.unwrap_or(Classification::A),
1131            })
1132        }
1133    }
1134
1135    #[tokio::test]
1136    async fn classification_idle_limits_preserve_mru_workers_per_bucket() {
1137        let pool = ClassifiedWorkerPool::new(
1138            TestFactory(AtomicUsize::new(0)),
1139            ClassifiedWorkerPoolConfig {
1140                idle_timeout: None,
1141                max_count_per_classification: None,
1142                max_idle_count_per_classification: Some(1),
1143                ..Default::default()
1144            },
1145        );
1146        let a0 = pool.get_classified_worker(Classification::A).await.unwrap();
1147        let a1 = pool.get_classified_worker(Classification::A).await.unwrap();
1148        let b2 = pool.get_classified_worker(Classification::B).await.unwrap();
1149        drop(a0);
1150        drop(b2);
1151        drop(a1);
1152
1153        let a = pool.get_classified_worker(Classification::A).await.unwrap();
1154        let b = pool.get_classified_worker(Classification::B).await.unwrap();
1155        assert_eq!(a.id, 1);
1156        assert_eq!(b.id, 2);
1157    }
1158
1159    #[tokio::test]
1160    async fn acquisition_cleans_expired_worker_behind_bucket_mru() {
1161        let pool = ClassifiedWorkerPool::new(
1162            TestFactory(AtomicUsize::new(0)),
1163            ClassifiedWorkerPoolConfig::default()
1164                .with_idle_timeout(Some(Duration::from_millis(20))),
1165        );
1166        let a0 = pool.get_classified_worker(Classification::A).await.unwrap();
1167        let a1 = pool.get_classified_worker(Classification::A).await.unwrap();
1168
1169        drop(a0);
1170        tokio::time::sleep(Duration::from_millis(30)).await;
1171        drop(a1);
1172
1173        let a = pool.get_classified_worker(Classification::A).await.unwrap();
1174        assert_eq!(a.id, 1);
1175        assert_eq!(pool.cleanup_idle_worker(), 0);
1176    }
1177
1178    #[tokio::test]
1179    async fn classified_acquisition_only_cleans_and_reuses_requested_bucket() {
1180        let pool = ClassifiedWorkerPool::new(
1181            TestFactory(AtomicUsize::new(0)),
1182            ClassifiedWorkerPoolConfig::default()
1183                .with_idle_timeout(Some(Duration::from_millis(20))),
1184        );
1185        let a = pool.get_classified_worker(Classification::A).await.unwrap();
1186        drop(a);
1187        tokio::time::sleep(Duration::from_millis(30)).await;
1188
1189        let b = pool.get_classified_worker(Classification::B).await.unwrap();
1190        assert_eq!(b.id, 1);
1191        assert_eq!(b.classification(), Classification::B);
1192        assert_eq!(pool.cleanup_idle_worker(), 1);
1193    }
1194
1195    #[tokio::test]
1196    async fn generic_acquisition_only_cleans_visited_buckets() {
1197        let pool = ClassifiedWorkerPool::new(
1198            TestFactory(AtomicUsize::new(0)),
1199            ClassifiedWorkerPoolConfig::default()
1200                .with_idle_timeout(Some(Duration::from_millis(20))),
1201        );
1202        let a0 = pool.get_classified_worker(Classification::A).await.unwrap();
1203        let a1 = pool.get_classified_worker(Classification::A).await.unwrap();
1204        let b0 = pool.get_classified_worker(Classification::B).await.unwrap();
1205        let b1 = pool.get_classified_worker(Classification::B).await.unwrap();
1206        drop(a0);
1207        drop(b0);
1208        tokio::time::sleep(Duration::from_millis(30)).await;
1209        drop(a1);
1210        drop(b1);
1211
1212        let worker = pool.get_worker().await.unwrap();
1213        assert_eq!(pool.cleanup_idle_worker(), 1);
1214        drop(worker);
1215    }
1216
1217    #[tokio::test]
1218    async fn returned_worker_only_wakes_waiter_for_its_primary_classification() {
1219        let pool = ClassifiedWorkerPool::new(
1220            TestFactory(AtomicUsize::new(0)),
1221            ClassifiedWorkerPoolConfig::default().with_max_count_per_classification(Some(1)),
1222        );
1223        let a = pool.get_classified_worker(Classification::A).await.unwrap();
1224        let b = pool.get_classified_worker(Classification::B).await.unwrap();
1225        let pool_ref = pool.clone();
1226        let waiting_b =
1227            tokio::spawn(async move { pool_ref.get_classified_worker(Classification::B).await });
1228        tokio::time::sleep(Duration::from_millis(20)).await;
1229
1230        drop(a);
1231        tokio::task::yield_now().await;
1232        assert!(!waiting_b.is_finished());
1233
1234        drop(b);
1235        let b = tokio::time::timeout(Duration::from_secs(1), waiting_b)
1236            .await
1237            .unwrap()
1238            .unwrap()
1239            .unwrap();
1240        assert_eq!(b.classification(), Classification::B);
1241    }
1242
1243    #[tokio::test]
1244    async fn zero_per_classification_idle_limit_disables_idle_cache() {
1245        let pool = ClassifiedWorkerPool::new(
1246            TestFactory(AtomicUsize::new(0)),
1247            ClassifiedWorkerPoolConfig {
1248                max_idle_count_per_classification: Some(0),
1249                ..Default::default()
1250            },
1251        );
1252        let a = pool.get_classified_worker(Classification::A).await.unwrap();
1253        assert_eq!(a.id, 0);
1254        drop(a);
1255        let a = pool.get_classified_worker(Classification::A).await.unwrap();
1256        assert_eq!(a.id, 1);
1257    }
1258}
1259
1260#[cfg(test)]
1261fn new_classified_worker_pool<
1262    C: WorkerClassification,
1263    W: ClassifiedWorker<C>,
1264    F: ClassifiedWorkerFactory<C, W>,
1265>(
1266    max_count: u16,
1267    factory: F,
1268    mut config: ClassifiedWorkerPoolConfig,
1269) -> ClassifiedWorkerPoolRef<C, W, F> {
1270    if config.max_count_per_classification.is_none() {
1271        config.max_count_per_classification = Some(max_count);
1272    }
1273    ClassifiedWorkerPool::new(factory, config)
1274}
1275
1276#[tokio::test]
1277async fn test_pool() {
1278    struct TestWorker {
1279        work: bool,
1280        classification: TestWorkerClassification,
1281    }
1282
1283    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1284    enum TestWorkerClassification {
1285        A,
1286        B,
1287    }
1288    #[async_trait::async_trait]
1289    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1290        fn is_work(&self) -> bool {
1291            self.work
1292        }
1293
1294        fn is_valid(&self, c: TestWorkerClassification) -> bool {
1295            self.classification == c
1296        }
1297
1298        fn classification(&self) -> TestWorkerClassification {
1299            self.classification.clone()
1300        }
1301    }
1302
1303    struct TestWorkerFactory;
1304
1305    #[async_trait::async_trait]
1306    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1307        async fn create(
1308            &self,
1309            classification: Option<TestWorkerClassification>,
1310        ) -> PoolResult<TestWorker> {
1311            if let Some(classification) = classification {
1312                Ok(TestWorker {
1313                    work: true,
1314                    classification,
1315                })
1316            } else {
1317                Ok(TestWorker {
1318                    work: true,
1319                    classification: TestWorkerClassification::A,
1320                })
1321            }
1322        }
1323    }
1324
1325    let pool = ClassifiedWorkerPool::new(
1326        TestWorkerFactory,
1327        ClassifiedWorkerPoolConfig {
1328            max_count_per_classification: Some(2),
1329            ..Default::default()
1330        },
1331    );
1332
1333    let worker_a1 = pool.get_worker().await.unwrap();
1334    let worker_a2 = pool.get_worker().await.unwrap();
1335    let worker_b1 = pool
1336        .get_classified_worker(TestWorkerClassification::B)
1337        .await
1338        .unwrap();
1339    let worker_b2 = pool
1340        .get_classified_worker(TestWorkerClassification::B)
1341        .await
1342        .unwrap();
1343
1344    let pool_ref = pool.clone();
1345    let classified_waiter = tokio::spawn(async move {
1346        pool_ref
1347            .get_classified_worker(TestWorkerClassification::B)
1348            .await
1349    });
1350    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
1351    assert!(!classified_waiter.is_finished());
1352
1353    drop(worker_b1);
1354    let worker_b = tokio::time::timeout(std::time::Duration::from_secs(1), classified_waiter)
1355        .await
1356        .unwrap()
1357        .unwrap()
1358        .unwrap();
1359    drop(worker_a1);
1360    drop(worker_a2);
1361    drop(worker_b2);
1362    drop(worker_b);
1363
1364    let worker_b1 = pool
1365        .get_classified_worker(TestWorkerClassification::B)
1366        .await
1367        .unwrap();
1368    let worker_b2 = pool
1369        .get_classified_worker(TestWorkerClassification::B)
1370        .await
1371        .unwrap();
1372    let worker1 = pool.get_worker().await.unwrap();
1373    let worker2 = pool.get_worker().await.unwrap();
1374
1375    let pool_ref = pool.clone();
1376    let generic_waiter = tokio::spawn(async move { pool_ref.get_worker().await });
1377    let pool_ref = pool.clone();
1378    let classified_waiter = tokio::spawn(async move {
1379        pool_ref
1380            .get_classified_worker(TestWorkerClassification::B)
1381            .await
1382    });
1383    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
1384    assert!(!generic_waiter.is_finished());
1385    assert!(!classified_waiter.is_finished());
1386
1387    let pool_ref = pool.clone();
1388    let clear_task = tokio::spawn(async move {
1389        pool_ref.clear_all_worker().await;
1390    });
1391    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
1392
1393    assert!(generic_waiter.await.unwrap().is_err());
1394    assert!(classified_waiter.await.unwrap().is_err());
1395
1396    drop(worker1);
1397    drop(worker2);
1398    drop(worker_b1);
1399    drop(worker_b2);
1400
1401    tokio::time::timeout(std::time::Duration::from_secs(1), clear_task)
1402        .await
1403        .unwrap()
1404        .unwrap();
1405}
1406
1407#[tokio::test]
1408async fn test_clear_all_worker_waits_for_inflight_create() {
1409    use std::sync::atomic::{AtomicUsize, Ordering};
1410    use std::sync::Arc;
1411
1412    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1413    enum TestWorkerClassification {
1414        A,
1415    }
1416
1417    struct TestWorker {
1418        classification: TestWorkerClassification,
1419    }
1420
1421    #[async_trait::async_trait]
1422    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1423        fn is_work(&self) -> bool {
1424            true
1425        }
1426
1427        fn is_valid(&self, c: TestWorkerClassification) -> bool {
1428            self.classification == c
1429        }
1430
1431        fn classification(&self) -> TestWorkerClassification {
1432            self.classification.clone()
1433        }
1434    }
1435
1436    struct TestWorkerFactory {
1437        create_count: Arc<AtomicUsize>,
1438    }
1439
1440    #[async_trait::async_trait]
1441    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1442        async fn create(
1443            &self,
1444            classification: Option<TestWorkerClassification>,
1445        ) -> PoolResult<TestWorker> {
1446            self.create_count.fetch_add(1, Ordering::SeqCst);
1447            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1448            Ok(TestWorker {
1449                classification: classification.unwrap_or(TestWorkerClassification::A),
1450            })
1451        }
1452    }
1453
1454    let create_count = Arc::new(AtomicUsize::new(0));
1455    let pool = new_classified_worker_pool(
1456        1,
1457        TestWorkerFactory {
1458            create_count: create_count.clone(),
1459        },
1460        Default::default(),
1461    );
1462
1463    let pool_ref = pool.clone();
1464    let worker_task = tokio::spawn(async move { pool_ref.get_worker().await });
1465    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1466
1467    pool.clear_all_worker().await;
1468
1469    let worker = worker_task.await.unwrap();
1470    assert!(worker.is_err());
1471    assert_eq!(create_count.load(Ordering::SeqCst), 1);
1472}
1473
1474#[tokio::test]
1475async fn test_concurrent_clear_all_worker() {
1476    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1477    enum TestWorkerClassification {
1478        A,
1479    }
1480
1481    struct TestWorker {
1482        classification: TestWorkerClassification,
1483    }
1484
1485    #[async_trait::async_trait]
1486    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1487        fn is_work(&self) -> bool {
1488            true
1489        }
1490
1491        fn is_valid(&self, c: TestWorkerClassification) -> bool {
1492            self.classification == c
1493        }
1494
1495        fn classification(&self) -> TestWorkerClassification {
1496            self.classification.clone()
1497        }
1498    }
1499
1500    struct TestWorkerFactory;
1501
1502    #[async_trait::async_trait]
1503    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1504        async fn create(
1505            &self,
1506            classification: Option<TestWorkerClassification>,
1507        ) -> PoolResult<TestWorker> {
1508            Ok(TestWorker {
1509                classification: classification.unwrap_or(TestWorkerClassification::A),
1510            })
1511        }
1512    }
1513
1514    let pool = ClassifiedWorkerPool::new(
1515        TestWorkerFactory,
1516        ClassifiedWorkerPoolConfig {
1517            max_count_per_classification: Some(1),
1518            ..Default::default()
1519        },
1520    );
1521    let worker = pool.get_worker().await.unwrap();
1522
1523    let pool_ref = pool.clone();
1524    let clear_task1 = tokio::spawn(async move {
1525        pool_ref.clear_all_worker().await;
1526    });
1527
1528    let pool_ref = pool.clone();
1529    let clear_task2 = tokio::spawn(async move {
1530        pool_ref.clear_all_worker().await;
1531    });
1532
1533    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1534    drop(worker);
1535
1536    tokio::time::timeout(std::time::Duration::from_secs(1), async {
1537        clear_task1.await.unwrap();
1538        clear_task2.await.unwrap();
1539    })
1540    .await
1541    .unwrap();
1542}
1543
1544#[tokio::test]
1545async fn test_zero_max_count_per_classification_returns_error() {
1546    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1547    enum TestWorkerClassification {
1548        A,
1549    }
1550
1551    struct TestWorker {
1552        classification: TestWorkerClassification,
1553    }
1554
1555    #[async_trait::async_trait]
1556    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1557        fn is_work(&self) -> bool {
1558            true
1559        }
1560
1561        fn is_valid(&self, c: TestWorkerClassification) -> bool {
1562            self.classification == c
1563        }
1564
1565        fn classification(&self) -> TestWorkerClassification {
1566            self.classification.clone()
1567        }
1568    }
1569
1570    struct TestWorkerFactory;
1571
1572    #[async_trait::async_trait]
1573    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1574        async fn create(
1575            &self,
1576            classification: Option<TestWorkerClassification>,
1577        ) -> PoolResult<TestWorker> {
1578            Ok(TestWorker {
1579                classification: classification.unwrap_or(TestWorkerClassification::A),
1580            })
1581        }
1582    }
1583
1584    let pool = ClassifiedWorkerPool::new(
1585        TestWorkerFactory,
1586        ClassifiedWorkerPoolConfig {
1587            max_count_per_classification: Some(0),
1588            ..Default::default()
1589        },
1590    );
1591    let worker = pool.get_worker().await;
1592    assert!(worker.is_err());
1593    assert_eq!(
1594        worker.err().unwrap().code(),
1595        crate::PoolErrorCode::InvalidConfig
1596    );
1597}
1598
1599#[tokio::test]
1600async fn test_default_config_has_no_per_classification_count_limit() {
1601    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1602    enum TestWorkerClassification {
1603        A,
1604    }
1605
1606    struct TestWorker;
1607
1608    #[async_trait::async_trait]
1609    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1610        fn is_work(&self) -> bool {
1611            true
1612        }
1613
1614        fn is_valid(&self, _classification: TestWorkerClassification) -> bool {
1615            true
1616        }
1617
1618        fn classification(&self) -> TestWorkerClassification {
1619            TestWorkerClassification::A
1620        }
1621    }
1622
1623    struct TestWorkerFactory;
1624
1625    #[async_trait::async_trait]
1626    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1627        async fn create(
1628            &self,
1629            _classification: Option<TestWorkerClassification>,
1630        ) -> PoolResult<TestWorker> {
1631            Ok(TestWorker)
1632        }
1633    }
1634
1635    let pool = ClassifiedWorkerPool::new(TestWorkerFactory, Default::default());
1636    let worker1 = pool.get_worker().await.unwrap();
1637    let worker2 = pool.get_worker().await.unwrap();
1638
1639    assert_eq!(pool.state.lock().unwrap().current_count, 2);
1640
1641    drop(worker1);
1642    drop(worker2);
1643}
1644
1645#[tokio::test]
1646async fn test_classified_pool_waits_when_classification_already_has_worker() {
1647    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1648    enum TestWorkerClassification {
1649        A,
1650        B,
1651    }
1652
1653    struct TestWorker {
1654        classification: TestWorkerClassification,
1655    }
1656
1657    #[async_trait::async_trait]
1658    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1659        fn is_work(&self) -> bool {
1660            true
1661        }
1662
1663        fn is_valid(&self, c: TestWorkerClassification) -> bool {
1664            self.classification == c
1665        }
1666
1667        fn classification(&self) -> TestWorkerClassification {
1668            self.classification.clone()
1669        }
1670    }
1671
1672    struct TestWorkerFactory;
1673
1674    #[async_trait::async_trait]
1675    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1676        async fn create(
1677            &self,
1678            classification: Option<TestWorkerClassification>,
1679        ) -> PoolResult<TestWorker> {
1680            Ok(TestWorker {
1681                classification: classification.unwrap_or(TestWorkerClassification::A),
1682            })
1683        }
1684    }
1685
1686    let pool = ClassifiedWorkerPool::new(
1687        TestWorkerFactory,
1688        ClassifiedWorkerPoolConfig {
1689            max_count_per_classification: Some(1),
1690            ..Default::default()
1691        },
1692    );
1693    let _worker = pool
1694        .get_classified_worker(TestWorkerClassification::B)
1695        .await
1696        .unwrap();
1697
1698    let pool_ref = pool.clone();
1699    let result = tokio::time::timeout(std::time::Duration::from_millis(100), async move {
1700        pool_ref
1701            .get_classified_worker(TestWorkerClassification::B)
1702            .await
1703    })
1704    .await;
1705
1706    assert!(result.is_err());
1707}
1708
1709#[tokio::test]
1710async fn test_different_classification_is_not_blocked_by_per_classification_limit() {
1711    use std::sync::atomic::{AtomicUsize, Ordering};
1712    use std::sync::Arc;
1713
1714    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1715    enum TestWorkerClassification {
1716        A,
1717        B,
1718    }
1719
1720    struct TestWorker {
1721        id: usize,
1722        classification: TestWorkerClassification,
1723    }
1724
1725    #[async_trait::async_trait]
1726    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1727        fn is_work(&self) -> bool {
1728            true
1729        }
1730
1731        fn is_valid(&self, c: TestWorkerClassification) -> bool {
1732            self.classification == c
1733        }
1734
1735        fn classification(&self) -> TestWorkerClassification {
1736            self.classification.clone()
1737        }
1738    }
1739
1740    struct TestWorkerFactory {
1741        create_count: Arc<AtomicUsize>,
1742    }
1743
1744    #[async_trait::async_trait]
1745    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1746        async fn create(
1747            &self,
1748            classification: Option<TestWorkerClassification>,
1749        ) -> PoolResult<TestWorker> {
1750            let id = self.create_count.fetch_add(1, Ordering::SeqCst);
1751            Ok(TestWorker {
1752                id,
1753                classification: classification.unwrap_or(TestWorkerClassification::A),
1754            })
1755        }
1756    }
1757
1758    let create_count = Arc::new(AtomicUsize::new(0));
1759    let pool = new_classified_worker_pool(
1760        1,
1761        TestWorkerFactory {
1762            create_count: create_count.clone(),
1763        },
1764        Default::default(),
1765    );
1766
1767    let worker_a = pool
1768        .get_classified_worker(TestWorkerClassification::A)
1769        .await
1770        .unwrap();
1771    let worker_b = pool
1772        .get_classified_worker(TestWorkerClassification::B)
1773        .await
1774        .unwrap();
1775
1776    assert_eq!(worker_a.id, 0);
1777    assert_eq!(worker_b.id, 1);
1778    assert_eq!(worker_b.classification(), TestWorkerClassification::B);
1779    assert_eq!(create_count.load(Ordering::SeqCst), 2);
1780}
1781
1782#[tokio::test]
1783async fn test_classified_create_failure_fails_same_classification_waiters() {
1784    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1785    enum TestWorkerClassification {
1786        B,
1787    }
1788
1789    struct TestWorker {
1790        classification: TestWorkerClassification,
1791    }
1792
1793    #[async_trait::async_trait]
1794    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1795        fn is_work(&self) -> bool {
1796            true
1797        }
1798
1799        fn is_valid(&self, c: TestWorkerClassification) -> bool {
1800            self.classification == c
1801        }
1802
1803        fn classification(&self) -> TestWorkerClassification {
1804            self.classification.clone()
1805        }
1806    }
1807
1808    struct TestWorkerFactory;
1809
1810    #[async_trait::async_trait]
1811    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1812        async fn create(
1813            &self,
1814            _classification: Option<TestWorkerClassification>,
1815        ) -> PoolResult<TestWorker> {
1816            tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1817            Err(crate::pool_invalid_config_error("create failed"))
1818        }
1819    }
1820
1821    let pool = ClassifiedWorkerPool::new(
1822        TestWorkerFactory,
1823        ClassifiedWorkerPoolConfig {
1824            max_count_per_classification: Some(1),
1825            ..Default::default()
1826        },
1827    );
1828
1829    let pool_ref = pool.clone();
1830    let worker1 = tokio::spawn(async move {
1831        pool_ref
1832            .get_classified_worker(TestWorkerClassification::B)
1833            .await
1834    });
1835    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
1836
1837    let pool_ref = pool.clone();
1838    let worker2 = tokio::spawn(async move {
1839        pool_ref
1840            .get_classified_worker(TestWorkerClassification::B)
1841            .await
1842    });
1843
1844    let (worker1, worker2) = tokio::time::timeout(std::time::Duration::from_secs(1), async {
1845        (worker1.await.unwrap(), worker2.await.unwrap())
1846    })
1847    .await
1848    .unwrap();
1849
1850    assert_eq!(
1851        worker1.err().unwrap().code(),
1852        crate::PoolErrorCode::InvalidConfig
1853    );
1854    assert_eq!(
1855        worker2.err().unwrap().code(),
1856        crate::PoolErrorCode::InvalidConfig
1857    );
1858}
1859
1860#[tokio::test]
1861async fn test_classified_create_failure_wakes_generic_waiter_to_create() {
1862    use std::sync::atomic::{AtomicUsize, Ordering};
1863    use std::sync::Arc;
1864
1865    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1866    enum TestWorkerClassification {
1867        A,
1868    }
1869
1870    struct TestWorker {
1871        id: usize,
1872        classification: TestWorkerClassification,
1873    }
1874
1875    #[async_trait::async_trait]
1876    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1877        fn is_work(&self) -> bool {
1878            true
1879        }
1880
1881        fn is_valid(&self, c: TestWorkerClassification) -> bool {
1882            self.classification == c
1883        }
1884
1885        fn classification(&self) -> TestWorkerClassification {
1886            self.classification.clone()
1887        }
1888    }
1889
1890    struct TestWorkerFactory {
1891        create_count: Arc<AtomicUsize>,
1892    }
1893
1894    #[async_trait::async_trait]
1895    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1896        async fn create(
1897            &self,
1898            classification: Option<TestWorkerClassification>,
1899        ) -> PoolResult<TestWorker> {
1900            let id = self.create_count.fetch_add(1, Ordering::SeqCst);
1901            tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1902            if id == 0 && classification == Some(TestWorkerClassification::A) {
1903                Err(crate::pool_invalid_config_error("create failed"))
1904            } else {
1905                Ok(TestWorker {
1906                    id,
1907                    classification: classification.unwrap_or(TestWorkerClassification::A),
1908                })
1909            }
1910        }
1911    }
1912
1913    let create_count = Arc::new(AtomicUsize::new(0));
1914    let pool = new_classified_worker_pool(
1915        1,
1916        TestWorkerFactory {
1917            create_count: create_count.clone(),
1918        },
1919        Default::default(),
1920    );
1921
1922    let pool_ref = pool.clone();
1923    let classified = tokio::spawn(async move {
1924        pool_ref
1925            .get_classified_worker(TestWorkerClassification::A)
1926            .await
1927    });
1928    tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1929
1930    let pool_ref = pool.clone();
1931    let generic = tokio::spawn(async move { pool_ref.get_worker().await });
1932
1933    let (classified, generic) = tokio::time::timeout(std::time::Duration::from_secs(1), async {
1934        (classified.await.unwrap(), generic.await.unwrap())
1935    })
1936    .await
1937    .unwrap();
1938
1939    assert_eq!(
1940        classified.err().unwrap().code(),
1941        crate::PoolErrorCode::InvalidConfig
1942    );
1943    let generic = generic.unwrap();
1944    assert_eq!(generic.id, 1);
1945    assert_eq!(create_count.load(Ordering::SeqCst), 2);
1946}
1947
1948#[tokio::test]
1949async fn test_classified_retry_notification_skips_canceled_waiter() {
1950    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
1951    enum TestWorkerClassification {
1952        A,
1953    }
1954
1955    struct TestWorker {
1956        classification: TestWorkerClassification,
1957    }
1958
1959    #[async_trait::async_trait]
1960    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
1961        fn is_work(&self) -> bool {
1962            true
1963        }
1964
1965        fn is_valid(&self, c: TestWorkerClassification) -> bool {
1966            self.classification == c
1967        }
1968
1969        fn classification(&self) -> TestWorkerClassification {
1970            self.classification.clone()
1971        }
1972    }
1973
1974    struct TestWorkerFactory;
1975
1976    #[async_trait::async_trait]
1977    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
1978        async fn create(
1979            &self,
1980            classification: Option<TestWorkerClassification>,
1981        ) -> PoolResult<TestWorker> {
1982            Ok(TestWorker {
1983                classification: classification.unwrap_or(TestWorkerClassification::A),
1984            })
1985        }
1986    }
1987
1988    let (canceled_notify, canceled_waiter) = Notify::new();
1989    drop(canceled_waiter);
1990    let (notify, waiter) = Notify::new();
1991
1992    ClassifiedWorkerPool::<TestWorkerClassification, TestWorker, TestWorkerFactory>::notify_retry_waiters(
1993        vec![canceled_notify, notify],
1994    );
1995
1996    let result = tokio::time::timeout(std::time::Duration::from_secs(1), waiter)
1997        .await
1998        .unwrap();
1999    assert!(matches!(result, ClassifiedWorkerWaitResult::Retry));
2000}
2001
2002#[tokio::test]
2003async fn test_classified_request_replaces_non_matching_idle_worker() {
2004    use std::sync::atomic::{AtomicUsize, Ordering};
2005    use std::sync::Arc;
2006
2007    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2008    enum TestWorkerClassification {
2009        A,
2010        B,
2011    }
2012
2013    struct TestWorker {
2014        id: usize,
2015        classification: TestWorkerClassification,
2016    }
2017
2018    #[async_trait::async_trait]
2019    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2020        fn is_work(&self) -> bool {
2021            true
2022        }
2023
2024        fn is_valid(&self, c: TestWorkerClassification) -> bool {
2025            self.classification == c
2026        }
2027
2028        fn classification(&self) -> TestWorkerClassification {
2029            self.classification.clone()
2030        }
2031    }
2032
2033    struct TestWorkerFactory {
2034        create_count: Arc<AtomicUsize>,
2035    }
2036
2037    #[async_trait::async_trait]
2038    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2039        async fn create(
2040            &self,
2041            classification: Option<TestWorkerClassification>,
2042        ) -> PoolResult<TestWorker> {
2043            let id = self.create_count.fetch_add(1, Ordering::SeqCst);
2044            Ok(TestWorker {
2045                id,
2046                classification: classification.unwrap_or(TestWorkerClassification::A),
2047            })
2048        }
2049    }
2050
2051    let create_count = Arc::new(AtomicUsize::new(0));
2052    let pool = new_classified_worker_pool(
2053        1,
2054        TestWorkerFactory {
2055            create_count: create_count.clone(),
2056        },
2057        Default::default(),
2058    );
2059
2060    {
2061        let worker = pool
2062            .get_classified_worker(TestWorkerClassification::A)
2063            .await
2064            .unwrap();
2065        assert_eq!(worker.id, 0);
2066    }
2067
2068    let worker = pool
2069        .get_classified_worker(TestWorkerClassification::B)
2070        .await
2071        .unwrap();
2072    assert_eq!(worker.id, 1);
2073    assert_eq!(worker.classification(), TestWorkerClassification::B);
2074    assert_eq!(create_count.load(Ordering::SeqCst), 2);
2075}
2076
2077#[tokio::test]
2078async fn test_other_classification_does_not_wait_for_returned_non_matching_worker() {
2079    use std::sync::atomic::{AtomicUsize, Ordering};
2080    use std::sync::Arc;
2081
2082    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2083    enum TestWorkerClassification {
2084        A,
2085        B,
2086    }
2087
2088    struct TestWorker {
2089        id: usize,
2090        classification: TestWorkerClassification,
2091    }
2092
2093    #[async_trait::async_trait]
2094    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2095        fn is_work(&self) -> bool {
2096            true
2097        }
2098
2099        fn is_valid(&self, c: TestWorkerClassification) -> bool {
2100            self.classification == c
2101        }
2102
2103        fn classification(&self) -> TestWorkerClassification {
2104            self.classification.clone()
2105        }
2106    }
2107
2108    struct TestWorkerFactory {
2109        create_count: Arc<AtomicUsize>,
2110    }
2111
2112    #[async_trait::async_trait]
2113    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2114        async fn create(
2115            &self,
2116            classification: Option<TestWorkerClassification>,
2117        ) -> PoolResult<TestWorker> {
2118            let id = self.create_count.fetch_add(1, Ordering::SeqCst);
2119            Ok(TestWorker {
2120                id,
2121                classification: classification.unwrap_or(TestWorkerClassification::A),
2122            })
2123        }
2124    }
2125
2126    let create_count = Arc::new(AtomicUsize::new(0));
2127    let pool = new_classified_worker_pool(
2128        2,
2129        TestWorkerFactory {
2130            create_count: create_count.clone(),
2131        },
2132        Default::default(),
2133    );
2134    let worker_a = pool
2135        .get_classified_worker(TestWorkerClassification::A)
2136        .await
2137        .unwrap();
2138    let _worker_b = pool
2139        .get_classified_worker(TestWorkerClassification::B)
2140        .await
2141        .unwrap();
2142
2143    let pool_ref = pool.clone();
2144    let waiter = tokio::spawn(async move {
2145        pool_ref
2146            .get_classified_worker(TestWorkerClassification::B)
2147            .await
2148    });
2149    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
2150    assert!(waiter.is_finished());
2151
2152    let worker = tokio::time::timeout(std::time::Duration::from_secs(1), waiter)
2153        .await
2154        .unwrap()
2155        .unwrap()
2156        .unwrap();
2157    assert_eq!(worker.id, 2);
2158    assert_eq!(worker.classification(), TestWorkerClassification::B);
2159    assert_eq!(create_count.load(Ordering::SeqCst), 3);
2160    drop(worker_a);
2161}
2162
2163#[tokio::test]
2164async fn test_other_classification_does_not_wait_for_unwork_non_matching_worker() {
2165    use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
2166    use std::sync::Arc;
2167
2168    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2169    enum TestWorkerClassification {
2170        A,
2171        B,
2172    }
2173
2174    struct TestWorker {
2175        id: usize,
2176        work: AtomicBool,
2177        classification: TestWorkerClassification,
2178    }
2179
2180    #[async_trait::async_trait]
2181    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2182        fn is_work(&self) -> bool {
2183            self.work.load(Ordering::SeqCst)
2184        }
2185
2186        fn is_valid(&self, c: TestWorkerClassification) -> bool {
2187            self.classification == c
2188        }
2189
2190        fn classification(&self) -> TestWorkerClassification {
2191            self.classification.clone()
2192        }
2193    }
2194
2195    struct TestWorkerFactory {
2196        create_count: Arc<AtomicUsize>,
2197    }
2198
2199    #[async_trait::async_trait]
2200    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2201        async fn create(
2202            &self,
2203            classification: Option<TestWorkerClassification>,
2204        ) -> PoolResult<TestWorker> {
2205            let id = self.create_count.fetch_add(1, Ordering::SeqCst);
2206            Ok(TestWorker {
2207                id,
2208                work: AtomicBool::new(true),
2209                classification: classification.unwrap_or(TestWorkerClassification::A),
2210            })
2211        }
2212    }
2213
2214    let create_count = Arc::new(AtomicUsize::new(0));
2215    let pool = new_classified_worker_pool(
2216        2,
2217        TestWorkerFactory {
2218            create_count: create_count.clone(),
2219        },
2220        Default::default(),
2221    );
2222    let worker_a = pool
2223        .get_classified_worker(TestWorkerClassification::A)
2224        .await
2225        .unwrap();
2226    let _worker_b = pool
2227        .get_classified_worker(TestWorkerClassification::B)
2228        .await
2229        .unwrap();
2230
2231    let pool_ref = pool.clone();
2232    let waiter = tokio::spawn(async move {
2233        pool_ref
2234            .get_classified_worker(TestWorkerClassification::B)
2235            .await
2236    });
2237    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
2238    assert!(waiter.is_finished());
2239
2240    worker_a.work.store(false, Ordering::SeqCst);
2241    let worker = tokio::time::timeout(std::time::Duration::from_secs(1), waiter)
2242        .await
2243        .unwrap()
2244        .unwrap()
2245        .unwrap();
2246    assert_eq!(worker.id, 2);
2247    assert_eq!(worker.classification(), TestWorkerClassification::B);
2248    assert_eq!(create_count.load(Ordering::SeqCst), 3);
2249    drop(worker_a);
2250}
2251
2252#[tokio::test]
2253async fn test_factory_must_return_matching_classification() {
2254    use std::sync::atomic::{AtomicUsize, Ordering};
2255    use std::sync::Arc;
2256
2257    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2258    enum TestWorkerClassification {
2259        A,
2260        B,
2261    }
2262
2263    struct TestWorker {
2264        classification: TestWorkerClassification,
2265    }
2266
2267    #[async_trait::async_trait]
2268    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2269        fn is_work(&self) -> bool {
2270            true
2271        }
2272
2273        fn is_valid(&self, c: TestWorkerClassification) -> bool {
2274            self.classification == c
2275        }
2276
2277        fn classification(&self) -> TestWorkerClassification {
2278            self.classification.clone()
2279        }
2280    }
2281
2282    struct TestWorkerFactory {
2283        create_count: Arc<AtomicUsize>,
2284    }
2285
2286    #[async_trait::async_trait]
2287    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2288        async fn create(
2289            &self,
2290            classification: Option<TestWorkerClassification>,
2291        ) -> PoolResult<TestWorker> {
2292            let count = self.create_count.fetch_add(1, Ordering::SeqCst);
2293            let classification = if count == 0 {
2294                TestWorkerClassification::A
2295            } else {
2296                classification.unwrap_or(TestWorkerClassification::A)
2297            };
2298            Ok(TestWorker { classification })
2299        }
2300    }
2301
2302    let create_count = Arc::new(AtomicUsize::new(0));
2303    let pool = new_classified_worker_pool(
2304        1,
2305        TestWorkerFactory {
2306            create_count: create_count.clone(),
2307        },
2308        Default::default(),
2309    );
2310    let worker = pool
2311        .get_classified_worker(TestWorkerClassification::B)
2312        .await;
2313    assert!(worker.is_err());
2314    assert_eq!(
2315        worker.err().unwrap().code(),
2316        crate::PoolErrorCode::InvalidConfig
2317    );
2318
2319    let worker = pool
2320        .get_classified_worker(TestWorkerClassification::B)
2321        .await;
2322    assert!(worker.is_ok());
2323    assert_eq!(create_count.load(Ordering::SeqCst), 2);
2324}
2325
2326#[tokio::test(flavor = "multi_thread")]
2327async fn test_classified_and_generic_waiters_both_progress() {
2328    use std::sync::mpsc;
2329
2330    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2331    enum TestWorkerClassification {
2332        B,
2333    }
2334
2335    struct TestWorker {
2336        classification: TestWorkerClassification,
2337    }
2338
2339    #[async_trait::async_trait]
2340    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2341        fn is_work(&self) -> bool {
2342            true
2343        }
2344
2345        fn is_valid(&self, c: TestWorkerClassification) -> bool {
2346            self.classification == c
2347        }
2348
2349        fn classification(&self) -> TestWorkerClassification {
2350            self.classification.clone()
2351        }
2352    }
2353
2354    struct TestWorkerFactory;
2355
2356    #[async_trait::async_trait]
2357    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2358        async fn create(
2359            &self,
2360            classification: Option<TestWorkerClassification>,
2361        ) -> PoolResult<TestWorker> {
2362            Ok(TestWorker {
2363                classification: classification.unwrap_or(TestWorkerClassification::B),
2364            })
2365        }
2366    }
2367
2368    let pool = ClassifiedWorkerPool::new(
2369        TestWorkerFactory,
2370        ClassifiedWorkerPoolConfig {
2371            max_count_per_classification: Some(1),
2372            ..Default::default()
2373        },
2374    );
2375    let worker = pool
2376        .get_classified_worker(TestWorkerClassification::B)
2377        .await
2378        .unwrap();
2379
2380    let (tx, rx) = mpsc::channel();
2381
2382    let pool_ref = pool.clone();
2383    let tx_classified = tx.clone();
2384    let classified_task = tokio::spawn(async move {
2385        let _worker = pool_ref
2386            .get_classified_worker(TestWorkerClassification::B)
2387            .await
2388            .unwrap();
2389        tx_classified.send("classified").unwrap();
2390        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
2391    });
2392
2393    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
2394
2395    let pool_ref = pool.clone();
2396    let generic_task = tokio::spawn(async move {
2397        let _worker = pool_ref.get_worker().await.unwrap();
2398        tx.send("generic").unwrap();
2399    });
2400
2401    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
2402    drop(worker);
2403
2404    classified_task.await.unwrap();
2405    generic_task.await.unwrap();
2406    let first = rx.recv_timeout(std::time::Duration::from_secs(2)).unwrap();
2407    let second = rx.recv_timeout(std::time::Duration::from_secs(2)).unwrap();
2408    assert_ne!(first, second);
2409}
2410
2411#[tokio::test]
2412async fn test_generic_factory_worker_must_be_valid_for_its_primary_classification() {
2413    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2414    enum TestWorkerClassification {
2415        A,
2416        B,
2417    }
2418
2419    struct TestWorker {
2420        classification: TestWorkerClassification,
2421    }
2422
2423    #[async_trait::async_trait]
2424    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2425        fn is_work(&self) -> bool {
2426            true
2427        }
2428
2429        fn is_valid(&self, c: TestWorkerClassification) -> bool {
2430            c == TestWorkerClassification::B
2431        }
2432
2433        fn classification(&self) -> TestWorkerClassification {
2434            self.classification.clone()
2435        }
2436    }
2437
2438    struct TestWorkerFactory;
2439
2440    #[async_trait::async_trait]
2441    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2442        async fn create(
2443            &self,
2444            _classification: Option<TestWorkerClassification>,
2445        ) -> PoolResult<TestWorker> {
2446            Ok(TestWorker {
2447                classification: TestWorkerClassification::A,
2448            })
2449        }
2450    }
2451
2452    let pool = ClassifiedWorkerPool::new(
2453        TestWorkerFactory,
2454        ClassifiedWorkerPoolConfig {
2455            max_count_per_classification: Some(1),
2456            ..Default::default()
2457        },
2458    );
2459    let worker = pool.get_worker().await;
2460    assert!(worker.is_err());
2461    assert_eq!(
2462        worker.err().unwrap().code(),
2463        crate::PoolErrorCode::InvalidConfig
2464    );
2465}
2466
2467#[tokio::test]
2468async fn test_classified_idle_worker_timeout_releases_worker() {
2469    use std::sync::atomic::{AtomicUsize, Ordering};
2470    use std::sync::Arc;
2471
2472    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2473    enum TestWorkerClassification {
2474        A,
2475        B,
2476    }
2477
2478    struct TestWorker {
2479        id: usize,
2480        classification: TestWorkerClassification,
2481    }
2482
2483    #[async_trait::async_trait]
2484    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2485        fn is_work(&self) -> bool {
2486            true
2487        }
2488
2489        fn is_valid(&self, c: TestWorkerClassification) -> bool {
2490            self.classification == c
2491        }
2492
2493        fn classification(&self) -> TestWorkerClassification {
2494            self.classification.clone()
2495        }
2496    }
2497
2498    struct TestWorkerFactory {
2499        create_count: Arc<AtomicUsize>,
2500    }
2501
2502    #[async_trait::async_trait]
2503    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2504        async fn create(
2505            &self,
2506            classification: Option<TestWorkerClassification>,
2507        ) -> PoolResult<TestWorker> {
2508            let id = self.create_count.fetch_add(1, Ordering::SeqCst);
2509            Ok(TestWorker {
2510                id,
2511                classification: classification.unwrap_or(TestWorkerClassification::A),
2512            })
2513        }
2514    }
2515
2516    let create_count = Arc::new(AtomicUsize::new(0));
2517    let pool = new_classified_worker_pool(
2518        1,
2519        TestWorkerFactory {
2520            create_count: create_count.clone(),
2521        },
2522        ClassifiedWorkerPoolConfig {
2523            idle_timeout: Some(std::time::Duration::from_millis(30)),
2524            ..Default::default()
2525        },
2526    );
2527
2528    {
2529        let worker = pool
2530            .get_classified_worker(TestWorkerClassification::B)
2531            .await
2532            .unwrap();
2533        assert_eq!(worker.id, 0);
2534        assert_eq!(worker.classification(), TestWorkerClassification::B);
2535    }
2536
2537    tokio::time::sleep(std::time::Duration::from_millis(80)).await;
2538
2539    let worker = pool
2540        .get_classified_worker(TestWorkerClassification::A)
2541        .await
2542        .unwrap();
2543    assert_eq!(worker.id, 1);
2544    assert_eq!(worker.classification(), TestWorkerClassification::A);
2545    assert_eq!(create_count.load(Ordering::SeqCst), 2);
2546}
2547
2548#[tokio::test]
2549async fn test_get_classified_worker_uses_most_recent_matching_idle_worker() {
2550    use std::sync::atomic::{AtomicUsize, Ordering};
2551    use std::sync::Arc;
2552
2553    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2554    enum TestWorkerClassification {
2555        A,
2556    }
2557
2558    struct TestWorker {
2559        id: usize,
2560        classification: TestWorkerClassification,
2561    }
2562
2563    #[async_trait::async_trait]
2564    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2565        fn is_work(&self) -> bool {
2566            true
2567        }
2568
2569        fn is_valid(&self, c: TestWorkerClassification) -> bool {
2570            self.classification == c
2571        }
2572
2573        fn classification(&self) -> TestWorkerClassification {
2574            self.classification.clone()
2575        }
2576    }
2577
2578    struct TestWorkerFactory {
2579        create_count: Arc<AtomicUsize>,
2580    }
2581
2582    #[async_trait::async_trait]
2583    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2584        async fn create(
2585            &self,
2586            classification: Option<TestWorkerClassification>,
2587        ) -> PoolResult<TestWorker> {
2588            let id = self.create_count.fetch_add(1, Ordering::SeqCst);
2589            Ok(TestWorker {
2590                id,
2591                classification: classification.unwrap_or(TestWorkerClassification::A),
2592            })
2593        }
2594    }
2595
2596    let create_count = Arc::new(AtomicUsize::new(0));
2597    let pool = new_classified_worker_pool(
2598        2,
2599        TestWorkerFactory {
2600            create_count: create_count.clone(),
2601        },
2602        Default::default(),
2603    );
2604
2605    let worker1 = pool
2606        .get_classified_worker(TestWorkerClassification::A)
2607        .await
2608        .unwrap();
2609    let worker2 = pool
2610        .get_classified_worker(TestWorkerClassification::A)
2611        .await
2612        .unwrap();
2613    assert_eq!(worker1.id, 0);
2614    assert_eq!(worker2.id, 1);
2615
2616    drop(worker1);
2617    drop(worker2);
2618
2619    let worker = pool
2620        .get_classified_worker(TestWorkerClassification::A)
2621        .await
2622        .unwrap();
2623    assert_eq!(worker.id, 1);
2624    assert_eq!(create_count.load(Ordering::SeqCst), 2);
2625}
2626
2627#[tokio::test]
2628async fn test_classified_cleanup_idle_worker_can_be_triggered_externally() {
2629    use std::sync::atomic::{AtomicUsize, Ordering};
2630    use std::sync::Arc;
2631
2632    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2633    enum TestWorkerClassification {
2634        A,
2635        B,
2636    }
2637
2638    struct TestWorker {
2639        id: usize,
2640        classification: TestWorkerClassification,
2641    }
2642
2643    #[async_trait::async_trait]
2644    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2645        fn is_work(&self) -> bool {
2646            true
2647        }
2648
2649        fn is_valid(&self, c: TestWorkerClassification) -> bool {
2650            self.classification == c
2651        }
2652
2653        fn classification(&self) -> TestWorkerClassification {
2654            self.classification.clone()
2655        }
2656    }
2657
2658    struct TestWorkerFactory {
2659        create_count: Arc<AtomicUsize>,
2660    }
2661
2662    #[async_trait::async_trait]
2663    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2664        async fn create(
2665            &self,
2666            classification: Option<TestWorkerClassification>,
2667        ) -> PoolResult<TestWorker> {
2668            let id = self.create_count.fetch_add(1, Ordering::SeqCst);
2669            Ok(TestWorker {
2670                id,
2671                classification: classification.unwrap_or(TestWorkerClassification::A),
2672            })
2673        }
2674    }
2675
2676    let create_count = Arc::new(AtomicUsize::new(0));
2677    let pool = new_classified_worker_pool(
2678        1,
2679        TestWorkerFactory {
2680            create_count: create_count.clone(),
2681        },
2682        ClassifiedWorkerPoolConfig {
2683            idle_timeout: Some(std::time::Duration::from_millis(30)),
2684            ..Default::default()
2685        },
2686    );
2687
2688    {
2689        let worker = pool
2690            .get_classified_worker(TestWorkerClassification::B)
2691            .await
2692            .unwrap();
2693        assert_eq!(worker.id, 0);
2694        assert_eq!(worker.classification(), TestWorkerClassification::B);
2695    }
2696
2697    tokio::time::sleep(std::time::Duration::from_millis(80)).await;
2698
2699    assert_eq!(pool.cleanup_idle_worker(), 1);
2700
2701    let worker = pool
2702        .get_classified_worker(TestWorkerClassification::A)
2703        .await
2704        .unwrap();
2705    assert_eq!(worker.id, 1);
2706    assert_eq!(worker.classification(), TestWorkerClassification::A);
2707    assert_eq!(create_count.load(Ordering::SeqCst), 2);
2708}
2709
2710#[tokio::test]
2711async fn test_canceled_classified_create_rolls_back_reservation() {
2712    use std::sync::atomic::{AtomicBool, Ordering};
2713
2714    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2715    enum TestWorkerClassification {
2716        A,
2717    }
2718
2719    struct TestWorker;
2720
2721    #[async_trait::async_trait]
2722    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2723        fn is_work(&self) -> bool {
2724            true
2725        }
2726
2727        fn is_valid(&self, _classification: TestWorkerClassification) -> bool {
2728            true
2729        }
2730
2731        fn classification(&self) -> TestWorkerClassification {
2732            TestWorkerClassification::A
2733        }
2734    }
2735
2736    struct TestWorkerFactory {
2737        create_started: Arc<AtomicBool>,
2738        allow_create: Arc<AtomicBool>,
2739    }
2740
2741    #[async_trait::async_trait]
2742    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2743        async fn create(
2744            &self,
2745            _classification: Option<TestWorkerClassification>,
2746        ) -> PoolResult<TestWorker> {
2747            self.create_started.store(true, Ordering::SeqCst);
2748            while !self.allow_create.load(Ordering::SeqCst) {
2749                tokio::task::yield_now().await;
2750            }
2751            Ok(TestWorker)
2752        }
2753    }
2754
2755    let create_started = Arc::new(AtomicBool::new(false));
2756    let allow_create = Arc::new(AtomicBool::new(false));
2757    let pool = new_classified_worker_pool(
2758        1,
2759        TestWorkerFactory {
2760            create_started: create_started.clone(),
2761            allow_create: allow_create.clone(),
2762        },
2763        Default::default(),
2764    );
2765
2766    let pool_ref = pool.clone();
2767    let create_task = tokio::spawn(async move {
2768        pool_ref
2769            .get_classified_worker(TestWorkerClassification::A)
2770            .await
2771    });
2772    while !create_started.load(Ordering::SeqCst) {
2773        tokio::task::yield_now().await;
2774    }
2775    create_task.abort();
2776    assert!(matches!(create_task.await, Err(err) if err.is_cancelled()));
2777
2778    allow_create.store(true, Ordering::SeqCst);
2779    let worker = tokio::time::timeout(
2780        std::time::Duration::from_secs(1),
2781        pool.get_classified_worker(TestWorkerClassification::A),
2782    )
2783    .await
2784    .unwrap()
2785    .unwrap();
2786    drop(worker);
2787
2788    tokio::time::timeout(std::time::Duration::from_secs(1), pool.clear_all_worker())
2789        .await
2790        .unwrap();
2791}
2792
2793#[tokio::test]
2794async fn test_mutating_worker_classification_removes_returned_worker() {
2795    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2796    enum TestWorkerClassification {
2797        A,
2798        B,
2799    }
2800
2801    struct TestWorker {
2802        work: bool,
2803        classification: TestWorkerClassification,
2804    }
2805
2806    #[async_trait::async_trait]
2807    impl ClassifiedWorker<TestWorkerClassification> for TestWorker {
2808        fn is_work(&self) -> bool {
2809            self.work
2810        }
2811
2812        fn is_valid(&self, classification: TestWorkerClassification) -> bool {
2813            self.classification == classification
2814        }
2815
2816        fn classification(&self) -> TestWorkerClassification {
2817            self.classification.clone()
2818        }
2819    }
2820
2821    struct TestWorkerFactory;
2822
2823    #[async_trait::async_trait]
2824    impl ClassifiedWorkerFactory<TestWorkerClassification, TestWorker> for TestWorkerFactory {
2825        async fn create(
2826            &self,
2827            classification: Option<TestWorkerClassification>,
2828        ) -> PoolResult<TestWorker> {
2829            Ok(TestWorker {
2830                work: true,
2831                classification: classification.unwrap_or(TestWorkerClassification::A),
2832            })
2833        }
2834    }
2835
2836    let pool = new_classified_worker_pool(
2837        1,
2838        TestWorkerFactory,
2839        ClassifiedWorkerPoolConfig {
2840            idle_timeout: None,
2841            max_count_per_classification: Some(1),
2842            ..Default::default()
2843        },
2844    );
2845    let mut worker = pool
2846        .get_classified_worker(TestWorkerClassification::A)
2847        .await
2848        .unwrap();
2849    worker.classification = TestWorkerClassification::B;
2850    drop(worker);
2851
2852    {
2853        let state = pool.state.lock().unwrap();
2854        assert_eq!(state.current_count, 0);
2855        assert!(state.classified_count_map.is_empty());
2856    }
2857
2858    let worker = tokio::time::timeout(
2859        std::time::Duration::from_secs(1),
2860        pool.get_classified_worker(TestWorkerClassification::A),
2861    )
2862    .await
2863    .unwrap()
2864    .unwrap();
2865    assert_eq!(worker.classification(), TestWorkerClassification::A);
2866}
2867
2868#[cfg(test)]
2869mod affected_path_tests {
2870    use super::*;
2871    use std::collections::VecDeque;
2872    use std::sync::atomic::{AtomicBool, Ordering};
2873    use std::sync::mpsc;
2874
2875    #[derive(Clone, Debug, Eq, PartialEq, Hash)]
2876    enum Classification {
2877        A,
2878        B,
2879        C,
2880    }
2881
2882    struct BlockingWorker {
2883        classification: Classification,
2884    }
2885
2886    #[async_trait::async_trait]
2887    impl ClassifiedWorker<Classification> for BlockingWorker {
2888        fn is_work(&self) -> bool {
2889            true
2890        }
2891
2892        fn is_valid(&self, classification: Classification) -> bool {
2893            self.classification == classification
2894        }
2895
2896        fn classification(&self) -> Classification {
2897            self.classification.clone()
2898        }
2899    }
2900
2901    struct BlockingFactory {
2902        create_started: Arc<AtomicBool>,
2903        allow_create: Arc<AtomicBool>,
2904    }
2905
2906    #[async_trait::async_trait]
2907    impl ClassifiedWorkerFactory<Classification, BlockingWorker> for BlockingFactory {
2908        async fn create(
2909            &self,
2910            classification: Option<Classification>,
2911        ) -> PoolResult<BlockingWorker> {
2912            self.create_started.store(true, Ordering::SeqCst);
2913            while !self.allow_create.load(Ordering::SeqCst) {
2914                tokio::task::yield_now().await;
2915            }
2916            Ok(BlockingWorker {
2917                classification: classification.unwrap_or(Classification::A),
2918            })
2919        }
2920    }
2921
2922    async fn wait_for_create(create_started: &AtomicBool) {
2923        while !create_started.load(Ordering::SeqCst) {
2924            tokio::task::yield_now().await;
2925        }
2926    }
2927
2928    fn new_blocking_pool(
2929        max_count: u16,
2930    ) -> (
2931        ClassifiedWorkerPoolRef<Classification, BlockingWorker, BlockingFactory>,
2932        Arc<AtomicBool>,
2933        Arc<AtomicBool>,
2934    ) {
2935        let create_started = Arc::new(AtomicBool::new(false));
2936        let allow_create = Arc::new(AtomicBool::new(false));
2937        let pool = new_classified_worker_pool(
2938            max_count,
2939            BlockingFactory {
2940                create_started: create_started.clone(),
2941                allow_create: allow_create.clone(),
2942            },
2943            Default::default(),
2944        );
2945        (pool, create_started, allow_create)
2946    }
2947
2948    #[tokio::test]
2949    async fn test_canceled_generic_create_rolls_back_classified_pool_reservation() {
2950        let (pool, create_started, allow_create) = new_blocking_pool(1);
2951        let pool_ref = pool.clone();
2952        let create_task = tokio::spawn(async move { pool_ref.get_worker().await });
2953        wait_for_create(&create_started).await;
2954        create_task.abort();
2955        assert!(matches!(create_task.await, Err(err) if err.is_cancelled()));
2956
2957        allow_create.store(true, Ordering::SeqCst);
2958        let worker = tokio::time::timeout(Duration::from_secs(1), pool.get_worker())
2959            .await
2960            .unwrap()
2961            .unwrap();
2962        drop(worker);
2963        tokio::time::timeout(Duration::from_secs(1), pool.clear_all_worker())
2964            .await
2965            .unwrap();
2966    }
2967
2968    #[tokio::test]
2969    async fn test_canceled_replacement_create_rolls_back_classified_pool_reservation() {
2970        let (pool, create_started, allow_create) = new_blocking_pool(1);
2971        allow_create.store(true, Ordering::SeqCst);
2972        let worker = pool.get_classified_worker(Classification::A).await.unwrap();
2973        drop(worker);
2974
2975        create_started.store(false, Ordering::SeqCst);
2976        allow_create.store(false, Ordering::SeqCst);
2977        let pool_ref = pool.clone();
2978        let create_task =
2979            tokio::spawn(async move { pool_ref.get_classified_worker(Classification::B).await });
2980        wait_for_create(&create_started).await;
2981        create_task.abort();
2982        assert!(matches!(create_task.await, Err(err) if err.is_cancelled()));
2983
2984        allow_create.store(true, Ordering::SeqCst);
2985        let worker = tokio::time::timeout(
2986            Duration::from_secs(1),
2987            pool.get_classified_worker(Classification::B),
2988        )
2989        .await
2990        .unwrap()
2991        .unwrap();
2992        drop(worker);
2993        tokio::time::timeout(Duration::from_secs(1), pool.clear_all_worker())
2994            .await
2995            .unwrap();
2996    }
2997
2998    #[tokio::test]
2999    async fn test_canceled_overcommit_create_rolls_back_classified_pool_reservation() {
3000        let (pool, create_started, allow_create) = new_blocking_pool(1);
3001        allow_create.store(true, Ordering::SeqCst);
3002        let worker_a = pool.get_classified_worker(Classification::A).await.unwrap();
3003
3004        create_started.store(false, Ordering::SeqCst);
3005        allow_create.store(false, Ordering::SeqCst);
3006        let pool_ref = pool.clone();
3007        let create_task =
3008            tokio::spawn(async move { pool_ref.get_classified_worker(Classification::B).await });
3009        wait_for_create(&create_started).await;
3010        create_task.abort();
3011        assert!(matches!(create_task.await, Err(err) if err.is_cancelled()));
3012
3013        {
3014            let state = pool.state.lock().unwrap();
3015            assert_eq!(state.current_count, 1);
3016            assert_eq!(state.reserved_classified_count(&Classification::B), 0);
3017        }
3018
3019        allow_create.store(true, Ordering::SeqCst);
3020        let worker_b = tokio::time::timeout(
3021            Duration::from_secs(1),
3022            pool.get_classified_worker(Classification::B),
3023        )
3024        .await
3025        .unwrap()
3026        .unwrap();
3027        drop(worker_b);
3028        drop(worker_a);
3029        tokio::time::timeout(Duration::from_secs(1), pool.clear_all_worker())
3030            .await
3031            .unwrap();
3032    }
3033
3034    type DropCallback = Box<dyn FnOnce() + Send>;
3035
3036    struct DropProbeWorker {
3037        working: Arc<AtomicBool>,
3038        classification: Classification,
3039        on_drop: Option<DropCallback>,
3040    }
3041
3042    #[async_trait::async_trait]
3043    impl ClassifiedWorker<Classification> for DropProbeWorker {
3044        fn is_work(&self) -> bool {
3045            self.working.load(Ordering::SeqCst)
3046        }
3047
3048        fn is_valid(&self, classification: Classification) -> bool {
3049            self.classification == classification
3050        }
3051
3052        fn classification(&self) -> Classification {
3053            self.classification.clone()
3054        }
3055    }
3056
3057    impl Drop for DropProbeWorker {
3058        fn drop(&mut self) {
3059            if let Some(on_drop) = self.on_drop.take() {
3060                on_drop();
3061            }
3062        }
3063    }
3064
3065    struct DropProbeSpec {
3066        working: Arc<AtomicBool>,
3067        on_drop: Option<DropCallback>,
3068    }
3069
3070    struct DropProbeFactory {
3071        specs: Arc<Mutex<VecDeque<DropProbeSpec>>>,
3072    }
3073
3074    #[async_trait::async_trait]
3075    impl ClassifiedWorkerFactory<Classification, DropProbeWorker> for DropProbeFactory {
3076        async fn create(
3077            &self,
3078            classification: Option<Classification>,
3079        ) -> PoolResult<DropProbeWorker> {
3080            let spec = self.specs.lock().unwrap().pop_front().unwrap();
3081            Ok(DropProbeWorker {
3082                working: spec.working,
3083                classification: classification.unwrap_or(Classification::A),
3084                on_drop: spec.on_drop,
3085            })
3086        }
3087    }
3088
3089    type DropProbePool = ClassifiedWorkerPoolRef<Classification, DropProbeWorker, DropProbeFactory>;
3090
3091    fn new_drop_probe_pool(
3092        idle_timeout: Option<Duration>,
3093        max_idle_count: Option<u16>,
3094    ) -> (DropProbePool, Arc<Mutex<VecDeque<DropProbeSpec>>>) {
3095        let specs = Arc::new(Mutex::new(VecDeque::new()));
3096        let pool = new_classified_worker_pool(
3097            1,
3098            DropProbeFactory {
3099                specs: specs.clone(),
3100            },
3101            ClassifiedWorkerPoolConfig {
3102                max_idle_count_per_classification: max_idle_count,
3103                idle_timeout,
3104                ..Default::default()
3105            },
3106        );
3107        (pool, specs)
3108    }
3109
3110    fn drop_lock_probe(
3111        pool: &DropProbePool,
3112        working: Arc<AtomicBool>,
3113    ) -> (DropProbeSpec, mpsc::Receiver<bool>) {
3114        let (tx, rx) = mpsc::channel();
3115        let pool_ref = Arc::downgrade(pool);
3116        let on_drop = Box::new(move || {
3117            let pool_ref = pool_ref.upgrade().unwrap();
3118            tx.send(pool_ref.state.try_lock().is_ok()).unwrap();
3119        });
3120        (
3121            DropProbeSpec {
3122                working,
3123                on_drop: Some(on_drop),
3124            },
3125            rx,
3126        )
3127    }
3128
3129    fn plain_drop_probe_spec() -> DropProbeSpec {
3130        DropProbeSpec {
3131            working: Arc::new(AtomicBool::new(true)),
3132            on_drop: None,
3133        }
3134    }
3135
3136    #[derive(Copy, Clone)]
3137    enum IdleDropPath {
3138        Cleanup,
3139        IdleLimit,
3140        GenericInvalidScan,
3141        ClassifiedInvalidScan,
3142        Clear,
3143    }
3144
3145    async fn assert_idle_drop_path_runs_outside_lock(path: IdleDropPath) {
3146        let idle_timeout = matches!(path, IdleDropPath::Cleanup).then_some(Duration::ZERO);
3147        let max_idle_count = matches!(path, IdleDropPath::IdleLimit).then_some(0);
3148        let (pool, specs) = new_drop_probe_pool(idle_timeout, max_idle_count);
3149        let working = Arc::new(AtomicBool::new(true));
3150        let (spec, drop_result) = drop_lock_probe(&pool, working.clone());
3151        specs.lock().unwrap().push_back(spec);
3152
3153        let worker = pool.get_classified_worker(Classification::A).await.unwrap();
3154        drop(worker);
3155
3156        match path {
3157            IdleDropPath::Cleanup => {
3158                assert_eq!(pool.cleanup_idle_worker(), 1);
3159            }
3160            IdleDropPath::IdleLimit => {
3161                // The first return above evicts immediately from the zero-cap pool.
3162            }
3163            IdleDropPath::GenericInvalidScan => {
3164                working.store(false, Ordering::SeqCst);
3165                specs.lock().unwrap().push_back(plain_drop_probe_spec());
3166                let worker = pool.get_worker().await.unwrap();
3167                drop(worker);
3168            }
3169            IdleDropPath::ClassifiedInvalidScan => {
3170                working.store(false, Ordering::SeqCst);
3171                specs.lock().unwrap().push_back(plain_drop_probe_spec());
3172                let worker = pool.get_classified_worker(Classification::A).await.unwrap();
3173                drop(worker);
3174            }
3175            IdleDropPath::Clear => pool.clear_all_worker().await,
3176        }
3177
3178        assert!(drop_result.recv_timeout(Duration::from_secs(1)).unwrap());
3179    }
3180
3181    #[tokio::test]
3182    async fn test_all_classified_idle_drop_paths_run_outside_state_lock() {
3183        for path in [
3184            IdleDropPath::Cleanup,
3185            IdleDropPath::IdleLimit,
3186            IdleDropPath::GenericInvalidScan,
3187            IdleDropPath::ClassifiedInvalidScan,
3188            IdleDropPath::Clear,
3189        ] {
3190            assert_idle_drop_path_runs_outside_lock(path).await;
3191        }
3192    }
3193
3194    struct MutableWorker {
3195        working: Arc<AtomicBool>,
3196        valid: Arc<AtomicBool>,
3197        classification: Classification,
3198    }
3199
3200    #[async_trait::async_trait]
3201    impl ClassifiedWorker<Classification> for MutableWorker {
3202        fn is_work(&self) -> bool {
3203            self.working.load(Ordering::SeqCst)
3204        }
3205
3206        fn is_valid(&self, classification: Classification) -> bool {
3207            self.valid.load(Ordering::SeqCst) && self.classification == classification
3208        }
3209
3210        fn classification(&self) -> Classification {
3211            self.classification.clone()
3212        }
3213    }
3214
3215    struct MutableWorkerFactory;
3216
3217    #[async_trait::async_trait]
3218    impl ClassifiedWorkerFactory<Classification, MutableWorker> for MutableWorkerFactory {
3219        async fn create(
3220            &self,
3221            classification: Option<Classification>,
3222        ) -> PoolResult<MutableWorker> {
3223            Ok(MutableWorker {
3224                working: Arc::new(AtomicBool::new(true)),
3225                valid: Arc::new(AtomicBool::new(true)),
3226                classification: classification.unwrap_or(Classification::A),
3227            })
3228        }
3229    }
3230
3231    type MutablePool = ClassifiedWorkerPoolRef<Classification, MutableWorker, MutableWorkerFactory>;
3232
3233    fn new_mutable_pool(max_count: u16, idle_timeout: Option<Duration>) -> MutablePool {
3234        new_classified_worker_pool(
3235            max_count,
3236            MutableWorkerFactory,
3237            ClassifiedWorkerPoolConfig {
3238                idle_timeout,
3239                ..Default::default()
3240            },
3241        )
3242    }
3243
3244    fn new_limited_mutable_pool(max_count: u16, max_count_per_classification: u16) -> MutablePool {
3245        new_classified_worker_pool(
3246            max_count,
3247            MutableWorkerFactory,
3248            ClassifiedWorkerPoolConfig {
3249                idle_timeout: None,
3250                max_count_per_classification: Some(max_count_per_classification),
3251                ..Default::default()
3252            },
3253        )
3254    }
3255
3256    fn assert_accounting_empty(pool: &MutablePool) {
3257        let state = pool.state.lock().unwrap();
3258        assert_eq!(state.current_count, 0);
3259        assert!(state.classified_count_map.is_empty());
3260        assert!(state.pending_classified_count_map.is_empty());
3261    }
3262
3263    fn assert_only_classification(
3264        pool: &MutablePool,
3265        classification: Classification,
3266        count: usize,
3267    ) {
3268        let state = pool.state.lock().unwrap();
3269        assert_eq!(state.current_count, count);
3270        assert_eq!(state.classified_count_map.len(), 1);
3271        assert_eq!(
3272            state.classified_count_map.get(&classification).copied(),
3273            Some(count)
3274        );
3275    }
3276
3277    #[derive(Copy, Clone)]
3278    enum IdleAccountingPath {
3279        Cleanup,
3280        Clear,
3281        GenericInvalidScan,
3282        ClassifiedInvalidScan,
3283    }
3284
3285    async fn assert_idle_accounting_path(path: IdleAccountingPath) {
3286        let idle_timeout = matches!(path, IdleAccountingPath::Cleanup).then_some(Duration::ZERO);
3287        let pool = new_mutable_pool(1, idle_timeout);
3288        let worker = pool.get_classified_worker(Classification::A).await.unwrap();
3289        let working = worker.working.clone();
3290        drop(worker);
3291
3292        match path {
3293            IdleAccountingPath::Cleanup => {
3294                assert_eq!(pool.cleanup_idle_worker(), 1);
3295                assert_accounting_empty(&pool);
3296            }
3297            IdleAccountingPath::Clear => {
3298                pool.clear_all_worker().await;
3299                assert_accounting_empty(&pool);
3300            }
3301            IdleAccountingPath::GenericInvalidScan => {
3302                working.store(false, Ordering::SeqCst);
3303                let worker = pool.get_worker().await.unwrap();
3304                assert_only_classification(&pool, Classification::A, 1);
3305                worker.working.store(false, Ordering::SeqCst);
3306                drop(worker);
3307                assert_accounting_empty(&pool);
3308            }
3309            IdleAccountingPath::ClassifiedInvalidScan => {
3310                working.store(false, Ordering::SeqCst);
3311                let worker = pool.get_classified_worker(Classification::A).await.unwrap();
3312                assert_only_classification(&pool, Classification::A, 1);
3313                worker.working.store(false, Ordering::SeqCst);
3314                drop(worker);
3315                assert_accounting_empty(&pool);
3316            }
3317        }
3318    }
3319
3320    #[tokio::test]
3321    async fn test_all_idle_accounting_paths() {
3322        for path in [
3323            IdleAccountingPath::Cleanup,
3324            IdleAccountingPath::Clear,
3325            IdleAccountingPath::GenericInvalidScan,
3326            IdleAccountingPath::ClassifiedInvalidScan,
3327        ] {
3328            assert_idle_accounting_path(path).await;
3329        }
3330    }
3331
3332    async fn wait_for_waiter(pool: &MutablePool) {
3333        loop {
3334            if !pool.state.lock().unwrap().waiting_list.is_empty() {
3335                return;
3336            }
3337            tokio::task::yield_now().await;
3338        }
3339    }
3340
3341    #[tokio::test]
3342    async fn test_canceled_waiter_is_removed_when_worker_returns() {
3343        let pool = new_limited_mutable_pool(1, 1);
3344        let worker_a = pool.get_classified_worker(Classification::A).await.unwrap();
3345
3346        let pool_ref = pool.clone();
3347        let waiter =
3348            tokio::spawn(async move { pool_ref.get_classified_worker(Classification::A).await });
3349        wait_for_waiter(&pool).await;
3350        waiter.abort();
3351        assert!(matches!(waiter.await, Err(err) if err.is_cancelled()));
3352
3353        drop(worker_a);
3354
3355        let state = pool.state.lock().unwrap();
3356        assert!(state.waiting_list.is_empty());
3357        assert_eq!(
3358            state.worker_list.values().map(VecDeque::len).sum::<usize>(),
3359            1
3360        );
3361    }
3362
3363    #[tokio::test]
3364    async fn test_worker_invalid_for_primary_classification_wakes_waiter() {
3365        let pool = new_limited_mutable_pool(1, 1);
3366        let worker_a = pool.get_classified_worker(Classification::A).await.unwrap();
3367        let valid = worker_a.valid.clone();
3368
3369        let pool_ref = pool.clone();
3370        let waiting_a =
3371            tokio::spawn(async move { pool_ref.get_classified_worker(Classification::A).await });
3372        wait_for_waiter(&pool).await;
3373
3374        valid.store(false, Ordering::SeqCst);
3375        drop(worker_a);
3376
3377        let replacement_a = tokio::time::timeout(Duration::from_secs(1), waiting_a)
3378            .await
3379            .unwrap()
3380            .unwrap()
3381            .unwrap();
3382        assert_eq!(replacement_a.classification(), Classification::A);
3383    }
3384
3385    #[tokio::test]
3386    async fn test_idle_worker_invalid_for_primary_classification_is_replaced() {
3387        let pool = new_limited_mutable_pool(1, 1);
3388        let worker_a = pool.get_classified_worker(Classification::A).await.unwrap();
3389        let valid = worker_a.valid.clone();
3390        drop(worker_a);
3391
3392        valid.store(false, Ordering::SeqCst);
3393
3394        let replacement_a = tokio::time::timeout(
3395            Duration::from_secs(1),
3396            pool.get_classified_worker(Classification::A),
3397        )
3398        .await
3399        .unwrap()
3400        .unwrap();
3401        assert_eq!(replacement_a.classification(), Classification::A);
3402    }
3403
3404    #[tokio::test]
3405    async fn test_capped_waiter_does_not_replace_other_classification_worker() {
3406        let pool = new_limited_mutable_pool(2, 1);
3407        let worker_a = pool.get_classified_worker(Classification::A).await.unwrap();
3408        let worker_b = pool.get_classified_worker(Classification::B).await.unwrap();
3409
3410        let pool_ref = pool.clone();
3411        let waiting_a =
3412            tokio::spawn(async move { pool_ref.get_classified_worker(Classification::A).await });
3413        wait_for_waiter(&pool).await;
3414
3415        drop(worker_b);
3416        tokio::task::yield_now().await;
3417
3418        {
3419            let state = pool.state.lock().unwrap();
3420            assert_eq!(state.current_count, 2);
3421            assert_eq!(
3422                state.worker_list.values().map(VecDeque::len).sum::<usize>(),
3423                1
3424            );
3425            assert_eq!(
3426                state
3427                    .worker_list
3428                    .get(&Classification::B)
3429                    .and_then(VecDeque::front)
3430                    .unwrap()
3431                    .primary_classification,
3432                Classification::B
3433            );
3434        }
3435        assert!(!waiting_a.is_finished());
3436
3437        drop(worker_a);
3438        let replacement_a = tokio::time::timeout(Duration::from_secs(1), waiting_a)
3439            .await
3440            .unwrap()
3441            .unwrap()
3442            .unwrap();
3443        assert_eq!(replacement_a.classification(), Classification::A);
3444    }
3445
3446    #[tokio::test]
3447    async fn test_changed_classification_worker_is_removed_and_generic_waiter_retries() {
3448        let pool = new_mutable_pool(1, None);
3449        let mut worker = pool.get_classified_worker(Classification::A).await.unwrap();
3450        worker.classification = Classification::B;
3451
3452        let pool_ref = pool.clone();
3453        let waiter = tokio::spawn(async move { pool_ref.get_worker().await });
3454        wait_for_waiter(&pool).await;
3455        drop(worker);
3456
3457        let worker = waiter.await.unwrap().unwrap();
3458        assert_eq!(worker.classification(), Classification::A);
3459        worker.working.store(false, Ordering::SeqCst);
3460        drop(worker);
3461        assert_accounting_empty(&pool);
3462    }
3463
3464    #[tokio::test]
3465    async fn test_cached_classification_is_used_while_clearing() {
3466        let pool = new_mutable_pool(1, None);
3467        let mut worker = pool.get_classified_worker(Classification::A).await.unwrap();
3468        worker.classification = Classification::B;
3469
3470        let pool_ref = pool.clone();
3471        let clear_task = tokio::spawn(async move { pool_ref.clear_all_worker().await });
3472        loop {
3473            if pool.state.lock().unwrap().clearing {
3474                break;
3475            }
3476            tokio::task::yield_now().await;
3477        }
3478        drop(worker);
3479        clear_task.await.unwrap();
3480        assert_accounting_empty(&pool);
3481    }
3482
3483    #[tokio::test]
3484    async fn test_changed_classification_worker_does_not_wake_other_waiter() {
3485        let pool = new_mutable_pool(1, None);
3486        let mut worker_a = pool.get_classified_worker(Classification::A).await.unwrap();
3487        let worker_b = pool.get_classified_worker(Classification::B).await.unwrap();
3488        worker_a.classification = Classification::C;
3489
3490        let pool_ref = pool.clone();
3491        let waiter =
3492            tokio::spawn(async move { pool_ref.get_classified_worker(Classification::B).await });
3493        wait_for_waiter(&pool).await;
3494        drop(worker_a);
3495        tokio::task::yield_now().await;
3496        assert!(!waiter.is_finished());
3497
3498        drop(worker_b);
3499        let replacement_b = waiter.await.unwrap().unwrap();
3500        assert_only_classification(&pool, Classification::B, 1);
3501        replacement_b.working.store(false, Ordering::SeqCst);
3502        drop(replacement_b);
3503        assert_accounting_empty(&pool);
3504    }
3505
3506    #[tokio::test]
3507    async fn test_changed_classification_worker_is_removed_independently() {
3508        let pool = new_mutable_pool(1, None);
3509        let worker_a = pool.get_classified_worker(Classification::A).await.unwrap();
3510        let mut worker_b = pool.get_classified_worker(Classification::B).await.unwrap();
3511        worker_b.classification = Classification::C;
3512        drop(worker_b);
3513
3514        assert_only_classification(&pool, Classification::A, 1);
3515        worker_a.working.store(false, Ordering::SeqCst);
3516        drop(worker_a);
3517        assert_accounting_empty(&pool);
3518    }
3519
3520    #[tokio::test]
3521    async fn test_max_count_per_classification_blocks_only_that_classification() {
3522        let pool = new_limited_mutable_pool(4, 2);
3523        let worker_a1 = pool.get_classified_worker(Classification::A).await.unwrap();
3524        let worker_a2 = pool.get_classified_worker(Classification::A).await.unwrap();
3525
3526        let pool_ref = pool.clone();
3527        let waiting_a =
3528            tokio::spawn(async move { pool_ref.get_classified_worker(Classification::A).await });
3529        wait_for_waiter(&pool).await;
3530        assert!(!waiting_a.is_finished());
3531
3532        let worker_b = pool.get_classified_worker(Classification::B).await.unwrap();
3533        {
3534            let state = pool.state.lock().unwrap();
3535            assert_eq!(state.current_count, 3);
3536            assert_eq!(state.reserved_classified_count(&Classification::A), 2);
3537            assert_eq!(state.reserved_classified_count(&Classification::B), 1);
3538        }
3539
3540        drop(worker_a1);
3541        let replacement_a = tokio::time::timeout(Duration::from_secs(1), waiting_a)
3542            .await
3543            .unwrap()
3544            .unwrap()
3545            .unwrap();
3546        assert_eq!(replacement_a.classification(), Classification::A);
3547
3548        drop(worker_a2);
3549        drop(replacement_a);
3550        drop(worker_b);
3551    }
3552
3553    #[tokio::test]
3554    async fn test_generic_get_does_not_exceed_classification_limit() {
3555        let pool = new_limited_mutable_pool(3, 1);
3556        let worker_a = pool.get_classified_worker(Classification::A).await.unwrap();
3557
3558        let pool_ref = pool.clone();
3559        let generic = tokio::spawn(async move { pool_ref.get_worker().await });
3560        wait_for_waiter(&pool).await;
3561        assert!(!generic.is_finished());
3562        {
3563            let state = pool.state.lock().unwrap();
3564            assert_eq!(state.current_count, 1);
3565            assert_eq!(state.reserved_classified_count(&Classification::A), 1);
3566        }
3567
3568        let worker_b = pool.get_classified_worker(Classification::B).await.unwrap();
3569        drop(worker_b);
3570        let generic_worker = tokio::time::timeout(Duration::from_secs(1), generic)
3571            .await
3572            .unwrap()
3573            .unwrap()
3574            .unwrap();
3575        assert_eq!(generic_worker.classification(), Classification::B);
3576
3577        drop(worker_a);
3578        drop(generic_worker);
3579    }
3580
3581    #[tokio::test]
3582    async fn test_zero_max_count_per_classification_returns_error() {
3583        let pool = new_limited_mutable_pool(1, 0);
3584
3585        let generic_error = pool.get_worker().await.err().unwrap();
3586        assert_eq!(generic_error.code(), crate::PoolErrorCode::InvalidConfig);
3587
3588        let classified_error = pool
3589            .get_classified_worker(Classification::A)
3590            .await
3591            .err()
3592            .unwrap();
3593        assert_eq!(classified_error.code(), crate::PoolErrorCode::InvalidConfig);
3594    }
3595}