What happened?
apalis-postgres 1.0.0-rc.8 with apalis 1.0.0-rc.9.
In src/ack.rs the abort branch is commented out:
Err(e) => match &e {
// Error::Abort(_) => State::Killed,
_ if parts.ctx.max_attempts() as usize <= parts.attempt.current() => Status::Killed,
_ => Status::Failed,
},
Every Err becomes Status::Failed and the task is refetched until max_attempts (5 by default). There is no way to tell this backend a task must not be retried.
The two retry layers also disagree. apalis-core's BackoffRetryPolicy::retry does honour it:
Err(err) if (err as &dyn Any).downcast_ref::<AbortError>().is_some() => None,
The in-process tower retry stops correctly, then the storage layer refetches the task anyway. RetryPolicy::retry_if has the same problem: the predicate returns false, no in-process retry happens, and the job still runs 5 times.
Repro: a task fn that always returns Err(AbortError::new(...)) against PostgresStorage. Observe 5 executions and attempts = 5.
This makes non-idempotent tasks unsafe — anything with an external side effect gets repeated 5 times even when the failure is known to be permanent.
Expected behavior
Returning AbortError from a task fn settles the task as Killed after one attempt, with no refetch.
Steps to reproduce
- Build a worker on PostgresStorage with a task fn that always returns
Err(AbortError::new("permanent")).
- Push one task and run the worker.
- The task executes 5 times instead of once. The row ends at attempts = 5,
and every intermediate ack wrote Status::Failed instead of Killed.
Same result with RetryPolicy::retries(n).retry_if(|_| false) — the predicate
returns false, so the tower layer never retries, but the storage layer refetches
the task anyway.
Minimal code example
Version
1.0.0-rc.x
Environment
- OS: Fedora Linux 44 (Workstation Edition)
- Rust version: 1.97.1
- Cargo version: 1.97.1
Relevant log output
Additional context
No response
What happened?
apalis-postgres 1.0.0-rc.8 with apalis 1.0.0-rc.9.
In src/ack.rs the abort branch is commented out:
Every Err becomes Status::Failed and the task is refetched until max_attempts (5 by default). There is no way to tell this backend a task must not be retried.
The two retry layers also disagree. apalis-core's BackoffRetryPolicy::retry does honour it:
The in-process tower retry stops correctly, then the storage layer refetches the task anyway. RetryPolicy::retry_if has the same problem: the predicate returns false, no in-process retry happens, and the job still runs 5 times.
Repro: a task fn that always returns Err(AbortError::new(...)) against PostgresStorage. Observe 5 executions and attempts = 5.
This makes non-idempotent tasks unsafe — anything with an external side effect gets repeated 5 times even when the failure is known to be permanent.
Expected behavior
Returning AbortError from a task fn settles the task as Killed after one attempt, with no refetch.
Steps to reproduce
Err(AbortError::new("permanent")).
and every intermediate ack wrote Status::Failed instead of Killed.
Same result with RetryPolicy::retries(n).retry_if(|_| false) — the predicate
returns false, so the tower layer never retries, but the storage layer refetches
the task anyway.
Minimal code example
Version
1.0.0-rc.x
Environment
Relevant log output
Additional context
No response