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