mirror of https://github.com/telemt/telemt.git
675 lines
21 KiB
Rust
675 lines
21 KiB
Rust
use crate::proxy::client::handle_client_stream_with_shared;
|
|
use crate::proxy::handshake::{
|
|
auth_probe_fail_streak_for_testing_in_shared, auth_probe_is_throttled_for_testing_in_shared,
|
|
auth_probe_record_failure_for_testing, clear_auth_probe_state_for_testing_in_shared,
|
|
clear_unknown_sni_warn_state_for_testing_in_shared, clear_warned_secrets_for_testing_in_shared,
|
|
should_emit_unknown_sni_warn_for_testing_in_shared, warned_secrets_for_testing_in_shared,
|
|
};
|
|
use crate::proxy::middle_relay::{
|
|
clear_desync_dedup_for_testing_in_shared, clear_relay_idle_candidate_for_testing,
|
|
clear_relay_idle_pressure_state_for_testing_in_shared, mark_relay_idle_candidate_for_testing,
|
|
maybe_evict_idle_candidate_on_pressure_for_testing, note_relay_pressure_event_for_testing,
|
|
oldest_relay_idle_candidate_for_testing, relay_idle_mark_seq_for_testing,
|
|
relay_pressure_event_seq_for_testing, should_emit_full_desync_for_testing,
|
|
};
|
|
use crate::proxy::route_mode::{RelayRouteMode, RouteRuntimeController};
|
|
use crate::proxy::shared_state::ProxySharedState;
|
|
use crate::{
|
|
config::{ProxyConfig, UpstreamConfig, UpstreamType},
|
|
crypto::SecureRandom,
|
|
ip_tracker::UserIpTracker,
|
|
stats::{ReplayChecker, Stats, beobachten::BeobachtenStore},
|
|
stream::BufferPool,
|
|
transport::UpstreamManager,
|
|
};
|
|
use std::net::{IpAddr, Ipv4Addr};
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
use tokio::io::{AsyncWriteExt, duplex};
|
|
use tokio::sync::Barrier;
|
|
|
|
struct ClientHarness {
|
|
config: Arc<ProxyConfig>,
|
|
stats: Arc<Stats>,
|
|
upstream_manager: Arc<UpstreamManager>,
|
|
replay_checker: Arc<ReplayChecker>,
|
|
buffer_pool: Arc<BufferPool>,
|
|
rng: Arc<SecureRandom>,
|
|
route_runtime: Arc<RouteRuntimeController>,
|
|
ip_tracker: Arc<UserIpTracker>,
|
|
beobachten: Arc<BeobachtenStore>,
|
|
}
|
|
|
|
fn new_client_harness() -> ClientHarness {
|
|
let mut cfg = ProxyConfig::default();
|
|
cfg.censorship.mask = false;
|
|
cfg.general.modes.classic = true;
|
|
cfg.general.modes.secure = true;
|
|
let config = Arc::new(cfg);
|
|
let stats = Arc::new(Stats::new());
|
|
|
|
let upstream_manager = Arc::new(UpstreamManager::new(
|
|
vec![UpstreamConfig {
|
|
upstream_type: UpstreamType::Direct {
|
|
interface: None,
|
|
bind_addresses: None,
|
|
},
|
|
weight: 1,
|
|
enabled: true,
|
|
scopes: String::new(),
|
|
selected_scope: String::new(),
|
|
}],
|
|
1,
|
|
1,
|
|
1,
|
|
10,
|
|
1,
|
|
false,
|
|
stats.clone(),
|
|
));
|
|
|
|
ClientHarness {
|
|
config,
|
|
stats,
|
|
upstream_manager,
|
|
replay_checker: Arc::new(ReplayChecker::new(128, Duration::from_secs(60))),
|
|
buffer_pool: Arc::new(BufferPool::new()),
|
|
rng: Arc::new(SecureRandom::new()),
|
|
route_runtime: Arc::new(RouteRuntimeController::new(RelayRouteMode::Direct)),
|
|
ip_tracker: Arc::new(UserIpTracker::new()),
|
|
beobachten: Arc::new(BeobachtenStore::new()),
|
|
}
|
|
}
|
|
|
|
async fn drive_invalid_mtproto_handshake(
|
|
shared: Arc<ProxySharedState>,
|
|
peer: std::net::SocketAddr,
|
|
) {
|
|
let harness = new_client_harness();
|
|
let (server_side, mut client_side) = duplex(4096);
|
|
let invalid = [0u8; 64];
|
|
|
|
let task = tokio::spawn(handle_client_stream_with_shared(
|
|
server_side,
|
|
peer,
|
|
harness.config,
|
|
harness.stats,
|
|
harness.upstream_manager,
|
|
harness.replay_checker,
|
|
harness.buffer_pool,
|
|
harness.rng,
|
|
None,
|
|
harness.route_runtime,
|
|
None,
|
|
harness.ip_tracker,
|
|
harness.beobachten,
|
|
shared,
|
|
false,
|
|
));
|
|
|
|
client_side
|
|
.write_all(&invalid)
|
|
.await
|
|
.expect("failed to write invalid handshake");
|
|
client_side
|
|
.shutdown()
|
|
.await
|
|
.expect("failed to shutdown client");
|
|
let _ = tokio::time::timeout(Duration::from_secs(3), task)
|
|
.await
|
|
.expect("client task timed out")
|
|
.expect("client task join failed");
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_two_instances_do_not_share_auth_probe_state() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_auth_probe_state_for_testing_in_shared(a.as_ref());
|
|
|
|
let ip = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 10));
|
|
auth_probe_record_failure_for_testing(a.as_ref(), ip, Instant::now());
|
|
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip),
|
|
Some(1)
|
|
);
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip),
|
|
None
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_two_instances_do_not_share_desync_dedup() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_desync_dedup_for_testing_in_shared(a.as_ref());
|
|
|
|
let now = Instant::now();
|
|
let key = 0xA5A5_u64;
|
|
assert!(should_emit_full_desync_for_testing(
|
|
a.as_ref(),
|
|
key,
|
|
false,
|
|
now
|
|
));
|
|
assert!(should_emit_full_desync_for_testing(
|
|
b.as_ref(),
|
|
key,
|
|
false,
|
|
now
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_two_instances_do_not_share_idle_registry() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_relay_idle_pressure_state_for_testing_in_shared(a.as_ref());
|
|
|
|
assert!(mark_relay_idle_candidate_for_testing(a.as_ref(), 111));
|
|
assert_eq!(
|
|
oldest_relay_idle_candidate_for_testing(a.as_ref()),
|
|
Some(111)
|
|
);
|
|
assert_eq!(oldest_relay_idle_candidate_for_testing(b.as_ref()), None);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_reset_in_one_instance_does_not_affect_another() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_auth_probe_state_for_testing_in_shared(a.as_ref());
|
|
|
|
let ip_a = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1));
|
|
let ip_b = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 2));
|
|
let now = Instant::now();
|
|
|
|
auth_probe_record_failure_for_testing(a.as_ref(), ip_a, now);
|
|
auth_probe_record_failure_for_testing(b.as_ref(), ip_b, now);
|
|
clear_auth_probe_state_for_testing_in_shared(a.as_ref());
|
|
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip_a),
|
|
None
|
|
);
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip_b),
|
|
Some(1)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_parallel_auth_probe_updates_stay_per_instance() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_auth_probe_state_for_testing_in_shared(a.as_ref());
|
|
|
|
let ip = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 77));
|
|
let now = Instant::now();
|
|
|
|
for _ in 0..5 {
|
|
auth_probe_record_failure_for_testing(a.as_ref(), ip, now);
|
|
}
|
|
for _ in 0..3 {
|
|
auth_probe_record_failure_for_testing(b.as_ref(), ip, now + Duration::from_millis(1));
|
|
}
|
|
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip),
|
|
Some(5)
|
|
);
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip),
|
|
Some(3)
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn proxy_shared_state_client_pipeline_records_probe_failures_in_instance_state() {
|
|
let shared = ProxySharedState::new();
|
|
clear_auth_probe_state_for_testing_in_shared(shared.as_ref());
|
|
let peer_ip = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 200));
|
|
let peer = std::net::SocketAddr::new(peer_ip, 54001);
|
|
|
|
drive_invalid_mtproto_handshake(shared.clone(), peer).await;
|
|
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), peer_ip),
|
|
Some(1),
|
|
"invalid handshake in client pipeline must update injected shared auth-probe state"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn proxy_shared_state_client_pipeline_keeps_auth_probe_isolated_between_instances() {
|
|
let shared_a = ProxySharedState::new();
|
|
let shared_b = ProxySharedState::new();
|
|
clear_auth_probe_state_for_testing_in_shared(shared_a.as_ref());
|
|
clear_auth_probe_state_for_testing_in_shared(shared_b.as_ref());
|
|
|
|
let peer_a_ip = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 210));
|
|
let peer_b_ip = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 211));
|
|
|
|
drive_invalid_mtproto_handshake(
|
|
shared_a.clone(),
|
|
std::net::SocketAddr::new(peer_a_ip, 54110),
|
|
)
|
|
.await;
|
|
drive_invalid_mtproto_handshake(
|
|
shared_b.clone(),
|
|
std::net::SocketAddr::new(peer_b_ip, 54111),
|
|
)
|
|
.await;
|
|
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(shared_a.as_ref(), peer_a_ip),
|
|
Some(1)
|
|
);
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(shared_b.as_ref(), peer_b_ip),
|
|
Some(1)
|
|
);
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(shared_a.as_ref(), peer_b_ip),
|
|
None
|
|
);
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(shared_b.as_ref(), peer_a_ip),
|
|
None
|
|
);
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
async fn proxy_shared_state_client_pipeline_high_contention_same_ip_stays_lossless_per_instance() {
|
|
let shared_a = ProxySharedState::new();
|
|
let shared_b = ProxySharedState::new();
|
|
clear_auth_probe_state_for_testing_in_shared(shared_a.as_ref());
|
|
clear_auth_probe_state_for_testing_in_shared(shared_b.as_ref());
|
|
|
|
let ip = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 250));
|
|
let workers = 48u16;
|
|
let barrier = Arc::new(Barrier::new((workers as usize) * 2));
|
|
let mut tasks = Vec::new();
|
|
|
|
for i in 0..workers {
|
|
let shared_a = shared_a.clone();
|
|
let barrier_a = barrier.clone();
|
|
let peer_a = std::net::SocketAddr::new(ip, 56000 + i);
|
|
tasks.push(tokio::spawn(async move {
|
|
barrier_a.wait().await;
|
|
drive_invalid_mtproto_handshake(shared_a, peer_a).await;
|
|
}));
|
|
|
|
let shared_b = shared_b.clone();
|
|
let barrier_b = barrier.clone();
|
|
let peer_b = std::net::SocketAddr::new(ip, 56100 + i);
|
|
tasks.push(tokio::spawn(async move {
|
|
barrier_b.wait().await;
|
|
drive_invalid_mtproto_handshake(shared_b, peer_b).await;
|
|
}));
|
|
}
|
|
|
|
for task in tasks {
|
|
task.await.expect("pipeline task join failed");
|
|
}
|
|
|
|
let streak_a = auth_probe_fail_streak_for_testing_in_shared(shared_a.as_ref(), ip)
|
|
.expect("instance A must track probe failures");
|
|
let streak_b = auth_probe_fail_streak_for_testing_in_shared(shared_b.as_ref(), ip)
|
|
.expect("instance B must track probe failures");
|
|
|
|
assert!(streak_a > 0);
|
|
assert!(streak_b > 0);
|
|
|
|
clear_auth_probe_state_for_testing_in_shared(shared_a.as_ref());
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(shared_a.as_ref(), ip),
|
|
None,
|
|
"clearing one instance must reset only that instance"
|
|
);
|
|
assert!(
|
|
auth_probe_fail_streak_for_testing_in_shared(shared_b.as_ref(), ip).is_some(),
|
|
"clearing one instance must not clear the other instance"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_auth_saturation_does_not_bleed_across_instances() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_auth_probe_state_for_testing_in_shared(a.as_ref());
|
|
clear_auth_probe_state_for_testing_in_shared(b.as_ref());
|
|
|
|
let ip = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 77));
|
|
let future_now = Instant::now() + Duration::from_secs(1);
|
|
for _ in 0..8 {
|
|
auth_probe_record_failure_for_testing(a.as_ref(), ip, future_now);
|
|
}
|
|
|
|
assert!(auth_probe_is_throttled_for_testing_in_shared(
|
|
a.as_ref(),
|
|
ip
|
|
));
|
|
assert!(!auth_probe_is_throttled_for_testing_in_shared(
|
|
b.as_ref(),
|
|
ip
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_poison_clear_in_one_instance_does_not_affect_other_instance() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_auth_probe_state_for_testing_in_shared(a.as_ref());
|
|
clear_auth_probe_state_for_testing_in_shared(b.as_ref());
|
|
|
|
let ip_a = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 31));
|
|
let ip_b = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 32));
|
|
let now = Instant::now();
|
|
|
|
auth_probe_record_failure_for_testing(a.as_ref(), ip_a, now);
|
|
auth_probe_record_failure_for_testing(b.as_ref(), ip_b, now);
|
|
|
|
let a_for_poison = a.clone();
|
|
let _ = std::thread::spawn(move || {
|
|
let _hold = a_for_poison
|
|
.handshake
|
|
.auth_probe_saturation
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
panic!("intentional poison for per-instance isolation regression coverage");
|
|
})
|
|
.join();
|
|
|
|
clear_auth_probe_state_for_testing_in_shared(a.as_ref());
|
|
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip_a),
|
|
None
|
|
);
|
|
assert_eq!(
|
|
auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip_b),
|
|
Some(1),
|
|
"poison recovery and clear in one instance must not touch other instance state"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_unknown_sni_cooldown_does_not_bleed_across_instances() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_unknown_sni_warn_state_for_testing_in_shared(a.as_ref());
|
|
clear_unknown_sni_warn_state_for_testing_in_shared(b.as_ref());
|
|
|
|
let now = Instant::now();
|
|
assert!(should_emit_unknown_sni_warn_for_testing_in_shared(
|
|
a.as_ref(),
|
|
now
|
|
));
|
|
assert!(should_emit_unknown_sni_warn_for_testing_in_shared(
|
|
b.as_ref(),
|
|
now
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_warned_secret_cache_does_not_bleed_across_instances() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_warned_secrets_for_testing_in_shared(a.as_ref());
|
|
clear_warned_secrets_for_testing_in_shared(b.as_ref());
|
|
|
|
let key = ("isolation-user".to_string(), "invalid_hex".to_string());
|
|
{
|
|
let warned = warned_secrets_for_testing_in_shared(a.as_ref());
|
|
let mut guard = warned
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
guard.insert(key.clone());
|
|
}
|
|
|
|
let contains_in_a = {
|
|
let warned = warned_secrets_for_testing_in_shared(a.as_ref());
|
|
let guard = warned
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
guard.contains(&key)
|
|
};
|
|
let contains_in_b = {
|
|
let warned = warned_secrets_for_testing_in_shared(b.as_ref());
|
|
let guard = warned
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
guard.contains(&key)
|
|
};
|
|
|
|
assert!(contains_in_a);
|
|
assert!(!contains_in_b);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_idle_mark_seq_is_per_instance() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_relay_idle_pressure_state_for_testing_in_shared(a.as_ref());
|
|
clear_relay_idle_pressure_state_for_testing_in_shared(b.as_ref());
|
|
|
|
assert_eq!(relay_idle_mark_seq_for_testing(a.as_ref()), 0);
|
|
assert_eq!(relay_idle_mark_seq_for_testing(b.as_ref()), 0);
|
|
|
|
assert!(mark_relay_idle_candidate_for_testing(a.as_ref(), 9001));
|
|
assert_eq!(relay_idle_mark_seq_for_testing(a.as_ref()), 1);
|
|
assert_eq!(relay_idle_mark_seq_for_testing(b.as_ref()), 0);
|
|
|
|
assert!(mark_relay_idle_candidate_for_testing(b.as_ref(), 9002));
|
|
assert_eq!(relay_idle_mark_seq_for_testing(a.as_ref()), 1);
|
|
assert_eq!(relay_idle_mark_seq_for_testing(b.as_ref()), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_unknown_sni_clear_in_one_instance_does_not_reset_other() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_unknown_sni_warn_state_for_testing_in_shared(a.as_ref());
|
|
clear_unknown_sni_warn_state_for_testing_in_shared(b.as_ref());
|
|
|
|
let now = Instant::now();
|
|
assert!(should_emit_unknown_sni_warn_for_testing_in_shared(
|
|
a.as_ref(),
|
|
now
|
|
));
|
|
assert!(should_emit_unknown_sni_warn_for_testing_in_shared(
|
|
b.as_ref(),
|
|
now
|
|
));
|
|
|
|
clear_unknown_sni_warn_state_for_testing_in_shared(a.as_ref());
|
|
assert!(should_emit_unknown_sni_warn_for_testing_in_shared(
|
|
a.as_ref(),
|
|
now + Duration::from_millis(1)
|
|
));
|
|
assert!(!should_emit_unknown_sni_warn_for_testing_in_shared(
|
|
b.as_ref(),
|
|
now + Duration::from_millis(1)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_warned_secret_clear_in_one_instance_does_not_clear_other() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_warned_secrets_for_testing_in_shared(a.as_ref());
|
|
clear_warned_secrets_for_testing_in_shared(b.as_ref());
|
|
|
|
let key = (
|
|
"clear-isolation-user".to_string(),
|
|
"invalid_length".to_string(),
|
|
);
|
|
{
|
|
let warned_a = warned_secrets_for_testing_in_shared(a.as_ref());
|
|
let mut guard_a = warned_a
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
guard_a.insert(key.clone());
|
|
|
|
let warned_b = warned_secrets_for_testing_in_shared(b.as_ref());
|
|
let mut guard_b = warned_b
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
guard_b.insert(key.clone());
|
|
}
|
|
|
|
clear_warned_secrets_for_testing_in_shared(a.as_ref());
|
|
|
|
let has_a = {
|
|
let warned = warned_secrets_for_testing_in_shared(a.as_ref());
|
|
let guard = warned
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
guard.contains(&key)
|
|
};
|
|
let has_b = {
|
|
let warned = warned_secrets_for_testing_in_shared(b.as_ref());
|
|
let guard = warned
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
guard.contains(&key)
|
|
};
|
|
|
|
assert!(!has_a);
|
|
assert!(has_b);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_desync_duplicate_suppression_is_instance_scoped() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_desync_dedup_for_testing_in_shared(a.as_ref());
|
|
clear_desync_dedup_for_testing_in_shared(b.as_ref());
|
|
|
|
let now = Instant::now();
|
|
let key = 0xBEEF_0000_0000_0001u64;
|
|
assert!(should_emit_full_desync_for_testing(
|
|
a.as_ref(),
|
|
key,
|
|
false,
|
|
now
|
|
));
|
|
assert!(!should_emit_full_desync_for_testing(
|
|
a.as_ref(),
|
|
key,
|
|
false,
|
|
now + Duration::from_millis(1)
|
|
));
|
|
assert!(should_emit_full_desync_for_testing(
|
|
b.as_ref(),
|
|
key,
|
|
false,
|
|
now
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_desync_clear_in_one_instance_does_not_clear_other() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_desync_dedup_for_testing_in_shared(a.as_ref());
|
|
clear_desync_dedup_for_testing_in_shared(b.as_ref());
|
|
|
|
let now = Instant::now();
|
|
let key = 0xCAFE_0000_0000_0001u64;
|
|
assert!(should_emit_full_desync_for_testing(
|
|
a.as_ref(),
|
|
key,
|
|
false,
|
|
now
|
|
));
|
|
assert!(should_emit_full_desync_for_testing(
|
|
b.as_ref(),
|
|
key,
|
|
false,
|
|
now
|
|
));
|
|
|
|
clear_desync_dedup_for_testing_in_shared(a.as_ref());
|
|
|
|
assert!(should_emit_full_desync_for_testing(
|
|
a.as_ref(),
|
|
key,
|
|
false,
|
|
now + Duration::from_millis(2)
|
|
));
|
|
assert!(!should_emit_full_desync_for_testing(
|
|
b.as_ref(),
|
|
key,
|
|
false,
|
|
now + Duration::from_millis(2)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_idle_candidate_clear_in_one_instance_does_not_affect_other() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_relay_idle_pressure_state_for_testing_in_shared(a.as_ref());
|
|
clear_relay_idle_pressure_state_for_testing_in_shared(b.as_ref());
|
|
|
|
assert!(mark_relay_idle_candidate_for_testing(a.as_ref(), 1001));
|
|
assert!(mark_relay_idle_candidate_for_testing(b.as_ref(), 2002));
|
|
clear_relay_idle_candidate_for_testing(a.as_ref(), 1001);
|
|
|
|
assert_eq!(oldest_relay_idle_candidate_for_testing(a.as_ref()), None);
|
|
assert_eq!(
|
|
oldest_relay_idle_candidate_for_testing(b.as_ref()),
|
|
Some(2002)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_pressure_seq_increments_are_instance_scoped() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_relay_idle_pressure_state_for_testing_in_shared(a.as_ref());
|
|
clear_relay_idle_pressure_state_for_testing_in_shared(b.as_ref());
|
|
|
|
assert_eq!(relay_pressure_event_seq_for_testing(a.as_ref()), 0);
|
|
assert_eq!(relay_pressure_event_seq_for_testing(b.as_ref()), 0);
|
|
|
|
note_relay_pressure_event_for_testing(a.as_ref());
|
|
note_relay_pressure_event_for_testing(a.as_ref());
|
|
|
|
assert_eq!(relay_pressure_event_seq_for_testing(a.as_ref()), 2);
|
|
assert_eq!(relay_pressure_event_seq_for_testing(b.as_ref()), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_shared_state_pressure_consumption_does_not_cross_instances() {
|
|
let a = ProxySharedState::new();
|
|
let b = ProxySharedState::new();
|
|
clear_relay_idle_pressure_state_for_testing_in_shared(a.as_ref());
|
|
clear_relay_idle_pressure_state_for_testing_in_shared(b.as_ref());
|
|
|
|
assert!(mark_relay_idle_candidate_for_testing(a.as_ref(), 7001));
|
|
assert!(mark_relay_idle_candidate_for_testing(b.as_ref(), 7001));
|
|
note_relay_pressure_event_for_testing(a.as_ref());
|
|
|
|
let stats = Stats::new();
|
|
let mut seen_a = 0u64;
|
|
let mut seen_b = 0u64;
|
|
|
|
assert!(maybe_evict_idle_candidate_on_pressure_for_testing(
|
|
a.as_ref(),
|
|
7001,
|
|
&mut seen_a,
|
|
&stats
|
|
));
|
|
assert!(!maybe_evict_idle_candidate_on_pressure_for_testing(
|
|
b.as_ref(),
|
|
7001,
|
|
&mut seen_b,
|
|
&stats
|
|
));
|
|
}
|