Skip to content

fix: gRPC error handling — proper status codes and circuit breaker accuracy - #645

Merged
CatherineSue merged 8 commits into
mainfrom
chang/grpc-error-handling
Mar 5, 2026
Merged

CatherineSue merged 8 commits into
mainfrom
chang/grpc-error-handling

Conversation

@CatherineSue

@CatherineSue CatherineSue commented Mar 5, 2026 •

Copy link
Copy Markdown
Member

Description

Problem

Related to #639.

The gRPC request execution pipeline had two error handling deficiencies:

  1. All gRPC errors mapped to HTTP 500. GrpcClient::generate() returned Box<dyn Error>, erasing the tonic::Status type. Every downstream error handler called error::internal_error(...), so a 400 Bad Request from the backend (e.g., invalid prompt) surfaced as 500 Internal Server Error to the caller.

  2. Circuit breaker had incorrect failure classification. The circuit breaker recorded result.is_ok() — any gRPC failure (including InvalidArgument, NotFound, etc.) counted as a backend failure, potentially marking a healthy worker as unhealthy. Meanwhile, retryable errors like ResourceExhausted (429) were not tripping the circuit breaker.

Solution

Preserve the gRPC status code throughout the pipeline, then map it correctly at the HTTP boundary.

  • Change GrpcClient::generate() and GrpcClient::embed() return types from Result<T, Box<dyn Error>> to Result<T, tonic::Status>, adding an into_status() helper for the downcast.
  • Introduce TonicStatusExt / TonicResultExt extension traits (zero-cost at runtime, monomorphized) that provide:
    • http_status() — maps gRPC codes to HTTP codes (e.g., InvalidArgument → 400, ResourceExhausted → 429, Unavailable → 503, DeadlineExceeded → 504)
    • is_cb_failure() — composes http_status() with is_retryable_status() so the circuit breaker uses the same predicate for both HTTP and gRPC paths (408, 429, 500, 502, 503, 504)
    • to_http_error() — creates an HTTP error response with the correct status code
    • is_healthy() — on Result<T, tonic::Status>, returns true when Ok or when the error is not a CB failure
  • Replace all error::internal_error(...) calls in request execution with e.to_http_error(...).
  • Replace all result.is_ok() circuit breaker checks with result.is_healthy().
  • Align HTTP router circuit breaker to use !is_retryable_status(status) instead of !status.is_server_error().
  • Consolidate grpc_to_http_status from utils.rs into the trait (was previously a free function).

Circuit Breaker Alignment

Both HTTP and gRPC paths now use the same predicate (is_retryable_status) for circuit breaker decisions:

Status CB Failure? Retried?
400 Bad Request No No
408 Request Timeout Yes Yes
429 Too Many Requests Yes Yes
500 Internal Server Error Yes Yes
502 Bad Gateway Yes Yes
503 Service Unavailable Yes Yes
504 Gateway Timeout Yes Yes

Changes

  • client.rs: Change generate() / embed() return type to Result<T, tonic::Status>. Add into_status() to downcast from Box<dyn Error>.
  • tonic_ext.rs (new): TonicStatusExt and TonicResultExt extension traits with gRPC→HTTP mapping, CB failure classification via is_retryable_status(), and error response construction.
  • request_execution.rs: Replace all error::internal_error(...) with e.to_http_error(...). Replace result.is_ok() with result.is_healthy(). Use !e.is_cb_failure() for sequential PD error paths.
  • http/router.rs: Use !is_retryable_status(status) for circuit breaker outcome instead of !status.is_server_error().
  • utils.rs: Consolidate inline grpc_to_http_status into the new trait; update collect_stream_responses to use e.to_http_error().

Test Plan

  • cargo check -p smg — zero warnings
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo test -p smg — all tests pass
  • Manual verification: gRPC InvalidArgument now returns HTTP 400 (was 500); circuit breaker correctly trips on 408/429 but not on 400
Screenshot 2026-03-05 at 9 30 35 AM

Follow-up: Once upstream PRs (grpc-servicer, grpc-proto) are merged and CI infrastructure is in place, dedicated integration test cases for gRPC status code mapping and circuit breaker behavior should be added to the E2E test suite.

Checklist
  • cargo +nightly fmt passes
  • cargo clippy --all-targets --all-features -- -D warnings passes
  • (Optional) Documentation updated

Summary by CodeRabbit

  • Bug Fixes

    • Standardized error propagation so gRPC-style statuses are returned and consistently translated to HTTP-friendly errors.
    • Improved worker outcome recording to treat non-5xx responses as success and enhanced related logging.
  • Refactor

    • Centralized gRPC→HTTP error translation and health-check helpers for circuit-breaker logic.
    • Introduced execution mode handling and streamlined request dispatch/error flows for clearer runtime behavior.

Change GrpcClient::generate/embed to return tonic::Status instead of
Box<dyn Error>, so callers can inspect the gRPC status code directly
without downcasting.

Circuit breaker now only counts server errors (Internal, Unavailable,
Unknown, DataLoss, DeadlineExceeded) as failures. Client errors like
InvalidArgument no longer trip the breaker since they indicate a
healthy backend rejecting bad input.

gRPC status codes are mapped to proper HTTP status in both stream
creation (request_execution.rs) and stream iteration (utils.rs)
instead of always returning 500. In-band error path marked as legacy.

Signed-off-by: Chang Su <chang.s.su@oracle.com>
Signed-off-by: Chang Su <chang.s.su@oracle.com>
Signed-off-by: Chang Su <chang.s.su@oracle.com>
Signed-off-by: Chang Su <chang.s.su@oracle.com>
@coderabbitai

coderabbitai Bot commented Mar 5, 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: 6abf4130-3468-4ea6-8671-31025f1b22ea

📥 Commits

Reviewing files that changed from the base of the PR and between 85e57c1 and 48fff6d.

📒 Files selected for processing (1)
  • model_gateway/src/routers/grpc/tonic_ext.rs

📝 Walkthrough

Walkthrough

Switches internal error types to tonic::Status, adds tonic_ext traits to map gRPC→HTTP and assess circuit-breaker health, updates request execution and stream utilities to use the new traits, and adjusts HTTP router outcome recording accordingly.

Changes

Cohort / File(s) Summary
gRPC client & error conversion
model_gateway/src/routers/grpc/client.rs
generate/embed signatures changed to return Result<..., tonic::Status>; added into_status to convert boxed errors; backend calls map errors with map_err(into_status); added guards for mismatched backend pairings.
Request execution & streaming
model_gateway/src/routers/grpc/common/stages/request_execution.rs
StreamResult now Result<ProtoStream, tonic::Status>; added ExecutionMode and mode field with RequestExecutionStage::new; replaced is_ok checks with is_healthy; use to_http_error for error conversion and logging; reduced repeated runtime type calls.
gRPC extension utilities
model_gateway/src/routers/grpc/tonic_ext.rs, model_gateway/src/routers/grpc/mod.rs
New tonic_ext module exposing TonicStatusExt (maps gRPC codes→HTTP, builds HTTP error responses, detects CB failures) and TonicResultExt (is_healthy) with implementations.
Stream utils
model_gateway/src/routers/grpc/utils.rs
Stream error handling changed to use to_http_error; legacy in-band error logging replaced with warnings; imports updated to include TonicStatusExt.
HTTP router circuit-breaker
model_gateway/src/routers/http/router.rs
Worker outcome recording logic changed to treat non-retryable statuses (non-5xx) as success, so 2xx/3xx/4xx count as success while 5xx count as failure.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant GRPC_Client as GRPC Client
    participant ExecStage as RequestExecutionStage
    participant Backend
    participant CircuitBreaker

    Client->>GRPC_Client: call generate/embed
    GRPC_Client->>ExecStage: dispatch request
    ExecStage->>Backend: start prefill/decode or generate
    Backend-->>ExecStage: stream responses or tonic::Status error
    ExecStage->>CircuitBreaker: evaluate outcome (is_healthy / to_http_error)
    ExecStage-->>GRPC_Client: return ProtoStream or tonic::Status
    GRPC_Client-->>Client: stream or gRPC status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • key4ng
  • slin1237

Poem

🐰 I swapped the boxes for tonic cheer,
Mapping codes so routers hear,
Traits translate and circuits sing,
Streams hop light on error wing,
Hooray — fewer boxed bugs near!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing gRPC error handling to properly propagate status codes and align circuit breaker logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chang/grpc-error-handling

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

@github-actions github-actions Bot added grpc gRPC client and router changes model-gateway Model gateway crate changes labels Mar 5, 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 significantly enhances the robustness and accuracy of gRPC error handling and circuit breaker functionality within the model gateway. By preserving and correctly mapping gRPC status codes to appropriate HTTP responses, it provides more granular feedback to callers. Furthermore, the circuit breaker now intelligently differentiates between client-induced errors and genuine backend failures, preventing healthy workers from being incorrectly marked as unhealthy.

Highlights

  • Improved gRPC Error Handling: gRPC errors are now properly mapped to specific HTTP status codes (e.g., InvalidArgument to 400, Unavailable to 503) instead of always returning 500 Internal Server Error.
  • Accurate Circuit Breaker Logic: The circuit breaker now distinguishes between client-side and server-side gRPC errors, only tripping for actual backend failures (server errors) and ignoring client-initiated issues.
  • New Extension Traits for Tonic: Introduced TonicStatusExt and TonicResultExt to provide helper methods for gRPC status code mapping, server error classification, and HTTP error response creation, ensuring error details are preserved throughout the request execution pipeline.
  • Refactored Error Propagation: Changed gRPC client methods to return tonic::Status directly, replacing generic Box<dyn Error> for more precise error handling.
Changelog
  • model_gateway/src/routers/grpc/client.rs
    • Updated generate() and embed() methods to return Result<ProtoStream, tonic::Status> instead of Box<dyn std::error::Error + Send + Sync>.
    • Added into_status helper function to downcast boxed errors to tonic::Status.
  • model_gateway/src/routers/grpc/common/stages/request_execution.rs
    • Changed StreamResult type alias to use tonic::Status for errors.
    • Imported TonicResultExt and TonicStatusExt traits.
    • Replaced result.is_ok() with result.is_healthy() for circuit breaker outcome recording.
    • Replaced error::internal_error(...) calls with e.to_http_error(...) for consistent error response generation.
    • Optimized repeated access to ctx.state.dispatch.as_ref() and workers.pd_runtime_type() by binding them to local variables.
    • Updated record_outcome_prefill and record_outcome_decode to use !e.is_server_error().
  • model_gateway/src/routers/grpc/mod.rs
    • Added pub(crate) mod tonic_ext; to expose the new module.
  • model_gateway/src/routers/grpc/tonic_ext.rs
    • Added a new file defining TonicStatusExt and TonicResultExt traits.
    • TonicStatusExt provides http_status(), is_server_error(), and to_http_error() methods for tonic::Status.
    • TonicResultExt provides is_healthy() for Result<T, tonic::Status>.
  • model_gateway/src/routers/grpc/utils.rs
    • Imported TonicStatusExt.
    • Updated collect_stream_responses to use e.to_http_error() for stream errors.
    • Added grpc_code to error logging for worker stream errors.
    • Changed ProtoResponseVariant::Error logging from error! to warn! and added a legacy path comment.
  • model_gateway/src/routers/http/router.rs
    • Adjusted the circuit breaker outcome recording to align with the new server error classification, using !status.is_server_error().
Activity
  • No specific activity (comments, reviews, or progress updates) was provided in the context.
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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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. ↩

@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: 315bc286f7

ℹ️ 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/grpc/tonic_ext.rs Outdated

@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

This pull request significantly improves gRPC error handling by propagating tonic::Status throughout the request pipeline, which enables more accurate HTTP status code mapping and refines the circuit breaker logic to differentiate between client and server errors. The introduction of TonicStatusExt and TonicResultExt provides a clean and centralized way to manage this logic. The changes make the error handling more robust and the codebase cleaner. The suggestion to refine gRPC to HTTP status code mapping further aligns with handling external system errors gracefully and propagating them appropriately.

Comment thread model_gateway/src/routers/grpc/tonic_ext.rs
- Map Code::Cancelled to 400 Bad Request (client-initiated, not 500)
- Map Code::Unimplemented to 501 Not Implemented (dedicated HTTP status)
- Add Code::Unimplemented to is_server_error() to trip circuit breaker
  for workers on incompatible/older backends

Signed-off-by: Chang Su <chang.s.su@oracle.com>

@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: 00f6c9ecc5

ℹ️ 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/router.rs Outdated
The previous change from is_success() to !is_server_error() fixed
client errors (400) not tripping the circuit breaker, but also made
408 (Request Timeout) and 429 (Too Many Requests) count as healthy.
These indicate backend degradation and should trip the circuit breaker.

Using !is_retryable_status() aligns the circuit breaker with the retry
predicate: 400 = healthy, 408/429/5xx = failure.

Signed-off-by: Chang Su <chang.s.su@oracle.com>
Replace is_server_error() with is_cb_failure() that composes
http_status() with is_retryable_status(). This ensures the circuit
breaker uses the same predicate for both HTTP and gRPC paths.

Key change: ResourceExhausted (429) now correctly trips the circuit
breaker. Previously it was treated as healthy despite indicating
backend overload. Unimplemented (501) no longer trips CB since it
is a permanent condition, not a transient failure.

Signed-off-by: Chang Su <chang.s.su@oracle.com>

@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/grpc/tonic_ext.rs`:
- Around line 27-44: In the http_status method on tonic_ext.rs (function
http_status), explicitly handle the Code::Ok case instead of letting it fall
through to the catch-all; update the match to include an arm for Code::Ok that
either returns StatusCode::OK or triggers a debug/assertion (e.g.,
debug_assert!(false, "unexpected Code::Ok in error path") and return
INTERNAL_SERVER_ERROR) so that accidental Ok values are clearly signaled; ensure
the change references the Code::Ok variant in the match alongside the existing
arms like Code::InvalidArgument, Code::Unauthenticated, etc., to make the
behavior defensive and obvious.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d3ad1628-86f6-44c0-ad20-c1fdc314af1f

📥 Commits

Reviewing files that changed from the base of the PR and between 00f6c9e and 85e57c1.

📒 Files selected for processing (3)
  • model_gateway/src/routers/grpc/common/stages/request_execution.rs
  • model_gateway/src/routers/grpc/tonic_ext.rs
  • model_gateway/src/routers/http/router.rs

Comment thread model_gateway/src/routers/grpc/tonic_ext.rs
Signed-off-by: Chang Su <chang.s.su@oracle.com>

@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: 48fff6d890

ℹ️ 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/grpc/tonic_ext.rs
@CatherineSue
CatherineSue merged commit 507bffc into main Mar 5, 2026
24 of 25 checks passed
@CatherineSue
CatherineSue deleted the chang/grpc-error-handling branch March 5, 2026 21:07
@CatherineSue
CatherineSue restored the chang/grpc-error-handling branch March 5, 2026 21:22
@lightseek-bot
lightseek-bot deleted the chang/grpc-error-handling branch March 5, 2026 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

grpc gRPC client and router changes model-gateway Model gateway crate changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants