Goal
Make notification.deliver idempotent so a retry does not duplicate pushes.
Current behaviour
deliver is registered with retry=3 and re-reads its batch by id with no status filter. Any retry — worker restart, connection blip, a failure between the transport call and the status update — re-sends every delivery in the batch, including ones already marked sent.
Reproduced by invoking deliver on a delivery already at status=sent: the transport was called again with the original body.
Note that procrastinate's retry=3 resolves to RetryStrategy(max_attempts=3) with wait, linear_wait and exponential_wait all defaulting to 0, so a retry is rescheduled immediately rather than backing off. The duplicate therefore arrives seconds later, not hours later.
Expected behaviour
A delivery already marked sent is skipped on retry, and a delivery old enough to be misleading is dropped rather than sent.
Where
service/notification/tasks.py — deliver.
Approach
Filter on status=PENDING when loading the batch, and skip deliveries older than a sanity window. PUSH_TTL in service/notification/utils.py caps how long FCM buffers a message for an offline device, but it is computed at send time, so it does not bound the enqueue-to-send lag.
Goal
Make
notification.deliveridempotent so a retry does not duplicate pushes.Current behaviour
deliveris registered withretry=3and re-reads its batch by id with nostatusfilter. Any retry — worker restart, connection blip, a failure between the transport call and the status update — re-sends every delivery in the batch, including ones already markedsent.Reproduced by invoking
deliveron a delivery already atstatus=sent: the transport was called again with the original body.Note that procrastinate's
retry=3resolves toRetryStrategy(max_attempts=3)withwait,linear_waitandexponential_waitall defaulting to0, so a retry is rescheduled immediately rather than backing off. The duplicate therefore arrives seconds later, not hours later.Expected behaviour
A delivery already marked
sentis skipped on retry, and a delivery old enough to be misleading is dropped rather than sent.Where
service/notification/tasks.py—deliver.Approach
Filter on
status=PENDINGwhen loading the batch, and skip deliveries older than a sanity window.PUSH_TTLinservice/notification/utils.pycaps how long FCM buffers a message for an offline device, but it is computed at send time, so it does not bound the enqueue-to-send lag.