Skip to content

Conversation

@christeefy
Copy link
Contributor

@christeefy christeefy commented Apr 21, 2025

Summary

Fixes #12761 and #12746.

I'm interpreting the "special handling" required to mean erroring so that we skip the subsequent Git fetch operation in .git_metadata and .resolve_revision of SourceDistributionBuilder. Please advise if you meant something else instead.

Also let me know if you'd like me to generalize this to other HTTP status codes (i.e. 400 and 422, as mentioned in the comments).

Test Plan

None so far.

  • What's the best way to create new (snapshot?) test cases for this?
  • Should I use wiremock to mock a 429 response?

@christeefy
Copy link
Contributor Author

Hi @zanieb, I like to follow up on this PR as it has been awaiting review for a while. Do you have any feedback on this work? Let me know if this is still relevant. Thanks!

@charliermarsh
Copy link
Member

I think this might not quite be the right approach. I think what we want is: if we detect a rate limit, skip these "fast-path" attempts in any subsequent operations. So we might need to track some kind of "Are we rate-limited?" global state, and read-from + update it in these locations.

@zanieb
Copy link
Member

zanieb commented May 6, 2025

Indeed, that's what I had in mind. GitHub also returns a time to wait until attempting another request, so we can stash that in the global state and invalidate it after that much time (though I think in practice, skipping it until the process exits would be fine).

@christeefy
Copy link
Contributor Author

Thank you both for the feedback; I understand things better now after some studying.

I'm assuming that we want the global state to apply to everything within uv-git, including GitResolver and git.rs which both have similar fast-path logic.

Just to ensure I'm interpreting things correctly, here's the steps I'll implement:

  1. Keep track of an "Are we rate-limited?" global state
    • If we get a HTTP-429, mark this state to true
    • The state can revert to false based on a "time to wait" derived from GitHub's response header, and may be a nice-to-have assuming the process runtime is typically much shorter than this "time to wait"
  2. Subsequent calls to github_fast_path would avoid pinging the GitHub API if/while this global state is true, returning the appropriate value:
    • GitResolver::github_fast_path returns Ok(None), leading to a git fetch
    • git.rs::github_fast_path returns FastPathRev::Indeterminate leading to all branches and tags potentially being fetched in git.rs::fetch based on RefspecStrategy 🤔

Please let me know if I've misinterpreted anything here.

Thanks!

@christeefy
Copy link
Contributor Author

christeefy commented May 20, 2025

Hi @zanieb, I've updated the PR based on your feedback.
Appreciate you taking a look at it!

I found adding integration tests to not be easy. The base urls for the GitHub fast-paths are hardcoded and not exposed via the CLI / env vars, so I don't know how to mock their response via wiremock. I can go down the route of exposing them, unless you have better suggestions.

Thanks!

@zanieb
Copy link
Member

zanieb commented May 20, 2025

The base urls for the GitHub fast-paths are hardcoded and not exposed via the CLI / env vars,

It'd be fine to add this as an environment variable, imo.

@christeefy
Copy link
Contributor Author

christeefy commented May 20, 2025

I can add that env var (tentatively UV_GITHUB_FAST_PATH_URL).
In the meantime, let me know if I'm missing anything major in this PR.

@christeefy
Copy link
Contributor Author

I've made the changes and included integration tests.

I manually did some mutation testing (changing the status code from the mock response, and commenting out this PR's functionalities) to verify the tests aren't dummy tests.

Comment on lines 502 to 510
use uv_client::DEFAULT_RETRIES;

let context = TestContext::new("3.12");
let token = decode_token(READ_ONLY_GITHUB_TOKEN);

let server = MockServer::start().await;
Mock::given(method("GET"))
.respond_with(ResponseTemplate::new(429))
.expect(1 + u64::from(DEFAULT_RETRIES))
Copy link
Contributor Author

@christeefy christeefy May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uv makes 4 attempts (first try + 3 retries) because a HTTP 429 is considered transient in uv-client.

If there are other concurrent fast paths attempt before the first rate-limited request finishes retrying, the skip functionality won't kick in yet. I'm testing the implications of changing the retry semantics for HTTP 429 to Retryable::Fatal.

Copy link
Contributor Author

@christeefy christeefy May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local test suite is passing, so I can update these tests to expect zero retries.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full test suite fails, so not going through with that. See comment below as well.

"Considering skipping retry of response HTTP {status} for {}",
response.url()
);
if status == StatusCode::TOO_MANY_REQUESTS {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes a 429 fatal everywhere? We probably don't want that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Reverting.

@christeefy
Copy link
Contributor Author

Good morning @zanieb, the PR is ready for review.
Please take a look when you get the chance, thanks 😊

#[attr_hidden]
pub const UV_TEST_INDEX_URL: &'static str = "UV_TEST_INDEX_URL";

/// Used to set the GitHub fast-path url for tests.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should document what shape of URL is expected, is there something that GitHub considers as API root that users could exchange?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This env var's intended use is to mock the GitHub API root during tests. It's not meant for users to override, and is hidden from user-facing docs with #[attr_hidden].

I can rename this to UV_TEST_GITHUB_FAST_PATH_URL to be even more explicit, assuming we are okay with UV_TEST_* appearing in non-test files.

Please let me know what you think.

@oconnor663 oconnor663 removed their assignment Jun 18, 2025
@oconnor663 oconnor663 assigned oconnor663 and konstin and unassigned oconnor663 Jun 23, 2025
Copy link
Member

@konstin konstin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things that should be easy to fix, otherwise it looks good!

pub(crate) struct GitHubRateLimitStatus(AtomicBool);

impl GitHubRateLimitStatus {
pub(crate) const fn new() -> Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this method need to be pub(crate)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it doesn't. Updated to just const.


uv_snapshot!(context.filters(), context
.pip_install()
.arg("pip-test-package @ git+https://github.com/pypa/pip-test-package@5547fa909e83df8bd743d3978d6667497983a4b7")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +2104 to +2109
let server = MockServer::start().await;
Mock::given(method("GET"))
.respond_with(ResponseTemplate::new(429))
.expect(1 + u64::from(DEFAULT_RETRIES)) // Middleware retries on 429 by default
.mount(&server)
.await;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work on the testing strategy here!

@konstin konstin changed the title Skip git fetch when rate-limited by GitHub Skip GitHub fast path when rate-limited Jun 24, 2025
@konstin konstin added the enhancement New feature or improvement to existing functionality label Jun 24, 2025
@christeefy christeefy requested a review from konstin June 24, 2025 13:29
@christeefy
Copy link
Contributor Author

@konstin changes have been made and CI is passing. Thanks for your quick PR review 😊

@oconnor663 oconnor663 merged commit fe11cee into astral-sh:main Jun 24, 2025
87 checks passed
@christeefy christeefy deleted the gh-fast-path/429 branch June 24, 2025 19:20
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Jun 29, 2025
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [astral-sh/uv](https://github.com/astral-sh/uv) | patch | `0.7.14` -> `0.7.16` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>astral-sh/uv (astral-sh/uv)</summary>

### [`v0.7.16`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0716)

[Compare Source](astral-sh/uv@0.7.15...0.7.16)

##### Python

- Add Python 3.14.0b3

See the
[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250626)
for more details.

##### Enhancements

- Include path or URL when failing to convert in lockfile ([#&#8203;14292](astral-sh/uv#14292))
- Warn when `~=` is used as a Python version specifier without a patch version ([#&#8203;14008](astral-sh/uv#14008))

##### Preview features

- Ensure preview default Python installs are upgradeable ([#&#8203;14261](astral-sh/uv#14261))

##### Performance

- Share workspace cache between lock and sync operations ([#&#8203;14321](astral-sh/uv#14321))

##### Bug fixes

- Allow local indexes to reference remote files ([#&#8203;14294](astral-sh/uv#14294))
- Avoid rendering desugared prefix matches in error messages ([#&#8203;14195](astral-sh/uv#14195))
- Avoid using path URL for workspace Git dependencies in `requirements.txt` ([#&#8203;14288](astral-sh/uv#14288))
- Normalize index URLs to remove trailing slash ([#&#8203;14245](astral-sh/uv#14245))
- Respect URL-encoded credentials in redirect location ([#&#8203;14315](astral-sh/uv#14315))
- Lock the source tree when running setuptools, to protect concurrent builds ([#&#8203;14174](astral-sh/uv#14174))

##### Documentation

- Note that GCP Artifact Registry download URLs must have `/simple` component ([#&#8203;14251](astral-sh/uv#14251))

### [`v0.7.15`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0715)

[Compare Source](astral-sh/uv@0.7.14...0.7.15)

##### Enhancements

- Consistently use `Ordering::Relaxed` for standalone atomic use cases ([#&#8203;14190](astral-sh/uv#14190))
- Warn on ambiguous relative paths for `--index` ([#&#8203;14152](astral-sh/uv#14152))
- Skip GitHub fast path when rate-limited ([#&#8203;13033](astral-sh/uv#13033))
- Preserve newlines in `schema.json` descriptions ([#&#8203;13693](astral-sh/uv#13693))

##### Bug fixes

- Add check for using minor version link when creating a venv on Windows ([#&#8203;14252](astral-sh/uv#14252))
- Strip query parameters when parsing source URL ([#&#8203;14224](astral-sh/uv#14224))

##### Documentation

- Add a link to PyPI FAQ to clarify what per-project token is ([#&#8203;14242](astral-sh/uv#14242))

##### Preview features

- Allow symlinks in the build backend ([#&#8203;14212](astral-sh/uv#14212))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90Il19-->
sai-rekhawar pushed a commit to sai-rekhawar/cloe-nessy-py that referenced this pull request Jul 1, 2025
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ghcr.io/astral-sh/uv](https://github.com/astral-sh/uv) | final | patch | `0.7.13` -> `0.7.15` |

---

### Release Notes

<details>
<summary>astral-sh/uv (ghcr.io/astral-sh/uv)</summary>

### [`v0.7.15`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0715)

[Compare Source](astral-sh/uv@0.7.14...0.7.15)

##### Enhancements

-   Consistently use `Ordering::Relaxed` for standalone atomic use cases ([#&#8203;14190](astral-sh/uv#14190))
-   Warn on ambiguous relative paths for `--index` ([#&#8203;14152](astral-sh/uv#14152))
-   Skip GitHub fast path when rate-limited ([#&#8203;13033](astral-sh/uv#13033))
-   Preserve newlines in `schema.json` descriptions ([#&#8203;13693](astral-sh/uv#13693))

##### Bug fixes

-   Add check for using minor version link when creating a venv on Windows ([#&#8203;14252](astral-sh/uv#14252))
-   Strip query parameters when parsing source URL ([#&#8203;14224](astral-sh/uv#14224))

##### Documentation

-   Add a link to PyPI FAQ to clarify what per-project token is ([#&#8203;14242](astral-sh/uv#14242))

##### Preview features

-   Allow symlinks in the build backend ([#&#8203;14212](astral-sh/uv#14212))

### [`v0.7.14`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0714)

[Compare Source](astral-sh/uv@0.7.13...0.7.14)

##### Enhancements

-   Add XPU to `--torch-backend` ([#&#8203;14172](astral-sh/uv#14172))
-   Add ROCm backends to `--torch-backend` ([#&#8203;14120](astral-sh/uv#14120))
-   Remove preview label from `--torch-backend` ([#&#8203;14119](astral-sh/uv#14119))
-   Add `[tool.uv.dependency-groups].mygroup.requires-python` ([#&#8203;13735](astral-sh/uv#13735))
-   Add auto-detection for AMD GPUs ([#&#8203;14176](astral-sh/uv#14176))
-   Show retries for HTTP status code errors ([#&#8203;13897](astral-sh/uv#13897))
-   Support transparent Python patch version upgrades ([#&#8203;13954](astral-sh/uv#13954))
-   Warn on empty index directory ([#&#8203;13940](astral-sh/uv#13940))
-   Publish to DockerHub ([#&#8203;14088](astral-sh/uv#14088))

##### Performance

-   Make cold resolves about 10% faster ([#&#8203;14035](astral-sh/uv#14035))

##### Bug fixes

-   Don't use walrus operator in interpreter query script ([#&#8203;14108](astral-sh/uv#14108))
-   Fix handling of changes to `requires-python` ([#&#8203;14076](astral-sh/uv#14076))
-   Fix implied `platform_machine` marker for `...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or improvement to existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Avoid GitHub fast-path queries when rate limited

5 participants