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>>
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 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}