//! Atomic desired-target transactions, generation retirement, and pruning. use std::{ collections::HashSet, sync::Arc, task::{Context, Poll}, time::Instant, }; use crate::{ managed::{ ManagedCommandAcknowledgement, ManagedCommandApplyError, ManagedCompletionPolicy, ManagedEvent, ManagedTargetConfig, ManagedTargetLifecycle, TargetInstance, }, ClientConfig, }; use super::{ control::{validate_target_config, ManagedCommand, UpdateAdmission}, target::{RetirementReason, TargetCounters, TargetMembership, TargetPhase, TargetRuntime}, ManagedClientTask, TaskPhase, }; const COMMAND_WORK_BUDGET: usize = 32; struct PlannedTarget { target: ManagedTargetConfig, client_config: ClientConfig, generation: u64, } struct PlannedRetirement { index: usize, reason: RetirementReason, synchronous: bool, } struct UpdatePlan { retirements: Vec, created: Vec, next_generation: u64, next_command_sequence: u64, prospective_live_count: usize, } fn synchronously_retireable(phase: &TargetPhase) -> bool { match phase { TargetPhase::Pending { .. } | TargetPhase::Connecting { .. } | TargetPhase::Terminal { .. } => true, TargetPhase::Opening { open, .. } => !open.has_in_flight_work(), TargetPhase::Active { .. } | TargetPhase::Draining { .. } | TargetPhase::Closing { .. } => { false } } } fn runtime_satisfies_target(runtime: &TargetRuntime, target: &ManagedTargetConfig) -> bool { runtime.membership.is_desired() && !matches!(runtime.phase(), TargetPhase::Terminal { .. }) && runtime.config == *target } impl ManagedClientTask { pub(super) fn process_commands(&mut self, cx: &mut Context<'_>) -> bool { let mut immediate = false; for _ in 0..COMMAND_WORK_BUDGET { match self.commands.poll_recv(cx) { Poll::Ready(Some(ManagedCommand::UpdateTargets { targets, acknowledgement, })) => { // This check is the driver-side application linearization point. // `apply_targets` cannot suspend, so a stop observed here wins over // this command; once it passes, this command may complete atomically. let result = if !self.phase.is_running() || self.resources().stop.update_admission() != UpdateAdmission::Open { Err(ManagedCommandApplyError::Stopping) } else { self.apply_targets(targets) }; let _ = acknowledgement.send(result); immediate = true; } Poll::Ready(None) | Poll::Pending => break, } } immediate } fn apply_targets( &mut self, incoming: Vec, ) -> Result { let plan = self.plan_targets(incoming)?; debug_assert!(plan.prospective_live_count <= self.config.max_live_target_generations); let now = Instant::now(); let mut synchronous = HashSet::with_capacity(plan.retirements.len()); let mut finished = Vec::new(); for retirement in &plan.retirements { let target = &mut self.targets[retirement.index]; let was_paced_active = target.is_paced_active(); target.membership = TargetMembership::Withdrawn(retirement.reason); if retirement.synchronous { synchronous.insert(retirement.index); if !matches!(target.phase(), TargetPhase::Terminal { .. }) { let outcome = Arc::new(target.outcome(retirement.reason.into(), None)); self.history.record(outcome.as_ref().clone()); finished.push(ManagedEvent::TargetFinished { outcome }); } } else if was_paced_active { // Removal is directional: it may discard an elapsed gate but never // lengthens an existing future stagger gate. self.stagger_target_removed(now); } } if !synchronous.is_empty() { let mut index = 0; self.targets.retain(|_| { let keep = !synchronous.contains(&index); index += 1; keep }); self.rebase_target_cursors(); } let mut created_instances = Vec::with_capacity(plan.created.len()); for planned in plan.created { let instance = TargetInstance { id: planned.target.id.clone(), generation: planned.generation, }; created_instances.push(instance.clone()); self.targets.push(TargetRuntime { instance, server_addr: Arc::from(planned.target.server_addr.clone()), config: planned.target, membership: TargetMembership::Desired, remote: None, counters: TargetCounters::default(), phase: Some(TargetPhase::Pending { client_config: planned.client_config, }), }); } self.next_generation = plan.next_generation; self.applied_command_sequence = plan.next_command_sequence; let stopping = self .targets .iter() .all(|runtime| !runtime.membership.is_desired()) && self.config.completion == ManagedCompletionPolicy::FinishWhenQuiescent; if stopping { self.resources().stop.begin_stopping(); self.phase = TaskPhase::Stopping; } // A transaction has one externally visible durable point: all runtime and // history changes above, then this exact status snapshot, then its events and // acknowledgement. Do not use per-transition publishing helpers here. let status = self.snapshot(); self.resources().status.send_replace(Arc::clone(&status)); if stopping { self.publish_event(ManagedEvent::Stopping); } for event in finished { self.publish_event(event); } for target in created_instances { self.publish_event(ManagedEvent::TargetStateChanged { target, lifecycle: ManagedTargetLifecycle::Pending, }); } Ok(ManagedCommandAcknowledgement { sequence: self.applied_command_sequence, status, }) } fn plan_targets( &self, incoming: Vec, ) -> Result { let mut ids = HashSet::with_capacity(incoming.len()); let mut prepared = Vec::with_capacity(incoming.len()); for target in incoming { if !ids.insert(target.id.clone()) { return Err(ManagedCommandApplyError::DuplicateTargetId { id: target.id }); } let mut client_config = self.config.client.clone(); client_config.address_family = target .address_family .unwrap_or(self.config.client.address_family); client_config.auth = target.auth.resolve(&self.config.client.auth); validate_target_config(&client_config).map_err(|source| { ManagedCommandApplyError::InvalidTarget { id: target.id.clone(), source, } })?; prepared.push((target, client_config)); } let mut retirements = Vec::new(); for (index, runtime) in self.targets.iter().enumerate() { if !runtime.membership.is_desired() || prepared .iter() .any(|(target, _)| runtime_satisfies_target(runtime, target)) { continue; } let reason = if prepared .iter() .any(|(target, _)| target.id == runtime.instance.id) { RetirementReason::Replaced } else { RetirementReason::Removed }; retirements.push(PlannedRetirement { index, reason, synchronous: synchronously_retireable(runtime.phase()), }); } let mut next_generation = self.next_generation; let mut created = Vec::new(); for (target, client_config) in prepared { if self .targets .iter() .any(|runtime| runtime_satisfies_target(runtime, &target)) { continue; } let generation = next_generation; next_generation = next_generation .checked_add(1) .ok_or(ManagedCommandApplyError::GenerationExhausted)?; created.push(PlannedTarget { target, client_config, generation, }); } let next_command_sequence = self .applied_command_sequence .checked_add(1) .ok_or(ManagedCommandApplyError::CommandSequenceExhausted)?; let synchronous = retirements .iter() .filter(|retirement| retirement.synchronous) .map(|retirement| retirement.index) .collect::>(); let prospective_live_count = self .targets .iter() .enumerate() .filter(|(index, _)| !synchronous.contains(index)) .count() .saturating_add(created.len()); if prospective_live_count > self.config.max_live_target_generations { return Err(ManagedCommandApplyError::LiveGenerationLimitExceeded { required: prospective_live_count, limit: self.config.max_live_target_generations, }); } Ok(UpdatePlan { retirements, created, next_generation, next_command_sequence, prospective_live_count, }) } pub(super) fn prune_undesired_terminal(&mut self) { if !self.targets.iter().any(|target| { !target.membership.is_desired() && matches!(target.phase(), TargetPhase::Terminal { .. }) }) { return; } self.targets.retain(|target| { target.membership.is_desired() || !matches!(target.phase(), TargetPhase::Terminal { .. }) }); self.rebase_target_cursors(); self.replace_status(); } fn rebase_target_cursors(&mut self) { let len = self.targets.len(); if len == 0 { self.cursor = 0; self.timeout_cursor = 0; self.send_cursor = 0; } else { self.cursor %= len; self.timeout_cursor %= len; self.send_cursor %= len; } self.scan_remaining = self.scan_remaining.min(len); self.burst_remaining = self.burst_remaining.min(len); self.stagger_remaining = self.stagger_remaining.min(len); } }