ChatGPT Codex 5.5 xhigh refactored even more

This commit is contained in:
2026-05-02 15:31:37 +02:00
parent 86d0f93ede
commit b4585b663a
24 changed files with 2160 additions and 1972 deletions
@@ -0,0 +1,223 @@
//! Peer liveness checks and stale-peer cleanup.
use std::{
collections::{HashMap, HashSet},
sync::Arc,
time::Duration,
};
use tokio::{
sync::{RwLock, mpsc::UnboundedSender},
task::JoinHandle,
};
use crate::{
PeerEvent,
config::{PEER_PING_IDLE_SECS, PEER_PING_INTERVAL_SECS, peer_stale_timeout},
events,
network::ping_peer,
peer_db::{PeerGameDB, PeerId},
};
/// Runs the ping service to check peer liveness.
pub async fn run_ping_service(
tx_notify_ui: UnboundedSender<PeerEvent>,
peer_game_db: Arc<RwLock<PeerGameDB>>,
downloading_games: Arc<RwLock<HashSet<String>>>,
active_downloads: Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
) {
log::info!(
"Starting ping service ({PEER_PING_INTERVAL_SECS}s interval, \
{}s idle threshold, {}s timeout)",
PEER_PING_IDLE_SECS,
peer_stale_timeout().as_secs()
);
let mut interval = tokio::time::interval(Duration::from_secs(PEER_PING_INTERVAL_SECS));
loop {
interval.tick().await;
ping_idle_peers(
&peer_game_db,
&downloading_games,
&active_downloads,
&tx_notify_ui,
)
.await;
prune_stale_peers(
&peer_game_db,
&downloading_games,
&active_downloads,
&tx_notify_ui,
)
.await;
}
}
async fn ping_idle_peers(
peer_game_db: &Arc<RwLock<PeerGameDB>>,
downloading_games: &Arc<RwLock<HashSet<String>>>,
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
let peer_snapshots = { peer_game_db.read().await.peer_liveness_snapshot() };
for (peer_id, peer_addr, last_seen) in peer_snapshots {
if last_seen.elapsed() < Duration::from_secs(PEER_PING_IDLE_SECS) {
continue;
}
let tx_notify_ui = tx_notify_ui.clone();
let peer_game_db = peer_game_db.clone();
let downloading_games = downloading_games.clone();
let active_downloads = active_downloads.clone();
tokio::spawn(async move {
match ping_peer(peer_addr).await {
Ok(true) => {
peer_game_db.write().await.update_last_seen(&peer_id);
}
Ok(false) => {
log::warn!("Peer {peer_addr} failed ping check");
remove_peer_and_refresh(
&peer_game_db,
&downloading_games,
&active_downloads,
&tx_notify_ui,
peer_id,
"Removed stale peer",
)
.await;
}
Err(err) => {
log::error!("Failed to ping peer {peer_addr}: {err}");
remove_peer_and_refresh(
&peer_game_db,
&downloading_games,
&active_downloads,
&tx_notify_ui,
peer_id,
"Removed peer due to ping error",
)
.await;
}
}
});
}
}
async fn prune_stale_peers(
peer_game_db: &Arc<RwLock<PeerGameDB>>,
downloading_games: &Arc<RwLock<HashSet<String>>>,
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
let stale_peers = {
peer_game_db
.read()
.await
.get_stale_peer_ids(peer_stale_timeout())
};
let mut removed_any = false;
for peer_id in stale_peers {
removed_any |= remove_peer(peer_game_db, tx_notify_ui, peer_id, "Removed stale peer").await;
}
if removed_any {
events::emit_peer_game_list(peer_game_db, tx_notify_ui).await;
handle_active_downloads_without_peers(
peer_game_db,
downloading_games,
active_downloads,
tx_notify_ui,
)
.await;
}
}
async fn remove_peer_and_refresh(
peer_game_db: &Arc<RwLock<PeerGameDB>>,
downloading_games: &Arc<RwLock<HashSet<String>>>,
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
peer_id: PeerId,
log_label: &str,
) {
if remove_peer(peer_game_db, tx_notify_ui, peer_id, log_label).await {
events::emit_peer_game_list(peer_game_db, tx_notify_ui).await;
handle_active_downloads_without_peers(
peer_game_db,
downloading_games,
active_downloads,
tx_notify_ui,
)
.await;
}
}
async fn remove_peer(
peer_game_db: &Arc<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
peer_id: PeerId,
log_label: &str,
) -> bool {
let removed_peer = { peer_game_db.write().await.remove_peer(&peer_id) };
let Some(peer) = removed_peer else {
return false;
};
log::info!("{log_label}: {}", peer.addr);
events::emit_peer_lost(peer_game_db, tx_notify_ui, peer.addr).await;
true
}
async fn handle_active_downloads_without_peers(
peer_game_db: &Arc<RwLock<PeerGameDB>>,
downloading_games: &Arc<RwLock<HashSet<String>>>,
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
let active_ids = {
downloading_games
.read()
.await
.iter()
.cloned()
.collect::<Vec<_>>()
};
if active_ids.is_empty() {
return;
}
for id in active_ids {
if peers_still_have_game(peer_game_db, &id).await {
continue;
}
let removed_from_tracking = {
let mut guard = downloading_games.write().await;
guard.remove(&id)
};
if !removed_from_tracking {
continue;
}
if let Some(handle) = { active_downloads.write().await.remove(&id) } {
handle.abort();
}
events::send(
tx_notify_ui,
PeerEvent::DownloadGameFilesAllPeersGone { id },
"DownloadGameFilesAllPeersGone",
);
}
}
async fn peers_still_have_game(peer_game_db: &Arc<RwLock<PeerGameDB>>, game_id: &str) -> bool {
let guard = peer_game_db.read().await;
!guard.peers_with_game(game_id).is_empty()
}