1use std::{
4 collections::{HashSet, VecDeque},
5 fmt,
6 future::Future,
7 pin::Pin,
8 sync::{
9 atomic::{AtomicBool, AtomicU8, Ordering},
10 Arc,
11 },
12 task::{Context, Poll},
13 time::Instant,
14};
15
16use tokio::sync::{broadcast, mpsc, oneshot, watch};
17
18use crate::{
19 managed::{
20 ManagedClientConfig, ManagedCommandAcknowledgement, ManagedCommandApplyError,
21 ManagedCommandError, ManagedCompletionPolicy, ManagedConfigError, ManagedEvent,
22 ManagedEventSubscription, ManagedLifecycle, ManagedStatus, ManagedStatusSubscription,
23 ManagedSubscribeError, ManagedTargetConfig, ManagedTargetLifecycle, ManagedTargetStatus,
24 TargetInstance,
25 },
26 session::machine::SessionMachine,
27 socket_options::validate_ttl,
28 ClientConfig, ClientError,
29};
30
31use super::{
32 arm_wake,
33 target::{TargetCounters, TargetMembership, TargetPhase, TargetRuntime},
34 timeouts::TIMEOUT_WORK_BUDGET,
35 ManagedClientTask, OutcomeHistory, TaskPhase, TaskResources,
36};
37
38const MAX_BROADCAST_CHANNEL_CAPACITY: usize = usize::MAX >> 1;
41
42#[derive(Debug, Default)]
83pub struct ManagedClient;
84
85impl ManagedClient {
86 pub fn task(
88 config: ManagedClientConfig,
89 targets: Vec<ManagedTargetConfig>,
90 ) -> Result<(ManagedClientTask, ManagedClientHandle), ManagedConfigError> {
91 build_task(config, targets)
92 }
93}
94
95#[derive(Debug)]
96pub(super) struct StopSignal {
97 requested: AtomicBool,
98 update_admission: AtomicU8,
99 wake: watch::Sender<()>,
100}
101
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103#[repr(u8)]
104pub(super) enum UpdateAdmission {
105 Open,
106 Stopping,
107 Closed,
108}
109
110impl UpdateAdmission {
111 fn from_raw(value: u8) -> Self {
112 match value {
113 value if value == Self::Open as u8 => Self::Open,
114 value if value == Self::Stopping as u8 => Self::Stopping,
115 value if value == Self::Closed as u8 => Self::Closed,
116 _ => unreachable!("managed update admission stores a known state"),
117 }
118 }
119}
120
121impl StopSignal {
122 fn request(&self) {
123 self.begin_stopping();
124 if !self.requested.swap(true, Ordering::AcqRel) {
125 self.wake.send_replace(());
126 }
127 }
128
129 pub(super) fn update_admission(&self) -> UpdateAdmission {
130 UpdateAdmission::from_raw(self.update_admission.load(Ordering::Acquire))
131 }
132
133 pub(super) fn begin_stopping(&self) {
134 let mut current = self.update_admission.load(Ordering::Acquire);
135 loop {
136 match UpdateAdmission::from_raw(current) {
137 UpdateAdmission::Open => match self.update_admission.compare_exchange_weak(
138 UpdateAdmission::Open as u8,
139 UpdateAdmission::Stopping as u8,
140 Ordering::AcqRel,
141 Ordering::Acquire,
142 ) {
143 Ok(_) => return,
144 Err(observed) => current = observed,
145 },
146 UpdateAdmission::Stopping | UpdateAdmission::Closed => return,
147 }
148 }
149 }
150
151 pub(super) fn close_updates(&self) {
152 self.update_admission
153 .store(UpdateAdmission::Closed as u8, Ordering::Release);
154 }
155
156 pub(super) fn is_requested(&self) -> bool {
157 self.requested.load(Ordering::Acquire)
158 }
159}
160
161#[derive(Clone)]
163pub struct ManagedClientHandle {
164 stop: Arc<StopSignal>,
165 commands: mpsc::Sender<ManagedCommand>,
166 max_live_target_generations: usize,
167 status: watch::Receiver<Arc<ManagedStatus>>,
168 events: broadcast::WeakSender<ManagedEvent>,
169}
170
171impl fmt::Debug for ManagedClientHandle {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 f.debug_struct("ManagedClientHandle")
174 .field("status", &self.status())
175 .finish_non_exhaustive()
176 }
177}
178
179impl ManagedClientHandle {
180 pub fn status(&self) -> Arc<ManagedStatus> {
182 Arc::clone(&self.status.borrow())
183 }
184
185 pub fn subscribe_status(&self) -> ManagedStatusSubscription {
193 let mut status = self.status.clone();
194 drop(status.borrow_and_update());
195 status
196 }
197
198 pub fn subscribe(&self) -> Result<ManagedEventSubscription, ManagedSubscribeError> {
200 self.events
201 .upgrade()
202 .map(|sender| sender.subscribe())
203 .ok_or(ManagedSubscribeError::Closed)
204 }
205
206 pub fn stop(&self) -> ManagedStopReceipt {
208 self.stop.request();
209 ManagedStopReceipt::new(self.subscribe_status())
210 }
211
212 pub fn update_targets(
228 &self,
229 targets: Vec<ManagedTargetConfig>,
230 ) -> Result<ManagedCommandReceipt, ManagedCommandError> {
231 match self.stop.update_admission() {
232 UpdateAdmission::Open => {}
233 UpdateAdmission::Stopping => return Err(ManagedCommandError::Stopping),
234 UpdateAdmission::Closed => return Err(ManagedCommandError::DriverClosed),
235 }
236 if targets.len() > self.max_live_target_generations {
237 return Err(ManagedCommandError::TooManyTargets {
238 configured: targets.len(),
239 limit: self.max_live_target_generations,
240 });
241 }
242
243 let (acknowledgement, receiver) = oneshot::channel();
251 match self.commands.try_send(ManagedCommand::UpdateTargets {
252 targets,
253 acknowledgement,
254 }) {
255 Ok(()) => Ok(ManagedCommandReceipt { receiver }),
256 Err(mpsc::error::TrySendError::Full(_)) => match self.stop.update_admission() {
257 UpdateAdmission::Open => Err(ManagedCommandError::QueueFull),
258 UpdateAdmission::Stopping => Err(ManagedCommandError::Stopping),
259 UpdateAdmission::Closed => Err(ManagedCommandError::DriverClosed),
260 },
261 Err(mpsc::error::TrySendError::Closed(_)) => Err(ManagedCommandError::DriverClosed),
262 }
263 }
264}
265
266#[must_use = "await the receipt to observe target-update application"]
268pub struct ManagedCommandReceipt {
269 receiver: oneshot::Receiver<Result<ManagedCommandAcknowledgement, ManagedCommandApplyError>>,
270}
271
272impl ManagedCommandReceipt {
273 pub fn blocking_wait(self) -> Result<ManagedCommandAcknowledgement, ManagedCommandApplyError> {
279 self.receiver
280 .blocking_recv()
281 .unwrap_or(Err(ManagedCommandApplyError::AcknowledgementDisconnected))
282 }
283}
284
285impl Future for ManagedCommandReceipt {
286 type Output = Result<ManagedCommandAcknowledgement, ManagedCommandApplyError>;
287 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
288 match Pin::new(&mut self.receiver).poll(cx) {
289 Poll::Ready(Ok(result)) => Poll::Ready(result),
290 Poll::Ready(Err(_)) => {
291 Poll::Ready(Err(ManagedCommandApplyError::AcknowledgementDisconnected))
292 }
293 Poll::Pending => Poll::Pending,
294 }
295 }
296}
297
298pub(super) enum ManagedCommand {
299 UpdateTargets {
300 targets: Vec<ManagedTargetConfig>,
301 acknowledgement:
302 oneshot::Sender<Result<ManagedCommandAcknowledgement, ManagedCommandApplyError>>,
303 },
304}
305
306#[must_use = "await the receipt to observe durable stop acknowledgement"]
312pub struct ManagedStopReceipt {
313 future: Pin<Box<dyn Future<Output = ()> + Send>>,
314}
315
316impl ManagedStopReceipt {
317 fn new(mut status: ManagedStatusSubscription) -> Self {
318 let future = Box::pin(async move {
319 while !{
320 let snapshot = status.borrow();
321 snapshot.stop_requested
322 || matches!(
323 snapshot.lifecycle,
324 ManagedLifecycle::Completed
325 | ManagedLifecycle::Failed
326 | ManagedLifecycle::Abandoned
327 )
328 } {
329 if status.changed().await.is_err() {
332 break;
333 }
334 }
335 });
336 Self { future }
337 }
338}
339
340impl Future for ManagedStopReceipt {
341 type Output = ();
342
343 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
344 self.future.as_mut().poll(cx)
345 }
346}
347
348fn build_task(
349 config: ManagedClientConfig,
350 targets: Vec<ManagedTargetConfig>,
351) -> Result<(ManagedClientTask, ManagedClientHandle), ManagedConfigError> {
352 if config.event_capacity == 0 {
353 return Err(ManagedConfigError::ZeroEventCapacity);
354 }
355 if config.event_capacity > MAX_BROADCAST_CHANNEL_CAPACITY {
356 return Err(ManagedConfigError::EventCapacityTooLarge {
357 configured: config.event_capacity,
358 maximum: MAX_BROADCAST_CHANNEL_CAPACITY,
359 });
360 }
361 if config.command_capacity == 0 {
362 return Err(ManagedConfigError::ZeroCommandCapacity);
363 }
364 if config.command_capacity > tokio::sync::Semaphore::MAX_PERMITS {
365 return Err(ManagedConfigError::CommandCapacityTooLarge {
366 configured: config.command_capacity,
367 maximum: tokio::sync::Semaphore::MAX_PERMITS,
368 });
369 }
370 if config.max_live_target_generations == 0 {
371 return Err(ManagedConfigError::ZeroLiveGenerationLimit);
372 }
373 if Instant::now().checked_add(config.final_drain).is_none() {
374 return Err(ManagedConfigError::UnschedulableFinalDrain {
375 duration: config.final_drain,
376 });
377 }
378 if targets.len() > config.max_live_target_generations {
379 return Err(ManagedConfigError::TooManyTargets {
380 configured: targets.len(),
381 limit: config.max_live_target_generations,
382 });
383 }
384 if targets.is_empty() && config.completion == ManagedCompletionPolicy::FinishWhenQuiescent {
385 return Err(ManagedConfigError::EmptyInitialTargets);
386 }
387
388 let mut ids = HashSet::with_capacity(targets.len());
389 let mut runtimes = Vec::with_capacity(targets.len());
390 let mut next_generation = 1_u64;
391 for target in targets {
392 if !ids.insert(target.id.clone()) {
393 return Err(ManagedConfigError::DuplicateTargetId { id: target.id });
394 }
395 let generation = next_generation;
396 next_generation = next_generation
397 .checked_add(1)
398 .ok_or(ManagedConfigError::GenerationExhausted)?;
399 let mut client_config = config.client.clone();
400 client_config.address_family = target
401 .address_family
402 .unwrap_or(config.client.address_family);
403 client_config.auth = target.auth.resolve(&config.client.auth);
404 validate_target_config(&client_config).map_err(|source| {
405 ManagedConfigError::InvalidTarget {
406 id: target.id.clone(),
407 source,
408 }
409 })?;
410 runtimes.push(TargetRuntime {
411 instance: TargetInstance {
412 id: target.id.clone(),
413 generation,
414 },
415 config: target.clone(),
416 membership: TargetMembership::Desired,
417 server_addr: Arc::from(target.server_addr),
418 remote: None,
419 counters: TargetCounters::default(),
420 phase: Some(TargetPhase::Pending { client_config }),
421 });
422 }
423
424 let (event_sender, _) = broadcast::channel(config.event_capacity);
425 let weak_events = event_sender.downgrade();
426 let (wake_sender, wake_receiver) = watch::channel(());
427 let stop = Arc::new(StopSignal {
428 requested: AtomicBool::new(false),
429 update_admission: AtomicU8::new(UpdateAdmission::Open as u8),
430 wake: wake_sender,
431 });
432 let history = OutcomeHistory::new(config.outcome_history_limit);
433 let initial_targets = runtimes
434 .iter()
435 .map(|target| ManagedTargetStatus {
436 target: target.instance.clone(),
437 desired: target.membership.is_desired(),
438 lifecycle: ManagedTargetLifecycle::Pending,
439 outcome: None,
440 server_addr: Arc::clone(&target.server_addr),
441 remote: None,
442 })
443 .collect::<Vec<_>>();
444 let phase = TaskPhase::NotStarted;
445 let initial = Arc::new(ManagedStatus {
446 lifecycle: phase.lifecycle(),
447 stop_requested: false,
448 applied_command_sequence: 0,
449 desired_target_count: runtimes.len(),
450 connecting_target_count: 0,
451 opening_target_count: 0,
452 active_target_count: 0,
453 draining_target_count: 0,
454 closing_target_count: 0,
455 terminal_target_count: 0,
456 total_target_outcomes: 0,
457 successful_target_outcomes: 0,
458 failed_target_outcomes: 0,
459 peer_closed_target_outcomes: 0,
460 discarded_target_outcomes: 0,
461 targets: Arc::from(initial_targets.into_boxed_slice()),
462 recent_target_outcomes: history.recent(),
463 final_outcome: phase.final_outcome(),
464 });
465 let (status_sender, status_receiver) = watch::channel(initial);
466 let (command_sender, command_receiver) = mpsc::channel(config.command_capacity);
467 let max_live_target_generations = config.max_live_target_generations;
468 let resources = TaskResources {
469 status: status_sender,
470 events: Some(event_sender),
471 stop: Arc::clone(&stop),
472 };
473 let task = ManagedClientTask {
474 #[cfg(test)]
475 timeout_inspections: std::cell::RefCell::new(Vec::new()),
476 phase,
477 config,
478 targets: runtimes,
479 commands: command_receiver,
480 history,
481 resources: Some(resources),
482 wake: Some(arm_wake(wake_receiver)),
483 timer: None,
484 cursor: 0,
485 timeout_cursor: 0,
486 timeout_backlog: VecDeque::with_capacity(TIMEOUT_WORK_BUDGET),
487 scan_remaining: 0,
488 send_cursor: 0,
489 burst_remaining: 0,
490 stagger_remaining: 0,
491 send_gate: None,
492 last_stagger_send: None,
493 stop_observed: false,
494 next_generation,
495 applied_command_sequence: 0,
496 };
497 let handle = ManagedClientHandle {
498 stop,
499 commands: command_sender,
500 max_live_target_generations,
501 status: status_receiver,
502 events: weak_events,
503 };
504 Ok((task, handle))
505}
506
507pub(super) fn validate_target_config(config: &ClientConfig) -> Result<(), ClientError> {
508 config.open.validate()?;
509 SessionMachine::validate_config(config)?;
510 if let Some(ttl) = config.socket.ttl {
511 validate_ttl(ttl)?;
512 }
513 Ok(())
514}