Draft mid-streaming error retry

This commit is contained in:
konstin 2025-06-16 10:59:48 +02:00
parent 792512556c
commit 2a76a09c34

View file

@ -1,8 +1,13 @@
use std::{env, io};
use assert_fs::fixture::{ChildPath, FileWriteStr, PathChild};
use http::StatusCode;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Method, Request, Response};
use hyper_util::rt::TokioIo;
use serde_json::json;
use std::{convert::Infallible, env, io};
use tokio::net::TcpListener;
use wiremock::matchers::method;
use wiremock::{Mock, MockServer, ResponseTemplate};
@ -36,6 +41,41 @@ async fn io_error_server() -> (MockServer, String) {
(server, mock_server_uri)
}
/// Creates a server that sends partial HTTP response data, then drops the connection.
async fn mid_stream_io_error_server() -> (tokio::task::JoinHandle<()>, String) {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server_task = tokio::spawn(async move {
// Handle multiple connections (for retries)
loop {
if let Ok((mut socket, _)) = listener.accept().await {
// Spawn a task for each connection to handle them concurrently
tokio::spawn(async move {
// Read the incoming HTTP request (we don't parse it, just consume it)
let mut buffer = [0; 1024];
let _ = socket.read(&mut buffer).await;
// Send a partial HTTP response - start with valid headers but incomplete body
let partial_response = b"HTTP/1.1 200 OK\r\nContent-Length: 1000000\r\nContent-Type: application/octet-stream\r\n\r\n";
let _ = socket.write_all(partial_response).await;
// Send some initial data
let partial_data = b"PK\x03\x04"; // Start of a ZIP file (wheel file)
let _ = socket.write_all(partial_data).await;
// Wait a bit then drop the connection abruptly
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
drop(socket);
});
}
}
});
let server_uri = format!("http://{addr}");
(server_task, server_uri)
}
/// Check the simple index error message when the server returns HTTP status 500, a retryable error.
#[tokio::test]
async fn simple_http_500() {
@ -195,6 +235,45 @@ async fn direct_url_io_error() {
");
}
/// Check the direct package URL error message when the server sends partial data then errors.
#[tokio::test]
async fn direct_url_mid_stream_io_error() {
let context = TestContext::new("3.12");
let (server_task, mock_server_uri) = mid_stream_io_error_server().await;
let tqdm_url = format!(
"{mock_server_uri}/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl"
);
tokio::task::spawn_blocking(move || {
let filters = vec![(mock_server_uri.as_str(), "[SERVER]")];
uv_snapshot!(filters, context
.pip_install()
.arg(format!("tqdm @ {tqdm_url}")), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download `tqdm @ [SERVER]/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl`
Request failed after 3 retries
Failed to read metadata: `[SERVER]/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl`
Failed to read from zip file
an upstream reader returned an error: error decoding response body
error decoding response body
request or response body error
error reading a body from connection
end of file before message length reached
");
})
.await
.unwrap();
// Clean up the server task
server_task.abort();
}
fn write_python_downloads_json(context: &TestContext, mock_server_uri: &String) -> ChildPath {
let python_downloads_json = context.temp_dir.child("python_downloads.json");
let interpreter = json!({