-
Notifications
You must be signed in to change notification settings - Fork 72
fix(stargate): bound proxy retries and relay retention #1820
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a23eeb8
1b1912b
e0a4167
b222a3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # Stargate agent guide | ||
|
|
||
| This guidance covers the Rust workspace, including `stargate-k8s-router` and | ||
| the shared `stargate-forwarding` crate. | ||
|
|
||
| ## Assumed deployment invariants | ||
|
|
||
| - `stargate-k8s-router` is deployed only for `raw-quic` tunnel traffic. | ||
| HTTP/3 tunnel traffic does not pass through this router. | ||
| - Raw QUIC request streams are bidirectional. Prioritizing bidirectional | ||
| acceptance over unidirectional acceptance in the raw QUIC relay is intentional. | ||
| - Optional HTTP/3 and WebTransport implementations, flags, and tests do not | ||
| change this deployment assumption. Do not infer an HTTP/3 stream fairness | ||
| requirement for the raw QUIC router from their presence. | ||
|
|
||
| For router transport changes, inspect `crates/stargate-k8s-router/src/quic.rs` | ||
| and `crates/stargate-forwarding/src/lib.rs`. The | ||
| [transport guide](docs/tunnel-transports.md) describes available protocol | ||
| implementations and their routing paths. | ||
|
|
||
| ## Build and verification | ||
|
|
||
| Run commands from this directory. For router and relay Rust changes: | ||
|
|
||
| ```sh | ||
| cargo build --locked -p stargate-k8s-router | ||
| cargo test --locked -p stargate-k8s-router -p stargate-forwarding -- --test-threads=1 | ||
| cargo clippy --locked -p stargate-k8s-router -p stargate-forwarding --all-targets -- -D warnings | ||
| cargo fmt --all -- --check | ||
| ``` | ||
|
|
||
| For other Rust changes, select the affected workspace packages with `-p`. | ||
| For documentation changes, check whitespace and link targets. | ||
|
|
||
| ## Code style | ||
|
|
||
| Use Rust 2024 and the workspace formatter and Clippy rules. Match existing | ||
| crate structure and use `tracing` for structured logs. Keep deployment | ||
| assumptions explicit when reviewing transport behavior. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| @AGENTS.md |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,7 @@ pub(super) enum RetryDecision<T> { | |
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub(super) enum FinalRetryDisposition { | ||
| PassThrough, | ||
| AmbiguousDelivery, | ||
| Exhausted(String), | ||
| ReplayIncomplete(String), | ||
| PayloadTooLarge(Option<String>), | ||
|
|
@@ -73,6 +74,7 @@ impl FinalRetryDisposition { | |
| pub(super) fn label(&self) -> &'static str { | ||
| match self { | ||
| Self::PassThrough => "pass_through", | ||
| Self::AmbiguousDelivery => "ambiguous_delivery", | ||
| Self::Exhausted(_) => "retry_exhausted", | ||
| Self::ReplayIncomplete(_) => "replay_incomplete", | ||
| Self::PayloadTooLarge(_) => "payload_too_large", | ||
|
|
@@ -82,6 +84,7 @@ impl FinalRetryDisposition { | |
| pub(super) fn retry_reason(&self) -> Option<&str> { | ||
| match self { | ||
| Self::PassThrough => None, | ||
| Self::AmbiguousDelivery => Some("request_may_have_been_applied"), | ||
| Self::Exhausted(reason) | Self::ReplayIncomplete(reason) => Some(reason), | ||
| Self::PayloadTooLarge(reason) => reason.as_deref(), | ||
| } | ||
|
|
@@ -135,6 +138,7 @@ pub(super) fn decide_proxy_error_retry( | |
| retry: &ProxyRetryConfig, | ||
| retry_budget_remaining: bool, | ||
| connect_retries: u32, | ||
| request_body_started: bool, | ||
| replay_readiness: ReplayReadiness, | ||
| ) -> RetryDecision<()> { | ||
| if !matches!( | ||
|
|
@@ -151,7 +155,8 @@ pub(super) fn decide_proxy_error_retry( | |
| } | ||
|
|
||
| match replay_readiness { | ||
| ReplayReadiness::Ready => RetryDecision::Retry(()), | ||
| ReplayReadiness::Ready if !request_body_started => RetryDecision::Retry(()), | ||
| ReplayReadiness::Ready => RetryDecision::Final(FinalRetryDisposition::AmbiguousDelivery), | ||
|
Comment on lines
+158
to
+159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '45,180p' src/libraries/rust/stargate/crates/stargate/src/http_proxy/retry.rs
sed -n '340,405p' src/libraries/rust/stargate/crates/stargate/src/http_proxy/attempt.rs
sed -n '250,305p' src/libraries/rust/stargate/crates/stargate/src/http_proxy/retry.rs
sed -n '420,505p' src/libraries/rust/stargate/crates/stargate/src/http_proxy/retry.rs
sed -n '60,82p' src/libraries/rust/stargate/docs/diagrams/chat-completions-e2e.puml
rg -n 'AmbiguousDelivery|request_may_have_been_applied|retry_budget_exhausted|connection_retries_exhausted|ambiguous_delivery' src/libraries/rust/stargateRepository: NVIDIA/nvcf Length of output: 15261 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- retry call sites ---'
rg -n -C 8 'decide_proxy_error_retry|request_body_started|connect_retries' src/libraries/rust/stargate/crates/stargate/src/http_proxy
printf '%s\n' '--- attempt retry loop ---'
sed -n '250,390p' src/libraries/rust/stargate/crates/stargate/src/http_proxy/attempt.rs
printf '%s\n' '--- retry tests around relevant cases ---'
sed -n '240,475p' src/libraries/rust/stargate/crates/stargate/src/http_proxy/retry.rs
printf '%s\n' '--- repository docs/tests mentioning ambiguity or precedence ---'
rg -n -C 4 'AmbiguousDelivery|ambiguous_delivery|request_may_have_been_applied|retry_exhausted|retry_budget|connect_retries|after submission|body submission|precedence' \
src/libraries/rust/stargate/docs \
src/libraries/rust/stargate/crates/stargate/tests \
src/libraries/rust/stargate/crates/stargate/src/http_proxyRepository: NVIDIA/nvcf Length of output: 50368 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- caller and state flow ---'
rg -n -C 12 'decide_proxy_error_retry|request_body_started|ReplayReadiness' src/libraries/rust/stargate/crates/stargate/src/http_proxy/attempt.rs src/libraries/rust/stargate/crates/stargate/src/http_proxy/retry.rs
printf '%s\n' '--- retry definitions and tests ---'
sed -n '1,180p' src/libraries/rust/stargate/crates/stargate/src/http_proxy/retry.rs
sed -n '240,475p' src/libraries/rust/stargate/crates/stargate/src/http_proxy/retry.rs
printf '%s\n' '--- relevant docs ---'
rg -n -C 8 'after submission|may already|retry limit|retry budget|ambiguous|replay|transport failure' src/libraries/rust/stargate/docs src/libraries/rust/stargate/crates/stargate/testsRepository: NVIDIA/nvcf Length of output: 50367 Classify submitted requests before retry exhaustion.
Move only the submitted Proposed fix if !matches!(
status,
StatusCode::BAD_GATEWAY | StatusCode::GATEWAY_TIMEOUT | StatusCode::SERVICE_UNAVAILABLE
) {
return RetryDecision::Final(FinalRetryDisposition::PassThrough);
}
+ if request_body_started && matches!(&replay_readiness, ReplayReadiness::Ready) {
+ return RetryDecision::Final(FinalRetryDisposition::AmbiguousDelivery);
+ }
if !retry_budget_remaining {
return retry_exhausted("retry_budget_exhausted");
}Add assertions for an expired budget and an exhausted connection retry count with 🤖 Prompt for AI Agents |
||
| ReplayReadiness::Incomplete => RetryDecision::Final( | ||
| FinalRetryDisposition::ReplayIncomplete(RETRY_REASON_RETRYABLE_PROXY_ERROR.to_string()), | ||
| ), | ||
|
|
@@ -258,6 +263,21 @@ mod tests { | |
| .collect() | ||
| } | ||
|
|
||
| #[test] | ||
| fn completed_body_does_not_authorize_retry_after_submission() { | ||
| assert_eq!( | ||
| decide_proxy_error_retry( | ||
| StatusCode::BAD_GATEWAY, | ||
| &ProxyRetryConfig::default(), | ||
| true, | ||
| 0, | ||
| true, | ||
| ReplayReadiness::Ready, | ||
| ), | ||
| RetryDecision::Final(FinalRetryDisposition::AmbiguousDelivery) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn retry_requires_explicit_pylon_signal_by_default() { | ||
| let retry = ProxyRetryConfig::default(); | ||
|
|
@@ -409,6 +429,7 @@ mod tests { | |
| &retry, | ||
| true, | ||
| 0, | ||
| false, | ||
| ReplayReadiness::Ready, | ||
| ), | ||
| RetryDecision::Retry(()) | ||
|
|
@@ -425,6 +446,7 @@ mod tests { | |
| &retry, | ||
| false, | ||
| 0, | ||
| false, | ||
| ReplayReadiness::Ready, | ||
| ), | ||
| RetryDecision::Final(FinalRetryDisposition::Exhausted( | ||
|
|
@@ -437,6 +459,7 @@ mod tests { | |
| &retry, | ||
| true, | ||
| retry.max_connect_retries, | ||
| false, | ||
| ReplayReadiness::PayloadTooLarge, | ||
| ), | ||
| RetryDecision::Final(FinalRetryDisposition::Exhausted( | ||
|
|
@@ -449,6 +472,7 @@ mod tests { | |
| &retry, | ||
| true, | ||
| 0, | ||
| false, | ||
| ReplayReadiness::PayloadTooLarge, | ||
| ), | ||
| RetryDecision::Final(FinalRetryDisposition::PassThrough) | ||
|
|
@@ -465,6 +489,7 @@ mod tests { | |
| &retry, | ||
| true, | ||
| 0, | ||
| false, | ||
| ReplayReadiness::Incomplete, | ||
| ), | ||
| RetryDecision::Final(FinalRetryDisposition::ReplayIncomplete( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: NVIDIA/nvcf
Length of output: 26271
Correct the router deployment invariant.
stargate-k8s-routersupports bothraw-quicandwebtransport. Its CLI accepts those modes and rejects plainhttp3, which uses an L4 path instead. The current statement can cause valid WebTransport router deployments to be rejected or omitted. State that the router supportsraw-quicandwebtransport, while plainhttp3does not use the router.🤖 Prompt for AI Agents