Skip to content

fix: preserve error codes through BLS error paths - #454

Open
Swigler wants to merge 4 commits into
triton-inference-server:mainfrom
Swigler:fix/propagate-bls-error-code
Open

fix: preserve error codes through BLS error paths#454
Swigler wants to merge 4 commits into
triton-inference-server:mainfrom
Swigler:fix/propagate-bls-error-code

Conversation

@Swigler

@Swigler Swigler commented Sep 4, 2026

Copy link
Copy Markdown

Summary

Fixes triton-inference-server/server#7804

When Python models are chained via BLS, error codes (e.g. NOT_FOUND, UNSUPPORTED) were lost and always became INTERNAL. This PR preserves the original error code through all BLS error paths.

Root cause

Three structural issues caused error codes to be dropped:

  1. THROW_IF_TRITON_ERROR macro — extracted the error message but discarded the error code when throwing PythonBackendException
  2. ResponseBatch struct — had no error_code field, so batch-level errors were hardcoded to TRITONSERVER_ERROR_INTERNAL
  3. Exception catch blocks — in ExecuteBLSRequest, SendBLSDecoupledResponse, InferResponseComplete, and several other paths, PbError was created without the error code from the caught exception

Changes

File Change
src/pb_exception.h Add two-arg constructor preserving error code (stored as int to avoid tritonserver.h dependency)
src/pb_utils.h Add error_code field to ResponseBatch; update THROW_IF_TRITON_ERROR to capture error code
src/python_be.cc Read error_code from ResponseBatch instead of hardcoding INTERNAL; propagate in ExecuteBLSRequest and SendBLSDecoupledResponse catch blocks; initialize in PrepareResponseBatch
src/request_executor.cc Pass error code in CreateTritonErrorFromException and Infer() re-throw
src/infer_request.cc Pass error_code from ResponseBatch to PbError in Exec(); preserve in outer catch
src/pb_stub.cc Capture error code in ProcessRequests catch; write to ResponseBatch; pass in ProcessBLSResponseDecoupled

Test

Added examples/bls_error_code/ with two test models and a client script:

  • error_source — returns an InferenceResponse with a specific TritonError code
  • bls_error_caller — calls error_source via BLS and reports the received code

Tested on Triton 2.50.0 (r24.09) with an A100 GPU on RunPod:

Patched:

  Sent: NOT_FOUND       (2) -> Got: NOT_FOUND       (2)  [OK]
  Sent: INVALID_ARG     (3) -> Got: INVALID_ARG     (3)  [OK]
  Sent: UNAVAILABLE     (4) -> Got: UNAVAILABLE     (4)  [OK]
  Sent: UNSUPPORTED     (5) -> Got: UNSUPPORTED     (5)  [OK]
  Sent: CANCELLED       (7) -> Got: CANCELLED       (7)  [OK]
ALL 5 TESTS PASSED

Stock (unpatched):

  Sent: NOT_FOUND       (2) -> Got: INTERNAL        (1)  [FAIL]
  Sent: INVALID_ARG     (3) -> Got: INTERNAL        (1)  [FAIL]
  Sent: UNAVAILABLE     (4) -> Got: INTERNAL        (1)  [FAIL]
  Sent: UNSUPPORTED     (5) -> Got: INTERNAL        (1)  [FAIL]
  Sent: CANCELLED       (7) -> Got: INTERNAL        (1)  [FAIL]
ALL 5 FAILED

Test plan

  • Build patched python_backend against r24.09 Triton headers
  • Run integration test on live Triton 2.50.0 — all 5 error codes preserved
  • Confirm stock backend loses all error codes (control test)
  • Run existing CI/test suite (no existing BLS error code tests found in this repo)

🤖 Generated with Claude Code

Swigler and others added 3 commits September 4, 2026 13:14
The BLS error code was always lost and replaced with INTERNAL because:

1. ResponseBatch had no error_code field - batch-level errors could
   only carry a message string, so the code was hardcoded to INTERNAL
   when reading them back.

2. THROW_IF_TRITON_ERROR discarded error codes - it extracted the
   message but threw PythonBackendException(message) without the code,
   so any path going through this macro lost the original error type.

Fix:
- Add error_code field to ResponseBatch struct
- Add error code support to PythonBackendException (stored as int to
  keep the header lightweight)
- Update THROW_IF_TRITON_ERROR to preserve the error code
- Propagate error_code through all batch error write/read paths:
  ExecuteBLSRequest, SendBLSDecoupledResponse, ProcessRequests,
  ProcessBLSResponseDecoupled, and InferRequest::Exec

Fixes: triton-inference-server/server#7804

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The initial fix covered the primary ResponseBatch path but four
secondary paths still dropped error codes to INTERNAL:

- CreateTritonErrorFromException: hardcoded INTERNAL, now reads
  the code from PythonBackendException
- RequestExecutor::Infer re-throw: wrapped message without code,
  now passes ErrorCode() through
- Stub::ProcessRequests execute catch: never wrote error_code to
  ResponseBatch, now captures and writes it
- InferRequest::Exec outer catch: one-arg PbError, now passes
  the code from the caught exception

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test models and client script that verify error codes are preserved
when Python models are chained via BLS, covering the bug reported in
triton-inference-server/server#7804.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR preserves Triton error codes as exceptions and batch-level failures cross the BLS and shared-memory boundaries.

  • Adds explicit error-code presence tracking to PythonBackendException.
  • Carries batch error codes between the backend and Python stub for regular and decoupled BLS responses.
  • Preserves INTERNAL as the fallback for exceptions without an explicit code.
  • Adds an example integration test covering five non-INTERNAL error codes.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/pb_exception.h Adds explicit error-code presence tracking so message-only exceptions remain distinguishable from UNKNOWN-coded exceptions.
src/pb_utils.h Preserves Triton error codes in the exception macro and extends the shared response-batch payload with an INTERNAL-defaulted code.
src/request_executor.cc Propagates explicit Triton codes while retaining the INTERNAL fallback for uncoded setup and validation failures.
src/python_be.cc Initializes, transports, and reconstructs batch-level error codes across regular and decoupled BLS paths.
src/infer_request.cc Reconstructs Python-facing BLS errors with the transported code and safely defaults uncoded exceptions to INTERNAL.
src/pb_stub.cc Serializes model-execution exception codes and restores them in decoupled BLS responses.

Sequence Diagram

sequenceDiagram
    participant M as Calling Python model
    participant S as Python stub
    participant B as Backend BLS executor
    participant T as Target model
    M->>S: exec()
    S->>B: BLS request via shared memory
    B->>T: Submit inference
    T-->>B: Error response with Triton code
    B-->>S: ResponseBatch with message and error_code
    S-->>M: InferenceResponse with preserved TritonError code
Loading

Reviews (2): Last reviewed commit: "fix: preserve INTERNAL fallback for unco..." | Re-trigger Greptile

Comment thread src/request_executor.cc Outdated
When a PythonBackendException without an explicit error code was caught
and rethrown in RequestExecutor::Infer, the two-arg constructor marked
the default code (0/UNKNOWN) as explicitly present. This caused
CreateTritonErrorFromException to use UNKNOWN instead of falling back
to INTERNAL for setup/validation failures.

Now only passes the error code through if the original exception
actually carried one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

InferenceResponse error code is lost in Python BLS

1 participant