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