Bug
When a Cron or Perpetual task uses ExponentialRetry with a finite attempts count and the task fails persistently, the retries exhaust as expected — but then the task is rescheduled for a fresh cycle with attempt reset to 1. The entire retry sequence repeats every cycle, forever. There is no built-in way to say "retries are exhausted, stop scheduling this task."
A secondary issue makes subclassing ExponentialRetry to work around this harder than it should be: ExponentialRetry.__aenter__ constructs ExponentialRetry(...) rather than type(self)(...), so subclass method overrides are silently dropped at runtime unless __aenter__ is also overridden.
Repro
import asyncio
from datetime import timedelta
from docket import Docket
from docket.dependencies import ExponentialRetry, Perpetual
from docket.worker import Worker
fail_count = 0
async def main() -> None:
global fail_count
async with Docket(name="repro", url="memory://") as docket:
async def always_failing(
_perpetual: Perpetual = Perpetual(every=timedelta(0)),
_retry: ExponentialRetry = ExponentialRetry(
attempts=3,
minimum_delay=timedelta(0),
maximum_delay=timedelta(0),
),
) -> None:
global fail_count
fail_count += 1
raise ValueError(f"attempt {fail_count}")
docket.register(always_failing, names=["always-failing"])
await docket.add(always_failing)()
async with Worker(docket, schedule_automatic_tasks=False) as worker:
try:
await asyncio.wait_for(worker.run_until_finished(), timeout=2)
except asyncio.TimeoutError:
pass
# Expected: 3 (one per attempt)
# Actual: hundreds
print(f"Total executions: {fail_count}")
assert fail_count == 3, f"Expected 3, got {fail_count}"
asyncio.run(main())
Environment
- pydocket 0.21.1 (behavior unchanged on 0.22.0)
- Python 3.11
Happy to contribute a fix here, if we all agree that this is not the intended behavior.
Bug
When a
CronorPerpetualtask usesExponentialRetrywith a finiteattemptscount and the task fails persistently, the retries exhaust as expected — but then the task is rescheduled for a fresh cycle withattemptreset to 1. The entire retry sequence repeats every cycle, forever. There is no built-in way to say "retries are exhausted, stop scheduling this task."A secondary issue makes subclassing
ExponentialRetryto work around this harder than it should be:ExponentialRetry.__aenter__constructsExponentialRetry(...)rather thantype(self)(...), so subclass method overrides are silently dropped at runtime unless__aenter__is also overridden.Repro
Environment
Happy to contribute a fix here, if we all agree that this is not the intended behavior.