Skip to content

Commit 36042ac

Browse files
add nginx temporary fail codes to transient exception ignorification/retry
1 parent eca4986 commit 36042ac

2 files changed

Lines changed: 39 additions & 15 deletions

File tree

scripts/run_integration_tests.sh

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
# dedicated AI-enabled server (test_ai_task_types.py and test_ai_examples.py
88
# hardcode http://localhost:7001/api, and test_agentic_workflows.py needs an
99
# `openai` LLM provider configured on the server), so they don't run against
10-
# the standard test server this suite targets.
10+
# the standard test server this suite targets. The entire tests/integration/ai/
11+
# suite is excluded for the same reason: it drives the agent runtime (POST
12+
# /api/agent/start plus an LLM provider), which the standard server doesn't
13+
# expose (it 404s /api/agent/start).
1114
#
1215
# The performance test (test_update_task_v2_perf.py) is also excluded by
1316
# default: it submits ~1000 workflows and takes several minutes. Pass
@@ -93,11 +96,15 @@ for arg in "$@"; do
9396
done
9497

9598
# The AI/agentic tests always target a dedicated server (see header) and are
96-
# never part of these buckets.
99+
# never part of these buckets. The tests/integration/ai/ suite drives the agent
100+
# runtime (POST /api/agent/start, an LLM provider, SSE streaming): the standard
101+
# sdkdev server has no agent API and returns 404 for every one of them, so the
102+
# whole directory is excluded here rather than file-by-file.
97103
ai_ignore=(
98104
--ignore=tests/integration/test_ai_task_types.py
99105
--ignore=tests/integration/test_ai_examples.py
100106
--ignore=tests/integration/test_agentic_workflows.py
107+
--ignore=tests/integration/ai
101108
)
102109

103110
# Build the target paths + selection for the chosen bucket. The slow buckets are

tests/integration/retry_helpers.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
99
The suites run against a shared dev server whose transport occasionally blips
1010
(read timeout, "server disconnected without a response", HTTP/2 GOAWAY, stale
11-
keep-alive) and surfaces as ``ApiException(status=0)``. Those are not real test
12-
failures.
11+
keep-alive) and surfaces as ``ApiException(status=0)``, or whose proxy/LB
12+
briefly returns a gateway-class 5xx (502/503/504) while the upstream is
13+
unreachable or restarting. Those are not real test failures.
1314
1415
Rather than re-running the whole suite on such a blip, ``test_all`` establishes
1516
a single overall wall-clock deadline once, and each *scenario* is retried on a
@@ -41,14 +42,28 @@
4142
# helpers across the integration suites so the set can't drift file to file.
4243
TERMINAL_WORKFLOW_STATES = ('COMPLETED', 'FAILED', 'TIMED_OUT', 'TERMINATED')
4344

45+
# Gateway-class 5xx statuses. On the shared dev server these come from the
46+
# proxy/LB in front of Conductor (nginx "502 Bad Gateway" / "503 Service
47+
# Temporarily Unavailable" / "504 Gateway Timeout") when the upstream is briefly
48+
# unreachable or restarting, not from the Conductor app itself — so they're
49+
# transient infrastructure blips, not real test failures. 500 is deliberately
50+
# excluded (more likely a genuine app error) and so is 429 (wants Retry-After
51+
# handling rather than blind backoff).
52+
GATEWAY_STATUSES = (502, 503, 504)
53+
4454

4555
def is_transient(exc):
46-
"""True when ``exc`` is a transient transport blip against the shared dev
47-
server (read timeout, connection reset, HTTP/2 GOAWAY, stale keep-alive)
48-
rather than a real failure. status 0/None means no HTTP response arrived.
56+
"""True when ``exc`` is a transient blip against the shared dev server
57+
rather than a real failure. Covers both transport-level hiccups where no
58+
HTTP response arrived (read timeout, connection reset, HTTP/2 GOAWAY, stale
59+
keep-alive — surfaced as status 0/None or the ``transient`` flag) and
60+
gateway-class 5xx (502/503/504) returned by the proxy/LB in front of the
61+
server (see ``GATEWAY_STATUSES``).
4962
"""
5063
return isinstance(exc, ApiException) and (
51-
getattr(exc, 'transient', False) or exc.status in (0, None))
64+
getattr(exc, 'transient', False)
65+
or exc.status in (0, None)
66+
or exc.status in GATEWAY_STATUSES)
5267

5368

5469
def first_transient_api_exception(exc):
@@ -72,9 +87,10 @@ def first_transient_api_exception(exc):
7287

7388

7489
def retry_on_transient(func, *args, retries=4, base_delay=1, **kwargs):
75-
"""Retry ``func(*args, **kwargs)`` on a transient (status 0) transport blip
76-
with capped-free exponential backoff. Non-transient errors (real 4xx/5xx,
77-
assertion failures) raise immediately.
90+
"""Retry ``func(*args, **kwargs)`` on a transient blip (see ``is_transient``:
91+
status 0/None transport hiccups plus gateway-class 502/503/504) with
92+
capped-free exponential backoff. Non-transient errors (real 4xx, app-level
93+
500, assertion failures) raise immediately.
7894
7995
This is *per-request* retry, suited to idempotent calls (get/register).
8096
For non-idempotent scenarios (start_workflow, signal) use ``retry_scenario``,
@@ -96,8 +112,8 @@ def retry_on_transient(func, *args, retries=4, base_delay=1, **kwargs):
96112
def retry_on_status(func, *args, statuses=(404,), retries=5, base_delay=1.0,
97113
max_delay=None, **kwargs):
98114
"""Retry ``func(*args, **kwargs)`` on an ``ApiException`` whose status is in
99-
``statuses`` (in addition to transient status-0 blips), with capped
100-
exponential backoff. Any other error raises immediately.
115+
``statuses`` (in addition to transient blips — see ``is_transient``), with
116+
capped exponential backoff. Any other error raises immediately.
101117
102118
Intended for read-after-write races against the shared dev server: a GET
103119
issued right after a register/update can briefly 404 until the write
@@ -124,8 +140,9 @@ def retry_on_status(func, *args, statuses=(404,), retries=5, base_delay=1.0,
124140
def retry_scenario(label, func, *args, deadline=None,
125141
base_delay=DEFAULT_BASE_DELAY_SECONDS,
126142
max_delay=DEFAULT_MAX_DELAY_SECONDS, **kwargs):
127-
"""Run ``func(*args, **kwargs)``, retrying only on a transient (status 0)
128-
transport blip until the shared ``deadline`` passes.
143+
"""Run ``func(*args, **kwargs)``, retrying only on a transient blip (see
144+
``is_transient``: status 0/None transport hiccups plus gateway-class
145+
502/503/504) until the shared ``deadline`` passes.
129146
130147
Args:
131148
label: Human-readable scenario name for logs.

0 commit comments

Comments
 (0)