Advanced Programming – Riding the OTP
Advanced Programming
Riding the OTP
Ken Friis Larsen
kflarsen@diku.dk
Department of Computer Science
University of Copenhagen
October 11, 2018
1 / 17
Today’s Menu
I Library code for making robust servers
I Open Telecom Platform (OTP)
2 / 17
Generic Servers
I Goal: Abstract out the difficult handling of concurrency to a
generic library
I The difficult parts:
I The start–request_reply(/async)–loop pattern
I Registering processes
I Supervisors
I Hot-swapping of code
3 / 17
Basic Server Library
start(Name, Mod) ->
register(Name, spawn(fun() -> loop(Name, Mod, Mod:init())
end)).
request_reply(Pid, Request) ->
Pid ! {self(), Request},
receive
{Pid, Reply} -> Reply
end.
loop(Name, Mod, State) ->
receive
{From, Request} ->
{Reply, State1} = Mod:handle(Request, State),
From ! {Name, Reply},
loop(Name, Mod, State1)
end.
4 / 17
Example: Phonebook Callback Module, 1
-module(pb).
-import(basicserver, [request_reply/2]).
%% Interface
start() -> basicserver:start(phonebook, pb).
add(Contact) -> request_reply(phonebook, {add, Contact}).
list_all() -> request_reply(phonebook, list_all).
update(Contact) -> request_reply(phonebook, {update, Contact}).
5 / 17
Example: Phonebook Callback Module, 2
%% Callback functions
init() -> #{}.
handle({add, {Name, _, _} = Contact}, Contacts) ->
case maps:is_key(Name, Contacts) of
false -> {ok, Contacts#{Name => Contact}};
true -> {{error, Name, is_already_there},
Contacts}
end;
handle(list_all, Contacts) ->
List = maps:to_list(Contacts),
{{ok, lists:map(fun({_, C}) -> C end, List)},
Contacts};
handle({update, {Name, _, _} = Contact}, Contacts) ->
{ok, Contacts#{Name => Contact}}.
6 / 17
Hot Code Swapping
swap_code(Name, Mod) -> request_reply(Name, {swap_code, Mod}).
request_reply(Pid, Request) ->
Pid ! {self(), Request},
receive {Pid, Reply} -> Reply
end.
loop(Name, Mod, State) ->
receive
{From, {swap_code, NewMod}} ->
From ! {Name, ok},
loop(Name, NewMod, State);
{From, Request} ->
{Reply, State1} = Mod:handle(Request, State),
From ! {Name, Reply},
loop(Name, Mod, State1)
end.
7 / 17
What is the behaviour
I For a callback module to work with basicserver and codeswap
it need to export two functions init and handle.
It would be great if someone could help us get that right. . .
I The compiler can help us
I Add the following to basicserver.erl:
-callback init() -> State :: term().
-callback handle(Arg :: term(), State :: term()) ->
{ ok, State :: term() } |
{ {error, Reason :: term()}, State :: term() }.
I Add the following to pb.erl:
-behaviour(basicserver).
8 / 17
Open Telecom Platform (OTP)
I Library(/framework/platform) for building large-scale,
fault-tolerant, distributed applications.
I A central concept is the OTP behaviour
I Some behaviours
I supervisor
I gen_server
I gen_statem (or gen_fsm)
I gen_event
I See proc_lib and sys modules for basic building blocks.
9 / 17
Using gen_server
I Step 1: Decide module name
I Step 2: Write client interface functions
I Step 3: Write the six server callback functions:
I init/1
I handle_call/3
I handle_cast/2
I handle_info/2
I terminate/2
I code_change/3
(you can implement the callback functions by need.)
10 / 17
Supervisors
(Image credit Fred Herbert)
11 / 17
Using gen_statem
I Step 1: Decide module name
I Step 2: Write client interface functions
I Step 3: Write following callback functions:
I init/1
I callback_mode/0 should return state_functions or
handle_event_function
I terminate/3
I code_change/4
I handle_event/4 or some StateName/3functions
12 / 17
Example State Machine: A Door
I A door can be locked or open
Locked Open
press buttons
correct code
timeout 5s
13 / 17
Callback module for gen_statem, part 1
-module(door).
-behaviour(gen_statem).
-export([…]).
start(Code) ->
gen_statem:start({local, door}, door,
lists:reverse(Code), []).
button(Digit) ->
gen_statem:cast(door, {button, Digit}).
stop() ->
gen_statem:stop(door).
14 / 17
Callback module for gen_statem, part 2
locked(cast, {button, Digit}, {SoFar, Code}) ->
beep(Digit),
case [Digit|SoFar] of
Code ->
do_unlock(),
{next_state, open, {[], Code}, 5000};
Incomplete when length(Incomplete)
{next_state, locked, {Incomplete, Code}};
_Wrong ->
thats_not_gonna_do_it(),
{keep_state, {[], Code}}
end.
open(timeout, _, State) ->
do_lock(),
{next_state, locked, State}.
15 / 17
Summary
I To make a robust system we need two parts: one to do the job
and one to take over in case of errors
I Structure your code into the infrastructure parts and the
functional parts.
I Use gen_server for building robust servers.
I Use gen_statem (or gen_fsm) for servers that can be in different
states.
16 / 17
Exam
I One week take-home project (2/11–9/11)
I Hand in via Digital Exam
I Max group size is 1 (one)
I The University has a zero-tolerance policy against exam fraud
(including assisting).
17 / 17