Ankusa.RetryPolicy behaviour (ankusa v0.7.0)

Copy Markdown View Source

Backoff and give-up rules for dispatch.

A policy decides how long to wait (backoff/2) and may give up early (:give_up). The wall-clock horizon is not the policy's to enforce: dispatch gives every delivery max_elapsed_ms from its first attempt (the :max_elapsed_ms policy option, default 24 hours; a source's retry: overrides it) and dead-letters a delivery that is still failing once that has passed, whatever backoff/2 answers. schedule/5 is that rule as a pure function: no retry it returns is due later than the horizon.

Summary

Callbacks

Given the attempt number (1-based, the attempt that just failed), return {:retry, delay_ms} to retry after a delay, or :give_up to dead-letter.

Functions

When a delivery whose first attempt was at first_attempt_at runs out of time.

The default horizon: 24 hours from a delivery's first attempt.

The policy a source runs under: the instance's {module, opts} with the source's retry: options laid over opts.

The horizon, in milliseconds, of a policy's opts.

Validate a per-source retry: (a keyword list or a map with atom keys) and return it as a keyword list. nil is no override. Raises ArgumentError naming label for an unknown key or a value out of range.

What to do with a delivery that just failed, as the absolute time of its next attempt (now is the clock in milliseconds): {:retry, at} or :give_up.

The range check of one retry option, shared by normalize_opts!/2 and Ankusa.Config. label is the dotted path of the retry block. Raises ArgumentError.

Callbacks

backoff(attempt, opts)

@callback backoff(attempt :: pos_integer(), opts :: keyword()) ::
  {:retry, non_neg_integer()} | :give_up

Given the attempt number (1-based, the attempt that just failed), return {:retry, delay_ms} to retry after a delay, or :give_up to dead-letter.

Functions

deadline(first_attempt_at, arg)

@spec deadline(integer(), {module(), keyword()}) :: integer()

When a delivery whose first attempt was at first_attempt_at runs out of time.

default_max_elapsed_ms()

@spec default_max_elapsed_ms() :: pos_integer()

The default horizon: 24 hours from a delivery's first attempt.

for_source(arg, override)

@spec for_source({module(), keyword()}, keyword() | nil) :: {module(), keyword()}

The policy a source runs under: the instance's {module, opts} with the source's retry: options laid over opts.

max_elapsed_ms(opts)

@spec max_elapsed_ms(keyword()) :: pos_integer()

The horizon, in milliseconds, of a policy's opts.

normalize_opts!(opts, label)

@spec normalize_opts!(nil | keyword() | map(), String.t()) :: keyword() | nil

Validate a per-source retry: (a keyword list or a map with atom keys) and return it as a keyword list. nil is no override. Raises ArgumentError naming label for an unknown key or a value out of range.

schedule(policy, class, row, now, max_retry_after_ms)

@spec schedule(
  {module(), keyword()},
  Ankusa.Sink.class(),
  %{
    :attempts => pos_integer(),
    :first_attempt_at => integer(),
    optional(atom()) => term()
  },
  integer(),
  pos_integer()
) :: {:retry, integer()} | :give_up

What to do with a delivery that just failed, as the absolute time of its next attempt (now is the clock in milliseconds): {:retry, at} or :give_up.

class is Ankusa.Sink.classify/1's answer. A {:permanent, _} failure, a delivery that is past its horizon (first_attempt_at + max_elapsed_ms) and a policy that answers :give_up are all :give_up. A {:retry_after, ms, _} failure waits the later of the policy's backoff and ms, with ms capped at max_retry_after_ms. at is never later than the horizon, so the last attempt runs on it and the one after that is the dead letter.

validate_opt!(arg1, value, label)

@spec validate_opt!(atom(), term(), String.t()) :: :ok

The range check of one retry option, shared by normalize_opts!/2 and Ankusa.Config. label is the dotted path of the retry block. Raises ArgumentError.