A common solution to keep ETS tables around is to create a GenServer with no other purpose than to encapsulate the table. You can avoid running code on that server, but still neatly expose module functions to access it. Here’s a very simple example
defmodule Wrapper do
use GenServer
def init(arg) do
:ets.new(:wrapper, [
:set,
:public,
:named_table,
{:read_concurrency, true},
{:write_concurrency, true}
])
{:ok, arg}
end
def start_link(arg) do
GenServer.start_link(__MODULE__, arg, name: __MODULE__)
end
def get(key) do
case :ets.lookup(:wrapper, key) do
[] ->
nil
[{_key, value}] ->
value
end
end
def put(key, value) do
:ets.insert(:wrapper, {key, value})
end
end
The concurrency options should be tweaked to your use case, but the access level has to be :public
like you noticed.
Put the GenServer in your application supervision tree and then usage is
Wrapper.put("key", "value")
Wrapper.get("key") # "value"
Even though it’s been wrapped in a GenServer all the operations run in the calling process, making it fully concurrent (assuming the correct options are set at table creation).