From 805b24208115cfbe72cbb636f667debc9e6283d2 Mon Sep 17 00:00:00 2001 From: Marco Milanesi Date: Sun, 14 Apr 2019 14:57:04 +0200 Subject: [PATCH 1/2] Introduce :ets in the worker --- lib/forecastr/cache/worker.ex | 38 ++++++++++++++++++++++++----------- test/forecastr_test.exs | 9 +++++---- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/forecastr/cache/worker.ex b/lib/forecastr/cache/worker.ex index aed5e1d..a7f3d7b 100644 --- a/lib/forecastr/cache/worker.ex +++ b/lib/forecastr/cache/worker.ex @@ -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 @@ -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 diff --git a/test/forecastr_test.exs b/test/forecastr_test.exs index a49dfc8..e5ca18c 100644 --- a/test/forecastr_test.exs +++ b/test/forecastr_test.exs @@ -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 From d28e07320d845834929c48d725fa2a1fcd886c7b Mon Sep 17 00:00:00 2001 From: Marco Milanesi Date: Sun, 14 Apr 2019 15:03:39 +0200 Subject: [PATCH 2/2] Address credo issue --- lib/forecastr/renderer/png.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/forecastr/renderer/png.ex b/lib/forecastr/renderer/png.ex index f1a4c08..d8179cd 100644 --- a/lib/forecastr/renderer/png.ex +++ b/lib/forecastr/renderer/png.ex @@ -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)