Repository navigation
perf(http): use shared pooled HTTP client in monitoring_integration (#12979) - #12982
Merged
Merged
Conversation
…12979) Replaces 11 per-request aiohttp.ClientSession constructions with the shared pooled client from autobot_shared.http_client. Nine JSON-consuming sites that did raise_for_status() + json() become get_json()/post_json(), which is semantically exact and lifetime-safe. The two test_connection() sites inspect response.status and return a structured IntegrationHealth error rather than raising, so they cannot use get_json(). They use async with await get_http_client().get(...) instead: client.get() returns a ClientResponse rather than yielding one, so the async with is the connection-release point back into the pool and dropping it would leak pooled connections. Also gains W3C trace-context propagation on all 11 calls, which raw per-request sessions did not have.
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thinking Path
Pilot file for #12979, chosen in the issue discussion because its 11 sites are uniform enough to establish a pattern but contained enough that a mistake is reviewable.
The naive substitution here is wrong. Call sites currently nest two context managers:
The inner
async withis the connection-release point.HTTPClientManager.get()returns aClientResponserather than yielding one, so dropping thatasync withduring conversion would trade per-request session creation for leaked pooled connections — strictly worse, because a finite pool exhausts rather than merely paying a handshake.So the file splits by how each site consumes the response, not by shape:
raise_for_status()+json()get_json()/post_json()— semantically exact, lifetime handled by the helperif response.status == 200:→ structured errorasync with await client.get(...),async withmandatoryThe 2 exceptions are both
test_connection()(Datadog:66, New Relic:289). They return anIntegrationHealth(status=ERROR, message=f"... {response.status}")object instead of raising.get_json()callsraise_for_status(), so converting them uniformly would silently turn "report the failure" into "raise" — precisely in the error paths of two monitoring integrations, i.e. where behaviour change is least welcome and least likely to be noticed.What Changed
autobot-backend/integrations/monitoring_integration.pyonly.from autobot_shared.http_client import get_http_client.await get_http_client().get_json(url, headers=..., timeout=...)(
_list_monitors,_get_metrics,_list_hosts,_get_eventson Datadog;_list_applications,_list_alerts,_get_app_healthon New Relic)await get_http_client().post_json(url, payload, headers=..., timeout=...)(
_create_monitor, New Relic NRQL GraphQL_get_metrics)async with await get_http_client().get(...) as response:with the existingif response.status == 200: ... else: ...logic preserved verbatim. Each carries a comment explaining why theasync withis load-bearing.import aiohttpis retained — still needed foraiohttp.ClientTimeoutand theexcept aiohttp.ClientErrorhandlers. Per-call timeouts and headers are unchanged;HTTPClientManager.request()forwards both straight through tosession.request().No behaviour change: the 9 converted sites already called
raise_for_status()by hand, which is exactly whatget_json/post_jsondo. The 2 error-reporting paths keep returning structuredIntegrationHealthobjects.Incidental gain:
request()merges W3C trace context into caller headers via OTelinject(), so all 11 calls now propagate distributed traces. Raw per-request sessions missed this entirely.Verification
Counts, before → after:
aiohttp.ClientSession(constructionsget_json(call sitespost_json(call sitesasync with await get_http_client()sitesStatic checks:
Import smoke:
Functional — all 11 converted paths exercised end-to-end against a local
aiohttptest server, including the 503 branch that the 2 special-cased sites exist to serve:The 503 line is the regression this split exists to prevent: a uniform
get_json()conversion would have raised there instead of returningIntegrationStatus.ERROR.Connection-lifetime check — the trap this PR is guarding against. After 12 requests through the shared pool:
One connection reused across all 12 requests, zero still acquired. Both the
get_jsonhelpers and the two hand-writtenasync withblocks release correctly; had either dropped itsasync with,still-acquiredwould be non-zero.No existing tests reference this module (
grep -rl "monitoring_integration\|DatadogIntegration\|NewRelicIntegration" --include=*.pyreturns only the module itself,api/integration_monitoring.py, and an unrelated infrastructure script), so the local server exercise above stands in for regression coverage.Discovered, not fixed here
Filed #12981:
HTTPClientManager._active_requestsincrements on everyrequest()but only decrements on the error path, andget_json/post_jsonnever decrement — so the counter grows monotonically (active_requests: 12after 12 clean requests, visible in the stats above). This drives pool auto-sizing utilization permanently upward and means a deferred pool recreation never applies. Exactly one caller in the repo honours the contract, against ~151 call sites.Pre-existing and not introduced by this PR — it affects every shared-client call site equally. Left out of scope deliberately: the fix belongs in
autobot_shared/http_client.pyand would change behaviour for all 151 sites, which does not belong in a single-file pilot conversion.Model Used
claude-opus-5
Refs #12979.