use std::time::Duration;
use super::declarations::QueueDeclaration;
use super::taxonomy::QueueServiceReason;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PoolCensus {
pub workers_in_pool: usize,
pub workers_serving_activity: usize,
pub compatible_workers: usize,
pub eligible_compatible_workers: usize,
pub compatible_workers_reachability_lost: usize,
pub compatible_workers_at_capacity: usize,
pub compatible_workers_capacity_unannounced: usize,
pub last_compatible_poller_age: Option<Duration>,
}
impl PoolCensus {
#[must_use]
pub const fn is_served(&self) -> bool {
self.compatible_workers > 0
}
#[must_use]
pub const fn will_be_served(&self) -> bool {
if self.eligible_compatible_workers > 0 {
return true;
}
self.compatible_workers > 0
&& self.compatible_workers_reachability_lost == 0
&& self.compatible_workers_capacity_unannounced == 0
}
}
#[must_use]
pub fn classify(declaration: QueueDeclaration, census: &PoolCensus) -> Option<QueueServiceReason> {
if census.eligible_compatible_workers > 0 {
return None;
}
if declaration == QueueDeclaration::NotDeclared {
return Some(QueueServiceReason::NoQueueDeclaration);
}
if census.compatible_workers > 0 {
if census.compatible_workers_reachability_lost > 0 {
return Some(QueueServiceReason::PollersUnreachable);
}
if census.compatible_workers_capacity_unannounced > 0 {
return Some(QueueServiceReason::PollersCapacityUnannounced);
}
if census.compatible_workers_at_capacity == census.compatible_workers {
return Some(QueueServiceReason::PollersAtCapacity);
}
return None;
}
if census.workers_in_pool > 0 {
return Some(QueueServiceReason::PollersIncompatible);
}
Some(QueueServiceReason::NoLivePollers)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_served_address_is_not_classified() {
let census = PoolCensus {
workers_in_pool: 1,
workers_serving_activity: 1,
compatible_workers: 1,
eligible_compatible_workers: 1,
compatible_workers_reachability_lost: 0,
last_compatible_poller_age: Some(Duration::ZERO),
compatible_workers_at_capacity: 0,
compatible_workers_capacity_unannounced: 0,
};
assert_eq!(classify(QueueDeclaration::Declared, &census), None);
assert_eq!(classify(QueueDeclaration::NotDeclared, &census), None);
}
#[test]
fn an_undeclared_queue_is_structural() {
assert_eq!(
classify(QueueDeclaration::NotDeclared, &PoolCensus::default()),
Some(QueueServiceReason::NoQueueDeclaration)
);
}
#[test]
fn an_empty_pool_on_a_declared_queue_has_no_live_pollers() {
assert_eq!(
classify(QueueDeclaration::Declared, &PoolCensus::default()),
Some(QueueServiceReason::NoLivePollers)
);
}
#[test]
fn an_unknowable_declaration_never_produces_a_structural_refusal() {
assert_eq!(
classify(QueueDeclaration::Unknown, &PoolCensus::default()),
Some(QueueServiceReason::NoLivePollers)
);
}
#[test]
fn a_pool_whose_every_worker_is_full_reports_at_capacity() {
let census = PoolCensus {
workers_in_pool: 2,
workers_serving_activity: 2,
compatible_workers: 2,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
compatible_workers_at_capacity: 2,
compatible_workers_capacity_unannounced: 0,
last_compatible_poller_age: Some(Duration::ZERO),
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersAtCapacity)
);
assert_eq!(
classify(QueueDeclaration::Declared, &PoolCensus::default()),
Some(QueueServiceReason::NoLivePollers)
);
assert!(census.will_be_served());
}
#[test]
fn a_pool_whose_workers_have_not_announced_capacity_is_not_reported_as_busy() {
let census = PoolCensus {
workers_in_pool: 2,
workers_serving_activity: 2,
compatible_workers: 2,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
compatible_workers_at_capacity: 0,
compatible_workers_capacity_unannounced: 2,
last_compatible_poller_age: Some(Duration::ZERO),
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersCapacityUnannounced)
);
assert_ne!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersAtCapacity),
"an unannounced pool must never wear the busy pool's name: their remedies differ"
);
assert!(
!census.will_be_served(),
"an all-unannounced pool is not going to serve anything by itself"
);
}
#[test]
fn an_unannounced_worker_outranks_a_busy_one() {
let census = PoolCensus {
workers_in_pool: 3,
workers_serving_activity: 3,
compatible_workers: 3,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
compatible_workers_at_capacity: 2,
compatible_workers_capacity_unannounced: 1,
last_compatible_poller_age: Some(Duration::ZERO),
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersCapacityUnannounced)
);
}
#[test]
fn a_busy_pool_with_nobody_unannounced_still_reports_at_capacity() {
let census = PoolCensus {
workers_in_pool: 2,
workers_serving_activity: 2,
compatible_workers: 2,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
compatible_workers_at_capacity: 2,
compatible_workers_capacity_unannounced: 0,
last_compatible_poller_age: Some(Duration::ZERO),
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersAtCapacity)
);
}
#[test]
fn lost_reachability_outranks_capacity() {
let census = PoolCensus {
workers_in_pool: 2,
workers_serving_activity: 2,
compatible_workers: 2,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 1,
compatible_workers_at_capacity: 2,
compatible_workers_capacity_unannounced: 0,
last_compatible_poller_age: Some(Duration::ZERO),
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersUnreachable)
);
}
#[test]
fn a_pool_only_partly_at_capacity_is_not_reported() {
let census = PoolCensus {
workers_in_pool: 2,
workers_serving_activity: 2,
compatible_workers: 2,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
compatible_workers_at_capacity: 1,
compatible_workers_capacity_unannounced: 0,
last_compatible_poller_age: Some(Duration::ZERO),
};
assert_eq!(classify(QueueDeclaration::Declared, &census), None);
}
fn pool(compatible: usize, eligible: usize, lost: usize) -> PoolCensus {
PoolCensus {
workers_in_pool: compatible,
workers_serving_activity: compatible,
compatible_workers: compatible,
eligible_compatible_workers: eligible,
compatible_workers_reachability_lost: lost,
last_compatible_poller_age: Some(Duration::ZERO),
compatible_workers_at_capacity: 0,
compatible_workers_capacity_unannounced: 0,
}
}
#[test]
fn a_pool_excluded_only_by_an_opening_probation_is_not_reported() {
assert_eq!(classify(QueueDeclaration::Declared, &pool(2, 0, 0)), None);
}
#[test]
fn a_pool_that_lost_reachability_is_reported() {
assert_eq!(
classify(QueueDeclaration::Declared, &pool(2, 0, 2)),
Some(QueueServiceReason::PollersUnreachable)
);
}
#[test]
fn the_exclusion_cause_is_what_decides() {
let probation = pool(2, 0, 0);
let unreachable = pool(2, 0, 2);
assert_eq!(
probation.workers_in_pool, unreachable.workers_in_pool,
"precondition: the two pools differ ONLY in the exclusion cause"
);
assert_eq!(probation.compatible_workers, unreachable.compatible_workers);
assert_eq!(
probation.eligible_compatible_workers,
unreachable.eligible_compatible_workers
);
assert_ne!(
classify(QueueDeclaration::Declared, &probation),
classify(QueueDeclaration::Declared, &unreachable),
"a pool serving its probation and a pool that lost reachability must not get the \
same answer: one clears itself in seconds and the other never does, and the \
operator's remedies are different"
);
}
#[test]
fn a_genuinely_empty_pool_is_still_reported_as_having_no_pollers() {
assert_eq!(
classify(QueueDeclaration::Declared, &PoolCensus::default()),
Some(QueueServiceReason::NoLivePollers)
);
}
#[test]
fn workers_that_do_not_serve_the_activity_are_still_incompatible_not_unreachable() {
let census = PoolCensus {
workers_in_pool: 3,
workers_serving_activity: 0,
compatible_workers: 0,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
last_compatible_poller_age: None,
compatible_workers_at_capacity: 0,
compatible_workers_capacity_unannounced: 0,
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersIncompatible)
);
}
#[test]
fn one_eligible_worker_means_the_address_is_served() {
assert_eq!(classify(QueueDeclaration::Declared, &pool(3, 1, 2)), None);
}
#[test]
fn pollers_that_do_not_cover_the_activity_are_incompatible() {
let census = PoolCensus {
workers_in_pool: 2,
workers_serving_activity: 0,
compatible_workers: 0,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
last_compatible_poller_age: None,
compatible_workers_at_capacity: 0,
compatible_workers_capacity_unannounced: 0,
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersIncompatible)
);
}
#[test]
fn pollers_off_the_pinned_node_are_incompatible() {
let census = PoolCensus {
workers_in_pool: 1,
workers_serving_activity: 1,
compatible_workers: 0,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
last_compatible_poller_age: Some(Duration::from_secs(9)),
compatible_workers_at_capacity: 0,
compatible_workers_capacity_unannounced: 0,
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersIncompatible)
);
}
}