Skip to content

Commit

Permalink
Merge pull request #13 from kpanic/introduce-ets
Browse files Browse the repository at this point in the history
Introduce :ets in the worker
  • Loading branch information
kpanic authored Apr 14, 2019
2 parents 5fdcf58 + d28e073 commit 714df9f
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 17 deletions.
38 changes: 26 additions & 12 deletions lib/forecastr/cache/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ defmodule Forecastr.Cache.Worker do

# Client API
@spec start_link(Keyword.t()) :: {:ok, pid()}
def start_link(opts) do
GenServer.start_link(__MODULE__, %{}, opts)
def start_link([name: worker_name] = opts) do
GenServer.start_link(__MODULE__, %{name: worker_name, timer_ref: nil}, opts)
end

@spec get(atom(), String.t()) :: map() | nil
Expand All @@ -21,27 +21,41 @@ defmodule Forecastr.Cache.Worker do
end

# Server callbacks
def init(state) do
def init(%{name: worker_name} = state) do
^worker_name = :ets.new(worker_name, [:named_table])
{:ok, state}
end

def handle_call({:get, query}, _from, state) do
entry = Map.get(state, query)
def handle_call({:get, query}, _from, %{name: worker_name} = state) do
entry =
case :ets.lookup(worker_name, query) do
[] -> nil
[{_key, value}] -> value
end

{:reply, entry, state}
end

def handle_call({:set, query, response, options}, _from, state) do
state = Map.put(state, query, response)
purge_cache(query, options)
{:reply, :ok, state}
def handle_call(
{:set, query, response, options},
_from,
%{name: worker_name, timer_ref: timer_ref} = state
) do
true = :ets.insert(worker_name, {query, response})
timer_ref = schedule_purge_cache(query, timer_ref, options)
{:reply, :ok, %{state | timer_ref: timer_ref}}
end

def purge_cache(query, ttl: minutes) do
# Purge every N minutes
def schedule_purge_cache(query, nil = _timer_ref, ttl: minutes),
do: Process.send_after(self(), {:purge_cache, query}, minutes)

def schedule_purge_cache(query, timer_ref, ttl: minutes) do
Process.cancel_timer(timer_ref)
Process.send_after(self(), {:purge_cache, query}, minutes)
end

def handle_info({:purge_cache, query}, state) do
{:noreply, Map.delete(state, query)}
true = :ets.delete_object(Keyword.get(state, :name), query)
{:noreply, state}
end
end
2 changes: 1 addition & 1 deletion lib/forecastr/renderer/png.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Forecastr.Renderer.PNG do
Render a map coming from the backend (OWM API currently)
"""
@spec render(map()) :: {:ok, map()}
def render(map = %{"name" => city_name}) do
def render(%{"name" => city_name} = map) do
map
|> Forecastr.Renderer.ASCII.render(:png)
|> render_png(city_name)
Expand Down
9 changes: 5 additions & 4 deletions test/forecastr_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,18 @@ defmodule ForecastrTest do
test "Forecastr.forecast cache correctly :today" do
Application.put_env(:forecastr, :backend, OWMBackendToday)
assert {:ok, _response} = Forecastr.forecast(:today, "Wonderland")
state = :sys.get_state(Forecastr.Cache.Today)

assert %{"wonderland" => today_weather()} == state
[state] = :ets.tab2list(Forecastr.Cache.Today)

assert {"wonderland", today_weather()} == state
end

test "Forecastr.forecast cache correctly :in_five_days" do
Application.put_env(:forecastr, :backend, OWMBackendFiveDays)
assert {:ok, _response} = Forecastr.forecast(:in_five_days, "Wonderland")
state = :sys.get_state(Forecastr.Cache.InFiveDays)
[state] = :ets.tab2list(Forecastr.Cache.InFiveDays)

assert %{"wonderland" => five_days_weather()} == state
assert {"wonderland", five_days_weather()} == state
end

test "Forecastr.forecast hits the cache when it's pre-warmed for today" do
Expand Down

0 comments on commit 714df9f

Please sign in to comment.