Resist
Reimplementing
OTP
Any sufficiently complicated
Erlang or elixir
program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of
OTP
What is OTP?
Patterns & Generic Abstractions over the Common Process
http://learnyousomeerlang.com/what-is-otp
Let's take a trip into implementing a stateful process in vanilla elixir
CraicStack
new()
{:ok, pid}
P
spawn
CraicStack
push(ref, value)
ref
ref :: pid | name
name :: atom()
value :: any()
{:push, value}
P
CraicStack
pop(ref)
value
from :: ref
ref :: pid | name
name :: atom()
value :: any()
{:pop, from}
P
{:pop_reply, value}
CraicStack
peek(ref)
value
ref :: pid | name
name :: atom()
value :: any()
{:peek, from}
P
{:peek_reply, value}
CraicStack
count(ref)
size
ref :: pid | name
name :: atom
size :: positive_integer
{:count, from}
P
{:count_reply, value}
mix new craic_stack
vim craic_stack
STEVEN GOTO VIM NOW...
Lets Look at
Observer and get our
Mailbox working...
{:ok, stack} = CraicStack.new
:observer.start
for x <- 1..10_000_000, do: CraicStack.push(stack, x)
for _ <- 1..10_000_000, do: CraicStack.pop(stack)
So out of the box vanilla elixir processes give us some instrumentation but as we will soon see there there is much we are missing out on
Error Scenario: Pop Crash
Our code is starting to look lame and bloated as we try to harden it, even though we are still embracing the
"Let it Crash"
philosphy
Did our Error capture any useful contextual information?
00:42:13.356 [error] Process #PID<0.95.0> raised an exception
** (MatchError) no match of right hand side value: []
(craic_stack) lib/craic_stack.ex:57: CraicStack.handle/3
(craic_stack) lib/craic_stack.ex:45: CraicStack.loop/1
What would our recovery scenario be if we want the stack to recover?
Even if we hand roll a solution to restart our process either through links, monitors, or our custom protocol it is still going to be reinventing the wheel...
Any sufficiently complicated
Erlang or elixir
program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of
OTP
The point:
Let's do the same thing in OTP
vim craic_stack
Let's get a peek inside with Observer and some :sys calls
:sys.statistics(stack, :get)
{:ok,
[start_time: {{2017, 1, 31}, {14, 39, 37}},
current_time: {{2017, 1, 31}, {14, 40, 36}},
reductions: 205,
messages_in: 2,
messages_out: 0]}
When we recreate the same Error in GenServer version we get more details, such as last message and state
14:26:25.966 [error] GenServer #PID<0.120.0> terminating
** (MatchError) no match of right hand side value: []
(craic_stack) lib/gen_stack.ex:43: GenStack.handle_call/3
(stdlib) gen_server.erl:615: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:647: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: :pop
State: %{count: 0, store: []}
Let's Supervise our process and have that restart policy we have always dreamt of...
Why restart you ask?
Thats why...
Image Credit: High Throughput Erlang - Geoff Cant
Let's wrap our CraicStack in an Application so we can share the fun with all our OTP friends
Let's spin up our CraicStack Application and have a look with :observer
In conclusion... unless you are doing something highly bespoke, fall into the loving arms of OTP
For times when GenSever and GenEvent are just not enough there are Special Processes...
It is possible to make your custom processes play the OTP game, you just need to follow the OTP rules and leverage
:gen, :sys and :proc_lib
defmodule ProblemChild do
use SpecialProcess
def start_link do
SpecialProcess.start_link(__MODULE__, :loop, [])
end
def loop do
IO.puts "Yay I'm looping!"
:timer.sleep(1000)
loop
end
end
An Elixir Macro that makes your process sing the OTP song
"The benefits of using a special process is that the process can be part of a Supervision tree and will emit and handle proper system and error logger messages."
https://github.com/rbishop/special_process
Thanks for listening!
@holsee
holsee.github.com
code: github.com/holsee/craic_stack
Sponsored Book Raffle!
OTP
By Steven Holdsworth
OTP
Why you should embrace OTP in Elixir
- 382