use std::{
collections::{HashMap, HashSet},
time::{Duration, Instant},
};
use crate::options::{PartitionFailoverOptions, Region};
use super::{partition_key_range_id::PartitionKeyRangeId, CosmosEndpoint};
#[derive(Clone, Debug)]
pub(crate) struct PartitionEndpointState {
pub failover_overrides: HashMap<PartitionKeyRangeId, PartitionFailoverEntry>,
pub circuit_breaker_overrides: HashMap<PartitionKeyRangeId, PartitionFailoverEntry>,
pub per_partition_automatic_failover_enabled: bool,
pub per_partition_circuit_breaker_enabled: bool,
pub consecutive_hedge_wins: HashMap<(PartitionKeyRangeId, Option<Region>), u32>,
pub config: PartitionFailoverOptions,
#[cfg(test)]
pub(crate) _test_canary: Option<std::sync::Arc<()>>,
}
impl PartitionEndpointState {
pub fn new(config: PartitionFailoverOptions) -> Self {
let per_partition_circuit_breaker_enabled = config
.circuit_breaker_enabled_override()
.unwrap_or_else(|| config.circuit_breaker_enabled());
Self {
per_partition_circuit_breaker_enabled,
failover_overrides: HashMap::new(),
circuit_breaker_overrides: HashMap::new(),
consecutive_hedge_wins: HashMap::new(),
per_partition_automatic_failover_enabled: false,
config,
#[cfg(test)]
_test_canary: None,
}
}
}
impl Default for PartitionEndpointState {
fn default() -> Self {
Self::new(PartitionFailoverOptions::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum HealthStatus {
Unhealthy,
ProbeCandidate,
}
#[derive(Clone, Debug)]
pub(crate) struct PartitionFailoverEntry {
pub current_endpoint: CosmosEndpoint,
pub first_failed_endpoint: CosmosEndpoint,
pub failed_endpoints: HashSet<CosmosEndpoint>,
pub read_failure_count: i32,
pub write_failure_count: i32,
pub first_failure_time: Instant,
pub last_failure_time: Instant,
pub health_status: HealthStatus,
pub failback_jitter: Duration,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_partition_state() {
let state = PartitionEndpointState::default();
assert!(state.failover_overrides.is_empty());
assert!(state.circuit_breaker_overrides.is_empty());
assert!(state.consecutive_hedge_wins.is_empty());
assert!(!state.per_partition_automatic_failover_enabled);
assert!(state.per_partition_circuit_breaker_enabled);
assert!(state.config.circuit_breaker_enabled());
}
#[test]
fn new_propagates_circuit_breaker_enabled_to_state_flag() {
let opts = PartitionFailoverOptions::builder()
.with_circuit_breaker_enabled(true)
.build()
.unwrap();
let state = PartitionEndpointState::new(opts);
assert!(state.per_partition_circuit_breaker_enabled);
assert!(state.config.circuit_breaker_enabled());
}
#[test]
fn new_override_off_wins_over_enabled_base_option() {
let opts = PartitionFailoverOptions::builder()
.with_circuit_breaker_enabled(true)
.with_circuit_breaker_enabled_override(false)
.build()
.unwrap();
let state = PartitionEndpointState::new(opts);
assert!(!state.per_partition_circuit_breaker_enabled);
}
#[test]
fn new_override_on_wins_over_disabled_base_option() {
let opts = PartitionFailoverOptions::builder()
.with_circuit_breaker_enabled(false)
.with_circuit_breaker_enabled_override(true)
.build()
.unwrap();
let state = PartitionEndpointState::new(opts);
assert!(state.per_partition_circuit_breaker_enabled);
}
}