fix(fetch): Undo the claim when submitting to the push pool fails - #794
enochtangg wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4d91332. Configure here.
`send_async` moves the activation onto a flume waiter that a push thread can take at any moment. Dropping that future on timeout neither returns the activation nor reports whether it was delivered, so a submit timeout could not tell a full queue from a late delivery. The fetch thread then undid a claim for an activation a worker had already started, and the row became claimable a second time. `try_send` keeps the activation on this thread and hands it back in `TrySendError::Full`, so every non-Ok exit proves the push pool never saw it. The cost is polling every millisecond while the queue is full instead of parking on the channel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2ed9f71 to
012ad05
Compare
| self.store.undo_claim(&id, "fetch.undo_claim").await; | ||
|
|
||
| // We cannot recover from a closed channel | ||
| return false; |
There was a problem hiding this comment.
if we abort early here, then the remaining ids in the batch are still stuck in Claimed, right?
also isn't this fundamentally racy? if the claim expires before we get to this line, we might undo somebody else's claim.
There was a problem hiding this comment.
just tested it and fable was able to find the same thing (and more things i didn't verify)
There was a problem hiding this comment.
Good catches, tried to fix with the following:
- When a submit fails, the fetch thread now releases the current activation and the rest of the batch in one query.
- Releases also check
claim_expires_atnow. A release happens if the row still has the expiry we claimed it with, so it can't undo someone else's newer claim. I tested this end to end locally.
A release matched on id and status alone, so one that arrived after its lease expired could undo a later claim on the same row. The row went back to pending while the new claimant was delivering it, and the task could run twice. Releases now also match on the claim_expires_at the activation was fetched with. Every claim writes a fresh expiry, so a stale release no longer matches. On a submit failure the fetch thread released only the current activation. A closed queue returned early and stranded the rest of the batch until lease expiry, and a timeout made each remaining activation wait out its own timeout. The first failure now releases the current activation and the rest of the batch in one query. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

** Description
When the fetch thread claims an activation but cannot hand it to the push pool, the row is left in the Claimed state with nothing holding it. The claim query only selects Pending rows, so nothing re-picks it and it sits until handle_claim_expiration reverts it.
This inflates the SLO metric. pending_activation.max_lag.sec counts Claimed rows, so one stranded activation drives it up until the lease expires. This symptom can be observed consistently in process-segments-push in s4s2: DD link. During this time, throughput, occupancy and AlloyDB latency were all normal, so the metric was tracking a single stuck row rather than real pipeline latency.
When workers briefly go unavailable (due to a deployment), failed pushes each revert their own claim, which is correct, but the retried work fills the push queue to its cap. Activations that then time out submitting to the push queue are not reverted. The fetch thread only logs them.
**Fix
Release the claim when a submit to the push pool fails, for both Timeout and Closed.
This is safe because only activations that never left the broker are released, so no worker can be running them. A submit failure now costs roughly the fetch backoff instead of the full claim lease. The push thread already reverted inline after a failed gRPC push. That logic moved to ActivationStore::undo_claims, so the push thread gets the same fenced release.