Skip to content

Lets a timer delivery answer a snooze - #139

Merged
johnnyt merged 1 commit into
mainfrom
sob-46l-snooze-delivery-answer
Sep 29, 2026
Merged

johnnyt merged 1 commit into
mainfrom
sob-46l-snooze-delivery-answer

Conversation

@johnnyt

@johnnyt johnnyt commented Sep 29, 2026

Copy link
Copy Markdown
Member

The code half of ADR-0010 ("A timer delivery may answer a snooze, so a parked timer spends no retries"), which is on main at proposed and is not edited here; it stays proposed until this code ships in a published version.

What changes. c:StatifierOban.Timer.Delivery.deliver/2 may answer {:snooze, seconds} with seconds a positive integer (decision 1), typed as the new t:StatifierOban.Timer.Delivery.snooze/0 beside t:discard_reason/0. StatifierOban.Timer.Worker.perform/1 returns that tuple to Oban unchanged (decision 2), so the job is rescheduled at least seconds later without spending a retry. The worker's own guard (is_integer(seconds) and seconds > 0) is what keeps zero, a negative count, a non-integer and an Oban period tuple out: each matches no clause and raises, as any unrecognised answer did before (decision 6); Oban 2.23.1 itself would accept zero and period tuples, and would record a negative count as a success. No telemetry event is added (decision 5) and no bound is kept (decision 7). The existing :delivered and {:discarded, reason} answers, and the default StatifierOban.Timer.Delivery.Session, are unchanged: the change is additive.

Docs. The StatifierOban.Timer.Delivery moduledoc section "A parked execution retries; it is never discarded" now teaches the snooze and keeps the raise, with its bound, for a delivery that raises; the sentence saying an Oban snooze is not available from inside the callback is gone. The callback doc names the snooze answer. The worker moduledoc's outcome list gains the snooze. The README's "Delivering timers to a durable execution" section: the durable module's {:error, {:needs_migration, _execution}} arm answers {:snooze, 300}, and the paragraph after it says what a snooze does (no retry spent, still pending, still cancellable, the period as the host's trade, no cap here) and keeps the raise as the alternative with its bound. Nothing outside that section of the README changes.

Tests (test/statifier_oban/timer/parked_execution_test.exs, Lite engine, the locked Oban 2.23.1):

  • a timer firing into a parked execution snoozes twice: each drain reports snoozed: 1, the row is scheduled at least 59 seconds out with no error recorded, max_attempts - attempt is the same as before the first run, and the timer is still pending for its scope; after the unpark the next run delivers and the job completes;
  • a snoozed timer is still reached by a cancel of its send id;
  • a finished execution still discards under a delivery that snoozes parked ones;
  • a zero snooze, a negative snooze, a non-integer snooze and an Oban period tuple each fail the run (failure: 1, snoozed: 0), leave the row retryable on attempt 1 with max_attempts unchanged, and record a CaseClauseError.

The stand-in store (test/support/parked_store_delivery.ex) gains answer_parked_with/1; its default is still the raise, so the two existing tests in that file are untouched.

Sabotage (each on lib/statifier_oban/timer/worker.ex from a copy, restored byte-equal and recompiled before the next): removing the snooze clause took the snooze test and the cancel test red on the drain assertion (failure: 1 instead of snoozed: 1); dropping seconds > 0 took the zero and negative cases red (a snooze and a success); dropping is_integer(seconds) took the non-integer and period-tuple cases red (a success and a snooze); answering {:snooze, 60} in place of the discard took the finished-execution test red (snoozed: 1 instead of cancelled: 1). One line above each test records it.

Changelog. changelog.d/sob-46l.md under Added, for the next minor, as changelog.d/README.md asks for a public API addition.

Provenance. Two edits the file map did not name, forced by the README change: the header comment and the delivery module's comment in test/statifier_oban/timer/parked_persisted_execution_test.exs said that test's module is the README's durable module and that it raises; they now say it keeps the raise where the README snoozes. No test in that file changes.

Gate. Full mix quality green on the rebased head (313 tests, 0 failures; format, compile with warnings as errors, credo, docs, doc links, dependencies and dialyzer all passing).

Refs: sob-46l

StatifierOban.Timer.Delivery.deliver/2 may now answer
{:snooze, seconds} with a positive integer (ADR-0010, proposed). The
timer worker hands it to Oban unchanged, so the job is rescheduled
without spending a retry and stays a pending, cancellable timer. Zero,
a negative count, a non-integer and an Oban period tuple match no
clause and raise, as any unrecognised answer did before. No telemetry
event is added and no bound is kept here.

The behaviour gains t:snooze/0; its moduledoc, the worker's outcome
list and the README's durable-delivery section teach the snooze for a
parked execution and keep the raise for environment failures. Tests
drive a snoozing stand-in store on the locked Oban 2.23.1: two snoozes
leave max_attempts minus attempt unchanged, the run after the unpark
delivers, a cancel reaches the snoozed row, a finished execution still
discards, and the four refused shapes raise.

Refs: sob-46l
@johnnyt
johnnyt merged commit 05d461f into main Sep 29, 2026
1 check passed
@johnnyt
johnnyt deleted the sob-46l-snooze-delivery-answer branch September 29, 2026 04:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant