88
99The 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
1415Rather than re-running the whole suite on such a blip, ``test_all`` establishes
1516a single overall wall-clock deadline once, and each *scenario* is retried on a
4142# helpers across the integration suites so the set can't drift file to file.
4243TERMINAL_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
4555def 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
5469def first_transient_api_exception (exc ):
@@ -72,9 +87,10 @@ def first_transient_api_exception(exc):
7287
7388
7489def 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):
96112def 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,
124140def 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