use super::connections::PeerReporter;
use local_async_utils::prelude::*;
use mtorrent_core::pwp::PeerOrigin;
use mtorrent_dht as dht;
use mtorrent_utils::info_stopwatch;
use std::time::Duration;
use tokio::sync::mpsc;
use tokio::time::{self, Instant};
const MIN_INTERVAL_BETWEEN_RESTARTS: Duration = min!(1);
pub async fn run_dht_search(
info_hash: [u8; 20],
dht_cmds: dht::CommandSink,
peer_reporter: PeerReporter,
pwp_listener_port: u16,
) {
loop {
let earliest_restart_time = Instant::now() + MIN_INTERVAL_BETWEEN_RESTARTS;
let (sender, mut result_receiver) = mpsc::channel(1024);
if let Err(e) = dht_cmds
.send(dht::Command::FindPeers {
info_hash,
callback: sender,
local_peer_port: pwp_listener_port,
})
.await
{
log::error!("Failed to start DHT search: {e}");
return;
}
let sw = info_stopwatch!("DHT search operation");
while let Some(peer_addr) = result_receiver.recv().await {
if !peer_reporter.report_discovered(peer_addr, PeerOrigin::Dht).await {
return;
}
}
drop(sw);
if Instant::now() < earliest_restart_time {
time::sleep_until(earliest_restart_time).await;
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
use tokio::{task, time};
#[tokio::test(start_paused = true)]
async fn test_restart_dht_search_when_it_finishes() {
let (cmd_sender, mut cmd_receiver) = mpsc::channel(1);
task::spawn(run_dht_search([0; 20], cmd_sender, PeerReporter::new_mock(), 6881));
task::yield_now().await;
let find_peers_cmd = cmd_receiver.try_recv().expect("search should have started");
let dht::Command::FindPeers { callback, .. } = find_peers_cmd else {
panic!("expected FindPeers command");
};
assert!(matches!(cmd_receiver.try_recv(), Err(mpsc::error::TryRecvError::Empty)));
drop(callback);
task::yield_now().await;
assert!(matches!(cmd_receiver.try_recv(), Err(mpsc::error::TryRecvError::Empty)));
time::sleep(MIN_INTERVAL_BETWEEN_RESTARTS).await;
task::yield_now().await;
let find_peers_cmd = cmd_receiver.try_recv().expect("search should have restarted");
let dht::Command::FindPeers { .. } = find_peers_cmd else {
panic!("expected FindPeers command");
};
assert!(matches!(cmd_receiver.try_recv(), Err(mpsc::error::TryRecvError::Empty)));
}
#[tokio::test(start_paused = true)]
async fn test_dont_restart_dht_search_periodically() {
let (cmd_sender, mut cmd_receiver) = mpsc::channel(1);
task::spawn(run_dht_search([0; 20], cmd_sender, PeerReporter::new_mock(), 6881));
task::yield_now().await;
let find_peers_cmd = cmd_receiver.try_recv().expect("search should have started");
assert!(matches!(find_peers_cmd, dht::Command::FindPeers { .. }));
assert!(matches!(cmd_receiver.try_recv(), Err(mpsc::error::TryRecvError::Empty)));
time::sleep(Duration::MAX).await;
assert!(matches!(cmd_receiver.try_recv(), Err(mpsc::error::TryRecvError::Empty)));
}
#[tokio::test]
async fn test_dht_search_fails_when_dht_is_shut_down() {
let (cmd_sender, _) = mpsc::channel(1);
let handle =
task::spawn(run_dht_search([0; 20], cmd_sender, PeerReporter::new_mock(), 6881));
task::yield_now().await;
assert!(
handle.is_finished(),
"search should have exited when DHT commands channel was closed"
);
}
}