Current section

Files

Jump to
rivet_utils lib interval index2.ex
Raw

lib/interval/index2.ex

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