mirror of
https://github.com/telemt/telemt.git
synced 2026-10-08 18:35:58 +03:00
143 lines
4.7 KiB
Rust
143 lines
4.7 KiB
Rust
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicU8, Ordering};
|
|
|
|
use super::{ConnRegistry, WriterReplacementState};
|
|
|
|
/// Result of atomically committing a client-to-writer binding.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub(in crate::transport::middle_proxy) enum WriterBindOutcome {
|
|
/// The client route was bound to the selected writer.
|
|
Bound,
|
|
/// The client route disappeared before the binding commit.
|
|
RouteMissing,
|
|
/// The selected writer disappeared before the binding commit.
|
|
WriterMissing,
|
|
/// The selected writer is retiring and no longer accepts new clients.
|
|
WriterRetiring,
|
|
}
|
|
|
|
/// Cancellation-safe ownership of one prospective writer replacement.
|
|
pub(in crate::transport::middle_proxy) struct WriterReplacementReservation<'a> {
|
|
registry: &'a ConnRegistry,
|
|
writer_id: u64,
|
|
state: Arc<AtomicU8>,
|
|
require_idle: bool,
|
|
committed: bool,
|
|
}
|
|
|
|
impl WriterReplacementReservation<'_> {
|
|
/// Returns the stable identifier of the prospective victim.
|
|
pub(in crate::transport::middle_proxy) fn writer_id(&self) -> u64 {
|
|
self.writer_id
|
|
}
|
|
|
|
/// Returns the registry whose binding lock linearizes this reservation.
|
|
pub(super) fn registry(&self) -> &ConnRegistry {
|
|
self.registry
|
|
}
|
|
|
|
/// Returns the writer-local replacement state identity captured at reservation time.
|
|
pub(super) fn state(&self) -> &Arc<AtomicU8> {
|
|
&self.state
|
|
}
|
|
|
|
/// Reports whether commit must revalidate that the victim remains unbound.
|
|
pub(super) fn requires_idle(&self) -> bool {
|
|
self.require_idle
|
|
}
|
|
|
|
/// Transfers retirement ownership to the published replacement.
|
|
pub(in crate::transport::middle_proxy) fn mark_committed(&mut self) {
|
|
self.committed = true;
|
|
}
|
|
}
|
|
|
|
impl Drop for WriterReplacementReservation<'_> {
|
|
fn drop(&mut self) {
|
|
if self.committed {
|
|
return;
|
|
}
|
|
let preparing = WriterReplacementState::Preparing as u8;
|
|
let open = WriterReplacementState::Open as u8;
|
|
let _ = self
|
|
.state
|
|
.compare_exchange(preparing, open, Ordering::AcqRel, Ordering::Acquire);
|
|
}
|
|
}
|
|
|
|
impl ConnRegistry {
|
|
/// Claims an idle writer as a prospective replacement victim.
|
|
///
|
|
/// Preparing prevents duplicate replacement work but intentionally permits new client binds.
|
|
/// Commit revalidates idleness under the existing binding lock before blocking future binds.
|
|
pub(in crate::transport::middle_proxy) async fn try_reserve_writer_replacement(
|
|
&self,
|
|
writer_id: u64,
|
|
) -> Option<WriterReplacementReservation<'_>> {
|
|
self.reserve_writer_replacement(writer_id, true).await
|
|
}
|
|
|
|
/// Claims a writer while retaining its existing client associations through replacement.
|
|
pub(in crate::transport::middle_proxy) async fn try_reserve_writer_replacement_preserving_clients(
|
|
&self,
|
|
writer_id: u64,
|
|
) -> Option<WriterReplacementReservation<'_>> {
|
|
self.reserve_writer_replacement(writer_id, false).await
|
|
}
|
|
|
|
async fn reserve_writer_replacement(
|
|
&self,
|
|
writer_id: u64,
|
|
require_idle: bool,
|
|
) -> Option<WriterReplacementReservation<'_>> {
|
|
let binding = self.binding.inner.lock().await;
|
|
let state = self
|
|
.writers
|
|
.map
|
|
.get(&writer_id)
|
|
.map(|route| Arc::clone(&route.replacement_state))?;
|
|
if require_idle
|
|
&& binding
|
|
.conns_for_writer
|
|
.get(&writer_id)
|
|
.is_none_or(|conn_ids| !conn_ids.is_empty())
|
|
{
|
|
return None;
|
|
}
|
|
state
|
|
.compare_exchange(
|
|
WriterReplacementState::Open as u8,
|
|
WriterReplacementState::Preparing as u8,
|
|
Ordering::AcqRel,
|
|
Ordering::Acquire,
|
|
)
|
|
.ok()?;
|
|
|
|
Some(WriterReplacementReservation {
|
|
registry: self,
|
|
writer_id,
|
|
state,
|
|
require_idle,
|
|
committed: false,
|
|
})
|
|
}
|
|
|
|
/// Returns fixed-cardinality gauges for preparing and retiring replacements.
|
|
pub(in crate::transport::middle_proxy) fn writer_replacement_counts(&self) -> (usize, usize) {
|
|
let mut preparing = 0usize;
|
|
let mut retiring = 0usize;
|
|
for route in &self.writers.map {
|
|
match route.replacement_state.load(Ordering::Acquire) {
|
|
state if state == WriterReplacementState::Preparing as u8 => {
|
|
preparing = preparing.saturating_add(1);
|
|
}
|
|
state if state == WriterReplacementState::Retiring as u8 => {
|
|
retiring = retiring.saturating_add(1);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
(preparing, retiring)
|
|
}
|
|
}
|