Created
May 12, 2017 04:30
-
-
Save raorao/ac99362e4c437a3115273f4389903835 to your computer and use it in GitHub Desktop.
Simple Process cache in Elixir.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule RequestCache do | |
@moduledoc """ | |
Simple GenServer-based cache. | |
""" | |
use GenServer | |
@type t :: %{cache: %{optional(cache_key) => cache_value}, interval: integer} | |
@typep cache_key :: any | |
@typep cache_value :: any | |
@doc """ | |
Starts a RequestCache process linked to the current process. See | |
`GenServer.start_link/2` for details. | |
By default, the cache clears every 6 hours. | |
""" | |
@spec start_link(integer) :: GenServer.on_start | |
def start_link(interval_seconds \\ 21_600) do | |
GenServer.start_link(__MODULE__, interval_seconds, name: __MODULE__) | |
end | |
@doc """ | |
Asynchronous call to cache a value at the provided key. Any key that can | |
be used with `Map.t` can be used, and will be evaluated using `==`. | |
""" | |
@spec cache(cache_key, cache_value) :: :ok | |
def cache(key, val) do | |
GenServer.cast(__MODULE__, {:cache, key, val}) | |
end | |
@doc """ | |
Asynchronous clears all values in the cache. | |
""" | |
@spec clear() :: :ok | |
def clear do | |
GenServer.cast(__MODULE__, :clear) | |
end | |
@doc """ | |
Sychronously reads the cache for the provided key. If no value is found, | |
returns :not_found . | |
""" | |
@spec read(cache_key) :: cache_value | :not_found | |
def read(key) do | |
GenServer.call(__MODULE__, {:read, key}) | |
end | |
@doc """ | |
Sychronously reads the cache for the provided key. If no value is found, | |
invokes default_fn and caches the result. Note: in order to prevent congestion | |
of the RequestCache process, default_fn is invoked in the context of the caller | |
process. | |
""" | |
@spec read_or_cache_default(cache_key, (() -> cache_value)) :: cache_value | |
def read_or_cache_default(key, default_fn) do | |
case read(key) do | |
:not_found -> | |
value = default_fn.() | |
cache key, value | |
value | |
value -> | |
value | |
end | |
end | |
# GenServer Callbacks | |
@spec init(integer) :: {:ok, t} | |
def init(interval_seconds) do | |
initial_state = %{cache: %{}, interval: interval_seconds * 1000} | |
schedule_clear initial_state | |
{:ok, initial_state} | |
end | |
@spec handle_cast({:cache, cache_key, cache_value}, t) :: {:noreply, t} | |
def handle_cast({:cache, key, val}, state = %{cache: cache}) do | |
{:noreply, %{state | cache: Map.put(cache, key, val)}} | |
end | |
@spec handle_cast(:clear, t) :: {:noreply, t} | |
def handle_cast(:clear, state) do | |
{:noreply, %{state | cache: %{}}} | |
end | |
@spec handle_info(:clear_and_schedule, t) :: {:noreply, t} | |
def handle_info(:clear_and_schedule, state) do | |
schedule_clear state | |
{:noreply, %{state | cache: %{}}} | |
end | |
@spec handle_call({:read, cache_key}, GenServer.from, t) :: {:reply, cache_value | :not_found, t} | |
def handle_call({:read, key}, _from, state = %{cache: cache}) do | |
{:reply, Map.get(cache, key, :not_found), state} | |
end | |
defp schedule_clear(%{interval: interval}) do | |
Process.send_after self(), :clear_and_schedule, interval | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
cool