fix(queue): retry queued requests on transient errors instead of dropping them - #115
Open
ErikBjare wants to merge 2 commits into
Open
fix(queue): retry queued requests on transient errors instead of dropping them#115ErikBjare wants to merge 2 commits into
ErikBjare wants to merge 2 commits into
Conversation
…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).
Greptile SummaryRetry queued heartbeat requests after transient transport and server failures instead of dropping them.
Confidence Score: 5/5The PR appears safe to merge. No blocking failures remain. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Dispatch queued heartbeat] --> B{Request result}
B -->|Success| C[Remove queue head]
B -->|Connection error or timeout| D[Mark disconnected]
D --> E[Keep queue head]
B -->|HTTP 429, 500, 502, 503, or 504| F[Calculate bounded retry delay]
F --> G[Stop-aware wait]
G --> E
B -->|Other request error| H[Drop queue head]
Reviews (2): Last reviewed commit: "fix(queue): honor Retry-After on transie..." | Re-trigger Greptile |
Comment on lines
549
to
552
| logger.warning( | ||
| f"Server error {status_code}, will retry: {request.endpoint}" | ||
| ) | ||
| sleep(0.5) |
Member
Author
There was a problem hiding this comment.
Fixed in b31ba5a — retry delay now honors Retry-After (delta-seconds form, floored at 0.5s, capped at 60s; HTTP-date form falls back to default), and uses the stop-aware wait() instead of sleep() so a long delay can't block shutdown.
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.
Member
Author
|
@greptileai review |
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.
Problem
Diagnosed while investigating sustained aw-server CPU load (see ActivityWatch/aw-core#147): the server was 503-rejecting heartbeats (
Heartbeat lock could not be acquired within timeout), and it turned out each rejection permanently lost data, despite the on-disk request queue existing precisely to prevent that.Three bugs in
RequestQueue._dispatch_request's error classification:Unknown error, not retryingbranch, which pops the request from the persistqueue permanently. 503 Service Unavailable is the most retry-worthy status there is.Response.__bool__returnsResponse.ok, which isFalsefor every error status — soif e.response and e.response.status_code == 400never matched, and all HTTP errors (including the 500s the code claimed to retry) were dropped.ConnectionErrormid-dispatch (server died after connect; onlyConnectTimeoutwas caught) hit the drop branch, and sinceconnectedstayedTruethe dispatch loop kept popping and discarding queued requests one by one — draining the entire on-disk queue into the void, the exact scenario the class docstring says it protects against.Fix
No new retry machinery — the persistqueue is the retry mechanism. "Retry" here means returning without
_task_done()so the request stays at the head of the queue, exactly the pathConnectTimeoutalready took. The change is purely classification:ConnectionError/Timeout(also setsconnected = Falseso the run loop reconnects), and HTTP 429/500/502/503/504 (with the existing 0.5 s backoff).e.response is not Noneso the check actually works.Retrying is safe for heartbeats (the only thing in this queue): the 503 lock-rejection happens before any server-side processing, and even a true duplicate (processed but response lost) merges into the last event as a no-op. FIFO head-retry also preserves ordering, which heartbeat merging requires — dropping requests from the middle of the stream broke merge chains and fragmented events.
Tests
test_dispatch_retries_transient_server_errors(parametrized over 429/500/502/503/504): request survives the error, is retried, and is popped once the server recovers. Also guards theResponse.__bool__pitfall.test_dispatch_drops_client_errors: HTTP 400 is dropped and doesn't block the queue.test_dispatch_keeps_queue_on_connection_error: request stays queued and the queue marks itself disconnected.