Anthony W
Anthony W

Reputation: 1327

Working with Elixir ETS across different processes

I'm trying without much success to implement a really simple example around ETS. I want to have a number of worker processes write to an ETS table, and then have a single (and different) reader process retrieve values periodically as a sum. I can't seem to insert into the table without a crash, and the reader returns zero when ever it's executed...here's my code, any help very gratefully received:

The Supervisor module:

defmodule Stackex do
  use Application

  @noOfWriters 1

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    Stackex.Reader.start_link
    Stackex.Table.start_link

    opts = [strategy: :one_for_one, name: Stackex.Supervisor]

    children =
      for i <- 1..@noOfWriters do
        worker(Stackex.Writer, [i], id: i)
      end

    Supervisor.start_link(children, opts)

  end
end

The Writer module:

defmodule Stackex.Writer do
  use GenServer

  def start_link(id) do
    GenServer.start_link(__MODULE__,{id})
  end

  def init({id}) do
    state = %{writer_id: id, value: value}
    schedule_work()
    {:ok, state}
  end

  def handle_info(:update, state) do
    Stackex.Table.add_kvp(state)
    update = %{writer_id: state.writer_id, value: value}
    schedule_work()
    {:noreply, update}
  end

  defp value do
    :random.seed(:erlang.now())
    :random.uniform(10) 
  end

  defp schedule_work() do
    Process.send_after(self, :update, 1000)
  end
end

The Table module:

defmodule Stackex.Table do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__,[], name: __MODULE__)
  end

  def init do
    {:ok, :ets.new(:table, [:set, :named_table])}
  end

  #Client API
  def add_kvp(update) do
    GenServer.cast __MODULE__, {:load, update}
  end

  def get_kvp_sum do
    GenServer.call __MODULE__, {:sum}
  end

  ###### Server Callback Functions ########

  def handle_cast({:load, update}, state) do
    %{writer_id: key, value: value} = update
    {:noreply, :ets.insert(:table, {key, value})}
  end

  #Return total system load
  def handle_call({:sum}, _from, state) do
    sum = :ets.foldl(fn({{_,v}, acc}) -> v + acc end, 0, :table)
    {:reply, sum, state}
  end
end

The Reader module:

defmodule Stackex.Reader do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def init do
    schedule_update
    {:ok, nil}
  end

  def handle_info(:update) do
    sum = Stackex.Table.get_kvp_sum
    IO.puts("Sum #{sum}")

    schedule_update
    {:noreply, nil}
  end

  defp schedule_update do
    Process.send_after(self, :update, 2000)
  end
end

Upvotes: 1

Views: 868

Answers (1)

jisaacstone
jisaacstone

Reputation: 4284

In answer to the question in the title, you can make a named ets table accessible to other processes by passing :public in the options when you call :ets.new/2

But according to the code you posted you do not have multiple processes accessing your table. There is only one Stackex.Table genserver started. Looks like you don't even need it to be a :named_table since you can just pass the pid in your state variable with very little change to your code.

Looks like your immediate problem is you are passing the wrong variable to you add_kvp method (state instead of update).

Upvotes: 1

Related Questions