class: center, middle ![Indy Elixir](https://www.indyelixir.org/images/indyelixir.svg) # Processes and
Supervision in Elixir ### Steve Grossi @ Lessonly --- # The plan... 1. What is Elixir? - History - Quick Syntax Primer 2. Process & Supervision - `spawn`, `send` and `receive` - Agent - GenServer 3. Building a sample application --- class: center, middle # What is Elixir? --- # What is Elixir? - Created by José Valim in 2012 - Dynamic, functional, immutable - Compiles to Erlang bytecode (interoperable) - runs on the Erlang VM ("the BEAM") - Erlang symantics, with a friendlier syntax, more powerful metaprogramming, and an expanded standard library. The best of Erlang, but even better. --- # What is Erlang? - Developed in 1986 by Ericsson for use in telecom switches - Process-based actor model - OTP - In the news: Whatsapp bought for $19B, [50 engineers supporting 900M users](http://www.wired.com/2015/09/ whatsapp-serves-900-million-users-50-engineers/) Battle-tested, highly-available, low-maintenance, high-performance. --- class: center, middle # “Let It Crash.” --- class: center, middle # Quick Syntax Primer --- # The IEx shell - `iex` to enter - Ctrl+C twice to exit - `iex -S mix` within an Elixir project - `h Module`, `h Module.function` for help - `i whatever` to inspect ```shell $ iex Erlang/OTP 19 [erts-8.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> ``` --- # Basic Math ```elixir iex> 1 + 1 2 # Scientific notation! iex> 2.0e5 / 5.0e4 4.0 # Division operator always returns a float # Unlike e.g. Ruby, where 5 / 2 = 2 (rounded down) iex> 5 / 2 2.5 # Use `div` to return an integer and `rem` for the remainder iex> div(5, 2) 2 iex> rem(5, 2) 1 iex> abs(-11) 11 ``` --- # Atoms Constants whose name is its own value (like Ruby symbols) ```elixir iex> :steve :steve iex> is_atom(:hydrogen) true ``` --- # Strings ```elixir # double-quoted, UTF-8 encoded iex> "すし🍣" "すし🍣" # Great case support String.upcase("hellö") "HELLÖ" # Strings are stored as binary iex> is_binary("💩") true # Interpolation iex> name = "Steve" iex> "Hello, #{name}" "Hello, Steve" # Concatenation operator iex> "Hello" <> " " <> "there!" "Hello there!" ``` --- # Booleans ```elixir # `true` and `false` are shortcuts for the symbols iex> :true true # Equality iex> false == :false true # Truthy operators: &&, || ! iex> "true" || :one 1 iex> false || :one :one # Strict operators: and, or, not iex> "true" or :one ** (ArgumentError) argument error: "true" iex> false or :one :one ``` --- # Tuples ```elixir # Can contain anything iex> tuple = {:ok, "Result", true} {:ok, "Result", true} iex> elem(tuple, 1) "Result" iex> tuple_size(tuple) 3 ``` Stored contiguously in memory: - getting a value by index is fast - determining size is fast - updating or adding elements is slow - good for small, static collections --- # (Linked) Lists ```elixir # Can contain anything iex> list = [3.14, "π", :pie] # Heads and tails iex> hd(list) 3.14 iex> tl(list) ["π", :pie] # Pattern matching iex> message = ["H", "A", "I"] ["H", "A", "I"] iex> [head | tail] = message ["H", "A", "I"] iex> head "H" iex> tail ["A", "I"] ``` --- # (Linked) Lists ```elixir # So technically... iex> message = ["H" | ["A" | ["I" | []]]] ["H", "A", "I"] # Appending iex> ["O" | message] ["O", "H", "A", "I"] # Adding iex> [1, 2, 3] ++ [4, 5] [1, 2, 3, 4, 5] # Subtracting: matches left-to-right iex> [1, true, "oh", true] -- [1, true] ["oh", true] ``` - Updating, prepending, iterating all fast - Accessing by index is slow - Typically, use lists for larger dynamic collections --- # 2D Collections: Keyword Lists List of 2-item tuples: keys are atoms, ordered, and not necessarily unique. Used for lists of options. ```elixir # Handy syntax iex> [{:foo, "bar"}, {:hello, "world"}] [foo: "bar", hello: "world"] # e.g. render conn, "index.html", posts: posts, filter: :published # or Ecto query = from account in "accounts", where: account.active == true, where: account.email == "me@myemail.com" select: account.name ``` --- # 2D Collections: Maps Your go-to key-value data structure (e.g. hash). All types can be keys and values. Keys are unique. ```elixir # All types can be a key or value iex> user = %{"name" => "Steve", :cool? => true} %{:cool? => true, "name" => "Steve"} # Get value iex> user["name"] "Steve" # Handy getter for symbols iex> user.cool? true # Updating values iex> updated_user = %{user | cool?: false} %{:cool? => false, "name" => "Steve"} ``` --- # 2D Collections: Structs Named maps with their own module: can provide defaults. ```elixir # Declaring default keys and values defmodule Post do defstruct title: "Untitled", published: false end # Module name between % and {} iex> %Post{title: "My Fancy Post"} %Post{published: false, title: "My Fancy Post"} ``` - compile-time guarantees of key validity - more specific pattern-matching - naming things is good for clarity! --- # Anonymous Functions ```elixir iex> multiply = fn a, b -> a * b end #Function<12.52032458/2 in :erl_eval.expr/5> iex> is_function(multiply) true iex> multiply.(2, 4) 8 # Functions can be passed around like any value iex> shout_result = fn func, arg1, arg2 -> "#{func.(arg1, arg2)}!" end #Function<6.71889879/1 in :erl_eval.expr/5> iex> shout_result.(multiply, 2, 3) "6!" # Composable iex> square = fn a -> multiply.(a, a) end iex> square.(5) 25 ``` --- # Anonymous Functions ```elixir # Multiple bodies iex> handle_response = fn ...> {:ok, response} -> "Got it: #{response}" ...> {:error, reason} -> "Nope! #{reason}" ...> end iex> handle_response.({:ok, "Success!"}) "Got it: Success!" iex> handle_response.({:error, "Nothing is real."}) "Nope! Nothing is real." # Errors if no matching clause iex> handle_response.(nil) ** (FunctionClauseError) no function clause matching in... # Though we could add a catch-all clause iex> handle_response = fn ...> {:ok, response} -> "Got it: #{response}" ...> {:error, reason} -> "Nope! #{reason}" ...> _ -> "Unknown response" ...> end ``` --- # Named Functions ```elixir # called through modules, e.g. iex> String.upcase "pew!" "PEW!" # Define modules with `defmodule` defmodule Phaser do # default args with `\\`, also guard clauses! def fire!(times \\ 3) when is_integer(times) do String.duplicate("PEW!", times) end # Multiple function bodies def fire!(input) do {:error, "I cannot fire #{input}"} end end iex> Phaser.fire!(2) "PEW!PEW!"" iex> Phaser.fire!("🍌") {:error, "I cannot fire 🍌"} ``` --- class: center, middle # Processes & Supervision --- ## What is a Process? - Not an OS process - Scheduled by the BEAM - More like a function call - Share nothing - Extremely lightweight: easily spawn millions - Messages & mailboxes --- ## `spawn` ```elixir iex> friend = spawn(fn -> IO.puts("I'm alive!") end) I'm alive! #PID<0.84.0> iex> Process.alive?(friend) false ``` Processes die when they’re done executing their function. --- ## `send` and `receive` ```elixir iex> friend = spawn fn -> ...(1)> receive do ...(1)> :hi -> IO.puts("Oh, hello there!") ...(1)> end ...(1)> end #PID<0.87.0> iex> Process.alive?(friend) true iex> send(friend, :hi) Oh, hello there! :hi iex> Process.alive?(friend) false ``` Processes stay alive while receiving, but die once they've received a message. --- ## `send` and `receive` ```elixir defmodule Friend do def listen do receive do :hi -> IO.puts("Hello there!") listen() _ -> nil end end end ``` Anonymous functions cannot recurse. We need to recursively call a named function to stay alive indefinitely. --- ## `send` and `receive` ```elixir iex> friend = spawn fn -> Friend.listen() end #PID<0.108.0> iex> send(friend, :hi) Hello there! :hi iex> send(friend, :hi) Hello there! :hi iex> send(friend, :hi) Hello there! :hi ``` A friend indeed! --- ## `spawn`, `send` and `receive` A shortcut for spawning named functions: ```elixir # friend = spawn fn -> Friend.listen() end iex> friend = spawn(Friend, :listen, []) # [] = No args #PID<0.119.0> iex> send(friend, :hi) Hello there! :hi ``` Now, to put our friend to work... --- ## Holding State In Processes ```elixir defmodule Friend do def listen(state \\ %{}) do receive do {:set, key, value} -> new_state = Map.put(state, key, value) listen(new_state) {:get, key} -> IO.puts(Map.get(state, key)) listen(state) _ -> nil end end end ``` --- ## Holding State In Processes ```elixir iex> friend = spawn(Friend, :listen, []) #PID<0.119.0> iex> send(friend, {:set, "Foo", "bar"}) {:set, "Foo", "bar"} iex> send(friend, {:get, "Foo"}) bar {:get, "Foo"} iex> ``` --- ## Agents Agents are friends! ```elixir iex> {:ok, friend} = Agent.start(fn -> %{} end) {:ok, #PID<0.127.0>} iex> Process.alive? friend true iex> Agent.update(friend, fn(state) -> Map.put(state, "Foo", "Bar") end) :ok iex> Agent.get(friend, fn(state) -> Map.get(state, "Foo") end) "Bar" ``` Our API is still a little verbose, but Agents are handy. Can return values. --- ## Agents Agents can be named: ```elixir iex> {:ok, bond} = Agent.start(fn -> %{} end, name: :bond) {:ok, #PID<0.122.0>} iex> Process.whereis(:bond) {:ok, #PID<0.122.0>} iex> Agent.update(:bond, fn(state) -> Map.put(state, "Foo", "Bar") end) :ok iex> Agent.get(:bond, fn(state) -> Map.get(state, "Foo") end) "Bar" ``` If an agent dies in the line of duty, you can spin up another with the same name and continue the mission (but you lose the state). --- ## GenServer Agent is built on GenServer (another friend) ```elixir defmodule Friend do use GenServer def start_link do GenServer.start_link(__MODULE__, %{}, name: __MODULE__) end # Callbacks def handle_cast({:set, key, value}, state) do new_state = Map.put(state, key, value) {:noreply, new_state} end def handle_call({:get, key}, _sender, state) do response = Map.get(state, key) {:reply, response, state} end end ``` --- ## GenServer ```elixir iex> Friend.start_link {:ok, #PID<0.119.0>} iex> GenServer.cast(Friend, {:set, "foo", "bar"}) :ok iex> GenServer.call(Friend, {:get, "foo"}) "bar" ``` --- ## GenServer Can provide a really nice API: ```elixir defmodule Friend do use GenServer def start_link do GenServer.start_link(__MODULE__, %{}, name: __MODULE__) end def set(key, value) do GenServer.cast(__MODULE__, {:set, key, value}) end def get(key) do GenServer.call(__MODULE__, {:get, key}) end # Callbacks # ... ``` --- ## GenServer Can provide a really nice API: ```elixir iex> Friend.start_link {:ok, #PID<0.119.0>} iex> Friend.set("foo", "bar") :ok iex> Friend.get("foo") "bar" ``` --- # Supervision We can avoid calling `Friend.start_link` (and get other benefits) by making our application a supervisor: ```sh mix new . --sup ``` Only overwrite `mix.exs` and `app_name.ex`. --- # Supervision ```elixir # mix.exs: def application do [applications: [:logger], mod: {Reminders, []}] end # reminders.ex defmodule Reminders do def start(_type, _args) do import Supervisor.Spec, warn: false children = [ worker(Friend, []), ] opts = [strategy: :one_for_one, name: Reminders.Supervisor] Supervisor.start_link(children, opts) end end ``` --- # Supervision Run `iex -S mix`: ```elixir iex> friend = Process.whereis(Friend) #PID<0.144.0> # Already started! iex> Friend.set("foo", "bar") :ok iex> Friend.get("foo") "bar" iex> Process.exit(friend, :kill) true iex> Process.whereis(Friend) #PID<0.150.0> # Restarted! iex> Friend.get("foo") nil ``` --- class: center, middle # Keeping Track of Reminders --- ## Keeping Track of Reminders - We’ll interact via console to keep it simple - Hold state in a GenServer - API: `Client.remind_me(message, seconds_in_future)` - `IO.puts` when timer elapses --- ## The Client ```elixir defmodule Reminders.Client do use GenServer def start_link do GenServer.start_link(__MODULE__, nil, name: __MODULE__) end def remind_me(message, seconds_in_future) do GenServer.cast(__MODULE__, {:new, message, seconds_in_future}) end def handle_cast({:new, message, seconds_in_future}, state) do milliseconds = seconds_in_future * 1000 Process.send_after(self(), {:reminder, message}, milliseconds) {:noreply, state} end def handle_info({:reminder, message}, state) do IO.puts("REMINDER: #{message}") {:noreply, state} end end ``` --- ## The Client ```elixir iex> Reminders.Client.new("Get milk!", 3) :ok # 3 seconds later... REMINDER: Get milk! ``` --- ## The Client However... - If the `Reminders.Client` dies, we lose all pending reminders - We can’t add more `Reminders.Client` processes if they need to share state So... We can extract a `Reminders.Store` process to hold reminders --- ## The Store What do we need it to do? - Store a list of reminders - Return any reminders for in the past - and remove them from the list ```elixir children = [ worker(Reminders.Store, []), worker(Reminders.Client, []), ] ``` --- ## The Store Saving reminders: ```elixir defmodule Reminders.Store do use GenServer def save({message, timestamp}) do GenServer.cast(__MODULE__, {:save, {message, timestamp}}) end def handle_cast({:save, {message, timestamp}}, state) do new_state = [{message, timestamp} | state] {:noreply, new_state} end end ``` --- ## The Store Removing and returning past reminders: ```elixir def pop_past_reminders do GenServer.call(__MODULE__, :pop_past_reminders) end def handle_call(:pop_past_reminders, _caller, state) do past_reminders = Enum.filter(state, fn({_message, timestamp}) -> timestamp <= :os.system_time(:seconds) end) new_state = state -- past_reminders {:reply, past_reminders, new_state} end ``` --- ## Client, Meet Store ```elixir def init(state) do run_clock() {:ok, state} end defp run_clock do Process.send_after(self(), :tick, :timer.seconds(1)) end def handle_cast({:new, message, seconds_in_future}, state) do remind_at = :os.system_time(:seconds) + seconds_in_future Store.save({message, remind_at}) {:noreply, state} end def handle_info(:tick, state) do Enum.each(Store.pop_past_reminders, fn({message, _timestamp}) -> IO.puts("REMINDER: #{message}") end) run_clock() {:noreply, state} end ``` --- ## Next Steps - How might we break this up further? - What if the store goes down? - How can we increase throughput? --- ## Links - https://github.com/stevegrossi/reminders --- class: center, middle ## `Process.exit(steve, :normal)` [@stevegrossi](https://twitter.com/stevegrossi) [work.stevegrossi.com](https://work.stevegrossi.com) Want more Elixir?
[www.indyelixir.org](http://www.indyelixir.org)