From 4a208f60f97b9e2fd9a2236cf2f74fdfbb58d8b4 Mon Sep 17 00:00:00 2001 From: Arlo Siemsen Date: Mon, 20 Apr 2026 11:05:54 -0500 Subject: [PATCH 1/4] move curl response processing into separate function --- src/cargo/util/network/http_async.rs | 37 +++++++++++++++------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/cargo/util/network/http_async.rs b/src/cargo/util/network/http_async.rs index 63163e32948..c3026cab848 100644 --- a/src/cargo/util/network/http_async.rs +++ b/src/cargo/util/network/http_async.rs @@ -242,6 +242,24 @@ impl WorkerServer { } } + fn process_response(mut easy: Easy2) -> Response { + let mut response = + std::mem::replace(&mut easy.get_mut().response, Response::new(Vec::new())); + if let Ok(status) = easy.response_code() + && status != 0 + && let Ok(status) = http::StatusCode::from_u16(status as u16) + { + *response.status_mut() = status; + } + // Would be nice to set HTTP version via `response.version_mut()`, but `curl` doesn't have it exposed. + let extensions = Extensions { + client_ip: easy.primary_ip().ok().flatten().map(str::to_string), + effective_url: easy.effective_url().ok().flatten().map(str::to_string), + }; + response.extensions_mut().insert(extensions); + response + } + /// Marks the start of a new timeout window. fn reset_low_speed_timeout(&mut self) { self.low_speed_window_start = Instant::now(); @@ -297,23 +315,8 @@ impl WorkerServer { return; }; let result = msg.result_for2(&handle).expect("handle must have a result"); - let mut easy = self.multi.remove2(handle).expect("handle must be in multi"); - let mut response = std::mem::replace( - &mut easy.get_mut().response, - Response::new(Vec::new()), - ); - if let Ok(status) = easy.response_code() - && status != 0 - && let Ok(status) = http::StatusCode::from_u16(status as u16) - { - *response.status_mut() = status; - } - // Would be nice to set HTTP version via `response.version_mut()`, but `curl` doesn't have it exposed. - let extensions = Extensions { - client_ip: easy.primary_ip().ok().flatten().map(str::to_string), - effective_url: easy.effective_url().ok().flatten().map(str::to_string), - }; - response.extensions_mut().insert(extensions); + let easy = self.multi.remove2(handle).expect("handle must be in multi"); + let response = Self::process_response(easy); let _ = sender.send(result.map(|()| response).map_err(Into::into)); }); From 14c6be40d72f77d593006140d816b810b10716cd Mon Sep 17 00:00:00 2001 From: Arlo Siemsen Date: Mon, 20 Apr 2026 11:06:45 -0500 Subject: [PATCH 2/4] move curl request processing into helper function --- src/cargo/util/network/http_async.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/cargo/util/network/http_async.rs b/src/cargo/util/network/http_async.rs index c3026cab848..2034d2d9495 100644 --- a/src/cargo/util/network/http_async.rs +++ b/src/cargo/util/network/http_async.rs @@ -97,6 +97,17 @@ impl Client { /// Perform an HTTP request using this client. pub async fn request(&self, request: Request) -> HttpResult { + let handle = self.request_helper(request)?; + let (sender, receiver) = oneshot::channel(); + let req = Message { + easy: handle, + sender, + }; + self.channel.as_ref().unwrap().send(req).unwrap(); + receiver.await.unwrap() + } + + fn request_helper(&self, request: Request) -> HttpResult> { let url = request.uri().to_string(); debug!(target: "network::fetch", url); let mut collector = Collector::new(self.stats.clone()); @@ -141,14 +152,7 @@ impl Client { } handle.http_headers(headers)?; - let (sender, receiver) = oneshot::channel(); - let req = Message { - easy: handle, - sender, - }; - - self.channel.as_ref().unwrap().send(req).unwrap(); - receiver.await.unwrap() + Ok(handle) } /// Returns the number pending bytes across all active transfers. From bff19308e7de1630c74500d847d6025335303603 Mon Sep 17 00:00:00 2001 From: Arlo Siemsen Date: Mon, 20 Apr 2026 11:07:21 -0500 Subject: [PATCH 3/4] Add request_blocking function to http_async --- src/cargo/util/network/http_async.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cargo/util/network/http_async.rs b/src/cargo/util/network/http_async.rs index 2034d2d9495..25b371269e5 100644 --- a/src/cargo/util/network/http_async.rs +++ b/src/cargo/util/network/http_async.rs @@ -95,6 +95,14 @@ impl Client { } } + /// Perform a blocking HTTP request using this client. + /// Does not start an async executor. + pub fn request_blocking(&self, request: Request) -> HttpResult { + let handle = self.request_helper(request)?; + handle.perform()?; + Ok(WorkerServer::process_response(handle)) + } + /// Perform an HTTP request using this client. pub async fn request(&self, request: Request) -> HttpResult { let handle = self.request_helper(request)?; From 810cb7e8f45bee5cd13fbd4d8f8c2812fdb75efc Mon Sep 17 00:00:00 2001 From: Arlo Siemsen Date: Mon, 20 Apr 2026 11:09:08 -0500 Subject: [PATCH 4/4] convert github fast path to use http_async Does not start an async executor, so cannot panic due to executor nesting --- src/cargo/sources/git/utils.rs | 41 +++++++++++----------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/src/cargo/sources/git/utils.rs b/src/cargo/sources/git/utils.rs index 366736ee5ef..402ccc8d093 100644 --- a/src/cargo/sources/git/utils.rs +++ b/src/cargo/sources/git/utils.rs @@ -14,8 +14,8 @@ use crate::util::{GlobalContext, IntoUrl, MetricsCounter, Progress, network}; use anyhow::{Context as _, anyhow}; use cargo_util::{ProcessBuilder, paths}; use cargo_util_terminal::Verbosity; -use curl::easy::List; use git2::{ErrorClass, ObjectType, Oid}; +use http::{Request, StatusCode}; use tracing::{debug, info}; use url::Url; @@ -1580,36 +1580,21 @@ fn github_fast_path( "https://api.github.com/repos/{}/{}/commits/{}", username, repository, github_branch_name, ); - let mut handle = gctx.http()?.lock().unwrap(); debug!("attempting GitHub fast path for {}", url); - handle.get(true)?; - handle.url(&url)?; - handle.useragent("cargo")?; - handle.follow_location(true)?; // follow redirects - handle.http_headers({ - let mut headers = List::new(); - headers.append("Accept: application/vnd.github.3.sha")?; - if let Some(local_object) = local_object { - headers.append(&format!("If-None-Match: \"{}\"", local_object))?; - } - headers - })?; - - let mut response_body = Vec::new(); - let mut transfer = handle.transfer(); - transfer.write_function(|data| { - response_body.extend_from_slice(data); - Ok(data.len()) - })?; - transfer.perform()?; - drop(transfer); // end borrow of handle so that response_code can be called - - let response_code = handle.response_code()?; - if response_code == 304 { + let mut request = + Request::get(url).header(http::header::ACCEPT, "application/vnd.github.3.sha"); + if let Some(local_object) = local_object { + request = request.header(http::header::IF_NONE_MATCH, &format!("\"{local_object}\"")); + } + let response = gctx + .http_async()? + .request_blocking(request.body(Vec::new())?)?; + let response_code = response.status(); + if response_code == StatusCode::NOT_MODIFIED { debug!("github fast path up-to-date"); Ok(FastPathRev::UpToDate) - } else if response_code == 200 - && let Some(oid_to_fetch) = rev_to_oid(str::from_utf8(&response_body)?) + } else if response_code == StatusCode::OK + && let Some(oid_to_fetch) = rev_to_oid(str::from_utf8(&response.body())?) { // response expected to be a full hash hexstring (40 or 64 chars) debug!("github fast path fetch {oid_to_fetch}");