analytics/lib/plausible/cache/warmer.ex

84 lines
2.3 KiB
Elixir

defmodule Plausible.Cache.Warmer do
@moduledoc """
A periodic cache warmer.
Child specification options available:
* `cache_impl` - module expected to implement `Plausible.Cache` behaviour
* `interval` - the number of milliseconds for each warm-up cycle
* `cache_name` - defaults to cache_impl.name() but can be overridden for testing
* `force_start?` - enforcess process startup for testing, even if it's barred
by `Plausible.Cache.enabled?`. This is useful for avoiding issues with DB ownership
and async tests.
* `warmer_fn` - by convention, either `:refresh_all` or `:refresh_updated_recently`,
both are automatically provided by `cache_impl` module. Technically any exported
or captured function will work, if need be.
See tests for more comprehensive examples.
"""
@behaviour :gen_cycle
require Logger
@spec child_spec(Keyword.t()) :: Supervisor.child_spec() | :ignore
def child_spec(opts) do
child_name = Keyword.get(opts, :child_name, __MODULE__)
%{
id: child_name,
start: {:gen_cycle, :start_link, [{:local, child_name}, __MODULE__, opts]}
}
end
@impl true
def init_cycle(opts) do
cache_impl = Keyword.fetch!(opts, :cache_impl)
cache_name = Keyword.get(opts, :cache_name, cache_impl.name())
interval = Keyword.fetch!(opts, :interval)
warmer_fn =
case Keyword.fetch!(opts, :warmer_fn) do
f when is_function(f, 1) ->
f
f when is_atom(f) ->
true = function_exported?(cache_impl, f, 1)
Function.capture(cache_impl, f, 1)
end
force_start? = Keyword.get(opts, :force_start?, false)
if Plausible.Cache.enabled?() or force_start? do
Logger.notice(
"#{__MODULE__} initializing #{inspect(warmer_fn)} #{cache_name} with interval #{interval}..."
)
{:ok,
{interval,
opts
|> Keyword.put(:cache_name, cache_name)
|> Keyword.put(:warmer_fn, warmer_fn)}}
else
:ignore
end
end
@impl true
def handle_cycle(opts) do
cache_name = Keyword.fetch!(opts, :cache_name)
warmer_fn = Keyword.fetch!(opts, :warmer_fn)
Logger.notice("#{__MODULE__} running #{inspect(warmer_fn)} on #{cache_name}...")
warmer_fn.(opts)
{:continue_hibernated, opts}
end
@impl true
def handle_info(_msg, state) do
{:continue, state}
end
end