fix(bitdefender): retry 429/5xx/timeout with backoff + reuse one httpx client

Audit fix H2 (+ M2): the live GravityZone tenant is rate-limited and sweeps fan
out one getManagedEndpointDetails per endpoint across every company, which hit a
real HTTP 429 (errorlog 2026-06-21). _post had zero retry and opened a fresh
httpx.Client (new TLS handshake) per request.
- _post now retries 429/500/502/503/504/timeout up to RETRY_MAX_ATTEMPTS with
  bounded exponential backoff + jitter, honoring Retry-After (numeric or HTTP-date).
  Retry notices go to stderr (don't pollute --json). Terminal errors still raise.
- M2: a single httpx.Client is created lazily and reused (connection pooling),
  closed via client.close() in main()'s finally. Makes the docstring's pooling
  claim true and cuts handshake overhead + 429 pressure during sweeps.
- Verified: compile clean; offline unit tests (persistent 429 -> 4 attempts then
  raise, flaky 503 -> recovers, Retry-After honored); live status read OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 12:51:08 -07:00
parent d8f0974e0f
commit 51751e6473
2 changed files with 109 additions and 8 deletions

View File

@@ -1310,6 +1310,7 @@ HANDLERS = {
def main(argv=None) -> int:
args = build_parser().parse_args(argv)
handler = HANDLERS[args.command]
client = None
try:
client = GravityZoneClient()
rc = handler(client, args)
@@ -1322,6 +1323,9 @@ def main(argv=None) -> int:
return 1
except KeyboardInterrupt:
return 130
finally:
if client is not None:
client.close()
if __name__ == "__main__":