From b53b5cc000d0f86ad31e64a31d09b56636f7b424 Mon Sep 17 00:00:00 2001 From: Bendik Lynghaug Date: Sun, 16 Aug 2026 15:39:58 +0200 Subject: [PATCH] Bound every await in push_to; plain-git checkout for real this time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI (run 208) showed the Push RPC hanging past the test client's 120s read timeout: endpoint.connect and the delivery-verification observe loop had no bounds, so a wedged path hung the socket instead of failing. Connect now times out at 30s and each observe step at 60s, producing structured errors that name the stage. The checkout action is back out: run 212 proved the failure is not the action's version — the runner execs the action's JS with node inside the job container, and rust:1 has no node ('exec: "node": executable file not found'). Plain git needs nothing the container lacks. Co-Authored-By: Claude Fable 5 --- .gitea/workflows/ci.yml | 12 +++++++++++- .gitea/workflows/release.yml | 12 +++++++++++- varde-daemon/src/transfer.rs | 38 +++++++++++++++++++++++++----------- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d02f97d..4f60259 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -12,7 +12,17 @@ jobs: runs-on: ubuntu-latest container: rust:1 steps: - - uses: actions/checkout@v5 + # Not the checkout action: it runs on node, which the rust:1 job + # container doesn't have and this runner doesn't inject (the v5 + # run died with 'exec: "node": not found'). Plain git needs + # neither, and the token URL works against the private repo. + - name: checkout + run: | + git init -q . + git fetch --depth 1 \ + "https://gitea:${{ secrets.GITHUB_TOKEN }}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git" \ + "$GITHUB_SHA" + git checkout -q FETCH_HEAD - name: rustfmt run: | rustup component add rustfmt diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 1f79170..c477836 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -19,7 +19,17 @@ jobs: matrix: target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu] steps: - - uses: actions/checkout@v5 + # Not the checkout action: it runs on node, which the rust:1 job + # container doesn't have and this runner doesn't inject (the v5 + # run died with 'exec: "node": not found'). Plain git needs + # neither, and the token URL works against the private repo. + - name: checkout + run: | + git init -q . + git fetch --depth 1 \ + "https://gitea:${{ secrets.GITHUB_TOKEN }}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git" \ + "$GITHUB_SHA" + git checkout -q FETCH_HEAD - name: install tooling run: | diff --git a/varde-daemon/src/transfer.rs b/varde-daemon/src/transfer.rs index 78b7494..e977d27 100644 --- a/varde-daemon/src/transfer.rs +++ b/varde-daemon/src/transfer.rs @@ -7,6 +7,7 @@ use std::path::Path; use std::sync::Arc; +use std::time::Duration; use anyhow::{Context, Result}; use iroh::address_lookup::memory::MemoryLookup; @@ -170,11 +171,16 @@ impl Transfer { !self.metered.is_metered(), "metered connection: refusing to push" ); - let conn = self - .endpoint - .connect(peer, iroh_blobs::ALPN) - .await - .with_context(|| format!("connecting to {peer}"))?; + // Every await here is bounded: this runs inside a socket-API + // request, and a wedged peer must produce a structured error, + // not a hung client connection. + let conn = tokio::time::timeout( + Duration::from_secs(30), + self.endpoint.connect(peer, iroh_blobs::ALPN), + ) + .await + .map_err(|_| anyhow::anyhow!("connecting to {peer} timed out"))? + .with_context(|| format!("connecting to {peer}"))?; let request = match content.format { BlobFormat::Raw => { iroh_blobs::protocol::PushRequest::from(GetRequest::blob(content.hash)) @@ -240,7 +246,8 @@ impl Transfer { } /// Watch `hash` on the remote end of `conn` until its bitfield - /// reports the blob complete. + /// reports the blob complete. Bounded: the peer has already received + /// the bytes, so verification is bookkeeping, not transfer. async fn wait_remote_complete( &self, conn: &iroh::endpoint::Connection, @@ -251,13 +258,22 @@ impl Transfer { iroh_blobs::protocol::ObserveRequest::new(hash), ); let mut observe = std::pin::pin!(observe); - while let Some(bitfield) = observe.next().await { - let bitfield = bitfield.context("observing pushed content on the peer")?; - if bitfield.is_complete() { - return Ok(()); + loop { + let next = tokio::time::timeout(Duration::from_secs(60), observe.next()) + .await + .map_err(|_| anyhow::anyhow!("verifying pushed content on the peer timed out"))?; + match next { + Some(bitfield) => { + let bitfield = bitfield.context("observing pushed content on the peer")?; + if bitfield.is_complete() { + return Ok(()); + } + } + None => { + anyhow::bail!("peer stopped reporting before the pushed content completed") + } } } - anyhow::bail!("peer stopped reporting before the pushed content completed"); } /// Produce a ticket for out-of-band sharing of `content`.