Holds the armed deadlines for AshWorkflow.Scheduler.Precise.
One timer per pending deadline, keyed by resource, work name and primary key. A record entering a step replaces every timer it already had, and a record reaching a terminal step drops them.
Start it with the resources it sweeps
{AshWorkflow.Scheduler.Precise.Timeline, resources: [MyApp.Candidate]}The DSL selects a scheduler per resource, but this process is started once by the application, so it cannot infer which resources to recover deadlines for. Listing them is what makes the sweep possible. A resource whose workflow selects a different scheduler is ignored, so listing one is harmless.
Firing
Every fire re-reads the record and re-applies the work's match expression
before running the action. That check is what makes a duplicate timer safe,
and it is the same guarantee ash_oban gets by re-checking a trigger's where
before a job runs. A record that has left the step, or had its deadline
moved, fires nothing.
Retry
A failed attempt that has not yet reached work.retry.max_attempts re-arms a
timer for the backoff delay, under the same key as the original deadline.
That is what lets a record leaving the step drop a pending retry through the
same drop_timers_for/2 and cancel/2 paths as any other timer. on_error
only runs once the last attempt has failed, exactly as
AshWorkflow.Scheduler.execute/3 decides.
Summary
Functions
Drop every timer this record has.
Returns a specification to start this module under a supervisor.
Replace every timer this record has with the deadlines of its current state.
Run every unit of work that is due now for a resource, in the calling process.
Start the timeline.
Functions
Drop every timer this record has.
Returns a specification to start this module under a supervisor.
See Supervisor.
Replace every timer this record has with the deadlines of its current state.
Called by AshWorkflow.Scheduler.notify_state_change/1 after a state change.
Replacing rather than adding is what stops a record that moved between steps
from keeping the previous step's deadlines armed.
@spec run_due( Ash.Resource.t(), keyword() ) :: non_neg_integer()
Run every unit of work that is due now for a resource, in the calling process.
This is the synchronous counterpart to the timers, and it exists for tests.
A timer fires from this process, which under Ecto.Adapters.SQL.Sandbox
cannot see the test's transaction, so a test that waited for a timer would
wait for work that could not read its own data. Calling this instead runs the
same work through AshWorkflow.Scheduler.execute/3 on the test's own
connection.
Work.match is what "due now" means: for a timeout it is true exactly when
the deadline has passed, and for an automatic step it is true as soon as a
record occupies it. So this needs no horizon and no timer.
Returns the number of records it ran work for. An automatic step that
transitions into another automatic step needs another call, the same way
AshOban.schedule_and_run_triggers/1 needs another pass, so loop until it
returns 0.
run_due(MyApp.Candidate)
#=> 2
Start the timeline.
Options
:resources— the workflow resources to sweep for pending deadlines.:name— the process name. Defaults to this module.:leader,:look_ahead_ms,:horizon_ms,:actor,:authorize?,:tenant— seeAshWorkflow.Scheduler.Precise.