mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-24 05:35:04 +00:00
Rename to uv
(#1302)
First, replace all usages in files in-place. I used my editor for this. If someone wants to add a one-liner that'd be fun. Then, update directory and file names: ``` # Run twice for nested directories find . -type d -print0 | xargs -0 rename s/puffin/uv/g find . -type d -print0 | xargs -0 rename s/puffin/uv/g # Update files find . -type f -print0 | xargs -0 rename s/puffin/uv/g ``` Then add all the files again ``` # Add all the files again git add crates git add python/uv # This one needs a force-add git add -f crates/uv-trampoline ```
This commit is contained in:
parent
328b116d5d
commit
2586f655bb
229 changed files with 1796 additions and 1818 deletions
45
crates/uv-git/src/util/errors.rs
Normal file
45
crates/uv-git/src/util/errors.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
//! Git support is derived from Cargo's implementation.
|
||||
//! Cargo is dual-licensed under either Apache 2.0 or MIT, at the user's choice.
|
||||
//! Source: <https://github.com/rust-lang/cargo/blob/23eb492cf920ce051abfc56bbaf838514dc8365c/src/cargo/util/errors.rs>
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
use super::truncate_with_ellipsis;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct HttpNotSuccessful {
|
||||
pub(crate) code: u32,
|
||||
pub(crate) url: String,
|
||||
pub(crate) ip: Option<String>,
|
||||
pub(crate) body: Vec<u8>,
|
||||
}
|
||||
|
||||
impl HttpNotSuccessful {
|
||||
fn render(&self) -> String {
|
||||
let mut result = String::new();
|
||||
let body = std::str::from_utf8(&self.body).map_or_else(
|
||||
|_| format!("[{} non-utf8 bytes]", self.body.len()),
|
||||
|s| truncate_with_ellipsis(s, 512),
|
||||
);
|
||||
|
||||
write!(
|
||||
result,
|
||||
"failed to get successful HTTP response from `{}`",
|
||||
self.url
|
||||
)
|
||||
.unwrap();
|
||||
if let Some(ip) = &self.ip {
|
||||
write!(result, " ({ip})").unwrap();
|
||||
}
|
||||
writeln!(result, ", got {}", self.code).unwrap();
|
||||
write!(result, "body:\n{body}").unwrap();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HttpNotSuccessful {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.render())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for HttpNotSuccessful {}
|
17
crates/uv-git/src/util/mod.rs
Normal file
17
crates/uv-git/src/util/mod.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
//! Git support is derived from Cargo's implementation.
|
||||
//! Cargo is dual-licensed under either Apache 2.0 or MIT, at the user's choice.
|
||||
//! Source: <https://github.com/rust-lang/cargo/blob/23eb492cf920ce051abfc56bbaf838514dc8365c/src/cargo/util/mod.rs>
|
||||
pub(crate) mod errors;
|
||||
pub(crate) mod retry;
|
||||
|
||||
pub(crate) fn truncate_with_ellipsis(s: &str, max_width: usize) -> String {
|
||||
// We should truncate at grapheme-boundary and compute character-widths,
|
||||
// yet the dependencies on unicode-segmentation and unicode-width are
|
||||
// not worth it.
|
||||
let mut chars = s.chars();
|
||||
let mut prefix = (&mut chars).take(max_width - 1).collect::<String>();
|
||||
if chars.next().is_some() {
|
||||
prefix.push('…');
|
||||
}
|
||||
prefix
|
||||
}
|
188
crates/uv-git/src/util/retry.rs
Normal file
188
crates/uv-git/src/util/retry.rs
Normal file
|
@ -0,0 +1,188 @@
|
|||
//! Utilities for retrying a network operation.
|
||||
//!
|
||||
//! Some network errors are considered "spurious", meaning it is not a real
|
||||
//! error (such as a 404 not found) and is likely a transient error (like a
|
||||
//! bad network connection) that we can hope will resolve itself shortly. The
|
||||
//! [`Retry`] type offers a way to repeatedly perform some kind of network
|
||||
//! operation with a delay if it detects one of these possibly transient
|
||||
//! errors.
|
||||
//!
|
||||
//! This supports errors from [`git2`], [`reqwest`], and [`HttpNotSuccessful`]
|
||||
//! 5xx HTTP errors.
|
||||
//!
|
||||
//! The number of retries can be configured by the user via the `net.retry`
|
||||
//! config option. This indicates the number of times to retry the operation
|
||||
//! (default 3 times for a total of 4 attempts).
|
||||
//!
|
||||
//! There are hard-coded constants that indicate how long to sleep between
|
||||
//! retries. The constants are tuned to balance a few factors, such as the
|
||||
//! responsiveness to the user (we don't want cargo to hang for too long
|
||||
//! retrying things), and accommodating things like Cloudfront's default
|
||||
//! negative TTL of 10 seconds (if Cloudfront gets a 5xx error for whatever
|
||||
//! reason it won't try to fetch again for 10 seconds).
|
||||
//!
|
||||
//! The timeout also implements a primitive form of random jitter. This is so
|
||||
//! that if multiple requests fail at the same time that they don't all flood
|
||||
//! the server at the same time when they are retried. This jitter still has
|
||||
//! some clumping behavior, but should be good enough.
|
||||
//!
|
||||
//! [`Retry`] is the core type for implementing retry logic. The
|
||||
//! [`Retry::try`] method can be called with a callback, and it will
|
||||
//! indicate if it needs to be called again sometime in the future if there
|
||||
//! was a possibly transient error. The caller is responsible for sleeping the
|
||||
//! appropriate amount of time and then calling [`Retry::try`] again.
|
||||
//!
|
||||
//! [`with_retry`] is a convenience function that will create a [`Retry`] and
|
||||
//! handle repeatedly running a callback until it succeeds, or it runs out of
|
||||
//! retries.
|
||||
//!
|
||||
//! Some interesting resources about retries:
|
||||
//! - <https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/>
|
||||
//! - <https://en.wikipedia.org/wiki/Exponential_backoff>
|
||||
//! - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After>
|
||||
|
||||
//! Git support is derived from Cargo's implementation.
|
||||
//! Cargo is dual-licensed under either Apache 2.0 or MIT, at the user's choice.
|
||||
//! Source: <https://github.com/rust-lang/cargo/blob/23eb492cf920ce051abfc56bbaf838514dc8365c/src/cargo/util/network/retry.rs>
|
||||
use std::cmp::min;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
use rand::Rng;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::util::errors::HttpNotSuccessful;
|
||||
|
||||
/// State for managing retrying a network operation.
|
||||
pub(crate) struct Retry {
|
||||
/// The number of failed attempts that have been done so far.
|
||||
///
|
||||
/// Starts at 0, and increases by one each time an attempt fails.
|
||||
retries: u64,
|
||||
/// The maximum number of times the operation should be retried.
|
||||
///
|
||||
/// 0 means it should never retry.
|
||||
max_retries: u64,
|
||||
}
|
||||
|
||||
/// The result of attempting some operation via [`Retry::try`].
|
||||
pub(crate) enum RetryResult<T> {
|
||||
/// The operation was successful.
|
||||
///
|
||||
/// The wrapped value is the return value of the callback function.
|
||||
Success(T),
|
||||
/// The operation was an error, and it should not be tried again.
|
||||
Err(Error),
|
||||
/// The operation failed, and should be tried again in the future.
|
||||
///
|
||||
/// The wrapped value is the number of milliseconds to wait before trying
|
||||
/// again. The caller is responsible for waiting this long and then
|
||||
/// calling [`Retry::try`] again.
|
||||
Retry(u64),
|
||||
}
|
||||
|
||||
/// Maximum amount of time a single retry can be delayed (milliseconds).
|
||||
const MAX_RETRY_SLEEP_MS: u64 = 10 * 1000;
|
||||
/// The minimum initial amount of time a retry will be delayed (milliseconds).
|
||||
///
|
||||
/// The actual amount of time will be a random value above this.
|
||||
const INITIAL_RETRY_SLEEP_BASE_MS: u64 = 500;
|
||||
/// The maximum amount of additional time the initial retry will take (milliseconds).
|
||||
///
|
||||
/// The initial delay will be [`INITIAL_RETRY_SLEEP_BASE_MS`] plus a random range
|
||||
/// from 0 to this value.
|
||||
const INITIAL_RETRY_JITTER_MS: u64 = 1000;
|
||||
|
||||
impl Retry {
|
||||
pub(crate) fn new() -> Retry {
|
||||
Retry {
|
||||
retries: 0,
|
||||
max_retries: 3,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls the given callback, and returns a [`RetryResult`] which
|
||||
/// indicates whether or not this needs to be called again at some point
|
||||
/// in the future to retry the operation if it failed.
|
||||
pub(crate) fn r#try<T>(&mut self, f: impl FnOnce() -> Result<T>) -> RetryResult<T> {
|
||||
match f() {
|
||||
Err(ref err) if maybe_spurious(err) && self.retries < self.max_retries => {
|
||||
let err_msg = err.downcast_ref::<HttpNotSuccessful>().map_or_else(
|
||||
|| err.root_cause().to_string(),
|
||||
HttpNotSuccessful::to_string,
|
||||
);
|
||||
warn!(
|
||||
"Spurious network error ({} tries remaining): {err_msg}",
|
||||
self.max_retries - self.retries,
|
||||
);
|
||||
self.retries += 1;
|
||||
RetryResult::Retry(self.next_sleep_ms())
|
||||
}
|
||||
Err(e) => RetryResult::Err(e),
|
||||
Ok(r) => RetryResult::Success(r),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the next sleep duration in milliseconds.
|
||||
fn next_sleep_ms(&self) -> u64 {
|
||||
if self.retries == 1 {
|
||||
let mut rng = rand::thread_rng();
|
||||
INITIAL_RETRY_SLEEP_BASE_MS + rng.gen_range(0..INITIAL_RETRY_JITTER_MS)
|
||||
} else {
|
||||
min(
|
||||
((self.retries - 1) * 3) * 1000 + INITIAL_RETRY_SLEEP_BASE_MS,
|
||||
MAX_RETRY_SLEEP_MS,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_spurious(err: &Error) -> bool {
|
||||
if let Some(git_err) = err.downcast_ref::<git2::Error>() {
|
||||
match git_err.class() {
|
||||
git2::ErrorClass::Net
|
||||
| git2::ErrorClass::Os
|
||||
| git2::ErrorClass::Zlib
|
||||
| git2::ErrorClass::Ssl
|
||||
| git2::ErrorClass::Http => return git_err.code() != git2::ErrorCode::Certificate,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if let Some(reqwest_err) = err.downcast_ref::<reqwest::Error>() {
|
||||
if reqwest_err.is_timeout()
|
||||
|| reqwest_err.is_connect()
|
||||
|| reqwest_err
|
||||
.status()
|
||||
.map_or(false, |status| status.is_server_error())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(not_200) = err.downcast_ref::<HttpNotSuccessful>() {
|
||||
if 500 <= not_200.code && not_200.code < 600 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Wrapper method for network call retry logic.
|
||||
///
|
||||
/// Retry counts provided by Config object `net.retry`. Config shell outputs
|
||||
/// a warning on per retry.
|
||||
///
|
||||
/// Closure must return a `Result`.
|
||||
pub(crate) fn with_retry<T, F>(mut callback: F) -> Result<T>
|
||||
where
|
||||
F: FnMut() -> Result<T>,
|
||||
{
|
||||
let mut retry = Retry::new();
|
||||
loop {
|
||||
match retry.r#try(&mut callback) {
|
||||
RetryResult::Success(r) => return Ok(r),
|
||||
RetryResult::Err(e) => return Err(e),
|
||||
RetryResult::Retry(sleep) => std::thread::sleep(Duration::from_millis(sleep)),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue