Current section
Files
Jump to
Current section
Files
lib/interval/index.ex
defmodule Rivet.Utils.Interval 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
"""
import Rivet.Utils.Time, only: [epoch_time: 1]
require Logger
@margin 2
defmacro __using__(_) do
quote location: :keep do
def handle_info({:intervals, method}, state) do
state =
case get_in(state, [:intervals, method]) do
{_, interval} ->
Rivet.Utils.Interval.queue(state, method, interval)
nil ->
state
end
with {:ok, state} <- apply(__MODULE__, method, [state]) do
{:noreply, state}
else
error ->
IO.inspect(error, label: "Unexpected result from Interval call")
{:noreply, state}
end
rescue
err ->
Logger.error(Exception.format(:error, err, __STACKTRACE__))
{:noreply, state}
end
end
end
def start(state, method, interval) do
last =
case get_in(state, [:intervals, method]) do
nil -> 0
{last, _} -> last
end
now = epoch_time(:millisecond)
diff = now - last
if diff < interval * @margin do
# we've seen it within 2x the interval, so it's likely already running,
# don't start another...
state
else
queue(state, method, interval)
end
rescue
err ->
Logger.error(Exception.format(:error, err, __STACKTRACE__))
state
end
def stop(state, method) do
intervals =
Map.get(state, :intervals, %{})
|> Map.delete(method)
Map.put(state, :intervals, intervals)
end
def queue(state, method, interval) do
now = epoch_time(:millisecond)
Process.send_after(self(), {:intervals, method}, interval)
intervals =
Map.get(state, :intervals, %{})
|> Map.put(method, {now, interval})
Map.put(state, :intervals, intervals)
end
end