Skip to content

fix(pd): abort both PD requests when one side hits a transport error - #844

Merged
slin1237 merged 1 commit into
mainfrom
slin/fix-pd-hang
Mar 21, 2026
Merged

slin1237 merged 1 commit into
mainfrom
slin/fix-pd-hang

Conversation

@slin1237

@slin1237 slin1237 commented Mar 21, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes PD disaggregation requests hanging forever when one side hits a transport error.

Closes #831

Problem

tokio::join! waits for both prefill and decode requests to complete. If prefill fails with a connection reset, the decode request hangs waiting for a PD bootstrap from prefill that will never come — until the bootstrap timeout (default 60s).

Fix

Replace tokio::join! with tokio::try_join!. When either request returns Err (transport error), try_join! drops the other future, cancelling the in-flight request immediately. Both workers are recorded as failed (circuit breaker), and a 502 Bad Gateway is returned.

What changed

  • pd_router.rs: execute_pd_request_once uses tokio::try_join! instead of tokio::join! for the prefill+decode concurrent send. On transport error, returns bad_gateway immediately.
  • Health check path (health_generate) still uses tokio::join! — health checks don't involve bootstrap.

Test plan

  • cargo check -p smg passes
  • cargo clippy -p smg -- -D warnings passes
  • PD request with one failing worker returns 502 immediately instead of hanging
  • PD request with both workers healthy continues to work normally

Summary by CodeRabbit

  • Bug Fixes
    • Gateway now fails fast on the first transport error when making parallel requests, canceling remaining in-flight work.
    • Transport errors are logged and an explicit error response is returned immediately.
    • Improves responsiveness and reliability during network or connection issues.

@github-actions github-actions Bot added the model-gateway Model gateway crate changes label Mar 21, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue where PD disaggregation requests could hang indefinitely if one of the concurrent prefill or decode operations failed due to a transport error. By switching to tokio::try_join!, the system now gracefully handles such failures, immediately cancelling the remaining operation and returning an appropriate error, thereby improving the robustness and responsiveness of the service.

Highlights

  • Concurrent Request Handling: Replaced tokio::join! with tokio::try_join! for sending prefill and decode requests concurrently in execute_pd_request_once.
  • Error Propagation: Ensured that if one side of the concurrent prefill or decode request encounters a transport error, the other future is immediately cancelled, preventing requests from hanging.
  • Immediate Error Response: Implemented logic to return a 502 Bad Gateway error immediately when a transport error occurs during PD disaggregation requests, rather than waiting for a timeout.
  • Circuit Breaker Integration: Recorded both prefill and decode workers as failed (for circuit breaker purposes) when a transport error aborts the requests.
  • Health Check Exemption: Confirmed that the health check path (health_generate) continues to use tokio::join! as it does not involve PD bootstrap and the same error conditions do not apply.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@coderabbitai

coderabbitai Bot commented Mar 21, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cdb23146-0e8d-45f7-be0a-1b771d4c668c

📥 Commits

Reviewing files that changed from the base of the PR and between 67bba3e and 7411c52.

📒 Files selected for processing (1)
  • model_gateway/src/routers/http/pd_router.rs

📝 Walkthrough

Walkthrough

Replaced tokio::join! with tokio::try_join! for concurrent PD prefill/decode requests; transport-layer errors on either request now cause an immediate Err return, the error is logged, record_outcome for the worker is skipped (left to the caller), and the function returns a bad_gateway error early.

Changes

Cohort / File(s) Summary
PD Router dispatch logic
model_gateway/src/routers/http/pd_router.rs
Switched concurrent execution from tokio::join! to tokio::try_join!. On transport errors the code now returns early with error::bad_gateway(...), logs the transport error, and avoids calling record_outcome for the failing worker; successful branch destructures Ok((prefill_resp, decode_resp)) into typed Result<reqwest::Response, reqwest::Error> variables for downstream processing.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Router as PD Router
    participant Prefill as Worker Prefill
    participant Decode as Worker Decode

    Client->>Router: /generate request
    Router->>Prefill: prefill_request (concurrent)
    Router->>Decode: decode_request (concurrent)
    alt transport error from one worker
        Prefill--xRouter: transport error
        Router->>Router: log transport error\nskip worker record_outcome
        Router->>Client: 502 Bad Gateway (transport error)
    else both requests complete
        Prefill-->>Router: prefill_resp
        Decode-->>Router: decode_resp
        Router->>Client: aggregated success response
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I raced two paths beneath the log,

One tripped and sent a sudden fog.
I stamped my paw, I did not wait,
I logged the stumble, closed the gate.
Hopping onward — safe and straight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: replacing tokio::join! with tokio::try_join! to abort both PD requests when a transport error occurs on either side.
Linked Issues check ✅ Passed The PR implements the core fix for issue #831 by replacing tokio::join! with tokio::try_join! to immediately abort concurrent requests on transport errors, returning 502 instead of hanging.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing issue #831; the PR modifies only pd_router.rs execute_dual_dispatch_internal to replace the join mechanism and handle transport errors appropriately.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slin/fix-pd-hang

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

The pull request successfully addresses the issue of PD disaggregation requests hanging by replacing tokio::join! with tokio::try_join!. This change correctly ensures that if one side of the concurrent requests encounters a transport error, the other is immediately cancelled, preventing indefinite waits and improving the robustness of the system. The error handling and outcome recording for both workers in case of a transport error are well implemented.

Comment thread model_gateway/src/routers/http/pd_router.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e9faa09d2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread model_gateway/src/routers/http/pd_router.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@model_gateway/src/routers/http/pd_router.rs`:
- Around line 599-602: The transport error path currently calls
prefill.record_outcome(false) and decode.record_outcome(false) and then returns
error::bad_gateway, which causes the caller to double-count failures (the caller
also records outcomes after this function returns); remove the two outcome
recordings from this error branch so only the caller records failures—i.e.,
delete the calls to prefill.record_outcome(false) and
decode.record_outcome(false) in the block that logs "PD request transport error,
both sides aborted" and keep the error::bad_gateway return.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b5bba8ff-4690-44d4-8244-ba9828cd169c

📥 Commits

Reviewing files that changed from the base of the PR and between 8463183 and 6e9faa0.

📒 Files selected for processing (1)
  • model_gateway/src/routers/http/pd_router.rs

Comment thread model_gateway/src/routers/http/pd_router.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
model_gateway/src/routers/http/pd_router.rs (1)

596-599: ⚠️ Potential issue | 🟠 Major

Remove duplicate failure recording on transport-error path.

Line 597 and Line 598 already mark both workers failed, and the caller records failures again at Line 360 and Line 361 after this function returns 502. This double-counts a single transport failure.

Proposed fix
             Err(e) => {
                 error!("PD request transport error, both sides aborted: {e}");
-                prefill.record_outcome(false);
-                decode.record_outcome(false);
                 return error::bad_gateway(
                     "PD disaggregation request failed",
                     format!("Transport error: {e}"),
                 );
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model_gateway/src/routers/http/pd_router.rs` around lines 596 - 599, The
transport-error branch in pd_router.rs currently calls
prefill.record_outcome(false) and decode.record_outcome(false) before returning
error::bad_gateway, which duplicates failure recording already done by the
caller; remove those two record_outcome(false) calls (the ones immediately
before the return of error::bad_gateway) so that failures are recorded only once
by the caller and not double-counted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@model_gateway/src/routers/http/pd_router.rs`:
- Around line 596-599: The transport-error branch in pd_router.rs currently
calls prefill.record_outcome(false) and decode.record_outcome(false) before
returning error::bad_gateway, which duplicates failure recording already done by
the caller; remove those two record_outcome(false) calls (the ones immediately
before the return of error::bad_gateway) so that failures are recorded only once
by the caller and not double-counted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 247f4b01-7e5b-4ce0-a2a1-7c23e1409359

📥 Commits

Reviewing files that changed from the base of the PR and between 6e9faa0 and 67bba3e.

📒 Files selected for processing (1)
  • model_gateway/src/routers/http/pd_router.rs

When prefill and decode requests are sent concurrently with tokio::join!,
if one side fails with a transport error (e.g., connection reset), the
other side hangs forever waiting for a PD bootstrap that will never come.

Switch from tokio::join! to tokio::try_join! so that when either request
returns Err, the other future is dropped (cancelled). Both sides are
recorded as failed and a bad_gateway response is returned immediately.

The health check path (health_generate) still uses tokio::join! since
health checks are quick and don't involve bootstrap.

Closes #831

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@slin1237
slin1237 merged commit 0b055fe into main Mar 21, 2026
30 of 33 checks passed
@slin1237
slin1237 deleted the slin/fix-pd-hang branch March 21, 2026 05:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

model-gateway Model gateway crate changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: PD requests hang forever if one side hits a transport error (e.g. connection reset)

1 participant