fix(agent): отрицательный Retry-After, обрыв при чтении тела ошибки
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
This commit is contained in:
parent
1d5e128bd0
commit
24cfdcf25c
@ -86,12 +86,14 @@ class Http:
|
|||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
status = resp.status
|
status = resp.status
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
|
self._touch()
|
||||||
try:
|
try:
|
||||||
text = e.read().decode("utf-8", "replace")
|
|
||||||
headers = {k.lower(): v for k, v in e.headers.items()}
|
headers = {k.lower(): v for k, v in e.headers.items()}
|
||||||
|
text = e.read().decode("utf-8", "replace")
|
||||||
|
except (OSError, ConnectionError, TimeoutError) as err:
|
||||||
|
raise ConnectionError("%s: обрыв при чтении ответа: %s" % (self.base, err)) from None
|
||||||
finally:
|
finally:
|
||||||
e.close()
|
e.close()
|
||||||
self._touch()
|
|
||||||
raise HttpError(e.code, text, headers) from None
|
raise HttpError(e.code, text, headers) from None
|
||||||
except (urllib.error.URLError, ConnectionError, TimeoutError, OSError) as e:
|
except (urllib.error.URLError, ConnectionError, TimeoutError, OSError) as e:
|
||||||
raise ConnectionError("%s недоступен: %s" % (self.base, e)) from None
|
raise ConnectionError("%s недоступен: %s" % (self.base, e)) from None
|
||||||
@ -149,7 +151,7 @@ class JiraClient(Http):
|
|||||||
if e.code in (429, 503) and attempt < 5:
|
if e.code in (429, 503) and attempt < 5:
|
||||||
wait = 60 * attempt
|
wait = 60 * attempt
|
||||||
try:
|
try:
|
||||||
wait = int(e.headers.get("retry-after"))
|
wait = max(0, int(e.headers.get("retry-after")))
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
self.rate_limited += 1
|
self.rate_limited += 1
|
||||||
|
|||||||
@ -66,6 +66,29 @@ class ClientsTest(unittest.TestCase):
|
|||||||
self.jira.get("/rest/api/2/issue/AURORA-1")
|
self.jira.get("/rest/api/2/issue/AURORA-1")
|
||||||
self.assertEqual(waits, [60])
|
self.assertEqual(waits, [60])
|
||||||
|
|
||||||
|
def test_retry_after_negative(self):
|
||||||
|
waits = []
|
||||||
|
self.jira.sleep = waits.append
|
||||||
|
self.fake.add("AURORA-1")
|
||||||
|
self.fake.fail_next.append((429, {"Retry-After": "-5"}))
|
||||||
|
self.jira.get("/rest/api/2/issue/AURORA-1")
|
||||||
|
self.assertEqual(waits, [0])
|
||||||
|
|
||||||
|
def test_error_body_read_failure_is_connection_error(self):
|
||||||
|
import io
|
||||||
|
import urllib.error
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
class Broken(io.BytesIO):
|
||||||
|
def read(self, *a):
|
||||||
|
raise TimeoutError("timed out")
|
||||||
|
|
||||||
|
err = urllib.error.HTTPError("http://x", 500, "boom", {}, Broken())
|
||||||
|
with mock.patch("urllib.request.urlopen", side_effect=err):
|
||||||
|
with self.assertRaises(ConnectionError):
|
||||||
|
self.jira.get("/x")
|
||||||
|
self.assertEqual(self.jira.calls, 1)
|
||||||
|
|
||||||
def test_bearer_header(self):
|
def test_bearer_header(self):
|
||||||
cfg = jira_cfg(self.fake)
|
cfg = jira_cfg(self.fake)
|
||||||
cfg["jira"]["auth"] = {"type": "bearer", "token": "tok123"}
|
cfg["jira"]["auth"] = {"type": "bearer", "token": "tok123"}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user