From a49cba488b0f61cf2eceda3114869bce6fdae7ad Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Thu, 29 Oct 2026 23:56:38 +0100 Subject: [PATCH] fix(progress): close the sink on failed transfers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A download that died mid-stream (read error, digest mismatch) returned before progress.finish(), leaving the sink with an open operation — a lingering progress bar in the CLI and a broken begin/finish pairing for any other sink. --- crates/ecrou/src/download.rs | 6 +++++- crates/ecrou/src/registry.rs | 10 +++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/ecrou/src/download.rs b/crates/ecrou/src/download.rs index 52ba5fd..abecf07 100644 --- a/crates/ecrou/src/download.rs +++ b/crates/ecrou/src/download.rs @@ -257,7 +257,6 @@ fn download_file( progress.advance(n as u64); } - progress.finish(); hasher.verify(expected)?; if expected.is_some() { log::debug!("Verified content digest: {}", dest.display()); @@ -270,6 +269,11 @@ fn download_file( Ok(()) })(); + // The operation ended — success or not. A sink is told finish() + // exactly once per begin(), so a failed transfer leaves no bar + // hanging and no implicit open operation behind it. + progress.finish(); + if result.is_err() { // Best-effort cleanup; ignore errors (file may not exist if creation failed). let _ = std::fs::remove_file(&temp_dest); diff --git a/crates/ecrou/src/registry.rs b/crates/ecrou/src/registry.rs index 6079edd..0c27d24 100644 --- a/crates/ecrou/src/registry.rs +++ b/crates/ecrou/src/registry.rs @@ -487,18 +487,22 @@ fn download_blob_with_auth( } file.flush().context("Failed to flush the layer file")?; - progress.finish(); // The blob was addressed by this digest in the manifest; a mismatch means // truncated or tampered content. The blob lives in a temp directory that // is discarded on error, so a bad layer never reaches the cache. - hasher.verify(Some(expected_digest)).with_context(|| { + let verified = hasher.verify(Some(expected_digest)).with_context(|| { format!( "layer blob {} failed verification against manifest digest", url ) - })?; + }); + // The operation ended — success or not. A sink is told finish() + // exactly once per begin(), so a failed layer leaves no bar + // hanging and no implicit open operation behind it. + progress.finish(); + verified?; Ok(()) }