Calling a function or variable once

I am using :ets for creating tables inside my modules and then trying to analyze the data. The problem I am facing is that every time the function is called, a new table is created and previous data is lost. Is there any way that I can create the table once and then use that table in my modules and functions?

here is my code:

defmodule Base do

  def insert(s, d) do
    dl = :ets.new(:table, [:duplicate_bag])
    :ets.insert(dl, {s, d})
    :ets.lookup(dl, s) 
  end

end

As you can see, every time I call this function the table will be created. Is there any way to avoid this?

Thanks

Pass in :named_table in the options to :ets.new/2, this will cause an error when it is created a second time, and then catch that exception in your function :slight_smile:

2 Likes

ets tables are owned by the process creating them, so when that process finishes the etc table is removed. I would guess that your function is called by different processes. You probably need to spin up a GenServer or similar to own the ets table. You can also use the named option too as above.

3 Likes

Generally you only want to call :ets.new once, and then pass around the PID (or use a named table). Something like:

defmodule Base do
  def init() do
    :ets.new(:table, [:duplicate_bag])
  end

  def insert(dl, s, d) do
    :ets.insert(dl, {s, d})
    :ets.lookup(dl, s) 
  end

end
4 Likes

Thank you so much and it worked perfectly …

yes but i am not using gen servers at this time. Secondly, the table never gets removed, its just replaced by a new one. But the idea of using gen servers is great and i will definitely have a look at it.

The PID will be same in this case too, which will overwrite the table every time i call it. Guide me if am wrong.

You’d use that code like this:

table_pid = Base.init()

Base.insert(table_pid, :foo, :bar)
Base.insert(table_pid, :wat, :huh)

# not written in the example, but a plain wrapper around :ets.lookup
{key, value} = Base.lookup(table_pid, :foo)
1 Like

How can i handle the error and skip the line where the table is generated …

Thank you

This still requires a way to know that the table has already been initialized … and this, of course, is where a GenServer really helps: start it up and it manages the table over its lifetime, creating it in its init/1 and then the table is cleaned up automatically when the GenServer exits.

However … holding on to a PID outside the GenServer is a bit dangerous: the GenServer could fall over and be restarted by its supervisor, leaving a stale PID. This is a benefit of having a named table … but … one can also provide an API that fetches the PID from the GenServer’s state and then have something like:

    def lookup(key), do: :ets.lookup(GenServer.call(__MODULE__, :ets_pid), key)

    def init(_), do: {:ok, :ets.new(__MODULE__, [:duplicate_bag, protected])}
    def handle_call(:ets_pid, _from, state), do: {:reply, state, state}

The GenServer is storing the ets table’s PID as its state, and returning it on request. The lookup call can still fail, but now the error would be that the GenServer is not running which is probably a bit more clearly diagnostic of the actual error. This does make the GenServer a bottleneck for lookups in that it must do a message roundtrip to get the pid …

… and so we’re back to having a named table:

    def lookup(key), do: :ets.lookup(__MODULE__,, key)
    def init(_), do: {:ok, :ets.new(__MODULE__, [:duplicate_bag, :named_table, :protected])}

Simpler, less code, fewer bottlenecks…

1 Like

The error is thrown as an exception (try it in iex to see!), so:

try do
  :ets.new(:hello2, [:duplicate_bag, :named_table])
rescue
   ArgumentError -> :ok
end
1 Like

Thank for such a good explanation.

How much of an issue that is depends on what the code’s using ETS to do - for instance, the :digraph module in stdlib uses ETS tables to store vertices + edges during a computation; initialization of the tables is then just part of the setup for the computation.