defmodule Rivet.Utils.Interval2 do @moduledoc """ Make sure only one of a method callback is ever running. Interval and last run time are stored in state. You can adjust interval on the fly, but it'll kick in on second iteration after this one because the next one is already queued. Contributor: Brandon Gillespie Usage: ``` use Interval2, tick: 1_000 # or use Interval2, ... options ``` In process startup: ``` interval_start(state) ``` Then later: ``` def interval(state) do # do a thing {:ok, state} end ``` Supported options: tick: integer # REQUIRED error_logger: {M, f} # default: {Logger, :error} # two-arg arity callback: :interval # function name to call """ require Logger defmacro __using__(opts) do quote location: :keep, bind_quoted: [opts: opts] do # required @tick Keyword.get(opts, :tick) @error_logger Keyword.get(opts, :error_logger, {Logger, :error}) @method Keyword.get(opts, :callback, :interval) defp interval_error(s, msg, opts \\ []) do {log_m, log_f} = @error_logger apply(log_m, log_f, ["Interval: " <> msg, opts]) {:noreply, s} end def handle_info(:interval, state) do start = System.system_time(:millisecond) with {:noreply, state} <- interval_run(state), do: interval_queue(state, start) end defp interval_run(state) do case apply(__MODULE__, @method, [state]) do {:ok, state} -> {:noreply, state} other -> interval_error(state, "Unexpected result from interval call", result: other) end rescue err -> interval_error(state, "Unexpected exception during interval call", traceback: Exception.format(:error, err, __STACKTRACE__) ) end def interval_queue(state, start) do Process.send_after( self(), :interval, calc_interval(@tick, @tick - System.system_time(:millisecond) - start) ) end def interval_start(state), do: interval_queue(state, 0) defp calc_interval(tick, diff) when diff <= 1 do interval_error(nil, "Process has exceeded interval, skipping to next interval") {:ok, tick} end defp calc_interval(tick, diff) when diff < tick / 2 do interval_error(nil, "Process is running slow") {:ok, diff} end defp calc_interval(_, diff), do: diff end end end