use crate::router::BackendCount;
use crate::types::BackendId;
pub const MAX_BACKENDS: usize = BackendId::MAX_COUNT;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendStatus {
Unknown,
Missing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ArticleAvailability {
missing: usize,
}
impl ArticleAvailability {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self { missing: 0 }
}
#[inline]
pub fn record_missing(&mut self, backend_id: BackendId) -> &mut Self {
let mask = backend_id.availability_bit();
self.missing |= mask; self
}
#[inline]
#[must_use]
pub fn is_missing(&self, backend_id: BackendId) -> bool {
self.missing & backend_id.availability_bit() != 0
}
#[inline]
#[must_use]
pub(crate) fn should_try(&self, backend_id: BackendId) -> bool {
!self.is_missing(backend_id)
}
#[inline]
#[must_use]
pub const fn missing_bits(&self) -> usize {
self.missing
}
#[inline]
#[must_use]
pub fn all_exhausted(&self, backend_count: BackendCount) -> bool {
let expected_missing = match backend_count.get() {
0 => 0,
MAX_BACKENDS => usize::MAX,
n => (1usize << n) - 1,
};
self.missing & expected_missing == expected_missing
}
#[inline]
#[must_use]
pub(crate) const fn from_missing_bits(missing: usize) -> Self {
Self { missing }
}
#[inline]
#[must_use]
pub const fn has_availability_info(&self) -> bool {
self.missing != 0
}
#[inline]
#[must_use]
pub fn status(&self, backend_id: BackendId) -> BackendStatus {
let mask = backend_id.availability_bit();
if self.missing & mask != 0 {
BackendStatus::Missing
} else {
BackendStatus::Unknown
}
}
}
impl Default for ArticleAvailability {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::router::BackendCount;
fn backend_count(count: usize) -> BackendCount {
BackendCount::try_new(count).expect("test backend count fits availability bitmap")
}
use crate::types::BackendId;
#[test]
fn test_backend_availability_basic() {
let mut avail = ArticleAvailability::new();
let b0 = BackendId::from_index(0);
let b1 = BackendId::from_index(1);
assert!(avail.should_try(b0));
assert!(avail.should_try(b1));
avail.record_missing(b0);
assert!(!avail.should_try(b0)); assert!(avail.should_try(b1));
avail.record_missing(b1);
assert!(!avail.should_try(b1));
}
#[test]
fn missing_backend_is_not_eligible() {
let mut avail = ArticleAvailability::new();
let b0 = BackendId::from_index(0);
avail.record_missing(b0);
assert!(avail.is_missing(b0));
assert!(avail.is_missing(b0));
}
#[test]
fn success_observation_does_not_change_availability() {
let mut cache_state = ArticleAvailability::new();
let b0 = BackendId::from_index(0);
let b1 = BackendId::from_index(1);
cache_state.record_missing(b0);
cache_state.record_missing(b1);
assert!(cache_state.is_missing(b0));
assert!(cache_state.is_missing(b1));
let fresh = ArticleAvailability::new();
assert!(!fresh.is_missing(b0));
assert_eq!(cache_state.missing_bits(), 0b11);
}
#[test]
fn test_backend_availability_all_exhausted() {
let mut avail = ArticleAvailability::new();
assert!(!avail.all_exhausted(backend_count(2)));
assert!(!avail.all_exhausted(backend_count(3)));
avail.record_missing(BackendId::from_index(0));
avail.record_missing(BackendId::from_index(1));
assert!(avail.all_exhausted(backend_count(2)));
assert!(!avail.all_exhausted(backend_count(3)));
}
}