Skip to content

fix(ai-proxy): map upstream LLM timeouts to 504 instead of 500#13481

Merged
nic-6443 merged 1 commit into
apache:masterfrom
nic-6443:fix/ai-proxy-timeout-504
Jun 9, 2026
Merged

fix(ai-proxy): map upstream LLM timeouts to 504 instead of 500#13481
nic-6443 merged 1 commit into
apache:masterfrom
nic-6443:fix/ai-proxy-timeout-504

Conversation

@nic-6443

@nic-6443 nic-6443 commented Jun 8, 2026

Copy link
Copy Markdown
Member

Description

When an AI proxy request times out reaching the upstream LLM, the client receives 500 Internal Server Error instead of 504 Gateway Timeout.

apisix/plugins/ai-transport/http.lua maps errors to 504 only when the error string contains the contiguous substring timeout:

function _M.handle_error(err)
    if core.string.find(err, "timeout") then
        return 504
    end
    return 500
end

core.string.find is a plain-text search, and an OS connect timeout surfaces as Operation timed out / Connection timed out, which contains timed out (with a space), not the contiguous timeout. So it falls through to the default 500.

Timeout error taxonomy

handle_error receives its error from lua-resty-http over OpenResty cosockets. lua-resty-http propagates the raw cosocket error unchanged (return nil, err in every connect/send/receive/body-reader path), so the only timeout spellings that can reach handle_error are produced by the cosocket layer and the nginx resolver:

Scenario Source String reaching handle_error matched by
connect / send / read / streaming-read deadline (the set_timeout value fires) cosocket timer (ngx_http_lua_socket_tcp.c, FT_TIMEOUT) timeout timeout
kernel connect ETIMEDOUT, Linux errno strerror (lowercased by ngx_lua) connection timed out timed out
kernel connect ETIMEDOUT, macOS/BSD errno strerror (lowercased by ngx_lua) operation timed out timed out
DNS resolver timeout ngx_resolver_strerror (hardcoded) Operation timed out timed out

The complete timeout set collapses to exactly two substrings: timeout (cosocket timer) and timed out (errno / resolver). Matching both is necessary (the bare timeout case has no space; the … timed out cases have no contiguous timeout) and sufficient. Non-timeout errors (connection refused, connection reset by peer, closed) keep returning 500.

Fix

Match timed out in addition to timeout:

function _M.handle_error(err)
    if core.string.find(err, "timeout") or core.string.find(err, "timed out") then
        return 504
    end
    return 500
end

handle_error is the single status mapper behind all AI upstream failure paths (ai-proxy request/metric, ai-providers sidecar request and streaming read), so all of them now return 504 on timeout.

Tests

Added two cases to t/plugin/ai-transport-http.t:

  • a regression test that mocks connect returning Operation timed out and asserts the mapped status is 504;
  • a matrix over one representative per timeout class plus non-timeout controls.

Both fail before the change (the … timed out cases return 500) and pass after.

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change (N/A — internal error-code mapping, no documented behavior change)
  • I have verified that this change is backward compatible (timeouts previously returned 500; they now return the more accurate 504)

handle_error() only matched the contiguous substring "timeout", so OS-level
connect timeouts (ETIMEDOUT -> "connection timed out" / "operation timed out")
and DNS resolver timeouts ("Operation timed out") fell through to 500. These
are gateway-timeout conditions and should surface as 504. Match "timed out"
in addition to "timeout" so every timeout variant maps to 504, while
non-timeout errors keep returning 500.
Copilot AI review requested due to automatic review settings June 8, 2026 08:29
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working labels Jun 8, 2026

Copilot AI 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.

Pull request overview

This PR fixes AI upstream timeout handling by mapping additional upstream timeout error strings (notably "... timed out" variants coming from OS errno / resolver) to 504 Gateway Timeout instead of incorrectly returning 500 Internal Server Error.

Changes:

  • Extend apisix.plugins.ai-transport.http.handle_error to match both timeout and timed out.
  • Add regression and matrix tests to ensure all known timeout spellings map to 504, while non-timeout network errors remain 500.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
apisix/plugins/ai-transport/http.lua Expands timeout detection logic in handle_error to cover "... timed out" errors and documents the rationale.
t/plugin/ai-transport-http.t Adds tests covering Operation timed out and a representative matrix of timeout/non-timeout strings for handle_error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@nic-6443
nic-6443 merged commit e99fb63 into apache:master Jun 9, 2026
19 checks passed
@nic-6443
nic-6443 deleted the fix/ai-proxy-timeout-504 branch June 9, 2026 06:05
wistefan pushed a commit to wistefan/apisix that referenced this pull request Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants