mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-12 10:52:13 +02:00
MetadatResolve hardening rerties after errors (#611)
* MetadatResolve hardening rerties after errors
This commit is contained in:
@@ -243,7 +243,7 @@ impl ConfigFile {
|
||||
let template_changed = current_template_path != next_template_path;
|
||||
|
||||
let mut config: Config = Config::from(config_dto);
|
||||
config.prepare(paths.config_path.as_str()).await?;
|
||||
config.prepare(paths.config_path.as_str())?;
|
||||
|
||||
// Compute effective runtime paths for the NEW config before apply.
|
||||
// This ensures prepare-phase reads/validates against the same files that will be active after apply.
|
||||
|
||||
@@ -70,6 +70,7 @@ struct ActiveConnectionInfo {
|
||||
struct SharedConnections {
|
||||
by_key: HashMap<String, SharedAllocation>,
|
||||
key_by_addr: HashMap<ClientConnectionId, String>,
|
||||
shared_by_allocation_id: HashMap<AllocationId, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -303,6 +304,10 @@ impl ActiveProviderManager {
|
||||
if !still_single {
|
||||
None
|
||||
} else if let Some(shared) = connections.shared.by_key.remove(&key) {
|
||||
connections
|
||||
.shared
|
||||
.shared_by_allocation_id
|
||||
.remove(&shared.allocation_id);
|
||||
for shared_addr in &shared.connections {
|
||||
connections.shared.key_by_addr.remove(shared_addr);
|
||||
}
|
||||
@@ -329,6 +334,12 @@ impl ActiveProviderManager {
|
||||
let mut removed_provider_name = None;
|
||||
let mut remove_addr_entry = false;
|
||||
if let Some(per_addr) = connections.single.get_mut(&addr) {
|
||||
if let Some(info) = per_addr.get(&alloc_id) {
|
||||
// Revalidate victim selection under write lock.
|
||||
if info.priority != v_prio || info.created_at != victim_created_at {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
if let Some(info) = per_addr.remove(&alloc_id) {
|
||||
removed_provider_name = info.allocation.get_provider_name();
|
||||
remove_addr_entry = per_addr.is_empty();
|
||||
@@ -511,6 +522,15 @@ impl ActiveProviderManager {
|
||||
if shared.connections.is_empty() {
|
||||
// If this was the last user of the shared allocation:
|
||||
connections.shared.by_key.remove(&key);
|
||||
connections
|
||||
.shared
|
||||
.shared_by_allocation_id
|
||||
.remove(&shared.allocation_id);
|
||||
if let Some(name) = shared.allocation.get_provider_name() {
|
||||
if let Some(list) = connections.by_provider.get_mut(&name) {
|
||||
list.retain(|(_, i)| *i != shared.allocation_id);
|
||||
}
|
||||
}
|
||||
Some(shared.allocation)
|
||||
} else {
|
||||
// Update the entry back with the remaining connections
|
||||
@@ -556,21 +576,21 @@ impl ActiveProviderManager {
|
||||
|
||||
if released.is_none() {
|
||||
// Try removing from Shared
|
||||
let mut remove_key: Option<String> = None;
|
||||
// TODO O(n) over all keys, maybe better approach ist to use a Hashmap shared_by_allocation_id: HashMap<AllocationId, String>
|
||||
for (key, shared) in &connections.shared.by_key {
|
||||
if shared.allocation_id == handle.allocation_id {
|
||||
remove_key = Some(key.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(key) = remove_key {
|
||||
if let Some(key) = connections
|
||||
.shared
|
||||
.shared_by_allocation_id
|
||||
.remove(&handle.allocation_id)
|
||||
{
|
||||
if let Some(shared) = connections.shared.by_key.remove(&key) {
|
||||
released = Some(shared.allocation);
|
||||
for addr in shared.connections {
|
||||
connections.shared.key_by_addr.remove(&addr);
|
||||
}
|
||||
if let Some(name) = released.as_ref().and_then(ProviderAllocation::get_provider_name) {
|
||||
if let Some(list) = connections.by_provider.get_mut(&name) {
|
||||
list.retain(|(_, i)| *i != handle.allocation_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -641,6 +661,10 @@ impl ActiveProviderManager {
|
||||
},
|
||||
);
|
||||
connections.shared.key_by_addr.insert(*addr, key.to_string());
|
||||
connections
|
||||
.shared
|
||||
.shared_by_allocation_id
|
||||
.insert(handle.0.allocation_id, key.to_string());
|
||||
}
|
||||
extras
|
||||
};
|
||||
|
||||
@@ -123,9 +123,9 @@ impl ActiveUserManager {
|
||||
if is_log_user_enabled {
|
||||
let last_user_count = self.last_logged_user_count.load(Ordering::Relaxed);
|
||||
let last_connection_count = self.last_logged_user_connection_count.load(Ordering::Relaxed);
|
||||
if last_user_count != user_count && last_connection_count != user_connection_count {
|
||||
self.last_logged_user_count.store(last_user_count, Ordering::Relaxed);
|
||||
self.last_logged_user_connection_count.store(last_connection_count, Ordering::Relaxed);
|
||||
if last_user_count != user_count || last_connection_count != user_connection_count {
|
||||
self.last_logged_user_count.store(user_count, Ordering::Relaxed);
|
||||
self.last_logged_user_connection_count.store(user_connection_count, Ordering::Relaxed);
|
||||
info!("Active Users: {user_count}, Active User Connections: {user_connection_count}");
|
||||
}
|
||||
}
|
||||
@@ -325,7 +325,6 @@ impl ActiveUserManager {
|
||||
session_token,
|
||||
);
|
||||
|
||||
user_connections.key_by_addr.insert(fingerprint.addr, username.to_string());
|
||||
let total_active = user_connections.key_by_addr.len();
|
||||
|
||||
if let Some(connection_data) = user_connections.by_key.get_mut(username) {
|
||||
@@ -529,15 +528,22 @@ impl ActiveUserManager {
|
||||
let ts = gc_ts.load(Ordering::Acquire);
|
||||
let now = current_time_secs();
|
||||
|
||||
if now - ts > USER_GC_TTL {
|
||||
if now.saturating_sub(ts) > USER_GC_TTL
|
||||
&& gc_ts
|
||||
.compare_exchange(ts, now, Ordering::AcqRel, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
if let Ok(mut user_connections) = self.connections.try_write() {
|
||||
user_connections.kicked.retain(|_, (expires_at, _)| *expires_at > now);
|
||||
user_connections.by_key.retain(|_k, v| now - v.ts < USER_CON_TTL && v.connections > 0);
|
||||
user_connections
|
||||
.by_key
|
||||
.retain(|_k, v| now.saturating_sub(v.ts) < USER_CON_TTL && v.connections > 0);
|
||||
for connection_data in user_connections.by_key.values_mut() {
|
||||
connection_data.sessions.retain(|s| now - s.ts < USER_CON_TTL);
|
||||
connection_data.sessions.retain(|s| now.saturating_sub(s.ts) < USER_CON_TTL);
|
||||
}
|
||||
|
||||
gc_ts.store(now, Ordering::Release);
|
||||
} else {
|
||||
// Lock contention: release the GC claim so a subsequent caller can retry immediately.
|
||||
let _ = gc_ts.compare_exchange(now, ts, Ordering::AcqRel, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -507,12 +507,17 @@ impl MultiProviderLineup {
|
||||
pub(in crate::api::model) struct ProviderLineupManager {
|
||||
grace_period_millis: AtomicU64,
|
||||
grace_period_timeout_secs: AtomicU64,
|
||||
inputs: Arc<ArcSwap<Vec<Arc<ConfigInput>>>>,
|
||||
providers: Arc<ArcSwap<Vec<ProviderLineup>>>,
|
||||
snapshot: Arc<ArcSwap<LineupSnapshot>>,
|
||||
provider_connections: DashMap<Arc<str>, Arc<RwLock<ProviderConfigConnection>>>,
|
||||
event_manager: Arc<EventManager>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LineupSnapshot {
|
||||
inputs: Vec<Arc<ConfigInput>>,
|
||||
providers: Vec<ProviderLineup>,
|
||||
}
|
||||
|
||||
impl ProviderLineupManager {
|
||||
pub fn new(inputs: Vec<Arc<ConfigInput>>, grace_period_options: GracePeriodOptions, event_manager: &Arc<EventManager>) -> Self {
|
||||
let provider_connections: DashMap<Arc<str>, Arc<RwLock<ProviderConfigConnection>>> = DashMap::new();
|
||||
@@ -523,8 +528,10 @@ impl ProviderLineupManager {
|
||||
Self {
|
||||
grace_period_millis: AtomicU64::new(grace_period_options.period_millis),
|
||||
grace_period_timeout_secs: AtomicU64::new(grace_period_options.timeout_secs),
|
||||
inputs: Arc::new(ArcSwap::from_pointee(inputs)),
|
||||
providers: Arc::new(ArcSwap::from_pointee(lineups)),
|
||||
snapshot: Arc::new(ArcSwap::from_pointee(LineupSnapshot {
|
||||
inputs,
|
||||
providers: lineups,
|
||||
})),
|
||||
provider_connections,
|
||||
event_manager: Arc::clone(event_manager),
|
||||
}
|
||||
@@ -592,12 +599,12 @@ impl ProviderLineupManager {
|
||||
}
|
||||
|
||||
fn has_changed(&self, new_inputs: &[Arc<ConfigInput>]) -> bool {
|
||||
let old_inputs = self.inputs.load();
|
||||
if old_inputs.len() != new_inputs.len() {
|
||||
let snapshot = self.snapshot.load();
|
||||
if snapshot.inputs.len() != new_inputs.len() {
|
||||
return true;
|
||||
}
|
||||
for new_input in new_inputs {
|
||||
let Some(old_input) = old_inputs.iter().find(|i| i.name == new_input.name) else {
|
||||
let Some(old_input) = snapshot.inputs.iter().find(|i| i.name == new_input.name) else {
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -625,8 +632,10 @@ impl ProviderLineupManager {
|
||||
debug_if_enabled!("inputs {}", sanitize_sensitive_info(&display_vec(&new_inputs)));
|
||||
debug_if_enabled!("lineup {}", sanitize_sensitive_info(&display_vec(&new_lineups)));
|
||||
|
||||
self.inputs.store(Arc::new(new_inputs));
|
||||
self.providers.store(Arc::new(new_lineups));
|
||||
self.snapshot.store(Arc::new(LineupSnapshot {
|
||||
inputs: new_inputs,
|
||||
providers: new_lineups,
|
||||
}));
|
||||
}
|
||||
|
||||
pub async fn reconcile_connections(&self, mut counts: HashMap<Arc<str>, usize>) {
|
||||
@@ -648,6 +657,8 @@ impl ProviderLineupManager {
|
||||
}
|
||||
|
||||
// 2. Handle new providers that weren't in the registry yet (e.g. newly added/renamed).
|
||||
// Same lock-ordering rule as Phase 1: clone the Arc<RwLock<_>> out of DashMap first,
|
||||
// then await on the provider RwLock without holding any DashMap shard lock.
|
||||
for (name, count) in counts {
|
||||
let conn_lock = self.provider_connections
|
||||
.entry(name)
|
||||
@@ -676,8 +687,8 @@ impl ProviderLineupManager {
|
||||
// deterministic check: Is the name still in any current lineup?
|
||||
let current_names: std::collections::HashSet<Arc<str>> = {
|
||||
let mut names = std::collections::HashSet::new();
|
||||
let lineups = self.providers.load();
|
||||
for lineup in lineups.iter() {
|
||||
let snapshot = self.snapshot.load();
|
||||
for lineup in &snapshot.providers {
|
||||
match lineup {
|
||||
ProviderLineup::Single(s) => { names.insert(s.provider.name.clone()); }
|
||||
ProviderLineup::Multi(m) => {
|
||||
@@ -729,8 +740,8 @@ impl ProviderLineupManager {
|
||||
}
|
||||
|
||||
pub async fn force_exact_acquire_connection(&self, provider_name: &Arc<str>) -> ProviderAllocation {
|
||||
let providers = self.providers.load();
|
||||
let allocation = match Self::get_provider_config_by_name(provider_name, &providers) {
|
||||
let snapshot = self.snapshot.load_full();
|
||||
let allocation = match Self::get_provider_config_by_name(provider_name, &snapshot.providers) {
|
||||
None => ProviderAllocation::Exhausted, // No Name matched, we don't have this provider
|
||||
Some((_lineup, config)) => config.force_allocate().await,
|
||||
};
|
||||
@@ -746,9 +757,9 @@ impl ProviderLineupManager {
|
||||
provider_name: &Arc<str>,
|
||||
allow_grace: bool,
|
||||
) -> ProviderAllocation {
|
||||
let providers = self.providers.load();
|
||||
let snapshot = self.snapshot.load_full();
|
||||
let with_grace = allow_grace && self.grace_period_millis.load(Ordering::Acquire) > 0;
|
||||
let allocation = match Self::get_provider_config_by_name(provider_name, &providers) {
|
||||
let allocation = match Self::get_provider_config_by_name(provider_name, &snapshot.providers) {
|
||||
None => ProviderAllocation::Exhausted,
|
||||
Some((_lineup, config)) => {
|
||||
config
|
||||
@@ -774,8 +785,8 @@ impl ProviderLineupManager {
|
||||
input_name: &Arc<str>,
|
||||
allow_grace: bool,
|
||||
) -> ProviderAllocation {
|
||||
let providers = self.providers.load();
|
||||
let lineup_opt = Self::get_provider_config_by_name(input_name, &providers);
|
||||
let snapshot = self.snapshot.load_full();
|
||||
let lineup_opt = Self::get_provider_config_by_name(input_name, &snapshot.providers);
|
||||
let with_grace = allow_grace && self.grace_period_millis.load(Ordering::Acquire) > 0;
|
||||
let allocation = match lineup_opt {
|
||||
None => ProviderAllocation::Exhausted, // No Name matched, we don't have this provider
|
||||
@@ -837,8 +848,8 @@ impl ProviderLineupManager {
|
||||
// This method is used for redirects to cycle through provider
|
||||
//
|
||||
pub async fn get_next_provider(&self, input_name: &Arc<str>) -> Option<Arc<ProviderConfig>> {
|
||||
let providers = self.providers.load();
|
||||
match Self::get_provider_config_by_name(input_name, &providers) {
|
||||
let snapshot = self.snapshot.load_full();
|
||||
match Self::get_provider_config_by_name(input_name, &snapshot.providers) {
|
||||
None => None,
|
||||
Some((lineup, _config)) => {
|
||||
let cfg = lineup.get_next(self.grace_period_timeout_secs.load(Ordering::Relaxed)).await;
|
||||
@@ -859,8 +870,8 @@ impl ProviderLineupManager {
|
||||
result.insert(name.clone(), count);
|
||||
}
|
||||
};
|
||||
let providers = self.providers.load();
|
||||
for lineup in providers.iter() {
|
||||
let snapshot = self.snapshot.load_full();
|
||||
for lineup in &snapshot.providers {
|
||||
match lineup {
|
||||
ProviderLineup::Single(provider_lineup) => {
|
||||
let connections = provider_lineup.provider.get_current_connections().await;
|
||||
@@ -881,8 +892,8 @@ impl ProviderLineupManager {
|
||||
|
||||
pub async fn active_connection_count(&self) -> usize {
|
||||
let mut count = 0;
|
||||
let providers = self.providers.load();
|
||||
for lineup in providers.iter() {
|
||||
let snapshot = self.snapshot.load_full();
|
||||
for lineup in &snapshot.providers {
|
||||
match lineup {
|
||||
ProviderLineup::Single(provider_lineup) => {
|
||||
count += provider_lineup.provider.get_current_connections().await;
|
||||
@@ -896,8 +907,8 @@ impl ProviderLineupManager {
|
||||
}
|
||||
|
||||
pub async fn is_over_limit(&self, provider_name: &Arc<str>) -> bool {
|
||||
let providers = self.providers.load();
|
||||
if let Some((_, config)) = Self::get_provider_config_by_name(provider_name, &providers) {
|
||||
let snapshot = self.snapshot.load_full();
|
||||
if let Some((_, config)) = Self::get_provider_config_by_name(provider_name, &snapshot.providers) {
|
||||
config.is_over_limit(self.grace_period_timeout_secs.load(Ordering::Relaxed)).await
|
||||
} else {
|
||||
false
|
||||
@@ -905,8 +916,8 @@ impl ProviderLineupManager {
|
||||
}
|
||||
|
||||
pub async fn is_exhausted(&self, provider_name: &Arc<str>) -> bool {
|
||||
let providers = self.providers.load();
|
||||
if let Some((_, config)) = Self::get_provider_config_by_name(provider_name, &providers) {
|
||||
let snapshot = self.snapshot.load_full();
|
||||
if let Some((_, config)) = Self::get_provider_config_by_name(provider_name, &snapshot.providers) {
|
||||
config.is_exhausted().await
|
||||
} else {
|
||||
false
|
||||
@@ -914,8 +925,10 @@ impl ProviderLineupManager {
|
||||
}
|
||||
|
||||
pub fn is_provider_for_input(&self, provider_name: &str, input_name: &str) -> bool {
|
||||
let lineups = self.providers.load();
|
||||
if let Some((lineup, _)) = Self::get_provider_config_by_name(&provider_name.into(), &lineups) {
|
||||
let snapshot = self.snapshot.load();
|
||||
if let Some((lineup, _)) =
|
||||
Self::get_provider_config_by_name(&provider_name.into(), &snapshot.providers)
|
||||
{
|
||||
match lineup {
|
||||
ProviderLineup::Single(_) => return input_name == provider_name,
|
||||
ProviderLineup::Multi(m) => return m.name.as_ref() == input_name,
|
||||
|
||||
Reference in New Issue
Block a user