fix(peer): drain cancelled download workers
Keep initial peer transfers, retry attempts, and chunk receivers structurally owned until they quiesce. Cancellation now stops opening new streams, flushes accepted file writes, and drains active work before ownership rollback can begin. Leave operation admission owned by the running download task when liveness detects that every source disappeared, and emit the peers-gone notification only once. Test Plan: - just fmt (Rust and configured formatters completed; 39 pre-existing rumdl findings remain) - just clippy - just test - git diff --cached --check
This commit is contained in:
6 files changed
+417
-127
No files matched your search
@@ -1,5 +1,6 @@
|
||||
use std::{collections::HashMap, net::SocketAddr, path::Path, sync::Arc};
|
||||
|
||||
use futures::stream::FuturesUnordered;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
@@ -11,6 +12,7 @@ use super::{
|
||||
progress::{DownloadProgressTracker, sample_download_progress},
|
||||
retry::{RetryContext, retry_failed_chunks},
|
||||
storage::{prepare_game_storage, sync_game_storage},
|
||||
task_drain::collect_or_drain_on_cancel,
|
||||
transport::download_from_peer,
|
||||
version_ini::{
|
||||
VersionIniBuffer,
|
||||
@@ -198,14 +200,14 @@ async fn download_transfer_chunks(
|
||||
) -> eyre::Result<()> {
|
||||
let plans = build_peer_plans(ctx.peers, transfer_descs, ctx.file_peer_map);
|
||||
|
||||
let mut tasks = Vec::new();
|
||||
let tasks = FuturesUnordered::new();
|
||||
for (peer_addr, plan) in plans {
|
||||
let game_root = ctx.game_root.clone();
|
||||
let game_id = ctx.game_id.to_string();
|
||||
let cancel_token = ctx.cancel_token.clone();
|
||||
let version_buffer = ctx.version_buffer.clone();
|
||||
let progress_tracker = ctx.progress_tracker.clone();
|
||||
tasks.push(tokio::spawn(async move {
|
||||
tasks.push(async move {
|
||||
download_from_peer(
|
||||
peer_addr,
|
||||
&game_id,
|
||||
@@ -216,23 +218,19 @@ async fn download_transfer_chunks(
|
||||
progress_tracker,
|
||||
)
|
||||
.await
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
let mut failed_chunks: Vec<DownloadChunk> = Vec::new();
|
||||
let mut last_err: Option<eyre::Report> = None;
|
||||
|
||||
for handle in tasks {
|
||||
for result in collect_or_drain_on_cancel(tasks, ctx.cancel_token, ctx.game_id).await? {
|
||||
if ctx.cancel_token.is_cancelled() {
|
||||
eyre::bail!("download cancelled for game {}", ctx.game_id);
|
||||
}
|
||||
|
||||
match handle.await {
|
||||
Ok(Ok(results)) => {
|
||||
if ctx.cancel_token.is_cancelled() {
|
||||
eyre::bail!("download cancelled for game {}", ctx.game_id);
|
||||
}
|
||||
|
||||
match result {
|
||||
Ok(results) => {
|
||||
collect_chunk_results(
|
||||
ctx.game_id,
|
||||
ctx.tx_notify_ui,
|
||||
@@ -241,11 +239,10 @@ async fn download_transfer_chunks(
|
||||
&mut last_err,
|
||||
);
|
||||
}
|
||||
Ok(Err(_)) | Err(_) if ctx.cancel_token.is_cancelled() => {
|
||||
Err(_) if ctx.cancel_token.is_cancelled() => {
|
||||
eyre::bail!("download cancelled for game {}", ctx.game_id);
|
||||
}
|
||||
Ok(Err(e)) => last_err = Some(e),
|
||||
Err(e) => last_err = Some(eyre::eyre!("task join error: {e}")),
|
||||
Err(e) => last_err = Some(e),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user