Skip to content

fix(bedrock): enforce the model deadline and connect timeout on the SDK client - #845

Merged
jarvis9443 merged 2 commits into
mainfrom
fix/bedrock-timeout-enforcement
Jul 30, 2026
Merged

fix(bedrock): enforce the model deadline and connect timeout on the SDK client#845
jarvis9443 merged 2 commits into
mainfrom
fix/bedrock-timeout-enforcement

Conversation

@jarvis9443

@jarvis9443 jarvis9443 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Problem

The Bedrock bridge was the only dispatch path where a model's timeout did not actually bound the call. The SDK client is built with no timeout_config at all, and ctx.deadline was consulted only inside map_sdk_error — i.e. after a call had already failed for some other reason, purely to relabel the error as Timeout. A Bedrock upstream that accepted the connection and then went silent held the request open indefinitely, while every other bridge cancels at the deadline via its with_deadline / tokio::time::timeout wrapper. (The connect phase itself was bounded only by the SDK default plugins' own 3.1 s — upstream.connect_timeout_ms, which reaches every reqwest-based bridge through the shared client builder, never reached the AWS SDK client, including its documented 0 = disabled convention.)

Change

build_client now installs a TimeoutConfig on the SDK client:

  • connect_timeout ← the shared upstream.connect_timeout_ms (same knob every other bridge honors);
  • operation_timeout ← the per-request ctx.deadline (the model/group/deployment-resolved timeout). An elapsed deadline surfaces as SdkError::TimeoutError, which map_sdk_error already maps to BridgeError::Timeout, so retry / fallback / cooldown classification is untouched. The per-request client construction the bridge already does (stateless by design) is what makes a per-request operation_timeout possible.

converse_stream bodies stay bounded per-chunk by the proxy-side read-timeout wrapper, exactly like every other bridge's streams; operation_timeout covers send + response headers only.

SDK-internal retries are disabled (RetryConfig::disabled()): retries belong to the gateway's routing::effective_retries budget, which emits per-attempt telemetry and honors per-model config. Left at its default, the SDK adds a hidden standard-mode retry layer (up to 3 attempts) underneath the gateway's own budget, grinding the upstream invisibly — no other bridge's HTTP client retries on its own.

Not in scope: the Bedrock guardrail client builds its own SDK client but wraps every call in tokio::time::timeout(guardrail.timeout_ms) (default 5 s), so it is already bounded end-to-end.

Behaviour changes

  1. Bedrock models now time out at their resolved deadline like every other provider (previously: unbounded on a silent upstream).
  2. Hidden SDK-level retries (up to 3 transparent attempts on retryable failures) no longer happen; retry behaviour is governed solely by Model.retries / routing.retries / upstream.retries.

Tests

chat_deadline_cancels_a_silent_upstream drives the real bridge.chat() through wiremock with an 8 s response delay and a 300 ms deadline, asserting BridgeError::Timeout and an elapsed time far below the delay (cancellation, not relabelling). Verified to fail without the fix: the call sits through the full 8 s and returns Ok.

sdk_does_not_retry_on_its_own pins the single-attempt semantics with a wiremock hit counter: a 500 reaches the gateway after exactly one wire attempt. Verified to fail without RetryConfig::disabled() — the SDK hits the upstream 3 times.

…DK client

The Bedrock bridge was the only dispatch path where a model's timeout did
not actually bound the call: the SDK client was built with no
timeout_config at all, and ctx.deadline was consulted only inside
map_sdk_error to relabel an already-failed call as Timeout. A Bedrock
upstream that accepted the connection and then stalled held the request
open indefinitely — while every other bridge cancels via its
with_deadline/tokio::time::timeout wrapper.

- build_client now sets TimeoutConfig: connect_timeout from the shared
  upstream.connect_timeout_ms, operation_timeout from the per-request
  ctx.deadline. An elapsed deadline surfaces as SdkError::TimeoutError,
  which map_sdk_error already turns into BridgeError::Timeout, so retry /
  fallback / cooldown classification is unchanged.
- SDK-internal retries are disabled: retries belong to the gateway's
  routing::effective_retries budget (per-attempt telemetry, per-model
  config). Left at the default the SDK would silently add a
  standard-mode retry layer (3 attempts) underneath it — no other
  bridge's HTTP client retries on its own.
- converse_stream bodies stay bounded per-chunk by the proxy-side read
  wrapper, same as other bridges; operation_timeout covers send +
  response headers only.

The regression test drives the real bridge.chat() through wiremock with
an 8 s response delay and a 300 ms deadline: before this fix the call
sat through the full delay and returned Ok; now it cancels at the
deadline with BridgeError::Timeout.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fdfd9b23-cb39-4d01-8000-ccc488b13871

📥 Commits

Reviewing files that changed from the base of the PR and between 80467af and aa698f8.

📒 Files selected for processing (1)
  • crates/aisix-provider-bedrock/src/bridge.rs

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

…ut_ms=0

Audit follow-ups:

- New wiremock hit-count test: a 500 from the upstream must reach the
  gateway's routing budget after exactly one wire attempt. Verified to
  fail without RetryConfig::disabled() — the SDK's standard mode hits
  the upstream 3 times.
- connect_timeout None (operator set upstream.connect_timeout_ms: 0,
  the documented off switch) now calls disable_connect_timeout();
  leaving the builder slot unset let the SDK's default plugins restore
  their own 3.1 s connect timeout.
- Comment wording: the pre-fix client was not timeout-free — the SDK
  defaults supplied a 3.1 s connect timeout; what was unbounded is
  everything after connect (operation/read), which is the actual bug.
@jarvis9443

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jarvis9443
jarvis9443 merged commit 35eae92 into main Jul 30, 2026
12 checks passed
@jarvis9443
jarvis9443 deleted the fix/bedrock-timeout-enforcement branch July 30, 2026 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant