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:
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user