fix(queue): don't count never-executed transient releases against max_attempts
`claim` pre-increments `attempt` before the handler runs, so a real execution failure legitimately counts toward `max_attempts`. But the two TRANSIENT release paths — gate saturation (server at capacity) and script-disabled-at- fire-time — re-queue the message WITHOUT executing it, and previously used the same `nack` that leaves the pre-increment in place. Under sustained overload a message could therefore be dead-lettered after N gate-rejections having run zero times (each rejection burning a retry). Add a non-counting `release` to both `QueueRepo` and `GroupQueueRepo` (re-queue + `attempt = GREATEST(attempt - 1, 0)`, mirroring `nack` otherwise) and route the two transient paths through a new `Dispatcher::q_release`. A real handler failure still uses `nack` and counts. No schema change. Pinned by a new `queue_release` DB test (release undoes the claim increment; nack keeps it) and the updated `disabled_queue_consumer_...` dispatcher test (now asserts a release, not a nack). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -210,6 +210,14 @@ mod tests {
|
||||
) -> Result<bool, crate::queue_repo::QueueRepoError> {
|
||||
Ok(true)
|
||||
}
|
||||
async fn release(
|
||||
&self,
|
||||
_message_id: QueueMessageId,
|
||||
_claim_token: uuid::Uuid,
|
||||
_retry_delay: chrono::Duration,
|
||||
) -> Result<bool, crate::queue_repo::QueueRepoError> {
|
||||
Ok(true)
|
||||
}
|
||||
async fn reclaim_visibility_timeouts(
|
||||
&self,
|
||||
) -> Result<u64, crate::queue_repo::QueueRepoError> {
|
||||
|
||||
Reference in New Issue
Block a user