Skip to content

Commit eb21e4b

Browse files
Retry on tar extraction errors (#9753)
## Summary So the error here is: ```rust ExtractError("cpython-3.11.11%2B20241206-aarch64-apple-darwin-install_only_stripped.tar.gz", Io(Custom { kind: UnexpectedEof, error: TarError { desc: "failed to unpack `/Users/crmarsh/.local/share/uv/python/.cache/.tmpkqFzqE/python/lib/libpython3.11.dylib`", io: Custom { kind: UnexpectedEof, error: TarError { desc: "failed to unpack `python/lib/libpython3.11.dylib` into `/Users/crmarsh/.local/share/uv/python/.cache/.tmpkqFzqE/python/lib/libpython3.11.dylib`", io: Custom { kind: UnexpectedEof, error: "unexpected end of file" } } } } })) ``` This isn't a Reqwest error, so we miss it in `is_extended_transient_error`. We could add `TarError` or `ExtractError` here, but... should we? This PR just extends it to any error that has an IO source. I don't see much of a downside. Closes #9747. ## Test Plan First, ran: `uv run ./scripts/create-python-mirror.py --name cpython --arch aarch64 --os darwin`. Then, dropped this into `./scripts/mirror/server.py`: ```python import os import random from http.server import SimpleHTTPRequestHandler, HTTPServer class GlitchyStaticServer(SimpleHTTPRequestHandler): def do_GET(self): """Handle GET request.""" file_path = self.translate_path(self.path) if not os.path.exists(file_path): self.send_error(404, "File not found") return try: with open(file_path, 'rb') as f: file_content = f.read() # Introduce an "unexpected end of file" glitch randomly if random.random() < 0.75: # 75% chance of glitch glitch_point = random.randint(1, len(file_content) - 1) file_content = file_content[:glitch_point] self.send_response(200) self.send_header("Content-type", self.guess_type(file_path)) self.send_header("Content-Length", len(file_content)) self.end_headers() self.wfile.write(file_content) except Exception as e: self.send_error(500, f"Internal Server Error: {e}") def run(server_class=HTTPServer, handler_class=GlitchyStaticServer, port=8080): """Run the server.""" server_address = ('', port) httpd = server_class(server_address, handler_class) print(f"Serving on port {port} with glitchy behavior") httpd.serve_forever() if __name__ == "__main__": run() ``` Then ran `python server.py` from that directory. From there, ran `UV_PYTHON_INSTALL_MIRROR="http://localhost:8080" cargo run python install 3.11 --reinstall --verbose` to reliably test retries.
1 parent 85a4fb4 commit eb21e4b

1 file changed

Lines changed: 9 additions & 44 deletions

File tree

crates/uv-client/src/base_client.rs

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use uv_warnings::warn_user_once;
2525
use crate::linehaul::LineHaul;
2626
use crate::middleware::OfflineMiddleware;
2727
use crate::tls::read_identity;
28-
use crate::{Connectivity, WrappedReqwestError};
28+
use crate::Connectivity;
2929

3030
pub const DEFAULT_RETRIES: u32 = 3;
3131

@@ -458,51 +458,16 @@ impl RetryableStrategy for UvRetryableStrategy {
458458
pub fn is_extended_transient_error(err: &dyn Error) -> bool {
459459
trace!("Attempting to retry error: {err:?}");
460460

461-
if let Some(err) = find_source::<WrappedReqwestError>(&err) {
462-
// First, look for `WrappedReqwestError`, which wraps `reqwest::Error` but doesn't always
463-
// include it in the source.
464-
if let Some(io) = find_source::<std::io::Error>(&err) {
465-
if io.kind() == std::io::ErrorKind::ConnectionReset
466-
|| io.kind() == std::io::ErrorKind::UnexpectedEof
467-
{
468-
trace!(
469-
"Retrying error: `ConnectionReset` or `UnexpectedEof` (`WrappedReqwestError`)"
470-
);
471-
return true;
472-
}
473-
trace!("Cannot retry error: not one of `ConnectionReset` or `UnexpectedEof` (`WrappedReqwestError`)");
474-
} else {
475-
trace!("Cannot retry error: not an IO error (`WrappedReqwestError`)");
476-
}
477-
} else if let Some(err) = find_source::<reqwest_middleware::Error>(&err) {
478-
// Next, look for `reqwest_middleware::Error`, which wraps `reqwest::Error`, but also
479-
// includes errors from the middleware stack.
480-
if let Some(io) = find_source::<std::io::Error>(&err) {
481-
if io.kind() == std::io::ErrorKind::ConnectionReset
482-
|| io.kind() == std::io::ErrorKind::UnexpectedEof
483-
{
484-
trace!("Retrying error: `ConnectionReset` or `UnexpectedEof` (`reqwest_middleware::Error`)");
485-
return true;
486-
}
487-
trace!("Cannot retry error: not one of `ConnectionReset` or `UnexpectedEof` (`reqwest_middleware::Error`)");
488-
} else {
489-
trace!("Cannot retry error: not an IO error (`reqwest_middleware::Error`)");
490-
}
491-
} else if let Some(err) = find_source::<reqwest::Error>(&err) {
492-
// Finally, look for `reqwest::Error`, which is the most common error type.
493-
if let Some(io) = find_source::<std::io::Error>(&err) {
494-
if io.kind() == std::io::ErrorKind::ConnectionReset
495-
|| io.kind() == std::io::ErrorKind::UnexpectedEof
496-
{
497-
trace!("Retrying error: `ConnectionReset` or `UnexpectedEof` (`reqwest::Error`)");
498-
return true;
499-
}
500-
trace!("Cannot retry error: not one of `ConnectionReset` or `UnexpectedEof` (`reqwest::Error`)");
501-
} else {
502-
trace!("Cannot retry error: not an IO error (`reqwest::Error`)");
461+
if let Some(io) = find_source::<std::io::Error>(&err) {
462+
if io.kind() == std::io::ErrorKind::ConnectionReset
463+
|| io.kind() == std::io::ErrorKind::UnexpectedEof
464+
{
465+
trace!("Retrying error: `ConnectionReset` or `UnexpectedEof`");
466+
return true;
503467
}
468+
trace!("Cannot retry error: not one of `ConnectionReset` or `UnexpectedEof`");
504469
} else {
505-
trace!("Cannot retry error: not a reqwest error");
470+
trace!("Cannot retry error: not an IO error");
506471
}
507472

508473
false

0 commit comments

Comments
 (0)