Blanket covers your ETS tables

Build Status

If you use ETS with Elixir and Erlang, you know that an ETS table is bound to a process, and that if this process chrashes, the tables vanishes. All data is lost.

The simplest thing to do is to start a process responsible for owning the table, and do all actual table operations on another process. But this requires coodination between the two process, public named tables with an unique name. Sometimes, you need several private or protected unnamed tables.

More informations on theese problems and a simple solution to them can be found on Steve Vinoski's Blog here : Don't Lose Your ets Tables.

You can also look et an Erlang implementation of the solution on github.

But all you need to from there is to use this package !

Installation

Just define the :blanket dependency in your project's mix.exs.

  defp deps do
    [{:blanket, "~> 0.3.0"}]
  end

Documentation

The documentation can be found on Hex Docs.

Example

This is a simple example generic table owner server using Process.register to register its name. Just check out the comments.

Others strategies for identifying processes are available.

defmodule MyApp.TableTop do
  use GenServer
  use Blanket

  def start_link(name) do
    # 1. Define a table as you would do with ETS. Here is the equivalent of
    # :ets.new(:users, [:set, :protected])
    table_def = {:users, [:set, :protected]}
    # 2. Create a new Blanket process to be the table heir, passing a name for
    # the table owner process. The table is created.
    {:ok, _} = Blanket.new(__MODULE__, name, table_def)
    # 3. Start your table owner process.
    GenServer.start_link(__MODULE__, [name])
  end

  # …

  def init([name]) do
    # 1. The table owner registers its name to be found by the table heir. Any
    # registration system is possible, would it be Process.register, or gproc,
    # global, a custom pid store …
    Process.register(self, name)
    # 2. Call Blanket.receive_table to be given the table on fresh start and
    # each time you restart. You must register *before* so the heir can find
    # you.
    {:ok, tab} = Blanket.receive_table
    # 3. You now own the table and can use it
    {:ok, tab}
  end

end

Identifying processes

The previous example uses Process.register to register its name. The heir is given the owner's module and its name, and can find the owner's pid by calling module.get_owner_pid(name). This function is defined in your module by calling use Blanket in your module definition.

This is equivalent to the following function :

  def get_owner_pid(name) do
    Process.whereis(name)
  end

If you wish to use a different mechanism, e.g.gproc, you must define your own get_owner_pid function :

  def get_owner_pid(name) do
    :gproc.whereis({:n, :l, name})
  end

You can now register your process with gproc :

  def init([name]) do
    :gproc.reg({:n, :l, name})
  end

Remember to always register your process before calling Blanket.receive_table.

Options

Blanket.new accepts a keyword-list as its fourth argument. At the moment, two keys are accepted :

  opts = [transient: true, populate: fn(tab) -> :ok = do_things(tab) end]
  Blanket.new(module, owner, tab_def, opts)

Transient tables

If you need to shutdown your table owner, you should tell the heir not to wait for a restart by calling Blanket.abandon_table(tab, heir).

You must do this in the table owner process :

defmodule MyApp.TemporaryTable do
  use GenServer
  use Blanket

  # …

  def terminate(:normal, {tab, heir}) do
    :ok = Blanket.abandon_table(tab, heir)
  end

  def terminate(error, state) do
    # …
  end

  # …

end

This technique has a drawback, you must store the pid of the heir in your generic server state (or somewhere else) in order to turn it off. You can also use the :transient option in the heir configuration to achieve this.