Packages
pow
0.1.0-alpha.8
1.0.39
1.0.38
1.0.37
1.0.36
1.0.35
1.0.34
1.0.33
1.0.32
1.0.31
1.0.30
1.0.29
1.0.28
1.0.27
1.0.26
1.0.25
1.0.24
1.0.23
1.0.22
1.0.21
1.0.20
1.0.19
1.0.18
1.0.17
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.1.0-rc.1
0.1.0-alpha.8
0.1.0-alpha.7
retired
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
0.1.0-alpha
Robust user authentication solution
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/pow/store/backend/ets_cache.ex
defmodule Pow.Store.Backend.EtsCache do
@moduledoc """
GenServer based key value ETS cache store with auto expiration.
This module is not recommended for production, but mostly used as an example
for how to build a cache. All data is stored in-memory, so cached values are
not shared between machines.
## Configuration options
* `:ttl` - integer value in milliseconds for ttl of records. If this value
is not provided, or is set to nil, the records will never expire.
* `:namespace` - string value to use for namespacing keys. Defaults to
"cache".
"""
@behaviour Pow.Store.Base
use GenServer
alias Pow.Config
@ets_cache_tab __MODULE__
@spec start_link(Config.t()) :: GenServer.on_start()
def start_link(config) do
GenServer.start_link(__MODULE__, config, name: __MODULE__)
end
@spec put(Config.t(), binary(), any()) :: :ok
def put(config, key, value) do
GenServer.cast(__MODULE__, {:cache, config, key, value})
end
@spec delete(Config.t(), binary()) :: :ok
def delete(config, key) do
GenServer.cast(__MODULE__, {:delete, config, key})
end
@spec get(Config.t(), binary()) :: any() | :not_found
def get(config, key) do
table_get(config, key)
end
# Callbacks
@spec init(Config.t()) :: {:ok, map()}
def init(_config) do
table_init()
{:ok, %{invalidators: %{}}}
end
@spec handle_cast({:cache, Config.t(), binary(), any()}, map()) :: {:noreply, map()}
def handle_cast({:cache, config, key, value}, %{invalidators: invalidators} = state) do
invalidators = update_invalidators(config, invalidators, key)
table_update(config, key, value)
{:noreply, %{state | invalidators: invalidators}}
end
@spec handle_cast({:delete, Config.t(), binary()}, map()) :: {:noreply, map()}
def handle_cast({:delete, config, key}, %{invalidators: invalidators} = state) do
invalidators = clear_invalidator(invalidators, key)
table_delete(config, key)
{:noreply, %{state | invalidators: invalidators}}
end
@spec handle_info({:invalidate, Config.t(), binary()}, map()) :: {:noreply, map()}
def handle_info({:invalidate, config, key}, %{invalidators: invalidators} = state) do
invalidators = clear_invalidator(invalidators, key)
table_delete(config, key)
{:noreply, %{state | invalidators: invalidators}}
end
defp update_invalidators(config, invalidators, key) do
case Config.get(config, :ttl, nil) do
nil ->
invalidators
ttl ->
invalidators = clear_invalidator(invalidators, key)
invalidator = Process.send_after(self(), {:invalidate, config, key}, ttl)
Map.put(invalidators, key, invalidator)
end
end
defp clear_invalidator(invalidators, key) do
case Map.get(invalidators, key) do
nil -> nil
invalidator -> Process.cancel_timer(invalidator)
end
Map.drop(invalidators, [key])
end
defp table_get(config, key) do
ets_key = ets_key(config, key)
@ets_cache_tab
|> :ets.lookup(ets_key)
|> case do
[{^ets_key, value} | _rest] -> value
[] -> :not_found
end
end
defp table_update(config, key, value), do: :ets.insert(@ets_cache_tab, {ets_key(config, key), value})
defp table_delete(config, key), do: :ets.delete(@ets_cache_tab, ets_key(config, key))
defp table_init do
:ets.new(@ets_cache_tab, [:set, :protected, :named_table])
end
defp ets_key(config, key) do
namespace = Config.get(config, :namespace, "cache")
"#{namespace}:#{key}"
end
end