use std::hash::Hash;
use std::io;
use std::net::{IpAddr, SocketAddr};
use std::time::{Duration, Instant};
use ipnet::IpNet;
use tokio_util::sync::CancellationToken;
use tracing::{info, instrument, warn};
use crate::bounds::{Key, Value};
use super::{ConfigError, ReplicatedMap};
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct SyncState {
pub rounds: u64,
pub last_round_at: Option<Instant>,
pub peers: usize,
pub last_snapshot_at: Option<Instant>,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct RunOutcome {
pub final_snapshot: io::Result<()>,
}
impl<K: Key + Hash, V: Value> ReplicatedMap<K, V> {
pub async fn start_reconciliation(&self) {
let mut buf = Vec::new();
self.engine.start_reconciliation(&mut buf).await;
}
pub fn forget_peer(&self, peer: IpAddr) {
self.engine.decommission_peer(peer);
}
#[cfg(any(test, reconcile_internal_testing))]
pub fn members_snapshot(&self) -> std::collections::HashSet<std::net::IpAddr> {
self.engine.members_snapshot()
}
#[cfg(any(test, reconcile_internal_testing))]
pub fn peers_map_len(&self) -> usize {
self.engine.peers_map_len()
}
#[cfg(any(test, reconcile_internal_testing))]
pub fn replay_filter_len(&self) -> usize {
self.engine.replay_filter_len()
}
#[cfg(any(test, reconcile_internal_testing))]
pub fn tombstone_acks_len(&self) -> usize {
self.engine.tombstone_acks_len()
}
#[cfg(any(test, reconcile_internal_testing))]
pub fn bulk_dumps_in_flight_count(&self) -> usize {
self.engine.bulk_dumps_in_flight_count()
}
pub fn set_nets(&self, nets: &[IpNet]) -> Result<(), ConfigError> {
self.engine.set_nets(nets)
}
#[must_use]
pub fn add_net(&self, net: IpNet) -> bool {
self.engine.add_net(net)
}
#[must_use]
pub fn remove_net(&self, net: IpNet) -> bool {
self.engine.remove_net(net)
}
pub fn nets(&self) -> Vec<IpNet> {
self.engine.nets()
}
pub fn local_net(&self) -> IpNet {
self.engine.local_net()
}
pub fn set_remote_interval(&self, interval: u32) {
self.engine.set_remote_interval(interval);
}
pub fn set_remote_fanout(&self, fanout: usize) {
self.engine.set_remote_fanout(fanout);
}
pub fn set_reconcile_interval(&self, interval: Duration) {
self.engine.set_reconcile_interval(interval);
}
pub fn set_coalesce_window(&self, window: Duration) {
self.engine.set_coalesce_window(window);
}
#[cfg(any(test, reconcile_internal_testing))]
pub fn coalesce_window(&self) -> Duration {
self.engine.coalesce_window()
}
pub fn sync_state(&self) -> SyncState {
SyncState {
rounds: u64::from(self.engine.round()),
last_round_at: self.engine.last_round_at(),
peers: self.engine.peers_vec().len(),
last_snapshot_at: *self.last_snapshot_at.read(),
}
}
pub fn peers(&self) -> Vec<IpAddr> {
self.engine.peers_vec()
}
pub fn members(&self) -> Vec<IpAddr> {
self.engine.members_vec()
}
pub fn local_addr(&self) -> io::Result<SocketAddr> {
self.engine.local_addr()
}
#[instrument(name = "reconcile.store", skip_all)]
pub async fn run(self, shutdown: CancellationToken) -> RunOutcome {
info!("reconcile store starting");
let engine = self.engine.clone();
let tombstones = self.clone();
let snapshots = self.clone();
let discovery = self.clone();
tokio::select! {
() = async {
tokio::join!(
engine.run(),
tombstones.clear_expired_tombstones(),
snapshots.snapshot_periodically(),
discovery.discover_periodically(),
);
} => {}
() = shutdown.cancelled() => {
info!("reconcile store received shutdown signal");
}
}
info!("flushing final snapshot before returning");
let final_snapshot = self.snapshot_now();
if let Err(ref err) = final_snapshot {
warn!("final snapshot on shutdown failed: {err}");
}
RunOutcome { final_snapshot }
}
}