Concurrency, Robustness & 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
- 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