Diving deeper into OTP: Agents, Tasks, GenEvent, ETS
Alex Rozumii
Elixir Club Kharkiv, 2017
Intro to history

Plan for today
- Task
- GenEvent
- Agent
- ETS
Let's start
What if...
I need to make bunch of external requests
What if...
I need to make a long computation
Task
Task
defmodule Example do
def double(x) do
:timer.sleep(2000)
x * 2
end
end
iex> task = Task.async(Example, :double, [2000])
%Task{pid: #PID<0.111.0>, ref: #Reference<0.0.8.200>}
iex> Task.await(task)
4000
Alternatives?
spawn
spawn
defmodule Example do
def add(a, b) do
IO.puts(a + b)
end
end
iex> spawn(Example, :add, [2, 3])
5
#PID<0.80.0>
What if...
I want dynamically added handlers
GenEvent / GenStage
GenEvent
GenEvent
# Start a new event manager.
{:ok, pid} = GenEvent.start_link([])
GenEvent
# Define an Event Handler
defmodule LoggerHandler do
use GenEvent
# Callbacks
def handle_event({:log, x}, messages) do
{:ok, [x | messages]}
end
def handle_call(:messages, messages) do
{:ok, Enum.reverse(messages), []}
end
end
# Start a new event manager.
{:ok, pid} = GenEvent.start_link([])
# Attach an event handler to the event manager.
GenEvent.add_handler(pid, LoggerHandler, [])
#=> :ok
GenEvent
# Attach an event handler to the event manager.
GenEvent.add_handler(pid, LoggerHandler, [])
#=> :ok
# Send some events to the event manager.
GenEvent.notify(pid, {:log, 1})
#=> :ok
GenEvent.notify(pid, {:log, 2})
#=> :ok
# Call functions on specific handlers in the manager.
GenEvent.call(pid, LoggerHandler, :messages)
#=> [1, 2]
GenEvent.call(pid, LoggerHandler, :messages)
#=> []
Gotcha - supervision
defmodule LoggerHandlerWatcher do
@doc """
inits the GenServer by starting a new handler
"""
def init(logger_pid) do
start_handler(logger_pid)
end
@doc """
handles EXIT messages from the GenEvent handler and restarts it
"""
def handle_info({:gen_event_EXIT, _handler, _reason}, logger_pid) do
{:ok, logger_pid} = start_handler(logger_pid)
{:noreply, logger_pid}
end
@doc """
starts a new handler listening for events on `logger_pid`
"""
defp start_handler(logger_pid) do
case GenEvent.add_mon_handler(logger_pid, LoggerHandler, []) do
:ok ->
{:ok, logger_pid}
{:error, reason} ->
{:stop, reason}
end
end
end
GenStage? See next talk ;)
What if...
I have a state which needs to be persisted
What if...
I need a global storage
What if...
I need to store mutable data
GenServer
With some access methods!
GenServer
Why bother? We already have such functionality
Agent
Agent
iex> {:ok, agent} = Agent.start_link(fn -> [1, 2, 3] end)
{:ok, #PID<0.65.0>}
iex> Agent.update(agent, fn (state) -> state ++ [4, 5] end)
:ok
iex> Agent.get(agent, &(&1))
[1, 2, 3, 4, 5]
Agent
iex> self
#PID<0.115.0>
iex> {:ok, agent} = Agent.start_link(fn -> [] end)
{:ok, #PID<0.123.0>}
iex> Agent.get(agent, fn(_) -> IO.inspect self end)
#PID<0.123.0>
iex> self
#PID<0.115.0>
Agent
Operations supported:
- get
- get_and_update
- update
- cast
Agent
# They can be named!
iex> Agent.start_link(fn -> [1, 2, 3] end, name: Numbers)
{:ok, #PID<0.74.0>}
iex> Agent.get(Numbers, &(&1))
[1, 2, 3]
What if...
you want to do complex queries?
What if...
you want to do store even more data (50 mb, 1 gb)?
What if...
you want to persist data?
What if...
you want to have concurrent access?
What if...
Agent is not enough?
ETS / DETS
ETS
O(log n) or O(1) access times!
ETS
iex> table = :ets.new(:meetups, [:set, :protected])
8212
iex> :ets.new(:meetups, [:set, :protected, :named_table])
:user_lookup
iex> :ets.insert(:meetups, {"Kyiv Elixir", "awesome", ["Elixir"]})
true
iex> :ets.lookup(:meetups, "Kyiv Elixir")
{"Kyiv Elixir", "awesome", ["Elixir"]}
iex> :ets.match(:meetups, {:"$1", "awesome", :"_"})
[["Kyiv Elixir"]]
iex> :ets.delete(:meetups, "Kyiv Elixir")
true
iex> :ets.delete(:meetups)
true
ETS
- set — (default) One value per key. Keys are unique.
- ordered_set — Similar to set but ordered by Erlang/Elixir term.
- bag — Many objects per key but only one instance of each object per key.
- duplicate_bag — Many objects per key, with duplicates allowed.
Even ETS is not enough?
Mnesia
real-time distributed database management system
Now you have even more tools
Now you have even more tools to make your app needlessly complex
Now you have even more tools to make your app needlessly complex
Thanks!
Ask some questions
Alex Rozumii
@brain-geek
FB: arozumiy
Diving deeper into OTP
By Alex Rozumii
Diving deeper into OTP
- 1,466