fix(sources): bound the HTTP timeouts so a stalled peer cannot hang a command - #221
Open
luantaraschi wants to merge 1 commit into
Open
fix(sources): bound the HTTP timeouts so a stalled peer cannot hang a command#221luantaraschi wants to merge 1 commit into
luantaraschi wants to merge 1 commit into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #221 +/- ##
==========================================
+ Coverage 92.31% 92.53% +0.21%
==========================================
Files 42 42
Lines 23186 23274 +88
==========================================
+ Hits 21405 21537 +132
+ Misses 1781 1737 -44 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… command ureq leaves every timeout at None, so any endpoint that accepts the socket and then never answers blocks install, add and search forever, with a spinner on a TTY and in silence otherwise. search_all queries registries in sequence, so one wedged registry also blocks the others. Bound connect, receive response and receive body on the agent config, and name the timeout in the error instead of leaking ureq's phrasing. The phases are bounded one by one rather than with a single global timeout so that a slow but healthy download is not cut off midway. Closes eljulians#196
luantaraschi
force-pushed
the
fix/http-timeouts
branch
from
August 18, 2026 18:10
8884916 to
0940ac7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #196.
UreqClient::newonly configuredredirect_auth_headers, and ureq 3.4 leaves every entry ofTimeoutsatNone, so a peer that accepts the socket and never answers holds the command open with no way out.What changed:
connect10s,recv_response30s andrecv_body60s on the agent config.timed out fetching <url> (receive response)instead of ureq's owntimeout: receive response fetching <url>. The same helper coverspost_jsonandpost_json_with_bearer, which had the identical catch-all.Two shape decisions, both easy to reverse if you disagree:
Per phase instead of
timeout_global. Your sketch suggeststimeout_connectplustimeout_global. A global cap also bounds legitimate transfers, so a large directory entry on a slow link would start failing once it crosses the cap. Bounding each phase kills the hang (the reported case is "accepts and never answers", which isrecv_response) without putting a ceiling on healthy downloads. Worst case against a wedged endpoint is now about 100s per request instead of forever. Say the word and I swap it for a global timeout.No env override. The issue says "possibly env-overridable". I left it out: it would be a new public knob to carry in the README and SPEC, and with per phase timeouts nothing legitimate is cut off. Happy to add
SKILLFILE_HTTP_TIMEOUTif you want the escape hatch.Tests
Three unit tests in
crates/sources/src/http.rs. The first two were written before the fix:new_client_bounds_every_transport_phasereads the agent config back. On master it fails withleft: None, right: Some(10s).get_bytes_gives_up_on_a_peer_that_never_answersis your repro, as aTcpListenerthat accepts and stalls. Measured with the timeouts removed from the agent config: the call waited 3.001s, which is exactly how long the test peer stayed alive, so against a real wedged server it waits forever. With the fix and a 500ms configured timeout it returns an error in 0.5s.The private
with_timeoutsconstructor exists so that second test does not have to sit through the 30s default.a_refused_connection_keeps_the_existing_wordingpoints all four entry points at a closed loopback port and pins the non timeout wording (fetching <url>,posting to <url>), so the new error arm cannot quietly swallow the ordinary failures. Added after the first CI run, wherecodecov/patchflagged those arms as uncovered.Gates
Run in a
rust:1.97container against this branch:cargo fmt --checkcleancargo clippy --workspace --all-targets -- -D warningscleancargo test --workspaceclean, 18 test binaries, no failurescargo denyandcargo machetewere not run here. The change adds no dependency.This contribution was made with AI assistance. I reviewed the change and ran every command quoted above.