use std::collections::{HashSet, VecDeque};
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Instant;
use parking_lot::Mutex;
use peashape::{Lane, Target};
use rand::SeedableRng;
use rand::seq::IndexedRandom;
use rand_chacha::ChaCha20Rng;
use tokio::sync::Notify;
use tokio::task::JoinHandle;
use tokio::time;
use tracing::debug;
use crate::node::NodeInner;
use crate::sample::{PeerEntry, PeerSample};
pub(crate) struct Explorer {
inner: Arc<NodeInner>,
rng: Mutex<ChaCha20Rng>,
recent: Mutex<VecDeque<SocketAddr>>,
wake: Notify,
handle: Mutex<Option<JoinHandle<()>>>,
recent_depth: usize,
}
impl Explorer {
pub fn new(inner: Arc<NodeInner>, seed: u64) -> Self {
let recent_depth = inner.recent_window();
Self {
inner,
rng: Mutex::new(ChaCha20Rng::seed_from_u64(seed)),
recent: Mutex::new(VecDeque::with_capacity(recent_depth)),
wake: Notify::new(),
handle: Mutex::new(None),
recent_depth,
}
}
pub fn reseed(&self, seed: u64) {
*self.rng.lock() = ChaCha20Rng::seed_from_u64(seed);
}
pub fn poke(&self) {
self.wake.notify_one();
}
pub fn spawn(&self) {
let inner = self.inner.clone();
let handle = tokio::spawn(async move { explorer_loop(inner).await });
*self.handle.lock() = Some(handle);
}
pub fn abort(&self) {
if let Some(h) = self.handle.lock().take() {
h.abort();
}
}
}
async fn explorer_loop(inner: Arc<NodeInner>) {
let interval = inner.config.exchange_interval;
let mut ticker = time::interval(interval);
ticker.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
let explorer = inner
.explorer
.get()
.expect("explorer initialized before explorer_loop starts");
let mut wake = std::pin::pin!(explorer.wake.notified());
ticker.tick().await;
loop {
tokio::select! {
_ = ticker.tick() => {}
_ = inner.shutdown_waiter.notified() => break,
_ = wake.as_mut() => {
wake.set(explorer.wake.notified());
}
}
if inner.shutting_down.load(Ordering::SeqCst) {
break;
}
if let Err(e) = tick(&inner) {
debug!(parent: inner.span.clone(), "explorer tick failed: {e}");
}
}
}
fn tick(inner: &Arc<NodeInner>) -> Result<(), crate::Error> {
let now = Instant::now();
let evicted = inner.view.lock().reclassify_and_evict(now);
for addr in evicted {
inner
.events
.dispatch(crate::node::DiscoveryEvent::PeerEvicted { addr });
}
if let Some(target) = pick_initiation_target(inner) {
ship_sample(inner, target)?;
}
Ok(())
}
fn ship_sample(inner: &Arc<NodeInner>, target: SocketAddr) -> Result<(), crate::Error> {
let explorer = inner
.explorer
.get()
.expect("explorer initialized before ship_sample");
let exclude: HashSet<SocketAddr> = [target].into_iter().collect();
let entries: Vec<PeerEntry> = {
let view = inner.view.lock();
let mut rng = explorer.rng.lock();
view.sample(inner.config.sample_size, &exclude, &mut rng)
};
let sample = PeerSample::from_entries(entries);
let needed = sample.encoded_size();
let available = inner.config.frame_size.saturating_sub(peashape::ID_SIZE);
if needed > available {
return Err(crate::Error::SampleTooLarge { needed, available });
}
let bytes = peashape::build_frame(inner.peashape.config(), &sample.encode()).1;
{
let mut recent = explorer.recent.lock();
recent.push_back(target);
while recent.len() > explorer.recent_depth {
recent.pop_front();
}
}
if let Err(e) = inner
.peashape
.shaper()
.enqueue_raw(Lane::High, Target::Unicast(target), bytes)
{
return Err(e.into());
}
inner
.events
.dispatch(crate::node::DiscoveryEvent::SampleSent {
to: target,
count: sample.len(),
});
Ok(())
}
fn pick_initiation_target(inner: &Arc<NodeInner>) -> Option<SocketAddr> {
let explorer = inner.explorer.get()?;
let connected = inner.peashape.connected_peers();
if connected.is_empty() {
return None;
}
let recent: HashSet<SocketAddr> = explorer.recent.lock().iter().copied().collect();
let mut rng = explorer.rng.lock();
let fresh: Vec<SocketAddr> = connected
.iter()
.copied()
.filter(|a| !recent.contains(a))
.collect();
let pool: &[SocketAddr] = if fresh.is_empty() { &connected } else { &fresh };
pool.choose(&mut rng).copied()
}
pub(crate) fn handle_received_sample(inner: &Arc<NodeInner>, sample: PeerSample) {
let now = Instant::now();
let (new_addrs, evicted): (
Vec<(SocketAddr, crate::view::PeerCategory)>,
Vec<SocketAddr>,
) = {
let mut view = inner.view.lock();
let before = view.snapshot_address_set();
let evicted = view.merge_sample(sample.entries(), now);
let after = view.snapshot_address_set();
let new_addrs = after
.iter()
.filter(|addr| !before.contains(addr))
.map(|addr| {
let category = view
.category(addr)
.unwrap_or(crate::view::PeerCategory::Recent);
(*addr, category)
})
.collect();
let evicted = evicted
.into_iter()
.filter(|addr| before.contains(addr))
.collect();
(new_addrs, evicted)
};
for (addr, category) in new_addrs {
inner
.events
.dispatch(crate::node::DiscoveryEvent::PeerDiscovered { addr, category });
}
for addr in evicted {
inner
.events
.dispatch(crate::node::DiscoveryEvent::PeerEvicted { addr });
}
inner
.events
.dispatch(crate::node::DiscoveryEvent::SampleReceived {
from: std::net::SocketAddr::from(([0, 0, 0, 0], 0)),
count: sample.len(),
});
if let Some(e) = inner.explorer.get() {
e.poke();
}
}