Skip to content

Instantly share code, notes, and snippets.

@mgwidmann mgwidmann/supervisor.ex
Last active Jul 30, 2018

Embed
What would you like to do?
Overview of the Supervision system
# The golden trinity of Erlang is the secret sauce behind what makes
# Elixir a strong choice for any backend application and/or system.
# https://tkowal.wordpress.com/2015/10/20/failing-fast-and-slow-in-erlang-and-elixir/
# The supervision and worker system, commonly referred to as OTP (Open
# Telecom Platform, which has nothing to do with telephony), is what
# gives Erlang/Elixir applications them a robust "self-healing" type
# property that makes them extremely fault tolerant.
# Erlang/Elixir is an Actor based system. Each "Process" runs within
# the virtual machine and is only similar to OS processes in design.
# They are concurrent, share nothing, and are cheap to make.
# Fire up iex to play around
$ iex
# The current actor's PID (the one waiting for input) can be retrieved
# from the `self` function, available everywhere.
iex> self
#PID<0.91.0>
# And we can kill ourself like so (equivalent of `kill -9`, no chance for recovery)
iex> Process.exit(self, :kill)
** (EXIT from #PID<0.91.0>) killed
Interactive Elixir (1.2.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
# The terminal starts back up as if for the first time, resets the command
# counter and our history (hitting up arrow) is gone also. A supervisor
# restarted the IEx process and it has "healed". We can see that by checking
# our own PID again to see that it has changed.
iex> self
#PID<0.106.0>
# Lets start a new mix project, passing `--sup` to tell mix to generate
# a supervision tree for us. Elixir applications heal themselves like
# the superhero Wolverine, so lets make a project based on him.
$ mix new wolverine --sup
# As we can see in mix.exs:5, our app is named `:wolverine`, and on line
# 18, we tell it to use the module `Wolverine` to start our application.
# In lib/wolverine.ex we have our application, where we start the children
# of our application (which currently happens to be empty). Lets pretend we're
# coding a robot version of Wolverine and we want to model out what that might
# look like.
# Lets start at the top and work our way down, create the head.
# In lib/wolverine/head.ex write the following:
################################## DISCLAIMER #########################################
# Supervision tree design is a whole different topic. The design presented here
# is in no way a typical supervision tree design. Typically you'd design as systems
# and subsystems, based upon their dependencies between each system they should be
# supervised accordingly with potentially different supervision strategies.
defmodule Wolverine.Head do
use Supervisor
def start_link() do
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def init() do
children = [
]
IO.puts "#{__MODULE__} (#{inspect self}) started up from!"
supervise(children, strategy: :one_for_one)
end
end
# Copy that same file into lib/wolverine/body.ex and lib/wolverine/enemy.ex
# and rename the module name on line 1 to be Wolverine.Body and Wolverine.Enemy
# respectively. Also, in Woverine.Enemy, replace `Supervisor` with `GenServer` everywhere
# and delete everything in the `init/1` function except the print statement and return
# {:ok, nil} instead.
# And make sure it gets started up, modify lib/wolverine.ex:11
supervisor(Wolverine.Head, []),
worker(Wolverine.Enemy, [])
# And the head supervises the body... modify lib/head.ex:10
supervisor(Wolverine.Body, [])
# Add the final pieces to complete him. Copy lib/wolverine/enemy.ex into
# lib/wolverine/claw.ex and lib/wolverine/leg.ex. In those two files Change the `start_link/0` to
# accept a name parameter so that we can start up multiple of them:
def start_link(name) do
GenServer.start_link(__MODULE__, name: name)
end
# In lib/wolverine/body.ex add four workers on line 10. This will start multiple independent
# versions of the claw and leg. The second parameter is a list of arguments passed to `start_link`:
worker(Wolverine.Claw, [Wolverine.LeftClaw], id: Wolverine.LeftClaw),
worker(Wolverine.Claw, [Wolverine.RightClaw], id: Wolverine.RightClaw),
worker(Wolverine.Leg, [Wolverine.LeftLeg], id: Wolverine.LeftLeg),
worker(Wolverine.Leg, [Wolverine.RightLeg], id: Wolverine.RightLeg)
# Start up the system and see the printout of each supervisor/worker
$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Elixir.Wolverine.Head (#PID<0.88.0>) started up from!
Elixir.Wolverine.Body (#PID<0.89.0>) started up from!
Elixir.Wolverine.Claw (#PID<0.90.0>) started up from!
Elixir.Wolverine.Claw (#PID<0.91.0>) started up from!
Elixir.Wolverine.Leg (#PID<0.92.0>) started up from!
Elixir.Wolverine.Leg (#PID<0.93.0>) started up from!
Elixir.Wolverine.Enemy (#PID<0.94.0>) started up from!
Interactive Elixir (1.2.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
# Take note of the PIDs there, we can get their PID and kill them
# If we kill the body, it will regenerate, but because it supervises
# the claws and legs, they also will be recreated.
iex> Process.whereis(Wolverine.Body) |> Process.exit(:kill)
Elixir.Wolverine.Body (#PID<0.97.0>) started up from!
Elixir.Wolverine.Claw (#PID<0.98.0>) started up from!
Elixir.Wolverine.Claw (#PID<0.99.0>) started up from!
Elixir.Wolverine.Leg (#PID<0.100.0>) started up from!
Elixir.Wolverine.Leg (#PID<0.101.0>) started up from!
true
iex(2)>
# Notice the actors for the Head and Enemy are isolated from the crash.
# Only the subsystems we designated are rebooted.
iex> Process.whereis(Wolverine.Head)
#PID<0.88.0>
iex> Process.whereis(Wolverine.Enemy)
#PID<0.94.0>
# We can visualize our supervision tree by opening up observer and
# looking at the applications tab
iex> :observer.start
# Finally lets make them able to fight! Add to both the lib/wolverine/claw.ex and
# lib/wolverine/leg.ex the below function:
@attack_power 1
def handle_cast({:attack, enemy}, _) do
IO.puts "Attacking #{inspect enemy} with #{@attack_power} damage"
:timer.sleep(1000) # Sleep one second
GenServer.cast(enemy, {:hit, @attack_power})
{:noreply, nil}
end
# To make the claw more powerful, change the attack power to 5.
# Lets make sure the enemy can respond to the message we sent him.
# In lib/wolverine/enemy.ex add a function to handle the incoming message:
def handle_cast({:hit, damage}, _) do
IO.puts "Ouch, hit with #{damage} damage!"
{:noreply, nil}
end
# Lastly, when any piece of code wants to make an attack, we need to make a function
# to make that easy to do. Typically this goes inside the GenServer module, but we can
# put it anywhere.
# In lib/wolverine.ex lets add the attack function
def attack(attack_with, enemy) do
GenServer.cast(attack_with, {:attack, enemy})
end
# Lets try it out
$ iex -S mix
iex> Wolverine.attack(Wolverine.LeftClaw, Wolverine.Enemy)
Attacking Wolverine.Enemy with 5 damage
:ok
iex> Ouch, hit with 5 damage!
# Notice the attack message and prompt shows up immediately but the ouch
# message is a second later
iex> Wolverine.attack(Wolverine.RightLeg, Wolverine.Enemy)
Attacking Wolverine.Enemy with 1 damage
:ok
iex> Ouch, hit with 1 damage!
# Another fun tool is a third party project visualixir
# https://github.com/koudelka/visualixir
# Clone the repo, run `mix deps.get` and start it up with
$ iex --name visualixir@127.0.0.1 -S mix phoenix.server
# Restart the wolverine application with
$ iex --name wolverine@127.0.0.1 -S mix
# Join the nodes together
iex> Node.connect :"visualixir@127.0.0.1"
# View at http://localhost:4000/, select the wolverine node and find among
# all the dots the ones labeled with the claws and legs as well as the enemy.
# Alt + click them to add tracing so we can see the messages coming in and
# out, then execute the attack functions again to see the messages flying
# back and forth.
@hopewise

This comment has been minimized.

Copy link

commented Jul 30, 2018

a typo in def init() do where it should be def init(state) do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.