use std::{
collections::HashMap,
hash::{Hash, Hasher},
sync::Arc,
time::{Duration, Instant, SystemTime},
};
use tracing::warn;
use crate::driver::cache::AccountProperties;
use crate::options::{PartitionFailoverOptions, Region};
use super::{
partition_endpoint_state::{HealthStatus, PartitionEndpointState, PartitionFailoverEntry},
partition_key_range_id::PartitionKeyRangeId,
AccountEndpointState, CosmosEndpoint, UnavailablePartition, UnavailableReason,
};
pub(crate) fn build_account_endpoint_state(
properties: &AccountProperties,
default_endpoint: CosmosEndpoint,
previous_generation: Option<u64>,
gateway20_enabled: bool,
preferred_regions: &[Region],
) -> AccountEndpointState {
let generation = previous_generation.map_or(0, |g| g.saturating_add(1));
let mut preferred_read_endpoints = build_preferred_endpoints(
&properties.readable_locations,
&properties.thin_client_readable_locations,
gateway20_enabled,
);
let mut preferred_write_endpoints = build_preferred_endpoints(
&properties.writable_locations,
&properties.thin_client_writable_locations,
gateway20_enabled,
);
if !preferred_regions.is_empty() {
preferred_read_endpoints =
reorder_by_preferred_regions(preferred_read_endpoints, preferred_regions);
preferred_write_endpoints =
reorder_by_preferred_regions(preferred_write_endpoints, preferred_regions);
}
if preferred_read_endpoints.is_empty() {
preferred_read_endpoints.push(default_endpoint.clone());
}
if preferred_write_endpoints.is_empty() {
preferred_write_endpoints.push(default_endpoint.clone());
}
AccountEndpointState {
generation,
preferred_read_endpoints: preferred_read_endpoints.into(),
preferred_write_endpoints: preferred_write_endpoints.into(),
unavailable_endpoints: Default::default(),
multiple_write_locations_enabled: properties.enable_multiple_write_locations,
default_endpoint,
}
}
fn build_preferred_endpoints(
standard_locations: &[crate::driver::cache::AccountRegion],
thin_client_locations: &[crate::driver::cache::AccountRegion],
gateway20_enabled: bool,
) -> Vec<CosmosEndpoint> {
let thin_client_urls = if gateway20_enabled {
parse_thin_client_locations(thin_client_locations)
} else {
HashMap::new()
};
let mut endpoints = Vec::with_capacity(standard_locations.len());
for region in standard_locations {
let url = region.database_account_endpoint.url().clone();
let endpoint = thin_client_urls
.get(®ion.name)
.cloned()
.map(|gateway20_url| {
CosmosEndpoint::regional_with_gateway20(
region.name.clone(),
url.clone(),
gateway20_url,
)
})
.unwrap_or_else(|| CosmosEndpoint::regional(region.name.clone(), url));
endpoints.push(endpoint);
}
endpoints
}
fn parse_thin_client_locations(
thin_client_locations: &[crate::driver::cache::AccountRegion],
) -> HashMap<crate::options::Region, url::Url> {
let mut urls = HashMap::new();
for region in thin_client_locations {
let url = region.database_account_endpoint.url().clone();
if url.scheme() != "https" {
warn!(
region = %region.name,
endpoint = %region.database_account_endpoint,
scheme = url.scheme(),
"Ignoring non-HTTPS thin-client endpoint URL"
);
continue;
}
urls.entry(region.name.clone())
.and_modify(|existing| {
if existing != &url {
warn!(
region = %region.name,
existing_url = %existing,
new_url = %url,
"Duplicate thin-client region with conflicting URL; keeping first entry"
);
}
})
.or_insert(url);
}
urls
}
fn reorder_by_preferred_regions(
endpoints: Vec<CosmosEndpoint>,
preferred_regions: &[Region],
) -> Vec<CosmosEndpoint> {
let mut ordered = Vec::with_capacity(endpoints.len());
let mut remaining: Vec<Option<CosmosEndpoint>> = endpoints.into_iter().map(Some).collect();
for region in preferred_regions {
for slot in remaining.iter_mut() {
if let Some(ep) = slot {
if ep.region().is_some_and(|r| r == region) {
ordered.push(slot.take().unwrap());
break;
}
}
}
}
for ep in remaining.into_iter().flatten() {
ordered.push(ep);
}
ordered
}
pub(crate) fn mark_endpoint_unavailable(
state: &AccountEndpointState,
endpoint: &CosmosEndpoint,
reason: UnavailableReason,
) -> AccountEndpointState {
let mut unavailable = state.unavailable_endpoints.clone();
unavailable.insert(endpoint.url().clone(), (Instant::now(), reason));
AccountEndpointState {
generation: state.generation,
preferred_read_endpoints: Arc::clone(&state.preferred_read_endpoints),
preferred_write_endpoints: Arc::clone(&state.preferred_write_endpoints),
unavailable_endpoints: unavailable,
multiple_write_locations_enabled: state.multiple_write_locations_enabled,
default_endpoint: state.default_endpoint.clone(),
}
}
#[allow(dead_code)] pub(crate) fn expire_unavailable_endpoints(
state: &AccountEndpointState,
now: Instant,
expiry_duration: Duration,
) -> AccountEndpointState {
if state.unavailable_endpoints.is_empty() {
return state.clone();
}
let mut unavailable = state.unavailable_endpoints.clone();
unavailable
.retain(|_, (marked_at, _)| now.saturating_duration_since(*marked_at) < expiry_duration);
AccountEndpointState {
generation: state.generation,
preferred_read_endpoints: Arc::clone(&state.preferred_read_endpoints),
preferred_write_endpoints: Arc::clone(&state.preferred_write_endpoints),
unavailable_endpoints: unavailable,
multiple_write_locations_enabled: state.multiple_write_locations_enabled,
default_endpoint: state.default_endpoint.clone(),
}
}
pub(crate) fn is_eligible_for_ppaf(
partition_state: &PartitionEndpointState,
account_state: &AccountEndpointState,
is_read_only: bool,
is_partitioned_resource: bool,
) -> bool {
partition_state.per_partition_automatic_failover_enabled
&& is_partitioned_resource
&& !is_read_only
&& !account_state.multiple_write_locations_enabled
&& account_state.preferred_read_endpoints.len() > 1
}
pub(crate) fn is_eligible_for_ppcb(
partition_state: &PartitionEndpointState,
account_state: &AccountEndpointState,
is_read_only: bool,
is_partitioned_resource: bool,
) -> bool {
partition_state.per_partition_circuit_breaker_enabled
&& is_partitioned_resource
&& (is_read_only || account_state.multiple_write_locations_enabled)
&& account_state.preferred_read_endpoints.len() > 1
}
pub(crate) fn can_circuit_breaker_trigger_failover(
entry: &PartitionFailoverEntry,
is_read_only: bool,
config: &PartitionFailoverOptions,
) -> bool {
if is_read_only {
entry.read_failure_count >= config.read_failure_threshold() as i32
} else {
entry.write_failure_count >= config.write_failure_threshold() as i32
}
}
fn ppcb_failback_jitter(
unavailability_duration: Duration,
pk_range_id: &PartitionKeyRangeId,
) -> Duration {
let max_jitter_nanos = (unavailability_duration / 2).as_nanos() as u64;
if max_jitter_nanos == 0 {
return Duration::ZERO;
}
let now_nanos = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let mut hasher = std::collections::hash_map::DefaultHasher::new();
pk_range_id.as_str().hash(&mut hasher);
let pk_hash = hasher.finish();
let mut z = now_nanos ^ pk_hash;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^= z >> 31;
Duration::from_nanos(z % max_jitter_nanos)
}
pub(crate) fn mark_partition_unavailable(
current_state: &PartitionEndpointState,
account_state: &AccountEndpointState,
unavail: &UnavailablePartition,
is_partitioned_resource: bool,
) -> PartitionEndpointState {
let mut new_state = current_state.clone();
let now = Instant::now();
let Some(pk_range_id) = &unavail.partition_key_range_id else {
return new_state;
};
let is_read = unavail.is_read;
let failed_endpoint = match &unavail.region {
Some(region) => {
account_state
.preferred_read_endpoints
.iter()
.chain(account_state.preferred_write_endpoints.iter())
.find(|e| e.region().is_some_and(|r| r == region))
.cloned()
}
None => None,
};
let Some(failed_endpoint) = failed_endpoint else {
return new_state;
};
let next_endpoints = &account_state.preferred_read_endpoints;
if is_eligible_for_ppcb(
current_state,
account_state,
is_read,
is_partitioned_resource,
) {
let entry = new_state
.circuit_breaker_overrides
.entry(pk_range_id.clone())
.or_insert_with(|| PartitionFailoverEntry {
current_endpoint: failed_endpoint.clone(),
first_failed_endpoint: failed_endpoint.clone(),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: now,
last_failure_time: now,
health_status: HealthStatus::Unhealthy,
failback_jitter: ppcb_failback_jitter(
current_state.config.partition_unavailability_duration(),
pk_range_id,
),
});
if entry.health_status == HealthStatus::ProbeCandidate {
entry.health_status = HealthStatus::Unhealthy;
entry.read_failure_count = 0;
entry.write_failure_count = 0;
entry.current_endpoint = entry.first_failed_endpoint.clone();
entry.failed_endpoints.clear();
entry.first_failure_time = now;
entry.last_failure_time = now;
entry.failback_jitter = ppcb_failback_jitter(
current_state.config.partition_unavailability_duration(),
pk_range_id,
);
return new_state;
}
if now.saturating_duration_since(entry.last_failure_time)
> current_state.config.counter_reset_window()
{
entry.read_failure_count = 0;
entry.write_failure_count = 0;
}
if is_read {
entry.read_failure_count += 1;
} else {
entry.write_failure_count += 1;
}
entry.last_failure_time = now;
if !can_circuit_breaker_trigger_failover(entry, is_read, ¤t_state.config) {
return new_state;
}
if !try_move_next_endpoint(entry, next_endpoints, &failed_endpoint) {
new_state
.circuit_breaker_overrides
.remove(pk_range_id.as_str());
}
} else if is_eligible_for_ppaf(
current_state,
account_state,
is_read,
is_partitioned_resource,
) {
let entry = new_state
.failover_overrides
.entry(pk_range_id.clone())
.or_insert_with(|| PartitionFailoverEntry {
current_endpoint: failed_endpoint.clone(),
first_failed_endpoint: failed_endpoint.clone(),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: now,
last_failure_time: now,
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
});
entry.last_failure_time = now;
if !try_move_next_endpoint(entry, next_endpoints, &failed_endpoint) {
new_state.failover_overrides.remove(pk_range_id.as_str());
}
}
new_state
}
fn try_move_next_endpoint(
entry: &mut PartitionFailoverEntry,
next_endpoints: &[CosmosEndpoint],
failed_endpoint: &CosmosEndpoint,
) -> bool {
if *failed_endpoint != entry.current_endpoint {
return true;
}
entry.failed_endpoints.insert(failed_endpoint.clone());
for candidate in next_endpoints {
if candidate == &entry.current_endpoint {
continue;
}
if entry.failed_endpoints.contains(candidate) {
continue;
}
entry.current_endpoint = candidate.clone();
return true;
}
false
}
pub(crate) fn expire_partition_overrides(
state: &PartitionEndpointState,
now: Instant,
unavailability_duration: Duration,
) -> PartitionEndpointState {
let mut new_state = state.clone();
for entry in new_state.circuit_breaker_overrides.values_mut() {
if entry.health_status == HealthStatus::Unhealthy
&& now.saturating_duration_since(entry.first_failure_time)
>= unavailability_duration + entry.failback_jitter
{
entry.health_status = HealthStatus::ProbeCandidate;
}
}
new_state
}
pub(crate) fn remove_probe_succeeded_entry(
state: &PartitionEndpointState,
partition_key_range_id: &PartitionKeyRangeId,
) -> PartitionEndpointState {
let mut new_state = state.clone();
new_state
.circuit_breaker_overrides
.remove(partition_key_range_id.as_str());
new_state
}
pub(crate) fn record_hedge_alternate_win(
state: &PartitionEndpointState,
account_state: &AccountEndpointState,
partition_key_range_id: &PartitionKeyRangeId,
primary_region: Option<&Region>,
) -> PartitionEndpointState {
let mut new_state = state.clone();
let key = (partition_key_range_id.clone(), primary_region.cloned());
let count = new_state
.consecutive_hedge_wins
.entry(key.clone())
.or_insert(0);
*count = count.saturating_add(1);
let triggered = *count >= state.config.consecutive_hedge_win_threshold();
if !triggered {
return new_state;
}
if let Some(existing) = new_state
.circuit_breaker_overrides
.get(partition_key_range_id.as_str())
{
if existing.health_status == HealthStatus::Unhealthy {
new_state.consecutive_hedge_wins.remove(&key);
return new_state;
}
}
let primary_endpoint = primary_region.and_then(|region| {
account_state
.preferred_read_endpoints
.iter()
.chain(account_state.preferred_write_endpoints.iter())
.find(|e| e.region().is_some_and(|r| r == region))
.cloned()
});
let Some(primary_endpoint) = primary_endpoint else {
return new_state;
};
let now = Instant::now();
let mut entry = PartitionFailoverEntry {
current_endpoint: primary_endpoint.clone(),
first_failed_endpoint: primary_endpoint.clone(),
failed_endpoints: Default::default(),
read_failure_count: state.config.read_failure_threshold() as i32,
write_failure_count: state.config.write_failure_threshold() as i32,
first_failure_time: now,
last_failure_time: now,
health_status: HealthStatus::Unhealthy,
failback_jitter: ppcb_failback_jitter(
state.config.partition_unavailability_duration(),
partition_key_range_id,
),
};
let next_endpoints = &account_state.preferred_read_endpoints;
if !try_move_next_endpoint(&mut entry, next_endpoints, &primary_endpoint) {
return new_state;
}
new_state.consecutive_hedge_wins.remove(&key);
new_state
.circuit_breaker_overrides
.insert(partition_key_range_id.clone(), entry);
new_state
}
pub(crate) fn record_hedge_primary_win(
state: &PartitionEndpointState,
partition_key_range_id: &PartitionKeyRangeId,
primary_region: Option<&Region>,
) -> PartitionEndpointState {
let key = (partition_key_range_id.clone(), primary_region.cloned());
if !state.consecutive_hedge_wins.contains_key(&key) {
return state.clone();
}
let mut new_state = state.clone();
new_state.consecutive_hedge_wins.remove(&key);
new_state
}
#[cfg(test)]
mod tests {
use super::*;
use crate::driver::cache::AccountProperties;
use crate::options::Region;
use std::collections::HashSet;
fn default_endpoint() -> CosmosEndpoint {
CosmosEndpoint::global(url::Url::parse("https://test.documents.azure.com:443/").unwrap())
}
fn test_properties() -> AccountProperties {
serde_json::from_value(serde_json::json!({
"_self": "",
"id": "test",
"_rid": "test.documents.azure.com",
"media": "//media/",
"addresses": "//addresses/",
"_dbs": "//dbs/",
"writableLocations": [{ "name": "eastus", "databaseAccountEndpoint": "https://test-eastus.documents.azure.com:443/" }],
"readableLocations": [{ "name": "westus2", "databaseAccountEndpoint": "https://test-westus2.documents.azure.com:443/" }],
"enableMultipleWriteLocations": true,
"userReplicationPolicy": { "minReplicaSetSize": 3, "maxReplicasetSize": 4 },
"userConsistencyPolicy": { "defaultConsistencyLevel": "Session" },
"systemReplicationPolicy": { "minReplicaSetSize": 3, "maxReplicasetSize": 4 },
"readPolicy": { "primaryReadCoefficient": 1, "secondaryReadCoefficient": 1 },
"queryEngineConfiguration": "{}"
}))
.unwrap()
}
#[test]
fn build_state_uses_metadata_locations() {
let state =
build_account_endpoint_state(&test_properties(), default_endpoint(), None, false, &[]);
assert_eq!(state.generation, 0);
assert_eq!(state.preferred_write_endpoints.len(), 1);
assert_eq!(state.preferred_read_endpoints.len(), 1);
assert!(state.multiple_write_locations_enabled);
}
#[test]
fn build_state_adds_gateway20_endpoint_when_enabled() {
let properties: AccountProperties = serde_json::from_value(serde_json::json!({
"_self": "",
"id": "test",
"_rid": "test.documents.azure.com",
"media": "//media/",
"addresses": "//addresses/",
"_dbs": "//dbs/",
"writableLocations": [{ "name": "eastus", "databaseAccountEndpoint": "https://test-eastus.documents.azure.com:443/" }],
"readableLocations": [{ "name": "westus2", "databaseAccountEndpoint": "https://test-westus2.documents.azure.com:443/" }],
"thinClientReadableLocations": [{ "name": "westus2", "databaseAccountEndpoint": "https://test-westus2-thin.documents.azure.com:444/" }],
"enableMultipleWriteLocations": true,
"userReplicationPolicy": { "minReplicaSetSize": 3, "maxReplicasetSize": 4 },
"userConsistencyPolicy": { "defaultConsistencyLevel": "Session" },
"systemReplicationPolicy": { "minReplicaSetSize": 3, "maxReplicasetSize": 4 },
"readPolicy": { "primaryReadCoefficient": 1, "secondaryReadCoefficient": 1 },
"queryEngineConfiguration": "{}"
}))
.unwrap();
let state = build_account_endpoint_state(&properties, default_endpoint(), None, true, &[]);
assert!(state.preferred_read_endpoints[0].gateway20_url().is_some());
assert!(state.preferred_write_endpoints[0].gateway20_url().is_none());
}
#[test]
fn build_state_adds_gateway20_for_write_endpoints_when_present() {
let properties: AccountProperties = serde_json::from_value(serde_json::json!({
"_self": "",
"id": "test",
"_rid": "test.documents.azure.com",
"media": "//media/",
"addresses": "//addresses/",
"_dbs": "//dbs/",
"writableLocations": [{ "name": "eastus", "databaseAccountEndpoint": "https://test-eastus.documents.azure.com:443/" }],
"readableLocations": [{ "name": "westus2", "databaseAccountEndpoint": "https://test-westus2.documents.azure.com:443/" }],
"thinClientReadableLocations": [{ "name": "westus2", "databaseAccountEndpoint": "https://test-westus2-thin.documents.azure.com:444/" }],
"thinClientWritableLocations": [{ "name": "eastus", "databaseAccountEndpoint": "https://test-eastus-thin.documents.azure.com:444/" }],
"enableMultipleWriteLocations": true,
"userReplicationPolicy": { "minReplicaSetSize": 3, "maxReplicasetSize": 4 },
"userConsistencyPolicy": { "defaultConsistencyLevel": "Session" },
"systemReplicationPolicy": { "minReplicaSetSize": 3, "maxReplicasetSize": 4 },
"readPolicy": { "primaryReadCoefficient": 1, "secondaryReadCoefficient": 1 },
"queryEngineConfiguration": "{}"
}))
.unwrap();
let state = build_account_endpoint_state(&properties, default_endpoint(), None, true, &[]);
assert!(state.preferred_read_endpoints[0].gateway20_url().is_some());
assert!(state.preferred_write_endpoints[0].gateway20_url().is_some());
}
#[test]
fn mark_and_expire_unavailable_endpoint() {
let state =
build_account_endpoint_state(&test_properties(), default_endpoint(), None, false, &[]);
let endpoint = state.preferred_read_endpoints[0].clone();
let marked =
mark_endpoint_unavailable(&state, &endpoint, UnavailableReason::TransportError);
assert_eq!(marked.unavailable_endpoints.len(), 1);
let expired = expire_unavailable_endpoints(
&marked,
Instant::now() + Duration::from_secs(61),
Duration::from_secs(60),
);
assert!(expired.unavailable_endpoints.is_empty());
}
fn regional_endpoint(name: &str) -> CosmosEndpoint {
CosmosEndpoint::regional(
Region::new(name.to_string()),
url::Url::parse(&format!("https://test-{name}.documents.azure.com:443/")).unwrap(),
)
}
fn single_master_account() -> AccountEndpointState {
AccountEndpointState {
generation: 0,
preferred_read_endpoints: vec![
regional_endpoint("eastus"),
regional_endpoint("westus"),
]
.into(),
preferred_write_endpoints: vec![regional_endpoint("eastus")].into(),
unavailable_endpoints: Default::default(),
multiple_write_locations_enabled: false,
default_endpoint: default_endpoint(),
}
}
fn multi_master_account() -> AccountEndpointState {
AccountEndpointState {
generation: 0,
preferred_read_endpoints: vec![
regional_endpoint("eastus"),
regional_endpoint("westus"),
]
.into(),
preferred_write_endpoints: vec![
regional_endpoint("eastus"),
regional_endpoint("westus"),
]
.into(),
unavailable_endpoints: Default::default(),
multiple_write_locations_enabled: true,
default_endpoint: default_endpoint(),
}
}
fn partition_state_with_ppaf_ppcb_enabled() -> PartitionEndpointState {
PartitionEndpointState {
per_partition_automatic_failover_enabled: true,
per_partition_circuit_breaker_enabled: true,
..PartitionEndpointState::default()
}
}
fn pk(s: &str) -> PartitionKeyRangeId {
s.parse().unwrap()
}
fn unavailable_partition(
pk_range_id: &str,
region: &str,
is_read: bool,
) -> UnavailablePartition {
UnavailablePartition {
partition_key_range_id: Some(pk(pk_range_id)),
region: Some(Region::new(region.to_string())),
is_read,
is_partitioned_resource: true,
}
}
#[test]
fn ppaf_eligible_for_write_on_single_master() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
assert!(is_eligible_for_ppaf(&ps, &account, false, true));
}
#[test]
fn ppaf_not_eligible_for_read() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
assert!(!is_eligible_for_ppaf(&ps, &account, true, true));
}
#[test]
fn ppaf_not_eligible_for_multi_master() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = multi_master_account();
assert!(!is_eligible_for_ppaf(&ps, &account, false, true));
}
#[test]
fn ppaf_not_eligible_when_disabled() {
let ps = PartitionEndpointState {
per_partition_automatic_failover_enabled: false,
..PartitionEndpointState::default()
};
let account = single_master_account();
assert!(!is_eligible_for_ppaf(&ps, &account, false, true));
}
#[test]
fn ppaf_not_eligible_for_non_partitioned_resource() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
assert!(!is_eligible_for_ppaf(&ps, &account, false, false));
}
#[test]
fn ppaf_not_eligible_with_single_read_endpoint() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = AccountEndpointState {
preferred_read_endpoints: vec![regional_endpoint("eastus")].into(),
..single_master_account()
};
assert!(!is_eligible_for_ppaf(&ps, &account, false, true));
}
#[test]
fn ppcb_eligible_for_read_on_single_master() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
assert!(is_eligible_for_ppcb(&ps, &account, true, true));
}
#[test]
fn ppcb_eligible_for_write_on_multi_master() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = multi_master_account();
assert!(is_eligible_for_ppcb(&ps, &account, false, true));
}
#[test]
fn ppcb_not_eligible_for_write_on_single_master() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
assert!(!is_eligible_for_ppcb(&ps, &account, false, true));
}
#[test]
fn ppcb_not_eligible_when_disabled() {
let ps = PartitionEndpointState {
per_partition_circuit_breaker_enabled: false,
..PartitionEndpointState::default()
};
let account = single_master_account();
assert!(!is_eligible_for_ppcb(&ps, &account, true, true));
}
#[test]
fn ppcb_not_eligible_for_non_partitioned_resource() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
assert!(!is_eligible_for_ppcb(&ps, &account, true, false));
}
#[test]
fn circuit_breaker_not_triggered_below_threshold() {
let config = PartitionFailoverOptions::default();
let entry = PartitionFailoverEntry {
current_endpoint: regional_endpoint("eastus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 1, write_failure_count: 0,
first_failure_time: Instant::now(),
last_failure_time: Instant::now(),
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
};
assert!(!can_circuit_breaker_trigger_failover(&entry, true, &config));
}
#[test]
fn circuit_breaker_triggered_above_read_threshold() {
let config = PartitionFailoverOptions::default();
let entry = PartitionFailoverEntry {
current_endpoint: regional_endpoint("eastus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 10, write_failure_count: 0,
first_failure_time: Instant::now(),
last_failure_time: Instant::now(),
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
};
assert!(can_circuit_breaker_trigger_failover(&entry, true, &config));
}
#[test]
fn circuit_breaker_triggered_above_write_threshold() {
let config = PartitionFailoverOptions::default();
let entry = PartitionFailoverEntry {
current_endpoint: regional_endpoint("eastus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 5, first_failure_time: Instant::now(),
last_failure_time: Instant::now(),
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
};
assert!(can_circuit_breaker_trigger_failover(&entry, false, &config));
}
#[test]
fn mark_partition_unavailable_ppaf_creates_entry() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
let unavail = unavailable_partition("pk-1", "eastus", false);
let result = mark_partition_unavailable(&ps, &account, &unavail, true);
assert_eq!(result.failover_overrides.len(), 1);
assert!(result.circuit_breaker_overrides.is_empty());
let entry = &result.failover_overrides["pk-1"];
assert_eq!(entry.current_endpoint, regional_endpoint("westus"));
assert!(entry
.failed_endpoints
.contains(®ional_endpoint("eastus")));
}
#[test]
fn mark_partition_unavailable_ppcb_increments_counter() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
let unavail = unavailable_partition("pk-1", "eastus", true);
let result = mark_partition_unavailable(&ps, &account, &unavail, true);
assert_eq!(result.circuit_breaker_overrides.len(), 1);
assert!(result.failover_overrides.is_empty());
let entry = &result.circuit_breaker_overrides["pk-1"];
assert_eq!(entry.read_failure_count, 1);
assert_eq!(entry.current_endpoint, regional_endpoint("eastus"));
}
#[test]
fn mark_partition_unavailable_ppcb_exceeds_threshold_moves_endpoint() {
let mut ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
ps.circuit_breaker_overrides.insert(
pk("pk-1"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("eastus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 9,
write_failure_count: 0,
first_failure_time: Instant::now(),
last_failure_time: Instant::now(),
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
},
);
let unavail = unavailable_partition("pk-1", "eastus", true);
let result = mark_partition_unavailable(&ps, &account, &unavail, true);
let entry = &result.circuit_breaker_overrides["pk-1"];
assert_eq!(entry.read_failure_count, 10);
assert_eq!(entry.current_endpoint, regional_endpoint("westus"));
}
#[test]
fn mark_partition_unavailable_all_endpoints_exhausted_removes_entry() {
let mut ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
let mut failed = HashSet::new();
failed.insert(regional_endpoint("westus"));
ps.failover_overrides.insert(
pk("pk-1"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("eastus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: failed,
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: Instant::now(),
last_failure_time: Instant::now(),
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
},
);
let unavail = unavailable_partition("pk-1", "eastus", false);
let result = mark_partition_unavailable(&ps, &account, &unavail, true);
assert!(result.failover_overrides.is_empty());
}
#[test]
fn mark_partition_unavailable_unknown_region_returns_unchanged() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
let unavail = unavailable_partition("pk-1", "nonexistent", false);
let result = mark_partition_unavailable(&ps, &account, &unavail, true);
assert!(result.failover_overrides.is_empty());
assert!(result.circuit_breaker_overrides.is_empty());
}
#[test]
fn mark_partition_unavailable_ppcb_counter_reset_after_window() {
let stale_time = Instant::now() - Duration::from_secs(10 * 60);
let mut ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
ps.circuit_breaker_overrides.insert(
pk("pk-1"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("eastus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 100,
write_failure_count: 100,
first_failure_time: stale_time,
last_failure_time: stale_time,
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
},
);
let unavail = unavailable_partition("pk-1", "eastus", true);
let result = mark_partition_unavailable(&ps, &account, &unavail, true);
let entry = &result.circuit_breaker_overrides["pk-1"];
assert_eq!(entry.read_failure_count, 1);
assert_eq!(entry.write_failure_count, 0);
}
#[test]
fn expire_partition_overrides_transitions_old_entries_to_probe_candidate() {
let old_time = Instant::now() - Duration::from_secs(60);
let recent_time = Instant::now();
let mut state = partition_state_with_ppaf_ppcb_enabled();
state.failover_overrides.insert(
pk("old-pk"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: old_time,
last_failure_time: old_time,
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
},
);
state.failover_overrides.insert(
pk("recent-pk"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: recent_time,
last_failure_time: recent_time,
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
},
);
state.circuit_breaker_overrides.insert(
pk("old-ppcb"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: old_time,
last_failure_time: old_time,
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
},
);
let expired = expire_partition_overrides(&state, Instant::now(), Duration::from_secs(30));
assert_eq!(expired.circuit_breaker_overrides.len(), 1);
assert_eq!(
expired.circuit_breaker_overrides["old-ppcb"].health_status,
HealthStatus::ProbeCandidate
);
assert_eq!(expired.failover_overrides.len(), 2);
assert_eq!(
expired.failover_overrides["old-pk"].health_status,
HealthStatus::Unhealthy
);
assert_eq!(
expired.failover_overrides["recent-pk"].health_status,
HealthStatus::Unhealthy
);
}
#[test]
fn expire_partition_overrides_keeps_all_when_none_expired() {
let recent = Instant::now();
let mut state = partition_state_with_ppaf_ppcb_enabled();
state.failover_overrides.insert(
pk("pk-1"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: recent,
last_failure_time: recent,
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
},
);
let expired = expire_partition_overrides(&state, Instant::now(), Duration::from_secs(60));
assert_eq!(expired.failover_overrides.len(), 1);
}
#[test]
fn expire_partition_overrides_does_not_retransition_probe_candidate() {
let old_time = Instant::now() - Duration::from_secs(60);
let mut state = partition_state_with_ppaf_ppcb_enabled();
state.circuit_breaker_overrides.insert(
pk("pk-1"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: old_time,
last_failure_time: old_time,
health_status: HealthStatus::ProbeCandidate,
failback_jitter: Duration::ZERO,
},
);
let expired = expire_partition_overrides(&state, Instant::now(), Duration::from_secs(30));
assert_eq!(
expired.circuit_breaker_overrides["pk-1"].health_status,
HealthStatus::ProbeCandidate
);
}
#[test]
fn ppcb_failback_jitter_stays_within_half_of_unavailability_duration() {
let unavailability = Duration::from_secs(5);
let max = unavailability / 2;
for i in 0..100 {
let pk: PartitionKeyRangeId = format!("pk-{i}").parse().unwrap();
let j = ppcb_failback_jitter(unavailability, &pk);
assert!(j < max, "jitter {j:?} exceeded half-window {max:?}");
}
}
#[test]
fn ppcb_failback_jitter_is_zero_for_zero_duration() {
let pk: PartitionKeyRangeId = "pk-0".parse().unwrap();
assert_eq!(ppcb_failback_jitter(Duration::ZERO, &pk), Duration::ZERO);
}
#[test]
fn ppcb_failback_jitter_uses_full_window_above_one_second() {
let unavailability = Duration::from_secs(10); let one_second = Duration::from_secs(1);
let mut saw_above_one_second = false;
for i in 0..1_000 {
let pk: PartitionKeyRangeId = format!("pk-{i}").parse().unwrap();
if ppcb_failback_jitter(unavailability, &pk) > one_second {
saw_above_one_second = true;
break;
}
}
assert!(
saw_above_one_second,
"jitter never exceeded 1 s across 1000 partitions — full window not in use"
);
}
#[test]
fn ppcb_failback_jitter_decorrelates_simultaneous_failures() {
let unavailability = Duration::from_secs(5);
let mut samples = std::collections::HashSet::new();
for i in 0..256 {
let pk: PartitionKeyRangeId = format!("pk-{i}").parse().unwrap();
samples.insert(ppcb_failback_jitter(unavailability, &pk));
}
assert!(
samples.len() >= 250,
"expected ≥250 distinct jitter values across 256 partition keys, got {}",
samples.len()
);
}
#[test]
fn expire_partition_overrides_respects_failback_jitter() {
let unavailability = Duration::from_secs(5);
let jitter = Duration::from_secs(2);
let first_failure = Instant::now() - unavailability;
let mut state = partition_state_with_ppaf_ppcb_enabled();
state.circuit_breaker_overrides.insert(
pk("jittered-pk"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: first_failure,
last_failure_time: first_failure,
health_status: HealthStatus::Unhealthy,
failback_jitter: jitter,
},
);
let expired =
expire_partition_overrides(&state, first_failure + unavailability, unavailability);
assert_eq!(
expired.circuit_breaker_overrides["jittered-pk"].health_status,
HealthStatus::Unhealthy,
"entry must remain Unhealthy until unavailability + jitter has elapsed"
);
let expired = expire_partition_overrides(
&state,
first_failure + unavailability + jitter,
unavailability,
);
assert_eq!(
expired.circuit_breaker_overrides["jittered-pk"].health_status,
HealthStatus::ProbeCandidate,
);
}
#[test]
fn mark_partition_unavailable_ppcb_samples_jitter() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
let unavail = unavailable_partition("pk-1", "eastus", true);
let result = mark_partition_unavailable(&ps, &account, &unavail, true);
let entry = &result.circuit_breaker_overrides["pk-1"];
let max = ps.config.partition_unavailability_duration() / 2;
assert!(
entry.failback_jitter < max,
"jitter {:?} must be < half of unavailability window {:?}",
entry.failback_jitter,
max
);
}
#[test]
fn mark_partition_unavailable_ppaf_uses_zero_jitter() {
let ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
let unavail = unavailable_partition("pk-1", "eastus", false);
let result = mark_partition_unavailable(&ps, &account, &unavail, true);
let entry = &result.failover_overrides["pk-1"];
assert_eq!(entry.failback_jitter, Duration::ZERO);
}
#[test]
fn probe_failure_transitions_back_to_unhealthy() {
let mut ps = partition_state_with_ppaf_ppcb_enabled();
let account = single_master_account();
let mut failed = HashSet::new();
failed.insert(regional_endpoint("eastus"));
ps.circuit_breaker_overrides.insert(
pk("pk-1"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: failed,
read_failure_count: 3,
write_failure_count: 0,
first_failure_time: Instant::now() - Duration::from_secs(10),
last_failure_time: Instant::now() - Duration::from_secs(10),
health_status: HealthStatus::ProbeCandidate,
failback_jitter: Duration::ZERO,
},
);
let unavail = unavailable_partition("pk-1", "eastus", true);
let result = mark_partition_unavailable(&ps, &account, &unavail, true);
let entry = &result.circuit_breaker_overrides["pk-1"];
assert_eq!(entry.health_status, HealthStatus::Unhealthy);
assert_eq!(entry.read_failure_count, 0);
assert_eq!(entry.write_failure_count, 0);
assert_eq!(entry.current_endpoint, regional_endpoint("eastus"));
assert!(entry.failed_endpoints.is_empty());
}
#[test]
fn ppcb_re_failover_after_probe_failure_respects_threshold() {
let account = single_master_account();
let threshold = PartitionFailoverOptions::default().read_failure_threshold() as i32;
let mut ps = partition_state_with_ppaf_ppcb_enabled();
let unavail = unavailable_partition("pk-1", "eastus", true);
for _ in 0..=(threshold as usize) {
ps = mark_partition_unavailable(&ps, &account, &unavail, true);
}
let entry = &ps.circuit_breaker_overrides["pk-1"];
assert_eq!(entry.read_failure_count, threshold + 1);
assert_eq!(entry.current_endpoint, regional_endpoint("westus"));
ps = expire_partition_overrides(
&ps,
Instant::now() + Duration::from_secs(60),
Duration::from_secs(5),
);
assert_eq!(
ps.circuit_breaker_overrides["pk-1"].health_status,
HealthStatus::ProbeCandidate,
);
ps = mark_partition_unavailable(&ps, &account, &unavail, true);
let entry = &ps.circuit_breaker_overrides["pk-1"];
assert_eq!(entry.health_status, HealthStatus::Unhealthy);
assert_eq!(entry.read_failure_count, 0);
assert_eq!(entry.current_endpoint, regional_endpoint("eastus"));
assert!(!can_circuit_breaker_trigger_failover(
entry, true, &ps.config
));
for i in 1..threshold {
ps = mark_partition_unavailable(&ps, &account, &unavail, true);
let entry = &ps.circuit_breaker_overrides["pk-1"];
assert_eq!(entry.read_failure_count, i);
assert_eq!(entry.current_endpoint, regional_endpoint("eastus"));
}
ps = mark_partition_unavailable(&ps, &account, &unavail, true);
let entry = &ps.circuit_breaker_overrides["pk-1"];
assert_eq!(entry.read_failure_count, threshold);
assert_eq!(entry.current_endpoint, regional_endpoint("westus"));
}
#[test]
fn remove_probe_succeeded_entry_removes_from_circuit_breaker_only() {
let mut state = partition_state_with_ppaf_ppcb_enabled();
state.circuit_breaker_overrides.insert(
pk("pk-1"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: Instant::now(),
last_failure_time: Instant::now(),
health_status: HealthStatus::ProbeCandidate,
failback_jitter: Duration::ZERO,
},
);
state.failover_overrides.insert(
pk("pk-1"),
PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: Default::default(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: Instant::now(),
last_failure_time: Instant::now(),
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
},
);
let result = remove_probe_succeeded_entry(&state, &pk("pk-1"));
assert!(result.circuit_breaker_overrides.is_empty());
assert_eq!(result.failover_overrides.len(), 1);
assert_eq!(
result.failover_overrides["pk-1"].health_status,
HealthStatus::Unhealthy
);
}
fn multi_region_properties() -> AccountProperties {
serde_json::from_value(serde_json::json!({
"_self": "",
"id": "test",
"_rid": "test.documents.azure.com",
"media": "//media/",
"addresses": "//addresses/",
"_dbs": "//dbs/",
"writableLocations": [
{ "name": "eastus", "databaseAccountEndpoint": "https://test-eastus.documents.azure.com:443/" },
{ "name": "westus2", "databaseAccountEndpoint": "https://test-westus2.documents.azure.com:443/" },
{ "name": "westus3", "databaseAccountEndpoint": "https://test-westus3.documents.azure.com:443/" }
],
"readableLocations": [
{ "name": "eastus", "databaseAccountEndpoint": "https://test-eastus.documents.azure.com:443/" },
{ "name": "westus2", "databaseAccountEndpoint": "https://test-westus2.documents.azure.com:443/" },
{ "name": "westus3", "databaseAccountEndpoint": "https://test-westus3.documents.azure.com:443/" }
],
"enableMultipleWriteLocations": true,
"userReplicationPolicy": { "minReplicaSetSize": 3, "maxReplicasetSize": 4 },
"userConsistencyPolicy": { "defaultConsistencyLevel": "Session" },
"systemReplicationPolicy": { "minReplicaSetSize": 3, "maxReplicasetSize": 4 },
"readPolicy": { "primaryReadCoefficient": 1, "secondaryReadCoefficient": 1 },
"queryEngineConfiguration": "{}"
}))
.unwrap()
}
#[test]
fn preferred_regions_reorder_write_endpoints() {
let preferred = vec![Region::WEST_US_3, Region::EAST_US];
let state = build_account_endpoint_state(
&multi_region_properties(),
default_endpoint(),
None,
false,
&preferred,
);
assert_eq!(state.preferred_write_endpoints.len(), 3);
assert_eq!(
state.preferred_write_endpoints[0].region().unwrap(),
&Region::WEST_US_3
);
assert_eq!(
state.preferred_write_endpoints[1].region().unwrap(),
&Region::EAST_US
);
assert_eq!(
state.preferred_write_endpoints[2].region().unwrap(),
&Region::WEST_US_2
);
}
#[test]
fn preferred_regions_reorder_read_endpoints() {
let preferred = vec![Region::WEST_US_2, Region::WEST_US_3];
let state = build_account_endpoint_state(
&multi_region_properties(),
default_endpoint(),
None,
false,
&preferred,
);
assert_eq!(state.preferred_read_endpoints.len(), 3);
assert_eq!(
state.preferred_read_endpoints[0].region().unwrap(),
&Region::WEST_US_2
);
assert_eq!(
state.preferred_read_endpoints[1].region().unwrap(),
&Region::WEST_US_3
);
assert_eq!(
state.preferred_read_endpoints[2].region().unwrap(),
&Region::EAST_US
);
}
#[test]
fn preferred_regions_unknown_regions_are_skipped() {
let preferred = vec![Region::new("nonexistent"), Region::WEST_US_3];
let state = build_account_endpoint_state(
&multi_region_properties(),
default_endpoint(),
None,
false,
&preferred,
);
assert_eq!(
state.preferred_write_endpoints[0].region().unwrap(),
&Region::WEST_US_3
);
assert_eq!(
state.preferred_write_endpoints[1].region().unwrap(),
&Region::EAST_US
);
assert_eq!(
state.preferred_write_endpoints[2].region().unwrap(),
&Region::WEST_US_2
);
}
#[test]
fn empty_preferred_regions_preserves_original_order() {
let state = build_account_endpoint_state(
&multi_region_properties(),
default_endpoint(),
None,
false,
&[],
);
assert_eq!(
state.preferred_write_endpoints[0].region().unwrap(),
&Region::EAST_US
);
assert_eq!(
state.preferred_write_endpoints[1].region().unwrap(),
&Region::WEST_US_2
);
assert_eq!(
state.preferred_write_endpoints[2].region().unwrap(),
&Region::WEST_US_3
);
}
fn partition_state_with_hedge_threshold(threshold: u32) -> PartitionEndpointState {
let config = PartitionFailoverOptions::builder()
.with_consecutive_hedge_win_threshold(threshold)
.build()
.expect("valid partition failover options");
let mut state = PartitionEndpointState::new(config);
state.per_partition_automatic_failover_enabled = true;
state.per_partition_circuit_breaker_enabled = true;
state
}
#[test]
fn hedge_alternate_win_increments_counter_below_threshold() {
let state = partition_state_with_hedge_threshold(3);
let account = multi_master_account();
let pk_range = pk("pk-1");
let primary = Region::new("eastus".to_string());
let after = record_hedge_alternate_win(&state, &account, &pk_range, Some(&primary));
assert_eq!(
after.consecutive_hedge_wins[&(pk_range.clone(), Some(primary))],
1,
"first hedge win increments to 1",
);
assert!(
after.circuit_breaker_overrides.is_empty(),
"no trip until threshold reached",
);
}
#[test]
fn hedge_alternate_win_at_threshold_installs_unhealthy_entry() {
let mut state = partition_state_with_hedge_threshold(3);
let account = multi_master_account();
let pk_range = pk("pk-1");
let primary = Region::new("eastus".to_string());
for _ in 0..3 {
state = record_hedge_alternate_win(&state, &account, &pk_range, Some(&primary));
}
let entry = state
.circuit_breaker_overrides
.get(pk_range.as_str())
.expect("threshold-th win must install a circuit breaker entry");
assert_eq!(entry.health_status, HealthStatus::Unhealthy);
assert_eq!(
entry.first_failed_endpoint,
regional_endpoint("eastus"),
"first_failed_endpoint must match the primary region that lost",
);
assert_eq!(
entry.current_endpoint,
regional_endpoint("westus"),
"current_endpoint must point at the alternate region after the trip",
);
assert!(
!state
.consecutive_hedge_wins
.contains_key(&(pk_range, Some(primary))),
"counter must be cleared after the trip is installed",
);
}
#[test]
fn hedge_alternate_win_seeds_failure_counts_to_trigger_failover() {
let mut state = partition_state_with_hedge_threshold(3);
let account = multi_master_account();
let pk_range = pk("pk-1");
let primary = Region::new("eastus".to_string());
for _ in 0..3 {
state = record_hedge_alternate_win(&state, &account, &pk_range, Some(&primary));
}
let entry = state
.circuit_breaker_overrides
.get(pk_range.as_str())
.expect("threshold-th win must install a circuit breaker entry");
assert_eq!(
entry.read_failure_count,
state.config.read_failure_threshold() as i32,
"read_failure_count must equal the configured threshold so a \
read attempt's `can_circuit_breaker_trigger_failover` returns true",
);
assert_eq!(
entry.write_failure_count,
state.config.write_failure_threshold() as i32,
"write_failure_count must equal the configured threshold so a \
write attempt's `can_circuit_breaker_trigger_failover` returns true",
);
assert!(
can_circuit_breaker_trigger_failover(
entry,
true,
&state.config
),
"the freshly-installed hedge-trip entry must immediately pass \
the failover gate for read operations",
);
assert!(
can_circuit_breaker_trigger_failover(
entry,
false,
&state.config
),
"the freshly-installed hedge-trip entry must immediately pass \
the failover gate for write operations",
);
}
#[test]
fn hedge_primary_win_resets_counter_only() {
let state = partition_state_with_hedge_threshold(3);
let account = multi_master_account();
let pk_range = pk("pk-1");
let primary = Region::new("eastus".to_string());
let s1 = record_hedge_alternate_win(&state, &account, &pk_range, Some(&primary));
let s2 = record_hedge_alternate_win(&s1, &account, &pk_range, Some(&primary));
assert_eq!(
s2.consecutive_hedge_wins[&(pk_range.clone(), Some(primary.clone()))],
2
);
let s3 = record_hedge_primary_win(&s2, &pk_range, Some(&primary));
assert!(s3.consecutive_hedge_wins.is_empty());
assert!(s3.circuit_breaker_overrides.is_empty());
let s4 = record_hedge_alternate_win(&s3, &account, &pk_range, Some(&primary));
assert_eq!(s4.consecutive_hedge_wins[&(pk_range, Some(primary))], 1);
assert!(s4.circuit_breaker_overrides.is_empty());
}
#[test]
fn hedge_primary_win_does_not_touch_existing_trip() {
let mut state = partition_state_with_hedge_threshold(1);
let account = multi_master_account();
let pk_range = pk("pk-1");
let primary = Region::new("eastus".to_string());
state = record_hedge_alternate_win(&state, &account, &pk_range, Some(&primary));
assert!(state
.circuit_breaker_overrides
.contains_key(pk_range.as_str()));
let after = record_hedge_primary_win(&state, &pk_range, Some(&primary));
assert!(
after
.circuit_breaker_overrides
.contains_key(pk_range.as_str()),
"primary wins must never clear an active PPCB trip; only the failback sweep does",
);
}
#[test]
fn hedge_counter_keyed_per_partition() {
let mut state = partition_state_with_hedge_threshold(2);
let account = multi_master_account();
let pk_a = pk("pk-A");
let pk_b = pk("pk-B");
let primary = Region::new("eastus".to_string());
state = record_hedge_alternate_win(&state, &account, &pk_a, Some(&primary));
state = record_hedge_alternate_win(&state, &account, &pk_b, Some(&primary));
assert!(state.circuit_breaker_overrides.is_empty());
assert_eq!(state.consecutive_hedge_wins.len(), 2);
}
#[test]
fn hedge_counter_keyed_per_primary_region() {
let mut state = partition_state_with_hedge_threshold(2);
let account = multi_master_account();
let pk_range = pk("pk-1");
let east = Region::new("eastus".to_string());
let west = Region::new("westus".to_string());
state = record_hedge_alternate_win(&state, &account, &pk_range, Some(&east));
state = record_hedge_alternate_win(&state, &account, &pk_range, Some(&west));
assert!(state.circuit_breaker_overrides.is_empty());
assert_eq!(state.consecutive_hedge_wins.len(), 2);
}
#[test]
fn hedge_alternate_win_skips_trip_when_existing_ppcb_entry_present() {
let mut state = partition_state_with_hedge_threshold(1);
let account = multi_master_account();
let pk_range = pk("pk-1");
let primary = Region::new("eastus".to_string());
let existing = PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: HashSet::new(),
read_failure_count: 42, write_failure_count: 0,
first_failure_time: Instant::now(),
last_failure_time: Instant::now(),
health_status: HealthStatus::Unhealthy,
failback_jitter: Duration::ZERO,
};
state
.circuit_breaker_overrides
.insert(pk_range.clone(), existing);
let after = record_hedge_alternate_win(&state, &account, &pk_range, Some(&primary));
let entry = &after.circuit_breaker_overrides[pk_range.as_str()];
assert_eq!(
entry.read_failure_count, 42,
"existing PPCB entry must be preserved verbatim",
);
assert!(
after.consecutive_hedge_wins.is_empty(),
"counter must be cleared even when the trip is skipped, so we don't loop on every subsequent hedge win",
);
}
#[test]
fn hedge_alternate_win_re_trips_when_existing_entry_is_probe_candidate() {
let mut state = partition_state_with_hedge_threshold(1);
let account = multi_master_account();
let pk_range = pk("pk-1");
let primary = Region::new("eastus".to_string());
let stale_probe_candidate = PartitionFailoverEntry {
current_endpoint: regional_endpoint("westus"),
first_failed_endpoint: regional_endpoint("eastus"),
failed_endpoints: HashSet::new(),
read_failure_count: 0,
write_failure_count: 0,
first_failure_time: Instant::now(),
last_failure_time: Instant::now(),
health_status: HealthStatus::ProbeCandidate,
failback_jitter: Duration::ZERO,
};
state
.circuit_breaker_overrides
.insert(pk_range.clone(), stale_probe_candidate);
let after = record_hedge_alternate_win(&state, &account, &pk_range, Some(&primary));
let entry = &after.circuit_breaker_overrides[pk_range.as_str()];
assert_eq!(
entry.health_status,
HealthStatus::Unhealthy,
"stale ProbeCandidate must be replaced with a fresh Unhealthy entry"
);
assert_eq!(
entry.read_failure_count,
state.config.read_failure_threshold() as i32,
"re-trip must seed read_failure_count to the threshold so \
can_circuit_breaker_trigger_failover accepts the entry on the \
very next routing decision"
);
assert_eq!(
entry.first_failed_endpoint,
regional_endpoint("eastus"),
"re-trip must record the still-unhealthy primary as the first \
failed endpoint"
);
assert_eq!(
entry.current_endpoint,
regional_endpoint("westus"),
"re-trip must point routing at the alternate region"
);
assert!(
after.consecutive_hedge_wins.is_empty(),
"counter must be cleared after the re-trip is installed"
);
}
#[test]
fn hedge_alternate_win_no_op_when_primary_region_unknown_to_account() {
let mut state = partition_state_with_hedge_threshold(1);
let account = multi_master_account();
let pk_range = pk("pk-1");
let unknown_region = Region::new("mars-central-1".to_string());
state = record_hedge_alternate_win(&state, &account, &pk_range, Some(&unknown_region));
assert_eq!(
state.consecutive_hedge_wins[&(pk_range, Some(unknown_region))],
1,
"counter still tracks the (partition, primary_region) pair even if we can't trip",
);
assert!(
state.circuit_breaker_overrides.is_empty(),
"no trip without an endpoint identity to route away from",
);
}
#[test]
fn hedge_alternate_win_with_none_primary_region_does_not_trip() {
let mut state = partition_state_with_hedge_threshold(1);
let account = multi_master_account();
let pk_range = pk("pk-1");
state = record_hedge_alternate_win(&state, &account, &pk_range, None);
assert_eq!(state.consecutive_hedge_wins[&(pk_range, None)], 1);
assert!(state.circuit_breaker_overrides.is_empty());
}
}