Repository navigation
fix(queue): retry queued requests on transient errors instead of dropping them - #130
Conversation
|
…quests - tests: back each new dispatch test with its own tmp_path persistqueue instead of draining the shared on-disk queue - client: use logger.exception for non-retryable request errors so the failure reason is logged, not just the payload Addresses Greptile review on ActivityWatch#130. Git-Session-Id: 4948e2f0-e57b-5a82-899b-c1b20628051d
|
@greptileai review |
|
Both review findings are fixed in 82a7736 and Greptile now reports 5/5 with no new actionable issues. Seven checks have passed; the macOS test is still queued. No further code change or review round is needed. Merge remains with a maintainer (Bob has pull-only access); once CI finishes, this carry is ready for your review/merge or superseding with a direct rebase of #115. |
…ping them The request queue exists to preserve heartbeats when the server is unavailable, but _dispatch_request's error classification defeated it: - HTTP 503 (sent by aw-server when the heartbeat lock times out, e.g. while the server is slow) fell into the 'Unknown error, not retrying' branch, permanently discarding the queued request. - The existing 400/500 branches were dead code: Response.__bool__ returns Response.ok, which is False for any error status, so 'if e.response and ...' never matched and every HTTP error was dropped - including the 500s the code claimed to retry. - A plain ConnectionError mid-dispatch (server died after connect) also hit the drop branch, and since 'connected' stayed True the dispatch loop kept popping and discarding requests one by one - draining the entire on-disk queue during a server outage. Now transient errors (ConnectionError/Timeout, HTTP 429/500/502/503/504) leave the request at the head of the persistqueue for a later retry, exactly like ConnectTimeout already did; only permanent client errors (e.g. HTTP 400 bad payload) are dropped so they can't wedge the queue. Connection errors also mark the queue disconnected so the run loop goes back to reconnecting. Retrying is safe for heartbeats: a duplicate of an already-processed heartbeat merges into the last event as a no-op, and FIFO head-retry preserves the ordering that merging requires (dropping requests from the middle of the stream broke merge chains, fragmenting events). Git-Session-Id: e99c
Use the Retry-After header (delta-seconds form, floored at 0.5s and capped at 60s) for the retry delay on 429/503-style responses, and use the stop-aware wait() instead of sleep() so a long delay can't block shutdown. Git-Session-Id: e99c
…quests - tests: back each new dispatch test with its own tmp_path persistqueue instead of draining the shared on-disk queue - client: use logger.exception for non-retryable request errors so the failure reason is logged, not just the payload Addresses Greptile review on ActivityWatch#130. Git-Session-Id: 4948e2f0-e57b-5a82-899b-c1b20628051d
82a7736 to
65148ab
Compare
|
Rebased onto current master after #124 and #125 landed. Conflict was only in Local: |
…vityWatch#125/ActivityWatch#130 - tests: pass tmp_path to _fresh_queue (ActivityWatch#130 made queues isolated per test) - RequestQueue.__init__: keep _queue_write_failing (ActivityWatch#124) after the batch state - wait_for_queue_empty (ActivityWatch#125): track the in-flight batch instead of _current - drop an import left unused by the rebase Git-Session-Id: 68f2e145-fcff-459d-8e16-c3e43269f5fa
…ityWatch#125/ActivityWatch#130 - send an empty heartbeat payload instead of dropping it: the post guard should only skip an insert reconciled down to nothing (caught by ActivityWatch#125's test_wait_for_queue_empty_timeout) - tests: pass tmp_path to _fresh_queue (ActivityWatch#130 made queues isolated per test) - tests: silence mypy method-assign on the refuse-then-restore stubs (CI runs make typecheck) Git-Session-Id: 68f2e145-fcff-459d-8e16-c3e43269f5fa
This is a carry of #115 (Erik's work) rebased onto current master, resolving the single conflict where #118 added
persistqueue_pathtoRequestQueue.__init__at the same site where #115 addsRETRY_STATUS_CODES. Resolution keeps both:All commits are authored by @ErikBjare. Erik: feel free to close this in favour of rebasing #115 yourself — this carry exists only to unblock #127 and #128 which stack on this base.
Why carry instead of waiting on #115: The conflict has been unresolved for 4 days since I noted it on #115 (#115 comment 2026-10-02). A fork carry lets #127/#128 land without further delay while keeping the option to supersede this PR with a direct rebase.
Closes #115 (if merged; Erik may prefer to merge #115 directly instead).
Stack:
Fixes #32 (partially — #127 handles the merge half; #128 handles batch insert).
Problem
On master,
RequestQueuepermanently drops any queued heartbeat that gets an HTTP error, including 500 and 503. The retry-on-500 branch can never run:if e.response and ...is always false, becauserequests.Response.__bool__returnsFalsefor every non-2xx status. AConnectionErrorraised mid-dispatch (server died after connect) is also dropped, since onlyConnectTimeoutwas caught. aw-server sends 503 when the heartbeat lock times out, so a briefly busy server silently loses watcher data.Reproduce
Run the new tests against master's
client.py:Six of the seven failures are behavioural: each retryable status (429/500/502/503/504) and a connection error leave the queue empty. The seventh is an
ImportErrorfor the new_retry_delay.What the tests cover
ConnectionError: the request stays queued and the queue goes back to reconnecting.Retry-After: delta-seconds are honored, capped at 60s and floored at 0.5s; the HTTP-date form falls back to the default.