SlideShare a Scribd company logo
Concurrency, Robustness and Elixir
SoCraTes 2015
Erlang About Erlang
What is Erlang?
Erlang is ...
●
A programming language (Concurrent, Functional)
●
A virtual machine (lightweight, massively concurrent, asynchronous)
●
An ecosystem (OTP, design patterns and libraries for building robust systems)
●
Developed since 1986, Joe Armstrong et al (Ericsson)
●
Open Source since 1998
●
Initially used for telephone switches
●
Now for many other applications requiring High Availability & distributed
concurrency:
➔
ejabberd
➔
CouchDB
➔
GitHub
Elixir The Elixir project
What is Elixir?
Elixir is a functional, concurrent, general-purpose programming language built atop
the Erlang Virtual Machine.
●
Full compatibility with the Erlang universe, compiles to Erlang bytecode
●
Refurbished syntax (Ruby inspired)
●
Aims for enhanced productivity (better build tools, less boilerplate)
●
Extensibility (Structs, Protocols, DSLs -> Clojure inspired)
Quite young -> First commit Jan 9, 2011
Current version 1.0.5
http://elixir-lang.org/
Erlang Computing challenges of the 21st century
4004
8080
186
286
i386
i486
P5 Pentium
P6
P7
Core
Nehalem
Ivy Bridge
Haswell
0.1
1
10
100
1000
10000
MHz
Clock speed limit Multicore Clustering & Cloud High availability
Concurrent
programming
in Elixir
Elixir The Actor model
Process
Mailbox
Msg 1
Msg 2
Msg 3
defmodule ListenerServer do
use GenServer
def init(args) do
Process.register self, :listener
{:ok, []}
end
def handle_cast(:quit, state) do
{:stop, :normal, state}
end
def handle_cast(message, state) do
IO.puts "Got the message '#{message}'"
{:noreply, state}
end
end
Behaviour
{ state }
Elixir The Actor model
Process
Spawn
Send/
Receive
Mailbox
Msg 1
Msg 2
Msg 3
defmodule ListenerServer do
use GenServer
def init(args) do
Process.register self, :listener
{:ok, []}
end
def handle_cast(:quit, state) do
{:stop, :normal, state}
end
def handle_cast(message, state) do
IO.puts "Got the message'"
{:noreply, state}
end
end
Behaviour
{state}
Elixir Actors in Elixir
Erlang / Elixirs key feature: Packing code into small chunks that can be run
independently and concurrently.
Actor Model:
●
Independent asynchronous processes
●
Share nothing with other processes
●
Communication only via messages
●
Messages are stored in the process mailbox until consumed
●
A process can create new processes
●
Processes have an inner state
Elixir processes ...
●
Aren't system threads or processes.
●
Managed by the Erlang Virtual Machine
●
Extremely light-weight, nearly no overhead to create one.
●
Even a modest computer can run > 100000 Elixir processes.
●
Can be changed at run-time (hot-code swapping)
Elixir OOP versus COP
Object oriented programming Concurrent oriented programming
Class Actor
Instance Process
Instantiation Spawn
Constructor Initializer
Prototype Behaviour
Setter/Getter Message
Interface Protocol
Attribute State
Singleton Registered process
pid = spawn function # Start a new process, return its PID
send pid, message # Send message to process with pid
receive do # Try to match messages in process mailbox
match1 -> result1
match2 -> result2
...
after timeout -> result # Result when no message arrived in timeout
end
self # Process ID of current process
Process.list # List all running processes
flush # (iex) Flush mailbox, print all messages
:pman.start # Start Erlang process manager
Elixir Concurrent programming
defmodule Listener do
def listen do
receive do
:quit -> exit(nil)
message -> IO.puts "Got the message '#{message}'"
end
listen
end
end
listener = spawn fn -> Listener.listen end
send listener, "Hello World"
send listener, :quit
Elixir Process programming
defmodule Listener do
def listen do
receive do
:quit -> exit(nil)
{message, from} -> IO.puts "Got the message '#{message}'"
send from, "Thank you for the message!"
end
listen
end
end
listener = spawn fn -> Listener.listen end
send listener, {"Hello World", self}
flush
send listener, :quit
flush
Elixir Process programming
defmodule Listener do
def start do
Process.register self, :listener
listen
end
def listen do
receive do
:quit -> exit(nil)
message -> IO.puts "Got the message '#{message}'"
end
listen
end
end
spawn fn -> Listener.start end
send :listener, "Hello World"
send :listener, :quit
Elixir Process registering
Elixir Process behaviours
Task Asynchronous calculation
Agent State-carrying process
GenServer Generic OTP Server
Supervisor OTP Supervisor
Application OTP Application
GenEvent Generic Event Handler
:gen_fsm Generic Finite State Machine
Elixir Tasks and Agents
# Start an independent task
task = Task.async(fn -> do_some_work() end)
# Collect the result
result = Task.await(task)
Tasks: Independent asynchronous calculations
# Start a new agent
{:ok, agent} = Agent.start(fn -> initial_state end)
# Update the agent state
Agent.update(agent, fn old_state -> new_state end)
# Get the current agent state
Agent.get(agent, fn state -> value end)
# Terminate the agent
Agent.stop(agent)
Agents: Wrappers around state
line = "Freude schöner Götterfunken"
task = Task.async(fn -> String.upcase(line) end)
IO.puts Task.await(task)
task = Task.async(fn -> :timer.sleep(3000);
String.upcase(line) end)
IO.puts "Waiting for the task to complete..."
IO.puts Task.await(task)
Elixir Task and Agent example
{:ok, agent} = Agent.start(fn -> 0 end, [name: :counter])
IO.puts Agent.get(:counter, fn value -> value end)
Agent.update(:counter, fn value -> value+1 end)
IO.puts Agent.get(:counter, fn value -> value end)
Agent.stop(:counter)
Elixir Generic Server Callbacks
defmodule MyServer do
use GenServer
# Initialize server
def init(args), do: {:ok, state}
# Terminate server
def terminate(reason, state), do: {reason, state}
# Call with reply
def handle_call(msg, from, state), do: {:reply, reply, new_state}
# Casts without reply
def handle_cast( msg, state ), do: {:noreply, new_state}
# All other messages
def handle_info( msg, state ), do: {:noreply, new_state}
# Hot code swap
def code_change( vsn, state, extra ), do: {:ok, new_state}
end
defmodule ListenerServer do
use GenServer
# Public API
def start_link, do: GenServer.start_link ListenerServer, []
# Callback functions
def init(args) do
Process.register self, :listener
{:ok, []}
end
def handle_cast(:quit, state) do
{:stop, :normal, state}
end
def handle_cast(message, state) do
IO.puts "Got the message '#{message}'"
{:noreply, state}
end
end
{:ok, pid} = ListenerServer.start_link
GenServer.cast :listener, "Hello"
GenServer.cast :listener, :quit
Elixir Generic Server
defmodule ListenerServer.Supervisor do
import Supervisor.Spec
def start do
children = [ worker(ListenerServer, [], restart: :transient) ]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
ListenerServer.Supervisor.start
GenServer.cast :listener, "Hello"
GenServer.cast :listener, {1,2,3} # Crashes the listener server
GenServer.cast :listener, "Hello World"
GenServer.cast :listener, :quit
Elixir Supervision
defmodule FirstEventHandler do
use GenEvent
def handle_event(message, state) do
IO.puts "The first event handler was notified: '#{message}'"
{:ok, state}
end
end
defmodule SecondEventHandler do
use GenEvent
def handle_event(message, state) do
IO.puts "The second event handler was notified: '#{message}'"
{:ok, state}
end
end
GenEvent.start_link(name: :eventmanager)
GenEvent.add_handler(:eventmanager, FirstEventHandler, nil)
GenEvent.add_handler(:eventmanager, SecondEventHandler, nil)
GenEvent.notify(:eventmanager, "Hi there!")
Elixir Event Handler
defmodule FiniteStateMachine do
@behaviour :gen_fsm
def start_link(_opts) do
:gen_fsm.start_link({:local, FSM}, __MODULE__, [], [])
end
def next_state() do
:gen_fsm.send_event(FSM, :round)
end
def init(_), do: IO.puts "FSM started"; { :ok, :ping, [] }
def ping(:round, state), do: IO.puts "Ping!"; {:next_state, :pong, state}
def pong(:round, state), do: IO.puts "Pong!"; {:next_state, :ping, state}
end
FiniteStateMachine.start_link [] # "FSM started"
FiniteStateMachine.next_state # "Ping!"
FiniteStateMachine.next_state # "Pong!"
FiniteStateMachine.next_state # "Ping!"
FiniteStateMachine.next_state # "Pong!"
Elixir Finite state machine
Elixir OTP
Super-Supervisor
Supervisor Supervisor
Supervisor
Worker Worker Worker
Worker
Worker Worker WorkerHelper Helper
Application
defmodule ExampleApp do
use Application
def start(_type, _args) do
MainSupervisor.start
end
end
Elixir Elixir application project
Build tool mix:
> mix new exampleapp
mix.exs # Mix project definition file
/config/config.exs # Project specific configuration
/lib/ # Source code
/test/ # Tests suites
# Starts the BEAM Erlang Virtual Machine
# and the EPMD Erlang Port Mapper Daemon (default port 4369)
cluster01> elixir --sname node --no-halt --cookie secret_cookie
Elixir Distributed computing: Nodes
# Elixir master node (on same subnet as cluster nodes)
master> iex --sname masternode --cookie secret_cookie
Node.connect :"node@cluster01" # Establish node connection
pid = Node.spawn :"node@cluster01", func # Spawn a process
Node.self # Name of current node
Node.list # List all connected nodes

More Related Content

Concurrency, Robustness & Elixir SoCraTes 2015

  • 1. Concurrency, Robustness and Elixir SoCraTes 2015
  • 2. Erlang About Erlang What is Erlang? Erlang is ... ● A programming language (Concurrent, Functional) ● A virtual machine (lightweight, massively concurrent, asynchronous) ● An ecosystem (OTP, design patterns and libraries for building robust systems) ● Developed since 1986, Joe Armstrong et al (Ericsson) ● Open Source since 1998 ● Initially used for telephone switches ● Now for many other applications requiring High Availability & distributed concurrency: ➔ ejabberd ➔ CouchDB ➔ GitHub
  • 3. Elixir The Elixir project What is Elixir? Elixir is a functional, concurrent, general-purpose programming language built atop the Erlang Virtual Machine. ● Full compatibility with the Erlang universe, compiles to Erlang bytecode ● Refurbished syntax (Ruby inspired) ● Aims for enhanced productivity (better build tools, less boilerplate) ● Extensibility (Structs, Protocols, DSLs -> Clojure inspired) Quite young -> First commit Jan 9, 2011 Current version 1.0.5 http://elixir-lang.org/
  • 4. Erlang Computing challenges of the 21st century 4004 8080 186 286 i386 i486 P5 Pentium P6 P7 Core Nehalem Ivy Bridge Haswell 0.1 1 10 100 1000 10000 MHz Clock speed limit Multicore Clustering & Cloud High availability
  • 6. Elixir The Actor model Process Mailbox Msg 1 Msg 2 Msg 3 defmodule ListenerServer do use GenServer def init(args) do Process.register self, :listener {:ok, []} end def handle_cast(:quit, state) do {:stop, :normal, state} end def handle_cast(message, state) do IO.puts "Got the message '#{message}'" {:noreply, state} end end Behaviour { state }
  • 7. Elixir The Actor model Process Spawn Send/ Receive Mailbox Msg 1 Msg 2 Msg 3 defmodule ListenerServer do use GenServer def init(args) do Process.register self, :listener {:ok, []} end def handle_cast(:quit, state) do {:stop, :normal, state} end def handle_cast(message, state) do IO.puts "Got the message'" {:noreply, state} end end Behaviour {state}
  • 8. Elixir Actors in Elixir Erlang / Elixirs key feature: Packing code into small chunks that can be run independently and concurrently. Actor Model: ● Independent asynchronous processes ● Share nothing with other processes ● Communication only via messages ● Messages are stored in the process mailbox until consumed ● A process can create new processes ● Processes have an inner state Elixir processes ... ● Aren't system threads or processes. ● Managed by the Erlang Virtual Machine ● Extremely light-weight, nearly no overhead to create one. ● Even a modest computer can run > 100000 Elixir processes. ● Can be changed at run-time (hot-code swapping)
  • 9. Elixir OOP versus COP Object oriented programming Concurrent oriented programming Class Actor Instance Process Instantiation Spawn Constructor Initializer Prototype Behaviour Setter/Getter Message Interface Protocol Attribute State Singleton Registered process
  • 10. pid = spawn function # Start a new process, return its PID send pid, message # Send message to process with pid receive do # Try to match messages in process mailbox match1 -> result1 match2 -> result2 ... after timeout -> result # Result when no message arrived in timeout end self # Process ID of current process Process.list # List all running processes flush # (iex) Flush mailbox, print all messages :pman.start # Start Erlang process manager Elixir Concurrent programming
  • 11. defmodule Listener do def listen do receive do :quit -> exit(nil) message -> IO.puts "Got the message '#{message}'" end listen end end listener = spawn fn -> Listener.listen end send listener, "Hello World" send listener, :quit Elixir Process programming
  • 12. defmodule Listener do def listen do receive do :quit -> exit(nil) {message, from} -> IO.puts "Got the message '#{message}'" send from, "Thank you for the message!" end listen end end listener = spawn fn -> Listener.listen end send listener, {"Hello World", self} flush send listener, :quit flush Elixir Process programming
  • 13. defmodule Listener do def start do Process.register self, :listener listen end def listen do receive do :quit -> exit(nil) message -> IO.puts "Got the message '#{message}'" end listen end end spawn fn -> Listener.start end send :listener, "Hello World" send :listener, :quit Elixir Process registering
  • 14. Elixir Process behaviours Task Asynchronous calculation Agent State-carrying process GenServer Generic OTP Server Supervisor OTP Supervisor Application OTP Application GenEvent Generic Event Handler :gen_fsm Generic Finite State Machine
  • 15. Elixir Tasks and Agents # Start an independent task task = Task.async(fn -> do_some_work() end) # Collect the result result = Task.await(task) Tasks: Independent asynchronous calculations # Start a new agent {:ok, agent} = Agent.start(fn -> initial_state end) # Update the agent state Agent.update(agent, fn old_state -> new_state end) # Get the current agent state Agent.get(agent, fn state -> value end) # Terminate the agent Agent.stop(agent) Agents: Wrappers around state
  • 16. line = "Freude schöner Götterfunken" task = Task.async(fn -> String.upcase(line) end) IO.puts Task.await(task) task = Task.async(fn -> :timer.sleep(3000); String.upcase(line) end) IO.puts "Waiting for the task to complete..." IO.puts Task.await(task) Elixir Task and Agent example {:ok, agent} = Agent.start(fn -> 0 end, [name: :counter]) IO.puts Agent.get(:counter, fn value -> value end) Agent.update(:counter, fn value -> value+1 end) IO.puts Agent.get(:counter, fn value -> value end) Agent.stop(:counter)
  • 17. Elixir Generic Server Callbacks defmodule MyServer do use GenServer # Initialize server def init(args), do: {:ok, state} # Terminate server def terminate(reason, state), do: {reason, state} # Call with reply def handle_call(msg, from, state), do: {:reply, reply, new_state} # Casts without reply def handle_cast( msg, state ), do: {:noreply, new_state} # All other messages def handle_info( msg, state ), do: {:noreply, new_state} # Hot code swap def code_change( vsn, state, extra ), do: {:ok, new_state} end
  • 18. defmodule ListenerServer do use GenServer # Public API def start_link, do: GenServer.start_link ListenerServer, [] # Callback functions def init(args) do Process.register self, :listener {:ok, []} end def handle_cast(:quit, state) do {:stop, :normal, state} end def handle_cast(message, state) do IO.puts "Got the message '#{message}'" {:noreply, state} end end {:ok, pid} = ListenerServer.start_link GenServer.cast :listener, "Hello" GenServer.cast :listener, :quit Elixir Generic Server
  • 19. defmodule ListenerServer.Supervisor do import Supervisor.Spec def start do children = [ worker(ListenerServer, [], restart: :transient) ] Supervisor.start_link(children, strategy: :one_for_one) end end ListenerServer.Supervisor.start GenServer.cast :listener, "Hello" GenServer.cast :listener, {1,2,3} # Crashes the listener server GenServer.cast :listener, "Hello World" GenServer.cast :listener, :quit Elixir Supervision
  • 20. defmodule FirstEventHandler do use GenEvent def handle_event(message, state) do IO.puts "The first event handler was notified: '#{message}'" {:ok, state} end end defmodule SecondEventHandler do use GenEvent def handle_event(message, state) do IO.puts "The second event handler was notified: '#{message}'" {:ok, state} end end GenEvent.start_link(name: :eventmanager) GenEvent.add_handler(:eventmanager, FirstEventHandler, nil) GenEvent.add_handler(:eventmanager, SecondEventHandler, nil) GenEvent.notify(:eventmanager, "Hi there!") Elixir Event Handler
  • 21. defmodule FiniteStateMachine do @behaviour :gen_fsm def start_link(_opts) do :gen_fsm.start_link({:local, FSM}, __MODULE__, [], []) end def next_state() do :gen_fsm.send_event(FSM, :round) end def init(_), do: IO.puts "FSM started"; { :ok, :ping, [] } def ping(:round, state), do: IO.puts "Ping!"; {:next_state, :pong, state} def pong(:round, state), do: IO.puts "Pong!"; {:next_state, :ping, state} end FiniteStateMachine.start_link [] # "FSM started" FiniteStateMachine.next_state # "Ping!" FiniteStateMachine.next_state # "Pong!" FiniteStateMachine.next_state # "Ping!" FiniteStateMachine.next_state # "Pong!" Elixir Finite state machine
  • 22. Elixir OTP Super-Supervisor Supervisor Supervisor Supervisor Worker Worker Worker Worker Worker Worker WorkerHelper Helper Application
  • 23. defmodule ExampleApp do use Application def start(_type, _args) do MainSupervisor.start end end Elixir Elixir application project Build tool mix: > mix new exampleapp mix.exs # Mix project definition file /config/config.exs # Project specific configuration /lib/ # Source code /test/ # Tests suites
  • 24. # Starts the BEAM Erlang Virtual Machine # and the EPMD Erlang Port Mapper Daemon (default port 4369) cluster01> elixir --sname node --no-halt --cookie secret_cookie Elixir Distributed computing: Nodes # Elixir master node (on same subnet as cluster nodes) master> iex --sname masternode --cookie secret_cookie Node.connect :"node@cluster01" # Establish node connection pid = Node.spawn :"node@cluster01", func # Spawn a process Node.self # Name of current node Node.list # List all connected nodes