feat(api): reuse connections, add timeouts, and stamp emissions with measurement time - #1340
davidberenstein1957 wants to merge 2 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1340 +/- ##
==========================================
+ Coverage 91.43% 91.71% +0.27%
==========================================
Files 49 49
Lines 5057 5176 +119
==========================================
+ Hits 4624 4747 +123
+ Misses 433 429 -4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
a53dcb9 to
863b9c3
Compare
863b9c3 to
76e81b7
Compare
0658642 to
697e1e8
Compare
76e81b7 to
ec517ea
Compare
…measurement time `ApiClient` called the module-level `requests` functions, so every call opened a new TCP connection and TLS handshake. A tracker sending one measurement per tick opened 50 connections for 50 uploads; with a `Session` it opens 1. The flat 2s timeout is replaced with `(3.05, 10)`: the old value timed out against a healthy but loaded API, while a hung endpoint still cannot block the scheduler thread for long. `ApiClient.add_emission` also discarded `carbon_emission["timestamp"]` and called `get_datetime_with_timezone()` instead, so every row stored the moment the payload was built rather than the moment it was measured. Harmless while the two are milliseconds apart, wrong by the full latency as soon as a send is slow, retried or queued. The measurement timestamp is now normalised to offset-aware, falling back to now when the payload carries none or an unparseable one (the method is public and takes a plain dict). CSV output is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e9cecd4 to
7b566a8
Compare
Verdict: ✅ Approve with nits (please merge #1339 first)Session reuse (with Issues:
Nit:
|
A down API now costs one blocking run-creation call per minute instead of one per measurement tick. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Made the changes in 72c53b7: 60 s cooldown after a failed run creation. On the 2 x 13 s concern: Not done: rewriting the description (my edit was blocked; the retry/config/backoff sections are stale, the diff is Session + timeouts + measurement timestamp + cooldown). Skipped the DST nit: making the tracker timestamp offset-aware changes the CSV timestamp format, which needs a maintainer call. |
|
Deferring the DST timestamp nit: making the timestamp timezone-aware at the source changes the CSV timestamp format, so it will ship in the next minor release together with #1334 rather than in this PR. |
Description
ApiClient._requestcalled barerequests.get/post/patchwith a hardcodedtimeout=2, noSessionand no retry policy, andhttp.py:_emitlogged failures away. This adds aSessionwith a jitteredRetryadapter, configurable timeouts (api_timeout,api_retries), and a backoff guard around run creation. It also stamps emissions with their measurement time. POST requests use a deliberately narrower retry policy than GET/PATCH, sincecarbonserverhas no idempotency key onPOST /emissionsand the dashboard sums emission rows, so retrying a POST whose response was lost after the insert committed would silently inflate a user's reported emissions.Related Issue
Part of #1338
Motivation and Context
Without connection reuse or retries, every emission POST opened a new TCP connection, and a single transient failure (503, connection error) permanently dropped a measurement row with no visibility that it happened. This is stacked on #1341 (
scaling/01-emission-timestamps), so its diff is limited to its own two commits; retarget tomasteronce #1341 lands.How Has This Been Tested?
Measured against a loopback stub server (no real network): 50 sequential emission POSTs went from 50 TCP connections / 0.085s to 1 connection / 0.023s, and a row hitting two transient 503s went from 0/1 rows delivered to 1/1 delivered. New test file
tests/test_api_client_retry.py, 14 tests covering retry-then-succeed on 503, give-up after N, no-retry on 4xx, retry on connection error, connection reuse, timeout pass-through, both run-creation backoff directions, and pinning of the POST-specific policy (read timeout on POST not retried, 503/504 on POST handled per the split policy, read timeout on GET still retried). Plustest_api_timeout_and_retries_reach_the_api_clientintests/test_emissions_tracker.py, asserting the two config knobs survive the constructor path. Tests use a stdlib loopback server rather thanrequests_mock, sincerequests_mockbypasses theHTTPAdapter/Retrylayer under test. Known flake, verified pre-existing on master:tests/test_emissions_tracker_flush.pyandtests/test_logging_output.pyflake on the test machine independent of this change.Screenshots (if appropriate):
N/A
Types of changes
AI Usage Disclosure
Checklist:
api_timeout(default 5s) andapi_retries(default 2) are documented indocs/how-to/configuration.md, including which failures are retried on POST and why. Not included in this PR: aligning the directrequests.getcalls inelectricitymaps_api/geography.pywith the same policy, and a delivery-failure counter in the final log line.