ETS created on Application init

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).

7 Likes