make it possible to use multiple LruCache instances

This commit is contained in:
Alain Zscheile 2022-11-24 20:01:09 +01:00
parent de9c79016a
commit 98bfe3c98b
3 changed files with 86 additions and 55 deletions

View file

@ -16,24 +16,39 @@ defmodule Floof.Application do
pubkey_mgr = Application.fetch_env!(:floof, :pubkey_mgr) pubkey_mgr = Application.fetch_env!(:floof, :pubkey_mgr)
pubkey_config = Application.fetch_env!(:floof, :pubkey_config) pubkey_config = Application.fetch_env!(:floof, :pubkey_config)
children = [ children =
[
# Starts a worker by calling: Floof.Worker.start_link(arg) # Starts a worker by calling: Floof.Worker.start_link(arg)
# {Floof.Worker, arg} # {Floof.Worker, arg}
{Task.Supervisor, name: Floof.TaskSupervisor}, {Task.Supervisor, name: Floof.TaskSupervisor},
{Floof.Distributor, Floof.Distributor.fconfig( {Floof.LruCache,
markers: markers, attrs: attrs, set_markers: set_markers, %{
pubkey_mgr: pubkey_mgr, pubkey_config: pubkey_config :name => Floof.DistributorSeen,
)}, :capacity => 4096,
:position_table => Floof.DistributorSeen.PositionTable,
:cache_table => Floof.DistributorSeen.CacheTable
}},
{Floof.Distributor,
{Floof.Distributor.fconfig(
markers: markers,
attrs: attrs,
set_markers: set_markers,
pubkey_mgr: pubkey_mgr,
pubkey_config: pubkey_config
), Floof.DistributorSeen}},
{Floof.SessionManager, %{}}, {Floof.SessionManager, %{}},
{Task, fn -> Floof.accept(port) end}, {Task, fn -> Floof.accept(port) end}
] ++ (for upstream <- upstreams do ] ++
{host, port} = case upstream do for upstream <- upstreams do
{host, port} =
case upstream do
{host, port} -> {host, port} {host, port} -> {host, port}
{host} -> {host, 2540} {host} -> {host, 2540}
end end
# this establishes connections to upstream hosts # this establishes connections to upstream hosts
{Task, fn -> Floof.connect(host, port) end} {Task, fn -> Floof.connect(host, port) end}
end) end
# See https://hexdocs.pm/elixir/Supervisor.html # See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options # for other strategies and supported options

View file

@ -5,13 +5,18 @@ defmodule Floof.LruCache do
### Client API ### Client API
def start_link(opts) do
opts2 = Map.take(opts, [:capacity, :position_table, :cache_table])
GenServer.start_link(__MODULE__, opts2, name: opts.name)
end
@doc """ @doc """
Creates a LRU cache Creates a LRU cache
## Examples ## Examples
iex> Floof.LruCache.create() iex> Floof.LruCache.create()
""" """
def create(capacity \\ 5, name \\ __MODULE__) when is_integer(capacity) do def create(capacity \\ 5, name \\ __MODULE__) when is_integer(capacity) do
GenServer.start_link(__MODULE__, capacity, [name: name]) GenServer.start_link(__MODULE__, %{:capacity => capacity}, name: name)
end end
@doc """ @doc """
@ -54,61 +59,72 @@ defmodule Floof.LruCache do
### Server Callbacks ### Server Callbacks
def init(capacity) do def init(opts) do
:ets.new(:position_table, [:named_table, :public]) # kvp = {key: key, value: key_time } opts =
:ets.new(:cache_table, [:named_table, :ordered_set]) # kvp = {key: key_time, time: value} Map.merge(
lru_state = %Floof.LruCache{capacity: capacity} %{:capacity => 5, :position_table => :position_table, :cache_table => :cache_table},
{:ok, lru_state} opts
)
# kvp = {key: key, value: key_time }
:ets.new(opts.position_table, [:named_table, :public])
# kvp = {key: key_time, time: value}
:ets.new(opts.cache_table, [:named_table, :ordered_set])
{:ok, opts}
end end
def handle_call({:put, key, value}, _from, lru_state) do def handle_call({:put, key, value}, _from, lru_state) do
wasUpdated = insert_kvp(key, value) wasUpdated = insert_kvp(lru_state, key, value)
remove_least_recently_used(lru_state) remove_least_recently_used(lru_state)
{:reply, wasUpdated, lru_state} {:reply, wasUpdated, lru_state}
end end
def handle_call({:get, key}, _from, lru_state) do def handle_call({:get, key}, _from, lru_state) do
time_result = :ets.lookup(:position_table, key) time_result = :ets.lookup(lru_state.position_table, key)
case time_result do case time_result do
[{_, time_key}] -> [{_, time_key}] ->
val = update_item_position(key, time_key) val = update_item_position(lru_state, key, time_key)
{:reply, val, lru_state} {:reply, val, lru_state}
[] -> # key not found in cache
# key not found in cache
[] ->
{:reply, nil, lru_state} {:reply, nil, lru_state}
end end
end end
def handle_call({:delete, key}, _from, lru_state) do def handle_call({:delete, key}, _from, lru_state) do
result = :ets.delete(:position_table, key) result = :ets.delete(lru_state.position_table, key)
:ets.delete(:cache_table, key) :ets.delete(lru_state.cache_table, key)
{:reply, result, lru_state} {:reply, result, lru_state}
end end
defp remove_least_recently_used(lru_state) do defp remove_least_recently_used(lru_state) do
# if we exceed capacity remove least recently used item # if we exceed capacity remove least recently used item
num_items = :ets.info(:position_table, :size) num_items = :ets.info(lru_state.position_table, :size)
if num_items > lru_state.capacity do if num_items > lru_state.capacity do
time_key = :ets.first(:cache_table) time_key = :ets.first(lru_state.cache_table)
[{_, {key, _val}}] = :ets.lookup(:cache_table, time_key) [{_, {key, _val}}] = :ets.lookup(lru_state.cache_table, time_key)
:ets.delete(:cache_table, time_key) :ets.delete(lru_state.cache_table, time_key)
:ets.delete(:position_table, key) :ets.delete(lru_state.position_table, key)
end end
end end
defp update_item_position(key, time_key) do defp update_item_position(lru_state, key, time_key) do
# Puts item in back of table # Puts item in back of table
counter = :erlang.unique_integer([:monotonic]) counter = :erlang.unique_integer([:monotonic])
[{_, {_key, val}}] = :ets.lookup(:cache_table, time_key) [{_, {_key, val}}] = :ets.lookup(lru_state.cache_table, time_key)
:ets.delete(:cache_table, time_key) :ets.delete(lru_state.cache_table, time_key)
:ets.insert(:cache_table, {counter, {key, val}}) :ets.insert(lru_state.cache_table, {counter, {key, val}})
:ets.insert(:position_table, {key, counter}) :ets.insert(lru_state.position_table, {key, counter})
val val
end end
defp insert_kvp(key, value) do defp insert_kvp(lru_state, key, value) do
counter = :erlang.unique_integer([:monotonic]) counter = :erlang.unique_integer([:monotonic])
wasUpdated = :ets.insert(:position_table, {key, counter}) wasUpdated = :ets.insert(lru_state.position_table, {key, counter})
:ets.insert(:cache_table, {counter, {key, value}}) :ets.insert(lru_state.cache_table, {counter, {key, value}})
wasUpdated wasUpdated
end end
end end