Pending timers as a StatifierPersistence.PinSource, per ADR-0008.
Before statifier_persistence retires a chart it asks every pin source
the host names whether anything still pins it. A pending timer is state
that package cannot see: a row in the host's Oban table. This module
answers for it, so a host running both packages does not write the
adapter.
This module exists only when statifier_persistence is in the host's
dependencies: statifier_oban depends on it optionally.
Adopting it
defmodule MyApp.TimerPins do
use StatifierOban.Timer.PinSource, config: {MyApp, :statifier_oban_config}
end:config names a zero-arity function in the host that returns the
host's %StatifierOban.Config{}. The use declares the behaviour in the
host's module and defines its pins/2, which calls that function on
every call - a config built at runtime is read when the count is taken -
and hands the context's :execution_ids to
StatifierOban.Timer.pending_for/2. The answer is one count,
%{timers: n}, n being the timers still pending under those ids; no
ids, or ids with nothing pending, answer %{timers: 0}.
The host passes its own module, MyApp.TimerPins above, wherever the
persistence package takes pin sources.
Take a library hold: the hold's chart waits for copy.collected and
schedules a pickup.expired timer when the copy becomes available.
While that timer is pending, a retirement of the hold's chart is refused
with %{timers: 1} under MyApp.TimerPins; once it fires or is
cancelled the count is zero and the timer no longer holds the chart.
Only for a host that schedules under durable execution ids
The execution ids a pin source is handed go straight to
pending_for/2 as scopes. That is right for a process-less host, whose
timers were scheduled under its own durable execution ids. A host that
schedules under a live session's id (StatifierOban.Timer.Key) is
scoping by session, the ids it is handed match no stored scope, and
this module would answer %{timers: 0} for timers that exist, a zero
every caller reads as nothing pending. Nothing here can see which
choice a host made, so it cannot refuse on the mismatch. Such a host
does not adopt this module; it writes its own source that maps its
executions to the sessions its timers were scheduled under.
The content hash is unused
A timer job carries the scope and the send, never a chart identity, so this source cannot answer by hash and does not try. The hash still reaches it because every source is handed the same arguments.
A source that cannot count raises
pending_for/2 raises when the Oban instance is not running or its
repo cannot answer, and the injected pins/2 rescues nothing.
StatifierPersistence.PinSource.collect/3 turns the raise into a
refusal naming the host's module, so "no pending timers" and "could not
count" stay two answers.
Summary
Functions
Declares StatifierPersistence.PinSource in the calling module and
defines its pins/2 over the host's config.
Functions
Declares StatifierPersistence.PinSource in the calling module and
defines its pins/2 over the host's config.
opts must carry config: {module, function}, naming a zero-arity
function that returns a %StatifierOban.Config{}. Anything else is an
ArgumentError at compile time.